--- /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;
+ }
+ }
+}