# HG changeset patch # User aeremeev # Date 1418079971 -7200 # Node ID a9130450407e68ba69a60f973447c9ece656be38 # Parent 83fb13ff472d34195ccc8755c00be4426cebfae8 8064794: Implement negative tests for cyclic dependencies in import statements Reviewed-by: jlahoda, anazarov diff -r 83fb13ff472d -r a9130450407e langtools/test/tools/javac/importscope/NegativeCyclicDependencyTest.java --- /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 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 generateTestCases() { + List testCases = generateTestCasesWithTwoClasses(); + testCases.addAll(generateTestCasesWithOneClass()); + return testCases; + } + + private List generateTestCasesWithOneClass() { + String importedClassName = "pkg.A.InnerA"; + List 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 generateTestCasesWithTwoClasses() { + String importedClassName1 = "pkg.A.InnerA"; + String importedClassName2 = "pkg.B.InnerB"; + List 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; + } + } +} diff -r 83fb13ff472d -r a9130450407e langtools/test/tools/javac/staticImport/6695838/T6695838.java --- 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 */ diff -r 83fb13ff472d -r a9130450407e langtools/test/tools/javac/staticImport/6695838/T6695838.out --- /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