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