1 /*
2 * Copyright (c) 2019, 2024, 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 jdk.test.lib.util.ModuleInfoWriter
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
47 import static java.lang.module.ModuleDescriptor.newModule;
48
49 import jdk.internal.module.ModuleResolution;
50
51 import org.testng.annotations.Test;
52
53 import jdk.test.lib.process.ProcessTools;
54 import jdk.test.lib.process.OutputAnalyzer;
55 import jdk.test.lib.util.ModuleInfoWriter;
56
57 @Test
58 public class ServiceBinding {
59 private static final Path HERE = Path.of(".");
60
61 /**
62 * module m1 uses p.S
63 * (incubating) module m2 requires m1 provides p.S
64 */
65 public void test1() throws Exception {
66 Path mlib = Files.createTempDirectory(HERE, "mlib");
67
68 var m1 = newModule("m1").exports("p").uses("p.S").build();
69 var m2 = newModule("m2").requires("m1").provides("p.S", List.of("impl.S1")).build();
70
71 writeModule(mlib, m1);
72 writeIncubatingModule(mlib, m2);
73
74 // boot layer: root=m1, incubator module m2 should not be resolved
75 testBootLayer(mlib, Set.of("m1"), Set.of("m1"), Set.of("m2"))
76 .shouldNotMatch("WARNING:.*m2");
77
78 // custom configuration: root=m1, incubator module m2 should be resolved
79 testCustomConfiguration(mlib, Set.of("m1"), Set.of("m2"));
80 }
81
82 /**
83 * module m1 uses p.S
84 * (incubating) module m2 requires m1 provides P.S uses q.S
85 * (incubating) module m3 requires m2 provides q.S
86 */
87 public void test2() throws Exception {
88 Path mlib = Files.createTempDirectory("mlib");
89
90 var m1 = newModule("m1").exports("p").uses("p.S").build();
91 var m2 = newModule("m2")
92 .requires("m1")
93 .provides("p.S", List.of("impl.S1"))
94 .exports("q")
95 .uses("q.S")
96 .build();
97 var m3 = newModule("m3").requires("m2").provides("q.S", List.of("impl.S1")).build();
98
99 writeModule(mlib, m1);
100 writeIncubatingModule(mlib, m2);
101 writeIncubatingModule(mlib, m3);
102
103 // boot layer: root=m1, incubator modules m2 and m3 should not be resolved
104 testBootLayer(mlib, Set.of("m1"), Set.of("m1"), Set.of("m2", "m3"))
105 .shouldNotMatch("WARNING:.*m2")
106 .shouldNotMatch("WARNING:.*m3");
107
108 // boot layer: root=m2, incubator module m3 should not be resolved
109 testBootLayer(mlib, Set.of("m2"), Set.of("m1", "m2"), Set.of("m3"))
110 .shouldMatch("WARNING:.*m2")
111 .shouldNotMatch("WARNING:.*m3");
112
113 // custom configuration: root=m1, incubator modules m2 and m3 should be resolved
114 testCustomConfiguration(mlib, Set.of("m1"), Set.of("m1", "m2", "m3"));
115
116 // custom configuration: root=m2, incubator module m3 should be resolved
117 testCustomConfiguration(mlib, Set.of("m2"), Set.of("m1", "m2", "m3"));
118 }
119
120 /**
121 * Creates an exploded module on the file system.
122 *
123 * @param mlib the top-level module directory
124 * @param descriptor the module descriptor of the module to write
125 */
126 void writeModule(Path mlib, ModuleDescriptor descriptor) throws Exception {
127 writeModule(mlib, descriptor, false);
128 }
129
130 /**
131 * Creates an exploded module on the file system. The module will be an
132 * incubating module.
133 *
134 * @param mlib the top-level module directory
135 * @param descriptor the module descriptor of the module to write
136 */
137 void writeIncubatingModule(Path mlib, ModuleDescriptor descriptor) throws Exception {
138 writeModule(mlib, descriptor, true);
139 }
140
141 /**
142 * Creates an exploded module on the file system.
143 *
144 * @param mlib the top-level module directory
145 * @param descriptor the module descriptor of the module to write
146 * @param incubating to create an incubating module
147 */
148 void writeModule(Path mlib, ModuleDescriptor descriptor, boolean incubating)
149 throws Exception
150 {
151 // create ModuleResolution attribute if incubating module
152 ModuleResolution mres = (incubating) ? ModuleResolution.empty().withIncubating() : null;
153 String name = descriptor.name();
154
155 // create directory for module
156 Path dir = Files.createDirectory(mlib.resolve(name));
157
158 // module-info.class
159 try (OutputStream out = Files.newOutputStream(dir.resolve("module-info.class"))) {
160 ModuleInfoWriter.write(descriptor, mres, out);
161 }
162
163 // create a dummy class file for each package
164 for (String pn : descriptor.packages()) {
165 Path subdir = dir.resolve(pn.replace('.', File.separatorChar));
166 Files.createDirectories(subdir);
167 Files.createFile(subdir.resolve("C.class"));
168 }
169 }
170
171 /**
172 * Run TestBootLayer in a child VM with the given module path and the
173 * --add-modules option with additional root modules. TestBootLayer checks
174 * the modules in the boot layer.
175 *
176 * @param mlib the module path
177 * @param roots the modules to specify to --add-modules
178 * @param expected the names of modules that should be in the boot layer
179 * @param notExpected the names of modules that should not be in boot layer
180 */
181 OutputAnalyzer testBootLayer(Path mlib,
182 Set<String> roots,
183 Set<String> expected,
184 Set<String> notExpected)
185 throws Exception
186 {
187 var opts = Stream.of("-p", mlib.toString(),
188 "--add-modules", commaSeparated(roots),
189 "TestBootLayer", commaSeparated(expected), commaSeparated(notExpected));
190 return ProcessTools.executeTestJava(opts.toArray(String[]::new))
191 .outputTo(System.out)
192 .errorTo(System.out)
193 .shouldHaveExitValue(0);
194 }
195
196 /**
197 * Creates a Configuration by resolving a set of root modules, with service
198 * binding, then checks that the Configuration includes the expected modules.
199 *
200 * @param mlib the module path
201 * @param roots the names of the root modules
202 * @param expected the names of modules that should be in the configuration
203 */
204 void testCustomConfiguration(Path mlib, Set<String> roots, Set<String> expected) {
205 ModuleFinder finder = ModuleFinder.of(mlib);
206 Configuration cf = ModuleLayer.boot()
207 .configuration()
208 .resolveAndBind(finder, ModuleFinder.of(), roots);
209
210 Set<String> modules = cf.modules().stream()
211 .map(ResolvedModule::name)
212 .collect(Collectors.toSet());
213
214 expected.stream()
215 .filter(mn -> !modules.contains(mn))
216 .findAny()
217 .ifPresent(mn -> {
218 throw new RuntimeException(mn + " not in configuration!!!");
219 });
220 }
221
222 String commaSeparated(Set<String> s) {
223 return s.stream().collect(Collectors.joining(","));
224 }
225 }
--- EOF ---