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 
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 CompileModulePatchTest extends ModuleTestBase {
61 
main(String... args)62     public static void main(String... args) throws Exception {
63         new CompileModulePatchTest().runTests();
64     }
65 
66     @Test
testCorrectModulePatch(Path base)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
testCorrectModulePatchMultiModule(Path base)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
testCorrectModulePatchMultiModule2(Path base)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
testPatchModuleModuleSourcePathConflict(Path base)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
testSourcePath(Path base)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
testClassPath(Path base)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:75: compiler.err.package.not.visible: p, (compiler.misc.not.def.access.does.not.read.unnamed: p, java.compiler)",
256                 "1 error"
257         );
258 
259         if (!expectedOut.equals(log))
260             throw new Exception("expected output not found: " + log);
261     }
262 
263     @Test
testWithModulePath(Path base)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
testWithUpgradeModulePath(Path base)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
testUnnamedIsolation(Path base)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
process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)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
getSupportedSourceVersion()412         public SourceVersion getSupportedSourceVersion() {
413             return SourceVersion.latest();
414         }
415 
assertNonNull(String msg, Object val)416         private static void assertNonNull(String msg, Object val) {
417             if (val == null) {
418                 throw new AssertionError(msg);
419             }
420         }
421 
assertNull(String msg, Object val)422         private static void assertNull(String msg, Object val) {
423             if (val != null) {
424                 throw new AssertionError(msg);
425             }
426         }
427     }
428 
429     @Test
testSingleModeIncremental(Path base)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
testComplexMSPAndPatch(Path base)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 
checkFileExists(Path dir, String path)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