blob: 1d34e2e0241b17144d9604fcfc84b7ba82ea0ed5 [file] [log] [blame]
/*
* Copyright (C) 2014 The Guava Authors
*
* 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.value.processor;
import static com.google.common.truth.Truth.assertAbout;
import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.testing.compile.JavaFileObjects;
import junit.framework.TestCase;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;
import javax.tools.JavaFileObject;
/**
* @author emcmanus@google.com (Éamonn McManus)
*/
public class CompilationTest extends TestCase {
public void testCompilation() {
// Positive test case that ensures we generate the expected code for at least one case.
// Most AutoValue code-generation tests are functional, meaning that we check that the generated
// code does the right thing rather than checking what it looks like, but this test is a sanity
// check that we are not generating correct but weird code.
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" public abstract int buh();",
"",
" public static Baz create(int buh) {",
" return new AutoValue_Baz(buh);",
" }",
"}");
JavaFileObject expectedOutput = JavaFileObjects.forSourceLines(
"foo.bar.AutoValue_Baz",
"package foo.bar;",
"",
"import javax.annotation.Generated;",
"",
"@Generated(\"" + AutoValueProcessor.class.getName() + "\")",
"final class AutoValue_Baz extends Baz {",
" private final int buh;",
"",
" AutoValue_Baz(int buh) {",
" this.buh = buh;",
" }",
"",
" @Override public int buh() {",
" return buh;",
" }",
"",
" @Override public String toString() {",
" return \"Baz{\"",
" + \"buh=\" + buh",
" + \"}\";",
" }",
"",
" @Override public boolean equals(Object o) {",
" if (o == this) {",
" return true;",
" }",
" if (o instanceof Baz) {",
" Baz that = (Baz) o;",
" return (this.buh == that.buh());",
" }",
" return false;",
" }",
"",
" @Override public int hashCode() {",
" int h = 1;",
" h *= 1000003;",
" h ^= buh;",
" return h;",
" }",
"}"
);
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor())
.compilesWithoutError()
.and().generatesSources(expectedOutput);
}
public void testImports() {
// Test that referring to the same class in two different ways does not confuse the import logic
// into thinking it is two different classes and that therefore it can't import. The code here
// is nonsensical but successfully reproduces a real problem, which is that a TypeMirror that is
// extracted using Elements.getTypeElement(name).asType() does not compare equal to one that is
// extracted from ExecutableElement.getReturnType(), even though Types.isSameType considers them
// equal. So unless we are careful, the java.util.Arrays that we import explicitly to use its
// methods will appear different from the java.util.Arrays that is the return type of the
// arrays() method here.
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"import java.util.Arrays;",
"",
"@AutoValue",
"public abstract class Baz {",
" public abstract int[] ints();",
" public abstract Arrays arrays();",
"",
" public static Baz create(int[] ints, Arrays arrays) {",
" return new AutoValue_Baz(ints, arrays);",
" }",
"}");
JavaFileObject expectedOutput = JavaFileObjects.forSourceLines(
"foo.bar.AutoValue_Baz",
"package foo.bar;",
"",
"import java.util.Arrays;",
"import javax.annotation.Generated;",
"",
"@Generated(\"" + AutoValueProcessor.class.getName() + "\")",
"final class AutoValue_Baz extends Baz {",
" private final int[] ints;",
" private final Arrays arrays;",
"",
" AutoValue_Baz(int[] ints, Arrays arrays) {",
" if (ints == null) {",
" throw new NullPointerException(\"Null ints\");",
" }",
" this.ints = ints;",
" if (arrays == null) {",
" throw new NullPointerException(\"Null arrays\");",
" }",
" this.arrays = arrays;",
" }",
"",
" @Override public int[] ints() {",
" return ints.clone();",
" }",
"",
" @Override public Arrays arrays() {",
" return arrays;",
" }",
"",
" @Override public String toString() {",
" return \"Baz{\"",
" + \"ints=\" + Arrays.toString(ints) + \", \"",
" + \"arrays=\" + arrays",
" + \"}\";",
" }",
"",
" @Override public boolean equals(Object o) {",
" if (o == this) {",
" return true;",
" }",
" if (o instanceof Baz) {",
" Baz that = (Baz) o;",
" return (Arrays.equals(this.ints, (that instanceof AutoValue_Baz) "
+ "? ((AutoValue_Baz) that).ints : that.ints()))",
" && (this.arrays.equals(that.arrays()));",
" }",
" return false;",
" }",
"",
" @Override public int hashCode() {",
" int h = 1;",
" h *= 1000003;",
" h ^= Arrays.hashCode(ints);",
" h *= 1000003;",
" h ^= arrays.hashCode();",
" return h;",
" }",
"}"
);
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor())
.compilesWithoutError()
.and().generatesSources(expectedOutput);
}
public void testNoMultidimensionalPrimitiveArrays() throws Exception {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" public abstract int[][] ints();",
"",
" public static Baz create(int[][] ints) {",
" return new AutoValue_Baz(ints);",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor())
.failsToCompile()
.withErrorContaining("AutoValue class cannot define an array-valued property "
+ "unless it is a primitive array")
.in(javaFileObject).onLine(7);
}
public void testNoObjectArrays() throws Exception {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" public abstract String[] strings();",
"",
" public static Baz create(String[] strings) {",
" return new AutoValue_Baz(strings);",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor())
.failsToCompile()
.withErrorContaining("AutoValue class cannot define an array-valued property "
+ "unless it is a primitive array")
.in(javaFileObject).onLine(7);
}
public void testAnnotationOnInterface() throws Exception {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public interface Baz {}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor())
.failsToCompile()
.withErrorContaining("AutoValue only applies to classes")
.in(javaFileObject).onLine(6);
}
public void testAnnotationOnEnum() throws Exception {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public enum Baz {}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor())
.failsToCompile()
.withErrorContaining("AutoValue only applies to classes")
.in(javaFileObject).onLine(6);
}
public void testExtendAutoValue() throws Exception {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Outer",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"public class Outer {",
" @AutoValue",
" static abstract class Parent {",
" static Parent create(int randomProperty) {",
" return new AutoValue_Outer_Parent(randomProperty);",
" }",
"",
" abstract int randomProperty();",
" }",
"",
" @AutoValue",
" static abstract class Child extends Parent {",
" static Child create(int randomProperty) {",
" return new AutoValue_Outer_Child(randomProperty);",
" }",
"",
" abstract int randomProperty();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor())
.failsToCompile()
.withErrorContaining("may not extend")
.in(javaFileObject).onLine(16);
}
public void testBogusSerialVersionUID() throws Exception {
String[] mistakes = {
"final long serialVersionUID = 1234L", // not static
"static long serialVersionUID = 1234L", // not final
"static final Long serialVersionUID = 1234L", // not long
"static final long serialVersionUID = (Long) 1234L", // not a compile-time constant
};
for (String mistake : mistakes) {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz implements java.io.Serializable {",
" " + mistake + ";",
"",
" public abstract int foo();",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor())
.failsToCompile()
.withErrorContaining(
"serialVersionUID must be a static final long compile-time constant")
.in(javaFileObject).onLine(7);
}
}
public void testNonExistentSuperclass() throws Exception {
// The main purpose of this test is to check that AutoValueProcessor doesn't crash the
// compiler in this case.
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Existent extends NonExistent {",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor())
.failsToCompile()
.withErrorContaining("NonExistent")
.in(javaFileObject).onLine(6);
}
public void testCannotImplementAnnotation() throws Exception {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.RetentionImpl",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"import java.lang.annotation.Retention;",
"import java.lang.annotation.RetentionPolicy;",
"",
"@AutoValue",
"public abstract class RetentionImpl implements Retention {",
" public static Retention create(RetentionPolicy policy) {",
" return new AutoValue_RetentionImpl(policy);",
" }",
"",
" @Override public Class<? extends Retention> annotationType() {",
" return Retention.class;",
" }",
"",
" @Override public boolean equals(Object o) {",
" return (o instanceof Retention && value().equals((Retention) o).value());",
" }",
"",
" @Override public int hashCode() {",
" return (\"value\".hashCode() * 127) ^ value().hashCode();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor())
.failsToCompile()
.withErrorContaining("may not be used to implement an annotation interface")
.in(javaFileObject).onLine(8);
}
public void testMissingPropertyType() throws Exception {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" public abstract MissingType missingType();",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor())
.failsToCompile()
.withErrorContaining("MissingType")
.in(javaFileObject).onLine(7);
}
public void testMissingGenericPropertyType() throws Exception {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" public abstract MissingType<?> missingType();",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor())
.failsToCompile()
.withErrorContaining("MissingType")
.in(javaFileObject).onLine(7);
}
public void testMissingComplexGenericPropertyType() throws Exception {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"import java.util.Map;",
"import java.util.Set;",
"",
"@AutoValue",
"public abstract class Baz {",
" public abstract Map<Set<?>, MissingType<?>> missingType();",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor())
.failsToCompile()
.withErrorContaining("MissingType")
.in(javaFileObject).onLine(10);
}
public void testMissingSuperclassGenericParameter() throws Exception {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz<T extends MissingType<?>> {",
" public abstract int foo();",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor())
.failsToCompile()
.withErrorContaining("MissingType")
.in(javaFileObject).onLine(6);
}
public void testCorrectBuilder() throws Exception {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"import java.util.List;",
"import javax.annotation.Nullable;",
"",
"@AutoValue",
"public abstract class Baz<T extends Number> {",
" public abstract int anInt();",
" public abstract byte[] aByteArray();",
" @Nullable public abstract int[] aNullableIntArray();",
" public abstract List<T> aList();",
"",
" public abstract Builder<T> toBuilder();",
"",
" @AutoValue.Validate",
" void validate() {",
" if (anInt() < 0) {",
" throw new IllegalStateException(\"Negative integer\");",
" }",
" }",
"",
" @AutoValue.Builder",
" public interface Builder<T extends Number> {",
" Builder<T> anInt(int x);",
" Builder<T> aByteArray(byte[] x);",
" Builder<T> aNullableIntArray(@Nullable int[] x);",
" Builder<T> aList(List<T> x);",
" Baz<T> build();",
" }",
"",
" public static <T extends Number> Builder<T> builder() {",
" return AutoValue_Baz.builder();",
" }",
"}");
JavaFileObject expectedOutput = JavaFileObjects.forSourceLines(
"foo.bar.AutoValue_Baz",
"package foo.bar;",
"",
"import java.util.Arrays;",
"import java.util.BitSet;",
"import java.util.List;",
"import javax.annotation.Generated;",
"",
"@Generated(\"" + AutoValueProcessor.class.getName() + "\")",
"final class AutoValue_Baz<T extends Number> extends Baz<T> {",
" private final int anInt;",
" private final byte[] aByteArray;",
" private final int[] aNullableIntArray;",
" private final List<T> aList;",
"",
" AutoValue_Baz(int anInt, byte[] aByteArray, int[] aNullableIntArray, List<T> aList) {",
" this.anInt = anInt;",
" if (aByteArray == null) {",
" throw new NullPointerException(\"Null aByteArray\");",
" }",
" this.aByteArray = aByteArray;",
" this.aNullableIntArray = aNullableIntArray;",
" if (aList == null) {",
" throw new NullPointerException(\"Null aList\");",
" }",
" this.aList = aList;",
" }",
"",
" @Override public int anInt() {",
" return anInt;",
" }",
"",
" @Override public byte[] aByteArray() {",
" return aByteArray.clone();",
" }",
"",
" @javax.annotation.Nullable",
" @Override public int[] aNullableIntArray() {",
" return aNullableIntArray == null ? null : aNullableIntArray.clone();",
" }",
"",
" @Override public List<T> aList() {",
" return aList;",
" }",
"",
" @Override public String toString() {",
" return \"Baz{\"",
" + \"anInt=\" + anInt + \", \"",
" + \"aByteArray=\" + Arrays.toString(aByteArray) + \", \"",
" + \"aNullableIntArray=\" + Arrays.toString(aNullableIntArray) + \", \"",
" + \"aList=\" + aList",
" + \"}\";",
" }",
"",
" @Override public boolean equals(Object o) {",
" if (o == this) {",
" return true;",
" }",
" if (o instanceof Baz) {",
" Baz<?> that = (Baz<?>) o;",
" return (this.anInt == that.anInt())",
" && (Arrays.equals(this.aByteArray, "
+ "(that instanceof AutoValue_Baz) "
+ "? ((AutoValue_Baz) that).aByteArray : that.aByteArray()))",
" && (Arrays.equals(this.aNullableIntArray, "
+ "(that instanceof AutoValue_Baz) "
+ "? ((AutoValue_Baz) that).aNullableIntArray : that.aNullableIntArray()))",
" && (this.aList.equals(that.aList()));",
" }",
" return false;",
" }",
"",
" @Override public int hashCode() {",
" int h = 1;",
" h *= 1000003;",
" h ^= anInt;",
" h *= 1000003;",
" h ^= Arrays.hashCode(aByteArray);",
" h *= 1000003;",
" h ^= Arrays.hashCode(aNullableIntArray);",
" h *= 1000003;",
" h ^= aList.hashCode();",
" return h;",
" }",
"",
" @Override public Baz.Builder<T> toBuilder() {",
" return new Builder<T>(this);",
" }",
"",
" static final class Builder<T extends Number> implements Baz.Builder<T> {",
" private final BitSet set$ = new BitSet(4);",
"",
" private int anInt;",
" private byte[] aByteArray;",
" private int[] aNullableIntArray;",
" private List<T> aList;",
"",
" Builder() {",
" }",
"",
" Builder(Baz<T> source) {",
" anInt(source.anInt());",
" aByteArray(source.aByteArray());",
" aNullableIntArray(source.aNullableIntArray());",
" aList(source.aList());",
" }",
"",
" @Override",
" public Baz.Builder<T> anInt(int anInt) {",
" this.anInt = anInt;",
" set$.set(0);",
" return this;",
" }",
"",
" @Override",
" public Baz.Builder<T> aByteArray(byte[] aByteArray) {",
" this.aByteArray = aByteArray.clone();",
" set$.set(1);",
" return this;",
" }",
"",
" @Override",
" public Baz.Builder<T> aNullableIntArray(int[] aNullableIntArray) {",
" this.aNullableIntArray = "
+ "(aNullableIntArray == null) ? null : aNullableIntArray.clone();",
" set$.set(2);",
" return this;",
" }",
"",
" @Override",
" public Baz.Builder<T> aList(List<T> aList) {",
" this.aList = aList;",
" set$.set(3);",
" return this;",
" }",
"",
" @Override",
" public Baz<T> build() {",
" if (set$.cardinality() < 4) {",
" String[] propertyNames = {",
" \"anInt\", \"aByteArray\", \"aNullableIntArray\", \"aList\",",
" };",
" StringBuilder missing = new StringBuilder();",
" for (int i = 0; i < 4; i++) {",
" if (!set$.get(i)) {",
" missing.append(' ').append(propertyNames[i]);",
" }",
" }",
" throw new IllegalStateException(\"Missing required properties:\" + missing);",
" }",
" Baz<T> result = new AutoValue_Baz<T>(",
" this.anInt, this.aByteArray, this.aNullableIntArray, this.aList);",
" result.validate();",
" return result;",
" }",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor())
.compilesWithoutError()
.and()
.generatesSources(expectedOutput);
}
public void testAutoValueBuilderOnTopLevelClass() throws Exception {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Builder",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue.Builder",
"public interface Builder {",
" Builder foo(int x);",
" Object build();",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining("can only be applied to a class or interface inside")
.in(javaFileObject).onLine(6);
}
public void testAutoValueBuilderNotInsideAutoValue() throws Exception {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"public abstract class Baz {",
" abstract int foo();",
"",
" static Builder builder() {",
" return new AutoValue_Baz.Builder();",
" }",
"",
" @AutoValue.Builder",
" public interface Builder {",
" Builder foo(int x);",
" Baz build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining("can only be applied to a class or interface inside")
.in(javaFileObject).onLine(13);
}
public void testAutoValueBuilderOnEnum() throws Exception {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" abstract int foo();",
"",
" static Builder builder() {",
" return null;",
" }",
"",
" @AutoValue.Builder",
" public enum Builder {}",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining("can only apply to a class or an interface")
.in(javaFileObject).onLine(14);
}
public void testAutoValueBuilderDuplicate() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" @AutoValue.Builder",
" public interface Builder1 {",
" Baz build();",
" }",
"",
" @AutoValue.Builder",
" public interface Builder2 {",
" Baz build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining("already has a Builder: foo.bar.Baz.Builder1")
.in(javaFileObject).onLine(13);
}
public void testAutoValueBuilderMissingSetter() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" abstract int blim();",
" abstract String blam();",
"",
" @AutoValue.Builder",
" public interface Builder {",
" Builder blam(String x);",
" Baz build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining("with this signature: foo.bar.Baz.Builder blim(int)")
.in(javaFileObject).onLine(11);
}
public void testAutoValueBuilderMissingSetterUsingSetPrefix() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" abstract int blim();",
" abstract String blam();",
"",
" @AutoValue.Builder",
" public interface Builder {",
" Builder setBlam(String x);",
" Baz build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining("with this signature: foo.bar.Baz.Builder setBlim(int)")
.in(javaFileObject).onLine(11);
}
public void testAutoValueBuilderWrongTypeSetter() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" abstract int blim();",
" abstract String blam();",
"",
" @AutoValue.Builder",
" public interface Builder {",
" Builder blim(String x);",
" Builder blam(String x);",
" Baz build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining("Parameter type should be int")
.in(javaFileObject).onLine(12);
}
public void testAutoValueBuilderWrongTypeSetterWithGetPrefix() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" abstract int getBlim();",
" abstract String getBlam();",
"",
" @AutoValue.Builder",
" public interface Builder {",
" Builder blim(String x);",
" Builder blam(String x);",
" Baz build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining("Parameter type should be int")
.in(javaFileObject).onLine(12);
}
public void testAutoValueBuilderExtraSetter() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" abstract String blam();",
"",
" @AutoValue.Builder",
" public interface Builder {",
" Builder blim(int x);",
" Builder blam(String x);",
" Baz build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining("Method does not correspond to a property of foo.bar.Baz")
.in(javaFileObject).onLine(11);
}
public void testAutoValueBuilderSetPrefixAndNoSetPrefix() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" abstract int blim();",
" abstract String blam();",
"",
" @AutoValue.Builder",
" public interface Builder {",
" Builder blim(int x);",
" Builder setBlam(String x);",
" Baz build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining("If any setter methods use the setFoo convention then all must")
.in(javaFileObject).onLine(12);
}
public void testAutoValueBuilderAlienMethod() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" abstract String blam();",
"",
" @AutoValue.Builder",
" public interface Builder {",
" Builder blam(String x, String y);",
" Baz build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining(
"Builder methods must either have no arguments and return foo.bar.Baz or have one"
+ " argument and return foo.bar.Baz.Builder")
.in(javaFileObject).onLine(11);
}
public void testAutoValueBuilderMissingBuildMethod() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz<T> {",
" abstract T blam();",
"",
" @AutoValue.Builder",
" public interface Builder<T> {",
" Builder<T> blam(T x);",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining(
"Builder must have a single no-argument method returning foo.bar.Baz<T>")
.in(javaFileObject).onLine(10);
}
public void testAutoValueBuilderDuplicateBuildMethods() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" abstract String blam();",
"",
" @AutoValue.Builder",
" public interface Builder {",
" Builder blam(String x);",
" Baz build();",
" Baz create();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining("Builder must have a single no-argument method returning foo.bar.Baz")
.in(javaFileObject).onLine(12)
.and()
.withErrorContaining("Builder must have a single no-argument method returning foo.bar.Baz")
.in(javaFileObject).onLine(13);
}
public void testAutoValueBuilderWrongTypeBuildMethod() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" abstract String blam();",
"",
" @AutoValue.Builder",
" public interface Builder {",
" Builder blam(String x);",
" String build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining("Builder must have a single no-argument method returning foo.bar.Baz")
.in(javaFileObject).onLine(10);
}
public void testAutoValueBuilderTypeParametersDontMatch1() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz<T> {",
" abstract String blam();",
"",
" @AutoValue.Builder",
" public interface Builder {",
" Builder blam(String x);",
" Baz build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining("Type parameters of foo.bar.Baz.Builder must have same names and "
+ "bounds as type parameters of foo.bar.Baz")
.in(javaFileObject).onLine(10);
}
public void testAutoValueBuilderTypeParametersDontMatch2() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz<T> {",
" abstract T blam();",
"",
" @AutoValue.Builder",
" public interface Builder<E> {",
" Builder<E> blam(E x);",
" Baz build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining("Type parameters of foo.bar.Baz.Builder must have same names and "
+ "bounds as type parameters of foo.bar.Baz")
.in(javaFileObject).onLine(10);
}
public void testAutoValueBuilderTypeParametersDontMatch3() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz<T extends Number & Comparable<T>> {",
" abstract T blam();",
"",
" @AutoValue.Builder",
" public interface Builder<T extends Number> {",
" Builder<T> blam(T x);",
" Baz build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining("Type parameters of foo.bar.Baz.Builder must have same names and "
+ "bounds as type parameters of foo.bar.Baz")
.in(javaFileObject).onLine(10);
}
public void testAutoValueBuilderToBuilderWrongTypeParameters() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"abstract class Baz<K extends Comparable<K>, V> {",
" abstract K key();",
" abstract V value();",
" abstract Builder<V, K> toBuilder1();",
"",
" @AutoValue.Builder",
" interface Builder<K extends Comparable<K>, V> {",
" Builder<K, V> key(K key);",
" Builder<K, V> value(V value);",
" Baz<K, V> build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining("Builder converter method should return foo.bar.Baz.Builder<K, V>")
.in(javaFileObject).onLine(9);
}
public void testAutoValueBuilderToBuilderDuplicate() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"abstract class Baz<K extends Comparable<K>, V> {",
" abstract K key();",
" abstract V value();",
" abstract Builder<K, V> toBuilder1();",
" abstract Builder<K, V> toBuilder2();",
"",
" @AutoValue.Builder",
" interface Builder<K extends Comparable<K>, V> {",
" Builder<K, V> key(K key);",
" Builder<K, V> value(V value);",
" Baz<K, V> build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining("There can be at most one builder converter method")
.in(javaFileObject).onLine(9);
}
public void testAutoValueValidateNotInAutoValue() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"public abstract class Baz {",
" abstract String blam();",
"",
" @AutoValue.Validate",
" void validate() {}",
"",
" public interface Builder {",
" Builder blam(String x);",
" Baz build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining(
"@AutoValue.Validate can only be applied to a method inside an @AutoValue class")
.in(javaFileObject).onLine(9);
}
public void testAutoValueValidateWithoutBuilder() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" abstract String blam();",
"",
" @AutoValue.Validate",
" void validate() {}",
"",
" public interface Builder {",
" Builder blam(String x);",
" Baz build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining(
"@AutoValue.Validate is only meaningful if there is an @AutoValue.Builder")
.in(javaFileObject).onLine(10);
}
public void testAutoValueBuilderValidateMethodStatic() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" abstract String blam();",
"",
" @AutoValue.Validate",
" static void validate() {}",
"",
" @AutoValue.Builder",
" public interface Builder {",
" Builder blam(String x);",
" Baz build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining("@AutoValue.Validate cannot apply to a static method")
.in(javaFileObject).onLine(10);
}
public void testAutoValueBuilderValidateMethodNotVoid() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" abstract String blam();",
"",
" @AutoValue.Validate",
" Baz validate() {",
" return this;",
" }",
"",
" @AutoValue.Builder",
" public interface Builder {",
" Builder blam(String x);",
" Baz build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining("@AutoValue.Validate method must be void")
.in(javaFileObject).onLine(10);
}
public void testAutoValueBuilderValidateMethodWithParameters() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" abstract String blam();",
"",
" @AutoValue.Validate",
" void validate(boolean why) {}",
"",
" @AutoValue.Builder",
" public interface Builder {",
" Builder blam(String x);",
" Baz build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining("@AutoValue.Validate method must not have parameters")
.in(javaFileObject).onLine(10);
}
public void testAutoValueBuilderValidateMethodDuplicate() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" abstract String blam();",
"",
" @AutoValue.Validate",
" void validate() {}",
"",
" @AutoValue.Validate",
" void validateSomeMore() {}",
"",
" @AutoValue.Builder",
" public interface Builder {",
" Builder blam(String x);",
" Baz build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining("There can only be one @AutoValue.Validate method")
.in(javaFileObject).onLine(13);
}
public void testGetFooIsFoo() throws Exception {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" abstract int getFoo();",
" abstract boolean isFoo();",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor())
.failsToCompile()
.withErrorContaining("More than one @AutoValue property called foo")
.in(javaFileObject).onLine(8);
}
private static class PoisonedAutoValueProcessor extends AutoValueProcessor {
private final IllegalArgumentException filerException;
PoisonedAutoValueProcessor(IllegalArgumentException filerException) {
this.filerException = filerException;
}
private class ErrorInvocationHandler implements InvocationHandler {
private final ProcessingEnvironment originalProcessingEnv;
ErrorInvocationHandler(ProcessingEnvironment originalProcessingEnv) {
this.originalProcessingEnv = originalProcessingEnv;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("getFiler")) {
throw filerException;
} else {
return method.invoke(originalProcessingEnv, args);
}
}
};
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
ProcessingEnvironment poisonedProcessingEnv = (ProcessingEnvironment) Proxy.newProxyInstance(
getClass().getClassLoader(),
new Class<?>[] {ProcessingEnvironment.class},
new ErrorInvocationHandler(processingEnv));
processingEnv = poisonedProcessingEnv;
return super.process(annotations, roundEnv);
}
}
public void testExceptionBecomesError() throws Exception {
// Ensure that if the annotation processor code gets an unexpected exception, it is converted
// into a compiler error rather than being propagated. Otherwise the output can be very
// confusing to the user who stumbles into a bug that causes an exception, whether in
// AutoValueProcessor or javac.
// We inject an exception by subclassing AutoValueProcessor in order to poison its processingEnv
// in a way that will cause an exception the first time it tries to get the Filer.
IllegalArgumentException exception =
new IllegalArgumentException("I don't understand the question, and I won't respond to it");
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" public abstract int foo();",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new PoisonedAutoValueProcessor(exception))
.failsToCompile()
.withErrorContaining(exception.toString())
.in(javaFileObject).onLine(6);
}
@Retention(RetentionPolicy.SOURCE)
public @interface Foo {}
/* Processor that generates an empty class BarFoo every time it sees a class Bar annotated with
* @Foo.
*/
public static class FooProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
return ImmutableSet.of(Foo.class.getCanonicalName());
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Foo.class);
for (TypeElement type : ElementFilter.typesIn(elements)) {
try {
generateFoo(type);
} catch (IOException e) {
throw new AssertionError(e);
}
}
return false;
}
private void generateFoo(TypeElement type) throws IOException {
String pkg = TypeSimplifier.packageNameOf(type);
String className = type.getSimpleName().toString();
String generatedClassName = className + "Foo";
JavaFileObject source =
processingEnv.getFiler().createSourceFile(pkg + "." + generatedClassName, type);
PrintWriter writer = new PrintWriter(source.openWriter());
writer.println("package " + pkg + ";");
writer.println("public class " + generatedClassName + " {}");
writer.close();
}
}
public void testReferencingGeneratedClass() {
// Test that ensures that a type that does not exist can be the type of an @AutoValue property
// as long as it later does come into existence. The BarFoo type referenced here does not exist
// when the AutoValueProcessor runs on the first round, but the FooProcessor then generates it.
// That generation provokes a further round of annotation processing and AutoValueProcessor
// should succeed then.
JavaFileObject bazFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
"public abstract class Baz {",
" public abstract BarFoo barFoo();",
"",
" public static Baz create(BarFoo barFoo) {",
" return new AutoValue_Baz(barFoo);",
" }",
"}");
JavaFileObject barFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Bar",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
"@" + Foo.class.getCanonicalName(),
"public abstract class Bar {",
" public abstract BarFoo barFoo();",
"}");
assertAbout(javaSources())
.that(ImmutableList.of(bazFileObject, barFileObject))
.processedWith(new AutoValueProcessor(), new FooProcessor())
.compilesWithoutError();
}
}