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.Arrays;
  39 import java.util.Collections;
  40 import java.util.HashMap;
  41 import java.util.List;
  42 import java.util.Map;
  43 import java.util.regex.Pattern;
  44 import java.util.stream.Collectors;
  45 
  46 import toolbox.JavacTask;
  47 import toolbox.Task;
  48 import toolbox.ToolBox;
  49 
  50 public class ModuleTestBase {
  51     protected final ToolBox tb = new ToolBox();
  52     private final TestResult tr = new TestResult();
  53 
  54 
  55     protected void run() throws Exception {
  56         boolean noTests = true;
  57         for (Method method : this.getClass().getMethods()) {
  58             if (method.isAnnotationPresent(Test.class)) {
  59                 noTests = false;
  60                 try {
  61                     tr.addTestCase(method.getName());
  62                     method.invoke(this, Paths.get(method.getName()));
  63                 } catch (Throwable th) {
  64                     tr.addFailure(th);
  65                 }
  66             }
  67         }
  68         if (noTests) throw new AssertionError("Tests are not found.");
  69         tr.checkStatus();
  70     }
  71 
  72     protected void testModuleAttribute(Path modulePath, ModuleDescriptor moduleDescriptor) throws Exception {
  73         ClassFile classFile = ClassFile.read(modulePath.resolve("module-info.class"));
  74         Module_attribute moduleAttribute = (Module_attribute) classFile.getAttribute("Module");
  75         ConstantPool constantPool = classFile.constant_pool;
  76 
  77         testRequires(moduleDescriptor, moduleAttribute, constantPool);
  78         testExports(moduleDescriptor, moduleAttribute, constantPool);
  79         testProvides(moduleDescriptor, moduleAttribute, constantPool);
  80         testUses(moduleDescriptor, moduleAttribute, constantPool);
  81     }
  82 
  83     private void testRequires(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
  84         tr.checkEquals(module.requires_count, moduleDescriptor.requires.size(), "Wrong amount of requires.");
  85 
  86         List<Pair<String, Integer>> actualRequires = new ArrayList<>();
  87         for (Module_attribute.RequiresEntry require : module.requires) {
  88             actualRequires.add(Pair.of(
  89                     require.getRequires(constantPool), require.requires_flags));
  90         }
  91         tr.checkContains(actualRequires, moduleDescriptor.requires, "Lists of requires don't match");
  92     }
  93 
  94     private void testExports(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPool.InvalidIndex, ConstantPool.UnexpectedEntry {
  95         tr.checkEquals(module.exports_count, moduleDescriptor.exports.size(), "Wrong amount of exports.");
  96         for (Module_attribute.ExportsEntry export : module.exports) {
  97             String pkg = constantPool.getUTF8Value(export.exports_index);
  98             if (tr.checkTrue(moduleDescriptor.exports.containsKey(pkg), "Unexpected export " + pkg)) {
  99                 List<String> expectedTo = moduleDescriptor.exports.get(pkg);
 100                 tr.checkEquals(export.exports_to_count, expectedTo.size(), "Wrong amount of exports to");
 101                 List<String> actualTo = new ArrayList<>();
 102                 for (int toIdx : export.exports_to_index) {
 103                     actualTo.add(constantPool.getUTF8Value(toIdx));
 104                 }
 105                 tr.checkContains(actualTo, expectedTo, "Lists of \"exports to\" don't match.");
 106             }
 107         }
 108     }
 109 
 110     private void testUses(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
 111         tr.checkEquals(module.uses_count, moduleDescriptor.uses.size(), "Wrong amount of uses.");
 112         List<String> actualUses = new ArrayList<>();
 113         for (int usesIdx : module.uses_index) {
 114             String uses = constantPool.getClassInfo(usesIdx).getBaseName().replace('/', '.');
 115             actualUses.add(uses);
 116         }
 117         tr.checkContains(actualUses, moduleDescriptor.uses, "Lists of uses don't match");
 118     }
 119 
 120     private void testProvides(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
 121         tr.checkEquals(module.provides_count, moduleDescriptor.provides.size(), "Wrong amount of provides.");
 122         List<Pair<String, String>> actualProvides = new ArrayList<>();
 123         for (Module_attribute.ProvidesEntry provide : module.provides) {
 124             String provides = constantPool.getClassInfo(provide.provides_index).getBaseName().replace('/', '.');
 125             String with = constantPool.getClassInfo(provide.with_index).getBaseName().replace('/', '.');
 126             actualProvides.add(Pair.of(provides, with));
 127         }
 128         tr.checkContains(actualProvides, moduleDescriptor.provides, "Lists of provides don't match");
 129     }
 130 
 131     protected void compile(Path base, String... options) throws IOException {
 132         new JavacTask(tb)
 133                 .options(options)
 134                 .files(findJavaFiles(base))
 135                 .run(Task.Expect.SUCCESS)
 136                 .writeAll();
 137     }
 138 
 139     private static Path[] findJavaFiles(Path src) throws IOException {
 140         return Files.find(src, Integer.MAX_VALUE, (path, attr) -> path.toString().endsWith(".java"))
 141                 .toArray(Path[]::new);
 142     }
 143 
 144     @Retention(RetentionPolicy.RUNTIME)
 145     @interface Test {
 146     }
 147 
 148     class ModuleDescriptor {
 149 
 150         private final String name;
 151         //pair is name of module and flag(public,mandated,synthetic)
 152         private final List<Pair<String, Integer>> requires = new ArrayList<>();
 153 
 154         {
 155             requires.add(new Pair<>("java.base", Module_attribute.ACC_MANDATED));
 156         }
 157 
 158         private final Map<String, List<String>> exports = new HashMap<>();
 159 
 160         //List of service and implementation
 161         private final List<Pair<String, String>> provides = new ArrayList<>();
 162         private final List<String> uses = new ArrayList<>();
 163 
 164         private static final String LINE_END = ";\n";
 165 
 166         StringBuilder content = new StringBuilder("module ");
 167 
 168         public ModuleDescriptor(String moduleName) {
 169             this.name = moduleName;
 170             content.append(name).append('{').append('\n');
 171         }
 172 
 173         public ModuleDescriptor requires(String... requires) {
 174             for (String require : requires) {
 175                 this.requires.add(Pair.of(require, 0));
 176                 content.append("    requires ").append(require).append(LINE_END);
 177             }
 178             return this;
 179         }
 180 
 181         public ModuleDescriptor requiresPublic(String... requiresPublic) {
 182             for (String require : requiresPublic) {
 183                 this.requires.add(new Pair<>(require, Module_attribute.ACC_PUBLIC));
 184                 content.append("    requires public ").append(require).append(LINE_END);
 185             }
 186             return this;
 187         }
 188 
 189         public ModuleDescriptor exports(String... exports) {
 190             for (String export : exports) {
 191                 this.exports.putIfAbsent(export, new ArrayList<>());
 192                 content.append("    exports ").append(export).append(LINE_END);
 193             }
 194             return this;
 195         }
 196 
 197         public ModuleDescriptor exportsTo(String exports, String to) {
 198             List<String> tos = Pattern.compile(",")
 199                     .splitAsStream(to)
 200                     .map(String::trim)
 201                     .collect(Collectors.toList());
 202             this.exports.computeIfAbsent(exports, k -> new ArrayList<>()).addAll(tos);
 203             content.append("    exports ").append(exports).append(" to ").append(to).append(LINE_END);
 204             return this;
 205         }
 206 
 207         public ModuleDescriptor provides(String provides, String with) {
 208             this.provides.add(Pair.of(provides, with));
 209             content.append("    provides ").append(provides).append(" with ").append(with).append(LINE_END);
 210             return this;
 211         }
 212 
 213         public ModuleDescriptor uses(String... uses) {
 214             Collections.addAll(this.uses, uses);
 215             for (String use : uses) {
 216                 content.append("    uses ").append(use).append(LINE_END);
 217             }
 218             return this;
 219         }
 220 
 221         public ModuleDescriptor write(Path path) throws IOException {
 222             String src = content.append('}').toString();
 223 
 224             tb.createDirectories(path);
 225             tb.writeJavaFiles(path, src);
 226             return this;
 227         }
 228     }
 229 }