1/*
2 * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24import java.io.*;
25import java.lang.module.ModuleDescriptor;
26import java.lang.reflect.Method;
27import java.nio.file.*;
28import java.nio.file.attribute.BasicFileAttributes;
29import java.util.*;
30import java.util.function.Consumer;
31import java.util.jar.JarEntry;
32import java.util.jar.JarFile;
33import java.util.jar.JarInputStream;
34import java.util.jar.Manifest;
35import java.util.regex.Pattern;
36import java.util.stream.Collectors;
37import java.util.stream.Stream;
38
39import jdk.testlibrary.FileUtils;
40import jdk.testlibrary.JDKToolFinder;
41import org.testng.annotations.BeforeTest;
42import org.testng.annotations.DataProvider;
43import org.testng.annotations.Test;
44
45import static java.lang.String.format;
46import static java.lang.System.out;
47
48/*
49 * @test
50 * @bug 8167328 8171830 8165640 8174248 8176772
51 * @library /lib/testlibrary
52 * @modules jdk.compiler
53 *          jdk.jartool
54 * @build jdk.testlibrary.FileUtils jdk.testlibrary.JDKToolFinder
55 * @compile Basic.java
56 * @run testng Basic
57 * @summary Tests for plain Modular jars & Multi-Release Modular jars
58 */
59
60public class Basic {
61    static final Path TEST_SRC = Paths.get(System.getProperty("test.src", "."));
62    static final Path TEST_CLASSES = Paths.get(System.getProperty("test.classes", "."));
63    static final Path MODULE_CLASSES = TEST_CLASSES.resolve("build");
64    static final Path MRJAR_DIR = MODULE_CLASSES.resolve("mrjar");
65
66    static final String VM_OPTIONS = System.getProperty("test.vm.opts", "");
67    static final String TOOL_VM_OPTIONS = System.getProperty("test.tool.vm.opts", "");
68    static final String JAVA_OPTIONS = System.getProperty("test.java.opts", "");
69
70    // Details based on the checked in module source
71    static TestModuleData FOO = new TestModuleData("foo",
72                                                   "1.123",
73                                                   "jdk.test.foo.Foo",
74                                                   "Hello World!!!",
75                                                   null, // no hashes
76                                                   Set.of("java.base"),
77                                                   Set.of("jdk.test.foo"),
78                                                   null, // no uses
79                                                   null, // no provides
80                                                   Set.of("jdk.test.foo.internal",
81                                                          "jdk.test.foo.resources"));
82    static TestModuleData BAR = new TestModuleData("bar",
83                                                   "4.5.6.7",
84                                                   "jdk.test.bar.Bar",
85                                                   "Hello from Bar!",
86                                                   null, // no hashes
87                                                   Set.of("java.base", "foo"),
88                                                   null, // no exports
89                                                   null, // no uses
90                                                   null, // no provides
91                                                   Set.of("jdk.test.bar",
92                                                          "jdk.test.bar.internal"));
93
94    static class TestModuleData {
95        final String moduleName;
96        final Set<String> requires;
97        final Set<String> exports;
98        final Set<String> uses;
99        final Set<String> provides;
100        final String mainClass;
101        final String version;
102        final String message;
103        final String hashes;
104        final Set<String> packages;
105
106        TestModuleData(String mn, String v, String mc, String m, String h,
107                       Set<String> requires, Set<String> exports, Set<String> uses,
108                       Set<String> provides, Set<String> contains) {
109            moduleName = mn; mainClass = mc; version = v; message = m; hashes = h;
110            this.requires = requires != null ? requires : Collections.emptySet();
111            this.exports = exports != null ? exports : Collections.emptySet();
112            this.uses = uses != null ? uses : Collections.emptySet();;
113            this.provides = provides != null ? provides : Collections.emptySet();
114            this.packages = Stream.concat(this.exports.stream(), contains.stream())
115                                  .collect(Collectors.toSet());
116        }
117        static TestModuleData from(String s) {
118            try {
119                BufferedReader reader = new BufferedReader(new StringReader(s));
120                String line;
121                String message = null;
122                String name = null, version = null, mainClass = null;
123                String hashes = null;
124                Set<String> requires, exports, uses, provides, conceals;
125                requires = exports = uses = provides = conceals = null;
126                while ((line = reader.readLine()) != null) {
127                    if (line.startsWith("message:")) {
128                        message = line.substring("message:".length());
129                    } else if (line.startsWith("nameAndVersion:")) {
130                        line = line.substring("nameAndVersion:".length());
131                        int i = line.indexOf('@');
132                        if (i != -1) {
133                            name = line.substring(0, i);
134                            version = line.substring(i + 1, line.length());
135                        } else {
136                            name = line;
137                        }
138                    } else if (line.startsWith("mainClass:")) {
139                        mainClass = line.substring("mainClass:".length());
140                    } else if (line.startsWith("requires:")) {
141                        line = line.substring("requires:".length());
142                        requires = stringToSet(line);
143                    } else if (line.startsWith("exports:")) {
144                        line = line.substring("exports:".length());
145                        exports = stringToSet(line);
146                    } else if (line.startsWith("uses:")) {
147                        line = line.substring("uses:".length());
148                        uses = stringToSet(line);
149                    } else if (line.startsWith("provides:")) {
150                        line = line.substring("provides:".length());
151                        provides = stringToSet(line);
152                    } else if (line.startsWith("hashes:")) {
153                        hashes = line.substring("hashes:".length());
154                    } else if (line.startsWith("contains:")) {
155                        line = line.substring("contains:".length());
156                        conceals = stringToSet(line);
157                    } else {
158                        throw new AssertionError("Unknown value " + line);
159                    }
160                }
161
162                return new TestModuleData(name, version, mainClass, message,
163                                          hashes, requires, exports, uses,
164                                          provides, conceals);
165            } catch (IOException x) {
166                throw new UncheckedIOException(x);
167            }
168        }
169        static Set<String> stringToSet(String commaList) {
170            Set<String> s = new HashSet<>();
171            int i = commaList.indexOf(',');
172            if (i != -1) {
173                String[] p = commaList.split(",");
174                Stream.of(p).forEach(s::add);
175            } else {
176                s.add(commaList);
177            }
178            return s;
179        }
180    }
181
182    static void assertModuleData(Result r, TestModuleData expected) {
183        //out.printf("%s%n", r.output);
184        TestModuleData received = TestModuleData.from(r.output);
185        if (expected.message != null)
186            assertTrue(expected.message.equals(received.message),
187                       "Expected message:", expected.message, ", got:", received.message);
188        assertTrue(expected.moduleName.equals(received.moduleName),
189                   "Expected moduleName: ", expected.moduleName, ", got:", received.moduleName);
190        assertTrue(expected.version.equals(received.version),
191                   "Expected version: ", expected.version, ", got:", received.version);
192        assertTrue(expected.mainClass.equals(received.mainClass),
193                   "Expected mainClass: ", expected.mainClass, ", got:", received.mainClass);
194        assertSetsEqual(expected.requires, received.requires);
195        assertSetsEqual(expected.exports, received.exports);
196        assertSetsEqual(expected.uses, received.uses);
197        assertSetsEqual(expected.provides, received.provides);
198        assertSetsEqual(expected.packages, received.packages);
199    }
200
201    static void assertSetsEqual(Set<String> s1, Set<String> s2) {
202        if (!s1.equals(s2)) {
203            org.testng.Assert.assertTrue(false, s1 + " vs " + s2);
204        }
205     }
206
207    @BeforeTest
208    public void compileModules() throws Exception {
209        compileModule(FOO.moduleName);
210        compileModule(BAR.moduleName, MODULE_CLASSES);
211        compileModule("baz");  // for service provider consistency checking
212
213        // copy resources
214        copyResource(TEST_SRC.resolve("src").resolve(FOO.moduleName),
215                     MODULE_CLASSES.resolve(FOO.moduleName),
216                     "jdk/test/foo/resources/foo.properties");
217
218        setupMRJARModuleInfo(FOO.moduleName);
219        setupMRJARModuleInfo(BAR.moduleName);
220        setupMRJARModuleInfo("baz");
221    }
222
223    @Test
224    public void createFoo() throws IOException {
225        Path mp = Paths.get("createFoo");
226        createTestDir(mp);
227        Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
228        Path modularJar = mp.resolve(FOO.moduleName + ".jar");
229
230        jar("--create",
231            "--file=" + modularJar.toString(),
232            "--main-class=" + FOO.mainClass,
233            "--module-version=" + FOO.version,
234            "--no-manifest",
235            "-C", modClasses.toString(), ".")
236            .assertSuccess();
237
238        assertSetsEqual(readPackagesAttribute(modularJar),
239                        Set.of("jdk.test.foo",
240                               "jdk.test.foo.resources",
241                               "jdk.test.foo.internal"));
242
243        java(mp, FOO.moduleName + "/" + FOO.mainClass)
244            .assertSuccess()
245            .resultChecker(r -> assertModuleData(r, FOO));
246        try (InputStream fis = Files.newInputStream(modularJar);
247             JarInputStream jis = new JarInputStream(fis)) {
248            assertTrue(!jarContains(jis, "./"),
249                       "Unexpected ./ found in ", modularJar.toString());
250        }
251    }
252
253    /** Similar to createFoo, but with a Multi-Release Modular jar. */
254    @Test
255    public void createMRMJarFoo() throws IOException {
256        Path mp = Paths.get("createMRMJarFoo");
257        createTestDir(mp);
258        Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
259        Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName);
260        Path modularJar = mp.resolve(FOO.moduleName + ".jar");
261
262        // Positive test, create
263        jar("--create",
264            "--file=" + modularJar.toString(),
265            "--main-class=" + FOO.mainClass,
266            "--module-version=" + FOO.version,
267            "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(),
268            "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class",
269            "-C", modClasses.toString(), ".")
270            .assertSuccess();
271        java(mp, FOO.moduleName + "/" + FOO.mainClass)
272            .assertSuccess()
273            .resultChecker(r -> assertModuleData(r, FOO));
274    }
275
276
277    @Test
278    public void updateFoo() throws IOException {
279        Path mp = Paths.get("updateFoo");
280        createTestDir(mp);
281        Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
282        Path modularJar = mp.resolve(FOO.moduleName + ".jar");
283
284        jar("--create",
285            "--file=" + modularJar.toString(),
286            "--no-manifest",
287            "-C", modClasses.toString(), "jdk")
288            .assertSuccess();
289        jar("--update",
290            "--file=" + modularJar.toString(),
291            "--main-class=" + FOO.mainClass,
292            "--module-version=" + FOO.version,
293            "--no-manifest",
294            "-C", modClasses.toString(), "module-info.class")
295            .assertSuccess();
296        java(mp, FOO.moduleName + "/" + FOO.mainClass)
297            .assertSuccess()
298            .resultChecker(r -> assertModuleData(r, FOO));
299    }
300
301    @Test
302    public void updateMRMJarFoo() throws IOException {
303        Path mp = Paths.get("updateMRMJarFoo");
304        createTestDir(mp);
305        Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
306        Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName);
307        Path modularJar = mp.resolve(FOO.moduleName + ".jar");
308
309        jar("--create",
310            "--file=" + modularJar.toString(),
311            "--no-manifest",
312            "-C", modClasses.toString(), "jdk")
313            .assertSuccess();
314        jar("--update",
315            "--file=" + modularJar.toString(),
316            "--main-class=" + FOO.mainClass,
317            "--module-version=" + FOO.version,
318            "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(),
319            "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class",
320            "-C", modClasses.toString(), "module-info.class")
321            .assertSuccess();
322        java(mp, FOO.moduleName + "/" + FOO.mainClass)
323            .assertSuccess()
324            .resultChecker(r -> assertModuleData(r, FOO));
325    }
326
327    @Test
328    public void partialUpdateFooMainClass() throws IOException {
329        Path mp = Paths.get("partialUpdateFooMainClass");
330        createTestDir(mp);
331        Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
332        Path modularJar = mp.resolve(FOO.moduleName + ".jar");
333
334        // A "bad" main class in first create ( and no version )
335        jar("--create",
336            "--file=" + modularJar.toString(),
337            "--main-class=" + "jdk.test.foo.IAmNotTheEntryPoint",
338            "--no-manifest",
339            "-C", modClasses.toString(), ".")  // includes module-info.class
340           .assertSuccess();
341        jar("--update",
342            "--file=" + modularJar.toString(),
343            "--main-class=" + FOO.mainClass,
344            "--module-version=" + FOO.version,
345            "--no-manifest")
346            .assertSuccess();
347        java(mp, FOO.moduleName + "/" + FOO.mainClass)
348            .assertSuccess()
349            .resultChecker(r -> assertModuleData(r, FOO));
350    }
351
352    @Test
353    public void partialUpdateFooVersion() throws IOException {
354        Path mp = Paths.get("partialUpdateFooVersion");
355        createTestDir(mp);
356        Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
357        Path modularJar = mp.resolve(FOO.moduleName + ".jar");
358
359        // A "bad" version in first create ( and no main class )
360        jar("--create",
361            "--file=" + modularJar.toString(),
362            "--module-version=" + "100000000",
363            "--no-manifest",
364            "-C", modClasses.toString(), ".")  // includes module-info.class
365            .assertSuccess();
366        jar("--update",
367            "--file=" + modularJar.toString(),
368            "--main-class=" + FOO.mainClass,
369            "--module-version=" + FOO.version,
370            "--no-manifest")
371            .assertSuccess();
372        java(mp, FOO.moduleName + "/" + FOO.mainClass)
373            .assertSuccess()
374            .resultChecker(r -> assertModuleData(r, FOO));
375    }
376
377    @Test
378    public void partialUpdateFooNotAllFiles() throws IOException {
379        Path mp = Paths.get("partialUpdateFooNotAllFiles");
380        createTestDir(mp);
381        Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
382        Path modularJar = mp.resolve(FOO.moduleName + ".jar");
383
384        // Not all files, and none from non-exported packages,
385        // i.e. no concealed list in first create
386        jar("--create",
387            "--file=" + modularJar.toString(),
388            "--no-manifest",
389            "-C", modClasses.toString(), "module-info.class",
390            "-C", modClasses.toString(), "jdk/test/foo/Foo.class",
391            "-C", modClasses.toString(), "jdk/test/foo/resources/foo.properties")
392            .assertSuccess();
393        jar("--update",
394            "--file=" + modularJar.toString(),
395            "--main-class=" + FOO.mainClass,
396            "--module-version=" + FOO.version,
397            "--no-manifest",
398            "-C", modClasses.toString(), "jdk/test/foo/internal/Message.class")
399            .assertSuccess();
400        java(mp, FOO.moduleName + "/" + FOO.mainClass)
401            .assertSuccess()
402            .resultChecker(r -> assertModuleData(r, FOO));
403    }
404
405    @Test
406    public void partialUpdateMRMJarFooNotAllFiles() throws IOException {
407        Path mp = Paths.get("partialUpdateMRMJarFooNotAllFiles");
408        createTestDir(mp);
409        Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
410        Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName);
411        Path modularJar = mp.resolve(FOO.moduleName + ".jar");
412
413        jar("--create",
414            "--file=" + modularJar.toString(),
415            "--module-version=" + FOO.version,
416            "-C", modClasses.toString(), ".")
417            .assertSuccess();
418        jar("--update",
419            "--file=" + modularJar.toString(),
420            "--main-class=" + FOO.mainClass,
421            "--module-version=" + FOO.version,
422            "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(),
423            "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class")
424            .assertSuccess();
425        java(mp, FOO.moduleName + "/" + FOO.mainClass)
426            .assertSuccess()
427            .resultChecker(r -> assertModuleData(r, FOO));
428    }
429
430    @Test
431    public void partialUpdateFooAllFilesAndAttributes() throws IOException {
432        Path mp = Paths.get("partialUpdateFooAllFilesAndAttributes");
433        createTestDir(mp);
434        Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
435        Path modularJar = mp.resolve(FOO.moduleName + ".jar");
436
437        // all attributes and files
438        jar("--create",
439            "--file=" + modularJar.toString(),
440            "--main-class=" + FOO.mainClass,
441            "--module-version=" + FOO.version,
442            "--no-manifest",
443            "-C", modClasses.toString(), ".")
444            .assertSuccess();
445        jar("--update",
446            "--file=" + modularJar.toString(),
447            "--main-class=" + FOO.mainClass,
448            "--module-version=" + FOO.version,
449            "--no-manifest",
450            "-C", modClasses.toString(), ".")
451            .assertSuccess();
452        java(mp, FOO.moduleName + "/" + FOO.mainClass)
453            .assertSuccess()
454            .resultChecker(r -> assertModuleData(r, FOO));
455    }
456
457    @Test
458    public void partialUpdateFooModuleInfo() throws IOException {
459        Path mp = Paths.get("partialUpdateFooModuleInfo");
460        createTestDir(mp);
461        Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
462        Path modularJar = mp.resolve(FOO.moduleName + ".jar");
463        Path barModInfo = MODULE_CLASSES.resolve(BAR.moduleName);
464
465        jar("--create",
466            "--file=" + modularJar.toString(),
467            "--main-class=" + FOO.mainClass,
468            "--module-version=" + FOO.version,
469            "--no-manifest",
470            "-C", modClasses.toString(), ".")
471            .assertSuccess();
472        jar("--update",
473            "--file=" + modularJar.toString(),
474            "--no-manifest",
475            "-C", barModInfo.toString(), "module-info.class")  // stuff in bar's info
476            .assertSuccess();
477        jar("-d",
478            "--file=" + modularJar.toString())
479            .assertSuccess()
480            .resultChecker(r -> {
481                // Expect "bar jar:file:/.../!module-info.class"
482                // conceals jdk.test.foo, conceals jdk.test.foo.internal"
483                String uri = "jar:" + modularJar.toUri().toString() + "/!module-info.class";
484                assertTrue(r.output.contains("bar " + uri),
485                           "Expecting to find \"bar " + uri + "\"",
486                           "in output, but did not: [" + r.output + "]");
487                Pattern p = Pattern.compile(
488                        "contains\\s+jdk.test.foo\\s+contains\\s+jdk.test.foo.internal");
489                assertTrue(p.matcher(r.output).find(),
490                           "Expecting to find \"contains jdk.test.foo,...\"",
491                           "in output, but did not: [" + r.output + "]");
492            });
493    }
494
495
496    @Test
497    public void partialUpdateFooPackagesAttribute() throws IOException {
498        Path mp = Paths.get("partialUpdateFooPackagesAttribute");
499        createTestDir(mp);
500        Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
501        Path modularJar = mp.resolve(FOO.moduleName + ".jar");
502
503        // Not all files, and none from non-exported packages,
504        // i.e. no concealed list in first create
505        jar("--create",
506            "--file=" + modularJar.toString(),
507            "--no-manifest",
508            "-C", modClasses.toString(), "module-info.class",
509            "-C", modClasses.toString(), "jdk/test/foo/Foo.class")
510            .assertSuccess();
511
512        assertSetsEqual(readPackagesAttribute(modularJar),
513                        Set.of("jdk.test.foo"));
514
515        jar("--update",
516            "--file=" + modularJar.toString(),
517            "-C", modClasses.toString(), "jdk/test/foo/resources/foo.properties")
518            .assertSuccess();
519
520        assertSetsEqual(readPackagesAttribute(modularJar),
521                        Set.of("jdk.test.foo", "jdk.test.foo.resources"));
522
523        jar("--update",
524            "--file=" + modularJar.toString(),
525            "--main-class=" + FOO.mainClass,
526            "--module-version=" + FOO.version,
527            "--no-manifest",
528            "-C", modClasses.toString(), "jdk/test/foo/internal/Message.class")
529            .assertSuccess();
530
531        assertSetsEqual(readPackagesAttribute(modularJar),
532                        Set.of("jdk.test.foo",
533                               "jdk.test.foo.resources",
534                               "jdk.test.foo.internal"));
535
536        java(mp, FOO.moduleName + "/" + FOO.mainClass)
537            .assertSuccess()
538            .resultChecker(r -> assertModuleData(r, FOO));
539    }
540
541    private Set<String> readPackagesAttribute(Path jar) {
542        return getModuleDescriptor(jar).packages();
543    }
544
545    @Test
546    public void hashBarInFooModule() throws IOException {
547        Path mp = Paths.get("dependencesFooBar");
548        createTestDir(mp);
549
550        Path modClasses = MODULE_CLASSES.resolve(BAR.moduleName);
551        Path modularJar = mp.resolve(BAR.moduleName + ".jar");
552        jar("--create",
553            "--file=" + modularJar.toString(),
554            "--main-class=" + BAR.mainClass,
555            "--module-version=" + BAR.version,
556            "--no-manifest",
557            "-C", modClasses.toString(), ".")
558            .assertSuccess();
559
560        modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
561        modularJar = mp.resolve(FOO.moduleName + ".jar");
562        jar("--create",
563            "--file=" + modularJar.toString(),
564            "--main-class=" + FOO.mainClass,
565            "--module-version=" + FOO.version,
566            "--module-path=" + mp.toString(),
567            "--hash-modules=" + "bar",
568            "--no-manifest",
569            "-C", modClasses.toString(), ".")
570            .assertSuccess();
571
572        java(mp, BAR.moduleName + "/" + BAR.mainClass,
573             "--add-exports", "java.base/jdk.internal.misc=bar",
574             "--add-exports", "java.base/jdk.internal.module=bar")
575            .assertSuccess()
576            .resultChecker(r -> {
577                assertModuleData(r, BAR);
578                TestModuleData received = TestModuleData.from(r.output);
579                assertTrue(received.hashes != null, "Expected non-null hashes value.");
580            });
581    }
582
583    @Test
584    public void invalidHashInFooModule() throws IOException {
585        Path mp = Paths.get("badDependencyFooBar");
586        createTestDir(mp);
587
588        Path barClasses = MODULE_CLASSES.resolve(BAR.moduleName);
589        Path barJar = mp.resolve(BAR.moduleName + ".jar");
590        jar("--create",
591            "--file=" + barJar.toString(),
592            "--main-class=" + BAR.mainClass,
593            "--module-version=" + BAR.version,
594            "--no-manifest",
595            "-C", barClasses.toString(), ".").assertSuccess();
596
597        Path fooClasses = MODULE_CLASSES.resolve(FOO.moduleName);
598        Path fooJar = mp.resolve(FOO.moduleName + ".jar");
599        jar("--create",
600            "--file=" + fooJar.toString(),
601            "--main-class=" + FOO.mainClass,
602            "--module-version=" + FOO.version,
603            "-p", mp.toString(),  // test short-form
604            "--hash-modules=" + "bar",
605            "--no-manifest",
606            "-C", fooClasses.toString(), ".").assertSuccess();
607
608        // Rebuild bar.jar with a change that will cause its hash to be different
609        FileUtils.deleteFileWithRetry(barJar);
610        jar("--create",
611            "--file=" + barJar.toString(),
612            "--main-class=" + BAR.mainClass,
613            "--module-version=" + BAR.version + ".1", // a newer version
614            "--no-manifest",
615            "-C", barClasses.toString(), ".").assertSuccess();
616
617        java(mp, BAR.moduleName + "/" + BAR.mainClass,
618             "--add-exports", "java.base/jdk.internal.misc=bar",
619             "--add-exports", "java.base/jdk.internal.module=bar")
620            .assertFailure()
621            .resultChecker(r -> {
622                // Expect similar output: "java.lang.module.ResolutionException: Hash
623                // of bar (WdktSIQSkd4+CEacpOZoeDrCosMATNrIuNub9b5yBeo=) differs to
624                // expected hash (iepvdv8xTeVrFgMtUhcFnmetSub6qQHCHc92lSaSEg0=)"
625                Pattern p = Pattern.compile(".*Hash of bar.*differs to expected hash.*");
626                assertTrue(p.matcher(r.output).find(),
627                      "Expecting error message containing \"Hash of bar ... differs to"
628                              + " expected hash...\" but got: [", r.output + "]");
629            });
630    }
631
632    @Test
633    public void badOptionsFoo() throws IOException {
634        Path mp = Paths.get("badOptionsFoo");
635        createTestDir(mp);
636        Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
637        Path modularJar = mp.resolve(FOO.moduleName + ".jar");
638
639        jar("--create",
640            "--file=" + modularJar.toString(),
641            "--module-version=" + 1.1,   // no module-info.class
642            "-C", modClasses.toString(), "jdk")
643            .assertFailure();      // TODO: expected failure message
644
645         jar("--create",
646             "--file=" + modularJar.toString(),
647             "--hash-modules=" + ".*",   // no module-info.class
648             "-C", modClasses.toString(), "jdk")
649             .assertFailure();      // TODO: expected failure message
650    }
651
652    @Test
653    public void servicesCreateWithoutFailure() throws IOException {
654        Path mp = Paths.get("servicesCreateWithoutFailure");
655        createTestDir(mp);
656        Path modClasses = MODULE_CLASSES.resolve("baz");
657        Path modularJar = mp.resolve("baz" + ".jar");
658
659        // Positive test, create
660        jar("--create",
661            "--file=" + modularJar.toString(),
662            "-C", modClasses.toString(), "module-info.class",
663            "-C", modClasses.toString(), "jdk/test/baz/BazService.class",
664            "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class")
665            .assertSuccess();
666    }
667
668    @Test
669    public void servicesCreateWithoutServiceImpl() throws IOException {
670        Path mp = Paths.get("servicesWithoutServiceImpl");
671        createTestDir(mp);
672        Path modClasses = MODULE_CLASSES.resolve("baz");
673        Path modularJar = mp.resolve("baz" + ".jar");
674
675        // Omit service impl
676        jar("--create",
677            "--file=" + modularJar.toString(),
678            "-C", modClasses.toString(), "module-info.class",
679            "-C", modClasses.toString(), "jdk/test/baz/BazService.class")
680            .assertFailure();
681    }
682
683    @Test
684    public void servicesUpdateWithoutFailure() throws IOException {
685        Path mp = Paths.get("servicesUpdateWithoutFailure");
686        createTestDir(mp);
687        Path modClasses = MODULE_CLASSES.resolve("baz");
688        Path modularJar = mp.resolve("baz" + ".jar");
689
690        // Positive test, update
691        jar("--create",
692            "--file=" + modularJar.toString(),
693            "-C", modClasses.toString(), "jdk/test/baz/BazService.class",
694            "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class")
695            .assertSuccess();
696        jar("--update",
697            "--file=" + modularJar.toString(),
698            "-C", modClasses.toString(), "module-info.class")
699            .assertSuccess();
700    }
701
702    @Test
703    public void servicesUpdateWithoutServiceImpl() throws IOException {
704        Path mp = Paths.get("servicesUpdateWithoutServiceImpl");
705        createTestDir(mp);
706        Path modClasses = MODULE_CLASSES.resolve("baz");
707        Path modularJar = mp.resolve("baz" + ".jar");
708
709        // Omit service impl
710        jar("--create",
711            "--file=" + modularJar.toString(),
712            "-C", modClasses.toString(), "jdk/test/baz/BazService.class")
713            .assertSuccess();
714        jar("--update",
715            "--file=" + modularJar.toString(),
716            "-C", modClasses.toString(), "module-info.class")
717            .assertFailure();
718    }
719
720    @Test
721    public void servicesCreateWithoutFailureMRMJAR() throws IOException {
722        Path mp = Paths.get("servicesCreateWithoutFailureMRMJAR");
723        createTestDir(mp);
724        Path modClasses = MODULE_CLASSES.resolve("baz");
725        Path mrjarDir = MRJAR_DIR.resolve("baz");
726        Path modularJar = mp.resolve("baz" + ".jar");
727
728        jar("--create",
729            "--file=" + modularJar.toString(),
730            "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(),
731            "-C", modClasses.toString(), "module-info.class",
732            "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class",
733            "-C", modClasses.toString(), "jdk/test/baz/BazService.class",
734            "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class")
735            .assertSuccess();
736    }
737
738    @Test
739    public void servicesCreateWithoutFailureNonRootMRMJAR() throws IOException {
740        // without a root module-info.class
741        Path mp = Paths.get("servicesCreateWithoutFailureNonRootMRMJAR");
742        createTestDir(mp);
743        Path modClasses = MODULE_CLASSES.resolve("baz");
744        Path mrjarDir = MRJAR_DIR.resolve("baz");
745        Path modularJar = mp.resolve("baz.jar");
746
747        jar("--create",
748            "--file=" + modularJar.toString(),
749            "--main-class=" + "jdk.test.baz.Baz",
750            "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(),
751            "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class",
752            "-C", modClasses.toString(), "jdk/test/baz/BazService.class",
753            "-C", modClasses.toString(), "jdk/test/baz/Baz.class",
754            "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class")
755            .assertSuccess();
756
757
758        for (String option : new String[]  {"--describe-module", "-d" }) {
759
760            jar(option,
761                "--file=" + modularJar.toString(),
762                "--release", "9")
763                .assertSuccess()
764                .resultChecker(r ->
765                    assertTrue(r.output.contains("main-class jdk.test.baz.Baz"),
766                              "Expected to find ", "main-class jdk.test.baz.Baz",
767                               " in [", r.output, "]"));
768
769            jarWithStdin(modularJar.toFile(), option, "--release", "9")
770                .assertSuccess()
771                .resultChecker(r ->
772                    assertTrue(r.output.contains("main-class jdk.test.baz.Baz"),
773                              "Expected to find ", "main-class jdk.test.baz.Baz",
774                               " in [", r.output, "]"));
775
776        }
777        // run module main class
778        java(mp, "baz/jdk.test.baz.Baz")
779            .assertSuccess()
780            .resultChecker(r ->
781               assertTrue(r.output.contains("mainClass:jdk.test.baz.Baz"),
782                          "Expected to find ", "mainClass:jdk.test.baz.Baz",
783                          " in [", r.output, "]"));
784    }
785
786    @Test
787    public void exportCreateWithMissingPkg() throws IOException {
788
789        Path foobar = TEST_SRC.resolve("src").resolve("foobar");
790        Path dst = Files.createDirectories(MODULE_CLASSES.resolve("foobar"));
791        javac(dst, null, sourceList(foobar));
792
793        Path mp = Paths.get("exportWithMissingPkg");
794        createTestDir(mp);
795        Path modClasses = dst;
796        Path modularJar = mp.resolve("foofoo.jar");
797
798        jar("--create",
799            "--file=" + modularJar.toString(),
800            "-C", modClasses.toString(), "module-info.class",
801            "-C", modClasses.toString(), "jdk/test/foo/Foo.class")
802            .assertFailure();
803    }
804
805    @Test
806    public void describeModuleFoo() throws IOException {
807        Path mp = Paths.get("describeModuleFoo");
808        createTestDir(mp);
809        Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
810        Path modularJar = mp.resolve(FOO.moduleName + ".jar");
811
812        jar("--create",
813            "--file=" + modularJar.toString(),
814            "--main-class=" + FOO.mainClass,
815            "--module-version=" + FOO.version,
816            "--no-manifest",
817            "-C", modClasses.toString(), ".")
818            .assertSuccess();
819
820        for (String option : new String[]  {"--describe-module", "-d" }) {
821            jar(option,
822                "--file=" + modularJar.toString())
823                .assertSuccess()
824                .resultChecker(r ->
825                    assertTrue(r.output.contains(FOO.moduleName + "@" + FOO.version),
826                               "Expected to find ", FOO.moduleName + "@" + FOO.version,
827                               " in [", r.output, "]")
828                );
829
830            jar(option,
831                "--file=" + modularJar.toString(),
832                modularJar.toString())
833            .assertFailure();
834
835            jar(option, modularJar.toString())
836            .assertFailure();
837        }
838    }
839
840    @Test
841    public void describeModuleFooFromStdin() throws IOException {
842        Path mp = Paths.get("describeModuleFooFromStdin");
843        createTestDir(mp);
844        Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
845        Path modularJar = mp.resolve(FOO.moduleName + ".jar");
846
847        jar("--create",
848            "--file=" + modularJar.toString(),
849            "--main-class=" + FOO.mainClass,
850            "--module-version=" + FOO.version,
851            "--no-manifest",
852            "-C", modClasses.toString(), ".")
853            .assertSuccess();
854
855        for (String option : new String[]  {"--describe-module", "-d" }) {
856            jarWithStdin(modularJar.toFile(),
857                         option)
858                         .assertSuccess()
859                         .resultChecker(r ->
860                             assertTrue(r.output.contains(FOO.moduleName + "@" + FOO.version),
861                                "Expected to find ", FOO.moduleName + "@" + FOO.version,
862                                " in [", r.output, "]")
863                );
864        }
865    }
866
867
868    @DataProvider(name = "autoNames")
869    public Object[][] autoNames() {
870        return new Object[][] {
871            // JAR file name                module-name[@version]
872            { "foo.jar",                    "foo" },
873            { "foo1.jar",                   "foo1" },
874            { "foo4j.jar",                  "foo4j", },
875            { "foo-1.2.3.4.jar",            "foo@1.2.3.4" },
876            { "foo-bar.jar",                "foo.bar" },
877            { "foo-1.2-SNAPSHOT.jar",       "foo@1.2-SNAPSHOT" },
878        };
879    }
880
881    @Test(dataProvider = "autoNames")
882    public void describeAutomaticModule(String jarName, String mid)
883        throws IOException
884    {
885        Path mp = Paths.get("describeAutomaticModule");
886        createTestDir(mp);
887        Path regularJar = mp.resolve(jarName);
888        Path t = Paths.get("t");
889        if (Files.notExists(t))
890            Files.createFile(t);
891
892        jar("--create",
893            "--file=" + regularJar.toString(),
894            t.toString())
895            .assertSuccess();
896
897        for (String option : new String[]  {"--describe-module", "-d" }) {
898            jar(option,
899                "--file=" + regularJar.toString())
900                .assertSuccess()
901                .resultChecker(r -> {
902                    assertTrue(r.output.contains("No module descriptor found"));
903                    assertTrue(r.output.contains("Derived automatic module"));
904                    assertTrue(r.output.contains(mid + " automatic"),
905                               "Expected [", "module " + mid,"] in [", r.output, "]");
906                    }
907                );
908        }
909    }
910
911    // -- Infrastructure
912
913    static Result jarWithStdin(File stdinSource, String... args) {
914        String jar = getJDKTool("jar");
915        List<String> commands = new ArrayList<>();
916        commands.add(jar);
917        if (!TOOL_VM_OPTIONS.isEmpty()) {
918            commands.addAll(Arrays.asList(TOOL_VM_OPTIONS.split("\\s+", -1)));
919        }
920        Stream.of(args).forEach(commands::add);
921        ProcessBuilder p = new ProcessBuilder(commands);
922        if (stdinSource != null)
923            p.redirectInput(stdinSource);
924        return run(p);
925    }
926
927    static Result jar(String... args) {
928        return jarWithStdin(null, args);
929    }
930
931    static Path compileModule(String mn) throws IOException {
932        return compileModule(mn, null);
933    }
934
935    static Path compileModule(String mn, Path mp)
936        throws IOException
937    {
938        Path sourcePath = TEST_SRC.resolve("src").resolve(mn);
939        Path build = Files.createDirectories(MODULE_CLASSES.resolve(mn));
940        javac(build, mp, sourceList(sourcePath));
941        return build;
942    }
943
944    static void copyResource(Path srcDir, Path dir, String resource)
945        throws IOException
946    {
947        Path dest = dir.resolve(resource);
948        Files.deleteIfExists(dest);
949
950        Files.createDirectories(dest.getParent());
951        Files.copy(srcDir.resolve(resource), dest);
952    }
953
954    static void setupMRJARModuleInfo(String moduleName) throws IOException {
955        Path modClasses = MODULE_CLASSES.resolve(moduleName);
956        Path metaInfDir = MRJAR_DIR.resolve(moduleName).resolve("META-INF");
957        Path versionSection = metaInfDir.resolve("versions").resolve("9");
958        createTestDir(versionSection);
959
960        Path versionModuleInfo = versionSection.resolve("module-info.class");
961        System.out.println("copying " + modClasses.resolve("module-info.class") + " to " + versionModuleInfo);
962        Files.copy(modClasses.resolve("module-info.class"), versionModuleInfo);
963
964        Manifest manifest = new Manifest();
965        manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
966        manifest.getMainAttributes().putValue("Multi-Release", "true");
967        try (OutputStream os = Files.newOutputStream(metaInfDir.resolve("MANIFEST.MF"))) {
968            manifest.write(os);
969        }
970    }
971
972    static ModuleDescriptor getModuleDescriptor(Path jar) {
973        ClassLoader cl = ClassLoader.getSystemClassLoader();
974        try (JarFile jf = new JarFile(jar.toFile())) {
975            JarEntry entry = jf.getJarEntry("module-info.class");
976            try (InputStream in = jf.getInputStream(entry)) {
977                return ModuleDescriptor.read(in);
978            }
979        } catch (IOException ioe) {
980            throw new UncheckedIOException(ioe);
981        }
982    }
983
984    // Re-enable when there is support in javax.tools for module path
985//    static void javac(Path dest, Path... sourceFiles) throws IOException {
986//        out.printf("Compiling %d source files %s%n", sourceFiles.length,
987//                   Arrays.asList(sourceFiles));
988//        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
989//        try (StandardJavaFileManager fileManager =
990//                     compiler.getStandardFileManager(null, null, null)) {
991//
992//            List<File> files = Stream.of(sourceFiles)
993//                                     .map(p -> p.toFile())
994//                                     .collect(Collectors.toList());
995//            List<File> dests = Stream.of(dest)
996//                                     .map(p -> p.toFile())
997//                                     .collect(Collectors.toList());
998//            Iterable<? extends JavaFileObject> compilationUnits =
999//                    fileManager.getJavaFileObjectsFromFiles(files);
1000//            fileManager.setLocation(StandardLocation.CLASS_OUTPUT, dests);
1001//            JavaCompiler.CompilationTask task =
1002//                    compiler.getTask(null, fileManager, null, null, null, compilationUnits);
1003//            boolean passed = task.call();
1004//            if (!passed)
1005//                throw new RuntimeException("Error compiling " + files);
1006//        }
1007//    }
1008
1009    static void javac(Path dest, Path... sourceFiles) throws IOException {
1010        javac(dest, null, sourceFiles);
1011    }
1012
1013    static void javac(Path dest, Path modulePath, Path... sourceFiles)
1014        throws IOException
1015    {
1016        String javac = getJDKTool("javac");
1017
1018        List<String> commands = new ArrayList<>();
1019        commands.add(javac);
1020        if (!TOOL_VM_OPTIONS.isEmpty()) {
1021            commands.addAll(Arrays.asList(TOOL_VM_OPTIONS.split("\\s+", -1)));
1022        }
1023        commands.add("-d");
1024        commands.add(dest.toString());
1025        if (dest.toString().contains("bar")) {
1026            commands.add("--add-exports");
1027            commands.add("java.base/jdk.internal.misc=bar");
1028            commands.add("--add-exports");
1029            commands.add("java.base/jdk.internal.module=bar");
1030        }
1031        if (modulePath != null) {
1032            commands.add("--module-path");
1033            commands.add(modulePath.toString());
1034        }
1035        Stream.of(sourceFiles).map(Object::toString).forEach(x -> commands.add(x));
1036
1037        quickFail(run(new ProcessBuilder(commands)));
1038    }
1039
1040    static Result java(Path modulePath, String entryPoint, String... args) {
1041        String java = getJDKTool("java");
1042
1043        List<String> commands = new ArrayList<>();
1044        commands.add(java);
1045        if (!VM_OPTIONS.isEmpty()) {
1046            commands.addAll(Arrays.asList(VM_OPTIONS.split("\\s+", -1)));
1047        }
1048        if (!JAVA_OPTIONS.isEmpty()) {
1049            commands.addAll(Arrays.asList(JAVA_OPTIONS.split("\\s+", -1)));
1050        }
1051        Stream.of(args).forEach(x -> commands.add(x));
1052        commands.add("--module-path");
1053        commands.add(modulePath.toString());
1054        commands.add("-m");
1055        commands.add(entryPoint);
1056
1057        return run(new ProcessBuilder(commands));
1058    }
1059
1060    static Path[] sourceList(Path directory) throws IOException {
1061        return Files.find(directory, Integer.MAX_VALUE,
1062                          (file, attrs) -> (file.toString().endsWith(".java")))
1063                    .toArray(Path[]::new);
1064    }
1065
1066    static void createTestDir(Path p) throws IOException{
1067        if (Files.exists(p))
1068            FileUtils.deleteFileTreeWithRetry(p);
1069        Files.createDirectories(p);
1070    }
1071
1072    static boolean jarContains(JarInputStream jis, String entryName)
1073        throws IOException
1074    {
1075        JarEntry e;
1076        while((e = jis.getNextJarEntry()) != null) {
1077            if (e.getName().equals(entryName))
1078                return true;
1079        }
1080        return false;
1081    }
1082
1083    static void quickFail(Result r) {
1084        if (r.ec != 0)
1085            throw new RuntimeException(r.output);
1086    }
1087
1088    static Result run(ProcessBuilder pb) {
1089        Process p;
1090        out.printf("Running: %s%n", pb.command());
1091        try {
1092            p = pb.start();
1093        } catch (IOException e) {
1094            throw new RuntimeException(
1095                    format("Couldn't start process '%s'", pb.command()), e);
1096        }
1097
1098        String output;
1099        try {
1100            output = toString(p.getInputStream(), p.getErrorStream());
1101        } catch (IOException e) {
1102            throw new RuntimeException(
1103                    format("Couldn't read process output '%s'", pb.command()), e);
1104        }
1105
1106        try {
1107            p.waitFor();
1108        } catch (InterruptedException e) {
1109            throw new RuntimeException(
1110                    format("Process hasn't finished '%s'", pb.command()), e);
1111        }
1112        return new Result(p.exitValue(), output);
1113    }
1114
1115    static final String DEFAULT_IMAGE_BIN = System.getProperty("java.home")
1116            + File.separator + "bin" + File.separator;
1117
1118    static String getJDKTool(String name) {
1119        try {
1120            return JDKToolFinder.getJDKTool(name);
1121        } catch (Exception x) {
1122            return DEFAULT_IMAGE_BIN + name;
1123        }
1124    }
1125
1126    static String toString(InputStream in1, InputStream in2) throws IOException {
1127        try (ByteArrayOutputStream dst = new ByteArrayOutputStream();
1128             InputStream concatenated = new SequenceInputStream(in1, in2)) {
1129            concatenated.transferTo(dst);
1130            return new String(dst.toByteArray(), "UTF-8");
1131        }
1132    }
1133
1134    static class Result {
1135        final int ec;
1136        final String output;
1137
1138        private Result(int ec, String output) {
1139            this.ec = ec;
1140            this.output = output;
1141        }
1142        Result assertSuccess() {
1143            assertTrue(ec == 0, "Expected ec 0, got: ", ec, " , output [", output, "]");
1144            return this;
1145        }
1146        Result assertFailure() {
1147            assertTrue(ec != 0, "Expected ec != 0, got:", ec, " , output [", output, "]");
1148            return this;
1149        }
1150        Result resultChecker(Consumer<Result> r) { r.accept(this); return this; }
1151    }
1152
1153    static void assertTrue(boolean cond, Object ... failedArgs) {
1154        if (cond)
1155            return;
1156        StringBuilder sb = new StringBuilder();
1157        for (Object o : failedArgs)
1158            sb.append(o);
1159        org.testng.Assert.assertTrue(false, sb.toString());
1160    }
1161
1162    // Standalone entry point.
1163    public static void main(String[] args) throws Throwable {
1164        Basic test = new Basic();
1165        test.compileModules();
1166        for (Method m : Basic.class.getDeclaredMethods()) {
1167            if (m.getAnnotation(Test.class) != null) {
1168                System.out.println("Invoking " + m.getName());
1169                m.invoke(test);
1170            }
1171        }
1172    }
1173}
1174