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