1 /*
2 * Copyright (c) 2019, 2023, 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 * @enablePreview
28 * @modules java.base/jdk.internal.module
29 * @library /test/lib
30 * @build ServiceBinding TestBootLayer
31 * @run testng ServiceBinding
32 * @summary Test service binding with incubator modules
33 */
34
35 import java.io.File;
36 import java.io.OutputStream;
37 import java.lang.module.ModuleDescriptor;
38 import java.lang.module.Configuration;
39 import java.lang.module.ModuleFinder;
40 import java.lang.module.ResolvedModule;
41 import java.nio.file.Path;
42 import java.nio.file.Files;
43 import java.util.List;
44 import java.util.Set;
45 import java.util.stream.Collectors;
46 import java.util.stream.Stream;
47
48 import static java.lang.module.ModuleDescriptor.newModule;
49
50 import jdk.internal.module.ModuleResolution;
51
52 import org.testng.annotations.Test;
53
54 import jdk.test.lib.process.ProcessTools;
55 import jdk.test.lib.process.OutputAnalyzer;
56 import jdk.test.lib.util.ModuleInfoWriter;
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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
223 String commaSeparated(Set<String> s) {
224 return s.stream().collect(Collectors.joining(","));
225 }
226 }
--- EOF ---