AutomaticModulesTest.java revision 13901:b2a69d66dc65
1/*
2 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/**
25 * @test
26 * @library /lib/testlibrary
27 * @build AutomaticModulesTest ModuleUtils
28 * @run testng AutomaticModulesTest
29 * @summary Basic tests for automatic modules
30 */
31
32import java.io.IOException;
33import java.io.OutputStream;
34import java.lang.module.Configuration;
35import java.lang.module.ModuleDescriptor;
36import java.lang.module.ModuleDescriptor.Exports;
37import java.lang.module.ModuleDescriptor.Requires.Modifier;
38import java.lang.module.ModuleFinder;
39import java.lang.module.ModuleReference;
40import java.lang.module.ResolvedModule;
41import java.lang.reflect.Layer;
42import java.lang.reflect.Module;
43import java.nio.file.Files;
44import java.nio.file.Path;
45import java.nio.file.Paths;
46import java.util.Optional;
47import java.util.Set;
48import java.util.jar.Attributes;
49import java.util.jar.JarEntry;
50import java.util.jar.JarFile;
51import java.util.jar.JarOutputStream;
52import java.util.jar.Manifest;
53import java.util.stream.Collectors;
54
55import org.testng.annotations.DataProvider;
56import org.testng.annotations.Test;
57import static org.testng.Assert.*;
58
59@Test
60public class AutomaticModulesTest {
61
62    private static final Path USER_DIR
63         = Paths.get(System.getProperty("user.dir"));
64
65
66    @DataProvider(name = "names")
67    public Object[][] createNames() {
68
69        return new Object[][] {
70
71            // JAR file name                module-name[/version]
72
73            { "foo.jar",                    "foo" },
74
75            { "foo-1.jar",                  "foo/1" },
76            { "foo-1.2.jar",                "foo/1.2" },
77            { "foo-1.2.3.jar",              "foo/1.2.3" },
78            { "foo-1.2.3.4.jar",            "foo/1.2.3.4" },
79
80            { "foo-10.jar",                 "foo/10" },
81            { "foo-10.20.jar",              "foo/10.20" },
82            { "foo-10.20.30.jar",           "foo/10.20.30" },
83            { "foo-10.20.30.40.jar",        "foo/10.20.30.40" },
84
85            { "foo-bar.jar",                "foo.bar" },
86            { "foo-bar-1.jar",              "foo.bar/1" },
87            { "foo-bar-1.2.jar",            "foo.bar/1.2"},
88            { "foo-bar-10.jar",             "foo.bar/10" },
89            { "foo-bar-10.20.jar",          "foo.bar/10.20" },
90
91            { "foo-1.2-SNAPSHOT.jar",       "foo/1.2-SNAPSHOT" },
92            { "foo-bar-1.2-SNAPSHOT.jar",   "foo.bar/1.2-SNAPSHOT" },
93
94            { "foo--bar-1.0.jar",           "foo.bar/1.0" },
95            { "-foo-bar-1.0.jar",           "foo.bar/1.0" },
96            { "foo-bar--1.0.jar",           "foo.bar/1.0" },
97
98        };
99    }
100
101    /**
102     * Test mapping of JAR file names to module names
103     */
104    @Test(dataProvider = "names")
105    public void testNames(String fn, String mid) throws IOException {
106
107        String[] s = mid.split("/");
108        String mn = s[0];
109        String vs = (s.length == 2) ? s[1] : null;
110
111        Path dir = Files.createTempDirectory(USER_DIR, "mods");
112        Path jf = dir.resolve(fn);
113
114        // create empty JAR file
115        createJarFile(jf);
116
117        // create a ModuleFinder to find modules in the directory
118        ModuleFinder finder = ModuleFinder.of(dir);
119
120        // a module with the expected name should be found
121        Optional<ModuleReference> mref = finder.find(mn);
122        assertTrue(mref.isPresent(), mn + " not found");
123
124        ModuleDescriptor descriptor = mref.get().descriptor();
125        assertEquals(descriptor.name(), mn);
126        if (vs == null) {
127            assertFalse(descriptor.version().isPresent());
128        } else {
129            assertEquals(descriptor.version().get().toString(), vs);
130        }
131
132    }
133
134
135    /**
136     * Test all packages are exported
137     */
138    public void testExports() throws IOException {
139        Path dir = Files.createTempDirectory(USER_DIR, "mods");
140        createJarFile(dir.resolve("m1.jar"),
141                      "p/C1.class", "p/C2.class", "q/C1.class");
142
143        ModuleFinder finder = ModuleFinder.of(dir);
144
145        Configuration parent = Layer.boot().configuration();
146        Configuration cf = resolve(parent, finder, "m1");
147
148        ModuleDescriptor m1 = findDescriptor(cf, "m1");
149
150        Set<String> exports
151            = m1.exports().stream().map(Exports::source).collect(Collectors.toSet());
152
153        assertTrue(exports.size() == 2);
154        assertTrue(exports.contains("p"));
155        assertTrue(exports.contains("q"));
156        assertTrue(m1.conceals().isEmpty());
157    }
158
159
160    /**
161     * Test that a JAR file with a Main-Class attribute results
162     * in a module with a main class.
163     */
164    public void testMainClass() throws IOException {
165        String mainClass = "p.Main";
166
167        Manifest man = new Manifest();
168        Attributes attrs = man.getMainAttributes();
169        attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0.0");
170        attrs.put(Attributes.Name.MAIN_CLASS, mainClass);
171
172        Path dir = Files.createTempDirectory(USER_DIR, "mods");
173        createJarFile(dir.resolve("m1.jar"), man);
174
175        ModuleFinder finder = ModuleFinder.of(dir);
176
177        Configuration parent = Layer.boot().configuration();
178        Configuration cf = resolve(parent, finder, "m1");
179
180        ModuleDescriptor m1 = findDescriptor(cf, "m1");
181
182        assertTrue(m1.mainClass().isPresent());
183        assertEquals(m1.mainClass().get(), mainClass);
184    }
185
186
187    /**
188     * Basic test of a configuration created with automatic modules.
189     *   m1 requires m2*
190     *   m1 requires m3*
191     *   m2*
192     *   m3*
193     */
194    public void testConfiguration1() throws Exception {
195        ModuleDescriptor descriptor1
196            = new ModuleDescriptor.Builder("m1")
197                .requires("m2")
198                .requires("m3")
199                .requires("java.base")
200                .build();
201
202        // m2 and m3 are automatic modules
203        Path dir = Files.createTempDirectory(USER_DIR, "mods");
204        createJarFile(dir.resolve("m2.jar"), "p/T.class");
205        createJarFile(dir.resolve("m3.jar"), "q/T.class");
206
207        // module finder locates m1 and the modules in the directory
208        ModuleFinder finder
209            = ModuleFinder.compose(ModuleUtils.finderOf(descriptor1),
210                ModuleFinder.of(dir));
211
212        Configuration parent = Layer.boot().configuration();
213        Configuration cf = resolve(parent, finder, "m1");
214
215        assertTrue(cf.modules().size() == 3);
216        assertTrue(cf.findModule("m1").isPresent());
217        assertTrue(cf.findModule("m2").isPresent());
218        assertTrue(cf.findModule("m3").isPresent());
219
220        ResolvedModule base = cf.findModule("java.base").get();
221        assertTrue(base.configuration() == Layer.boot().configuration());
222        ResolvedModule m1 = cf.findModule("m1").get();
223        ResolvedModule m2 = cf.findModule("m2").get();
224        ResolvedModule m3 = cf.findModule("m3").get();
225
226        // m2 && m3 only require java.base
227        assertTrue(m2.reference().descriptor().requires().size() == 1);
228        assertTrue(m3.reference().descriptor().requires().size() == 1);
229
230        // readability
231
232        assertTrue(m1.reads().size() == 3);
233        assertTrue(m1.reads().contains(base));
234        assertTrue(m1.reads().contains(m2));
235        assertTrue(m1.reads().contains(m3));
236
237        assertTrue(m2.reads().contains(m1));
238        assertTrue(m2.reads().contains(m3));
239        testReadAllBootModules(cf, "m2");  // m2 reads all modules in boot layer
240
241        assertTrue(m3.reads().contains(m1));
242        assertTrue(m3.reads().contains(m2));
243        testReadAllBootModules(cf, "m3");  // m3 reads all modules in boot layer
244
245    }
246
247    /**
248     * Basic test of a configuration created with automatic modules
249     *   m1 requires m2
250     *   m2 requires m3*
251     *   m3*
252     *   m4*
253     */
254    public void testInConfiguration2() throws IOException {
255
256        ModuleDescriptor descriptor1
257            =  new ModuleDescriptor.Builder("m1")
258                .requires("m2")
259                .requires("java.base")
260                .build();
261
262        ModuleDescriptor descriptor2
263            =  new ModuleDescriptor.Builder("m2")
264                .requires("m3")
265                .requires("java.base")
266                .build();
267
268        // m3 and m4 are automatic modules
269        Path dir = Files.createTempDirectory(USER_DIR, "mods");
270        createJarFile(dir.resolve("m3.jar"), "p/T.class");
271        createJarFile(dir.resolve("m4.jar"), "q/T.class");
272
273        // module finder locates m1 and the modules in the directory
274        ModuleFinder finder
275            = ModuleFinder.compose(ModuleUtils.finderOf(descriptor1, descriptor2),
276                                   ModuleFinder.of(dir));
277
278        Configuration parent = Layer.boot().configuration();
279        Configuration cf = resolve(parent, finder, "m1", "m4");
280
281        assertTrue(cf.modules().size() == 4);
282        assertTrue(cf.findModule("m1").isPresent());
283        assertTrue(cf.findModule("m2").isPresent());
284        assertTrue(cf.findModule("m3").isPresent());
285        assertTrue(cf.findModule("m4").isPresent());
286
287        // m3 && m4 should only require java.base
288        assertTrue(findDescriptor(cf, "m3").requires().size() == 1);
289        assertTrue(findDescriptor(cf, "m4").requires().size() == 1);
290
291        // readability
292
293        ResolvedModule base = cf.findModule("java.base").get();
294        assertTrue(base.configuration() == Layer.boot().configuration());
295        ResolvedModule m1 = cf.findModule("m1").get();
296        ResolvedModule m2 = cf.findModule("m2").get();
297        ResolvedModule m3 = cf.findModule("m3").get();
298        ResolvedModule m4 = cf.findModule("m4").get();
299
300        assertTrue(m1.reads().size() == 2);
301        assertTrue(m1.reads().contains(m2));
302        assertTrue(m1.reads().contains(base));
303
304        assertTrue(m2.reads().size() == 3);
305        assertTrue(m2.reads().contains(m3));
306        assertTrue(m2.reads().contains(m4));
307        assertTrue(m2.reads().contains(base));
308
309        assertTrue(m3.reads().contains(m1));
310        assertTrue(m3.reads().contains(m2));
311        assertTrue(m3.reads().contains(m4));
312        testReadAllBootModules(cf, "m3");   // m3 reads all modules in boot layer
313
314        assertTrue(m4.reads().contains(m1));
315        assertTrue(m4.reads().contains(m2));
316        assertTrue(m4.reads().contains(m3));
317        testReadAllBootModules(cf, "m4");    // m4 reads all modules in boot layer
318
319    }
320
321
322    /**
323     * Basic test of a configuration created with automatic modules
324     *   m1 requires m2
325     *   m2 requires public m3*
326     *   m3*
327     *   m4*
328     */
329    public void testInConfiguration3() throws IOException {
330
331        ModuleDescriptor descriptor1
332            =  new ModuleDescriptor.Builder("m1")
333                .requires("m2")
334                .requires("java.base")
335                .build();
336
337        ModuleDescriptor descriptor2
338            =  new ModuleDescriptor.Builder("m2")
339                .requires(Modifier.PUBLIC, "m3")
340                .requires("java.base")
341                .build();
342
343        // m3 and m4 are automatic modules
344        Path dir = Files.createTempDirectory(USER_DIR, "mods");
345        createJarFile(dir.resolve("m3.jar"), "p/T.class");
346        createJarFile(dir.resolve("m4.jar"), "q/T.class");
347
348        // module finder locates m1 and the modules in the directory
349        ModuleFinder finder
350            = ModuleFinder.compose(ModuleUtils.finderOf(descriptor1, descriptor2),
351                ModuleFinder.of(dir));
352
353        Configuration parent = Layer.boot().configuration();
354        Configuration cf = resolve(parent, finder, "m1", "m4");
355
356        assertTrue(cf.modules().size() == 4);
357        assertTrue(cf.findModule("m1").isPresent());
358        assertTrue(cf.findModule("m2").isPresent());
359        assertTrue(cf.findModule("m3").isPresent());
360        assertTrue(cf.findModule("m4").isPresent());
361
362        ResolvedModule base = cf.findModule("java.base").get();
363        assertTrue(base.configuration() == Layer.boot().configuration());
364        ResolvedModule m1 = cf.findModule("m1").get();
365        ResolvedModule m2 = cf.findModule("m2").get();
366        ResolvedModule m3 = cf.findModule("m3").get();
367        ResolvedModule m4 = cf.findModule("m4").get();
368
369        // m3 && m4 should only require java.base
370        assertTrue(findDescriptor(cf, "m3").requires().size() == 1);
371        assertTrue(findDescriptor(cf, "m4").requires().size() == 1);
372
373        // readability
374
375        assertTrue(m1.reads().size() == 4);
376        assertTrue(m1.reads().contains(m2));
377        assertTrue(m1.reads().contains(m3));
378        assertTrue(m1.reads().contains(m4));
379        assertTrue(m1.reads().contains(base));
380
381        assertTrue(m2.reads().size() == 3);
382        assertTrue(m2.reads().contains(m3));
383        assertTrue(m2.reads().contains(m4));
384        assertTrue(m2.reads().contains(base));
385
386        assertTrue(reads(cf, "m2", "m3"));
387        assertTrue(reads(cf, "m2", "m4"));
388        assertTrue(reads(cf, "m2", "java.base"));
389
390        assertTrue(m3.reads().contains(m1));
391        assertTrue(m3.reads().contains(m2));
392        assertTrue(m3.reads().contains(m4));
393        testReadAllBootModules(cf, "m3");   // m3 reads all modules in boot layer
394
395        assertTrue(m4.reads().contains(m1));
396        assertTrue(m4.reads().contains(m2));
397        assertTrue(m4.reads().contains(m3));
398        testReadAllBootModules(cf, "m4");    // m4 reads all modules in boot layer
399
400    }
401
402
403    /**
404     * Basic test of Layer containing automatic modules
405     */
406    public void testInLayer() throws IOException {
407        ModuleDescriptor descriptor
408            =  new ModuleDescriptor.Builder("m1")
409                .requires("m2")
410                .requires("m3")
411                .build();
412
413        // m2 and m3 are simple JAR files
414        Path dir = Files.createTempDirectory(USER_DIR, "mods");
415        createJarFile(dir.resolve("m2.jar"), "p/T.class");
416        createJarFile(dir.resolve("m3.jar"), "q/T2.class");
417
418        // module finder locates m1 and the modules in the directory
419        ModuleFinder finder
420            = ModuleFinder.compose(ModuleUtils.finderOf(descriptor),
421                ModuleFinder.of(dir));
422
423        Configuration parent = Layer.boot().configuration();
424        Configuration cf = resolve(parent, finder, "m1");
425        assertTrue(cf.modules().size() == 3);
426
427        // each module gets its own loader
428        Layer layer = Layer.boot().defineModules(cf, mn -> new ClassLoader() { });
429
430        // an unnamed module
431        Module unnamed = (new ClassLoader() { }).getUnnamedModule();
432
433        Module m2 = layer.findModule("m2").get();
434        assertTrue(m2.isNamed());
435        assertTrue(m2.canRead(unnamed));
436        testsReadsAll(m2, layer);
437
438        Module m3 = layer.findModule("m3").get();
439        assertTrue(m3.isNamed());
440        assertTrue(m2.canRead(unnamed));
441        testsReadsAll(m3, layer);
442    }
443
444
445    /**
446     * Test miscellaneous methods.
447     */
448    public void testMisc() throws IOException {
449        Path dir = Files.createTempDirectory(USER_DIR, "mods");
450        Path m1_jar = createJarFile(dir.resolve("m1.jar"), "p/T.class");
451
452        ModuleFinder finder = ModuleFinder.of(m1_jar);
453
454        assertTrue(finder.find("m1").isPresent());
455        ModuleDescriptor m1 = finder.find("m1").get().descriptor();
456
457        // test miscellaneous methods
458        assertTrue(m1.isAutomatic());
459        assertFalse(m1.isSynthetic());
460        assertFalse(m1.osName().isPresent());
461        assertFalse(m1.osArch().isPresent());
462        assertFalse(m1.osVersion().isPresent());
463    }
464
465
466    /**
467     * Invokes parent.resolveRequires to resolve the given root modules.
468     */
469    static Configuration resolve(Configuration parent,
470                                 ModuleFinder finder,
471                                 String... roots) {
472        return parent.resolveRequires(finder, ModuleFinder.empty(), Set.of(roots));
473    }
474
475    /**
476     * Finds a module in the given configuration or its parents, returning
477     * the module descriptor (or null if not found)
478     */
479    static ModuleDescriptor findDescriptor(Configuration cf, String name) {
480        Optional<ResolvedModule> om = cf.findModule(name);
481        if (om.isPresent()) {
482            return om.get().reference().descriptor();
483        } else {
484            return null;
485        }
486    }
487
488    /**
489     * Test that a module in a configuration reads all modules in the boot
490     * configuration.
491     */
492    static void testReadAllBootModules(Configuration cf, String mn) {
493
494        Set<String> bootModules = Layer.boot().modules().stream()
495                .map(Module::getName)
496                .collect(Collectors.toSet());
497
498        bootModules.forEach(other -> assertTrue(reads(cf, mn, other)));
499
500    }
501
502    /**
503     * Test that the given Module reads all module in the given Layer
504     * and its parent Layers.
505     */
506    static void testsReadsAll(Module m, Layer layer) {
507        while (layer != Layer.empty()) {
508
509            // check that m reads all module in the layer
510            layer.configuration().modules().stream()
511                .map(ResolvedModule::name)
512                .map(layer::findModule)
513                .map(Optional::get)
514                .forEach(m2 -> assertTrue(m.canRead(m2)));
515
516            layer = layer.parent().get();
517        }
518    }
519
520    /**
521     * Returns {@code true} if the configuration contains module mn1
522     * that reads module mn2.
523     */
524    static boolean reads(Configuration cf, String mn1, String mn2) {
525        Optional<ResolvedModule> om1 = cf.findModule(mn1);
526        if (!om1.isPresent())
527            return false;
528
529        return om1.get().reads().stream()
530                .map(ResolvedModule::name)
531                .anyMatch(mn2::equals);
532    }
533
534    /**
535     * Creates a JAR file, optionally with a manifest, and with the given
536     * entries. The entries will be empty in the resulting JAR file.
537     */
538    static Path createJarFile(Path file, Manifest man, String... entries)
539        throws IOException
540    {
541        try (OutputStream out = Files.newOutputStream(file)) {
542            try (JarOutputStream jos = new JarOutputStream(out)) {
543
544                if (man != null) {
545                    JarEntry je = new JarEntry(JarFile.MANIFEST_NAME);
546                    jos.putNextEntry(je);
547                    man.write(jos);
548                    jos.closeEntry();
549                }
550
551                for (String entry : entries) {
552                    JarEntry je = new JarEntry(entry);
553                    jos.putNextEntry(je);
554                    jos.closeEntry();
555                }
556
557            }
558        }
559        return file;
560    }
561
562    /**
563     * Creates a JAR file and with the given entries. The entries will be empty
564     * in the resulting JAR file.
565     */
566    static Path createJarFile(Path file, String... entries)
567        throws IOException
568    {
569        return createJarFile(file, null, entries);
570    }
571
572}
573