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 24/* 25 * @test 26 * @bug 8173777 27 * @summary tests for multi-module mode compilation 28 * @library /tools/lib 29 * @modules 30 * jdk.compiler/com.sun.tools.javac.api 31 * jdk.compiler/com.sun.tools.javac.code 32 * jdk.compiler/com.sun.tools.javac.main 33 * jdk.compiler/com.sun.tools.javac.processing 34 * @build toolbox.ToolBox toolbox.JavacTask toolbox.ModuleBuilder ModuleTestBase 35 * @run main CompileModulePatchTest 36 */ 37 38import java.nio.file.Files; 39import java.nio.file.Path; 40import java.util.Arrays; 41import java.util.List; 42import java.util.Set; 43import java.util.stream.Collectors; 44 45import javax.annotation.processing.AbstractProcessor; 46import javax.annotation.processing.RoundEnvironment; 47import javax.annotation.processing.SupportedAnnotationTypes; 48import javax.lang.model.SourceVersion; 49import javax.lang.model.element.ModuleElement; 50import javax.lang.model.element.TypeElement; 51import javax.lang.model.util.Elements; 52 53import com.sun.tools.javac.code.Symtab; 54import com.sun.tools.javac.processing.JavacProcessingEnvironment; 55import toolbox.JavacTask; 56import toolbox.ModuleBuilder; 57import toolbox.Task; 58import toolbox.Task.Expect; 59 60public class CompileModulePatchTest extends ModuleTestBase { 61 62 public static void main(String... args) throws Exception { 63 new CompileModulePatchTest().runTests(); 64 } 65 66 @Test 67 public void testCorrectModulePatch(Path base) throws Exception { 68 //note: avoiding use of java.base, as that gets special handling on some places: 69 Path src = base.resolve("src"); 70 tb.writeJavaFiles(src, "package javax.lang.model.element; public interface Extra extends Element { }"); 71 Path classes = base.resolve("classes"); 72 tb.createDirectories(classes); 73 74 String log = new JavacTask(tb) 75 .options("--patch-module", "java.compiler=" + src.toString()) 76 .outdir(classes) 77 .files(findJavaFiles(src)) 78 .run() 79 .writeAll() 80 .getOutput(Task.OutputKind.DIRECT); 81 82 if (!log.isEmpty()) 83 throw new Exception("expected output not found: " + log); 84 } 85 86 @Test 87 public void testCorrectModulePatchMultiModule(Path base) throws Exception { 88 //note: avoiding use of java.base, as that gets special handling on some places: 89 Path src = base.resolve("src"); 90 Path m1 = src.resolve("m1"); 91 tb.writeJavaFiles(m1, "package javax.lang.model.element; public interface Extra extends Element { }"); 92 Path m2 = src.resolve("m2"); 93 tb.writeJavaFiles(m2, "package com.sun.source.tree; public interface Extra extends Tree { }"); 94 Path classes = base.resolve("classes"); 95 tb.createDirectories(classes); 96 97 String log = new JavacTask(tb) 98 .options("--patch-module", "java.compiler=" + m1.toString(), 99 "--patch-module", "jdk.compiler=" + m2.toString(), 100 "--module-source-path", "dummy") 101 .outdir(classes) 102 .files(findJavaFiles(src)) 103 .run() 104 .writeAll() 105 .getOutput(Task.OutputKind.DIRECT); 106 107 if (!log.isEmpty()) 108 throw new Exception("expected output not found: " + log); 109 110 checkFileExists(classes, "java.compiler/javax/lang/model/element/Extra.class"); 111 checkFileExists(classes, "jdk.compiler/com/sun/source/tree/Extra.class"); 112 } 113 114 @Test 115 public void testCorrectModulePatchMultiModule2(Path base) throws Exception { 116 //note: avoiding use of java.base, as that gets special handling on some places: 117 Path src = base.resolve("src"); 118 Path m1 = src.resolve("m1"); 119 tb.writeJavaFiles(m1, 120 "package javax.lang.model.element; public interface Extra extends Element { }"); 121 Path m2 = src.resolve("m2"); 122 tb.writeJavaFiles(m2, 123 "package com.sun.source.tree; public interface Extra extends Tree { }"); 124 Path msp = base.resolve("msp"); 125 Path m3 = msp.resolve("m3x"); 126 tb.writeJavaFiles(m3, 127 "module m3x { }", 128 "package m3; public class Test { }"); 129 Path m4 = msp.resolve("m4x"); 130 tb.writeJavaFiles(m4, 131 "module m4x { }", 132 "package m4; public class Test { }"); 133 Path classes = base.resolve("classes"); 134 tb.createDirectories(classes); 135 136 String log = new JavacTask(tb) 137 .options("--patch-module", "java.compiler=" + m1.toString(), 138 "--patch-module", "jdk.compiler=" + m2.toString(), 139 "--module-source-path", msp.toString()) 140 .outdir(classes) 141 .files(findJavaFiles(src, msp)) 142 .run() 143 .writeAll() 144 .getOutput(Task.OutputKind.DIRECT); 145 146 if (!log.isEmpty()) 147 throw new Exception("expected output not found: " + log); 148 149 checkFileExists(classes, "java.compiler/javax/lang/model/element/Extra.class"); 150 checkFileExists(classes, "jdk.compiler/com/sun/source/tree/Extra.class"); 151 checkFileExists(classes, "m3x/m3/Test.class"); 152 checkFileExists(classes, "m4x/m4/Test.class"); 153 } 154 155 @Test 156 public void testPatchModuleModuleSourcePathConflict(Path base) throws Exception { 157 //note: avoiding use of java.base, as that gets special handling on some places: 158 Path src = base.resolve("src"); 159 Path m1 = src.resolve("m1x"); 160 tb.writeJavaFiles(m1, 161 "module m1x { }", 162 "package m1; public class Test { }"); 163 Path m2 = src.resolve("m2x"); 164 tb.writeJavaFiles(m2, 165 "module m2x { }", 166 "package m2; public class Test { }"); 167 Path classes = base.resolve("classes"); 168 tb.createDirectories(classes); 169 170 List<String> log = new JavacTask(tb) 171 .options("--patch-module", "m1x=" + m2.toString(), 172 "--module-source-path", src.toString(), 173 "-XDrawDiagnostics") 174 .outdir(classes) 175 .files(findJavaFiles(src.resolve("m1x").resolve("m1"), 176 src.resolve("m2x").resolve("m2"))) 177 .run(Expect.FAIL) 178 .writeAll() 179 .getOutputLines(Task.OutputKind.DIRECT); 180 181 List<String> expectedOut = Arrays.asList( 182 "Test.java:1:1: compiler.err.file.patched.and.msp: m1x, m2x", 183 "module-info.java:1:1: compiler.err.module.name.mismatch: m2x, m1x", 184 "- compiler.err.cant.access: m1x.module-info, (compiler.misc.cant.resolve.modules)", 185 "3 errors" 186 ); 187 188 if (!expectedOut.equals(log)) 189 throw new Exception("expected output not found: " + log); 190 } 191 192 @Test 193 public void testSourcePath(Path base) throws Exception { 194 //note: avoiding use of java.base, as that gets special handling on some places: 195 Path src = base.resolve("src"); 196 tb.writeJavaFiles(src, "package javax.lang.model.element; public interface Extra extends Element, Other { }"); 197 Path srcPath = base.resolve("src-path"); 198 tb.writeJavaFiles(srcPath, "package javax.lang.model.element; interface Other { }"); 199 Path classes = base.resolve("classes"); 200 tb.createDirectories(classes); 201 202 List<String> log = new JavacTask(tb) 203 .options("--patch-module", "java.compiler=" + src.toString(), 204 "-sourcepath", srcPath.toString(), 205 "-XDrawDiagnostics") 206 .outdir(classes) 207 .files(src.resolve("javax/lang/model/element/Extra.java")) 208 .run(Expect.FAIL) 209 .writeAll() 210 .getOutputLines(Task.OutputKind.DIRECT); 211 212 List<String> expectedOut = Arrays.asList( 213 "Extra.java:1:75: compiler.err.cant.resolve: kindname.class, Other, , ", 214 "1 error" 215 ); 216 217 if (!expectedOut.equals(log)) 218 throw new Exception("expected output not found: " + log); 219 } 220 221 @Test 222 public void testClassPath(Path base) throws Exception { 223 Path cpSrc = base.resolve("cpSrc"); 224 tb.writeJavaFiles(cpSrc, "package p; public interface Other { }"); 225 Path cpClasses = base.resolve("cpClasses"); 226 tb.createDirectories(cpClasses); 227 228 String cpLog = new JavacTask(tb) 229 .outdir(cpClasses) 230 .files(findJavaFiles(cpSrc)) 231 .run() 232 .writeAll() 233 .getOutput(Task.OutputKind.DIRECT); 234 235 if (!cpLog.isEmpty()) 236 throw new Exception("expected output not found: " + cpLog); 237 238 Path src = base.resolve("src"); 239 //note: avoiding use of java.base, as that gets special handling on some places: 240 tb.writeJavaFiles(src, "package javax.lang.model.element; public interface Extra extends Element, p.Other { }"); 241 Path classes = base.resolve("classes"); 242 tb.createDirectories(classes); 243 244 List<String> log = new JavacTask(tb) 245 .options("--patch-module", "java.compiler=" + src.toString(), 246 "--class-path", cpClasses.toString(), 247 "-XDrawDiagnostics") 248 .outdir(classes) 249 .files(src.resolve("javax/lang/model/element/Extra.java")) 250 .run(Expect.FAIL) 251 .writeAll() 252 .getOutputLines(Task.OutputKind.DIRECT); 253 254 List<String> expectedOut = Arrays.asList( 255 "Extra.java:1:76: compiler.err.doesnt.exist: p", 256 "1 error" 257 ); 258 259 if (!expectedOut.equals(log)) 260 throw new Exception("expected output not found: " + log); 261 } 262 263 @Test 264 public void testWithModulePath(Path base) throws Exception { 265 Path modSrc = base.resolve("modSrc"); 266 Path modules = base.resolve("modules"); 267 new ModuleBuilder(tb, "m1") 268 .classes("package pkg1; public interface E { }") 269 .build(modSrc, modules); 270 271 Path src = base.resolve("src"); 272 tb.writeJavaFiles(src, "package p; interface A extends pkg1.E { }"); 273 274 new JavacTask(tb, Task.Mode.CMDLINE) 275 .options("--module-path", modules.toString(), 276 "--patch-module", "m1=" + src.toString()) 277 .files(findJavaFiles(src)) 278 .run() 279 .writeAll(); 280 281 //checks module bounds still exist 282 new ModuleBuilder(tb, "m2") 283 .classes("package pkg2; public interface D { }") 284 .build(modSrc, modules); 285 286 Path src2 = base.resolve("src2"); 287 tb.writeJavaFiles(src2, "package p; interface A extends pkg2.D { }"); 288 289 List<String> log = new JavacTask(tb, Task.Mode.CMDLINE) 290 .options("-XDrawDiagnostics", 291 "--module-path", modules.toString(), 292 "--patch-module", "m1=" + src2.toString()) 293 .files(findJavaFiles(src2)) 294 .run(Task.Expect.FAIL) 295 .writeAll() 296 .getOutputLines(Task.OutputKind.DIRECT); 297 298 List<String> expected = Arrays.asList("A.java:1:32: compiler.err.package.not.visible: pkg2, (compiler.misc.not.def.access.does.not.read: m1, pkg2, m2)", 299 "1 error"); 300 301 if (!expected.equals(log)) 302 throw new Exception("expected output not found: " + log); 303 } 304 305 @Test 306 public void testWithUpgradeModulePath(Path base) throws Exception { 307 Path modSrc = base.resolve("modSrc"); 308 Path modules = base.resolve("modules"); 309 new ModuleBuilder(tb, "m1") 310 .classes("package pkg1; public interface E { }") 311 .build(modSrc, modules); 312 313 Path upgrSrc = base.resolve("upgradeSrc"); 314 Path upgrade = base.resolve("upgrade"); 315 new ModuleBuilder(tb, "m1") 316 .classes("package pkg1; public interface D { }") 317 .build(upgrSrc, upgrade); 318 319 Path src = base.resolve("src"); 320 tb.writeJavaFiles(src, "package p; interface A extends pkg1.D { }"); 321 322 new JavacTask(tb, Task.Mode.CMDLINE) 323 .options("--module-path", modules.toString(), 324 "--upgrade-module-path", upgrade.toString(), 325 "--patch-module", "m1=" + src.toString()) 326 .files(findJavaFiles(src)) 327 .run() 328 .writeAll(); 329 } 330 331 @Test 332 public void testUnnamedIsolation(Path base) throws Exception { 333 //note: avoiding use of java.base, as that gets special handling on some places: 334 Path sourcePath = base.resolve("source-path"); 335 tb.writeJavaFiles(sourcePath, "package src; public class Src {}"); 336 337 Path classPathSrc = base.resolve("class-path-src"); 338 tb.writeJavaFiles(classPathSrc, "package cp; public class CP { }"); 339 Path classPath = base.resolve("classPath"); 340 tb.createDirectories(classPath); 341 342 String cpLog = new JavacTask(tb) 343 .outdir(classPath) 344 .files(findJavaFiles(classPathSrc)) 345 .run() 346 .writeAll() 347 .getOutput(Task.OutputKind.DIRECT); 348 349 if (!cpLog.isEmpty()) 350 throw new Exception("expected output not found: " + cpLog); 351 352 Path modulePathSrc = base.resolve("module-path-src"); 353 tb.writeJavaFiles(modulePathSrc, 354 "module m {}", 355 "package m; public class M {}"); 356 Path modulePath = base.resolve("modulePath"); 357 tb.createDirectories(modulePath.resolve("m")); 358 359 String modLog = new JavacTask(tb) 360 .outdir(modulePath.resolve("m")) 361 .files(findJavaFiles(modulePathSrc)) 362 .run() 363 .writeAll() 364 .getOutput(Task.OutputKind.DIRECT); 365 366 if (!modLog.isEmpty()) 367 throw new Exception("expected output not found: " + modLog); 368 369 Path src = base.resolve("src"); 370 tb.writeJavaFiles(src, "package m; public class Extra { }"); 371 Path classes = base.resolve("classes"); 372 tb.createDirectories(classes); 373 374 String log = new JavacTask(tb) 375 .options("--patch-module", "m=" + sourcePath.toString(), 376 "--class-path", classPath.toString(), 377 "--source-path", sourcePath.toString(), 378 "--module-path", modulePath.toString(), 379 "--processor-path", System.getProperty("test.classes"), 380 "-XDaccessInternalAPI=true", 381 "-processor", CheckModuleContentProcessing.class.getName()) 382 .outdir(classes) 383 .files(findJavaFiles(sourcePath)) 384 .run() 385 .writeAll() 386 .getOutput(Task.OutputKind.DIRECT); 387 388 if (!log.isEmpty()) 389 throw new Exception("expected output not found: " + log); 390 } 391 392 @SupportedAnnotationTypes("*") 393 public static final class CheckModuleContentProcessing extends AbstractProcessor { 394 395 @Override 396 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 397 Symtab syms = Symtab.instance(((JavacProcessingEnvironment) processingEnv).getContext()); 398 Elements elements = processingEnv.getElementUtils(); 399 ModuleElement unnamedModule = syms.unnamedModule; 400 ModuleElement mModule = elements.getModuleElement("m"); 401 402 assertNonNull("mModule found", mModule); 403 assertNonNull("src.Src from m", elements.getTypeElement(mModule, "src.Src")); 404 assertNull("cp.CP not from m", elements.getTypeElement(mModule, "cp.CP")); 405 assertNull("src.Src not from unnamed", elements.getTypeElement(unnamedModule, "src.Src")); 406 assertNonNull("cp.CP from unnamed", elements.getTypeElement(unnamedModule, "cp.CP")); 407 408 return false; 409 } 410 411 @Override 412 public SourceVersion getSupportedSourceVersion() { 413 return SourceVersion.latest(); 414 } 415 416 private static void assertNonNull(String msg, Object val) { 417 if (val == null) { 418 throw new AssertionError(msg); 419 } 420 } 421 422 private static void assertNull(String msg, Object val) { 423 if (val != null) { 424 throw new AssertionError(msg); 425 } 426 } 427 } 428 429 @Test 430 public void testSingleModeIncremental(Path base) throws Exception { 431 //note: avoiding use of java.base, as that gets special handling on some places: 432 Path src = base.resolve("src"); 433 tb.writeJavaFiles(src, 434 "package javax.lang.model.element; public interface Extra extends Element { }", 435 "package javax.lang.model.element; public interface Extra2 extends Extra { }"); 436 Path classes = base.resolve("classes"); 437 tb.createDirectories(classes); 438 439 Thread.sleep(2000); //ensure newer timestamps on classfiles: 440 441 new JavacTask(tb) 442 .options("--patch-module", "java.compiler=" + src.toString()) 443 .outdir(classes) 444 .files(findJavaFiles(src)) 445 .run() 446 .writeAll() 447 .getOutput(Task.OutputKind.DIRECT); 448 449 List<String> log = new JavacTask(tb) 450 .options("--patch-module", "java.compiler=" + src.toString(), 451 "-verbose") 452 .outdir(classes) 453 .files(findJavaFiles(src.resolve("javax/lang/model/element/Extra2.java" 454 .replace("/", src.getFileSystem().getSeparator())))) 455 .run() 456 .writeAll() 457 .getOutputLines(Task.OutputKind.DIRECT) 458 .stream() 459 .filter(l -> l.contains("parsing")) 460 .collect(Collectors.toList()); 461 462 boolean parsesExtra2 = log.stream() 463 .anyMatch(l -> l.contains("Extra2.java")); 464 boolean parsesExtra = log.stream() 465 .anyMatch(l -> l.contains("Extra.java")); 466 467 if (!parsesExtra2 || parsesExtra) { 468 throw new AssertionError("Unexpected output: " + log); 469 } 470 } 471 472 @Test 473 public void testComplexMSPAndPatch(Path base) throws Exception { 474 //note: avoiding use of java.base, as that gets special handling on some places: 475 Path src1 = base.resolve("src1"); 476 Path src1ma = src1.resolve("ma"); 477 tb.writeJavaFiles(src1ma, 478 "module ma { exports ma; }", 479 "package ma; public class C1 { public static void method() { } }", 480 "package ma.impl; public class C2 { }"); 481 Path src1mb = src1.resolve("mb"); 482 tb.writeJavaFiles(src1mb, 483 "module mb { requires ma; }", 484 "package mb.impl; public class C2 { public static void method() { } }"); 485 Path src1mc = src1.resolve("mc"); 486 tb.writeJavaFiles(src1mc, 487 "module mc { }"); 488 Path classes1 = base.resolve("classes1"); 489 tb.createDirectories(classes1); 490 tb.cleanDirectory(classes1); 491 492 new JavacTask(tb) 493 .options("--module-source-path", src1.toString()) 494 .files(findJavaFiles(src1)) 495 .outdir(classes1) 496 .run() 497 .writeAll(); 498 499 //patching: 500 Path src2 = base.resolve("src2"); 501 Path src2ma = src2.resolve("ma"); 502 tb.writeJavaFiles(src2ma, 503 "package ma.impl; public class C2 { public static void extra() { ma.C1.method(); } }", 504 "package ma.impl; public class C3 { public void test() { C2.extra(); } }"); 505 Path src2mb = src2.resolve("mb"); 506 tb.writeJavaFiles(src2mb, 507 "package mb.impl; public class C3 { public void test() { C2.method(); ma.C1.method(); ma.impl.C2.extra(); } }"); 508 Path src2mc = src2.resolve("mc"); 509 tb.writeJavaFiles(src2mc, 510 "package mc.impl; public class C2 { public static void test() { } }", 511 //will require --add-reads ma: 512 "package mc.impl; public class C3 { public static void test() { ma.impl.C2.extra(); } }"); 513 Path src2mt = src2.resolve("mt"); 514 tb.writeJavaFiles(src2mt, 515 "module mt { requires ma; requires mb; }", 516 "package mt.impl; public class C2 { public static void test() { mb.impl.C2.method(); ma.impl.C2.extra(); } }", 517 "package mt.impl; public class C3 { public static void test() { C2.test(); mc.impl.C2.test(); } }"); 518 Path classes2 = base.resolve("classes2"); 519 tb.createDirectories(classes2); 520 tb.cleanDirectory(classes2); 521 522 Thread.sleep(2000); //ensure newer timestamps on classfiles: 523 524 new JavacTask(tb) 525 .options("--module-path", classes1.toString(), 526 "--patch-module", "ma=" + src2ma.toString(), 527 "--patch-module", "mb=" + src2mb.toString(), 528 "--add-exports", "ma/ma.impl=mb", 529 "--patch-module", "mc=" + src2mc.toString(), 530 "--add-reads", "mc=ma", 531 "--add-exports", "ma/ma.impl=mc", 532 "--add-exports", "ma/ma.impl=mt", 533 "--add-exports", "mb/mb.impl=mt", 534 "--add-exports", "mc/mc.impl=mt", 535 "--add-reads", "mt=mc", 536 "--module-source-path", src2.toString()) 537 .outdir(classes2) 538 .files(findJavaFiles(src2)) 539 .run() 540 .writeAll(); 541 542 //incremental compilation (C2 mustn't be compiled, C3 must): 543 tb.writeJavaFiles(src2ma, 544 "package ma.impl; public class C3 { public void test() { ma.C1.method(); C2.extra(); } }"); 545 tb.writeJavaFiles(src2mt, 546 "package mt.impl; public class C3 { public static void test() { mc.impl.C2.test(); C2.test(); } }"); 547 548 List<String> log = new JavacTask(tb) 549 .options("--module-path", classes1.toString(), 550 "--patch-module", "ma=" + src2ma.toString(), 551 "--patch-module", "mb=" + src2mb.toString(), 552 "--add-exports", "ma/ma.impl=mb", 553 "--patch-module", "mc=" + src2mc.toString(), 554 "--add-reads", "mc=ma", 555 "--add-exports", "ma/ma.impl=mc", 556 "--add-exports", "ma/ma.impl=mt", 557 "--add-exports", "mb/mb.impl=mt", 558 "--add-exports", "mc/mc.impl=mt", 559 "--add-reads", "mt=mc", 560 "--module-source-path", src2.toString(), 561 "--add-modules", "mc", 562 "-verbose") 563 .outdir(classes2) 564 .files(src2ma.resolve("ma").resolve("impl").resolve("C3.java"), 565 src2mt.resolve("mt").resolve("impl").resolve("C3.java")) 566 .run() 567 .writeAll() 568 .getOutputLines(Task.OutputKind.DIRECT) 569 .stream() 570 .filter(l -> l.contains("parsing")) 571 .collect(Collectors.toList()); 572 573 boolean parsesC3 = log.stream() 574 .anyMatch(l -> l.contains("C3.java")); 575 boolean parsesC2 = log.stream() 576 .anyMatch(l -> l.contains("C2.java")); 577 578 if (!parsesC3 || parsesC2) { 579 throw new AssertionError("Unexpected output: " + log); 580 } 581 } 582 583 private void checkFileExists(Path dir, String path) { 584 Path toCheck = dir.resolve(path.replace("/", dir.getFileSystem().getSeparator())); 585 586 if (!Files.exists(toCheck)) { 587 throw new AssertionError(toCheck.toString() + " does not exist!"); 588 } 589 } 590} 591