diff options
Diffstat (limited to 'common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java')
-rw-r--r-- | common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java | 387 |
1 files changed, 269 insertions, 118 deletions
diff --git a/common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java b/common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java index 59a135c4..f7534503 100644 --- a/common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java +++ b/common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java @@ -18,28 +18,35 @@ package com.google.auto.common; import static com.google.common.collect.Multimaps.transformValues; import static com.google.common.truth.Truth.assertAbout; import static com.google.common.truth.Truth.assertThat; +import static com.google.testing.compile.CompilationSubject.assertThat; +import static com.google.testing.compile.Compiler.javac; import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; import static javax.tools.Diagnostic.Kind.ERROR; import static javax.tools.StandardLocation.SOURCE_OUTPUT; +import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep; +import com.google.auto.common.BasicAnnotationProcessor.Step; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.SetMultimap; import com.google.common.truth.Correspondence; +import com.google.testing.compile.Compilation; +import com.google.testing.compile.CompilationRule; import com.google.testing.compile.JavaFileObjects; import java.io.IOException; import java.io.PrintWriter; import java.lang.annotation.Annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Set; import javax.annotation.processing.Filer; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; import javax.tools.JavaFileObject; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -47,30 +54,36 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class BasicAnnotationProcessorTest { + private abstract static class BaseAnnotationProcessor extends BasicAnnotationProcessor { + + static final String ENCLOSING_CLASS_NAME = + BasicAnnotationProcessorTest.class.getCanonicalName(); + + @Override + public final SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + } + @Retention(RetentionPolicy.SOURCE) public @interface RequiresGeneratedCode {} /** * Rejects elements unless the class generated by {@link GeneratesCode}'s processor is present. */ - private static final class RequiresGeneratedCodeProcessor extends BasicAnnotationProcessor { + private static class RequiresGeneratedCodeProcessor extends BaseAnnotationProcessor { int rejectedRounds; - final ImmutableList.Builder<ImmutableSetMultimap<Class<? extends Annotation>, Element>> - processArguments = ImmutableList.builder(); + final ImmutableList.Builder<ImmutableSetMultimap<String, Element>> processArguments = + ImmutableList.builder(); @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latestSupported(); - } - - @Override - protected Iterable<? extends ProcessingStep> initSteps() { + protected Iterable<? extends Step> steps() { return ImmutableSet.of( - new ProcessingStep() { + new Step() { @Override - public Set<Element> process( - SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { + public ImmutableSet<? extends Element> process( + ImmutableSetMultimap<String, Element> elementsByAnnotation) { processArguments.add(ImmutableSetMultimap.copyOf(elementsByAnnotation)); TypeElement requiredClass = processingEnv.getElementUtils().getTypeElement("test.SomeGeneratedClass"); @@ -83,25 +96,25 @@ public class BasicAnnotationProcessorTest { } @Override - public Set<? extends Class<? extends Annotation>> annotations() { - return ImmutableSet.of(RequiresGeneratedCode.class); + public ImmutableSet<String> annotations() { + return ImmutableSet.of(ENCLOSING_CLASS_NAME + ".RequiresGeneratedCode"); } }, - new ProcessingStep() { + new Step() { @Override - public Set<? extends Class<? extends Annotation>> annotations() { - return ImmutableSet.of(AnAnnotation.class); + public ImmutableSet<? extends Element> process( + ImmutableSetMultimap<String, Element> elementsByAnnotation) { + return ImmutableSet.of(); } @Override - public Set<? extends Element> process( - SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { - return ImmutableSet.of(); + public ImmutableSet<String> annotations() { + return ImmutableSet.of(ENCLOSING_CLASS_NAME + ".AnAnnotation"); } }); } - ImmutableList<ImmutableSetMultimap<Class<? extends Annotation>, Element>> processArguments() { + ImmutableList<ImmutableSetMultimap<String, Element>> processArguments() { return processArguments.build(); } } @@ -110,27 +123,21 @@ public class BasicAnnotationProcessorTest { public @interface GeneratesCode {} /** Generates a class called {@code test.SomeGeneratedClass}. */ - public class GeneratesCodeProcessor extends BasicAnnotationProcessor { - + public static class GeneratesCodeProcessor extends BaseAnnotationProcessor { @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latestSupported(); - } - - @Override - protected Iterable<? extends ProcessingStep> initSteps() { + protected Iterable<? extends Step> steps() { return ImmutableSet.of( - new ProcessingStep() { + new Step() { @Override - public Set<Element> process( - SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { + public ImmutableSet<? extends Element> process( + ImmutableSetMultimap<String, Element> elementsByAnnotation) { generateClass(processingEnv.getFiler(), "SomeGeneratedClass"); return ImmutableSet.of(); } @Override - public Set<? extends Class<? extends Annotation>> annotations() { - return ImmutableSet.of(GeneratesCode.class); + public ImmutableSet<String> annotations() { + return ImmutableSet.of(ENCLOSING_CLASS_NAME + ".GeneratesCode"); } }); } @@ -139,20 +146,15 @@ public class BasicAnnotationProcessorTest { public @interface AnAnnotation {} /** When annotating a type {@code Foo}, generates a class called {@code FooXYZ}. */ - public class AnAnnotationProcessor extends BasicAnnotationProcessor { + public static class AnAnnotationProcessor extends BaseAnnotationProcessor { @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latestSupported(); - } - - @Override - protected Iterable<? extends ProcessingStep> initSteps() { + protected Iterable<? extends Step> steps() { return ImmutableSet.of( - new ProcessingStep() { + new Step() { @Override - public Set<Element> process( - SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { + public ImmutableSet<Element> process( + ImmutableSetMultimap<String, Element> elementsByAnnotation) { for (Element element : elementsByAnnotation.values()) { generateClass(processingEnv.getFiler(), element.getSimpleName() + "XYZ"); } @@ -160,8 +162,8 @@ public class BasicAnnotationProcessorTest { } @Override - public Set<? extends Class<? extends Annotation>> annotations() { - return ImmutableSet.of(AnAnnotation.class); + public ImmutableSet<String> annotations() { + return ImmutableSet.of(ENCLOSING_CLASS_NAME + ".AnAnnotation"); } }); } @@ -171,19 +173,15 @@ public class BasicAnnotationProcessorTest { public @interface CauseError {} /** Report an error for any class annotated. */ - public static class CauseErrorProcessor extends BasicAnnotationProcessor { - @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latestSupported(); - } + public static class CauseErrorProcessor extends BaseAnnotationProcessor { @Override - protected Iterable<? extends ProcessingStep> initSteps() { + protected Iterable<? extends Step> steps() { return ImmutableSet.of( - new ProcessingStep() { + new Step() { @Override - public Set<Element> process( - SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { + public ImmutableSet<Element> process( + ImmutableSetMultimap<String, Element> elementsByAnnotation) { for (Element e : elementsByAnnotation.values()) { processingEnv.getMessager().printMessage(ERROR, "purposeful error", e); } @@ -191,26 +189,91 @@ public class BasicAnnotationProcessorTest { } @Override - public Set<? extends Class<? extends Annotation>> annotations() { - return ImmutableSet.of(CauseError.class); + public ImmutableSet<String> annotations() { + return ImmutableSet.of(ENCLOSING_CLASS_NAME + ".CauseError"); + } + }); + } + } + + public static class MissingAnnotationProcessor extends BaseAnnotationProcessor { + + private ImmutableSetMultimap<String, Element> elementsByAnnotation; + + @Override + protected Iterable<? extends Step> steps() { + return ImmutableSet.of( + new Step() { + @Override + public ImmutableSet<Element> process( + ImmutableSetMultimap<String, Element> elementsByAnnotation) { + MissingAnnotationProcessor.this.elementsByAnnotation = elementsByAnnotation; + for (Element element : elementsByAnnotation.values()) { + generateClass(processingEnv.getFiler(), element.getSimpleName() + "XYZ"); + } + return ImmutableSet.of(); + } + + @Override + public ImmutableSet<String> annotations() { + return ImmutableSet.of( + "test.SomeNonExistentClass", ENCLOSING_CLASS_NAME + ".AnAnnotation"); } }); } + + ImmutableSetMultimap<String, Element> getElementsByAnnotation() { + return elementsByAnnotation; + } } - @Test public void properlyDefersProcessing_typeElement() { - JavaFileObject classAFileObject = JavaFileObjects.forSourceLines("test.ClassA", - "package test;", - "", - "@" + RequiresGeneratedCode.class.getCanonicalName(), - "public class ClassA {", - " SomeGeneratedClass sgc;", - "}"); - JavaFileObject classBFileObject = JavaFileObjects.forSourceLines("test.ClassB", - "package test;", - "", - "@" + GeneratesCode.class.getCanonicalName(), - "public class ClassB {}"); + @SuppressWarnings("deprecation") // Deprecated ProcessingStep is being explicitly tested. + static final class MultiAnnotationProcessingStep implements ProcessingStep { + + private SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation; + + @Override + public ImmutableSet<? extends Class<? extends Annotation>> annotations() { + return ImmutableSet.of(AnAnnotation.class, ReferencesAClass.class); + } + + @Override + public ImmutableSet<? extends Element> process( + SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { + this.elementsByAnnotation = elementsByAnnotation; + return ImmutableSet.of(); + } + + SetMultimap<Class<? extends Annotation>, Element> getElementsByAnnotation() { + return elementsByAnnotation; + } + } + + @Retention(RetentionPolicy.SOURCE) + public @interface ReferencesAClass { + Class<?> value(); + } + + @Rule public CompilationRule compilation = new CompilationRule(); + + @Test + public void properlyDefersProcessing_typeElement() { + JavaFileObject classAFileObject = + JavaFileObjects.forSourceLines( + "test.ClassA", + "package test;", + "", + "@" + RequiresGeneratedCode.class.getCanonicalName(), + "public class ClassA {", + " SomeGeneratedClass sgc;", + "}"); + JavaFileObject classBFileObject = + JavaFileObjects.forSourceLines( + "test.ClassB", + "package test;", + "", + "@" + GeneratesCode.class.getCanonicalName(), + "public class ClassB {}"); RequiresGeneratedCodeProcessor requiresGeneratedCodeProcessor = new RequiresGeneratedCodeProcessor(); assertAbout(javaSources()) @@ -244,22 +307,22 @@ public class BasicAnnotationProcessorTest { .generatesFileNamed(SOURCE_OUTPUT, "test", "ValidInRound2XYZ.java"); } - @Retention(RetentionPolicy.SOURCE) - public @interface ReferencesAClass { - Class<?> value(); - } - - @Test public void properlyDefersProcessing_packageElement() { - JavaFileObject classAFileObject = JavaFileObjects.forSourceLines("test.ClassA", - "package test;", - "", - "@" + GeneratesCode.class.getCanonicalName(), - "public class ClassA {", - "}"); - JavaFileObject packageFileObject = JavaFileObjects.forSourceLines("test.package-info", - "@" + RequiresGeneratedCode.class.getCanonicalName(), - "@" + ReferencesAClass.class.getCanonicalName() + "(SomeGeneratedClass.class)", - "package test;"); + @Test + public void properlyDefersProcessing_packageElement() { + JavaFileObject classAFileObject = + JavaFileObjects.forSourceLines( + "test.ClassA", + "package test;", + "", + "@" + GeneratesCode.class.getCanonicalName(), + "public class ClassA {", + "}"); + JavaFileObject packageFileObject = + JavaFileObjects.forSourceLines( + "test.package-info", + "@" + RequiresGeneratedCode.class.getCanonicalName(), + "@" + ReferencesAClass.class.getCanonicalName() + "(SomeGeneratedClass.class)", + "package test;"); RequiresGeneratedCodeProcessor requiresGeneratedCodeProcessor = new RequiresGeneratedCodeProcessor(); assertAbout(javaSources()) @@ -272,21 +335,28 @@ public class BasicAnnotationProcessorTest { assertThat(requiresGeneratedCodeProcessor.rejectedRounds).isEqualTo(0); } - @Test public void properlyDefersProcessing_argumentElement() { - JavaFileObject classAFileObject = JavaFileObjects.forSourceLines("test.ClassA", - "package test;", - "", - "public class ClassA {", - " SomeGeneratedClass sgc;", - " public void myMethod(@" + RequiresGeneratedCode.class.getCanonicalName() + " int myInt)", - " {}", - "}"); - JavaFileObject classBFileObject = JavaFileObjects.forSourceLines("test.ClassB", - "package test;", - "", - "public class ClassB {", - " public void myMethod(@" + GeneratesCode.class.getCanonicalName() + " int myInt) {}", - "}"); + @Test + public void properlyDefersProcessing_argumentElement() { + JavaFileObject classAFileObject = + JavaFileObjects.forSourceLines( + "test.ClassA", + "package test;", + "", + "public class ClassA {", + " SomeGeneratedClass sgc;", + " public void myMethod(@" + + RequiresGeneratedCode.class.getCanonicalName() + + " int myInt)", + " {}", + "}"); + JavaFileObject classBFileObject = + JavaFileObjects.forSourceLines( + "test.ClassB", + "package test;", + "", + "public class ClassB {", + " public void myMethod(@" + GeneratesCode.class.getCanonicalName() + " int myInt) {}", + "}"); RequiresGeneratedCodeProcessor requiresGeneratedCodeProcessor = new RequiresGeneratedCodeProcessor(); assertAbout(javaSources()) @@ -311,11 +381,13 @@ public class BasicAnnotationProcessorTest { " @" + AnAnnotation.class.getCanonicalName(), " public void method() {}", "}"); - JavaFileObject classBFileObject = JavaFileObjects.forSourceLines("test.ClassB", - "package test;", - "", - "@" + GeneratesCode.class.getCanonicalName(), - "public class ClassB {}"); + JavaFileObject classBFileObject = + JavaFileObjects.forSourceLines( + "test.ClassB", + "package test;", + "", + "@" + GeneratesCode.class.getCanonicalName(), + "public class ClassB {}"); RequiresGeneratedCodeProcessor requiresGeneratedCodeProcessor = new RequiresGeneratedCodeProcessor(); assertAbout(javaSources()) @@ -332,8 +404,8 @@ public class BasicAnnotationProcessorTest { assertThat(requiresGeneratedCodeProcessor.processArguments()) .comparingElementsUsing(setMultimapValuesByString()) .containsExactly( - ImmutableSetMultimap.of(RequiresGeneratedCode.class, "test.ClassA"), - ImmutableSetMultimap.of(RequiresGeneratedCode.class, "test.ClassA")) + ImmutableSetMultimap.of(RequiresGeneratedCode.class.getCanonicalName(), "test.ClassA"), + ImmutableSetMultimap.of(RequiresGeneratedCode.class.getCanonicalName(), "test.ClassA")) .inOrder(); } @@ -345,20 +417,60 @@ public class BasicAnnotationProcessorTest { "is equivalent comparing multimap values by `toString()` to"); } - @Test public void reportsMissingType() { - JavaFileObject classAFileObject = JavaFileObjects.forSourceLines("test.ClassA", - "package test;", - "", - "@" + RequiresGeneratedCode.class.getCanonicalName(), - "public class ClassA {", - " SomeGeneratedClass bar;", - "}"); + @Test + public void properlySkipsMissingAnnotations_generatesClass() { + JavaFileObject source = + JavaFileObjects.forSourceLines( + "test.ValidInRound2", + "package test;", + "", + "@" + AnAnnotation.class.getCanonicalName(), + "public class ValidInRound2 {", + " ValidInRound1XYZ vir1xyz;", + " @" + AnAnnotation.class.getCanonicalName(), + " static class ValidInRound1 {}", + "}"); + Compilation compilation = + javac().withProcessors(new MissingAnnotationProcessor()).compile(source); + assertThat(compilation).succeeded(); + assertThat(compilation).generatedSourceFile("test.ValidInRound2XYZ"); + } + + @Test + public void properlySkipsMissingAnnotations_passesValidAnnotationsToProcess() { + JavaFileObject source = + JavaFileObjects.forSourceLines( + "test.ClassA", + "package test;", + "", + "@" + AnAnnotation.class.getCanonicalName(), + "public class ClassA {", + "}"); + MissingAnnotationProcessor missingAnnotationProcessor = new MissingAnnotationProcessor(); + assertThat(javac().withProcessors(missingAnnotationProcessor).compile(source)).succeeded(); + assertThat(missingAnnotationProcessor.getElementsByAnnotation().keySet()) + .containsExactly(AnAnnotation.class.getCanonicalName()); + assertThat(missingAnnotationProcessor.getElementsByAnnotation().values()).hasSize(1); + } + + @Test + public void reportsMissingType() { + JavaFileObject classAFileObject = + JavaFileObjects.forSourceLines( + "test.ClassA", + "package test;", + "", + "@" + RequiresGeneratedCode.class.getCanonicalName(), + "public class ClassA {", + " SomeGeneratedClass bar;", + "}"); assertAbout(javaSources()) .that(ImmutableList.of(classAFileObject)) .processedWith(new RequiresGeneratedCodeProcessor()) .failsToCompile() .withErrorContaining(RequiresGeneratedCodeProcessor.class.getCanonicalName()) - .in(classAFileObject).onLine(4); + .in(classAFileObject) + .onLine(4); } @Test @@ -378,6 +490,45 @@ public class BasicAnnotationProcessorTest { .withErrorContaining("purposeful"); } + @Test + public void processingStepAsStepAnnotationsNamesMatchClasses() { + Step step = BasicAnnotationProcessor.asStep(new MultiAnnotationProcessingStep()); + + assertThat(step.annotations()) + .containsExactly( + AnAnnotation.class.getCanonicalName(), ReferencesAClass.class.getCanonicalName()); + } + + /** + * Tests that a {@link ProcessingStep} passed to {@link + * BasicAnnotationProcessor#asStep(ProcessingStep)} still gets passed the correct arguments to + * {@link Step#process(ImmutableSetMultimap)}. + */ + @Test + public void processingStepAsStepProcessElementsMatchClasses() { + Elements elements = compilation.getElements(); + String anAnnotationName = AnAnnotation.class.getCanonicalName(); + String referencesAClassName = ReferencesAClass.class.getCanonicalName(); + TypeElement anAnnotationElement = elements.getTypeElement(anAnnotationName); + TypeElement referencesAClassElement = elements.getTypeElement(referencesAClassName); + MultiAnnotationProcessingStep processingStep = new MultiAnnotationProcessingStep(); + + BasicAnnotationProcessor.asStep(processingStep) + .process( + ImmutableSetMultimap.of( + anAnnotationName, + anAnnotationElement, + referencesAClassName, + referencesAClassElement)); + + assertThat(processingStep.getElementsByAnnotation()) + .containsExactly( + AnAnnotation.class, + anAnnotationElement, + ReferencesAClass.class, + referencesAClassElement); + } + private static void generateClass(Filer filer, String generatedClassName) { PrintWriter writer = null; try { |