1/* 2 * Copyright (c) 2015, 2017, 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 24import com.sun.tools.classfile.ClassFile; 25import com.sun.tools.classfile.ConstantPool; 26import com.sun.tools.classfile.ConstantPoolException; 27import com.sun.tools.classfile.Module_attribute; 28 29import java.io.IOException; 30import java.lang.annotation.Retention; 31import java.lang.annotation.RetentionPolicy; 32import java.lang.reflect.Method; 33import java.nio.file.Files; 34import java.nio.file.Path; 35import java.nio.file.Paths; 36import java.util.ArrayList; 37import java.util.Arrays; 38import java.util.HashMap; 39import java.util.LinkedHashMap; 40import java.util.List; 41import java.util.Map; 42import java.util.Objects; 43import java.util.regex.Pattern; 44import java.util.stream.Collectors; 45 46import toolbox.JavacTask; 47import toolbox.Task; 48import toolbox.ToolBox; 49 50public 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 testModuleName(moduleDescriptor, moduleAttribute, constantPool); 77 testModuleFlags(moduleDescriptor, moduleAttribute); 78 testRequires(moduleDescriptor, moduleAttribute, constantPool); 79 testExports(moduleDescriptor, moduleAttribute, constantPool); 80 testOpens(moduleDescriptor, moduleAttribute, constantPool); 81 testProvides(moduleDescriptor, moduleAttribute, constantPool); 82 testUses(moduleDescriptor, moduleAttribute, constantPool); 83 } 84 85 private void testModuleName(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException { 86 tr.checkEquals(constantPool.getModuleInfo(module.module_name).getName(), moduleDescriptor.name, "Unexpected module name"); 87 } 88 89 private void testModuleFlags(ModuleDescriptor moduleDescriptor, Module_attribute module) { 90 tr.checkEquals(module.module_flags, moduleDescriptor.flags, "Unexpected module flags"); 91 } 92 93 private void testRequires(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException { 94 tr.checkEquals(module.requires_count, moduleDescriptor.requires.size(), "Wrong amount of requires."); 95 96 List<Requires> actualRequires = new ArrayList<>(); 97 for (Module_attribute.RequiresEntry require : module.requires) { 98 actualRequires.add(new Requires( 99 require.getRequires(constantPool), 100 require.requires_flags)); 101 } 102 tr.checkContains(actualRequires, moduleDescriptor.requires, "Lists of requires don't match"); 103 } 104 105 private void testExports(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException { 106 tr.checkEquals(module.exports_count, moduleDescriptor.exports.size(), "Wrong amount of exports."); 107 for (Module_attribute.ExportsEntry export : module.exports) { 108 String pkg = constantPool.getPackageInfo(export.exports_index).getName(); 109 if (tr.checkTrue(moduleDescriptor.exports.containsKey(pkg), "Unexpected export " + pkg)) { 110 Export expectedExport = moduleDescriptor.exports.get(pkg); 111 tr.checkEquals(expectedExport.mask, export.exports_flags, "Wrong export flags"); 112 List<String> expectedTo = expectedExport.to; 113 tr.checkEquals(export.exports_to_count, expectedTo.size(), "Wrong amount of exports to"); 114 List<String> actualTo = new ArrayList<>(); 115 for (int toIdx : export.exports_to_index) { 116 actualTo.add(constantPool.getModuleInfo(toIdx).getName()); 117 } 118 tr.checkContains(actualTo, expectedTo, "Lists of \"exports to\" don't match."); 119 } 120 } 121 } 122 123 private void testOpens(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException { 124 tr.checkEquals(module.opens_count, moduleDescriptor.opens.size(), "Wrong amount of opens."); 125 for (Module_attribute.OpensEntry open : module.opens) { 126 String pkg = constantPool.getPackageInfo(open.opens_index).getName(); 127 if (tr.checkTrue(moduleDescriptor.opens.containsKey(pkg), "Unexpected open " + pkg)) { 128 Open expectedOpen = moduleDescriptor.opens.get(pkg); 129 tr.checkEquals(expectedOpen.mask, open.opens_flags, "Wrong open flags"); 130 List<String> expectedTo = expectedOpen.to; 131 tr.checkEquals(open.opens_to_count, expectedTo.size(), "Wrong amount of opens to"); 132 List<String> actualTo = new ArrayList<>(); 133 for (int toIdx : open.opens_to_index) { 134 actualTo.add(constantPool.getModuleInfo(toIdx).getName()); 135 } 136 tr.checkContains(actualTo, expectedTo, "Lists of \"opens to\" don't match."); 137 } 138 } 139 } 140 141 private void testUses(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException { 142 tr.checkEquals(module.uses_count, moduleDescriptor.uses.size(), "Wrong amount of uses."); 143 List<String> actualUses = new ArrayList<>(); 144 for (int usesIdx : module.uses_index) { 145 String uses = constantPool.getClassInfo(usesIdx).getBaseName(); 146 actualUses.add(uses); 147 } 148 tr.checkContains(actualUses, moduleDescriptor.uses, "Lists of uses don't match"); 149 } 150 151 private void testProvides(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException { 152 int moduleProvidesCount = Arrays.asList(module.provides).stream() 153 .mapToInt(e -> e.with_index.length) 154 .sum(); 155 int moduleDescriptorProvidesCount = moduleDescriptor.provides.values().stream() 156 .mapToInt(impls -> impls.size()) 157 .sum(); 158 tr.checkEquals(moduleProvidesCount, moduleDescriptorProvidesCount, "Wrong amount of provides."); 159 Map<String, List<String>> actualProvides = new HashMap<>(); 160 for (Module_attribute.ProvidesEntry provide : module.provides) { 161 String provides = constantPool.getClassInfo(provide.provides_index).getBaseName(); 162 List<String> impls = new ArrayList<>(); 163 for (int i = 0; i < provide.with_count; i++) { 164 String with = constantPool.getClassInfo(provide.with_index[i]).getBaseName(); 165 impls.add(with); 166 } 167 actualProvides.put(provides, impls); 168 } 169 tr.checkContains(actualProvides.entrySet(), moduleDescriptor.provides.entrySet(), "Lists of provides don't match"); 170 } 171 172 protected void compile(Path base, String... options) throws IOException { 173 new JavacTask(tb) 174 .options(options) 175 .files(findJavaFiles(base)) 176 .run(Task.Expect.SUCCESS) 177 .writeAll(); 178 } 179 180 private static Path[] findJavaFiles(Path src) throws IOException { 181 return Files.find(src, Integer.MAX_VALUE, (path, attr) -> path.toString().endsWith(".java")) 182 .toArray(Path[]::new); 183 } 184 185 @Retention(RetentionPolicy.RUNTIME) 186 @interface Test { 187 } 188 189 interface Mask { 190 int getMask(); 191 } 192 193 public enum ModuleFlag implements Mask { 194 OPEN("open", Module_attribute.ACC_OPEN); 195 196 private final String token; 197 private final int mask; 198 199 ModuleFlag(String token, int mask) { 200 this.token = token; 201 this.mask = mask; 202 } 203 204 @Override 205 public int getMask() { 206 return mask; 207 } 208 } 209 210 public enum RequiresFlag implements Mask { 211 TRANSITIVE("transitive", Module_attribute.ACC_TRANSITIVE), 212 STATIC("static", Module_attribute.ACC_STATIC_PHASE), 213 MANDATED("", Module_attribute.ACC_MANDATED); 214 215 private final String token; 216 private final int mask; 217 218 RequiresFlag(String token, int mask) { 219 this.token = token; 220 this.mask = mask; 221 } 222 223 @Override 224 public int getMask() { 225 return mask; 226 } 227 } 228 229 public enum ExportsFlag implements Mask { 230 SYNTHETIC("", Module_attribute.ACC_SYNTHETIC); 231 232 private final String token; 233 private final int mask; 234 235 ExportsFlag(String token, int mask) { 236 this.token = token; 237 this.mask = mask; 238 } 239 240 @Override 241 public int getMask() { 242 return mask; 243 } 244 } 245 246 public enum OpensFlag implements Mask { 247 SYNTHETIC("", Module_attribute.ACC_SYNTHETIC); 248 249 private final String token; 250 private final int mask; 251 252 OpensFlag(String token, int mask) { 253 this.token = token; 254 this.mask = mask; 255 } 256 257 @Override 258 public int getMask() { 259 return mask; 260 } 261 } 262 263 private class Export { 264 private final String pkg; 265 private final int mask; 266 private final List<String> to = new ArrayList<>(); 267 268 Export(String pkg, int mask) { 269 this.pkg = pkg; 270 this.mask = mask; 271 } 272 } 273 274 private class Open { 275 private final String pkg; 276 private final int mask; 277 private final List<String> to = new ArrayList<>(); 278 279 Open(String pkg, int mask) { 280 this.pkg = pkg; 281 this.mask = mask; 282 } 283 } 284 285 private class Requires { 286 private final String module; 287 private final int mask; 288 289 Requires(String module, int mask) { 290 this.module = module; 291 this.mask = mask; 292 } 293 294 @Override 295 public boolean equals(Object o) { 296 if (this == o) return true; 297 if (o == null || getClass() != o.getClass()) return false; 298 Requires requires = (Requires) o; 299 return mask == requires.mask && 300 Objects.equals(module, requires.module); 301 } 302 303 @Override 304 public int hashCode() { 305 return Objects.hash(module, mask); 306 } 307 } 308 309 protected class ModuleDescriptor { 310 311 private final String name; 312 private final int flags; 313 314 private final List<Requires> requires = new ArrayList<>(); 315 316 { 317 requires.add(new Requires("java.base", computeMask(RequiresFlag.MANDATED))); 318 } 319 320 private final Map<String, Export> exports = new HashMap<>(); 321 private final Map<String, Open> opens = new HashMap<>(); 322 323 //List of service and implementation 324 private final Map<String, List<String>> provides = new LinkedHashMap<>(); 325 private final List<String> uses = new ArrayList<>(); 326 327 private static final String LINE_END = ";\n"; 328 329 StringBuilder content = new StringBuilder(""); 330 331 public ModuleDescriptor(String moduleName, ModuleFlag... flags) { 332 this.name = moduleName; 333 this.flags = computeMask(flags); 334 for (ModuleFlag flag : flags) { 335 content.append(flag.token).append(" "); 336 } 337 content.append("module ").append(moduleName).append('{').append('\n'); 338 } 339 340 public ModuleDescriptor requires(String module) { 341 this.requires.add(new Requires(module, 0)); 342 content.append(" requires ").append(module).append(LINE_END); 343 344 return this; 345 } 346 347 public ModuleDescriptor requires(String module, RequiresFlag... flags) { 348 this.requires.add(new Requires(module, computeMask(flags))); 349 350 content.append(" requires "); 351 for (RequiresFlag flag : flags) { 352 content.append(flag.token).append(" "); 353 } 354 content.append(module).append(LINE_END); 355 356 return this; 357 } 358 359 public ModuleDescriptor exports(String pkg, ExportsFlag... flags) { 360 this.exports.put(toInternalForm(pkg), new Export(toInternalForm(pkg), computeMask(flags))); 361 content.append(" exports "); 362 for (ExportsFlag flag : flags) { 363 content.append(flag.token).append(" "); 364 } 365 content.append(pkg).append(LINE_END); 366 return this; 367 } 368 369 public ModuleDescriptor exportsTo(String pkg, String to, ExportsFlag... flags) { 370 List<String> tos = Pattern.compile(",") 371 .splitAsStream(to) 372 .map(String::trim) 373 .collect(Collectors.toList()); 374 this.exports.compute(toInternalForm(pkg), (k,v) -> new Export(k, computeMask(flags))) 375 .to.addAll(tos); 376 377 content.append(" exports "); 378 for (ExportsFlag flag : flags) { 379 content.append(flag.token).append(" "); 380 } 381 content.append(pkg).append(" to ").append(to).append(LINE_END); 382 return this; 383 } 384 385 public ModuleDescriptor opens(String pkg, OpensFlag... flags) { 386 this.opens.put(toInternalForm(pkg), new Open(toInternalForm(pkg), computeMask(flags))); 387 content.append(" opens "); 388 for (OpensFlag flag : flags) { 389 content.append(flag.token).append(" "); 390 } 391 content.append(pkg).append(LINE_END); 392 return this; 393 } 394 395 public ModuleDescriptor opensTo(String pkg, String to, OpensFlag... flags) { 396 List<String> tos = Pattern.compile(",") 397 .splitAsStream(to) 398 .map(String::trim) 399 .collect(Collectors.toList()); 400 this.opens.compute(toInternalForm(pkg), (k,v) -> new Open(toInternalForm(k), computeMask(flags))) 401 .to.addAll(tos); 402 403 content.append(" opens "); 404 for (OpensFlag flag : flags) { 405 content.append(flag.token).append(" "); 406 } 407 content.append(pkg).append(" to ").append(to).append(LINE_END); 408 return this; 409 } 410 411 public ModuleDescriptor provides(String provides, String... with) { 412 List<String> impls = Arrays.stream(with) 413 .map(this::toInternalForm) 414 .collect(Collectors.toList()); 415 this.provides.put(toInternalForm(provides), impls); 416 content.append(" provides ") 417 .append(provides) 418 .append(" with ") 419 .append(String.join(",", with)) 420 .append(LINE_END); 421 return this; 422 } 423 424 public ModuleDescriptor uses(String... uses) { 425 for (String use : uses) { 426 this.uses.add(toInternalForm(use)); 427 content.append(" uses ").append(use).append(LINE_END); 428 } 429 return this; 430 } 431 432 public ModuleDescriptor write(Path path) throws IOException { 433 String src = content.append('}').toString(); 434 435 tb.createDirectories(path); 436 tb.writeJavaFiles(path, src); 437 return this; 438 } 439 440 private String toInternalForm(String name) { 441 return name.replace('.', '/'); 442 } 443 444 private int computeMask(Mask... masks) { 445 return Arrays.stream(masks) 446 .map(Mask::getMask) 447 .reduce((a, b) -> a | b) 448 .orElseGet(() -> 0); 449 } 450 } 451} 452