8064794: Implement negative tests for cyclic dependencies in import statements
authoraeremeev
Tue, 09 Dec 2014 01:06:11 +0200
changeset 27993 a9130450407e
parent 27992 83fb13ff472d
child 27994 6515d11b2559
8064794: Implement negative tests for cyclic dependencies in import statements Reviewed-by: jlahoda, anazarov
langtools/test/tools/javac/importscope/NegativeCyclicDependencyTest.java
langtools/test/tools/javac/staticImport/6695838/T6695838.java
langtools/test/tools/javac/staticImport/6695838/T6695838.out
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/importscope/NegativeCyclicDependencyTest.java	Tue Dec 09 01:06:11 2014 +0200
@@ -0,0 +1,333 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8064794
+ * @summary The negative test against cyclic dependencies.
+ * @library /tools/lib
+ * @build ToolBox NegativeCyclicDependencyTest
+ * @run main NegativeCyclicDependencyTest
+ */
+
+import javax.tools.JavaCompiler;
+import javax.tools.ToolProvider;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The test generates the following code:
+ *
+ * package pkg;
+ * import pkg.B.InnerB;
+ * class A extends InnerB {
+ *    static class InnerA {}
+ * }
+ *
+ * package pkg;
+ * import pkg.A.InnerA;
+ * class B extends InnerA {
+ *     static class InnerB {}
+ * }
+ *
+ * compiles and checks whether compilation fails with the correct message.
+ * The test generates all possible combination of inheritance:
+ *     1. A extends InnerB, B extends InnerA;
+ *     2. InnerA extends InnerB, InnerB extends InnerA;
+ *     3. A extends InnerB, InnerB extends InnerA;
+ *     4. B extends InnerA, InnerA extends InnerB;
+ *     5. A extends InnerA.
+ * The test checks class, enum and interface as parent class, and checks all
+ * possible import statements.
+ */
+public class NegativeCyclicDependencyTest {
+    private final static String expectedErrorMessage =
+            "\\w+:\\d+:\\d+: compiler.err.cyclic.inheritance: [\\w.]+\n1 error\n";
+
+    private final static String[] sourceTemplatesA = {
+            "package pkg;\n" +
+            "#IMPORT_TYPE\n" +
+            "#OUTER_CLASS A #INHERIT InnerB {#ENUM_SEMI\n" +
+            "    static #INNER_CLASS InnerA {}\n" +
+            "}",
+            "package pkg;\n" +
+            "#IMPORT_TYPE\n" +
+            "#OUTER_CLASS A {#ENUM_SEMI\n" +
+            "    static #INNER_CLASS InnerA #INHERIT InnerB {}\n" +
+            "}"
+    };
+
+    private final static String[] sourceTemplatesB = {
+            "package pkg;\n" +
+            "#IMPORT_TYPE\n" +
+            "#OUTER_CLASS B #INHERIT InnerA {#ENUM_SEMI\n" +
+            "    static #INNER_CLASS InnerB {}\n" +
+            "}",
+            "package pkg;\n" +
+            "#IMPORT_TYPE\n" +
+            "#OUTER_CLASS B {#ENUM_SEMI\n" +
+            "    static #INNER_CLASS InnerB #INHERIT InnerA {}\n" +
+            "}"
+    };
+
+    private final static String sourceTemplate =
+            "package pkg;\n" +
+            "#IMPORT_TYPE\n" +
+            "#OUTER_CLASS A #INHERIT InnerA {#ENUM_SEMI\n" +
+            "    static #INNER_CLASS InnerA {}\n" +
+            "}";
+
+    public static void main(String[] args) {
+        new NegativeCyclicDependencyTest().test();
+    }
+
+    public void test() {
+        int passed = 0;
+        List<TestCase> testCases = generateTestCases();
+        for (TestCase testCase : testCases) {
+            try {
+                String output = compile(testCase.sources);
+                if (!output.matches(testCase.expectedMessage)) {
+                    reportFailure(testCase);
+                    printf(String.format("Message: %s, does not match regexp: %s\n",
+                            output, testCase.expectedMessage));
+                } else {
+                    ++passed;
+                }
+            } catch (RuntimeException e) {
+                reportFailure(testCase);
+                e.printStackTrace();
+            }
+        }
+        if (passed < testCases.size()) {
+            throw new RuntimeException(String.format("Test failed: " +
+                    "passed: %d, failed: %d, total: %d.",
+                    passed, testCases.size() - passed, testCases.size()));
+        }
+    }
+
+    private void reportFailure(TestCase testCase) {
+        echo("Test case failed.");
+        for (ToolBox.JavaSource source : testCase.sources) {
+            echo(source.getCharContent(true));
+            echo();
+        }
+    }
+
+    public List<TestCase> generateTestCases() {
+        List<TestCase> testCases = generateTestCasesWithTwoClasses();
+        testCases.addAll(generateTestCasesWithOneClass());
+        return testCases;
+    }
+
+    private List<TestCase> generateTestCasesWithOneClass() {
+        String importedClassName = "pkg.A.InnerA";
+        List<TestCase> testCases = new ArrayList<>();
+        for (ClassType outerClass : ClassType.values()) {
+            for (ClassType innerClass : ClassType.values()) {
+                if (!outerClass.canInherit(innerClass)) {
+                    continue;
+                }
+                for (ImportType importType : ImportType.values()) {
+                    String source = generateSource(
+                            sourceTemplate,
+                            outerClass,
+                            innerClass,
+                            outerClass.inheritedString(innerClass),
+                            importType,
+                            importedClassName);
+                    testCases.add(new TestCase(expectedErrorMessage,
+                            new ToolBox.JavaSource("A", source)));
+                }
+            }
+        }
+        return testCases;
+    }
+
+    private List<TestCase> generateTestCasesWithTwoClasses() {
+        String importedClassName1 = "pkg.A.InnerA";
+        String importedClassName2 = "pkg.B.InnerB";
+        List<TestCase> testCases = new ArrayList<>();
+        for (int i = 0; i < sourceTemplatesA.length; ++i) {
+            for (int j = 0; j < sourceTemplatesB.length; ++j) {
+                for (ClassType outerClass1 : ClassType.values()) {
+                    for (ClassType outerClass2 : ClassType.values()) {
+                        for (ClassType innerClass1 : ClassType.values()) {
+                            for (ClassType innerClass2 : ClassType.values()) {
+                                ClassType childClass1 = i == 0 ? outerClass1 : innerClass1;
+                                ClassType childClass2 = j == 0 ? outerClass2 : innerClass2;
+                                if (!childClass1.canInherit(innerClass2) ||
+                                        !childClass2.canInherit(innerClass1)) {
+                                    continue;
+                                }
+                                for (ImportType importType1 : ImportType.values()) {
+                                    for (ImportType importType2 : ImportType.values()) {
+                                        String sourceA = generateSource(
+                                                sourceTemplatesA[i],
+                                                outerClass1,
+                                                innerClass1,
+                                                childClass1.inheritedString(innerClass2),
+                                                importType1,
+                                                importedClassName2);
+                                        String sourceB = generateSource(
+                                                sourceTemplatesB[j],
+                                                outerClass2,
+                                                innerClass2,
+                                                childClass2.inheritedString(innerClass1),
+                                                importType2,
+                                                importedClassName1);
+                                        testCases.add(new TestCase(expectedErrorMessage,
+                                                new ToolBox.JavaSource("A", sourceA),
+                                                new ToolBox.JavaSource("B", sourceB)));
+                                        testCases.add(new TestCase(expectedErrorMessage,
+                                                new ToolBox.JavaSource("B", sourceB),
+                                                new ToolBox.JavaSource("A", sourceA)));
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return testCases;
+    }
+
+    public String generateSource(String template,
+                                 ClassType outerClass,
+                                 ClassType innerClass,
+                                 String inheritString,
+                                 ImportType importType,
+                                 String innerClassName) {
+        return template
+                .replace("#OUTER_CLASS", outerClass.getType())
+                .replace("#INNER_CLASS", innerClass.getType())
+                .replace("#INHERIT", inheritString)
+                .replace("#IMPORT_TYPE", importType.getImport(innerClassName))
+                .replace("#ENUM_SEMI", outerClass == ClassType.ENUM ? ";" : "");
+    }
+
+    /**
+     * Compiles sources with -XDrawDiagnostics flag and
+     * returns the output of compilation.
+     *
+     * @param sources sources
+     * @return the result of compilation
+     */
+    private String compile(ToolBox.JavaSource...sources) {
+        JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
+        StringWriter writer = new StringWriter();
+        JavaCompiler.CompilationTask ct = jc.getTask(writer, null, null,
+                Arrays.asList("-XDrawDiagnostics"),
+                null, Arrays.asList(sources));
+        if (ct.call()) {
+            throw new RuntimeException("Expected compilation failure.");
+        }
+        return writer.toString();
+    }
+
+    public void echo() {
+        echo("");
+    }
+
+    public void echo(CharSequence message) {
+        echo(message.toString());
+    }
+
+    public void echo(String message) {
+        printf(message + "\n");
+    }
+
+    public void printf(String template, Object...args) {
+        System.err.print(String.format(template, args).replace("\n", ToolBox.lineSeparator));
+    }
+
+    /**
+     * The class represents a test case.
+     */
+    public static class TestCase {
+        public final ToolBox.JavaSource[] sources;
+        public final String expectedMessage;
+
+        public TestCase(String expectedMessage, ToolBox.JavaSource...sources) {
+            this.sources = sources;
+            this.expectedMessage = expectedMessage;
+        }
+    }
+
+    /**
+     * The enum represents all possible imports.
+     */
+    public enum ImportType {
+        SINGLE_IMPORT("import %s;"),
+        IMPORT_ON_DEMAND("import %s.*;"),
+        SINGLE_STATIC_IMPORT("import static %s;"),
+        STATIC_IMPORT_ON_DEMAND("import static %s.*;");
+
+        private final String type;
+
+        private ImportType(String type) {
+            this.type = type;
+        }
+
+        public String getImport(String className) {
+            if (this == ImportType.IMPORT_ON_DEMAND || this == ImportType.STATIC_IMPORT_ON_DEMAND) {
+                int lastDot = className.lastIndexOf('.');
+                className = className.substring(0, lastDot);
+            }
+            return String.format(type, className);
+        }
+    }
+
+    /**
+     * The enum represents all possible class types that can be used in
+     * inheritance.
+     */
+    public enum ClassType {
+        CLASS("class"), INTERFACE("interface"), ENUM("enum");
+
+        public boolean canInherit(ClassType innerClass) {
+            return innerClass != ENUM && !(this == ENUM && innerClass == ClassType.CLASS
+                    || this == INTERFACE && innerClass == ClassType.CLASS);
+        }
+
+        public String inheritedString(ClassType innerClass) {
+            if (!canInherit(innerClass)) {
+                throw new IllegalArgumentException(String.format("%s cannot inherit %s", this, innerClass));
+            }
+            return this == innerClass ? "extends" : "implements";
+        }
+
+        private final String type;
+
+        private ClassType(String type) {
+            this.type = type;
+        }
+
+        public String getType() {
+            return type;
+        }
+    }
+}
--- a/langtools/test/tools/javac/staticImport/6695838/T6695838.java	Mon Dec 08 21:26:04 2014 +0100
+++ b/langtools/test/tools/javac/staticImport/6695838/T6695838.java	Tue Dec 09 01:06:11 2014 +0200
@@ -27,5 +27,5 @@
  * @summary javac does not detect cyclic inheritance involving static inner classes after import clause
  * @author Maurizio Cimadamore
  *
- * @compile/fail a/FooInterface.java
+ * @compile/fail/ref=T6695838.out -XDrawDiagnostics a/FooInterface.java
  */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/staticImport/6695838/T6695838.out	Tue Dec 09 01:06:11 2014 +0200
@@ -0,0 +1,2 @@
+Foo.java:26:1: compiler.err.cyclic.inheritance: a.Foo
+1 error
\ No newline at end of file