diff --git a/factory/pom.xml b/factory/pom.xml index f2f74dece2..a7bd250571 100644 --- a/factory/pom.xml +++ b/factory/pom.xml @@ -118,6 +118,12 @@ 0.21.0 test + + com.google.testparameterinjector + test-parameter-injector + 1.12 + test + junit junit diff --git a/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorNegativeTest.java b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorNegativeTest.java new file mode 100644 index 0000000000..c8394a6325 --- /dev/null +++ b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorNegativeTest.java @@ -0,0 +1,137 @@ +/* + * Copyright 2013 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.auto.factory.processor; + +import static com.google.testing.compile.CompilationSubject.assertThat; + +import com.google.testing.compile.Compilation; +import com.google.testing.compile.Compiler; +import com.google.testing.compile.JavaFileObjects; +import javax.tools.JavaFileObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Testing compilation errors from {@link AutoFactoryProcessor}. */ +@RunWith(JUnit4.class) +public class AutoFactoryProcessorNegativeTest { + private final Compiler javac = Compiler.javac().withProcessors(new AutoFactoryProcessor()); + + @Test + public void failsWithMixedFinals() { + JavaFileObject file = JavaFileObjects.forResource("bad/MixedFinals.java"); + Compilation compilation = javac.compile(file); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "Cannot mix allowSubclasses=true and allowSubclasses=false in one factory.") + .inFile(file) + .onLine(24); + assertThat(compilation) + .hadErrorContaining( + "Cannot mix allowSubclasses=true and allowSubclasses=false in one factory.") + .inFile(file) + .onLine(27); + } + + @Test + public void providedButNoAutoFactory() { + JavaFileObject file = JavaFileObjects.forResource("bad/ProvidedButNoAutoFactory.java"); + Compilation compilation = javac.compile(file); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "@Provided may only be applied to constructors requesting an auto-factory") + .inFile(file) + .onLineContaining("@Provided"); + } + + @Test + public void providedOnMethodParameter() { + JavaFileObject file = JavaFileObjects.forResource("bad/ProvidedOnMethodParameter.java"); + Compilation compilation = javac.compile(file); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining("@Provided may only be applied to constructor parameters") + .inFile(file) + .onLineContaining("@Provided"); + } + + @Test + public void invalidCustomName() { + JavaFileObject file = JavaFileObjects.forResource("bad/InvalidCustomName.java"); + Compilation compilation = javac.compile(file); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining("\"SillyFactory!\" is not a valid Java identifier") + .inFile(file) + .onLineContaining("SillyFactory!"); + } + + @Test + public void factoryExtendingAbstractClass_withConstructorParams() { + JavaFileObject file = + JavaFileObjects.forResource("bad/FactoryExtendingAbstractClassWithConstructorParams.java"); + Compilation compilation = javac.compile(file); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "tests.FactoryExtendingAbstractClassWithConstructorParams.AbstractFactory is not a" + + " valid supertype for a factory. Factory supertypes must have a no-arg" + + " constructor.") + .inFile(file) + .onLineContaining("@AutoFactory"); + } + + @Test + public void factoryExtendingInterface() { + JavaFileObject file = JavaFileObjects.forResource("bad/InterfaceSupertype.java"); + Compilation compilation = javac.compile(file); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "java.lang.Runnable is not a valid supertype for a factory. Supertypes must be" + + " non-final classes.") + .inFile(file) + .onLineContaining("@AutoFactory"); + } + + @Test + public void factoryExtendingEnum() { + JavaFileObject file = JavaFileObjects.forResource("bad/EnumSupertype.java"); + Compilation compilation = javac.compile(file); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "java.util.concurrent.TimeUnit is not a valid supertype for a factory. Supertypes must" + + " be non-final classes.") + .inFile(file) + .onLineContaining("@AutoFactory"); + } + + @Test + public void factoryExtendingFinalClass() { + JavaFileObject file = JavaFileObjects.forResource("bad/FinalSupertype.java"); + Compilation compilation = javac.compile(file); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "java.lang.Boolean is not a valid supertype for a factory. Supertypes must be" + + " non-final classes.") + .inFile(file) + .onLineContaining("@AutoFactory"); + } +} diff --git a/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java index 515ac43550..52018a390c 100644 --- a/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java +++ b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java @@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.TruthJUnit.assume; import static com.google.testing.compile.CompilationSubject.assertThat; import static java.lang.Math.max; @@ -32,8 +33,12 @@ import com.google.testing.compile.Compilation; import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; +import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -46,12 +51,78 @@ import org.junit.AfterClass; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; /** Functional tests for the {@link AutoFactoryProcessor}. */ -@RunWith(JUnit4.class) +@RunWith(TestParameterInjector.class) public class AutoFactoryProcessorTest { - private final Compiler javac = Compiler.javac().withProcessors(new AutoFactoryProcessor()); + private final Config config; + + public AutoFactoryProcessorTest(@TestParameter Config config) { + this.config = config; + } + + private enum InjectPackage { + JAVAX, + } + + private enum Config { + JAVAX_ONLY_ON_CLASSPATH(ImmutableList.of(InjectPackage.JAVAX), InjectPackage.JAVAX); + + /** Config that is used for negative tests, and to update the golden files. */ + static final Config DEFAULT = JAVAX_ONLY_ON_CLASSPATH; + + final ImmutableList packagesOnClasspath; + final InjectPackage unusedExpectedPackage; + final ImmutableList options; + + Config(ImmutableList packagesOnClasspath, InjectPackage expectedPackage) { + this(packagesOnClasspath, expectedPackage, ImmutableList.of()); + } + + Config( + ImmutableList packagesOnClasspath, + InjectPackage expectedPackage, + ImmutableList options) { + this.packagesOnClasspath = packagesOnClasspath; + this.unusedExpectedPackage = expectedPackage; + this.options = options; + } + + static final ImmutableList COMMON_CLASSPATH = + ImmutableList.of( + fileForClass("com.google.auto.factory.AutoFactory"), + fileForClass("javax.annotation.Nullable"), + fileForClass("org.checkerframework.checker.nullness.compatqual.NullableType")); + static final File JAVAX_CLASSPATH = fileForClass("javax.inject.Provider"); + + static File fileForClass(String className) { + Class c; + try { + c = Class.forName(className); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException(e); + } + URL url = c.getProtectionDomain().getCodeSource().getLocation(); + assertThat(url.getProtocol()).isEqualTo("file"); + return new File(url.getPath()); + } + + ImmutableList classpath() { + ImmutableList.Builder classpathBuilder = + ImmutableList.builder().addAll(COMMON_CLASSPATH); + if (packagesOnClasspath.contains(InjectPackage.JAVAX)) { + classpathBuilder.add(JAVAX_CLASSPATH); + } + return classpathBuilder.build(); + } + + Compiler javac() { + return Compiler.javac() + .withClasspath(classpath()) + .withProcessors(new AutoFactoryProcessor()) + .withOptions(options); + } + } private static volatile boolean goldenFileFailures; @@ -83,8 +154,8 @@ public static void explainGoldenFileFailures() { private void goldenTest( ImmutableList inputResources, ImmutableMap expectedOutput) { ImmutableList javaFileObjects = - inputResources.stream().map(JavaFileObjects::forResource).collect(toImmutableList()); - Compilation compilation = javac.compile(javaFileObjects); + inputResources.stream().map(this::goldenFile).collect(toImmutableList()); + Compilation compilation = config.javac().compile(javaFileObjects); assertThat(compilation).succeededWithoutWarnings(); expectedOutput.forEach( (className, expectedSourceResource) -> { @@ -97,15 +168,28 @@ private void goldenTest( goldenFileFailures = true; throw e; } - try { - updateGoldenFile(compilation, className, expectedSourceResource); - } catch (IOException e2) { - throw new UncheckedIOException(e2); + if (config.equals(Config.DEFAULT)) { + try { + updateGoldenFile(compilation, className, expectedSourceResource); + } catch (IOException e2) { + throw new UncheckedIOException(e2); + } } } }); } + private JavaFileObject goldenFile(String resourceName) { + try { + URL resourceUrl = Resources.getResource(resourceName); + String source = Resources.toString(resourceUrl, UTF_8); + String className = resourceName.replaceFirst("\\.java$", "").replace('/', '.'); + return JavaFileObjects.forSourceString(className, source); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + private void updateGoldenFile(Compilation compilation, String className, String relativePath) throws IOException { Path goldenFileRootPath = Paths.get(GOLDEN_FILE_ROOT); @@ -287,7 +371,7 @@ public void mixedDepsImplementingInterfaces() { @Test public void failsWithMixedFinals() { JavaFileObject file = JavaFileObjects.forResource("bad/MixedFinals.java"); - Compilation compilation = javac.compile(file); + Compilation compilation = config.javac().compile(file); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( @@ -304,7 +388,7 @@ public void failsWithMixedFinals() { @Test public void providedButNoAutoFactory() { JavaFileObject file = JavaFileObjects.forResource("bad/ProvidedButNoAutoFactory.java"); - Compilation compilation = javac.compile(file); + Compilation compilation = config.javac().compile(file); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( @@ -316,7 +400,7 @@ public void providedButNoAutoFactory() { @Test public void providedOnMethodParameter() { JavaFileObject file = JavaFileObjects.forResource("bad/ProvidedOnMethodParameter.java"); - Compilation compilation = javac.compile(file); + Compilation compilation = config.javac().compile(file); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining("@Provided may only be applied to constructor parameters") @@ -327,7 +411,7 @@ public void providedOnMethodParameter() { @Test public void invalidCustomName() { JavaFileObject file = JavaFileObjects.forResource("bad/InvalidCustomName.java"); - Compilation compilation = javac.compile(file); + Compilation compilation = config.javac().compile(file); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining("\"SillyFactory!\" is not a valid Java identifier") @@ -357,7 +441,7 @@ public void factoryWithConstructorThrowsClauseExtendingAbstractClass() { public void factoryExtendingAbstractClass_withConstructorParams() { JavaFileObject file = JavaFileObjects.forResource("bad/FactoryExtendingAbstractClassWithConstructorParams.java"); - Compilation compilation = javac.compile(file); + Compilation compilation = config.javac().compile(file); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( @@ -378,7 +462,7 @@ public void factoryExtendingAbstractClass_multipleConstructors() { @Test public void factoryExtendingInterface() { JavaFileObject file = JavaFileObjects.forResource("bad/InterfaceSupertype.java"); - Compilation compilation = javac.compile(file); + Compilation compilation = config.javac().compile(file); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( @@ -391,7 +475,7 @@ public void factoryExtendingInterface() { @Test public void factoryExtendingEnum() { JavaFileObject file = JavaFileObjects.forResource("bad/EnumSupertype.java"); - Compilation compilation = javac.compile(file); + Compilation compilation = config.javac().compile(file); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( @@ -404,7 +488,7 @@ public void factoryExtendingEnum() { @Test public void factoryExtendingFinalClass() { JavaFileObject file = JavaFileObjects.forResource("bad/FinalSupertype.java"); - Compilation compilation = javac.compile(file); + Compilation compilation = config.javac().compile(file); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( @@ -557,12 +641,9 @@ public void parameterAnnotations() { } private JavaFileObject loadExpectedFile(String resourceName) { - if (isJavaxAnnotationProcessingGeneratedAvailable()) { - return JavaFileObjects.forResource(resourceName); - } try { List sourceLines = Resources.readLines(Resources.getResource(resourceName), UTF_8); - replaceGeneratedImport(sourceLines); + rewriteImports(sourceLines); return JavaFileObjects.forSourceLines( resourceName.replace('/', '.').replace(".java", ""), sourceLines); } catch (IOException e) { @@ -570,11 +651,11 @@ private JavaFileObject loadExpectedFile(String resourceName) { } } - private boolean isJavaxAnnotationProcessingGeneratedAvailable() { + private static boolean isJavaxAnnotationProcessingGeneratedAvailable() { return SourceVersion.latestSupported().compareTo(SourceVersion.RELEASE_8) > 0; } - private static void replaceGeneratedImport(List sourceLines) { + private void rewriteImports(List sourceLines) { int i = 0; int firstImport = Integer.MAX_VALUE; int lastImport = -1; @@ -587,11 +668,13 @@ private static void replaceGeneratedImport(List sourceLines) { } if (lastImport >= 0) { List importLines = sourceLines.subList(firstImport, lastImport + 1); - importLines.replaceAll( - line -> - line.startsWith("import javax.annotation.processing.Generated;") - ? "import javax.annotation.Generated;" - : line); + if (!isJavaxAnnotationProcessingGeneratedAvailable()) { + importLines.replaceAll( + line -> + line.startsWith("import javax.annotation.processing.Generated;") + ? "import javax.annotation.Generated;" + : line); + } Collections.sort(importLines); } }