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 ---