blob: 0df4c9ca9f2ac4ee51f2c6f536776c5cfe86d3fd [file] [log] [blame]
/*
* 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.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION;
import static com.google.common.truth.TruthJUnit.assume;
import static com.google.testing.compile.CompilationSubject.assertThat;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.io.Resources;
import com.google.testing.compile.Compilation;
import com.google.testing.compile.Compiler;
import com.google.testing.compile.JavaFileObjects;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.List;
import javax.lang.model.SourceVersion;
import javax.tools.JavaFileObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Functional tests for the {@link AutoFactoryProcessor}. */
@RunWith(JUnit4.class)
public class AutoFactoryProcessorTest {
private final Compiler javac = Compiler.javac().withProcessors(new AutoFactoryProcessor());
@Test
public void simpleClass() {
Compilation compilation = javac.compile(JavaFileObjects.forResource("good/SimpleClass.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.SimpleClassFactory")
.hasSourceEquivalentTo(loadExpectedFile("expected/SimpleClassFactory.java"));
}
@Test
public void simpleClassWithConstructorThrowsClause() {
Compilation compilation =
javac.compile(JavaFileObjects.forResource("good/SimpleClassThrows.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.SimpleClassThrowsFactory")
.hasSourceEquivalentTo(loadExpectedFile("expected/SimpleClassThrowsFactory.java"));
}
@Test
public void nestedClasses() {
Compilation compilation = javac.compile(JavaFileObjects.forResource("good/NestedClasses.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.NestedClasses_SimpleNestedClassFactory")
.hasSourceEquivalentTo(
loadExpectedFile("expected/NestedClasses_SimpleNestedClassFactory.java"));
assertThat(compilation)
.generatedSourceFile("tests.NestedClassCustomNamedFactory")
.hasSourceEquivalentTo(loadExpectedFile("expected/NestedClassCustomNamedFactory.java"));
}
@Test
public void simpleClassNonFinal() {
Compilation compilation =
javac.compile(JavaFileObjects.forResource("good/SimpleClassNonFinal.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.SimpleClassNonFinalFactory")
.hasSourceEquivalentTo(loadExpectedFile("expected/SimpleClassNonFinalFactory.java"));
}
@Test
public void publicClass() {
Compilation compilation = javac.compile(JavaFileObjects.forResource("good/PublicClass.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.PublicClassFactory")
.hasSourceEquivalentTo(loadExpectedFile("expected/PublicClassFactory.java"));
}
@Test
public void simpleClassCustomName() {
Compilation compilation =
javac.compile(JavaFileObjects.forResource("good/SimpleClassCustomName.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.CustomNamedFactory")
.hasSourceEquivalentTo(loadExpectedFile("expected/CustomNamedFactory.java"));
}
@Test
public void simpleClassMixedDeps() {
Compilation compilation =
javac.compile(
JavaFileObjects.forResource("good/SimpleClassMixedDeps.java"),
JavaFileObjects.forResource("support/AQualifier.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.SimpleClassMixedDepsFactory")
.hasSourceEquivalentTo(loadExpectedFile("expected/SimpleClassMixedDepsFactory.java"));
}
@Test
public void simpleClassPassedDeps() {
Compilation compilation =
javac.compile(JavaFileObjects.forResource("good/SimpleClassPassedDeps.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.SimpleClassPassedDepsFactory")
.hasSourceEquivalentTo(loadExpectedFile("expected/SimpleClassPassedDepsFactory.java"));
}
@Test
public void simpleClassProvidedDeps() {
Compilation compilation =
javac.compile(
JavaFileObjects.forResource("support/AQualifier.java"),
JavaFileObjects.forResource("support/BQualifier.java"),
JavaFileObjects.forResource("good/SimpleClassProvidedDeps.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.SimpleClassProvidedDepsFactory")
.hasSourceEquivalentTo(loadExpectedFile("expected/SimpleClassProvidedDepsFactory.java"));
}
@Test
public void simpleClassProvidedProviderDeps() {
Compilation compilation =
javac.compile(
JavaFileObjects.forResource("support/AQualifier.java"),
JavaFileObjects.forResource("support/BQualifier.java"),
JavaFileObjects.forResource("good/SimpleClassProvidedProviderDeps.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.SimpleClassProvidedProviderDepsFactory")
.hasSourceEquivalentTo(
loadExpectedFile("expected/SimpleClassProvidedProviderDepsFactory.java"));
}
@Test
public void constructorAnnotated() {
Compilation compilation =
javac.compile(JavaFileObjects.forResource("good/ConstructorAnnotated.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.ConstructorAnnotatedFactory")
.hasSourceEquivalentTo(loadExpectedFile("expected/ConstructorAnnotatedFactory.java"));
}
@Test
public void constructorWithThrowsClauseAnnotated() {
Compilation compilation =
javac.compile(JavaFileObjects.forResource("good/ConstructorAnnotatedThrows.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.ConstructorAnnotatedThrowsFactory")
.hasSourceEquivalentTo(loadExpectedFile("expected/ConstructorAnnotatedThrowsFactory.java"));
}
@Test
public void constructorAnnotatedNonFinal() {
Compilation compilation =
javac.compile(JavaFileObjects.forResource("good/ConstructorAnnotatedNonFinal.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.ConstructorAnnotatedNonFinalFactory")
.hasSourceEquivalentTo(
loadExpectedFile("expected/ConstructorAnnotatedNonFinalFactory.java"));
}
@Test
public void simpleClassImplementingMarker() {
Compilation compilation =
javac.compile(JavaFileObjects.forResource("good/SimpleClassImplementingMarker.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.SimpleClassImplementingMarkerFactory")
.hasSourceEquivalentTo(
loadExpectedFile("expected/SimpleClassImplementingMarkerFactory.java"));
}
@Test
public void simpleClassImplementingSimpleInterface() {
Compilation compilation =
javac.compile(
JavaFileObjects.forResource("good/SimpleClassImplementingSimpleInterface.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.SimpleClassImplementingSimpleInterfaceFactory")
.hasSourceEquivalentTo(
loadExpectedFile("expected/SimpleClassImplementingSimpleInterfaceFactory.java"));
}
@Test
public void mixedDepsImplementingInterfaces() {
Compilation compilation =
javac.compile(JavaFileObjects.forResource("good/MixedDepsImplementingInterfaces.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.MixedDepsImplementingInterfacesFactory")
.hasSourceEquivalentTo(
loadExpectedFile("expected/MixedDepsImplementingInterfacesFactory.java"));
}
@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() {
Compilation compilation =
javac.compile(JavaFileObjects.forResource("good/FactoryExtendingAbstractClass.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.FactoryExtendingAbstractClassFactory")
.hasSourceEquivalentTo(
loadExpectedFile("expected/FactoryExtendingAbstractClassFactory.java"));
}
@Test
public void factoryWithConstructorThrowsClauseExtendingAbstractClass() {
Compilation compilation =
javac.compile(JavaFileObjects.forResource("good/FactoryExtendingAbstractClassThrows.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.FactoryExtendingAbstractClassThrowsFactory")
.hasSourceEquivalentTo(
loadExpectedFile("expected/FactoryExtendingAbstractClassThrowsFactory.java"));
}
@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 factoryExtendingAbstractClass_multipleConstructors() {
JavaFileObject file =
JavaFileObjects.forResource(
"good/FactoryExtendingAbstractClassWithMultipleConstructors.java");
Compilation compilation = javac.compile(file);
assertThat(compilation).succeededWithoutWarnings();
}
@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");
}
@Test
public void factoryImplementingGenericInterfaceExtension() {
JavaFileObject file =
JavaFileObjects.forResource("good/FactoryImplementingGenericInterfaceExtension.java");
Compilation compilation = javac.compile(file);
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.FactoryImplementingGenericInterfaceExtensionFactory")
.hasSourceEquivalentTo(
loadExpectedFile("expected/FactoryImplementingGenericInterfaceExtensionFactory.java"));
}
@Test
public void multipleFactoriesImpementingInterface() {
JavaFileObject file =
JavaFileObjects.forResource("good/MultipleFactoriesImplementingInterface.java");
Compilation compilation = javac.compile(file);
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.MultipleFactoriesImplementingInterface_ClassAFactory")
.hasSourceEquivalentTo(
loadExpectedFile("expected/MultipleFactoriesImplementingInterface_ClassAFactory.java"));
assertThat(compilation)
.generatedSourceFile("tests.MultipleFactoriesImplementingInterface_ClassBFactory")
.hasSourceEquivalentTo(
loadExpectedFile("expected/MultipleFactoriesImplementingInterface_ClassBFactory.java"));
}
@Test
public void classUsingQualifierWithArgs() {
Compilation compilation =
javac.compile(
JavaFileObjects.forResource("support/QualifierWithArgs.java"),
JavaFileObjects.forResource("good/ClassUsingQualifierWithArgs.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.ClassUsingQualifierWithArgsFactory")
.hasSourceEquivalentTo(
loadExpectedFile("expected/ClassUsingQualifierWithArgsFactory.java"));
}
@Test
public void factoryImplementingInterfaceWhichRedeclaresCreateMethods() {
JavaFileObject file = JavaFileObjects.forResource("good/FactoryImplementingCreateMethod.java");
Compilation compilation = javac.compile(file);
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.FactoryImplementingCreateMethod_ConcreteClassFactory")
.hasSourceEquivalentTo(
loadExpectedFile("expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java"));
}
@Test
public void nullableParams() {
Compilation compilation =
javac.compile(
JavaFileObjects.forResource("good/SimpleClassNullableParameters.java"),
JavaFileObjects.forResource("support/AQualifier.java"),
JavaFileObjects.forResource("support/BQualifier.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.SimpleClassNullableParametersFactory")
.hasSourceEquivalentTo(
loadExpectedFile("expected/SimpleClassNullableParametersFactory.java"));
}
@Test
public void customNullableType() {
Compilation compilation =
javac.compile(JavaFileObjects.forResource("good/CustomNullable.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.CustomNullableFactory")
.hasSourceEquivalentTo(loadExpectedFile("expected/CustomNullableFactory.java"));
}
@Test
public void checkerFrameworkNullableType() {
// TYPE_USE annotations are pretty much unusable with annotation processors on Java 8 because
// of bugs that mean they only appear in the javax.lang.model API when the compiler feels like
// it. Checking for a java.specification.version that does not start with "1." eliminates 8 and
// any earlier version.
assume().that(JAVA_SPECIFICATION_VERSION.value()).doesNotMatch("1\\..*");
Compilation compilation =
javac.compile(JavaFileObjects.forResource("good/CheckerFrameworkNullable.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.CheckerFrameworkNullableFactory")
.hasSourceEquivalentTo(loadExpectedFile("expected/CheckerFrameworkNullableFactory.java"));
}
@Test
public void multipleProvidedParamsWithSameKey() {
Compilation compilation =
javac.compile(JavaFileObjects.forResource("good/MultipleProvidedParamsSameKey.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.MultipleProvidedParamsSameKeyFactory")
.hasSourceEquivalentTo(
loadExpectedFile("expected/MultipleProvidedParamsSameKeyFactory.java"));
}
@Test
public void providerArgumentToCreateMethod() {
Compilation compilation =
javac.compile(JavaFileObjects.forResource("good/ProviderArgumentToCreateMethod.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.ProviderArgumentToCreateMethodFactory")
.hasSourceEquivalentTo(
loadExpectedFile("expected/ProviderArgumentToCreateMethodFactory.java"));
}
@Test
public void multipleFactoriesConflictingParameterNames() {
Compilation compilation =
javac.compile(
JavaFileObjects.forResource("good/MultipleFactoriesConflictingParameterNames.java"),
JavaFileObjects.forResource("support/AQualifier.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.MultipleFactoriesConflictingParameterNamesFactory")
.hasSourceEquivalentTo(
loadExpectedFile("expected/MultipleFactoriesConflictingParameterNamesFactory.java"));
}
@Test
public void factoryVarargs() {
Compilation compilation =
javac.compile(JavaFileObjects.forResource("good/SimpleClassVarargs.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.SimpleClassVarargsFactory")
.hasSourceEquivalentTo(loadExpectedFile("expected/SimpleClassVarargsFactory.java"));
}
@Test
public void onlyPrimitives() {
Compilation compilation =
javac.compile(JavaFileObjects.forResource("good/OnlyPrimitives.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.OnlyPrimitivesFactory")
.hasSourceEquivalentTo(loadExpectedFile("expected/OnlyPrimitivesFactory.java"));
}
@Test
public void defaultPackage() {
JavaFileObject file = JavaFileObjects.forResource("good/DefaultPackage.java");
Compilation compilation = javac.compile(file);
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("DefaultPackageFactory")
.hasSourceEquivalentTo(loadExpectedFile("expected/DefaultPackageFactory.java"));
}
private JavaFileObject loadExpectedFile(String resourceName) {
if (isJavaxAnnotationProcessingGeneratedAvailable()) {
return JavaFileObjects.forResource(resourceName);
}
try {
List<String> sourceLines = Resources.readLines(Resources.getResource(resourceName), UTF_8);
replaceGeneratedImport(sourceLines);
return JavaFileObjects.forSourceLines(
resourceName.replace('/', '.').replace(".java", ""), sourceLines);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private boolean isJavaxAnnotationProcessingGeneratedAvailable() {
return SourceVersion.latestSupported().compareTo(SourceVersion.RELEASE_8) > 0;
}
private static void replaceGeneratedImport(List<String> sourceLines) {
int i = 0;
int firstImport = Integer.MAX_VALUE;
int lastImport = -1;
for (String line : sourceLines) {
if (line.startsWith("import ") && !line.startsWith("import static ")) {
firstImport = min(firstImport, i);
lastImport = max(lastImport, i);
}
i++;
}
if (lastImport >= 0) {
List<String> importLines = sourceLines.subList(firstImport, lastImport + 1);
importLines.replaceAll(
line ->
line.startsWith("import javax.annotation.processing.Generated;")
? "import javax.annotation.Generated;"
: line);
Collections.sort(importLines);
}
}
}