1 /*
   2  * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import com.sun.tools.classfile.ClassFile;
  25 import com.sun.tools.classfile.ConstantPool;
  26 import com.sun.tools.classfile.ConstantPoolException;
  27 import com.sun.tools.classfile.Module_attribute;
  28 import com.sun.tools.javac.util.Pair;
  29 
  30 import java.io.IOException;
  31 import java.lang.annotation.Retention;
  32 import java.lang.annotation.RetentionPolicy;
  33 import java.lang.reflect.Method;
  34 import java.nio.file.Files;
  35 import java.nio.file.Path;
  36 import java.nio.file.Paths;
  37 import java.util.ArrayList;
  38 import java.util.Collections;
  39 import java.util.HashMap;
  40 import java.util.List;
  41 import java.util.Map;
  42 import java.util.regex.Pattern;
  43 import java.util.stream.Collectors;
  44 
  45 import toolbox.JavacTask;
  46 import toolbox.Task;
  47 import toolbox.ToolBox;
  48 
  49 public class ModuleTestBase {
  50     protected final ToolBox tb = new ToolBox();
  51     private final TestResult tr = new TestResult();
  52 
  53 
  54     protected void run() throws Exception {
  55         boolean noTests = true;
  56         for (Method method : this.getClass().getMethods()) {
  57             if (method.isAnnotationPresent(Test.class)) {
  58                 noTests = false;
  59                 try {
  60                     tr.addTestCase(method.getName());
  61                     method.invoke(this, Paths.get(method.getName()));
  62                 } catch (Throwable th) {
  63                     tr.addFailure(th);
  64                 }
  65             }
  66         }
  67         if (noTests) throw new AssertionError("Tests are not found.");
  68         tr.checkStatus();
  69     }
  70 
  71     protected void testModuleAttribute(Path modulePath, ModuleDescriptor moduleDescriptor) throws Exception {
  72         ClassFile classFile = ClassFile.read(modulePath.resolve("module-info.class"));
  73         Module_attribute moduleAttribute = (Module_attribute) classFile.getAttribute("Module");
  74         ConstantPool constantPool = classFile.constant_pool;
  75 
  76         testRequires(moduleDescriptor, moduleAttribute, constantPool);
  77         testExports(moduleDescriptor, moduleAttribute, constantPool);
  78         testProvides(moduleDescriptor, moduleAttribute, constantPool);
  79         testUses(moduleDescriptor, moduleAttribute, constantPool);
  80     }
  81 
  82     private void testRequires(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
  83         tr.checkEquals(module.requires_count, moduleDescriptor.requires.size(), "Wrong amount of requires.");
  84 
  85         List<Pair<String, Integer>> actualRequires = new ArrayList<>();
  86         for (Module_attribute.RequiresEntry require : module.requires) {
  87             actualRequires.add(Pair.of(
  88                     require.getRequires(constantPool), require.requires_flags));
  89         }
  90         tr.checkContains(actualRequires, moduleDescriptor.requires, "Lists of requires don't match");
  91     }
  92 
  93     private void testExports(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPool.InvalidIndex, ConstantPool.UnexpectedEntry {
  94         tr.checkEquals(module.exports_count, moduleDescriptor.exports.size(), "Wrong amount of exports.");
  95         for (Module_attribute.ExportsEntry export : module.exports) {
  96             String pkg = constantPool.getUTF8Value(export.exports_index);
  97             if (tr.checkTrue(moduleDescriptor.exports.containsKey(pkg), "Unexpected export " + pkg)) {
  98                 List<String> expectedTo = moduleDescriptor.exports.get(pkg);
  99                 tr.checkEquals(export.exports_to_count, expectedTo.size(), "Wrong amount of exports to");
 100                 List<String> actualTo = new ArrayList<>();
 101                 for (int toIdx : export.exports_to_index) {
 102                     actualTo.add(constantPool.getUTF8Value(toIdx));
 103                 }
 104                 tr.checkContains(actualTo, expectedTo, "Lists of \"exports to\" don't match.");
 105             }
 106         }
 107     }
 108 
 109     private void testUses(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
 110         tr.checkEquals(module.uses_count, moduleDescriptor.uses.size(), "Wrong amount of uses.");
 111         List<String> actualUses = new ArrayList<>();
 112         for (int usesIdx : module.uses_index) {
 113             String uses = constantPool.getClassInfo(usesIdx).getBaseName().replace('/', '.');
 114             actualUses.add(uses);
 115         }
 116         tr.checkContains(actualUses, moduleDescriptor.uses, "Lists of uses don't match");
 117     }
 118 
 119     private void testProvides(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
 120         tr.checkEquals(module.provides_count, moduleDescriptor.provides.size(), "Wrong amount of provides.");
 121         List<Pair<String, String>> actualProvides = new ArrayList<>();
 122         for (Module_attribute.ProvidesEntry provide : module.provides) {
 123             String provides = constantPool.getClassInfo(provide.provides_index).getBaseName().replace('/', '.');
 124             String with = constantPool.getClassInfo(provide.with_index).getBaseName().replace('/', '.');
 125             actualProvides.add(Pair.of(provides, with));
 126         }
 127         tr.checkContains(actualProvides, moduleDescriptor.provides, "Lists of provides don't match");
 128     }
 129 
 130     protected void compile(Path base) throws IOException {
 131         new JavacTask(tb)
 132                 .files(findJavaFiles(base))
 133                 .run(Task.Expect.SUCCESS)
 134                 .writeAll();
 135     }
 136 
 137     private static Path[] findJavaFiles(Path src) throws IOException {
 138         return Files.find(src, Integer.MAX_VALUE, (path, attr) -> path.toString().endsWith(".java"))
 139                 .toArray(Path[]::new);
 140     }
 141 
 142     @Retention(RetentionPolicy.RUNTIME)
 143     @interface Test {
 144     }
 145 
 146     class ModuleDescriptor {
 147 
 148         private final String name;
 149         //pair is name of module and flag(public,mandated,synthetic)
 150         private final List<Pair<String, Integer>> requires = new ArrayList<>();
 151 
 152         {
 153             requires.add(new Pair<>("java.base", Module_attribute.ACC_MANDATED));
 154         }
 155 
 156         private final Map<String, List<String>> exports = new HashMap<>();
 157 
 158         //List of service and implementation
 159         private final List<Pair<String, String>> provides = new ArrayList<>();
 160         private final List<String> uses = new ArrayList<>();
 161 
 162         private static final String LINE_END = ";\n";
 163 
 164         StringBuilder content = new StringBuilder("module ");
 165 
 166         public ModuleDescriptor(String moduleName) {
 167             this.name = moduleName;
 168             content.append(name).append('{').append('\n');
 169         }
 170 
 171         public ModuleDescriptor requires(String... requires) {
 172             for (String require : requires) {
 173                 this.requires.add(Pair.of(require, 0));
 174                 content.append("    requires ").append(require).append(LINE_END);
 175             }
 176             return this;
 177         }
 178 
 179         public ModuleDescriptor requiresPublic(String... requiresPublic) {
 180             for (String require : requiresPublic) {
 181                 this.requires.add(new Pair<>(require, Module_attribute.ACC_PUBLIC));
 182                 content.append("    requires public ").append(require).append(LINE_END);
 183             }
 184             return this;
 185         }
 186 
 187         public ModuleDescriptor exports(String... exports) {
 188             for (String export : exports) {
 189                 this.exports.putIfAbsent(export, new ArrayList<>());
 190                 content.append("    exports ").append(export).append(LINE_END);
 191             }
 192             return this;
 193         }
 194 
 195         public ModuleDescriptor exportsTo(String exports, String to) {
 196             List<String> tos = Pattern.compile(",")
 197                     .splitAsStream(to)
 198                     .map(String::trim)
 199                     .collect(Collectors.toList());
 200             this.exports.computeIfAbsent(exports, k -> new ArrayList<>()).addAll(tos);
 201             content.append("    exports ").append(exports).append(" to ").append(to).append(LINE_END);
 202             return this;
 203         }
 204 
 205         public ModuleDescriptor provides(String provides, String with) {
 206             this.provides.add(Pair.of(provides, with));
 207             content.append("    provides ").append(provides).append(" with ").append(with).append(LINE_END);
 208             return this;
 209         }
 210 
 211         public ModuleDescriptor uses(String... uses) {
 212             Collections.addAll(this.uses, uses);
 213             for (String use : uses) {
 214                 content.append("    uses ").append(use).append(LINE_END);
 215             }
 216             return this;
 217         }
 218 
 219         public ModuleDescriptor write(Path path) throws IOException {
 220             String src = content.append('}').toString();
 221 
 222             tb.createDirectories(path);
 223             tb.writeJavaFiles(path, src);
 224             return this;
 225         }
 226     }
 227 }