diff options
Diffstat (limited to 'value/src/it/functional')
11 files changed, 1360 insertions, 60 deletions
diff --git a/value/src/it/functional/pom.xml b/value/src/it/functional/pom.xml index 750b9c43..d4ae1386 100644 --- a/value/src/it/functional/pom.xml +++ b/value/src/it/functional/pom.xml @@ -32,6 +32,7 @@ <version>1.7.4</version> <name>Auto-Value Functional Integration Test</name> <properties> + <kotlin.version>1.5.21</kotlin.version> <exclude.tests>this-matches-nothing</exclude.tests> </properties> <dependencies> @@ -48,7 +49,7 @@ <dependency> <groupId>com.google.auto.service</groupId> <artifactId>auto-service</artifactId> - <version>1.0-rc6</version> + <version>1.0</version> </dependency> <dependency> <groupId>com.google.guava</groupId> @@ -62,7 +63,7 @@ <dependency> <groupId>com.google.gwt</groupId> <artifactId>gwt-user</artifactId> - <version>2.8.2</version> + <version>2.9.0</version> </dependency> <dependency> <groupId>junit</groupId> @@ -90,21 +91,32 @@ <scope>test</scope> </dependency> <dependency> + <groupId>dev.gradleplugins</groupId> + <artifactId>gradle-test-kit</artifactId> + <version>6.8.3</version> + <scope>test</scope> + </dependency> + <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> - <version>3.1.0</version> + <version>3.11.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.eclipse.jdt</groupId> <artifactId>ecj</artifactId> - <version>3.20.0</version> + <version>3.25.0</version> </dependency> <dependency> <groupId>com.google.escapevelocity</groupId> <artifactId>escapevelocity</artifactId> <version>0.9.1</version> </dependency> + <dependency> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-stdlib</artifactId> + <version>${kotlin.version}</version> + </dependency> </dependencies> <build> @@ -112,17 +124,49 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> + <version>3.2.0</version> + </plugin> + <plugin> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-maven-plugin</artifactId> + <version>${kotlin.version}</version> + <executions> + <!-- + The Kotlin configuration here is a bit unusual. JetBrains recommends + <https://kotlinlang.org/docs/maven.html#compile-kotlin-and-java-sources> + a fairly invasive reconfiguration of the maven-compiler-plugin (which compiles Java) + in order to ensure that the Kotlin compiler can run first. In our case, we have just + one Kotlin file that a test in Java accesses. So, even though it is in src/test/java, + we compile it in the `compile` goal, which means it is available to the Java test sources + when they are compiled in the `default-testCompile` goal. + + Currently if you want to use JDK ≥ 16 then you must set + JAVA_TOOL_OPTIONS=__illegal-access=permit + except the two underscores should be dashes (which XML comments won't allow us to write). + This is a bug that will presumably be fixed in a forthcoming version. + --> + <execution> + <id>compile</id> + <goals> + <goal>compile</goal> + </goals> + <configuration> + <sourceDirs> + <sourceDir>${project.basedir}/src/test/java</sourceDir> + </sourceDirs> + </configuration> + </execution> + </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>3.7.0</version> + <version>3.8.1</version> <dependencies> <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-java</artifactId> - <version>0.9.4</version> + <version>1.0.7</version> </dependency> </dependencies> <configuration> @@ -132,6 +176,7 @@ <arg>-Xlint:all</arg> <arg>-encoding</arg> <arg>utf8</arg> + <arg>-Acom.google.auto.value.AutoBuilderIsUnstable</arg> </compilerArgs> <showWarnings>true</showWarnings> <showDeprecation>true</showDeprecation> @@ -143,12 +188,23 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> - <version>2.7</version> + <version>2.8.2</version> <configuration> <!-- Build/test, but don't deploy --> <skip>true</skip> </configuration> </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <version>2.22.2</version> + <configuration> + <argLine>${test.jvm.flags}</argLine> + <systemPropertyVariables> + <autoValueVersion>${project.version}</autoValueVersion> + </systemPropertyVariables> + </configuration> + </plugin> </plugins> </build> @@ -160,12 +216,12 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>3.7.0</version> + <version>3.8.1</version> <dependencies> <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-java</artifactId> - <version>0.9.4</version> + <version>1.0.7</version> </dependency> </dependencies> <configuration> @@ -195,5 +251,27 @@ <exclude.tests>**/AutoValueJava8Test.java</exclude.tests> </properties> </profile> + <profile> + <id>open-modules</id> + <activation> + <jdk>[9,)</jdk> + </activation> + <properties> + <test.jvm.flags>--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</test.jvm.flags> + </properties> + </profile> + <profile> + <!-- Before JDK 11, parameter names from already-compiled classes are not reliably available + to the compiler even when they are present in class files. Since our Kotlin test file + obviously has to be compiled separately from the Java test that uses it, + AutoBuilderKotlinTest doesn't pass on earlier JDK versions. --> + <id>exclude-separate-compilation-parameter-names</id> + <activation> + <jdk>(,11)</jdk> + </activation> + <properties> + <exclude.tests>**/AutoBuilderKotlinTest.java</exclude.tests> + </properties> + </profile> </profiles> </project> diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoAnnotationTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoAnnotationTest.java index ca1ef6b9..a04d41f3 100644 --- a/value/src/it/functional/src/test/java/com/google/auto/value/AutoAnnotationTest.java +++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoAnnotationTest.java @@ -15,20 +15,24 @@ */ package com.google.auto.value; -import static org.junit.Assert.assertEquals; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.TruthJUnit.assume; import com.google.auto.value.annotations.Empty; import com.google.auto.value.annotations.GwtArrays; import com.google.auto.value.annotations.StringValues; +import com.google.common.base.StandardSystemProperty; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; -import com.google.common.primitives.Ints; import com.google.common.testing.EqualsTester; +import com.google.common.testing.SerializableTester; +import java.io.ObjectStreamClass; import java.lang.annotation.Annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -38,6 +42,7 @@ import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; +import org.junit.AssumptionViolatedException; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -66,13 +71,41 @@ public class AutoAnnotationTest { } @Test + public void testEqualsParameterAnnotation() throws ReflectiveOperationException { + assume() + .that(Double.parseDouble(StandardSystemProperty.JAVA_SPECIFICATION_VERSION.value())) + .isAtLeast(8.0); + Class<? extends Annotation> jspecifyNullable; + try { + // We write this using .concat in order to hide it from rewriting rules. + jspecifyNullable = + Class.forName("org".concat(".jspecify.nullness.Nullable")).asSubclass(Annotation.class); + } catch (ClassNotFoundException e) { + throw new AssumptionViolatedException("No JSpecify @Nullable available", e); + } + @SuppressWarnings("GetClassOnAnnotation") // yes, I really want the implementation class + Class<? extends StringValues> autoAnnotationImpl = newStringValues(new String[0]).getClass(); + Method equals = autoAnnotationImpl.getDeclaredMethod("equals", Object.class); + // The remaining faffing around with reflection is there because we have a Google-internal test + // that runs this code with -source 7 -target 7. We're really just doing this: + // assertThat(equals.getAnnotatedParameterTypes()[0].isAnnotationPresent(jspecifyNullable)) + // .isTrue(); + Method getAnnotatedParameterTypes = Method.class.getMethod("getAnnotatedParameterTypes"); + Object[] annotatedParameterTypes = (Object[]) getAnnotatedParameterTypes.invoke(equals); + Method isAnnotationPresent = + annotatedParameterTypes[0].getClass().getMethod("isAnnotationPresent", Class.class); + assertThat(isAnnotationPresent.invoke(annotatedParameterTypes[0], jspecifyNullable)) + .isEqualTo(true); + } + + @Test public void testArraysAreCloned() { String[] array = {"Jekyll"}; StringValues stringValues = newStringValues(array); array[0] = "Hyde"; - assertEquals("Jekyll", stringValues.value()[0]); + assertThat(stringValues.value()).asList().containsExactly("Jekyll"); stringValues.value()[0] = "Hyde"; - assertEquals("Jekyll", stringValues.value()[0]); + assertThat(stringValues.value()[0]).isEqualTo("Jekyll"); } @Test @@ -80,12 +113,12 @@ public class AutoAnnotationTest { String[] strings = {"Jekyll"}; int[] ints = {2, 3, 5}; GwtArrays arrays = newGwtArrays(strings, ints); - assertEquals(ImmutableList.of("Jekyll"), ImmutableList.copyOf(arrays.strings())); - assertEquals(ImmutableList.of(2, 3, 5), Ints.asList(arrays.ints())); + assertThat(arrays.strings()).asList().containsExactly("Jekyll"); + assertThat(arrays.ints()).asList().containsExactly(2, 3, 5).inOrder(); strings[0] = "Hyde"; ints[0] = -1; - assertEquals(ImmutableList.of("Jekyll"), ImmutableList.copyOf(arrays.strings())); - assertEquals(ImmutableList.of(2, 3, 5), Ints.asList(arrays.ints())); + assertThat(arrays.strings()).asList().containsExactly("Jekyll"); + assertThat(arrays.ints()).asList().containsExactly(2, 3, 5).inOrder(); } @AutoAnnotation @@ -406,7 +439,39 @@ public class AutoAnnotationTest { .testEquals(); } + @Test + public void testSerialization() { + Annotation[] instances = {EVERYTHING_FROM_AUTO, EVERYTHING_FROM_AUTO_COLLECTIONS}; + for (Annotation instance : instances) { + SerializableTester.reserializeAndAssert(instance); + } + } + + @Test + @SuppressWarnings("GetClassOnAnnotation") // yes, we really do want the implementation classes + public void testSerialVersionUid() { + Class<? extends Everything> everythingImpl = EVERYTHING_FROM_AUTO.getClass(); + Class<? extends Everything> everythingFromCollectionsImpl = + EVERYTHING_FROM_AUTO_COLLECTIONS.getClass(); + assertThat(everythingImpl).isNotEqualTo(everythingFromCollectionsImpl); + long everythingUid = ObjectStreamClass.lookup(everythingImpl).getSerialVersionUID(); + long everythingFromCollectionsUid = + ObjectStreamClass.lookup(everythingFromCollectionsImpl).getSerialVersionUID(); + // Two different implementations of the same annotation with the same members being provided + // (not defaulted) should have the same serialVersionUID. They won't be serial-compatible, of + // course, because their classes are different. So we're really just checking that the + // serialVersionUID depends only on the names and types of those members. + assertThat(everythingFromCollectionsUid).isEqualTo(everythingUid); + Class<? extends StringValues> stringValuesImpl = newStringValues(new String[0]).getClass(); + long stringValuesUid = ObjectStreamClass.lookup(stringValuesImpl).getSerialVersionUID(); + // The previous assertion would be vacuously true if every implementation had the same + // serialVersionUID, so check that that's not true. + assertThat(stringValuesUid).isNotEqualTo(everythingUid); + } + public static class IntList extends ArrayList<Integer> { + private static final long serialVersionUID = 1L; + IntList(Collection<Integer> c) { super(c); } @@ -440,7 +505,7 @@ public class AutoAnnotationTest { IntList intList = new IntList(ImmutableList.of(1, 2, 3)); IntArray actual = newIntArray(intList); IntArray expected = AnnotatedWithIntArray.class.getAnnotation(IntArray.class); - assertEquals(expected, actual); + assertThat(actual).isEqualTo(expected); } @Test @@ -461,8 +526,8 @@ public class AutoAnnotationTest { + "@com.google.auto.value.annotations.StringValues([\"foo\", \"bar\"])" + "]" + ")"; - assertEquals(expected, EVERYTHING_FROM_AUTO.toString()); - assertEquals(expected, EVERYTHING_FROM_AUTO_COLLECTIONS.toString()); + assertThat(EVERYTHING_FROM_AUTO.toString()).isEqualTo(expected); + assertThat(EVERYTHING_FROM_AUTO_COLLECTIONS.toString()).isEqualTo(expected); } @Test @@ -475,7 +540,7 @@ public class AutoAnnotationTest { String expected = "@com.google.auto.value.annotations.StringValues(" + "[\"\", \"\\r\\n\", \"hello, world\", \"Éamonn\", \"\\007\\uffef\"])"; - assertEquals(expected, instance.toString()); + assertThat(instance.toString()).isEqualTo(expected); } @Retention(RetentionPolicy.RUNTIME) @@ -498,7 +563,7 @@ public class AutoAnnotationTest { ImmutableList.<Class<? extends Annotation>>of(AnnotationsAnnotation.class)); AnnotationsAnnotation fromReflect = AnnotatedWithAnnotationsAnnotation.class.getAnnotation(AnnotationsAnnotation.class); - assertEquals(fromReflect, generated); + assertThat(generated).isEqualTo(fromReflect); } @Retention(RetentionPolicy.RUNTIME) @@ -520,7 +585,7 @@ public class AutoAnnotationTest { newClassesAnnotation(Arrays.<Class<?>>asList(AnnotationsAnnotation.class)); ClassesAnnotation fromReflect = AnnotatedWithClassesAnnotation.class.getAnnotation(ClassesAnnotation.class); - assertEquals(fromReflect, generated); + assertThat(generated).isEqualTo(fromReflect); } @Retention(RetentionPolicy.RUNTIME) diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderKotlinTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderKotlinTest.java new file mode 100644 index 00000000..1dc346c9 --- /dev/null +++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderKotlinTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2021 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.value; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class AutoBuilderKotlinTest { + @AutoBuilder(ofClass = KotlinData.class) + abstract static class KotlinDataBuilder { + static KotlinDataBuilder builder() { + return new AutoBuilder_AutoBuilderKotlinTest_KotlinDataBuilder(); + } + + abstract KotlinDataBuilder setInt(int x); + + abstract KotlinDataBuilder setString(String x); + + abstract KotlinData build(); + } + + @Test + public void simpleKotlin() { + KotlinData x = KotlinDataBuilder.builder().setInt(23).setString("skidoo").build(); + assertThat(x.getInt()).isEqualTo(23); + assertThat(x.getString()).isEqualTo("skidoo"); + } + + @AutoBuilder(ofClass = KotlinDataWithNullable.class) + abstract static class KotlinDataWithNullableBuilder { + static KotlinDataWithNullableBuilder builder() { + return new AutoBuilder_AutoBuilderKotlinTest_KotlinDataWithNullableBuilder(); + } + + abstract KotlinDataWithNullableBuilder setAnInt(int x); + + abstract KotlinDataWithNullableBuilder setAString(String x); + + abstract KotlinDataWithNullable build(); + } + + @Test + public void kotlinWithNullable() { + KotlinDataWithNullable empty = KotlinDataWithNullableBuilder.builder().build(); + assertThat(empty.getAnInt()).isNull(); + assertThat(empty.getAString()).isNull(); + + KotlinDataWithNullable notEmpty = + KotlinDataWithNullableBuilder.builder().setAString("answer").setAnInt(42).build(); + assertThat(notEmpty.getAString()).isEqualTo("answer"); + assertThat(notEmpty.getAnInt()).isEqualTo(42); + } + + @AutoBuilder(ofClass = KotlinDataWithDefaults.class) + abstract static class KotlinDataWithDefaultsBuilder { + static KotlinDataWithDefaultsBuilder builder() { + return new AutoBuilder_AutoBuilderKotlinTest_KotlinDataWithDefaultsBuilder(); + } + + abstract KotlinDataWithDefaultsBuilder setAnInt(int x); + + abstract KotlinDataWithDefaultsBuilder setAString(String x); + + abstract KotlinDataWithDefaults build(); + } + + @Test + public void kotlinWithDefaults() { + // AutoBuilder doesn't currently try to give the builder the same defaults as the Kotlin class, + // but we do at least check that the presence of defaults doesn't throw AutoBuilder off. + // When a constructor has default parameters, the Kotlin compiler generates an extra constructor + // with two extra parameters: an int bitmask saying which parameters were defaulted, and a + // DefaultConstructorMarker parameter to avoid clashing with another constructor that might have + // an extra int parameter for some other reason. If AutoBuilder found this constructor it might + // be confused, but fortunately the constructor is marked synthetic, and javax.lang.model + // doesn't show synthetic elements. + KotlinDataWithDefaults x = + KotlinDataWithDefaultsBuilder.builder().setAString("answer").setAnInt(42).build(); + assertThat(x.getAString()).isEqualTo("answer"); + assertThat(x.getAnInt()).isEqualTo(42); + } +} diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java new file mode 100644 index 00000000..952edaac --- /dev/null +++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java @@ -0,0 +1,580 @@ +/* + * Copyright 2021 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.value; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; +import static org.junit.Assert.fail; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import java.io.IOException; +import java.math.BigInteger; +import java.time.LocalTime; +import java.util.AbstractSet; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class AutoBuilderTest { + static class Simple { + private final int anInt; + private final String aString; + + Simple(int anInt, String aString) { + this.anInt = anInt; + this.aString = aString; + } + + static Simple of(int anInt, String aString) { + return new Simple(anInt, aString); + } + + @Override + public boolean equals(Object x) { + if (x instanceof Simple) { + Simple that = (Simple) x; + return this.anInt == that.anInt && Objects.equals(this.aString, that.aString); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(anInt, aString); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("anInt", anInt) + .add("aString", aString) + .toString(); + } + + static Builder builder() { + return new AutoBuilder_AutoBuilderTest_Simple_Builder(); + } + + @AutoBuilder + abstract static class Builder { + abstract Builder setAnInt(int x); + + abstract Builder setAString(String x); + + abstract Simple build(); + } + } + + @Test + public void simple() { + Simple x = Simple.builder().setAnInt(23).setAString("skidoo").build(); + assertThat(x).isEqualTo(new Simple(23, "skidoo")); + } + + @AutoValue + abstract static class SimpleAuto { + abstract int getFoo(); + + abstract String getBar(); + + static Builder builder() { + return new AutoBuilder_AutoBuilderTest_SimpleAuto_Builder(); + } + + // There's no particular reason to do this since @AutoValue.Builder works just as well, but + // let's check anyway. + @AutoBuilder(ofClass = AutoValue_AutoBuilderTest_SimpleAuto.class) + abstract static class Builder { + abstract Builder setFoo(int x); + + abstract Builder setBar(String x); + + abstract AutoValue_AutoBuilderTest_SimpleAuto build(); + } + } + + @Test + public void simpleAuto() { + SimpleAuto x = SimpleAuto.builder().setFoo(23).setBar("skidoo").build(); + assertThat(x.getFoo()).isEqualTo(23); + assertThat(x.getBar()).isEqualTo("skidoo"); + } + + enum Truthiness { + FALSY, + TRUTHY + } + + @interface MyAnnotation { + String value(); + + int DEFAULT_ID = -1; + + int id() default DEFAULT_ID; + + Truthiness DEFAULT_TRUTHINESS = Truthiness.FALSY; + + Truthiness truthiness() default Truthiness.FALSY; + } + + // This method has a parameter for `truthiness`, even though that has a default, but it has no + // parameter for `id`, which also has a default. + @AutoAnnotation + static MyAnnotation myAnnotation(String value, Truthiness truthiness) { + return new AutoAnnotation_AutoBuilderTest_myAnnotation(value, truthiness); + } + + @AutoBuilder(callMethod = "myAnnotation") + interface MyAnnotationBuilder { + MyAnnotationBuilder value(String x); + + MyAnnotationBuilder truthiness(Truthiness x); + + MyAnnotation build(); + } + + static MyAnnotationBuilder myAnnotationBuilder() { + return new AutoBuilder_AutoBuilderTest_MyAnnotationBuilder() + .truthiness(MyAnnotation.DEFAULT_TRUTHINESS); + } + + @Test + public void simpleAutoAnnotation() { + MyAnnotation annotation1 = myAnnotationBuilder().value("foo").build(); + assertThat(annotation1.value()).isEqualTo("foo"); + assertThat(annotation1.id()).isEqualTo(MyAnnotation.DEFAULT_ID); + assertThat(annotation1.truthiness()).isEqualTo(MyAnnotation.DEFAULT_TRUTHINESS); + MyAnnotation annotation2 = + myAnnotationBuilder().value("bar").truthiness(Truthiness.TRUTHY).build(); + assertThat(annotation2.value()).isEqualTo("bar"); + assertThat(annotation2.id()).isEqualTo(MyAnnotation.DEFAULT_ID); + assertThat(annotation2.truthiness()).isEqualTo(Truthiness.TRUTHY); + } + + static class Overload { + final int anInt; + final String aString; + final BigInteger aBigInteger; + + Overload(int anInt, String aString) { + this(anInt, aString, BigInteger.ZERO); + } + + Overload(int anInt, String aString, BigInteger aBigInteger) { + this.anInt = anInt; + this.aString = aString; + this.aBigInteger = aBigInteger; + } + + @Override + public boolean equals(Object x) { + if (x instanceof Overload) { + Overload that = (Overload) x; + return this.anInt == that.anInt + && Objects.equals(this.aString, that.aString) + && Objects.equals(this.aBigInteger, that.aBigInteger); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(anInt, aString, aBigInteger); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("anInt", anInt) + .add("aString", aString) + .add("aBigInteger", aBigInteger) + .toString(); + } + + static Builder1 builder1() { + return new AutoBuilder_AutoBuilderTest_Overload_Builder1(); + } + + static Builder2 builder2() { + return new AutoBuilder_AutoBuilderTest_Overload_Builder2(); + } + + @AutoBuilder + interface Builder1 { + Builder1 setAnInt(int x); + + Builder1 setAString(String x); + + Overload build(); + } + + @AutoBuilder + interface Builder2 { + Builder2 setAnInt(int x); + + Builder2 setAString(String x); + + Builder2 setABigInteger(BigInteger x); + + Overload build(); + } + } + + @Test + public void overloadedConstructor() { + Overload actual1 = Overload.builder1().setAnInt(23).setAString("skidoo").build(); + Overload expected1 = new Overload(23, "skidoo"); + assertThat(actual1).isEqualTo(expected1); + + BigInteger big17 = BigInteger.valueOf(17); + Overload actual2 = + Overload.builder2().setAnInt(17).setAString("17").setABigInteger(big17).build(); + Overload expected2 = new Overload(17, "17", big17); + assertThat(actual2).isEqualTo(expected2); + } + + @AutoBuilder(callMethod = "of", ofClass = Simple.class) + interface SimpleStaticBuilder { + SimpleStaticBuilder anInt(int x); + + SimpleStaticBuilder aString(String x); + + Simple build(); + } + + static SimpleStaticBuilder simpleStaticBuilder() { + return new AutoBuilder_AutoBuilderTest_SimpleStaticBuilder(); + } + + @Test + public void staticMethod() { + Simple actual = simpleStaticBuilder().anInt(17).aString("17").build(); + Simple expected = new Simple(17, "17"); + assertThat(actual).isEqualTo(expected); + } + + // We can't be sure that the java.time package has parameter names, so we use this intermediary. + // Otherwise we could just write @AutoBuilder(callMethod = "of", ofClass = LocalTime.class). + // It's still interesting to test this as a realistic example. + static LocalTime localTimeOf(int hour, int minute, int second, int nanoOfSecond) { + return LocalTime.of(hour, minute, second, nanoOfSecond); + } + + static LocalTimeBuilder localTimeBuilder() { + return new AutoBuilder_AutoBuilderTest_LocalTimeBuilder().nanoOfSecond(0); + } + + @AutoBuilder(callMethod = "localTimeOf") + interface LocalTimeBuilder { + LocalTimeBuilder hour(int hour); + + LocalTimeBuilder minute(int minute); + + LocalTimeBuilder second(int second); + + LocalTimeBuilder nanoOfSecond(int nanoOfSecond); + + LocalTime build(); + } + + @Test + public void staticMethodOfContainingClass() { + LocalTime actual = localTimeBuilder().hour(12).minute(34).second(56).build(); + LocalTime expected = LocalTime.of(12, 34, 56); + assertThat(actual).isEqualTo(expected); + } + + @Test + public void missingRequiredProperty() { + // This test is compiled at source level 7 by CompileWithEclipseTest, so we can't use + // assertThrows with a lambda. + try { + localTimeBuilder().hour(12).minute(34).build(); + fail(); + } catch (IllegalStateException e) { + assertThat(e).hasMessageThat().isEqualTo("Missing required properties: second"); + } + } + + static void throwException() throws IOException { + throw new IOException("oops"); + } + + static ThrowExceptionBuilder throwExceptionBuilder() { + return new AutoBuilder_AutoBuilderTest_ThrowExceptionBuilder(); + } + + @AutoBuilder(callMethod = "throwException") + interface ThrowExceptionBuilder { + void build() throws IOException; + } + + @Test + public void emptyBuilderThrowsException() { + try { + throwExceptionBuilder().build(); + fail(); + } catch (IOException expected) { + assertThat(expected).hasMessageThat().isEqualTo("oops"); + } + } + + static class ListContainer { + private final ImmutableList<String> list; + + ListContainer(ImmutableList<String> list) { + this.list = checkNotNull(list); + } + + @Override + public boolean equals(Object o) { + return o instanceof ListContainer && list.equals(((ListContainer) o).list); + } + + @Override + public int hashCode() { + return list.hashCode(); + } + + @Override + public String toString() { + return list.toString(); + } + + static Builder builder() { + return new AutoBuilder_AutoBuilderTest_ListContainer_Builder(); + } + + @AutoBuilder + interface Builder { + Builder setList(Iterable<String> list); + + ImmutableList.Builder<String> listBuilder(); + + ListContainer build(); + } + } + + @Test + public void propertyBuilder() { + ListContainer expected = new ListContainer(ImmutableList.of("one", "two", "three")); + ListContainer actual1 = + ListContainer.builder().setList(ImmutableList.of("one", "two", "three")).build(); + assertThat(actual1).isEqualTo(expected); + + ListContainer.Builder builder2 = ListContainer.builder(); + builder2.listBuilder().add("one", "two", "three"); + assertThat(builder2.build()).isEqualTo(expected); + + ListContainer.Builder builder3 = ListContainer.builder().setList(ImmutableList.of("one")); + builder3.listBuilder().add("two", "three"); + assertThat(builder3.build()).isEqualTo(expected); + + ListContainer.Builder builder4 = ListContainer.builder(); + builder4.listBuilder(); + try { + builder4.setList(ImmutableList.of("one", "two", "three")); + fail(); + } catch (IllegalStateException e) { + assertThat(e).hasMessageThat().isEqualTo("Cannot set list after calling listBuilder()"); + } + } + + static <T> String concatList(ImmutableList<T> list) { + // We're avoiding streams for now so we compile this in Java 7 mode in CompileWithEclipseTest. + StringBuilder sb = new StringBuilder(); + for (T element : list) { + sb.append(element); + } + return sb.toString(); + } + + @AutoBuilder(callMethod = "concatList") + interface ConcatListCaller<T> { + ImmutableList.Builder<T> listBuilder(); + + String call(); + } + + @Test + public void propertyBuilderWithoutSetter() { + ConcatListCaller<Integer> caller = new AutoBuilder_AutoBuilderTest_ConcatListCaller<>(); + caller.listBuilder().add(1, 1, 2, 3, 5, 8); + String s = caller.call(); + assertThat(s).isEqualTo("112358"); + } + + static <K, V extends Number> Map<K, V> singletonMap(K key, V value) { + return Collections.singletonMap(key, value); + } + + static <K, V extends Number> SingletonMapBuilder<K, V> singletonMapBuilder() { + return new AutoBuilder_AutoBuilderTest_SingletonMapBuilder<>(); + } + + @AutoBuilder(callMethod = "singletonMap") + interface SingletonMapBuilder<K, V extends Number> { + SingletonMapBuilder<K, V> key(K key); + + SingletonMapBuilder<K, V> value(V value); + + Map<K, V> build(); + } + + @Test + public void genericStaticMethod() { + ImmutableMap<String, Integer> expected = ImmutableMap.of("17", 17); + SingletonMapBuilder<String, Integer> builder = singletonMapBuilder(); + Map<String, Integer> actual = builder.key("17").value(17).build(); + assertThat(actual).isEqualTo(expected); + } + + static class SingletonSet<E> extends AbstractSet<E> { + private final E element; + + SingletonSet(E element) { + this.element = element; + } + + @Override + public int size() { + return 1; + } + + @Override + public Iterator<E> iterator() { + return new Iterator<E>() { + private boolean first = true; + + @Override + public boolean hasNext() { + return first; + } + + @Override + public E next() { + if (!first) { + throw new NoSuchElementException(); + } + first = false; + return element; + } + }; + } + } + + @AutoBuilder(ofClass = SingletonSet.class) + interface SingletonSetBuilder<E> { + SingletonSetBuilder<E> setElement(E element); + + SingletonSet<E> build(); + } + + static <E> SingletonSetBuilder<E> singletonSetBuilder() { + return new AutoBuilder_AutoBuilderTest_SingletonSetBuilder<>(); + } + + @Test + public void genericClass() { + ImmutableSet<String> expected = ImmutableSet.of("foo"); + SingletonSetBuilder<String> builder = singletonSetBuilder(); + Set<String> actual = builder.setElement("foo").build(); + assertThat(actual).isEqualTo(expected); + } + + static class TypedSingletonSet<E> extends SingletonSet<E> { + private final Class<?> type; + + <T extends E> TypedSingletonSet(T element, Class<T> type) { + super(element); + this.type = type; + } + + @Override + public String toString() { + return type.getName() + super.toString(); + } + } + + @AutoBuilder(ofClass = TypedSingletonSet.class) + interface TypedSingletonSetBuilder<E, T extends E> { + TypedSingletonSetBuilder<E, T> setElement(T element); + + TypedSingletonSetBuilder<E, T> setType(Class<T> type); + + TypedSingletonSet<E> build(); + } + + static <E, T extends E> TypedSingletonSetBuilder<E, T> typedSingletonSetBuilder() { + return new AutoBuilder_AutoBuilderTest_TypedSingletonSetBuilder<>(); + } + + @Test + public void genericClassWithGenericConstructor() { + TypedSingletonSetBuilder<CharSequence, String> builder = typedSingletonSetBuilder(); + TypedSingletonSet<CharSequence> set = builder.setElement("foo").setType(String.class).build(); + assertThat(set.toString()).isEqualTo("java.lang.String[foo]"); + } + + static <T> ImmutableList<T> pair(T first, T second) { + return ImmutableList.of(first, second); + } + + @AutoBuilder(callMethod = "pair") + interface PairBuilder<T> { + PairBuilder<T> setFirst(T x); + + T getFirst(); + + PairBuilder<T> setSecond(T x); + + Optional<T> getSecond(); + + ImmutableList<T> build(); + } + + static <T> PairBuilder<T> pairBuilder() { + return new AutoBuilder_AutoBuilderTest_PairBuilder<>(); + } + + @Test + public void genericGetters() { + PairBuilder<Number> builder = pairBuilder(); + assertThat(builder.getSecond()).isEmpty(); + builder.setSecond(2); + assertThat(builder.getSecond()).hasValue(2); + try { + builder.getFirst(); + fail(); + } catch (IllegalStateException expected) { + } + builder.setFirst(1.0); + assertThat(builder.getFirst()).isEqualTo(1.0); + assertThat(builder.build()).containsExactly(1.0, 2).inOrder(); + } +} diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoOneOfTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoOneOfTest.java index 1b587282..ee337409 100644 --- a/value/src/it/functional/src/test/java/com/google/auto/value/AutoOneOfTest.java +++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoOneOfTest.java @@ -499,10 +499,7 @@ public class AutoOneOfTest { ArrayValue string = ArrayValue.ofString("foo"); ArrayValue ints1 = ArrayValue.ofInts(new int[] {17, 23}); ArrayValue ints2 = ArrayValue.ofInts(new int[] {17, 23}); - new EqualsTester() - .addEqualityGroup(string) - .addEqualityGroup(ints1, ints2) - .testEquals(); + new EqualsTester().addEqualityGroup(string).addEqualityGroup(ints1, ints2).testEquals(); } @Retention(RetentionPolicy.RUNTIME) @@ -560,8 +557,11 @@ public class AutoOneOfTest { @AutoOneOf(MaybeEmpty.Kind.class) public abstract static class MaybeEmpty implements Serializable { + private static final long serialVersionUID = 1L; + public enum Kind { - EMPTY, STRING, + EMPTY, + STRING, } public abstract Kind getKind(); diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueJava8Test.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueJava8Test.java index 10812f8d..3f4e9bf5 100644 --- a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueJava8Test.java +++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueJava8Test.java @@ -15,9 +15,11 @@ */ package com.google.auto.value; +import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static com.google.common.truth.Truth8.assertThat; +import static com.google.common.truth.TruthJUnit.assume; import static com.google.testing.compile.CompilationSubject.assertThat; import static org.junit.Assert.assertThrows; import static org.junit.Assume.assumeTrue; @@ -53,6 +55,7 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.util.ElementFilter; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; +import org.junit.AssumptionViolatedException; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; @@ -153,6 +156,19 @@ public class AutoValueJava8Test { .isEqualTo("NullableProperties{nullableString=null, randomInt=23}"); } + @Test + public void testEqualsParameterIsAnnotated() throws NoSuchMethodException { + // Sadly we can't rely on JDK 8 to handle type annotations correctly. + // Some versions do, some don't. So skip the test unless we are on at least JDK 9. + double javaVersion = Double.parseDouble(JAVA_SPECIFICATION_VERSION.value()); + assume().that(javaVersion).isAtLeast(9.0); + Method equals = + NullableProperties.create(null, 23).getClass().getMethod("equals", Object.class); + AnnotatedType[] parameterTypes = equals.getAnnotatedParameterTypes(); + assertThat(parameterTypes).hasLength(1); + assertThat(parameterTypes[0].getAnnotation(Nullable.class)).isNotNull(); + } + @AutoAnnotation static Nullable nullable() { return new AutoAnnotation_AutoValueJava8Test_nullable(); @@ -162,9 +178,7 @@ public class AutoValueJava8Test { public void testNullablePropertyImplementationIsNullable() throws NoSuchMethodException { Method method = AutoValue_AutoValueJava8Test_NullableProperties.class.getDeclaredMethod("nullableString"); - assertThat(method.getAnnotatedReturnType().getAnnotations()) - .asList() - .contains(nullable()); + assertThat(method.getAnnotatedReturnType().getAnnotations()).asList().contains(nullable()); } @Test @@ -198,8 +212,9 @@ public class AutoValueJava8Test { @Test public void testExcludedNullablePropertyImplementation() throws NoSuchMethodException { - Method method = AutoValue_AutoValueJava8Test_NullablePropertiesNotCopied.class - .getDeclaredMethod("nullableString"); + Method method = + AutoValue_AutoValueJava8Test_NullablePropertiesNotCopied.class.getDeclaredMethod( + "nullableString"); assertThat(method.getAnnotatedReturnType().getAnnotations()) .asList() .doesNotContain(nullable()); @@ -536,6 +551,35 @@ public class AutoValueJava8Test { } } + @AutoValue + abstract static class NoNullableRef { + abstract String foo(); + + static NoNullableRef of(String foo) { + return new AutoValue_AutoValueJava8Test_NoNullableRef(foo); + } + } + + // Tests that we generate equals(@Nullable x) using JSpecify @Nullable if that annotation is + // available and there is no other @Nullable type annotation mentioned in the @AutoValue class. + // If there *are* other @Nullable type annotations, other test methods here will check that they + // are used instead. + @Test + public void testDefaultToJSpecifyNullable() throws ReflectiveOperationException { + Class<? extends Annotation> jspecifyNullable; + try { + // We write this using .concat in order to hide it from rewriting rules. + jspecifyNullable = + Class.forName("org".concat(".jspecify.nullness.Nullable")).asSubclass(Annotation.class); + } catch (ClassNotFoundException e) { + throw new AssumptionViolatedException("No JSpecify @Nullable available", e); + } + Class<? extends NoNullableRef> autoValueImpl = NoNullableRef.of("foo").getClass(); + Method equals = autoValueImpl.getDeclaredMethod("equals", Object.class); + assertThat(equals.getAnnotatedParameterTypes()[0].isAnnotationPresent(jspecifyNullable)) + .isTrue(); + } + @Test public void testBuilderWithUnprefixedGetter() { assumeTrue(javacHandlesTypeAnnotationsCorrectly); @@ -571,7 +615,7 @@ public class AutoValueJava8Test { public abstract static class BuilderWithPrefixedGetters<T extends Comparable<T>> { public abstract ImmutableList<T> getList(); - public abstract T getT(); + public abstract @Nullable T getT(); @SuppressWarnings("mutable") public abstract int @Nullable [] getInts(); @@ -586,7 +630,7 @@ public class AutoValueJava8Test { public abstract static class Builder<T extends Comparable<T>> { public abstract Builder<T> setList(ImmutableList<T> list); - public abstract Builder<T> setT(T t); + public abstract Builder<T> setT(@Nullable T t); public abstract Builder<T> setInts(int[] ints); @@ -761,6 +805,7 @@ public class AutoValueJava8Test { @AutoValue.Builder abstract static class Builder { abstract Builder maybeJustMaybe(Optional<String> maybe); + abstract OptionalOptional build(); } } @@ -795,6 +840,7 @@ public class AutoValueJava8Test { @AutoValue.Builder abstract static class Builder { abstract Builder setPredicate(Predicate<? super Integer> predicate); + abstract OptionalExtends build(); } } diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java index 346bc53a..3a7e7bc4 100644 --- a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java +++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java @@ -34,6 +34,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.ImmutableTable; +import com.google.common.collect.Ordering; import com.google.common.testing.EqualsTester; import com.google.common.testing.SerializableTester; import java.io.ObjectStreamClass; @@ -52,6 +53,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -60,6 +62,7 @@ import java.util.NavigableMap; import java.util.NavigableSet; import java.util.NoSuchElementException; import java.util.SortedMap; +import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import javax.annotation.Nullable; @@ -70,6 +73,7 @@ import org.junit.runners.JUnit4; /** @author emcmanus@google.com (Éamonn McManus) */ @RunWith(JUnit4.class) +@SuppressWarnings({"AutoValueImmutableFields", "AutoValueFinalMethods", "TypeNameShadowing"}) public class AutoValueTest { private static boolean omitIdentifiers; @@ -180,12 +184,15 @@ public class AutoValueTest { @AutoValue abstract static class StrangeGetters { abstract int get1st(); + abstract int get_1st(); // by default we'll use _1st where identifiers are needed, so foil that. @AutoValue.Builder abstract static class Builder { abstract Builder set1st(int x); + abstract Builder set_1st(int x); + abstract StrangeGetters build(); } @@ -284,6 +291,8 @@ public class AutoValueTest { @AutoValue public abstract static class Serialize implements Serializable { + private static final long serialVersionUID = 1L; + public abstract int integer(); public abstract String string(); @@ -845,6 +854,8 @@ public class AutoValueTest { @Test public void testGenericClassWithHairyBounds() throws Exception { class ComparableList<E> extends ArrayList<E> implements Comparable<ComparableList<E>> { + private static final long serialVersionUID = 1L; + @Override public int compareTo(ComparableList<E> list) { throw new UnsupportedOperationException(); @@ -1262,7 +1273,7 @@ public class AutoValueTest { } @AutoValue - public abstract static class ComplexInheritance extends AbstractBase implements A, B { + public abstract static class ComplexInheritance extends AbstractBase implements IntfA, IntfB { public static ComplexInheritance create(String name) { return new AutoValue_AutoValueTest_ComplexInheritance(name); } @@ -1277,9 +1288,9 @@ public class AutoValueTest { } } - interface A extends Base {} + interface IntfA extends Base {} - interface B extends Base {} + interface IntfB extends Base {} interface Base { int answer(); @@ -1352,7 +1363,7 @@ public class AutoValueTest { } @AutoValue - public abstract static class InheritTwice implements A, B { + public abstract static class InheritTwice implements IntfA, IntfB { public static InheritTwice create(int answer) { return new AutoValue_AutoValueTest_InheritTwice(answer); } @@ -1664,6 +1675,13 @@ public class AutoValueTest { .build(); assertThat(suppliedDirectly.optionalString()).hasValue("foo"); assertThat(suppliedDirectly.optionalInteger()).hasValue(23); + + try { + // The parameter is not marked @Nullable so this should fail. + OptionalPropertiesWithBuilder.builder().setOptionalString((String) null); + fail(); + } catch (NullPointerException expected) { + } } @AutoValue @@ -1874,6 +1892,40 @@ public class AutoValueTest { assertEquals((Integer) 17, instance3.u()); } + public interface ToBuilder<BuilderT> { + BuilderT toBuilder(); + } + + @AutoValue + public abstract static class InheritedToBuilder<T, U> + implements ToBuilder<InheritedToBuilder.Builder<T, U>> { + + public abstract T t(); + + public abstract U u(); + + public static <T, U> Builder<T, U> builder() { + return new AutoValue_AutoValueTest_InheritedToBuilder.Builder<T, U>(); + } + + @AutoValue.Builder + public abstract static class Builder<T, U> { + public abstract Builder<T, U> setT(T t); + + public abstract Builder<T, U> setU(U u); + + public abstract InheritedToBuilder<T, U> build(); + } + } + + @Test + public void testInheritedToBuilder() { + InheritedToBuilder<Integer, String> x = + InheritedToBuilder.<Integer, String>builder().setT(17).setU("wibble").build(); + InheritedToBuilder<Integer, String> y = x.toBuilder().setT(23).build(); + assertThat(y.u()).isEqualTo("wibble"); + } + @AutoValue public abstract static class BuilderWithSet<T extends Comparable<T>> { public abstract List<T> list(); @@ -2115,6 +2167,37 @@ public class AutoValueTest { } @AutoValue + public abstract static class BuilderWithPrefixedGettersAndUnprefixedSetters { + public abstract String getOAuth(); + + public abstract String getOBrien(); + + public static Builder builder() { + return new AutoValue_AutoValueTest_BuilderWithPrefixedGettersAndUnprefixedSetters.Builder(); + } + + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder oAuth(String x); + + public abstract Builder OBrien(String x); + + public abstract BuilderWithPrefixedGettersAndUnprefixedSetters build(); + } + } + + @Test + public void testBuilderWithPrefixedGetterAndUnprefixedSetter() { + BuilderWithPrefixedGettersAndUnprefixedSetters x = + BuilderWithPrefixedGettersAndUnprefixedSetters.builder() + .oAuth("OAuth") + .OBrien("Flann") + .build(); + assertThat(x.getOAuth()).isEqualTo("OAuth"); + assertThat(x.getOBrien()).isEqualTo("Flann"); + } + + @AutoValue public abstract static class BuilderWithPropertyBuilders<FooT extends Comparable<FooT>> { public abstract ImmutableList<FooT> getFoos(); @@ -2223,6 +2306,7 @@ public class AutoValueTest { @AutoValue.Builder abstract static class Builder { abstract ImmutableList.Builder<String> listBuilder(); + abstract PropertyBuilderInheritsType build(); } } @@ -2820,6 +2904,8 @@ public class AutoValueTest { } public static class MyMap<K, V> extends HashMap<K, V> { + private static final long serialVersionUID = 1L; + public MyMap() {} public MyMap(Map<K, V> map) { @@ -2828,6 +2914,8 @@ public class AutoValueTest { } public static class MyMapBuilder<K, V> extends LinkedHashMap<K, V> { + private static final long serialVersionUID = 1L; + public MyMapBuilder() {} public MyMapBuilder(Map<K, V> map) { @@ -2873,6 +2961,8 @@ public class AutoValueTest { } public static class MyStringMap<V> extends MyMap<String, V> { + private static final long serialVersionUID = 1L; + public MyStringMap() {} public MyStringMap(Map<String, V> map) { @@ -2885,6 +2975,8 @@ public class AutoValueTest { } public static class MyStringMapBuilder<V> extends MyMapBuilder<String, V> { + private static final long serialVersionUID = 1L; + public MyStringMapBuilder() {} public MyStringMapBuilder(Map<String, V> map) { @@ -3258,6 +3350,7 @@ public class AutoValueTest { @AutoValue.Builder abstract static class Builder { abstract Builder setMetrics(ImmutableSet<? extends Number> metrics); + abstract GenericExtends build(); } } @@ -3282,6 +3375,7 @@ public class AutoValueTest { @AutoValue.Builder abstract static class Builder { abstract Builder setList(List<String> list); + abstract Child build(); } } @@ -3325,14 +3419,18 @@ public class AutoValueTest { @SuppressWarnings("ClassCanBeStatic") static class OuterWithTypeParam<T extends Number> { class InnerWithTypeParam<U> {} + class InnerWithoutTypeParam {} + static class Nested {} } @AutoValue abstract static class Nesty { abstract OuterWithTypeParam<Double>.InnerWithTypeParam<String> innerWithTypeParam(); + abstract OuterWithTypeParam<Double>.InnerWithoutTypeParam innerWithoutTypeParam(); + abstract OuterWithTypeParam.Nested nested(); static Builder builder() { @@ -3343,8 +3441,11 @@ public class AutoValueTest { abstract static class Builder { abstract Builder setInnerWithTypeParam( OuterWithTypeParam<Double>.InnerWithTypeParam<String> x); + abstract Builder setInnerWithoutTypeParam(OuterWithTypeParam<Double>.InnerWithoutTypeParam x); + abstract Builder setNested(OuterWithTypeParam.Nested x); + abstract Nesty build(); } } @@ -3353,11 +3454,12 @@ public class AutoValueTest { public void outerWithTypeParam() throws ReflectiveOperationException { @SuppressWarnings("UseDiamond") // Currently we compile this with -source 6 in the Eclipse test. OuterWithTypeParam<Double> outer = new OuterWithTypeParam<Double>(); - Nesty nesty = Nesty.builder() - .setInnerWithTypeParam(outer.new InnerWithTypeParam<String>()) - .setInnerWithoutTypeParam(outer.new InnerWithoutTypeParam()) - .setNested(new OuterWithTypeParam.Nested()) - .build(); + Nesty nesty = + Nesty.builder() + .setInnerWithTypeParam(outer.new InnerWithTypeParam<String>()) + .setInnerWithoutTypeParam(outer.new InnerWithoutTypeParam()) + .setNested(new OuterWithTypeParam.Nested()) + .build(); Type originalReturnType = Nesty.class.getDeclaredMethod("innerWithTypeParam").getGenericReturnType(); Type generatedReturnType = @@ -3383,6 +3485,7 @@ public class AutoValueTest { @MyAnnotation("thing") abstract static class Builder { abstract Builder setFoo(String x); + abstract BuilderAnnotationsNotCopied build(); } } @@ -3407,6 +3510,7 @@ public class AutoValueTest { @MyAnnotation("thing") abstract static class Builder { abstract Builder setFoo(String x); + abstract BuilderAnnotationsCopied build(); } } @@ -3417,4 +3521,106 @@ public class AutoValueTest { assertThat(builder.getClass().getAnnotations()).asList().containsExactly(myAnnotation("thing")); assertThat(builder.setFoo("foo").build().foo()).isEqualTo("foo"); } + + @AutoValue + @AutoValue.CopyAnnotations + @SuppressWarnings({"rawtypes", "unchecked"}) // deliberately checking handling of raw types + abstract static class DataWithSortedCollectionBuilders<K, V> { + abstract ImmutableSortedMap<K, V> anImmutableSortedMap(); + + abstract ImmutableSortedSet<V> anImmutableSortedSet(); + + abstract ImmutableSortedMap<Integer, V> nonGenericImmutableSortedMap(); + + abstract ImmutableSortedSet rawImmutableSortedSet(); + + abstract DataWithSortedCollectionBuilders.Builder<K, V> toBuilder(); + + static <K, V> DataWithSortedCollectionBuilders.Builder<K, V> builder() { + return new AutoValue_AutoValueTest_DataWithSortedCollectionBuilders.Builder<K, V>(); + } + + @AutoValue.Builder + abstract static class Builder<K, V> { + abstract DataWithSortedCollectionBuilders.Builder<K, V> anImmutableSortedMap( + SortedMap<K, V> anImmutableSortedMap); + + abstract ImmutableSortedMap.Builder<K, V> anImmutableSortedMapBuilder( + Comparator<K> keyComparator); + + abstract DataWithSortedCollectionBuilders.Builder<K, V> anImmutableSortedSet( + SortedSet<V> anImmutableSortedSet); + + abstract ImmutableSortedSet.Builder<V> anImmutableSortedSetBuilder(Comparator<V> comparator); + + abstract ImmutableSortedMap.Builder<Integer, V> nonGenericImmutableSortedMapBuilder( + Comparator<Integer> keyComparator); + + abstract ImmutableSortedSet.Builder rawImmutableSortedSetBuilder(Comparator comparator); + + abstract DataWithSortedCollectionBuilders<K, V> build(); + } + } + + @Test + @SuppressWarnings({"rawtypes", "unchecked"}) // deliberately checking handling of raw types + public void shouldGenerateBuildersWithComparators() { + Comparator<String> stringComparator = + new Comparator<String>() { + @Override + public int compare(String left, String right) { + return left.compareTo(right); + } + }; + + Comparator<Integer> intComparator = + new Comparator<Integer>() { + @Override + public int compare(Integer o1, Integer o2) { + return o1 - o2; + } + }; + + Comparator comparator = + new Comparator() { + @Override + public int compare(Object left, Object right) { + return String.valueOf(left).compareTo(String.valueOf(right)); + } + }; + + AutoValueTest.DataWithSortedCollectionBuilders.Builder<String, Integer> builder = + AutoValueTest.DataWithSortedCollectionBuilders.builder(); + + builder + .anImmutableSortedMapBuilder(stringComparator) + .put("Charlie", 1) + .put("Alfa", 2) + .put("Bravo", 3); + builder.anImmutableSortedSetBuilder(intComparator).add(1, 5, 9, 3); + builder.nonGenericImmutableSortedMapBuilder(intComparator).put(9, 99).put(1, 11).put(3, 33); + builder.rawImmutableSortedSetBuilder(comparator).add("Bravo", "Charlie", "Alfa"); + + AutoValueTest.DataWithSortedCollectionBuilders<String, Integer> data = builder.build(); + + AutoValueTest.DataWithSortedCollectionBuilders.Builder<String, Integer> copiedBuilder = + data.toBuilder(); + AutoValueTest.DataWithSortedCollectionBuilders<String, Integer> copiedData = + copiedBuilder.build(); + + assertThat(data.anImmutableSortedMap().keySet()) + .containsExactly("Alfa", "Bravo", "Charlie") + .inOrder(); + assertThat(data.anImmutableSortedSet()).containsExactly(1, 3, 5, 9).inOrder(); + assertThat(data.nonGenericImmutableSortedMap().keySet()).containsExactly(1, 3, 9).inOrder(); + assertThat(data.rawImmutableSortedSet()).containsExactly("Alfa", "Bravo", "Charlie").inOrder(); + + assertThat(copiedData).isEqualTo(data); + + try { + builder.anImmutableSortedMapBuilder(Ordering.from(stringComparator).reverse()); + fail("Calling property builder method a second time should have failed"); + } catch (IllegalStateException expected) { + } + } } diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java index 15188274..ca10fb45 100644 --- a/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java +++ b/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import com.google.auto.value.processor.AutoAnnotationProcessor; +import com.google.auto.value.processor.AutoBuilderProcessor; import com.google.auto.value.processor.AutoOneOfProcessor; import com.google.auto.value.processor.AutoValueProcessor; import com.google.common.collect.ImmutableList; @@ -58,25 +59,28 @@ public class CompileWithEclipseTest { @BeforeClass public static void setSourceRoot() { assertWithMessage("basedir property must be set - test must be run from Maven") - .that(SOURCE_ROOT).isNotNull(); + .that(SOURCE_ROOT) + .isNotNull(); } public @Rule TemporaryFolder tmp = new TemporaryFolder(); private static final ImmutableSet<String> IGNORED_TEST_FILES = - ImmutableSet.of("AutoValueNotEclipseTest.java", "CompileWithEclipseTest.java"); + ImmutableSet.of( + "AutoValueNotEclipseTest.java", "CompileWithEclipseTest.java", "GradleTest.java"); private static final Predicate<File> JAVA_FILE = f -> f.getName().endsWith(".java") && !IGNORED_TEST_FILES.contains(f.getName()); private static final Predicate<File> JAVA8_TEST = - f -> f.getName().equals("AutoValueJava8Test.java") - || f.getName().equals("AutoOneOfJava8Test.java") - || f.getName().equals("EmptyExtension.java"); + f -> + f.getName().equals("AutoValueJava8Test.java") + || f.getName().equals("AutoOneOfJava8Test.java") + || f.getName().equals("EmptyExtension.java"); @Test - public void compileWithEclipseJava6() throws Exception { - compileWithEclipse("6", JAVA_FILE.and(JAVA8_TEST.negate())); + public void compileWithEclipseJava7() throws Exception { + compileWithEclipse("7", JAVA_FILE.and(JAVA8_TEST.negate())); } @Test @@ -103,17 +107,28 @@ public class CompileWithEclipseTest { // fileManager.getLocation(SYSTEM_MODULES). File rtJar = new File(JAVA_HOME.value() + "/lib/rt.jar"); if (rtJar.exists()) { - List<File> bootClassPath = ImmutableList.<File>builder() - .add(rtJar) - .addAll(fileManager.getLocation(StandardLocation.PLATFORM_CLASS_PATH)) - .build(); + List<File> bootClassPath = + ImmutableList.<File>builder() + .add(rtJar) + .addAll(fileManager.getLocation(StandardLocation.PLATFORM_CLASS_PATH)) + .build(); fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, bootClassPath); } Iterable<? extends JavaFileObject> sourceFileObjects = fileManager.getJavaFileObjectsFromFiles(sources); String outputDir = tmp.getRoot().toString(); ImmutableList<String> options = - ImmutableList.of("-d", outputDir, "-s", outputDir, "-source", version, "-target", version); + ImmutableList.of( + "-d", + outputDir, + "-s", + outputDir, + "-source", + version, + "-target", + version, + "-warn:-warningToken,-intfAnnotation", + "-Acom.google.auto.value.AutoBuilderIsUnstable"); JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, options, null, sourceFileObjects); // Explicitly supply an empty list of extensions for AutoValueProcessor, because otherwise this @@ -121,7 +136,10 @@ public class CompileWithEclipseTest { AutoValueProcessor autoValueProcessor = new AutoValueProcessor(ImmutableList.of()); ImmutableList<? extends Processor> processors = ImmutableList.of( - autoValueProcessor, new AutoOneOfProcessor(), new AutoAnnotationProcessor()); + autoValueProcessor, + new AutoOneOfProcessor(), + new AutoAnnotationProcessor(), + new AutoBuilderProcessor()); task.setProcessors(processors); assertWithMessage("Compilation should succeed").that(task.call()).isTrue(); } diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/GradleTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/GradleTest.java new file mode 100644 index 00000000..f4eb5388 --- /dev/null +++ b/value/src/it/functional/src/test/java/com/google/auto/value/GradleTest.java @@ -0,0 +1,187 @@ +/* + * Copyright 2021 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.value; + +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.collect.ImmutableList; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.gradle.util.GradleVersion; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class GradleTest { + @Rule public TemporaryFolder fakeProject = new TemporaryFolder(); + + private static final String BUILD_GRADLE_TEXT = + String.join( + "\n", + "plugins {", + " id 'java-library'", + "}", + "repositories {", + " maven { url = uri('${localRepository}') }", + "}", + "dependencies {", + " compileOnlyApi " + + " 'com.google.auto.value:auto-value-annotations:${autoValueVersion}'", + " annotationProcessor 'com.google.auto.value:auto-value:${autoValueVersion}'", + "}"); + + private static final String FOO_TEXT = + String.join( + "\n", + "package com.example;", + "", + "import com.google.auto.value.AutoValue;", + "", + "@AutoValue", + "abstract class Foo {", + " abstract String bar();", + "", + " static Foo of(String bar) {", + " return new AutoValue_Foo(bar);", + " }", + "}"); + + private static final Optional<File> GRADLE_INSTALLATION = getGradleInstallation(); + + @Test + public void basic() throws IOException { + String autoValueVersion = System.getProperty("autoValueVersion"); + assertThat(autoValueVersion).isNotNull(); + String localRepository = System.getProperty("localRepository"); + assertThat(localRepository).isNotNull(); + + // Set up the fake Gradle project. + String buildGradleText = expandSystemProperties(BUILD_GRADLE_TEXT); + writeFile(fakeProject.newFile("build.gradle").toPath(), buildGradleText); + Path srcDir = fakeProject.newFolder("src", "main", "java", "com", "example").toPath(); + writeFile(srcDir.resolve("Foo.java"), FOO_TEXT); + + // Build it the first time. + BuildResult result1 = buildFakeProject(); + assertThat(result1.getOutput()) + .contains( + "Full recompilation is required because no incremental change information is" + + " available"); + Path output = + fakeProject + .getRoot() + .toPath() + .resolve("build/classes/java/main/com/example/AutoValue_Foo.class"); + assertThat(Files.exists(output)).isTrue(); + + // Add a source file to the project. + String barText = FOO_TEXT.replace("Foo", "Bar"); + Path barFile = srcDir.resolve("Bar.java"); + writeFile(barFile, barText); + + // Build it a second time. + BuildResult result2 = buildFakeProject(); + assertThat(result2.getOutput()).doesNotContain("Full recompilation is required"); + + // Remove the second source file and build a third time. If incremental annotation processing + // is not working, this will produce a message like this: + // Full recompilation is required because com.google.auto.value.processor.AutoValueProcessor + // is not incremental + Files.delete(barFile); + BuildResult result3 = buildFakeProject(); + assertThat(result3.getOutput()).doesNotContain("Full recompilation is required"); + } + + private BuildResult buildFakeProject() throws IOException { + GradleRunner runner = + GradleRunner.create() + .withProjectDir(fakeProject.getRoot()) + .withArguments("--info", "compileJava"); + if (GRADLE_INSTALLATION.isPresent()) { + runner.withGradleInstallation(GRADLE_INSTALLATION.get()); + } else { + runner.withGradleVersion(GradleVersion.current().getVersion()); + } + return runner.build(); + } + + private static Optional<File> getGradleInstallation() { + String gradleHome = System.getenv("GRADLE_HOME"); + if (gradleHome != null) { + File gradleHomeFile = new File(gradleHome); + if (gradleHomeFile.isDirectory()) { + return Optional.of(new File(gradleHome)); + } + } + try { + Path gradleExecutable = Paths.get("/usr/bin/gradle"); + Path gradleLink = gradleExecutable.resolveSibling(Files.readSymbolicLink(gradleExecutable)); + if (!gradleLink.endsWith("bin/gradle")) { + return Optional.empty(); + } + Path installationPath = gradleLink.getParent().getParent(); + if (!Files.isDirectory(installationPath)) { + return Optional.empty(); + } + Optional<Path> coreJar; + Pattern corePattern = Pattern.compile("gradle-core-([0-9]+)\\..*\\.jar"); + try (Stream<Path> files = Files.walk(installationPath.resolve("lib"))) { + coreJar = + files + .filter( + p -> { + Matcher matcher = corePattern.matcher(p.getFileName().toString()); + if (matcher.matches()) { + int version = Integer.parseInt(matcher.group(1)); + if (version >= 5) { + return true; + } + } + return false; + }) + .findFirst(); + } + return coreJar.map(unused -> installationPath.toFile()); + } catch (IOException e) { + return Optional.empty(); + } + } + + private static String expandSystemProperties(String s) { + for (String name : System.getProperties().stringPropertyNames()) { + String value = System.getProperty(name); + s = s.replace("${" + name + "}", value); + } + return s; + } + + private static void writeFile(Path file, String text) throws IOException { + Files.write(file, ImmutableList.of(text), UTF_8); + } +} diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/KotlinData.kt b/value/src/it/functional/src/test/java/com/google/auto/value/KotlinData.kt new file mode 100644 index 00000000..f3318890 --- /dev/null +++ b/value/src/it/functional/src/test/java/com/google/auto/value/KotlinData.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2021 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.value + +data class KotlinData(val int: Int, val string: String) + +data class KotlinDataWithNullable(val anInt: Int?, val aString: String?) + +data class KotlinDataWithDefaults(val anInt: Int = 23, val aString: String = "skidoo") diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/gwt/EmptyExtension.java b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/EmptyExtension.java index 0476906c..e6f7abf7 100644 --- a/value/src/it/functional/src/test/java/com/google/auto/value/gwt/EmptyExtension.java +++ b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/EmptyExtension.java @@ -99,8 +99,7 @@ public class EmptyExtension extends AutoValueExtension { if (typeParameters.isEmpty()) { return ""; } - return typeParameters - .stream() + return typeParameters.stream() .map(e -> e.getSimpleName().toString()) .collect(joining(", ", "<", ">")); } |