NegativeCyclicDependencyTest.java revision 3294:9adfb22ff08f
1/* 2 * Copyright (c) 2014, 2015, 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 * @modules jdk.compiler/com.sun.tools.javac.api 30 * jdk.compiler/com.sun.tools.javac.file 31 * jdk.compiler/com.sun.tools.javac.main 32 * jdk.jdeps/com.sun.tools.javap 33 * @build ToolBox NegativeCyclicDependencyTest 34 * @run main NegativeCyclicDependencyTest 35 */ 36 37import javax.tools.JavaCompiler; 38import javax.tools.ToolProvider; 39import java.io.StringWriter; 40import java.util.ArrayList; 41import java.util.Arrays; 42import java.util.List; 43 44/** 45 * The test generates the following code: 46 * 47 * package pkg; 48 * import pkg.B.InnerB; 49 * class A extends InnerB { 50 * static class InnerA {} 51 * } 52 * 53 * package pkg; 54 * import pkg.A.InnerA; 55 * class B extends InnerA { 56 * static class InnerB {} 57 * } 58 * 59 * compiles and checks whether compilation fails with the correct message. 60 * The test generates all possible combination of inheritance: 61 * 1. A extends InnerB, B extends InnerA; 62 * 2. InnerA extends InnerB, InnerB extends InnerA; 63 * 3. A extends InnerB, InnerB extends InnerA; 64 * 4. B extends InnerA, InnerA extends InnerB; 65 * 5. A extends InnerA. 66 * The test checks class, enum and interface as parent class, and checks all 67 * possible import statements. 68 */ 69public class NegativeCyclicDependencyTest { 70 private final static String expectedErrorMessage = 71 "\\w+:\\d+:\\d+: compiler.err.cyclic.inheritance: [\\w.]+\n1 error\n"; 72 73 private final static String[] sourceTemplatesA = { 74 "package pkg;\n" + 75 "#IMPORT_TYPE\n" + 76 "#OUTER_CLASS A #INHERIT InnerB {#ENUM_SEMI\n" + 77 " static #INNER_CLASS InnerA {}\n" + 78 "}", 79 "package pkg;\n" + 80 "#IMPORT_TYPE\n" + 81 "#OUTER_CLASS A {#ENUM_SEMI\n" + 82 " static #INNER_CLASS InnerA #INHERIT InnerB {}\n" + 83 "}" 84 }; 85 86 private final static String[] sourceTemplatesB = { 87 "package pkg;\n" + 88 "#IMPORT_TYPE\n" + 89 "#OUTER_CLASS B #INHERIT InnerA {#ENUM_SEMI\n" + 90 " static #INNER_CLASS InnerB {}\n" + 91 "}", 92 "package pkg;\n" + 93 "#IMPORT_TYPE\n" + 94 "#OUTER_CLASS B {#ENUM_SEMI\n" + 95 " static #INNER_CLASS InnerB #INHERIT InnerA {}\n" + 96 "}" 97 }; 98 99 private final static String sourceTemplate = 100 "package pkg;\n" + 101 "#IMPORT_TYPE\n" + 102 "#OUTER_CLASS A #INHERIT InnerA {#ENUM_SEMI\n" + 103 " static #INNER_CLASS InnerA {}\n" + 104 "}"; 105 106 public static void main(String[] args) { 107 new NegativeCyclicDependencyTest().test(); 108 } 109 110 public void test() { 111 int passed = 0; 112 List<TestCase> testCases = generateTestCases(); 113 for (TestCase testCase : testCases) { 114 try { 115 String output = compile(testCase.sources); 116 if (!output.matches(testCase.expectedMessage)) { 117 reportFailure(testCase); 118 printf(String.format("Message: %s, does not match regexp: %s\n", 119 output, testCase.expectedMessage)); 120 } else { 121 ++passed; 122 } 123 } catch (RuntimeException e) { 124 reportFailure(testCase); 125 e.printStackTrace(); 126 } 127 } 128 String message = String.format( 129 "Total test cases run: %d, passed: %d, failed: %d.", 130 testCases.size(), passed, testCases.size() - passed); 131 if (passed != testCases.size()) { 132 throw new RuntimeException(message); 133 } 134 echo(message); 135 } 136 137 private void reportFailure(TestCase testCase) { 138 echo("Test case failed."); 139 for (ToolBox.JavaSource source : testCase.sources) { 140 echo(source.getCharContent(true)); 141 echo(); 142 } 143 } 144 145 public List<TestCase> generateTestCases() { 146 List<TestCase> testCases = generateTestCasesWithTwoClasses(); 147 testCases.addAll(generateTestCasesWithOneClass()); 148 return testCases; 149 } 150 151 private List<TestCase> generateTestCasesWithOneClass() { 152 String importedClassName = "pkg.A.InnerA"; 153 List<TestCase> testCases = new ArrayList<>(); 154 for (ClassType outerClass : ClassType.values()) { 155 for (ClassType innerClass : ClassType.values()) { 156 if (!outerClass.canInherit(innerClass)) { 157 continue; 158 } 159 for (ImportType importType : ImportType.values()) { 160 String source = generateSource( 161 sourceTemplate, 162 outerClass, 163 innerClass, 164 outerClass.inheritedString(innerClass), 165 importType, 166 importedClassName); 167 testCases.add(new TestCase(expectedErrorMessage, 168 new ToolBox.JavaSource("A", source))); 169 } 170 } 171 } 172 return testCases; 173 } 174 175 private List<TestCase> generateTestCasesWithTwoClasses() { 176 String importedClassName1 = "pkg.A.InnerA"; 177 String importedClassName2 = "pkg.B.InnerB"; 178 List<TestCase> testCases = new ArrayList<>(); 179 for (int i = 0; i < sourceTemplatesA.length; ++i) { 180 for (int j = 0; j < sourceTemplatesB.length; ++j) { 181 for (ClassType outerClass1 : ClassType.values()) { 182 for (ClassType outerClass2 : ClassType.values()) { 183 for (ClassType innerClass1 : ClassType.values()) { 184 for (ClassType innerClass2 : ClassType.values()) { 185 ClassType childClass1 = i == 0 ? outerClass1 : innerClass1; 186 ClassType childClass2 = j == 0 ? outerClass2 : innerClass2; 187 if (!childClass1.canInherit(innerClass2) || 188 !childClass2.canInherit(innerClass1)) { 189 continue; 190 } 191 for (ImportType importType1 : ImportType.values()) { 192 for (ImportType importType2 : ImportType.values()) { 193 String sourceA = generateSource( 194 sourceTemplatesA[i], 195 outerClass1, 196 innerClass1, 197 childClass1.inheritedString(innerClass2), 198 importType1, 199 importedClassName2); 200 String sourceB = generateSource( 201 sourceTemplatesB[j], 202 outerClass2, 203 innerClass2, 204 childClass2.inheritedString(innerClass1), 205 importType2, 206 importedClassName1); 207 testCases.add(new TestCase(expectedErrorMessage, 208 new ToolBox.JavaSource("A", sourceA), 209 new ToolBox.JavaSource("B", sourceB))); 210 testCases.add(new TestCase(expectedErrorMessage, 211 new ToolBox.JavaSource("B", sourceB), 212 new ToolBox.JavaSource("A", sourceA))); 213 } 214 } 215 } 216 } 217 } 218 } 219 } 220 } 221 return testCases; 222 } 223 224 public String generateSource(String template, 225 ClassType outerClass, 226 ClassType innerClass, 227 String inheritString, 228 ImportType importType, 229 String innerClassName) { 230 return template 231 .replace("#OUTER_CLASS", outerClass.getType()) 232 .replace("#INNER_CLASS", innerClass.getType()) 233 .replace("#INHERIT", inheritString) 234 .replace("#IMPORT_TYPE", importType.getImport(innerClassName)) 235 .replace("#ENUM_SEMI", outerClass == ClassType.ENUM ? ";" : ""); 236 } 237 238 /** 239 * Compiles sources with -XDrawDiagnostics flag and 240 * returns the output of compilation. 241 * 242 * @param sources sources 243 * @return the result of compilation 244 */ 245 private String compile(ToolBox.JavaSource...sources) { 246 JavaCompiler jc = ToolProvider.getSystemJavaCompiler(); 247 StringWriter writer = new StringWriter(); 248 JavaCompiler.CompilationTask ct = jc.getTask(writer, null, null, 249 Arrays.asList("-XDrawDiagnostics"), 250 null, Arrays.asList(sources)); 251 if (ct.call()) { 252 throw new RuntimeException("Expected compilation failure."); 253 } 254 return writer.toString().replace(ToolBox.lineSeparator, "\n"); 255 } 256 257 public void echo() { 258 echo(""); 259 } 260 261 public void echo(CharSequence message) { 262 echo(message.toString()); 263 } 264 265 public void echo(String message) { 266 printf(message + "\n"); 267 } 268 269 public void printf(String template, Object...args) { 270 System.err.print(String.format(template, args).replace("\n", ToolBox.lineSeparator)); 271 } 272 273 /** 274 * The class represents a test case. 275 */ 276 public static class TestCase { 277 public final ToolBox.JavaSource[] sources; 278 public final String expectedMessage; 279 280 public TestCase(String expectedMessage, ToolBox.JavaSource...sources) { 281 this.sources = sources; 282 this.expectedMessage = expectedMessage; 283 } 284 } 285 286 /** 287 * The enum represents all possible imports. 288 */ 289 public enum ImportType { 290 SINGLE_IMPORT("import %s;"), 291 IMPORT_ON_DEMAND("import %s.*;"), 292 SINGLE_STATIC_IMPORT("import static %s;"), 293 STATIC_IMPORT_ON_DEMAND("import static %s.*;"); 294 295 private final String type; 296 297 private ImportType(String type) { 298 this.type = type; 299 } 300 301 public String getImport(String className) { 302 if (this == ImportType.IMPORT_ON_DEMAND || this == ImportType.STATIC_IMPORT_ON_DEMAND) { 303 int lastDot = className.lastIndexOf('.'); 304 className = className.substring(0, lastDot); 305 } 306 return String.format(type, className); 307 } 308 } 309 310 /** 311 * The enum represents all possible class types that can be used in 312 * inheritance. 313 */ 314 public enum ClassType { 315 CLASS("class"), INTERFACE("interface"), ENUM("enum"); 316 317 public boolean canInherit(ClassType innerClass) { 318 return innerClass != ENUM && !(this == ENUM && innerClass == ClassType.CLASS 319 || this == INTERFACE && innerClass == ClassType.CLASS); 320 } 321 322 public String inheritedString(ClassType innerClass) { 323 if (!canInherit(innerClass)) { 324 throw new IllegalArgumentException(String.format("%s cannot inherit %s", this, innerClass)); 325 } 326 return this == innerClass ? "extends" : "implements"; 327 } 328 329 private final String type; 330 331 private ClassType(String type) { 332 this.type = type; 333 } 334 335 public String getType() { 336 return type; 337 } 338 } 339} 340