Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/jdk17u
Path: blob/master/test/jdk/tools/jar/multiRelease/ApiValidatorTest.java
66644 views
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
/*
25
* @test
26
# @bug 8196748
27
* @summary Tests for API validator.
28
* @library /test/lib
29
* @modules java.base/jdk.internal.misc
30
* jdk.compiler
31
* jdk.jartool
32
* @build jdk.test.lib.Utils
33
* jdk.test.lib.Asserts
34
* jdk.test.lib.JDKToolFinder
35
* jdk.test.lib.JDKToolLauncher
36
* jdk.test.lib.Platform
37
* jdk.test.lib.process.*
38
* MRTestBase
39
* @run testng/timeout=1200 ApiValidatorTest
40
*/
41
42
import jdk.test.lib.process.OutputAnalyzer;
43
import org.testng.annotations.BeforeMethod;
44
import org.testng.annotations.DataProvider;
45
import org.testng.annotations.Test;
46
47
import java.io.IOException;
48
import java.lang.reflect.Method;
49
import java.nio.file.FileSystem;
50
import java.nio.file.FileSystems;
51
import java.nio.file.Files;
52
import java.nio.file.Path;
53
import java.nio.file.Paths;
54
import java.util.Map;
55
import java.util.regex.Matcher;
56
import java.util.regex.Pattern;
57
import java.util.stream.Stream;
58
59
public class ApiValidatorTest extends MRTestBase {
60
61
static final Pattern MODULE_PATTERN = Pattern.compile("module (\\w+)");
62
static final Pattern CLASS_PATTERN = Pattern.compile("package (\\w+).*public class (\\w+)");
63
64
private Path root;
65
private Path classes;
66
67
@BeforeMethod
68
void testInit(Method method) {
69
root = Paths.get(method.getName());
70
classes = root.resolve("classes");
71
}
72
73
@Test(dataProvider = "signatureChange")
74
public void changeMethodSignature(String sigBase, String sigV10,
75
boolean isAcceptable) throws Throwable {
76
77
String METHOD_SIG = "#SIG";
78
String classTemplate =
79
"public class C { \n" +
80
" " + METHOD_SIG + "{ throw new RuntimeException(); };\n" +
81
"}\n";
82
String base = classTemplate.replace(METHOD_SIG, sigBase);
83
String v10 = classTemplate.replace(METHOD_SIG, sigV10);
84
85
compileTemplate(classes.resolve("base"), base);
86
compileTemplate(classes.resolve("v10"), v10);
87
88
String jarfile = root.resolve("test.jar").toString();
89
OutputAnalyzer result = jar("cf", jarfile,
90
"-C", classes.resolve("base").toString(), ".",
91
"--release", "10", "-C", classes.resolve("v10").toString(),
92
".");
93
94
String failureMessage = "contains a class with different api from earlier version";
95
checkResult(result, isAcceptable, failureMessage);
96
if (isAcceptable) result.shouldBeEmptyIgnoreVMWarnings();
97
98
99
Path malformed = root.resolve("zip").resolve("test.jar");
100
zip(malformed,
101
Map.entry("", classes.resolve("base")),
102
Map.entry("META-INF/versions/10", classes.resolve("v10")));
103
104
result = validateJar(malformed.toString(), isAcceptable, failureMessage);
105
if (isAcceptable) result.shouldBeEmptyIgnoreVMWarnings();
106
}
107
108
@DataProvider
109
Object[][] signatureChange() {
110
return new Object[][]{
111
{"public int m()", "protected int m()", false},
112
{"protected int m()", "public int m()", false},
113
{"public int m()", "int m()", false},
114
{"protected int m()", "private int m()", false},
115
{"private int m()", "int m()", true},
116
{"int m()", "private int m()", true},
117
{"int m()", "private int m(boolean b)", true},
118
{"public int m()", "public int m(int i)", false},
119
{"public int m()", "public int k()", false},
120
{"public int m()", "private int k()", false},
121
// @ignore JDK-8172147 {"public int m()", "public boolean m()", false},
122
// @ignore JDK-8172147 {"public boolean", "public Boolean", false},
123
// @ignore JDK-8172147 {"public <T> T", "public <T extends String> T", false},
124
};
125
}
126
127
@Test(dataProvider = "publicAPI")
128
public void introducingPublicMembers(String publicAPI) throws Throwable {
129
String API = "#API";
130
String classTemplate =
131
"public class C { \n" +
132
" " + API + "\n" +
133
" public void method(){ };\n" +
134
"}\n";
135
String base = classTemplate.replace(API, "");
136
String v10 = classTemplate.replace(API, publicAPI);
137
138
compileTemplate(classes.resolve("base"), base);
139
compileTemplate(classes.resolve("v10"), v10);
140
141
String failureMessage = "contains a class with different api from earlier version";
142
143
String jarfile = root.resolve("test.jar").toString();
144
jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
145
"--release", "10", "-C", classes.resolve("v10").toString(), ".")
146
.shouldNotHaveExitValue(SUCCESS)
147
.shouldContain(failureMessage);
148
149
Path malformed = root.resolve("zip").resolve("test.jar");
150
zip(malformed,
151
Map.entry("", classes.resolve("base")),
152
Map.entry("META-INF/versions/10", classes.resolve("v10")));
153
154
validateJar(malformed.toString(), false, failureMessage);
155
}
156
157
@DataProvider
158
Object[][] publicAPI() {
159
return new Object[][]{
160
// @ignore JDK-8172148 {"protected class Inner { public void m(){ } } "}, // protected inner class
161
// @ignore JDK-8172148 {"public class Inner { public void m(){ } }"}, // public inner class
162
// @ignore JDK-8172148 {"public enum E { A; }"}, // public enum
163
{"public void m(){ }"}, // public method
164
{"protected void m(){ }"}, // protected method
165
};
166
}
167
168
@Test(dataProvider = "privateAPI")
169
public void introducingPrivateMembers(String privateAPI) throws Throwable {
170
String API = "#API";
171
String classTemplate =
172
"public class C { \n" +
173
" " + API + "\n" +
174
" public void method(){ };\n" +
175
"}\n";
176
String base = classTemplate.replace(API, "");
177
String v10 = classTemplate.replace(API, privateAPI);
178
179
compileTemplate(classes.resolve("base"), base);
180
compileTemplate(classes.resolve("v10"), v10);
181
182
String jarfile = root.resolve("test.jar").toString();
183
jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
184
"--release", "10", "-C", classes.resolve("v10").toString(), ".")
185
.shouldHaveExitValue(SUCCESS);
186
validateJar(jarfile);
187
// add release
188
jar("uf", jarfile,
189
"--release", "11", "-C", classes.resolve("v10").toString(), ".")
190
.shouldHaveExitValue(SUCCESS);
191
validateJar(jarfile);
192
// replace release
193
jar("uf", jarfile,
194
"--release", "11", "-C", classes.resolve("v10").toString(), ".")
195
.shouldHaveExitValue(SUCCESS);
196
validateJar(jarfile);
197
}
198
199
@DataProvider
200
Object[][] privateAPI() {
201
return new Object[][]{
202
{"private class Inner { public void m(){ } } "}, // private inner class
203
{"class Inner { public void m(){ } }"}, // package private inner class
204
{"enum E { A; }"}, // package private enum
205
// Local class and private method
206
{"private void m(){ class Inner { public void m(){} } Inner i = null; }"},
207
{"void m(){ }"}, // package private method
208
};
209
}
210
211
private void compileTemplate(Path classes, String template) throws Throwable {
212
Path classSourceFile = Files.createDirectories(
213
classes.getParent().resolve("src").resolve(classes.getFileName()))
214
.resolve("C.java");
215
Files.write(classSourceFile, template.getBytes());
216
javac(classes, classSourceFile);
217
}
218
219
/* Modular multi-release checks */
220
221
@Test
222
public void moduleNameHasChanged() throws Throwable {
223
224
compileModule(classes.resolve("base"), "module A { }");
225
compileModule(classes.resolve("v10"), "module B { }");
226
227
String failureMessage = "incorrect name";
228
229
String jarfile = root.resolve("test.jar").toString();
230
jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
231
"--release", "10", "-C", classes.resolve("v10").toString(), ".")
232
.shouldNotHaveExitValue(SUCCESS)
233
.shouldContain(failureMessage);
234
235
Path malformed = root.resolve("zip").resolve("test.jar");
236
zip(malformed,
237
Map.entry("", classes.resolve("base")),
238
Map.entry("META-INF/versions/10", classes.resolve("v10")));
239
240
validateJar(malformed.toString(), false, failureMessage);
241
242
// update module-info release
243
jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
244
"--release", "10", "-C", classes.resolve("base").toString(), ".")
245
.shouldHaveExitValue(SUCCESS);
246
validateJar(jarfile);
247
248
jar("uf", jarfile,
249
"--release", "10", "-C", classes.resolve("v10").toString(), ".")
250
.shouldNotHaveExitValue(SUCCESS)
251
.shouldContain(failureMessage);
252
}
253
254
// @Test @ignore 8173370
255
public void moduleBecomeOpen() throws Throwable {
256
257
compileModule(classes.resolve("base"), "module A { }");
258
compileModule(classes.resolve("v10"), "open module A { }");
259
260
String jarfile = root.resolve("test.jar").toString();
261
jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
262
"--release", "10", "-C", classes.resolve("v10").toString(), ".")
263
.shouldNotHaveExitValue(SUCCESS)
264
.shouldContain("FIX ME");
265
266
// update module-info release
267
jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
268
"--release", "10", "-C", classes.resolve("base").toString(), ".")
269
.shouldHaveExitValue(SUCCESS);
270
validateJar(jarfile);
271
jar("uf", jarfile,
272
"--release", "10", "-C", classes.resolve("v10").toString(), ".")
273
.shouldNotHaveExitValue(SUCCESS)
274
.shouldContain("FIX ME");
275
}
276
277
@Test
278
public void moduleRequires() throws Throwable {
279
280
String BASE_VERSION_DIRECTIVE = "requires jdk.compiler;";
281
// add transitive flag
282
moduleDirectivesCase(BASE_VERSION_DIRECTIVE,
283
"requires transitive jdk.compiler;",
284
false,
285
"contains additional \"requires transitive\"");
286
// remove requires
287
moduleDirectivesCase(BASE_VERSION_DIRECTIVE,
288
"",
289
true,
290
"");
291
// add requires
292
moduleDirectivesCase(BASE_VERSION_DIRECTIVE,
293
"requires jdk.compiler; requires jdk.jartool;",
294
true,
295
"");
296
// add requires transitive
297
moduleDirectivesCase(BASE_VERSION_DIRECTIVE,
298
"requires jdk.compiler; requires transitive jdk.jartool;",
299
false,
300
"contains additional \"requires transitive\"");
301
}
302
303
@Test
304
public void moduleExports() throws Throwable {
305
306
String BASE_VERSION_DIRECTIVE = "exports pkg1; exports pkg2 to jdk.compiler;";
307
// add export
308
moduleDirectivesCase(BASE_VERSION_DIRECTIVE,
309
BASE_VERSION_DIRECTIVE + " exports pkg3;",
310
false,
311
"contains different \"exports\"");
312
// change exports to qualified exports
313
moduleDirectivesCase(BASE_VERSION_DIRECTIVE,
314
"exports pkg1 to jdk.compiler; exports pkg2;",
315
false,
316
"contains different \"exports\"");
317
// remove exports
318
moduleDirectivesCase(BASE_VERSION_DIRECTIVE,
319
"exports pkg1;",
320
false,
321
"contains different \"exports\"");
322
// add qualified exports
323
moduleDirectivesCase(BASE_VERSION_DIRECTIVE,
324
BASE_VERSION_DIRECTIVE + " exports pkg3 to jdk.compiler;",
325
false,
326
"contains different \"exports\"");
327
}
328
329
@Test
330
public void moduleOpens() throws Throwable {
331
332
String BASE_VERSION_DIRECTIVE = "opens pkg1; opens pkg2 to jdk.compiler;";
333
// add opens
334
moduleDirectivesCase(BASE_VERSION_DIRECTIVE,
335
BASE_VERSION_DIRECTIVE + " opens pkg3;",
336
false,
337
"contains different \"opens\"");
338
// change opens to qualified opens
339
moduleDirectivesCase(BASE_VERSION_DIRECTIVE,
340
"opens pkg1 to jdk.compiler; opens pkg2;",
341
false,
342
"contains different \"opens\"");
343
// remove opens
344
moduleDirectivesCase(BASE_VERSION_DIRECTIVE,
345
"opens pkg1;",
346
false,
347
"contains different \"opens\"");
348
// add qualified opens
349
moduleDirectivesCase(BASE_VERSION_DIRECTIVE,
350
BASE_VERSION_DIRECTIVE + " opens pkg3 to jdk.compiler;",
351
false,
352
"contains different \"opens\"");
353
}
354
355
@Test
356
public void moduleProvides() throws Throwable {
357
358
String BASE_VERSION_DIRECTIVE = "provides pkg1.A with pkg1.A;";
359
// add provides
360
moduleDirectivesCase(BASE_VERSION_DIRECTIVE,
361
BASE_VERSION_DIRECTIVE + " provides pkg2.B with pkg2.B;",
362
false,
363
"contains different \"provides\"");
364
// change service impl
365
moduleDirectivesCase(BASE_VERSION_DIRECTIVE,
366
"provides pkg1.A with pkg2.B;",
367
false,
368
"contains different \"provides\"");
369
// remove provides
370
moduleDirectivesCase(BASE_VERSION_DIRECTIVE,
371
"",
372
false,
373
"contains different \"provides\"");
374
}
375
376
@Test
377
public void moduleUses() throws Throwable {
378
379
String BASE_VERSION_DIRECTIVE = "uses pkg1.A;";
380
// add
381
moduleDirectivesCase(BASE_VERSION_DIRECTIVE,
382
BASE_VERSION_DIRECTIVE + " uses pkg2.B;",
383
true,
384
"");
385
// replace
386
moduleDirectivesCase(BASE_VERSION_DIRECTIVE,
387
"uses pkg2.B;",
388
true,
389
"");
390
// remove
391
moduleDirectivesCase(BASE_VERSION_DIRECTIVE,
392
"",
393
true,
394
"");
395
}
396
397
private void moduleDirectivesCase(String baseDirectives,
398
String versionedDirectives,
399
boolean expectSuccess,
400
String expectedMessage) throws Throwable {
401
String[] moduleClasses = {
402
"package pkg1; public class A { }",
403
"package pkg2; public class B extends pkg1.A { }",
404
"package pkg3; public class C extends pkg2.B { }"};
405
compileModule(classes.resolve("base"),
406
"module A { " + baseDirectives + " }",
407
moduleClasses);
408
compileModule(classes.resolve("v10"),
409
"module A { " + versionedDirectives + " }",
410
moduleClasses);
411
412
String jarfile = root.resolve("test.jar").toString();
413
OutputAnalyzer output = jar("cf", jarfile,
414
"-C", classes.resolve("base").toString(), ".",
415
"--release", "10", "-C", classes.resolve("v10").toString(), ".");
416
checkResult(output, expectSuccess, expectedMessage);
417
418
Path malformed = root.resolve("zip").resolve("test.jar");
419
zip(malformed,
420
Map.entry("", classes.resolve("base")),
421
Map.entry("META-INF/versions/10", classes.resolve("v10")));
422
423
validateJar(malformed.toString(), expectSuccess, expectedMessage);
424
}
425
426
private void compileModule(Path classes, String moduleSource,
427
String... classSources) throws Throwable {
428
Matcher moduleMatcher = MODULE_PATTERN.matcher(moduleSource);
429
moduleMatcher.find();
430
String name = moduleMatcher.group(1);
431
Path moduleinfo = Files.createDirectories(
432
classes.getParent().resolve("src").resolve(name))
433
.resolve("module-info.java");
434
Files.write(moduleinfo, moduleSource.getBytes());
435
436
Path[] sourceFiles = new Path[classSources.length + 1];
437
sourceFiles[0] = moduleinfo;
438
439
for (int i = 0; i < classSources.length; i++) {
440
String classSource = classSources[i];
441
Matcher classMatcher = CLASS_PATTERN.matcher(classSource);
442
classMatcher.find();
443
String packageName = classMatcher.group(1);
444
String className = classMatcher.group(2);
445
446
Path packagePath = moduleinfo.getParent()
447
.resolve(packageName.replace('.', '/'));
448
Path sourceFile = Files.createDirectories(packagePath)
449
.resolve(className + ".java");
450
Files.write(sourceFile, classSource.getBytes());
451
452
sourceFiles[i + 1] = sourceFile;
453
}
454
455
javac(classes, sourceFiles);
456
}
457
458
@SafeVarargs
459
private void zip(Path file, Map.Entry<String, Path>... copies) throws IOException {
460
Files.createDirectories(file.getParent());
461
Files.deleteIfExists(file);
462
try (FileSystem zipfs = FileSystems.newFileSystem(file, Map.of("create", "true"))) {
463
for (var entry : copies) {
464
Path dstDir = zipfs.getPath(entry.getKey());
465
Path srcDir = entry.getValue();
466
467
Files.createDirectories(dstDir);
468
469
try (Stream<Path> stream = Files.walk(srcDir)) {
470
stream.filter(Files::isRegularFile).forEach(srcFile -> {
471
try {
472
Path relativePath = srcDir.relativize(srcFile);
473
Path dst = dstDir.resolve(relativePath.toString());
474
Path dstParent = dst.getParent();
475
if (dstParent != null)
476
Files.createDirectories(dstParent);
477
Files.copy(srcFile, dst);
478
} catch (IOException e) {
479
throw new RuntimeException(e);
480
}
481
});
482
}
483
}
484
}
485
}
486
487
private static OutputAnalyzer checkResult(OutputAnalyzer result, boolean isAcceptable, String failureMessage) {
488
if (isAcceptable) {
489
result.shouldHaveExitValue(SUCCESS);
490
} else {
491
result.shouldNotHaveExitValue(SUCCESS)
492
.shouldContain(failureMessage);
493
}
494
495
return result;
496
}
497
498
private OutputAnalyzer validateJar(String jarFile) throws Throwable {
499
return validateJar(jarFile, true, "");
500
}
501
502
private OutputAnalyzer validateJar(String jarFile, boolean shouldSucceed, String failureMessage) throws Throwable {
503
return checkResult(jar("--validate", "--file", jarFile), shouldSucceed, failureMessage);
504
}
505
}
506
507