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