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 /*
  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 XModuleTest
  36  */
  37 
  38 import java.nio.file.Files;
  39 import java.nio.file.Path;
  40 import java.util.Arrays;
  41 import java.util.List;
  42 import java.util.Set;
  43 import java.util.stream.Collectors;
  44 
  45 import javax.annotation.processing.AbstractProcessor;
  46 import javax.annotation.processing.RoundEnvironment;
  47 import javax.annotation.processing.SupportedAnnotationTypes;
  48 import javax.lang.model.SourceVersion;
  49 import javax.lang.model.element.ModuleElement;
  50 import javax.lang.model.element.TypeElement;
  51 import javax.lang.model.util.Elements;
  52 
  53 import com.sun.tools.javac.code.Symtab;
  54 import com.sun.tools.javac.processing.JavacProcessingEnvironment;
  55 import toolbox.JavacTask;
  56 import toolbox.ModuleBuilder;
  57 import toolbox.Task;
  58 import toolbox.Task.Expect;
  59 
  60 public class XModuleTest extends ModuleTestBase {
  61 
  62     public static void main(String... args) throws Exception {
  63         new XModuleTest().runTests();
  64     }
  65 
  66     @Test
  67     public void testCorrectXModule(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 testCorrectXModuleMultiModule(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 testCorrectXModuleMultiModule2(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                 "1 error"
 184         );
 185 
 186         if (!expectedOut.equals(log))
 187             throw new Exception("expected output not found: " + log);
 188     }
 189 
 190     @Test
 191     public void testSourcePath(Path base) throws Exception {
 192         //note: avoiding use of java.base, as that gets special handling on some places:
 193         Path src = base.resolve("src");
 194         tb.writeJavaFiles(src, "package javax.lang.model.element; public interface Extra extends Element, Other { }");
 195         Path srcPath = base.resolve("src-path");
 196         tb.writeJavaFiles(srcPath, "package javax.lang.model.element; interface Other { }");
 197         Path classes = base.resolve("classes");
 198         tb.createDirectories(classes);
 199 
 200         List<String> log = new JavacTask(tb)
 201                 .options("--patch-module", "java.compiler=" + src.toString(),
 202                          "-sourcepath", srcPath.toString(),
 203                          "-XDrawDiagnostics")
 204                 .outdir(classes)
 205                 .files(src.resolve("javax/lang/model/element/Extra.java"))
 206                 .run(Expect.FAIL)
 207                 .writeAll()
 208                 .getOutputLines(Task.OutputKind.DIRECT);
 209 
 210         List<String> expectedOut = Arrays.asList(
 211                 "Extra.java:1:75: compiler.err.cant.resolve: kindname.class, Other, , ",
 212                 "1 error"
 213         );
 214 
 215         if (!expectedOut.equals(log))
 216             throw new Exception("expected output not found: " + log);
 217     }
 218 
 219     @Test
 220     public void testClassPath(Path base) throws Exception {
 221         Path cpSrc = base.resolve("cpSrc");
 222         tb.writeJavaFiles(cpSrc, "package p; public interface Other { }");
 223         Path cpClasses = base.resolve("cpClasses");
 224         tb.createDirectories(cpClasses);
 225 
 226         String cpLog = new JavacTask(tb)
 227                 .outdir(cpClasses)
 228                 .files(findJavaFiles(cpSrc))
 229                 .run()
 230                 .writeAll()
 231                 .getOutput(Task.OutputKind.DIRECT);
 232 
 233         if (!cpLog.isEmpty())
 234             throw new Exception("expected output not found: " + cpLog);
 235 
 236         Path src = base.resolve("src");
 237         //note: avoiding use of java.base, as that gets special handling on some places:
 238         tb.writeJavaFiles(src, "package javax.lang.model.element; public interface Extra extends Element, p.Other { }");
 239         Path classes = base.resolve("classes");
 240         tb.createDirectories(classes);
 241 
 242         List<String> log = new JavacTask(tb)
 243                 .options("--patch-module", "java.compiler=" + src.toString(),
 244                          "--class-path", cpClasses.toString(),
 245                          "-XDrawDiagnostics")
 246                 .outdir(classes)
 247                 .files(src.resolve("javax/lang/model/element/Extra.java"))
 248                 .run(Expect.FAIL)
 249                 .writeAll()
 250                 .getOutputLines(Task.OutputKind.DIRECT);
 251 
 252         List<String> expectedOut = Arrays.asList(
 253                 "Extra.java:1:76: compiler.err.doesnt.exist: p",
 254                 "1 error"
 255         );
 256 
 257         if (!expectedOut.equals(log))
 258             throw new Exception("expected output not found: " + log);
 259     }
 260 
 261     @Test
 262     public void testNoModuleInfoOnSourcePath(Path base) throws Exception {
 263         //note: avoiding use of java.base, as that gets special handling on some places:
 264         Path src = base.resolve("src");
 265         tb.writeJavaFiles(src,
 266                           "module java.compiler {}",
 267                           "package javax.lang.model.element; public interface Extra { }");
 268         Path classes = base.resolve("classes");
 269         tb.createDirectories(classes);
 270 
 271         List<String> log;
 272         List<String> expected;
 273 
 274         log = new JavacTask(tb)
 275                 .options("-XDrawDiagnostics",
 276                          "--patch-module", "java.compiler=" + src.toString())
 277                 .outdir(classes)
 278                 .files(findJavaFiles(src))
 279                 .run(Task.Expect.FAIL)
 280                 .writeAll()
 281                 .getOutputLines(Task.OutputKind.DIRECT);
 282 
 283         expected = Arrays.asList("Extra.java:1:1: compiler.err.module-info.with.patched.module.sourcepath",
 284                                  "1 error");
 285 
 286         if (!expected.equals(log))
 287             throw new Exception("expected output not found: " + log);
 288 
 289         //multi-module mode:
 290         log = new JavacTask(tb)
 291                 .options("-XDrawDiagnostics",
 292                          "--patch-module", "java.compiler=" + src.toString(),
 293                          "--module-source-path", "dummy")
 294                 .outdir(classes)
 295                 .files(findJavaFiles(src))
 296                 .run(Task.Expect.FAIL)
 297                 .writeAll()
 298                 .getOutputLines(Task.OutputKind.DIRECT);
 299 
 300         expected = Arrays.asList("- compiler.err.locn.module-info.not.allowed.on.patch.path: module-info.java",
 301                                  "1 error");
 302 
 303         if (!expected.equals(log))
 304             throw new Exception("expected output not found: " + log);
 305     }
 306 
 307     @Test
 308     public void testNoModuleInfoInClassOutput(Path base) throws Exception {
 309         //note: avoiding use of java.base, as that gets special handling on some places:
 310         Path srcMod = base.resolve("src-mod");
 311         tb.writeJavaFiles(srcMod,
 312                           "module mod {}");
 313         Path classes = base.resolve("classes").resolve("java.compiler");
 314         tb.createDirectories(classes);
 315 
 316         String logMod = new JavacTask(tb)
 317                 .options()
 318                 .outdir(classes)
 319                 .files(findJavaFiles(srcMod))
 320                 .run()
 321                 .writeAll()
 322                 .getOutput(Task.OutputKind.DIRECT);
 323 
 324         if (!logMod.isEmpty())
 325             throw new Exception("unexpected output found: " + logMod);
 326 
 327         Path src = base.resolve("src");
 328         tb.writeJavaFiles(src,
 329                           "package javax.lang.model.element; public interface Extra { }");
 330         tb.createDirectories(classes);
 331 
 332         List<String> log;
 333         List<String> expected;
 334 
 335         log = new JavacTask(tb)
 336                 .options("-XDrawDiagnostics",
 337                          "--patch-module", "java.compiler=" + src.toString())
 338                 .outdir(classes)
 339                 .files(findJavaFiles(src))
 340                 .run(Task.Expect.FAIL)
 341                 .writeAll()
 342                 .getOutputLines(Task.OutputKind.DIRECT);
 343 
 344         expected = Arrays.asList("Extra.java:1:1: compiler.err.module-info.with.patched.module.classoutput",
 345                                  "1 error");
 346 
 347         if (!expected.equals(log))
 348             throw new Exception("expected output not found: " + log);
 349 
 350         log = new JavacTask(tb)
 351                 .options("-XDrawDiagnostics",
 352                          "--patch-module", "java.compiler=" + src.toString(),
 353                          "--module-source-path", "dummy")
 354                 .outdir(classes.getParent())
 355                 .files(findJavaFiles(src))
 356                 .run(Task.Expect.FAIL)
 357                 .writeAll()
 358                 .getOutputLines(Task.OutputKind.DIRECT);
 359 
 360         expected = Arrays.asList("- compiler.err.locn.module-info.not.allowed.on.patch.path: module-info.class",
 361                                  "1 error");
 362 
 363         if (!expected.equals(log))
 364             throw new Exception("expected output not found: " + log);
 365     }
 366 
 367     @Test
 368     public void testWithModulePath(Path base) throws Exception {
 369         Path modSrc = base.resolve("modSrc");
 370         Path modules = base.resolve("modules");
 371         new ModuleBuilder(tb, "m1")
 372                 .classes("package pkg1; public interface E { }")
 373                 .build(modSrc, modules);
 374 
 375         Path src = base.resolve("src");
 376         tb.writeJavaFiles(src, "package p; interface A extends pkg1.E { }");
 377 
 378         new JavacTask(tb, Task.Mode.CMDLINE)
 379                 .options("--module-path", modules.toString(),
 380                         "--patch-module", "m1=" + src.toString())
 381                 .files(findJavaFiles(src))
 382                 .run()
 383                 .writeAll();
 384 
 385         //checks module bounds still exist
 386         new ModuleBuilder(tb, "m2")
 387                 .classes("package pkg2; public interface D { }")
 388                 .build(modSrc, modules);
 389 
 390         Path src2 = base.resolve("src2");
 391         tb.writeJavaFiles(src2, "package p; interface A extends pkg2.D { }");
 392 
 393         List<String> log = new JavacTask(tb, Task.Mode.CMDLINE)
 394                 .options("-XDrawDiagnostics",
 395                         "--module-path", modules.toString(),
 396                         "--patch-module", "m1=" + src2.toString())
 397                 .files(findJavaFiles(src2))
 398                 .run(Task.Expect.FAIL)
 399                 .writeAll()
 400                 .getOutputLines(Task.OutputKind.DIRECT);
 401 
 402         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)",
 403                 "1 error");
 404 
 405         if (!expected.equals(log))
 406             throw new Exception("expected output not found: " + log);
 407     }
 408 
 409     @Test
 410     public void testWithUpgradeModulePath(Path base) throws Exception {
 411         Path modSrc = base.resolve("modSrc");
 412         Path modules = base.resolve("modules");
 413         new ModuleBuilder(tb, "m1")
 414                 .classes("package pkg1; public interface E { }")
 415                 .build(modSrc, modules);
 416 
 417         Path upgrSrc = base.resolve("upgradeSrc");
 418         Path upgrade = base.resolve("upgrade");
 419         new ModuleBuilder(tb, "m1")
 420                 .classes("package pkg1; public interface D { }")
 421                 .build(upgrSrc, upgrade);
 422 
 423         Path src = base.resolve("src");
 424         tb.writeJavaFiles(src, "package p; interface A extends pkg1.D { }");
 425 
 426         new JavacTask(tb, Task.Mode.CMDLINE)
 427                 .options("--module-path", modules.toString(),
 428                         "--upgrade-module-path", upgrade.toString(),
 429                         "--patch-module", "m1=" + src.toString())
 430                 .files(findJavaFiles(src))
 431                 .run()
 432                 .writeAll();
 433     }
 434 
 435     @Test
 436     public void testUnnamedIsolation(Path base) throws Exception {
 437         //note: avoiding use of java.base, as that gets special handling on some places:
 438         Path sourcePath = base.resolve("source-path");
 439         tb.writeJavaFiles(sourcePath, "package src; public class Src {}");
 440 
 441         Path classPathSrc = base.resolve("class-path-src");
 442         tb.writeJavaFiles(classPathSrc, "package cp; public class CP { }");
 443         Path classPath = base.resolve("classPath");
 444         tb.createDirectories(classPath);
 445 
 446         String cpLog = new JavacTask(tb)
 447                 .outdir(classPath)
 448                 .files(findJavaFiles(classPathSrc))
 449                 .run()
 450                 .writeAll()
 451                 .getOutput(Task.OutputKind.DIRECT);
 452 
 453         if (!cpLog.isEmpty())
 454             throw new Exception("expected output not found: " + cpLog);
 455 
 456         Path modulePathSrc = base.resolve("module-path-src");
 457         tb.writeJavaFiles(modulePathSrc,
 458                           "module m {}",
 459                           "package m; public class M {}");
 460         Path modulePath = base.resolve("modulePath");
 461         tb.createDirectories(modulePath.resolve("m"));
 462 
 463         String modLog = new JavacTask(tb)
 464                 .outdir(modulePath.resolve("m"))
 465                 .files(findJavaFiles(modulePathSrc))
 466                 .run()
 467                 .writeAll()
 468                 .getOutput(Task.OutputKind.DIRECT);
 469 
 470         if (!modLog.isEmpty())
 471             throw new Exception("expected output not found: " + modLog);
 472 
 473         Path src = base.resolve("src");
 474         tb.writeJavaFiles(src, "package m; public class Extra { }");
 475         Path classes = base.resolve("classes");
 476         tb.createDirectories(classes);
 477 
 478         String log = new JavacTask(tb)
 479                 .options("--patch-module", "m=" + sourcePath.toString(),
 480                          "--class-path", classPath.toString(),
 481                          "--source-path", sourcePath.toString(),
 482                          "--module-path", modulePath.toString(),
 483                          "--processor-path", System.getProperty("test.classes"),
 484                          "-XDaccessInternalAPI=true",
 485                          "-processor", CheckModuleContentProcessing.class.getName())
 486                 .outdir(classes)
 487                 .files(findJavaFiles(sourcePath))
 488                 .run()
 489                 .writeAll()
 490                 .getOutput(Task.OutputKind.DIRECT);
 491 
 492         if (!log.isEmpty())
 493             throw new Exception("expected output not found: " + log);
 494     }
 495 
 496     @SupportedAnnotationTypes("*")
 497     public static final class CheckModuleContentProcessing extends AbstractProcessor {
 498 
 499         @Override
 500         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 501             Symtab syms = Symtab.instance(((JavacProcessingEnvironment) processingEnv).getContext());
 502             Elements elements = processingEnv.getElementUtils();
 503             ModuleElement unnamedModule = syms.unnamedModule;
 504             ModuleElement mModule = elements.getModuleElement("m");
 505 
 506             assertNonNull("mModule found", mModule);
 507             assertNonNull("src.Src from m", elements.getTypeElement(mModule, "src.Src"));
 508             assertNull("cp.CP not from m", elements.getTypeElement(mModule, "cp.CP"));
 509             assertNull("src.Src not from unnamed", elements.getTypeElement(unnamedModule, "src.Src"));
 510             assertNonNull("cp.CP from unnamed", elements.getTypeElement(unnamedModule, "cp.CP"));
 511 
 512             return false;
 513         }
 514 
 515         @Override
 516         public SourceVersion getSupportedSourceVersion() {
 517             return SourceVersion.latest();
 518         }
 519 
 520         private static void assertNonNull(String msg, Object val) {
 521             if (val == null) {
 522                 throw new AssertionError(msg);
 523             }
 524         }
 525 
 526         private static void assertNull(String msg, Object val) {
 527             if (val != null) {
 528                 throw new AssertionError(msg);
 529             }
 530         }
 531     }
 532 
 533     @Test
 534     public void testSingleModeIncremental(Path base) throws Exception {
 535         //note: avoiding use of java.base, as that gets special handling on some places:
 536         Path src = base.resolve("src");
 537         tb.writeJavaFiles(src,
 538                           "package javax.lang.model.element; public interface Extra extends Element { }",
 539                           "package javax.lang.model.element; public interface Extra2 extends Extra { }");
 540         Path classes = base.resolve("classes");
 541         tb.createDirectories(classes);
 542 
 543         Thread.sleep(2000); //ensure newer timestamps on classfiles:
 544 
 545         new JavacTask(tb)
 546             .options("--patch-module", "java.compiler=" + src.toString())
 547             .outdir(classes)
 548             .files(findJavaFiles(src))
 549             .run()
 550             .writeAll()
 551             .getOutput(Task.OutputKind.DIRECT);
 552 
 553         List<String> log = new JavacTask(tb)
 554             .options("--patch-module", "java.compiler=" + src.toString(),
 555                      "-verbose")
 556             .outdir(classes)
 557             .files(findJavaFiles(src.resolve("javax/lang/model/element/Extra2.java"
 558                                     .replace("/", src.getFileSystem().getSeparator()))))
 559             .run()
 560             .writeAll()
 561             .getOutputLines(Task.OutputKind.DIRECT)
 562             .stream()
 563             .filter(l -> l.contains("parsing"))
 564             .collect(Collectors.toList());
 565 
 566         boolean parsesExtra2 = log.stream()
 567                                   .anyMatch(l -> l.contains("Extra2.java"));
 568         boolean parsesExtra = log.stream()
 569                               .anyMatch(l -> l.contains("Extra.java"));
 570 
 571         if (!parsesExtra2 || parsesExtra) {
 572             throw new AssertionError("Unexpected output: " + log);
 573         }
 574     }
 575 
 576     @Test
 577     public void testComplexMSPAndPatch(Path base) throws Exception {
 578         //note: avoiding use of java.base, as that gets special handling on some places:
 579         Path src1 = base.resolve("src1");
 580         Path src1ma = src1.resolve("ma");
 581         tb.writeJavaFiles(src1ma,
 582                           "module ma { exports ma; }",
 583                           "package ma; public class C1 { public static void method() { } }",
 584                           "package ma.impl; public class C2 { }");
 585         Path src1mb = src1.resolve("mb");
 586         tb.writeJavaFiles(src1mb,
 587                           "module mb { requires ma; }",
 588                           "package mb.impl; public class C2 { public static void method() { } }");
 589         Path src1mc = src1.resolve("mc");
 590         tb.writeJavaFiles(src1mc,
 591                           "module mc { }");
 592         Path classes1 = base.resolve("classes1");
 593         tb.createDirectories(classes1);
 594         tb.cleanDirectory(classes1);
 595 
 596         new JavacTask(tb)
 597             .options("--module-source-path", src1.toString())
 598             .files(findJavaFiles(src1))
 599             .outdir(classes1)
 600             .run()
 601             .writeAll();
 602 
 603         //patching:
 604         Path src2 = base.resolve("src2");
 605         Path src2ma = src2.resolve("ma");
 606         tb.writeJavaFiles(src2ma,
 607                           "package ma.impl; public class C2 { public static void extra() { ma.C1.method(); } }",
 608                           "package ma.impl; public class C3 { public void test() { C2.extra(); } }");
 609         Path src2mb = src2.resolve("mb");
 610         tb.writeJavaFiles(src2mb,
 611                           "package mb.impl; public class C3 { public void test() { C2.method(); ma.C1.method(); ma.impl.C2.extra(); } }");
 612         Path src2mc = src2.resolve("mc");
 613         tb.writeJavaFiles(src2mc,
 614                           "package mc.impl; public class C2 { public static void test() { } }",
 615                           //will require --add-reads ma:
 616                           "package mc.impl; public class C3 { public static void test() { ma.impl.C2.extra(); } }");
 617         Path src2mt = src2.resolve("mt");
 618         tb.writeJavaFiles(src2mt,
 619                           "module mt { requires ma; requires mb; }",
 620                           "package mt.impl; public class C2 { public static void test() { mb.impl.C2.method(); ma.impl.C2.extra(); } }",
 621                           "package mt.impl; public class C3 { public static void test() { C2.test(); mc.impl.C2.test(); } }");
 622         Path classes2 = base.resolve("classes2");
 623         tb.createDirectories(classes2);
 624         tb.cleanDirectory(classes2);
 625 
 626         Thread.sleep(2000); //ensure newer timestamps on classfiles:
 627 
 628         new JavacTask(tb)
 629             .options("--module-path", classes1.toString(),
 630                      "--patch-module", "ma=" + src2ma.toString(),
 631                      "--patch-module", "mb=" + src2mb.toString(),
 632                      "--add-exports", "ma/ma.impl=mb",
 633                      "--patch-module", "mc=" + src2mc.toString(),
 634                      "--add-reads", "mc=ma",
 635                      "--add-exports", "ma/ma.impl=mc",
 636                      "--add-exports", "ma/ma.impl=mt",
 637                      "--add-exports", "mb/mb.impl=mt",
 638                      "--add-exports", "mc/mc.impl=mt",
 639                      "--add-reads", "mt=mc",
 640                      "--module-source-path", src2.toString())
 641             .outdir(classes2)
 642             .files(findJavaFiles(src2))
 643             .run()
 644             .writeAll();
 645 
 646         //incremental compilation (C2 mustn't be compiled, C3 must):
 647         tb.writeJavaFiles(src2ma,
 648                           "package ma.impl; public class C3 { public void test() { ma.C1.method(); C2.extra(); } }");
 649         tb.writeJavaFiles(src2mt,
 650                           "package mt.impl; public class C3 { public static void test() { mc.impl.C2.test(); C2.test(); } }");
 651 
 652         List<String> log = new JavacTask(tb)
 653             .options("--module-path", classes1.toString(),
 654                      "--patch-module", "ma=" + src2ma.toString(),
 655                      "--patch-module", "mb=" + src2mb.toString(),
 656                      "--add-exports", "ma/ma.impl=mb",
 657                      "--patch-module", "mc=" + src2mc.toString(),
 658                      "--add-reads", "mc=ma",
 659                      "--add-exports", "ma/ma.impl=mc",
 660                      "--add-exports", "ma/ma.impl=mt",
 661                      "--add-exports", "mb/mb.impl=mt",
 662                      "--add-exports", "mc/mc.impl=mt",
 663                      "--add-reads", "mt=mc",
 664                      "--module-source-path", src2.toString(),
 665                      "--add-modules", "mc",
 666                      "-verbose")
 667             .outdir(classes2)
 668             .files(src2ma.resolve("ma").resolve("impl").resolve("C3.java"),
 669                    src2mt.resolve("mt").resolve("impl").resolve("C3.java"))
 670             .run()
 671             .writeAll()
 672             .getOutputLines(Task.OutputKind.DIRECT)
 673             .stream()
 674             .filter(l -> l.contains("parsing"))
 675             .collect(Collectors.toList());
 676 
 677         boolean parsesC3 = log.stream()
 678                               .anyMatch(l -> l.contains("C3.java"));
 679         boolean parsesC2 = log.stream()
 680                               .anyMatch(l -> l.contains("C2.java"));
 681 
 682         if (!parsesC3 || parsesC2) {
 683             throw new AssertionError("Unexpected output: " + log);
 684         }
 685     }
 686 
 687     private void checkFileExists(Path dir, String path) {
 688         Path toCheck = dir.resolve(path.replace("/", dir.getFileSystem().getSeparator()));
 689 
 690         if (!Files.exists(toCheck)) {
 691             throw new AssertionError(toCheck.toString() + " does not exist!");
 692         }
 693     }
 694 }