1 /*
   2  * Copyright (c) 2016, 2018, 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  * @summary Tests split packages
  27  * @library ../lib
  28  * @build CompilerUtils JdepsUtil
  29  * @modules java.logging
  30  *          jdk.jdeps/com.sun.tools.jdeps
  31  *          jdk.unsupported
  32  * @run testng InverseDeps
  33  */
  34 
  35 import java.io.File;
  36 import java.nio.file.Files;
  37 import java.nio.file.Path;
  38 import java.nio.file.Paths;
  39 import java.util.Arrays;
  40 import java.util.LinkedHashSet;
  41 import java.util.List;
  42 import java.util.Set;
  43 import java.util.stream.Collectors;
  44 import java.util.stream.Stream;
  45 
  46 import com.sun.tools.jdeps.Archive;
  47 import com.sun.tools.jdeps.InverseDepsAnalyzer;
  48 import org.testng.annotations.BeforeTest;
  49 import org.testng.annotations.DataProvider;
  50 import org.testng.annotations.Test;
  51 
  52 import static org.testng.Assert.assertTrue;
  53 import static org.testng.Assert.assertFalse;
  54 import static org.testng.Assert.assertEquals;
  55 
  56 
  57 public class InverseDeps {
  58     private static final String TEST_SRC = System.getProperty("test.src");
  59     private static final String TEST_CLASSES = System.getProperty("test.classes");
  60 
  61     private static final Path SRC_DIR = Paths.get(TEST_SRC, "src");
  62     private static final Path MODS_DIR = Paths.get("mods");
  63     private static final Path LIBS_DIR = Paths.get("libs");
  64 
  65     private static final Set<String> modules = new LinkedHashSet(
  66         List.of("unsafe", "mIV", "mV", "mVI", "mVII")
  67     );
  68 
  69     /**
  70      * Compiles classes used by the test
  71      */
  72     @BeforeTest
  73     public void compileAll() throws Exception {
  74         CompilerUtils.cleanDir(MODS_DIR);
  75 
  76         for (String mn : modules) {
  77             // compile a module
  78             assertTrue(CompilerUtils.compileModule(SRC_DIR, MODS_DIR, mn));
  79 
  80             // create JAR files with no module-info.class
  81             Path root = MODS_DIR.resolve(mn);
  82 
  83             try (Stream<Path> stream = Files.walk(root, Integer.MAX_VALUE)) {
  84                 Stream<Path> entries = stream.filter(f -> {
  85                     String fn = f.getFileName().toString();
  86                     return fn.endsWith(".class") && !fn.equals("module-info.class");
  87                 });
  88                 JdepsUtil.createJar(LIBS_DIR.resolve(mn + ".jar"), root, entries);
  89             }
  90         }
  91     }
  92     @DataProvider(name = "jdkModules")
  93     public Object[][] jdkModules() {
  94         return new Object[][]{
  95             // --require and a subset of dependences
  96             { "jdk.compiler", new String[][] {
  97                     new String[] {"jdk.compiler", "jdk.jshell"},
  98                     new String[] {"jdk.compiler", "jdk.rmic"},
  99                     new String[] {"jdk.compiler", "jdk.javadoc", "jdk.rmic"},
 100                 }
 101             },
 102             { "java.compiler", new String[][] {
 103                     new String[] {"java.compiler", "jdk.jshell"},
 104                     new String[] {"java.compiler", "jdk.compiler", "jdk.jshell"},
 105                     new String[] {"java.compiler", "jdk.compiler", "jdk.rmic"},
 106                     new String[] {"java.compiler", "jdk.compiler", "jdk.javadoc", "jdk.rmic"},
 107                     new String[] {"java.compiler", "java.se"},
 108                 }
 109             },
 110         };
 111     }
 112 
 113     @Test(dataProvider = "jdkModules")
 114     public void testJDKModule(String moduleName, String[][] expected) throws Exception {
 115         // this invokes the jdeps launcher so that all system modules are observable
 116         JdepsRunner jdeps = JdepsRunner.run(
 117             "--inverse", "--require", moduleName
 118         );
 119         List<String> output = Arrays.stream(jdeps.output())
 120             .map(s -> s.trim())
 121             .collect(Collectors.toList());
 122 
 123         // verify the dependences
 124         assertTrue(Arrays.stream(expected)
 125                          .map(path -> Arrays.stream(path)
 126                          .collect(Collectors.joining(" <- ")))
 127                          .anyMatch(output::contains));
 128     }
 129 
 130 
 131     @DataProvider(name = "testrequires")
 132     public Object[][] expected1() {
 133         return new Object[][] {
 134             // --require and result
 135             { "java.sql", new String[][] {
 136                     new String[] { "java.sql", "mV" },
 137                 }
 138             },
 139             { "java.compiler", new String[][] {
 140                     new String[] { "java.compiler", "mV" },
 141                     new String[] { "java.compiler", "mIV", "mV" },
 142                 }
 143             },
 144             { "java.logging", new String[][]{
 145                     new String[] {"java.logging", "mV"},
 146                     new String[] {"java.logging", "mIV", "mV"},
 147                     new String[] {"java.logging", "java.sql", "mV"},
 148                 }
 149             },
 150             { "jdk.unsupported", new String[][] {
 151                     new String[] {"jdk.unsupported", "unsafe", "mVI", "mVII"},
 152                     new String[] {"jdk.unsupported", "unsafe", "mVII"}
 153                 }
 154             },
 155         };
 156     }
 157 
 158     @Test(dataProvider = "testrequires")
 159     public void testrequires(String name, String[][] expected) throws Exception {
 160         String cmd1 = String.format("jdeps --inverse --module-path %s --require %s --add-modules %s%n",
 161                 MODS_DIR, name, modules.stream().collect(Collectors.joining(",")));
 162 
 163         try (JdepsUtil.Command jdeps = JdepsUtil.newCommand(cmd1)) {
 164             jdeps.appModulePath(MODS_DIR.toString())
 165                 .addmods(modules)
 166                 .requires(Set.of(name));
 167 
 168             runJdeps(jdeps, expected);
 169         }
 170 
 171         String cmd2 = String.format("jdeps --inverse --module-path %s --require %s" +
 172             " --add-modules ALL-MODULE-PATH%n", LIBS_DIR, name);
 173 
 174             // automatic module
 175         try (JdepsUtil.Command jdeps = JdepsUtil.newCommand(cmd2)) {
 176             jdeps.appModulePath(MODS_DIR.toString())
 177                 .addmods(Set.of("ALL-MODULE-PATH"))
 178                 .requires(Set.of(name));
 179 
 180             runJdeps(jdeps, expected);
 181         }
 182     }
 183 
 184     @DataProvider(name = "testpackage")
 185     public Object[][] expected2() {
 186         return new Object[][] {
 187             // -package and result
 188             { "p4", new String[][] {
 189                         new String[] { "mIV", "mV"},
 190                     }
 191             },
 192             { "javax.tools", new String[][] {
 193                         new String[] {"java.compiler", "mV"},
 194                         new String[] {"java.compiler", "mIV", "mV"},
 195                     }
 196             },
 197             { "sun.misc", new String[][] {
 198                         new String[] {"jdk.unsupported", "unsafe", "mVI", "mVII"},
 199                         new String[] {"jdk.unsupported", "unsafe", "mVII"}
 200                     }
 201             }
 202         };
 203     }
 204 
 205     @Test(dataProvider = "testpackage")
 206     public void testpackage(String name, String[][] expected) throws Exception {
 207         String cmd = String.format("jdeps --inverse --module-path %s -package %s --add-modules %s%n",
 208             MODS_DIR, name, modules.stream().collect(Collectors.joining(",")));
 209         try (JdepsUtil.Command jdeps = JdepsUtil.newCommand(cmd)) {
 210             jdeps.appModulePath(MODS_DIR.toString())
 211                 .addmods(modules)
 212                 .matchPackages(Set.of(name));
 213 
 214             runJdeps(jdeps, expected);
 215         }
 216     }
 217 
 218     @DataProvider(name = "testregex")
 219     public Object[][] expected3() {
 220         return new Object[][] {
 221             // -regex and result
 222             { "org.safe.Lib", new String[][] {
 223                     new String[] { "unsafe", "mVII"},
 224                     new String[] { "unsafe", "mVI", "mVII"},
 225                 }
 226             },
 227             { "java.util.logging.*|org.safe.Lib", new String[][] {
 228                     new String[] { "unsafe", "mVII"},
 229                     new String[] { "unsafe", "mVI", "mVII"},
 230                     new String[] { "java.logging", "mV"},
 231                 }
 232             }
 233         };
 234     }
 235 
 236     @Test(dataProvider = "testregex")
 237     public void testregex(String name, String[][] expected) throws Exception {
 238         String cmd = String.format("jdeps --inverse --module-path %s -regex %s --add-modules %s%n",
 239                 MODS_DIR, name, modules.stream().collect(Collectors.joining(",")));
 240 
 241         try (JdepsUtil.Command jdeps = JdepsUtil.newCommand(cmd)) {
 242             jdeps.appModulePath(MODS_DIR.toString())
 243                 .addmods(modules)
 244                 .regex(name);
 245 
 246             runJdeps(jdeps, expected);
 247         }
 248     }
 249 
 250     @DataProvider(name = "classpath")
 251     public Object[][] expected4() {
 252         return new Object[][] {
 253             // -regex and result
 254             { "sun.misc.Unsafe", new String[][] {
 255                     new String[] {"jdk.unsupported", "unsafe.jar", "mVI.jar", "mVII.jar"},
 256                     new String[] {"jdk.unsupported", "unsafe.jar", "mVII.jar"}
 257                 }
 258             },
 259             { "org.safe.Lib", new String[][] {
 260                     new String[] { "unsafe.jar", "mVII.jar"},
 261                     new String[] { "unsafe.jar", "mVI.jar", "mVII.jar"},
 262                 }
 263             },
 264             { "java.util.logging.*|org.safe.Lib", new String[][] {
 265                     new String[] { "unsafe.jar", "mVII.jar"},
 266                     new String[] { "unsafe.jar", "mVI.jar", "mVII.jar"},
 267                     new String[] { "java.logging", "mV.jar"},
 268                 }
 269             }
 270         };
 271     }
 272 
 273     @Test(dataProvider = "classpath")
 274     public void testClassPath(String name, String[][] expected) throws Exception {
 275         // -classpath
 276         String cpath = modules.stream()
 277             .filter(mn -> !mn.equals("mVII"))
 278             .map(mn -> LIBS_DIR.resolve(mn + ".jar").toString())
 279             .collect(Collectors.joining(File.pathSeparator));
 280 
 281         Path jarfile = LIBS_DIR.resolve("mVII.jar");
 282 
 283         String cmd1 = String.format("jdeps --inverse -classpath %s -regex %s %s%n",
 284             cpath, name, jarfile);
 285         try (JdepsUtil.Command jdeps = JdepsUtil.newCommand(cmd1)) {
 286             jdeps.verbose("-verbose:class")
 287                 .addClassPath(cpath)
 288                 .regex(name).addRoot(jarfile);
 289             runJdeps(jdeps, expected);
 290         }
 291 
 292         // all JAR files on the command-line arguments
 293         Set<Path> paths = modules.stream()
 294                                  .map(mn -> LIBS_DIR.resolve(mn + ".jar"))
 295                                  .collect(Collectors.toSet());
 296         String cmd2 = String.format("jdeps --inverse -regex %s %s%n", name, paths);
 297         try (JdepsUtil.Command jdeps = JdepsUtil.newCommand(cmd2)) {
 298             jdeps.verbose("-verbose:class").regex(name);
 299             paths.forEach(jdeps::addRoot);
 300             runJdeps(jdeps, expected);
 301         }
 302     }
 303 
 304     private void runJdeps(JdepsUtil.Command jdeps, String[][] expected)  throws Exception {
 305         InverseDepsAnalyzer analyzer = jdeps.getInverseDepsAnalyzer();
 306 
 307         assertTrue(analyzer.run());
 308 
 309         // get the inverse transitive dependences
 310         List<String[]> paths = analyzer.inverseDependences().stream()
 311             .map(deque -> deque.stream()
 312                                .map(Archive::getName)
 313                                .collect(Collectors.toList()).toArray(new String[0]))
 314             .collect(Collectors.toList());
 315 
 316         jdeps.dumpOutput(System.err);
 317         paths.forEach(path -> System.err.println(Arrays.stream(path)
 318                 .collect(Collectors.joining(" <- "))));
 319 
 320         // verify the dependences
 321         assertEquals(paths.size(), expected.length);
 322 
 323         for (int i=0; i < paths.size(); i++) {
 324             String[] path = paths.get(i);
 325             boolean noneMatched = Arrays.stream(expected)
 326                     .filter(array -> array.length == path.length)
 327                     .noneMatch(array -> Arrays.equals(array, path));
 328             if (noneMatched)
 329                 System.err.format("Expected: %s found: %s%n",
 330                                   Arrays.stream(expected)
 331                                       .map(Arrays::toString)
 332                                       .collect(Collectors.joining(", ")),
 333                     Arrays.toString(path));
 334 
 335             assertFalse(noneMatched);
 336         }
 337     }
 338 
 339 }