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