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