1/*
2 * Copyright (c) 2016, 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
35import java.io.File;
36import java.nio.file.Files;
37import java.nio.file.Path;
38import java.nio.file.Paths;
39import java.util.Arrays;
40import java.util.LinkedHashSet;
41import java.util.List;
42import java.util.Set;
43import java.util.stream.Collectors;
44import java.util.stream.Stream;
45
46import com.sun.tools.jdeps.Archive;
47import com.sun.tools.jdeps.InverseDepsAnalyzer;
48import org.testng.annotations.BeforeTest;
49import org.testng.annotations.DataProvider;
50import org.testng.annotations.Test;
51
52import static org.testng.Assert.assertTrue;
53import static org.testng.Assert.assertFalse;
54import static org.testng.Assert.assertEquals;
55
56
57public 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", "java.se.ee"},
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}
340