1/*
2 * Copyright (c) 2017, 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
24import java.io.File;
25import java.io.PrintWriter;
26import java.io.StringWriter;
27import java.nio.file.Files;
28import java.nio.file.Path;
29import java.nio.file.Paths;
30import java.util.ArrayList;
31import java.util.List;
32import java.util.spi.ToolProvider;
33import java.util.stream.Collectors;
34import java.util.stream.Stream;
35import jdk.test.lib.compiler.CompilerUtils;
36
37import org.testng.annotations.BeforeTest;
38import org.testng.annotations.Test;
39import static org.testng.Assert.*;
40
41/**
42 * @test
43 * @bug 8174826
44 * @library /lib/testlibrary /test/lib
45 * @modules jdk.charsets jdk.compiler jdk.jlink
46 * @build SuggestProviders jdk.test.lib.compiler.CompilerUtils
47 * @run testng SuggestProviders
48 */
49
50public class SuggestProviders {
51    private static final String JAVA_HOME = System.getProperty("java.home");
52    private static final String TEST_SRC = System.getProperty("test.src");
53
54    private static final Path SRC_DIR = Paths.get(TEST_SRC, "src");
55    private static final Path MODS_DIR = Paths.get("mods");
56
57    private static final String MODULE_PATH =
58        Paths.get(JAVA_HOME, "jmods").toString() +
59        File.pathSeparator + MODS_DIR.toString();
60
61    // the names of the modules in this test
62    private static String[] modules = new String[] {"m1", "m2", "m3"};
63
64
65    private static boolean hasJmods() {
66        if (!Files.exists(Paths.get(JAVA_HOME, "jmods"))) {
67            System.err.println("Test skipped. NO jmods directory");
68            return false;
69        }
70        return true;
71    }
72
73    /*
74     * Compiles all modules used by the test
75     */
76    @BeforeTest
77    public void compileAll() throws Throwable {
78        if (!hasJmods()) return;
79
80        for (String mn : modules) {
81            Path msrc = SRC_DIR.resolve(mn);
82            assertTrue(CompilerUtils.compile(msrc, MODS_DIR,
83                "--module-source-path", SRC_DIR.toString()));
84        }
85    }
86
87    // check a subset of services used by java.base
88    private final List<String> JAVA_BASE_USES = List.of(
89        "uses java.lang.System$LoggerFinder",
90        "uses java.net.ContentHandlerFactory",
91        "uses java.net.spi.URLStreamHandlerProvider",
92        "uses java.nio.channels.spi.AsynchronousChannelProvider",
93        "uses java.nio.channels.spi.SelectorProvider",
94        "uses java.nio.charset.spi.CharsetProvider",
95        "uses java.nio.file.spi.FileSystemProvider",
96        "uses java.nio.file.spi.FileTypeDetector",
97        "uses java.security.Provider",
98        "uses java.util.spi.ToolProvider"
99    );
100
101    private final List<String> JAVA_BASE_PROVIDERS = List.of(
102        "java.base provides java.nio.file.spi.FileSystemProvider used by java.base"
103    );
104
105    private final List<String> SYSTEM_PROVIDERS = List.of(
106        "jdk.charsets provides java.nio.charset.spi.CharsetProvider used by java.base",
107        "jdk.compiler provides java.util.spi.ToolProvider used by java.base",
108        "jdk.compiler provides javax.tools.JavaCompiler used by java.compiler",
109        "jdk.jlink provides jdk.tools.jlink.plugin.Plugin used by jdk.jlink",
110        "jdk.jlink provides java.util.spi.ToolProvider used by java.base"
111    );
112
113    private final List<String> APP_USES = List.of(
114        "uses p1.S",
115        "uses p2.T"
116    );
117
118    private final List<String> APP_PROVIDERS = List.of(
119        "m1 provides p1.S used by m1",
120        "m2 provides p1.S used by m1",
121        "m2 provides p2.T used by m2",
122        "m3 provides p2.T used by m2",
123        "m3 provides p3.S not used by any observable module"
124    );
125
126    @Test
127    public void suggestProviders() throws Throwable {
128        if (!hasJmods()) return;
129
130        List<String> output = JLink.run("--module-path", MODULE_PATH,
131                                        "--suggest-providers").output();
132
133        Stream<String> uses =
134            Stream.concat(JAVA_BASE_USES.stream(), APP_USES.stream());
135        Stream<String> providers =
136            Stream.concat(SYSTEM_PROVIDERS.stream(), APP_PROVIDERS.stream());
137
138        assertTrue(output.containsAll(Stream.concat(uses, providers)
139                                            .collect(Collectors.toList())));
140    }
141
142    /**
143     * find providers from the observable modules and --add-modules has no
144     * effect on the suggested providers
145     */
146    @Test
147    public void observableModules() throws Throwable {
148        if (!hasJmods()) return;
149
150        List<String> output = JLink.run("--module-path", MODULE_PATH,
151                                        "--add-modules", "m1",
152                                        "--suggest-providers").output();
153
154        Stream<String> uses =
155            Stream.concat(JAVA_BASE_USES.stream(), Stream.of("uses p1.S"));
156        Stream<String> providers =
157            Stream.concat(SYSTEM_PROVIDERS.stream(), APP_PROVIDERS.stream());
158
159        assertTrue(output.containsAll(Stream.concat(uses, providers)
160                                            .collect(Collectors.toList())));
161    }
162
163    /**
164     * find providers from the observable modules with --limit-modules
165     */
166    @Test
167    public void limitModules() throws Throwable {
168        if (!hasJmods()) return;
169
170        List<String> output = JLink.run("--module-path", MODULE_PATH,
171                                        "--limit-modules", "m1",
172                                        "--suggest-providers").output();
173
174        Stream<String> uses =
175            Stream.concat(JAVA_BASE_USES.stream(), Stream.of("uses p1.S"));
176        Stream<String> providers =
177            Stream.concat(JAVA_BASE_PROVIDERS.stream(),
178                          Stream.of("m1 provides p1.S used by m1")
179        );
180
181        assertTrue(output.containsAll(Stream.concat(uses, providers)
182                                            .collect(Collectors.toList())));
183    }
184
185    @Test
186    public void providersForServices() throws Throwable {
187        if (!hasJmods()) return;
188
189        List<String> output =
190            JLink.run("--module-path", MODULE_PATH,
191                      "--suggest-providers",
192                      "java.nio.charset.spi.CharsetProvider,p1.S").output();
193
194        System.out.println(output);
195        Stream<String> expected = Stream.concat(
196            Stream.of("jdk.charsets provides java.nio.charset.spi.CharsetProvider used by java.base"),
197            Stream.of("m1 provides p1.S used by m1",
198                      "m2 provides p1.S used by m1")
199        );
200
201        assertTrue(output.containsAll(expected.collect(Collectors.toList())));
202    }
203
204    @Test
205    public void unusedService() throws Throwable {
206        if (!hasJmods()) return;
207
208        List<String> output =
209            JLink.run("--module-path", MODULE_PATH,
210                      "--suggest-providers",
211                      "p3.S").output();
212
213        List<String> expected = List.of(
214            "m3 provides p3.S not used by any observable module"
215        );
216        assertTrue(output.containsAll(expected));
217
218        // should not print other services m3 provides
219        assertFalse(output.contains("m3 provides p2.T used by m2"));
220    }
221
222    @Test
223    public void nonExistentService() throws Throwable {
224        if (!hasJmods()) return;
225
226        List<String> output =
227            JLink.run("--module-path", MODULE_PATH,
228                      "--suggest-providers",
229                      "nonExistentType").output();
230
231        List<String> expected = List.of(
232            "No provider found for service specified to --suggest-providers: nonExistentType"
233        );
234        assertTrue(output.containsAll(expected));
235    }
236
237    @Test
238    public void noSuggestProviders() throws Throwable {
239        if (!hasJmods()) return;
240
241        List<String> output =
242            JLink.run("--module-path", MODULE_PATH,
243                      "--bind-services",
244                      "--suggest-providers").output();
245
246        String expected = "--bind-services option is specified. No additional providers suggested.";
247        assertTrue(output.contains(expected));
248
249    }
250
251    @Test
252    public void suggestTypeNotRealProvider() throws Throwable {
253        if (!hasJmods()) return;
254
255        List<String> output =
256            JLink.run("--module-path", MODULE_PATH,
257                      "--add-modules", "m1",
258                      "--suggest-providers",
259                      "java.util.List").output();
260
261        System.out.println(output);
262        List<String> expected = List.of(
263            "No provider found for service specified to --suggest-providers: java.util.List"
264        );
265
266        assertTrue(output.containsAll(expected));
267    }
268
269    @Test
270    public void addNonObservableModule() throws Throwable {
271        if (!hasJmods()) return;
272
273        List<String> output =
274            JLink.run("--module-path", MODULE_PATH,
275                      "--add-modules", "nonExistentModule",
276                      "--suggest-providers",
277                      "java.nio.charset.spi.CharsetProvider").output();
278
279        System.out.println(output);
280        List<String> expected = List.of(
281            "jdk.charsets provides java.nio.charset.spi.CharsetProvider used by java.base"
282        );
283
284        assertTrue(output.containsAll(expected));
285    }
286
287    static class JLink {
288        static final ToolProvider JLINK_TOOL = ToolProvider.findFirst("jlink")
289            .orElseThrow(() ->
290                new RuntimeException("jlink tool not found")
291            );
292
293        static JLink run(String... options) {
294            JLink jlink = new JLink();
295            assertTrue(jlink.execute(options) == 0);
296            return jlink;
297        }
298
299        final List<String> output = new ArrayList<>();
300        private int execute(String... options) {
301            System.out.println("jlink " +
302                Stream.of(options).collect(Collectors.joining(" ")));
303
304            StringWriter writer = new StringWriter();
305            PrintWriter pw = new PrintWriter(writer);
306            int rc = JLINK_TOOL.run(pw, pw, options);
307            System.out.println(writer.toString());
308            Stream.of(writer.toString().split("\\v"))
309                  .map(String::trim)
310                  .forEach(output::add);
311            return rc;
312        }
313
314        boolean contains(String s) {
315            return output.contains(s);
316        }
317
318        List<String> output() {
319            return output;
320        }
321    }
322}
323