8065360: Implement a test that checks possibilty of class members to be imported
Reviewed-by: jlahoda, anazarov
--- /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) {