langtools/test/tools/javac/importscope/NegativeCyclicDependencyTest.java
changeset 27993 a9130450407e
child 27994 6515d11b2559
equal deleted inserted replaced
27992:83fb13ff472d 27993:a9130450407e
       
     1 /*
       
     2  * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.
       
     8  *
       
     9  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    12  * version 2 for more details (a copy is included in the LICENSE file that
       
    13  * accompanied this code).
       
    14  *
       
    15  * You should have received a copy of the GNU General Public License version
       
    16  * 2 along with this work; if not, write to the Free Software Foundation,
       
    17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    18  *
       
    19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    20  * or visit www.oracle.com if you need additional information or have any
       
    21  * questions.
       
    22  */
       
    23 
       
    24 /*
       
    25  * @test
       
    26  * @bug 8064794
       
    27  * @summary The negative test against cyclic dependencies.
       
    28  * @library /tools/lib
       
    29  * @build ToolBox NegativeCyclicDependencyTest
       
    30  * @run main NegativeCyclicDependencyTest
       
    31  */
       
    32 
       
    33 import javax.tools.JavaCompiler;
       
    34 import javax.tools.ToolProvider;
       
    35 import java.io.StringWriter;
       
    36 import java.util.ArrayList;
       
    37 import java.util.Arrays;
       
    38 import java.util.List;
       
    39 
       
    40 /**
       
    41  * The test generates the following code:
       
    42  *
       
    43  * package pkg;
       
    44  * import pkg.B.InnerB;
       
    45  * class A extends InnerB {
       
    46  *    static class InnerA {}
       
    47  * }
       
    48  *
       
    49  * package pkg;
       
    50  * import pkg.A.InnerA;
       
    51  * class B extends InnerA {
       
    52  *     static class InnerB {}
       
    53  * }
       
    54  *
       
    55  * compiles and checks whether compilation fails with the correct message.
       
    56  * The test generates all possible combination of inheritance:
       
    57  *     1. A extends InnerB, B extends InnerA;
       
    58  *     2. InnerA extends InnerB, InnerB extends InnerA;
       
    59  *     3. A extends InnerB, InnerB extends InnerA;
       
    60  *     4. B extends InnerA, InnerA extends InnerB;
       
    61  *     5. A extends InnerA.
       
    62  * The test checks class, enum and interface as parent class, and checks all
       
    63  * possible import statements.
       
    64  */
       
    65 public class NegativeCyclicDependencyTest {
       
    66     private final static String expectedErrorMessage =
       
    67             "\\w+:\\d+:\\d+: compiler.err.cyclic.inheritance: [\\w.]+\n1 error\n";
       
    68 
       
    69     private final static String[] sourceTemplatesA = {
       
    70             "package pkg;\n" +
       
    71             "#IMPORT_TYPE\n" +
       
    72             "#OUTER_CLASS A #INHERIT InnerB {#ENUM_SEMI\n" +
       
    73             "    static #INNER_CLASS InnerA {}\n" +
       
    74             "}",
       
    75             "package pkg;\n" +
       
    76             "#IMPORT_TYPE\n" +
       
    77             "#OUTER_CLASS A {#ENUM_SEMI\n" +
       
    78             "    static #INNER_CLASS InnerA #INHERIT InnerB {}\n" +
       
    79             "}"
       
    80     };
       
    81 
       
    82     private final static String[] sourceTemplatesB = {
       
    83             "package pkg;\n" +
       
    84             "#IMPORT_TYPE\n" +
       
    85             "#OUTER_CLASS B #INHERIT InnerA {#ENUM_SEMI\n" +
       
    86             "    static #INNER_CLASS InnerB {}\n" +
       
    87             "}",
       
    88             "package pkg;\n" +
       
    89             "#IMPORT_TYPE\n" +
       
    90             "#OUTER_CLASS B {#ENUM_SEMI\n" +
       
    91             "    static #INNER_CLASS InnerB #INHERIT InnerA {}\n" +
       
    92             "}"
       
    93     };
       
    94 
       
    95     private final static String sourceTemplate =
       
    96             "package pkg;\n" +
       
    97             "#IMPORT_TYPE\n" +
       
    98             "#OUTER_CLASS A #INHERIT InnerA {#ENUM_SEMI\n" +
       
    99             "    static #INNER_CLASS InnerA {}\n" +
       
   100             "}";
       
   101 
       
   102     public static void main(String[] args) {
       
   103         new NegativeCyclicDependencyTest().test();
       
   104     }
       
   105 
       
   106     public void test() {
       
   107         int passed = 0;
       
   108         List<TestCase> testCases = generateTestCases();
       
   109         for (TestCase testCase : testCases) {
       
   110             try {
       
   111                 String output = compile(testCase.sources);
       
   112                 if (!output.matches(testCase.expectedMessage)) {
       
   113                     reportFailure(testCase);
       
   114                     printf(String.format("Message: %s, does not match regexp: %s\n",
       
   115                             output, testCase.expectedMessage));
       
   116                 } else {
       
   117                     ++passed;
       
   118                 }
       
   119             } catch (RuntimeException e) {
       
   120                 reportFailure(testCase);
       
   121                 e.printStackTrace();
       
   122             }
       
   123         }
       
   124         if (passed < testCases.size()) {
       
   125             throw new RuntimeException(String.format("Test failed: " +
       
   126                     "passed: %d, failed: %d, total: %d.",
       
   127                     passed, testCases.size() - passed, testCases.size()));
       
   128         }
       
   129     }
       
   130 
       
   131     private void reportFailure(TestCase testCase) {
       
   132         echo("Test case failed.");
       
   133         for (ToolBox.JavaSource source : testCase.sources) {
       
   134             echo(source.getCharContent(true));
       
   135             echo();
       
   136         }
       
   137     }
       
   138 
       
   139     public List<TestCase> generateTestCases() {
       
   140         List<TestCase> testCases = generateTestCasesWithTwoClasses();
       
   141         testCases.addAll(generateTestCasesWithOneClass());
       
   142         return testCases;
       
   143     }
       
   144 
       
   145     private List<TestCase> generateTestCasesWithOneClass() {
       
   146         String importedClassName = "pkg.A.InnerA";
       
   147         List<TestCase> testCases = new ArrayList<>();
       
   148         for (ClassType outerClass : ClassType.values()) {
       
   149             for (ClassType innerClass : ClassType.values()) {
       
   150                 if (!outerClass.canInherit(innerClass)) {
       
   151                     continue;
       
   152                 }
       
   153                 for (ImportType importType : ImportType.values()) {
       
   154                     String source = generateSource(
       
   155                             sourceTemplate,
       
   156                             outerClass,
       
   157                             innerClass,
       
   158                             outerClass.inheritedString(innerClass),
       
   159                             importType,
       
   160                             importedClassName);
       
   161                     testCases.add(new TestCase(expectedErrorMessage,
       
   162                             new ToolBox.JavaSource("A", source)));
       
   163                 }
       
   164             }
       
   165         }
       
   166         return testCases;
       
   167     }
       
   168 
       
   169     private List<TestCase> generateTestCasesWithTwoClasses() {
       
   170         String importedClassName1 = "pkg.A.InnerA";
       
   171         String importedClassName2 = "pkg.B.InnerB";
       
   172         List<TestCase> testCases = new ArrayList<>();
       
   173         for (int i = 0; i < sourceTemplatesA.length; ++i) {
       
   174             for (int j = 0; j < sourceTemplatesB.length; ++j) {
       
   175                 for (ClassType outerClass1 : ClassType.values()) {
       
   176                     for (ClassType outerClass2 : ClassType.values()) {
       
   177                         for (ClassType innerClass1 : ClassType.values()) {
       
   178                             for (ClassType innerClass2 : ClassType.values()) {
       
   179                                 ClassType childClass1 = i == 0 ? outerClass1 : innerClass1;
       
   180                                 ClassType childClass2 = j == 0 ? outerClass2 : innerClass2;
       
   181                                 if (!childClass1.canInherit(innerClass2) ||
       
   182                                         !childClass2.canInherit(innerClass1)) {
       
   183                                     continue;
       
   184                                 }
       
   185                                 for (ImportType importType1 : ImportType.values()) {
       
   186                                     for (ImportType importType2 : ImportType.values()) {
       
   187                                         String sourceA = generateSource(
       
   188                                                 sourceTemplatesA[i],
       
   189                                                 outerClass1,
       
   190                                                 innerClass1,
       
   191                                                 childClass1.inheritedString(innerClass2),
       
   192                                                 importType1,
       
   193                                                 importedClassName2);
       
   194                                         String sourceB = generateSource(
       
   195                                                 sourceTemplatesB[j],
       
   196                                                 outerClass2,
       
   197                                                 innerClass2,
       
   198                                                 childClass2.inheritedString(innerClass1),
       
   199                                                 importType2,
       
   200                                                 importedClassName1);
       
   201                                         testCases.add(new TestCase(expectedErrorMessage,
       
   202                                                 new ToolBox.JavaSource("A", sourceA),
       
   203                                                 new ToolBox.JavaSource("B", sourceB)));
       
   204                                         testCases.add(new TestCase(expectedErrorMessage,
       
   205                                                 new ToolBox.JavaSource("B", sourceB),
       
   206                                                 new ToolBox.JavaSource("A", sourceA)));
       
   207                                     }
       
   208                                 }
       
   209                             }
       
   210                         }
       
   211                     }
       
   212                 }
       
   213             }
       
   214         }
       
   215         return testCases;
       
   216     }
       
   217 
       
   218     public String generateSource(String template,
       
   219                                  ClassType outerClass,
       
   220                                  ClassType innerClass,
       
   221                                  String inheritString,
       
   222                                  ImportType importType,
       
   223                                  String innerClassName) {
       
   224         return template
       
   225                 .replace("#OUTER_CLASS", outerClass.getType())
       
   226                 .replace("#INNER_CLASS", innerClass.getType())
       
   227                 .replace("#INHERIT", inheritString)
       
   228                 .replace("#IMPORT_TYPE", importType.getImport(innerClassName))
       
   229                 .replace("#ENUM_SEMI", outerClass == ClassType.ENUM ? ";" : "");
       
   230     }
       
   231 
       
   232     /**
       
   233      * Compiles sources with -XDrawDiagnostics flag and
       
   234      * returns the output of compilation.
       
   235      *
       
   236      * @param sources sources
       
   237      * @return the result of compilation
       
   238      */
       
   239     private String compile(ToolBox.JavaSource...sources) {
       
   240         JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
       
   241         StringWriter writer = new StringWriter();
       
   242         JavaCompiler.CompilationTask ct = jc.getTask(writer, null, null,
       
   243                 Arrays.asList("-XDrawDiagnostics"),
       
   244                 null, Arrays.asList(sources));
       
   245         if (ct.call()) {
       
   246             throw new RuntimeException("Expected compilation failure.");
       
   247         }
       
   248         return writer.toString();
       
   249     }
       
   250 
       
   251     public void echo() {
       
   252         echo("");
       
   253     }
       
   254 
       
   255     public void echo(CharSequence message) {
       
   256         echo(message.toString());
       
   257     }
       
   258 
       
   259     public void echo(String message) {
       
   260         printf(message + "\n");
       
   261     }
       
   262 
       
   263     public void printf(String template, Object...args) {
       
   264         System.err.print(String.format(template, args).replace("\n", ToolBox.lineSeparator));
       
   265     }
       
   266 
       
   267     /**
       
   268      * The class represents a test case.
       
   269      */
       
   270     public static class TestCase {
       
   271         public final ToolBox.JavaSource[] sources;
       
   272         public final String expectedMessage;
       
   273 
       
   274         public TestCase(String expectedMessage, ToolBox.JavaSource...sources) {
       
   275             this.sources = sources;
       
   276             this.expectedMessage = expectedMessage;
       
   277         }
       
   278     }
       
   279 
       
   280     /**
       
   281      * The enum represents all possible imports.
       
   282      */
       
   283     public enum ImportType {
       
   284         SINGLE_IMPORT("import %s;"),
       
   285         IMPORT_ON_DEMAND("import %s.*;"),
       
   286         SINGLE_STATIC_IMPORT("import static %s;"),
       
   287         STATIC_IMPORT_ON_DEMAND("import static %s.*;");
       
   288 
       
   289         private final String type;
       
   290 
       
   291         private ImportType(String type) {
       
   292             this.type = type;
       
   293         }
       
   294 
       
   295         public String getImport(String className) {
       
   296             if (this == ImportType.IMPORT_ON_DEMAND || this == ImportType.STATIC_IMPORT_ON_DEMAND) {
       
   297                 int lastDot = className.lastIndexOf('.');
       
   298                 className = className.substring(0, lastDot);
       
   299             }
       
   300             return String.format(type, className);
       
   301         }
       
   302     }
       
   303 
       
   304     /**
       
   305      * The enum represents all possible class types that can be used in
       
   306      * inheritance.
       
   307      */
       
   308     public enum ClassType {
       
   309         CLASS("class"), INTERFACE("interface"), ENUM("enum");
       
   310 
       
   311         public boolean canInherit(ClassType innerClass) {
       
   312             return innerClass != ENUM && !(this == ENUM && innerClass == ClassType.CLASS
       
   313                     || this == INTERFACE && innerClass == ClassType.CLASS);
       
   314         }
       
   315 
       
   316         public String inheritedString(ClassType innerClass) {
       
   317             if (!canInherit(innerClass)) {
       
   318                 throw new IllegalArgumentException(String.format("%s cannot inherit %s", this, innerClass));
       
   319             }
       
   320             return this == innerClass ? "extends" : "implements";
       
   321         }
       
   322 
       
   323         private final String type;
       
   324 
       
   325         private ClassType(String type) {
       
   326             this.type = type;
       
   327         }
       
   328 
       
   329         public String getType() {
       
   330             return type;
       
   331         }
       
   332     }
       
   333 }