Path: blob/master/test/jdk/tools/jar/multiRelease/ApiValidatorTest.java
66644 views
/*1* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/*24* @test25# @bug 819674826* @summary Tests for API validator.27* @library /test/lib28* @modules java.base/jdk.internal.misc29* jdk.compiler30* jdk.jartool31* @build jdk.test.lib.Utils32* jdk.test.lib.Asserts33* jdk.test.lib.JDKToolFinder34* jdk.test.lib.JDKToolLauncher35* jdk.test.lib.Platform36* jdk.test.lib.process.*37* MRTestBase38* @run testng/timeout=1200 ApiValidatorTest39*/4041import jdk.test.lib.process.OutputAnalyzer;42import org.testng.annotations.BeforeMethod;43import org.testng.annotations.DataProvider;44import org.testng.annotations.Test;4546import java.io.IOException;47import java.lang.reflect.Method;48import java.nio.file.FileSystem;49import java.nio.file.FileSystems;50import java.nio.file.Files;51import java.nio.file.Path;52import java.nio.file.Paths;53import java.util.Map;54import java.util.regex.Matcher;55import java.util.regex.Pattern;56import java.util.stream.Stream;5758public class ApiValidatorTest extends MRTestBase {5960static final Pattern MODULE_PATTERN = Pattern.compile("module (\\w+)");61static final Pattern CLASS_PATTERN = Pattern.compile("package (\\w+).*public class (\\w+)");6263private Path root;64private Path classes;6566@BeforeMethod67void testInit(Method method) {68root = Paths.get(method.getName());69classes = root.resolve("classes");70}7172@Test(dataProvider = "signatureChange")73public void changeMethodSignature(String sigBase, String sigV10,74boolean isAcceptable) throws Throwable {7576String METHOD_SIG = "#SIG";77String classTemplate =78"public class C { \n" +79" " + METHOD_SIG + "{ throw new RuntimeException(); };\n" +80"}\n";81String base = classTemplate.replace(METHOD_SIG, sigBase);82String v10 = classTemplate.replace(METHOD_SIG, sigV10);8384compileTemplate(classes.resolve("base"), base);85compileTemplate(classes.resolve("v10"), v10);8687String jarfile = root.resolve("test.jar").toString();88OutputAnalyzer result = jar("cf", jarfile,89"-C", classes.resolve("base").toString(), ".",90"--release", "10", "-C", classes.resolve("v10").toString(),91".");9293String failureMessage = "contains a class with different api from earlier version";94checkResult(result, isAcceptable, failureMessage);95if (isAcceptable) result.shouldBeEmptyIgnoreVMWarnings();969798Path malformed = root.resolve("zip").resolve("test.jar");99zip(malformed,100Map.entry("", classes.resolve("base")),101Map.entry("META-INF/versions/10", classes.resolve("v10")));102103result = validateJar(malformed.toString(), isAcceptable, failureMessage);104if (isAcceptable) result.shouldBeEmptyIgnoreVMWarnings();105}106107@DataProvider108Object[][] signatureChange() {109return new Object[][]{110{"public int m()", "protected int m()", false},111{"protected int m()", "public int m()", false},112{"public int m()", "int m()", false},113{"protected int m()", "private int m()", false},114{"private int m()", "int m()", true},115{"int m()", "private int m()", true},116{"int m()", "private int m(boolean b)", true},117{"public int m()", "public int m(int i)", false},118{"public int m()", "public int k()", false},119{"public int m()", "private int k()", false},120// @ignore JDK-8172147 {"public int m()", "public boolean m()", false},121// @ignore JDK-8172147 {"public boolean", "public Boolean", false},122// @ignore JDK-8172147 {"public <T> T", "public <T extends String> T", false},123};124}125126@Test(dataProvider = "publicAPI")127public void introducingPublicMembers(String publicAPI) throws Throwable {128String API = "#API";129String classTemplate =130"public class C { \n" +131" " + API + "\n" +132" public void method(){ };\n" +133"}\n";134String base = classTemplate.replace(API, "");135String v10 = classTemplate.replace(API, publicAPI);136137compileTemplate(classes.resolve("base"), base);138compileTemplate(classes.resolve("v10"), v10);139140String failureMessage = "contains a class with different api from earlier version";141142String jarfile = root.resolve("test.jar").toString();143jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",144"--release", "10", "-C", classes.resolve("v10").toString(), ".")145.shouldNotHaveExitValue(SUCCESS)146.shouldContain(failureMessage);147148Path malformed = root.resolve("zip").resolve("test.jar");149zip(malformed,150Map.entry("", classes.resolve("base")),151Map.entry("META-INF/versions/10", classes.resolve("v10")));152153validateJar(malformed.toString(), false, failureMessage);154}155156@DataProvider157Object[][] publicAPI() {158return new Object[][]{159// @ignore JDK-8172148 {"protected class Inner { public void m(){ } } "}, // protected inner class160// @ignore JDK-8172148 {"public class Inner { public void m(){ } }"}, // public inner class161// @ignore JDK-8172148 {"public enum E { A; }"}, // public enum162{"public void m(){ }"}, // public method163{"protected void m(){ }"}, // protected method164};165}166167@Test(dataProvider = "privateAPI")168public void introducingPrivateMembers(String privateAPI) throws Throwable {169String API = "#API";170String classTemplate =171"public class C { \n" +172" " + API + "\n" +173" public void method(){ };\n" +174"}\n";175String base = classTemplate.replace(API, "");176String v10 = classTemplate.replace(API, privateAPI);177178compileTemplate(classes.resolve("base"), base);179compileTemplate(classes.resolve("v10"), v10);180181String jarfile = root.resolve("test.jar").toString();182jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",183"--release", "10", "-C", classes.resolve("v10").toString(), ".")184.shouldHaveExitValue(SUCCESS);185validateJar(jarfile);186// add release187jar("uf", jarfile,188"--release", "11", "-C", classes.resolve("v10").toString(), ".")189.shouldHaveExitValue(SUCCESS);190validateJar(jarfile);191// replace release192jar("uf", jarfile,193"--release", "11", "-C", classes.resolve("v10").toString(), ".")194.shouldHaveExitValue(SUCCESS);195validateJar(jarfile);196}197198@DataProvider199Object[][] privateAPI() {200return new Object[][]{201{"private class Inner { public void m(){ } } "}, // private inner class202{"class Inner { public void m(){ } }"}, // package private inner class203{"enum E { A; }"}, // package private enum204// Local class and private method205{"private void m(){ class Inner { public void m(){} } Inner i = null; }"},206{"void m(){ }"}, // package private method207};208}209210private void compileTemplate(Path classes, String template) throws Throwable {211Path classSourceFile = Files.createDirectories(212classes.getParent().resolve("src").resolve(classes.getFileName()))213.resolve("C.java");214Files.write(classSourceFile, template.getBytes());215javac(classes, classSourceFile);216}217218/* Modular multi-release checks */219220@Test221public void moduleNameHasChanged() throws Throwable {222223compileModule(classes.resolve("base"), "module A { }");224compileModule(classes.resolve("v10"), "module B { }");225226String failureMessage = "incorrect name";227228String jarfile = root.resolve("test.jar").toString();229jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",230"--release", "10", "-C", classes.resolve("v10").toString(), ".")231.shouldNotHaveExitValue(SUCCESS)232.shouldContain(failureMessage);233234Path malformed = root.resolve("zip").resolve("test.jar");235zip(malformed,236Map.entry("", classes.resolve("base")),237Map.entry("META-INF/versions/10", classes.resolve("v10")));238239validateJar(malformed.toString(), false, failureMessage);240241// update module-info release242jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",243"--release", "10", "-C", classes.resolve("base").toString(), ".")244.shouldHaveExitValue(SUCCESS);245validateJar(jarfile);246247jar("uf", jarfile,248"--release", "10", "-C", classes.resolve("v10").toString(), ".")249.shouldNotHaveExitValue(SUCCESS)250.shouldContain(failureMessage);251}252253// @Test @ignore 8173370254public void moduleBecomeOpen() throws Throwable {255256compileModule(classes.resolve("base"), "module A { }");257compileModule(classes.resolve("v10"), "open module A { }");258259String jarfile = root.resolve("test.jar").toString();260jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",261"--release", "10", "-C", classes.resolve("v10").toString(), ".")262.shouldNotHaveExitValue(SUCCESS)263.shouldContain("FIX ME");264265// update module-info release266jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",267"--release", "10", "-C", classes.resolve("base").toString(), ".")268.shouldHaveExitValue(SUCCESS);269validateJar(jarfile);270jar("uf", jarfile,271"--release", "10", "-C", classes.resolve("v10").toString(), ".")272.shouldNotHaveExitValue(SUCCESS)273.shouldContain("FIX ME");274}275276@Test277public void moduleRequires() throws Throwable {278279String BASE_VERSION_DIRECTIVE = "requires jdk.compiler;";280// add transitive flag281moduleDirectivesCase(BASE_VERSION_DIRECTIVE,282"requires transitive jdk.compiler;",283false,284"contains additional \"requires transitive\"");285// remove requires286moduleDirectivesCase(BASE_VERSION_DIRECTIVE,287"",288true,289"");290// add requires291moduleDirectivesCase(BASE_VERSION_DIRECTIVE,292"requires jdk.compiler; requires jdk.jartool;",293true,294"");295// add requires transitive296moduleDirectivesCase(BASE_VERSION_DIRECTIVE,297"requires jdk.compiler; requires transitive jdk.jartool;",298false,299"contains additional \"requires transitive\"");300}301302@Test303public void moduleExports() throws Throwable {304305String BASE_VERSION_DIRECTIVE = "exports pkg1; exports pkg2 to jdk.compiler;";306// add export307moduleDirectivesCase(BASE_VERSION_DIRECTIVE,308BASE_VERSION_DIRECTIVE + " exports pkg3;",309false,310"contains different \"exports\"");311// change exports to qualified exports312moduleDirectivesCase(BASE_VERSION_DIRECTIVE,313"exports pkg1 to jdk.compiler; exports pkg2;",314false,315"contains different \"exports\"");316// remove exports317moduleDirectivesCase(BASE_VERSION_DIRECTIVE,318"exports pkg1;",319false,320"contains different \"exports\"");321// add qualified exports322moduleDirectivesCase(BASE_VERSION_DIRECTIVE,323BASE_VERSION_DIRECTIVE + " exports pkg3 to jdk.compiler;",324false,325"contains different \"exports\"");326}327328@Test329public void moduleOpens() throws Throwable {330331String BASE_VERSION_DIRECTIVE = "opens pkg1; opens pkg2 to jdk.compiler;";332// add opens333moduleDirectivesCase(BASE_VERSION_DIRECTIVE,334BASE_VERSION_DIRECTIVE + " opens pkg3;",335false,336"contains different \"opens\"");337// change opens to qualified opens338moduleDirectivesCase(BASE_VERSION_DIRECTIVE,339"opens pkg1 to jdk.compiler; opens pkg2;",340false,341"contains different \"opens\"");342// remove opens343moduleDirectivesCase(BASE_VERSION_DIRECTIVE,344"opens pkg1;",345false,346"contains different \"opens\"");347// add qualified opens348moduleDirectivesCase(BASE_VERSION_DIRECTIVE,349BASE_VERSION_DIRECTIVE + " opens pkg3 to jdk.compiler;",350false,351"contains different \"opens\"");352}353354@Test355public void moduleProvides() throws Throwable {356357String BASE_VERSION_DIRECTIVE = "provides pkg1.A with pkg1.A;";358// add provides359moduleDirectivesCase(BASE_VERSION_DIRECTIVE,360BASE_VERSION_DIRECTIVE + " provides pkg2.B with pkg2.B;",361false,362"contains different \"provides\"");363// change service impl364moduleDirectivesCase(BASE_VERSION_DIRECTIVE,365"provides pkg1.A with pkg2.B;",366false,367"contains different \"provides\"");368// remove provides369moduleDirectivesCase(BASE_VERSION_DIRECTIVE,370"",371false,372"contains different \"provides\"");373}374375@Test376public void moduleUses() throws Throwable {377378String BASE_VERSION_DIRECTIVE = "uses pkg1.A;";379// add380moduleDirectivesCase(BASE_VERSION_DIRECTIVE,381BASE_VERSION_DIRECTIVE + " uses pkg2.B;",382true,383"");384// replace385moduleDirectivesCase(BASE_VERSION_DIRECTIVE,386"uses pkg2.B;",387true,388"");389// remove390moduleDirectivesCase(BASE_VERSION_DIRECTIVE,391"",392true,393"");394}395396private void moduleDirectivesCase(String baseDirectives,397String versionedDirectives,398boolean expectSuccess,399String expectedMessage) throws Throwable {400String[] moduleClasses = {401"package pkg1; public class A { }",402"package pkg2; public class B extends pkg1.A { }",403"package pkg3; public class C extends pkg2.B { }"};404compileModule(classes.resolve("base"),405"module A { " + baseDirectives + " }",406moduleClasses);407compileModule(classes.resolve("v10"),408"module A { " + versionedDirectives + " }",409moduleClasses);410411String jarfile = root.resolve("test.jar").toString();412OutputAnalyzer output = jar("cf", jarfile,413"-C", classes.resolve("base").toString(), ".",414"--release", "10", "-C", classes.resolve("v10").toString(), ".");415checkResult(output, expectSuccess, expectedMessage);416417Path malformed = root.resolve("zip").resolve("test.jar");418zip(malformed,419Map.entry("", classes.resolve("base")),420Map.entry("META-INF/versions/10", classes.resolve("v10")));421422validateJar(malformed.toString(), expectSuccess, expectedMessage);423}424425private void compileModule(Path classes, String moduleSource,426String... classSources) throws Throwable {427Matcher moduleMatcher = MODULE_PATTERN.matcher(moduleSource);428moduleMatcher.find();429String name = moduleMatcher.group(1);430Path moduleinfo = Files.createDirectories(431classes.getParent().resolve("src").resolve(name))432.resolve("module-info.java");433Files.write(moduleinfo, moduleSource.getBytes());434435Path[] sourceFiles = new Path[classSources.length + 1];436sourceFiles[0] = moduleinfo;437438for (int i = 0; i < classSources.length; i++) {439String classSource = classSources[i];440Matcher classMatcher = CLASS_PATTERN.matcher(classSource);441classMatcher.find();442String packageName = classMatcher.group(1);443String className = classMatcher.group(2);444445Path packagePath = moduleinfo.getParent()446.resolve(packageName.replace('.', '/'));447Path sourceFile = Files.createDirectories(packagePath)448.resolve(className + ".java");449Files.write(sourceFile, classSource.getBytes());450451sourceFiles[i + 1] = sourceFile;452}453454javac(classes, sourceFiles);455}456457@SafeVarargs458private void zip(Path file, Map.Entry<String, Path>... copies) throws IOException {459Files.createDirectories(file.getParent());460Files.deleteIfExists(file);461try (FileSystem zipfs = FileSystems.newFileSystem(file, Map.of("create", "true"))) {462for (var entry : copies) {463Path dstDir = zipfs.getPath(entry.getKey());464Path srcDir = entry.getValue();465466Files.createDirectories(dstDir);467468try (Stream<Path> stream = Files.walk(srcDir)) {469stream.filter(Files::isRegularFile).forEach(srcFile -> {470try {471Path relativePath = srcDir.relativize(srcFile);472Path dst = dstDir.resolve(relativePath.toString());473Path dstParent = dst.getParent();474if (dstParent != null)475Files.createDirectories(dstParent);476Files.copy(srcFile, dst);477} catch (IOException e) {478throw new RuntimeException(e);479}480});481}482}483}484}485486private static OutputAnalyzer checkResult(OutputAnalyzer result, boolean isAcceptable, String failureMessage) {487if (isAcceptable) {488result.shouldHaveExitValue(SUCCESS);489} else {490result.shouldNotHaveExitValue(SUCCESS)491.shouldContain(failureMessage);492}493494return result;495}496497private OutputAnalyzer validateJar(String jarFile) throws Throwable {498return validateJar(jarFile, true, "");499}500501private OutputAnalyzer validateJar(String jarFile, boolean shouldSucceed, String failureMessage) throws Throwable {502return checkResult(jar("--validate", "--file", jarFile), shouldSucceed, failureMessage);503}504}505506507