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 }