Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/test/langtools/tools/jdeps/VerboseFormat/JdepsDependencyClosure.java
41149 views
1
/*
2
* Copyright (c) 2015, 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.IOException;
25
import java.io.OutputStream;
26
import java.io.PrintWriter;
27
import java.nio.file.Paths;
28
import java.util.ArrayList;
29
import java.util.Arrays;
30
import java.util.HashMap;
31
import java.util.HashSet;
32
import java.util.LinkedHashSet;
33
import java.util.List;
34
import java.util.Locale;
35
import java.util.Map;
36
import java.util.Set;
37
import java.util.function.Supplier;
38
import java.util.stream.Collectors;
39
import java.util.stream.Stream;
40
41
/**
42
* @test
43
* @bug 8080608
44
* @summary Test that jdeps verbose output has a summary line when dependencies
45
* are found within the same archive. For each testcase, compare the
46
* result obtained from jdeps with the expected result.
47
* @modules jdk.jdeps/com.sun.tools.jdeps
48
* java.base/sun.security.x509
49
* @build use.indirect.DontUseJdkInternal2
50
* @build use.indirect.UseJdkInternalIndirectly
51
* @build use.indirect2.DontUseJdkInternal3
52
* @build use.indirect2.UseJdkInternalIndirectly2
53
* @build use.internal.DontUseJdkInternal
54
* @build use.internal.UseClassWithJdkInternal
55
* @build use.internal.UseJdkInternalClass
56
* @build use.internal.UseJdkInternalClass2
57
* @run main JdepsDependencyClosure --test:0
58
* @run main JdepsDependencyClosure --test:1
59
* @run main JdepsDependencyClosure --test:2
60
* @run main JdepsDependencyClosure --test:3
61
*/
62
public class JdepsDependencyClosure {
63
64
static boolean VERBOSE = false;
65
static boolean COMPARE_TEXT = true;
66
67
static final String JDEPS_SUMMARY_TEXT_FORMAT = "%s -> %s%n";
68
static final String JDEPS_VERBOSE_TEXT_FORMAT = " %-50s -> %-50s %s%n";
69
70
/**
71
* Helper class used to store arguments to pass to
72
* {@code JdepsDependencyClosure.test} as well as expected
73
* results.
74
*/
75
static class TestCaseData {
76
final Map<String, Set<String>> expectedDependencies;
77
final String expectedText;
78
final String[] args;
79
final boolean closure;
80
81
TestCaseData(Map<String, Set<String>> expectedDependencies,
82
String expectedText,
83
boolean closure,
84
String[] args) {
85
this.expectedDependencies = expectedDependencies;
86
this.expectedText = expectedText;
87
this.closure = closure;
88
this.args = args;
89
}
90
91
public void test() {
92
if (expectedDependencies != null) {
93
String format = closure
94
? "Running (closure): jdeps %s %s %s %s"
95
: "Running: jdeps %s %s %s %s";
96
System.out.println(String.format(format, (Object[])args));
97
}
98
JdepsDependencyClosure.test(args, expectedDependencies, expectedText, closure);
99
}
100
101
/**
102
* Make a new test case data to invoke jdeps and test its output.
103
* @param pattern The pattern that will passed through to jdeps -e
104
* This is expected to match only one class.
105
* @param arcPath The archive to analyze. A jar or a class directory.
106
* @param classes For each reported archive dependency couple, the
107
* expected list of classes in the source that will
108
* be reported as having a dependency on the class
109
* in the target that matches the given pattern.
110
* @param dependencies For each archive dependency couple, a singleton list
111
* containing the name of the class in the target that
112
* matches the pattern. It is expected that the pattern
113
* will match only one class in the target.
114
* If the pattern matches several classes the
115
* expected text may no longer match the jdeps output.
116
* @param archives A list of archive dependency couple in the form
117
* {{sourceName1, sourcePath1, targetDescription1, targetPath1}
118
* {sourceName2, sourcePath2, targetDescription2, targetPath2}
119
* ... }
120
* For a JDK module - e.g. java.base, the targetDescription
121
* is usually something like "JDK internal API (java.base)"
122
* and the targetPath is usually the module name "java.base".
123
* @param closure Whether jdeps should be recursively invoked to build
124
* the closure.
125
* @return An instance of TestCaseData containing all the information
126
* needed to perform the jdeps invokation and test its output.
127
*/
128
public static TestCaseData make(String pattern, String arcPath, String[][] classes,
129
String[][] dependencies, String[][] archives, boolean closure) {
130
final String[] args = new String[] {
131
"-e", pattern, "-v", arcPath
132
};
133
Map<String, Set<String>> expected = new HashMap<>();
134
String expectedText = "";
135
for (int i=0; i<classes.length; i++) {
136
final int index = i;
137
expectedText += Stream.of(classes[i])
138
.map((cn) -> String.format(JDEPS_VERBOSE_TEXT_FORMAT, cn,
139
dependencies[index][0], archives[index][2]))
140
.reduce(String.format(JDEPS_SUMMARY_TEXT_FORMAT, archives[i][0],
141
archives[index][3]), (s1,s2) -> s1.concat(s2));
142
for (String cn : classes[index]) {
143
expected.putIfAbsent(cn, new HashSet<>());
144
expected.get(cn).add(dependencies[index][0]);
145
}
146
}
147
return new TestCaseData(expected, expectedText, closure, args);
148
}
149
150
public static TestCaseData valueOf(String[] args) {
151
if (args.length == 1 && args[0].startsWith("--test:")) {
152
// invoked from jtreg. build test case data for selected test.
153
int index = Integer.parseInt(args[0].substring("--test:".length()));
154
if (index >= dataSuppliers.size()) {
155
throw new RuntimeException("No such test case: " + index
156
+ " - available testcases are [0.."
157
+ (dataSuppliers.size()-1) + "]");
158
}
159
return dataSuppliers.get(index).get();
160
} else {
161
// invoked in standalone. just take the given argument
162
// and perform no validation on the output (except that it
163
// must start with a summary line)
164
return new TestCaseData(null, null, true, args);
165
}
166
}
167
168
}
169
170
static TestCaseData makeTestCaseOne() {
171
final String arcPath = System.getProperty("test.classes", "build/classes");
172
final String arcName = Paths.get(arcPath).getFileName().toString();
173
final String[][] classes = new String[][] {
174
{"use.indirect2.UseJdkInternalIndirectly2", "use.internal.UseClassWithJdkInternal"},
175
};
176
final String[][] dependencies = new String[][] {
177
{"use.internal.UseJdkInternalClass"},
178
};
179
final String[][] archives = new String[][] {
180
{arcName, arcPath, arcName, arcPath},
181
};
182
return TestCaseData.make("use.internal.UseJdkInternalClass", arcPath, classes,
183
dependencies, archives, false);
184
}
185
186
static TestCaseData makeTestCaseTwo() {
187
String arcPath = System.getProperty("test.classes", "build/classes");
188
String arcName = Paths.get(arcPath).getFileName().toString();
189
String[][] classes = new String[][] {
190
{"use.internal.UseJdkInternalClass", "use.internal.UseJdkInternalClass2"}
191
};
192
String[][] dependencies = new String[][] {
193
{"sun.security.x509.X509CertInfo"}
194
};
195
String[][] archive = new String[][] {
196
{arcName, arcPath, "JDK internal API (java.base)", "java.base"},
197
};
198
return TestCaseData.make("sun.security.x509.X509CertInfo", arcPath, classes,
199
dependencies, archive, false);
200
}
201
202
static TestCaseData makeTestCaseThree() {
203
final String arcPath = System.getProperty("test.classes", "build/classes");
204
final String arcName = Paths.get(arcPath).getFileName().toString();
205
final String[][] classes = new String[][] {
206
{"use.indirect2.UseJdkInternalIndirectly2", "use.internal.UseClassWithJdkInternal"},
207
{"use.indirect.UseJdkInternalIndirectly"}
208
};
209
final String[][] dependencies = new String[][] {
210
{"use.internal.UseJdkInternalClass"},
211
{"use.internal.UseClassWithJdkInternal"}
212
};
213
final String[][] archives = new String[][] {
214
{arcName, arcPath, arcName, arcPath},
215
{arcName, arcPath, arcName, arcPath}
216
};
217
return TestCaseData.make("use.internal.UseJdkInternalClass", arcPath, classes,
218
dependencies, archives, true);
219
}
220
221
222
static TestCaseData makeTestCaseFour() {
223
final String arcPath = System.getProperty("test.classes", "build/classes");
224
final String arcName = Paths.get(arcPath).getFileName().toString();
225
final String[][] classes = new String[][] {
226
{"use.internal.UseJdkInternalClass", "use.internal.UseJdkInternalClass2"},
227
{"use.indirect2.UseJdkInternalIndirectly2", "use.internal.UseClassWithJdkInternal"},
228
{"use.indirect.UseJdkInternalIndirectly"}
229
};
230
final String[][] dependencies = new String[][] {
231
{"sun.security.x509.X509CertInfo"},
232
{"use.internal.UseJdkInternalClass"},
233
{"use.internal.UseClassWithJdkInternal"}
234
};
235
final String[][] archives = new String[][] {
236
{arcName, arcPath, "JDK internal API (java.base)", "java.base"},
237
{arcName, arcPath, arcName, arcPath},
238
{arcName, arcPath, arcName, arcPath}
239
};
240
return TestCaseData.make("sun.security.x509.X509CertInfo", arcPath, classes, dependencies,
241
archives, true);
242
}
243
244
static final List<Supplier<TestCaseData>> dataSuppliers = Arrays.asList(
245
JdepsDependencyClosure::makeTestCaseOne,
246
JdepsDependencyClosure::makeTestCaseTwo,
247
JdepsDependencyClosure::makeTestCaseThree,
248
JdepsDependencyClosure::makeTestCaseFour
249
);
250
251
252
253
/**
254
* The OutputStreamParser is used to parse the format of jdeps.
255
* It is thus dependent on that format.
256
*/
257
static class OutputStreamParser extends OutputStream {
258
// OutputStreamParser will populate this map:
259
//
260
// For each archive, a list of class in where dependencies where
261
// found...
262
final Map<String, Set<String>> deps;
263
final StringBuilder text = new StringBuilder();
264
265
StringBuilder[] lines = { new StringBuilder(), new StringBuilder() };
266
int line = 0;
267
int sepi = 0;
268
char[] sep;
269
270
public OutputStreamParser(Map<String, Set<String>> deps) {
271
this.deps = deps;
272
this.sep = System.getProperty("line.separator").toCharArray();
273
}
274
275
@Override
276
public void write(int b) throws IOException {
277
lines[line].append((char)b);
278
if (b == sep[sepi]) {
279
if (++sepi == sep.length) {
280
text.append(lines[line]);
281
if (lines[0].toString().startsWith(" ")) {
282
throw new RuntimeException("Bad formatting: "
283
+ "summary line missing for\n"+lines[0]);
284
}
285
// Usually the output looks like that:
286
// <archive-1> -> java.base
287
// <class-1> -> <dependency> <dependency description>
288
// <class-2> -> <dependency> <dependency description>
289
// ...
290
// <archive-2> -> java.base
291
// <class-3> -> <dependency> <dependency description>
292
// <class-4> -> <dependency> <dependency description>
293
// ...
294
//
295
// We want to keep the <archive> line in lines[0]
296
// and have the ith <class-i> line in lines[1]
297
if (line == 1) {
298
// we have either a <class> line or an <archive> line.
299
String line1 = lines[0].toString();
300
String line2 = lines[1].toString();
301
if (line2.startsWith(" ")) {
302
// we have a class line, record it.
303
parse(line1, line2);
304
// prepare for next <class> line.
305
lines[1] = new StringBuilder();
306
} else {
307
// We have an archive line: We are switching to the next archive.
308
// put the new <archive> line in lines[0], and prepare
309
// for reading the next <class> line
310
lines[0] = lines[1];
311
lines[1] = new StringBuilder();
312
}
313
} else {
314
// we just read the first <archive> line.
315
// prepare to read <class> lines.
316
line = 1;
317
}
318
sepi = 0;
319
}
320
} else {
321
sepi = 0;
322
}
323
}
324
325
// Takes a couple of lines, where line1 is an <archive> line and
326
// line 2 is a <class> line. Parses the line to extract the archive
327
// name and dependent class name, and record them in the map...
328
void parse(String line1, String line2) {
329
String archive = line1.substring(0, line1.indexOf(" -> "));
330
int l2ArrowIndex = line2.indexOf(" -> ");
331
String className = line2.substring(2, l2ArrowIndex).replace(" ", "");
332
String depdescr = line2.substring(l2ArrowIndex + 4);
333
String depclass = depdescr.substring(0, depdescr.indexOf(" "));
334
deps.computeIfAbsent(archive, (k) -> new HashSet<>());
335
deps.get(archive).add(className);
336
if (VERBOSE) {
337
System.out.println(archive+": "+className+" depends on "+depclass);
338
}
339
}
340
341
}
342
343
/**
344
* The main method.
345
*
346
* Can be run in two modes:
347
* <ul>
348
* <li>From jtreg: expects 1 argument in the form {@code --test:<test-nb>}</li>
349
* <li>From command line: expected syntax is {@code -e <pattern> -v jar [jars..]}</li>
350
* </ul>
351
* <p>When called from the command line this method will call jdeps recursively
352
* to build a closure of the dependencies on {@code <pattern>} and print a summary.
353
* <p>When called from jtreg - it will call jdeps either once only or
354
* recursively depending on the pattern.
355
* @param args either {@code --test:<test-nb>} or {@code -e <pattern> -v jar [jars..]}.
356
*/
357
public static void main(String[] args) {
358
runWithLocale(Locale.ENGLISH, TestCaseData.valueOf(args)::test);
359
}
360
361
private static void runWithLocale(Locale loc, Runnable run) {
362
final Locale defaultLocale = Locale.getDefault();
363
Locale.setDefault(loc);
364
try {
365
run.run();
366
} finally {
367
Locale.setDefault(defaultLocale);
368
}
369
}
370
371
372
public static void test(String[] args, Map<String, Set<String>> expected,
373
String expectedText, boolean closure) {
374
try {
375
doTest(args, expected, expectedText, closure);
376
} catch (Throwable t) {
377
try {
378
printDiagnostic(args, expectedText, t, closure);
379
} catch(Throwable tt) {
380
throw t;
381
}
382
throw t;
383
}
384
}
385
386
static class TextFormatException extends RuntimeException {
387
final String expected;
388
final String actual;
389
TextFormatException(String message, String expected, String actual) {
390
super(message);
391
this.expected = expected;
392
this.actual = actual;
393
}
394
}
395
396
public static void printDiagnostic(String[] args, String expectedText,
397
Throwable t, boolean closure) {
398
if (expectedText != null || t instanceof TextFormatException) {
399
System.err.println("===== TEST FAILED =======");
400
System.err.println("command: " + Stream.of(args)
401
.reduce("jdeps", (s1,s2) -> s1.concat(" ").concat(s2)));
402
System.err.println("===== Expected Output =======");
403
System.err.append(expectedText);
404
System.err.println("===== Command Output =======");
405
if (t instanceof TextFormatException) {
406
System.err.print(((TextFormatException)t).actual);
407
} else {
408
com.sun.tools.jdeps.Main.run(args, new PrintWriter(System.err));
409
if (closure) System.err.println("... (closure not available) ...");
410
}
411
System.err.println("=============================");
412
}
413
}
414
415
public static void doTest(String[] args, Map<String, Set<String>> expected,
416
String expectedText, boolean closure) {
417
if (args.length < 3 || !"-e".equals(args[0]) || !"-v".equals(args[2])) {
418
System.err.println("Syntax: -e <classname> -v [list of jars or directories]");
419
return;
420
}
421
Map<String, Map<String, Set<String>>> alldeps = new HashMap<>();
422
String depName = args[1];
423
List<String> search = new ArrayList<>();
424
search.add(depName);
425
Set<String> searched = new LinkedHashSet<>();
426
StringBuilder text = new StringBuilder();
427
while(!search.isEmpty()) {
428
args[1] = search.remove(0);
429
if (VERBOSE) {
430
System.out.println("Looking for " + args[1]);
431
}
432
searched.add(args[1]);
433
Map<String, Set<String>> deps =
434
alldeps.computeIfAbsent(args[1], (k) -> new HashMap<>());
435
OutputStreamParser parser = new OutputStreamParser(deps);
436
PrintWriter writer = new PrintWriter(parser);
437
com.sun.tools.jdeps.Main.run(args, writer);
438
if (VERBOSE) {
439
System.out.println("Found: " + deps.values().stream()
440
.flatMap(s -> s.stream()).collect(Collectors.toSet()));
441
}
442
if (expectedText != null) {
443
text.append(parser.text.toString());
444
}
445
search.addAll(deps.values().stream()
446
.flatMap(s -> s.stream())
447
.filter(k -> !searched.contains(k))
448
.collect(Collectors.toSet()));
449
if (!closure) break;
450
}
451
452
// Print summary...
453
final Set<String> classes = alldeps.values().stream()
454
.flatMap((m) -> m.values().stream())
455
.flatMap(s -> s.stream()).collect(Collectors.toSet());
456
Map<String, Set<String>> result = new HashMap<>();
457
for (String c : classes) {
458
Set<String> archives = new HashSet<>();
459
Set<String> dependencies = new HashSet<>();
460
for (String d : alldeps.keySet()) {
461
Map<String, Set<String>> m = alldeps.get(d);
462
for (String a : m.keySet()) {
463
Set<String> s = m.get(a);
464
if (s.contains(c)) {
465
archives.add(a);
466
dependencies.add(d);
467
}
468
}
469
}
470
result.put(c, dependencies);
471
System.out.println(c + " " + archives + " depends on " + dependencies);
472
}
473
474
// If we're in jtreg, then check result (expectedText != null)
475
if (expectedText != null && COMPARE_TEXT) {
476
//text.append(String.format("%n"));
477
if (text.toString().equals(expectedText)) {
478
System.out.println("SUCCESS - got expected text");
479
} else {
480
throw new TextFormatException("jdeps output is not as expected",
481
expectedText, text.toString());
482
}
483
}
484
if (expected != null) {
485
if (expected.equals(result)) {
486
System.out.println("SUCCESS - found expected dependencies");
487
} else if (expectedText == null) {
488
throw new RuntimeException("Bad dependencies: Expected " + expected
489
+ " but found " + result);
490
} else {
491
throw new TextFormatException("Bad dependencies: Expected "
492
+ expected
493
+ " but found " + result,
494
expectedText, text.toString());
495
}
496
}
497
}
498
}
499
500