1 /*
2  * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 /**
25  * @test
26  * @bug 8233922
27  * @modules java.base/jdk.internal.module
28  * @library /test/lib
29  * @build ServiceBinding TestBootLayer
30  * @run testng ServiceBinding
31  * @summary Test service binding with incubator modules
32  */
33 
34 import java.io.File;
35 import java.io.OutputStream;
36 import java.lang.module.ModuleDescriptor;
37 import java.lang.module.Configuration;
38 import java.lang.module.ModuleFinder;
39 import java.lang.module.ResolvedModule;
40 import java.nio.file.Path;
41 import java.nio.file.Files;
42 import java.util.List;
43 import java.util.Set;
44 import java.util.stream.Collectors;
45 import java.util.stream.Stream;
46 import java.util.stream.Stream;
47 
48 import static java.lang.module.ModuleDescriptor.newModule;
49 
50 import jdk.internal.module.ModuleInfoWriter;
51 import jdk.internal.module.ModuleResolution;
52 
53 import org.testng.annotations.Test;
54 
55 import jdk.test.lib.process.ProcessTools;
56 import jdk.test.lib.process.OutputAnalyzer;
57 
58 @Test
59 public class ServiceBinding {
60     private static final Path HERE = Path.of(".");
61 
62     /**
63      * module m1 uses p.S
64      * (incubating) module m2 requires m1 provides p.S
65      */
test1()66     public void test1() throws Exception {
67         Path mlib = Files.createTempDirectory(HERE, "mlib");
68 
69         var m1 = newModule("m1").exports("p").uses("p.S").build();
70         var m2 = newModule("m2").requires("m1").provides("p.S", List.of("impl.S1")).build();
71 
72         writeModule(mlib, m1);
73         writeIncubatingModule(mlib, m2);
74 
75         // boot layer: root=m1, incubator module m2 should not be resolved
76         testBootLayer(mlib, Set.of("m1"), Set.of("m1"), Set.of("m2"))
77                 .shouldNotMatch("WARNING:.*m2");
78 
79         // custom configuration: root=m1, incubator module m2 should be resolved
80         testCustomConfiguration(mlib, Set.of("m1"), Set.of("m2"));
81     }
82 
83     /**
84      * module m1 uses p.S
85      * (incubating) module m2 requires m1 provides P.S uses q.S
86      * (incubating) module m3 requires m2 provides q.S
87      */
test2()88     public void test2() throws Exception {
89         Path mlib = Files.createTempDirectory("mlib");
90 
91         var m1 = newModule("m1").exports("p").uses("p.S").build();
92         var m2 = newModule("m2")
93                 .requires("m1")
94                 .provides("p.S", List.of("impl.S1"))
95                 .exports("q")
96                 .uses("q.S")
97                 .build();
98         var m3 = newModule("m3").requires("m2").provides("q.S", List.of("impl.S1")).build();
99 
100         writeModule(mlib, m1);
101         writeIncubatingModule(mlib, m2);
102         writeIncubatingModule(mlib, m3);
103 
104         // boot layer: root=m1, incubator modules m2 and m3 should not be resolved
105         testBootLayer(mlib, Set.of("m1"), Set.of("m1"), Set.of("m2", "m3"))
106                 .shouldNotMatch("WARNING:.*m2")
107                 .shouldNotMatch("WARNING:.*m3");
108 
109         // boot layer: root=m2, incubator module m3 should not be resolved
110         testBootLayer(mlib, Set.of("m2"), Set.of("m1", "m2"), Set.of("m3"))
111                 .shouldMatch("WARNING:.*m2")
112                 .shouldNotMatch("WARNING:.*m3");
113 
114         // custom configuration: root=m1, incubator modules m2 and m3 should be resolved
115         testCustomConfiguration(mlib, Set.of("m1"), Set.of("m1", "m2", "m3"));
116 
117         // custom configuration: root=m2, incubator module m3 should be resolved
118         testCustomConfiguration(mlib, Set.of("m2"), Set.of("m1", "m2", "m3"));
119     }
120 
121     /**
122      * Creates an exploded module on the file system.
123      *
124      * @param mlib the top-level module directory
125      * @param descriptor the module descriptor of the module to write
126      */
writeModule(Path mlib, ModuleDescriptor descriptor)127     void writeModule(Path mlib, ModuleDescriptor descriptor) throws Exception {
128         writeModule(mlib, descriptor, false);
129     }
130 
131     /**
132      * Creates an exploded module on the file system. The module will be an
133      * incubating module.
134      *
135      * @param mlib the top-level module directory
136      * @param descriptor the module descriptor of the module to write
137      */
writeIncubatingModule(Path mlib, ModuleDescriptor descriptor)138     void writeIncubatingModule(Path mlib, ModuleDescriptor descriptor) throws Exception {
139         writeModule(mlib, descriptor, true);
140     }
141 
142     /**
143      * Creates an exploded module on the file system.
144      *
145      * @param mlib the top-level module directory
146      * @param descriptor the module descriptor of the module to write
147      * @param incubating to create an incubating module
148      */
writeModule(Path mlib, ModuleDescriptor descriptor, boolean incubating)149     void writeModule(Path mlib, ModuleDescriptor descriptor, boolean incubating)
150         throws Exception
151     {
152         // create ModuleResolution attribute if incubating module
153         ModuleResolution mres = (incubating) ? ModuleResolution.empty().withIncubating() : null;
154         String name = descriptor.name();
155 
156         // create directory for module
157         Path dir = Files.createDirectory(mlib.resolve(name));
158 
159         // module-info.class
160         try (OutputStream out = Files.newOutputStream(dir.resolve("module-info.class"))) {
161             ModuleInfoWriter.write(descriptor, mres, out);
162         }
163 
164         // create a dummy class file for each package
165         for (String pn : descriptor.packages()) {
166             Path subdir = dir.resolve(pn.replace('.', File.separatorChar));
167             Files.createDirectories(subdir);
168             Files.createFile(subdir.resolve("C.class"));
169         }
170     }
171 
172     /**
173      * Run TestBootLayer in a child VM with the given module path and the
174      * --add-modules option with additional root modules. TestBootLayer checks
175      * the modules in the boot layer.
176      *
177      * @param mlib the module path
178      * @param roots the modules to specify to --add-modules
179      * @param expected the names of modules that should be in the boot layer
180      * @param notExpected the names of modules that should not be in boot layer
181      */
testBootLayer(Path mlib, Set<String> roots, Set<String> expected, Set<String> notExpected)182     OutputAnalyzer testBootLayer(Path mlib,
183                                  Set<String> roots,
184                                  Set<String> expected,
185                                  Set<String> notExpected)
186         throws Exception
187     {
188         var opts = Stream.of("-p", mlib.toString(),
189                              "--add-modules", commaSeparated(roots),
190                              "TestBootLayer", commaSeparated(expected), commaSeparated(notExpected));
191         return ProcessTools.executeTestJava(opts.toArray(String[]::new))
192                 .outputTo(System.out)
193                 .errorTo(System.out)
194                 .shouldHaveExitValue(0);
195     }
196 
197     /**
198      * Creates a Configuration by resolving a set of root modules, with service
199      * binding, then checks that the Configuration includes the expected modules.
200      *
201      * @param mlib the module path
202      * @param roots the names of the root modules
203      * @param expected the names of modules that should be in the configuration
204      */
testCustomConfiguration(Path mlib, Set<String> roots, Set<String> expected)205     void testCustomConfiguration(Path mlib, Set<String> roots, Set<String> expected) {
206         ModuleFinder finder = ModuleFinder.of(mlib);
207         Configuration cf = ModuleLayer.boot()
208                 .configuration()
209                 .resolveAndBind(finder, ModuleFinder.of(), roots);
210 
211         Set<String> modules = cf.modules().stream()
212                 .map(ResolvedModule::name)
213                 .collect(Collectors.toSet());
214 
215         expected.stream()
216                 .filter(mn -> !modules.contains(mn))
217                 .findAny()
218                 .ifPresent(mn -> {
219                     throw new RuntimeException(mn + " not in configuration!!!");
220                 });
221     }
222 
commaSeparated(Set<String> s)223     String commaSeparated(Set<String> s) {
224         return s.stream().collect(Collectors.joining(","));
225     }
226 }
227