8065360: Implement a test that checks possibilty of class members to be imported
authoraeremeev
Wed, 10 Dec 2014 21:45:39 +0200
changeset 27997 44407cab4584
parent 27996 4ae3503fd196
child 27998 592c5d3be509
8065360: Implement a test that checks possibilty of class members to be imported Reviewed-by: jlahoda, anazarov
langtools/test/tools/javac/importscope/ImportDependenciesTest.java
langtools/test/tools/javac/importscope/ImportMembersTest.java
langtools/test/tools/javac/importscope/NegativeCyclicDependencyTest.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/importscope/ImportDependenciesTest.java	Wed Dec 10 21:45:39 2014 +0200
@@ -0,0 +1,301 @@
+/*
+ * 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 8065360
+ * @summary The test checks dependencies through type parameters and implements/extends statements.
+ * @library /tools/lib
+ * @build ToolBox ImportDependenciesTest
+ * @run main ImportDependenciesTest
+ */
+
+import javax.tools.JavaCompiler;
+import javax.tools.ToolProvider;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * The test checks that code which contains dependencies through type parameters,
+ * implements/extends statements compiles properly. All combinations of
+ * import types are tested. In addition, the test checks various combinations
+ * of classes.
+ */
+public class ImportDependenciesTest {
+
+    private static final String sourceTemplate =
+            "package pkg;\n" +
+            "#IMPORT\n" +
+            "public class Test {\n" +
+            "    static #CLASS_TYPE InnerClass#TYPE_PARAMETER #PARENT {\n" +
+            "        static class Inner1 {\n" +
+            "        }\n" +
+            "        interface Inner2 {\n" +
+            "        }\n" +
+            "        interface Inner3 {\n" +
+            "        }\n" +
+            "    }\n" +
+            "    static class InnerClass1 {\n" +
+            "        static class IInner1 {\n" +
+            "        }\n" +
+            "    }\n" +
+            "    static class InnerInterface1 {\n" +
+            "        interface IInner2 {\n" +
+            "        }\n" +
+            "    }\n" +
+            "    static class InnerInterface2 {\n" +
+            "        interface IInner3 {\n" +
+            "        }\n" +
+            "    }\n" +
+            "}";
+
+    public static void main(String[] args) {
+        new ImportDependenciesTest().test();
+    }
+
+    public void test() {
+        List<List<InnerClass>> typeParameters = InnerClass.getAllCombinationsForTypeParameter();
+        List<List<InnerClass>> parents = InnerClass.getAllCombinationsForInheritance();
+        int passed = 0;
+        int total = 0;
+        for (ClassType classType : ClassType.values()) {
+            for (List<InnerClass> parent : parents) {
+                if (!classType.canBeInherited(parent)) {
+                    continue;
+                }
+                for (List<InnerClass> typeParameter : typeParameters) {
+                    List<InnerClass> innerClasses = new ArrayList<>(typeParameter);
+                    innerClasses.addAll(parent);
+                    for (ImportType importType : ImportType.values()) {
+                        ++total;
+                        String source = sourceTemplate
+                                .replace("#IMPORT", importType.generateImports(innerClasses))
+                                .replace("#CLASS_TYPE", classType.getClassType())
+                                .replace("#TYPE_PARAMETER", generateTypeParameter(typeParameter))
+                                .replace("#PARENT", classType.generateInheritanceString(parent));
+                        CompilationResult result = compile(new ToolBox.JavaSource("pkg/Test.java", source));
+                        if (!result.isSuccessful) {
+                            echo("Compilation failed!");
+                            echo(source);
+                            echo(result.message);
+                            echo();
+                        } else {
+                            ++passed;
+                        }
+                    }
+                }
+            }
+        }
+        String message = String.format(
+                "Total test cases run: %d, passed: %d, failed: %d.",
+                total, passed, total - passed);
+        if (passed != total) {
+            throw new RuntimeException(message);
+        }
+        echo(message);
+    }
+
+    private String generateTypeParameter(List<InnerClass> typeParameters) {
+        if (typeParameters.isEmpty()) {
+            return "";
+        }
+        return String.format("<T extends %s>", typeParameters.stream()
+                .map(InnerClass::getSimpleName)
+                .collect(Collectors.joining(" & ")));
+    }
+
+    private static class CompilationResult {
+        public final boolean isSuccessful;
+        public final String message;
+
+        public CompilationResult(boolean isSuccessful, String message) {
+            this.isSuccessful = isSuccessful;
+            this.message = message;
+        }
+    }
+
+    private CompilationResult compile(ToolBox.JavaSource...sources) {
+        StringWriter writer = new StringWriter();
+        JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
+        Boolean call = jc.getTask(writer, null, null, null, null, Arrays.asList(sources)).call();
+        return new CompilationResult(call, writer.toString().replace(ToolBox.lineSeparator, "\n"));
+    }
+
+    public void echo() {
+        echo("");
+    }
+
+    public void echo(String output) {
+        printf(output + "\n");
+    }
+
+    public void printf(String template, Object...args) {
+        System.err.print(String.format(template, args).replace("\n", ToolBox.lineSeparator));
+    }
+
+    enum ImportType {
+        IMPORT("import"), STATIC_IMPORT("import static"),
+        IMPORT_ON_DEMAND("import"), STATIC_IMPORT_ON_DEMAND("import static");
+
+        private final String importType;
+        private ImportType(String importType) {
+            this.importType = importType;
+        }
+
+        private boolean isOnDemand() {
+            return this == IMPORT_ON_DEMAND || this == STATIC_IMPORT_ON_DEMAND;
+        }
+
+        public String generateImports(List<InnerClass> innerClasses) {
+            return innerClasses.stream()
+                    .map(i -> isOnDemand() ? i.getPackageName() + ".*" : i.getCanonicalName())
+                    .distinct()
+                    .map(s -> String.format("%s %s;", importType, s))
+                    .collect(Collectors.joining("\n"));
+        }
+    }
+
+    enum ClassType {
+        CLASS("class") {
+            @Override
+            public boolean canBeInherited(List<InnerClass> innerClasses) {
+                return true;
+            }
+
+            @Override
+            public String generateInheritanceString(List<InnerClass> innerClasses) {
+                if (innerClasses.isEmpty()) {
+                    return "";
+                }
+                StringBuilder sb = new StringBuilder();
+                InnerClass firstClass = innerClasses.get(0);
+                if (firstClass.isClass()) {
+                    sb.append("extends ").append(firstClass.getSimpleName()).append(" ");
+                }
+                String str = innerClasses.stream()
+                        .filter(x -> !x.isClass())
+                        .map(InnerClass::getSimpleName)
+                        .collect(Collectors.joining(", "));
+                if (!str.isEmpty()) {
+                    sb.append("implements ").append(str);
+                }
+                return sb.toString();
+            }
+        }, INTERFACE("interface") {
+            @Override
+            public boolean canBeInherited(List<InnerClass> innerClasses) {
+                return !innerClasses.stream().anyMatch(InnerClass::isClass);
+            }
+
+            @Override
+            public String generateInheritanceString(List<InnerClass> innerClasses) {
+                if (innerClasses.isEmpty()) {
+                    return "";
+                }
+                return "extends " + innerClasses.stream()
+                        .map(InnerClass::getSimpleName)
+                        .collect(Collectors.joining(", "));
+            }
+        };
+
+        private final String classType;
+        private ClassType(String classType) {
+            this.classType = classType;
+        }
+
+        public String getClassType() {
+            return classType;
+        }
+
+        public abstract boolean canBeInherited(List<InnerClass> innerClasses);
+
+        public abstract String generateInheritanceString(List<InnerClass> innerClasses);
+    }
+
+    enum InnerClass {
+        INNER_1("pkg.Test.InnerClass.Inner1", true),
+        INNER_2("pkg.Test.InnerClass.Inner2", true),
+        INNER_3("pkg.Test.InnerClass.Inner3", true),
+        IINNER_1("pkg.Test.InnerClass1.IInner1", false),
+        IINNER_2("pkg.Test.InnerInterface1.IInner2", false),
+        IINNER_3("pkg.Test.InnerInterface2.IInner3", false);
+
+        private final String canonicalName;
+        private final boolean isForTypeParameter;
+
+        private InnerClass(String canonicalName, boolean isForTypeParameter) {
+            this.canonicalName = canonicalName;
+            this.isForTypeParameter = isForTypeParameter;
+        }
+
+        private static List<List<InnerClass>> getAllCombinations(boolean isTypeParameter) {
+            List<List<InnerClass>> result = new ArrayList<>();
+            List<InnerClass> tmpl = Stream.of(InnerClass.values())
+                    .filter(i -> i.isForTypeParameter() == isTypeParameter)
+                    .collect(Collectors.toCollection(ArrayList::new));
+            result.add(Arrays.asList());
+            for (int i = 0; i < tmpl.size(); ++i) {
+                result.add(Arrays.asList(tmpl.get(i)));
+                for (int j = i + 1; j < tmpl.size(); ++j) {
+                    result.add(Arrays.asList(tmpl.get(i), tmpl.get(j)));
+                }
+            }
+            result.add(tmpl);
+            return result;
+        }
+
+        public static List<List<InnerClass>> getAllCombinationsForTypeParameter() {
+            return getAllCombinations(true);
+        }
+
+        public static List<List<InnerClass>> getAllCombinationsForInheritance() {
+            return getAllCombinations(false);
+        }
+
+        public String getCanonicalName() {
+            return canonicalName;
+        }
+
+        public String getSimpleName() {
+            String cName = getCanonicalName();
+            return cName.substring(cName.lastIndexOf('.') + 1);
+        }
+
+        public String getPackageName() {
+            String cName = getCanonicalName();
+            int dotIndex = cName.lastIndexOf('.');
+            return dotIndex == -1 ? "" : cName.substring(0, dotIndex);
+        }
+
+        public boolean isClass() {
+            return this == INNER_1 || this == IINNER_1;
+        }
+        private boolean isForTypeParameter() {
+            return isForTypeParameter;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/importscope/ImportMembersTest.java	Wed Dec 10 21:45:39 2014 +0200
@@ -0,0 +1,330 @@
+/*
+ * 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 8065360
+ * @summary The test checks possibility of class members to be imported.
+ * @library /tools/lib
+ * @build ToolBox ImportMembersTest
+ * @run main ImportMembersTest
+ */
+
+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 checks that members of a class, an enum, an interface or annotation
+ * can be imported with help of a static import or an import statement.
+ * The tests generates a code, compiles it and checks whether it can be compiled
+ * successfully or fails with a proper message.
+ * The following is the example of a test case:
+ * package pkg;
+ * class ChildA extends A {}
+ *
+ * package pkg;
+ * class A {
+ *     static class Inner {}
+ *     static Object field;
+ *     static void method() {}
+ * }
+ *
+ * package pkg;
+ * import static pkg.ChildA.method;
+ * public class Test {{
+ *     method();
+ * }}
+ *
+ */
+public class ImportMembersTest {
+
+    private static final String[] expectedErrorMessages = {
+            "Test.java:\\d+:\\d+: compiler.err.cant.resolve.location: .*\n1 error\n",
+            "Test.java:\\d+:\\d+: compiler.err.import.requires.canonical: .*\n1 error\n"
+    };
+
+    private static final String sourceTemplate =
+            "package pkg;\n" +
+            "#IMPORT\n" +
+            "public class Test {{\n" +
+            "    #STATEMENT\n" +
+            "}}\n";
+
+    public static void main(String[] args) {
+        new ImportMembersTest().test();
+    }
+
+    public void test() {
+        int passed = 0;
+        int total = 0;
+        for (ClassType classType : ClassType.values()) {
+            for (ImportType importType : ImportType.values()) {
+                for (MemberType memberType : MemberType.values()) {
+                    ++total;
+                    List<ToolBox.JavaSource> sources = classType.getSources();
+                    sources.add(new ToolBox.JavaSource("Test.java",
+                            generateSource(classType, memberType, importType)));
+
+                    CompilationResult compilationResult = compile(sources);
+                    boolean isErrorExpected = importType.hasError(classType, memberType);
+                    if (!compilationResult.isSuccessful) {
+                        if (isErrorExpected) {
+                            String expectedErrorMessage =
+                                    getExpectedErrorMessage(classType, importType, memberType);
+                            if (compilationResult.message.matches(expectedErrorMessage)) {
+                                ++passed;
+                            } else {
+                                reportFailure(sources, String.format("Expected compilation failure message:\n" +
+                                                "%s\ngot message:\n%s",
+                                        expectedErrorMessage, compilationResult.message));
+                            }
+                        } else {
+                            reportFailure(sources, String.format("Unexpected compilation failure:\n%s",
+                                    compilationResult.message));
+                        }
+                    } else {
+                        if (isErrorExpected) {
+                            reportFailure(sources, "Expected compilation failure.");
+                        } else {
+                            ++passed;
+                        }
+                    }
+                }
+            }
+        }
+        String message = String.format(
+                "Total test cases run: %d, passed: %d, failed: %d.",
+                total, passed, total - passed);
+        if (passed != total) {
+            throw new RuntimeException(message);
+        }
+        echo(message);
+    }
+
+    private String getExpectedErrorMessage(ClassType classType, ImportType importType, MemberType memberType) {
+        String expectedErrorMessage;
+        if (importType == ImportType.IMPORT && classType == ClassType.CHILD_A &&
+                memberType == MemberType.CLASS) {
+            expectedErrorMessage = expectedErrorMessages[1];
+        } else {
+            expectedErrorMessage = expectedErrorMessages[0];
+        }
+        return expectedErrorMessage;
+    }
+
+    private void reportFailure(List<ToolBox.JavaSource> sources, String message) {
+        echo("Test case failed!");
+        printSources(sources);
+        echo(message);
+        echo();
+    }
+
+    private String generateSource(ClassType classType, MemberType memberType, ImportType importType) {
+        String importString = importType.generateImport(classType.getClassName(), memberType.getMemberType());
+        String statement;
+        if (importType.hasError(classType, memberType)) {
+            // if the source code has a compilation error, nothing is added.
+            // just to prevent the compiler from appending additional
+            // compilation errors to output
+            statement = "";
+        } else if (memberType == MemberType.STAR) {
+            // in case of import-on-demand, every class member is used
+            if (importType == ImportType.STATIC_IMPORT) {
+                statement = MemberType.CLASS.getStatement() + "\n    "
+                        + MemberType.FIELD.getStatement();
+                // an annotation does not have a static method.
+                if (classType != ClassType.D) {
+                    statement += "\n    " + MemberType.METHOD.getStatement() + "\n";
+                }
+            } else {
+                statement = classType != ClassType.CHILD_A
+                        ? MemberType.CLASS.getStatement() : "";
+            }
+        } else {
+            statement = memberType.getStatement();
+        }
+        return sourceTemplate
+                .replace("#IMPORT", importString)
+                .replace("#STATEMENT", statement);
+    }
+
+    private static class CompilationResult {
+        public final boolean isSuccessful;
+        public final String message;
+
+        public CompilationResult(boolean isSuccessful, String message) {
+            this.isSuccessful = isSuccessful;
+            this.message = message;
+        }
+    }
+
+    private CompilationResult compile(List<ToolBox.JavaSource> sources) {
+        StringWriter writer = new StringWriter();
+        JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
+        Boolean call = jc.getTask(writer, null, null, Arrays.asList("-XDrawDiagnostics"), null, sources).call();
+        return new CompilationResult(call, writer.toString().replace(ToolBox.lineSeparator, "\n"));
+    }
+
+    public void printSources(List<ToolBox.JavaSource> sources) {
+        for (ToolBox.JavaSource javaSource : sources) {
+            echo(javaSource.getCharContent(true).toString());
+        }
+    }
+
+    public void echo() {
+        echo("");
+    }
+
+    public void echo(String output) {
+        printf(output + "\n");
+    }
+
+    public void printf(String template, Object...args) {
+        System.err.print(String.format(template, args).replace("\n", ToolBox.lineSeparator));
+    }
+
+    enum ClassType {
+        A("A",
+        "package pkg;\n" +
+        "class A {\n" +
+        "    static class Inner {}\n" +
+        "    static Object field;\n" +
+        "    static void method() {}\n" +
+        "}\n"
+        ),
+        B("B",
+        "package pkg;\n" +
+        "interface B {\n" +
+        "    static class Inner {}\n" +
+        "    static Object field = null;\n" +
+        "    static void method() {}\n" +
+        "}\n"
+        ),
+        C("C",
+        "package pkg;\n" +
+        "enum C {field;\n" +
+        "    static class Inner {}\n" +
+        "    static void method() {}\n" +
+        "}\n"
+        ),
+        D("D",
+        "package pkg;\n" +
+        "@interface D {\n" +
+        "    static class Inner {}\n" +
+        "    static Object field = null;\n" +
+        "}\n"
+        ),
+        CHILD_A("ChildA",
+        "package pkg;\n" +
+        "class ChildA extends A {}\n",
+        A);
+
+        private final String className;
+        private final String source;
+        private final ClassType parentType;
+
+        private ClassType(String className, String source) {
+            this(className, source, null);
+        }
+
+        private ClassType(String className, String source, ClassType classType) {
+            this.className = className;
+            this.source = source;
+            this.parentType = classType;
+        }
+
+        public String getClassName() {
+            return className;
+        }
+
+        public List<ToolBox.JavaSource> getSources() {
+            List<ToolBox.JavaSource> sourceList = new ArrayList<>();
+            ClassType current = this;
+            while (current != null) {
+                sourceList.add(new ToolBox.JavaSource(current.className, current.source));
+                current = current.parentType;
+            }
+            return sourceList;
+        }
+    }
+
+    enum MemberType {
+        CLASS("Inner", "Inner inner = null;"),
+        FIELD("field", "Object o = field;"),
+        METHOD("method", "method();"),
+        STAR("*", ""),
+        NOT_EXIST("NotExist", "");
+
+        private final String memberType;
+        private final String statement;
+
+        private MemberType(String memberType, String statement) {
+            this.memberType = memberType;
+            this.statement = statement;
+        }
+
+        public String getStatement() {
+            return statement;
+        }
+
+        public String getMemberType() {
+            return memberType;
+        }
+    }
+
+    enum ImportType {
+        IMPORT("import pkg.#CLASS_NAME.#MEMBER_NAME;"),
+        STATIC_IMPORT("import static pkg.#CLASS_NAME.#MEMBER_NAME;");
+
+        private final String importType;
+
+        private ImportType(String importType) {
+            this.importType = importType;
+        }
+
+        public String generateImport(String className, String memberName) {
+            return importType
+                    .replace("#CLASS_NAME", className)
+                    .replace("#MEMBER_NAME", memberName);
+        }
+
+        public boolean hasError(ClassType classType, MemberType memberType) {
+            switch (memberType) {
+                case FIELD:
+                    return this != ImportType.STATIC_IMPORT;
+                case METHOD:
+                    return this != ImportType.STATIC_IMPORT || classType == ClassType.D;
+                case NOT_EXIST:
+                    return true;
+                case CLASS:
+                    return classType.parentType != null && this != STATIC_IMPORT;
+                default:
+                    return false;
+            }
+        }
+    }
+}
--- a/langtools/test/tools/javac/importscope/NegativeCyclicDependencyTest.java	Tue Dec 09 17:40:02 2014 +0000
+++ b/langtools/test/tools/javac/importscope/NegativeCyclicDependencyTest.java	Wed Dec 10 21:45:39 2014 +0200
@@ -121,11 +121,13 @@
                 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()));
+        String message = String.format(
+                "Total test cases run: %d, passed: %d, failed: %d.",
+                testCases.size(), passed, testCases.size() - passed);
+        if (passed != testCases.size()) {
+            throw new RuntimeException(message);
         }
+        echo(message);
     }
 
     private void reportFailure(TestCase testCase) {