8173372: Add tests for multi-release module jar API validator
authoranazarov
Thu, 26 Jan 2017 14:32:41 +0300
changeset 43306 9f6c9b11a4b1
parent 43305 8c539ce46344
child 43307 ccdf69ec24b2
8173372: Add tests for multi-release module jar API validator Reviewed-by: psandoz
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