# HG changeset patch # User anazarov # Date 1485430361 -10800 # Node ID 9f6c9b11a4b124b220662f9c5261cfe126d46d9d # Parent 8c539ce463449c9de76649040e269361c42060cb 8173372: Add tests for multi-release module jar API validator Reviewed-by: psandoz diff -r 8c539ce46344 -r 9f6c9b11a4b1 jdk/test/tools/jar/multiRelease/ApiValidatorTest.java --- a/jdk/test/tools/jar/multiRelease/ApiValidatorTest.java Wed Jan 25 10:53:01 2017 -0800 +++ b/jdk/test/tools/jar/multiRelease/ApiValidatorTest.java Thu Jan 26 14:32:41 2017 +0300 @@ -36,22 +36,42 @@ import jdk.test.lib.process.OutputAnalyzer; import jdk.testlibrary.FileUtils; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.io.IOException; import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class ApiValidatorTest extends MRTestBase { + static final Pattern MODULE_PATTERN = Pattern.compile("module (\\w+)"); + static final Pattern CLASS_PATTERN = Pattern.compile("package (\\w+).*public class (\\w+)"); + + private Path root; + private Path classes; + + @BeforeMethod + void testInit(Method method) { + root = Paths.get(method.getName()); + classes = root.resolve("classes"); + } + + @AfterMethod + void testCleanup() throws IOException { + FileUtils.deleteFileTreeWithRetry(root); + } + + @Test(dataProvider = "signatureChange") public void changeMethodSignature(String sigBase, String sigV10, - boolean isAcceptable, - Method method) throws Throwable { - Path root = Paths.get(method.getName()); - Path classes = root.resolve("classes"); + boolean isAcceptable) throws Throwable { String METHOD_SIG = "#SIG"; String classTemplate = @@ -76,8 +96,6 @@ result.shouldNotHaveExitValue(SUCCESS) .shouldContain("contains a class with different api from earlier version"); } - - FileUtils.deleteFileTreeWithRetry(root); } @DataProvider @@ -100,11 +118,7 @@ } @Test(dataProvider = "publicAPI") - public void introducingPublicMembers(String publicAPI, - Method method) throws Throwable { - Path root = Paths.get(method.getName()); - Path classes = root.resolve("classes"); - + public void introducingPublicMembers(String publicAPI) throws Throwable { String API = "#API"; String classTemplate = "public class C { \n" + @@ -122,8 +136,6 @@ "--release", "10", "-C", classes.resolve("v10").toString(), ".") .shouldNotHaveExitValue(SUCCESS) .shouldContain("contains a class with different api from earlier version"); - - FileUtils.deleteFileTreeWithRetry(root); } @DataProvider @@ -138,11 +150,7 @@ } @Test(dataProvider = "privateAPI") - public void introducingPrivateMembers(String privateAPI, - Method method) throws Throwable { - Path root = Paths.get(method.getName()); - Path classes = root.resolve("classes"); - + public void introducingPrivateMembers(String privateAPI) throws Throwable { String API = "#API"; String classTemplate = "public class C { \n" + @@ -167,8 +175,6 @@ jar("uf", jarfile, "--release", "11", "-C", classes.resolve("v10").toString(), ".") .shouldHaveExitValue(SUCCESS); - - FileUtils.deleteFileTreeWithRetry(root); } @DataProvider @@ -190,4 +196,229 @@ Files.write(classSourceFile, template.getBytes()); javac(classes, classSourceFile); } + + /* Modular multi-release checks */ + + @Test + public void moduleNameHasChanged() throws Throwable { + + compileModule(classes.resolve("base"), "module A { }"); + compileModule(classes.resolve("v10"), "module B { }"); + + String jarfile = root.resolve("test.jar").toString(); + jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", + "--release", "10", "-C", classes.resolve("v10").toString(), ".") + .shouldNotHaveExitValue(SUCCESS) + .shouldContain("incorrect name"); + + // update module-info release + jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", + "--release", "10", "-C", classes.resolve("base").toString(), ".") + .shouldHaveExitValue(SUCCESS); + jar("uf", jarfile, + "--release", "10", "-C", classes.resolve("v10").toString(), ".") + .shouldNotHaveExitValue(SUCCESS) + .shouldContain("incorrect name"); + } + + // @Test @ignore 8173370 + public void moduleBecomeOpen() throws Throwable { + + compileModule(classes.resolve("base"), "module A { }"); + compileModule(classes.resolve("v10"), "open module A { }"); + + String jarfile = root.resolve("test.jar").toString(); + jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", + "--release", "10", "-C", classes.resolve("v10").toString(), ".") + .shouldNotHaveExitValue(SUCCESS) + .shouldContain("FIX ME"); + + // update module-info release + jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", + "--release", "10", "-C", classes.resolve("base").toString(), ".") + .shouldHaveExitValue(SUCCESS); + jar("uf", jarfile, + "--release", "10", "-C", classes.resolve("v10").toString(), ".") + .shouldNotHaveExitValue(SUCCESS) + .shouldContain("FIX ME"); + } + + @Test + public void moduleRequires() throws Throwable { + + String BASE_VERSION_DIRECTIVE = "requires jdk.compiler;"; + // add transitive flag + moduleDirectivesCase(BASE_VERSION_DIRECTIVE, + "requires transitive jdk.compiler;", + false, + "contains additional \"requires transitive\""); + // remove requires + moduleDirectivesCase(BASE_VERSION_DIRECTIVE, + "", + true, + ""); + // add requires + moduleDirectivesCase(BASE_VERSION_DIRECTIVE, + "requires jdk.compiler; requires jdk.jartool;", + true, + ""); + // add requires transitive + moduleDirectivesCase(BASE_VERSION_DIRECTIVE, + "requires jdk.compiler; requires transitive jdk.jartool;", + false, + "contains additional \"requires transitive\""); + } + + @Test + public void moduleExports() throws Throwable { + + String BASE_VERSION_DIRECTIVE = "exports pkg1; exports pkg2 to jdk.compiler;"; + // add export + moduleDirectivesCase(BASE_VERSION_DIRECTIVE, + BASE_VERSION_DIRECTIVE + " exports pkg3;", + false, + "contains different \"exports\""); + // change exports to qualified exports + moduleDirectivesCase(BASE_VERSION_DIRECTIVE, + "exports pkg1 to jdk.compiler; exports pkg2;", + false, + "contains different \"exports\""); + // remove exports + moduleDirectivesCase(BASE_VERSION_DIRECTIVE, + "exports pkg1;", + false, + "contains different \"exports\""); + // add qualified exports + moduleDirectivesCase(BASE_VERSION_DIRECTIVE, + BASE_VERSION_DIRECTIVE + " exports pkg3 to jdk.compiler;", + false, + "contains different \"exports\""); + } + + @Test + public void moduleOpens() throws Throwable { + + String BASE_VERSION_DIRECTIVE = "opens pkg1; opens pkg2 to jdk.compiler;"; + // add opens + moduleDirectivesCase(BASE_VERSION_DIRECTIVE, + BASE_VERSION_DIRECTIVE + " opens pkg3;", + false, + "contains different \"opens\""); + // change opens to qualified opens + moduleDirectivesCase(BASE_VERSION_DIRECTIVE, + "opens pkg1 to jdk.compiler; opens pkg2;", + false, + "contains different \"opens\""); + // remove opens + moduleDirectivesCase(BASE_VERSION_DIRECTIVE, + "opens pkg1;", + false, + "contains different \"opens\""); + // add qualified opens + moduleDirectivesCase(BASE_VERSION_DIRECTIVE, + BASE_VERSION_DIRECTIVE + " opens pkg3 to jdk.compiler;", + false, + "contains different \"opens\""); + } + + @Test + public void moduleProvides() throws Throwable { + + String BASE_VERSION_DIRECTIVE = "provides pkg1.A with pkg1.A;"; + // add provides + moduleDirectivesCase(BASE_VERSION_DIRECTIVE, + BASE_VERSION_DIRECTIVE + " provides pkg2.B with pkg2.B;", + false, + "contains different \"provides\""); + // change service impl + moduleDirectivesCase(BASE_VERSION_DIRECTIVE, + "provides pkg1.A with pkg2.B;", + false, + "contains different \"provides\""); + // remove provides + moduleDirectivesCase(BASE_VERSION_DIRECTIVE, + "", + false, + "contains different \"provides\""); + } + + @Test + public void moduleUses() throws Throwable { + + String BASE_VERSION_DIRECTIVE = "uses pkg1.A;"; + // add + moduleDirectivesCase(BASE_VERSION_DIRECTIVE, + BASE_VERSION_DIRECTIVE + " uses pkg2.B;", + true, + ""); + // replace + moduleDirectivesCase(BASE_VERSION_DIRECTIVE, + "uses pkg2.B;", + true, + ""); + // remove + moduleDirectivesCase(BASE_VERSION_DIRECTIVE, + "", + true, + ""); + } + + private void moduleDirectivesCase(String baseDirectives, + String versionedDirectives, + boolean expectSuccess, + String expectedMessage) throws Throwable { + String[] moduleClasses = { + "package pkg1; public class A { }", + "package pkg2; public class B extends pkg1.A { }", + "package pkg3; public class C extends pkg2.B { }"}; + compileModule(classes.resolve("base"), + "module A { " + baseDirectives + " }", + moduleClasses); + compileModule(classes.resolve("v10"), + "module A { " + versionedDirectives + " }", + moduleClasses); + + String jarfile = root.resolve("test.jar").toString(); + OutputAnalyzer output = jar("cf", jarfile, + "-C", classes.resolve("base").toString(), ".", + "--release", "10", "-C", classes.resolve("v10").toString(), "."); + if (expectSuccess) { + output.shouldHaveExitValue(SUCCESS); + } else { + output.shouldNotHaveExitValue(SUCCESS) + .shouldContain(expectedMessage); + } + } + + private void compileModule(Path classes, String moduleSource, + String... classSources) throws Throwable { + Matcher moduleMatcher = MODULE_PATTERN.matcher(moduleSource); + moduleMatcher.find(); + String name = moduleMatcher.group(1); + Path moduleinfo = Files.createDirectories( + classes.getParent().resolve("src").resolve(name)) + .resolve("module-info.java"); + Files.write(moduleinfo, moduleSource.getBytes()); + + Path[] sourceFiles = new Path[classSources.length + 1]; + sourceFiles[0] = moduleinfo; + + for (int i = 0; i < classSources.length; i++) { + String classSource = classSources[i]; + Matcher classMatcher = CLASS_PATTERN.matcher(classSource); + classMatcher.find(); + String packageName = classMatcher.group(1); + String className = classMatcher.group(2); + + Path packagePath = moduleinfo.getParent() + .resolve(packageName.replace('.', '/')); + Path sourceFile = Files.createDirectories(packagePath) + .resolve(className + ".java"); + Files.write(sourceFile, classSource.getBytes()); + + sourceFiles[i + 1] = sourceFile; + } + + javac(classes, sourceFiles); + } } \ No newline at end of file