diff options
author | Krzysztof KosiĆski <krzysio@google.com> | 2023-10-07 23:51:04 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2023-10-07 23:51:04 +0000 |
commit | fcd07df6bfb3f5e22207077e929948500bc0fd04 (patch) | |
tree | 740ebb2fb004b6b1a1a2f11a0c2a694f9f122195 | |
parent | 568ad95aa758664a779187e3c9f70c9531267f11 (diff) | |
parent | 45390e1638b10cdb982ae306f7e835985cea02b9 (diff) | |
download | truth-fcd07df6bfb3f5e22207077e929948500bc0fd04.tar.gz |
Upgrade Truth to v1.1.5. am: a130a063f6 am: 6ed885765a am: cc2fd605d7 am: 45390e1638
Original change: https://android-review.googlesource.com/c/platform/external/truth/+/2775239
Change-Id: I4838ffc298a08fbf2c5a66b3ebc88e5e4e65b308
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
125 files changed, 2582 insertions, 2643 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b3b3e8b..eb5ec20a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,20 +18,20 @@ jobs: steps: # Cancel any previous runs for the same branch that are still running. - name: 'Cancel previous runs' - uses: styfle/cancel-workflow-action@0.9.0 + uses: styfle/cancel-workflow-action@b173b6ec0100793626c2d9e6b90435061f4fc3e5 with: access_token: ${{ github.token }} - name: 'Check out repository' - uses: actions/checkout@v2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 - name: 'Cache local Maven repository' - uses: actions/cache@v2.1.5 + uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 with: path: ~/.m2/repository key: maven-${{ hashFiles('**/pom.xml') }} restore-keys: | maven- - name: 'Set up JDK ${{ matrix.java }}' - uses: actions/setup-java@v2 + uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 with: java-version: ${{ matrix.java }} distribution: 'zulu' @@ -41,6 +41,9 @@ jobs: - name: 'Test' shell: bash run: mvn -B -P!standard-with-extra-repos verify -U -Dmaven.javadoc.skip=true + - name: 'Javadoc Test Run' + shell: bash + run: mvn -B -P!standard-with-extra-repos javadoc:aggregate -U publish_snapshot: name: 'Publish snapshot' @@ -49,16 +52,16 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Check out repository' - uses: actions/checkout@v2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 - name: 'Cache local Maven repository' - uses: actions/cache@v2.1.5 + uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 with: path: ~/.m2/repository key: maven-${{ hashFiles('**/pom.xml') }} restore-keys: | maven- - name: 'Set up JDK 11' - uses: actions/setup-java@v2 + uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 with: java-version: 11 distribution: 'zulu' @@ -78,16 +81,16 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Check out repository' - uses: actions/checkout@v2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 - name: 'Cache local Maven repository' - uses: actions/cache@v2.1.5 + uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 with: path: ~/.m2/repository key: maven-${{ hashFiles('**/pom.xml') }} restore-keys: | maven- - name: 'Set up JDK 11' - uses: actions/setup-java@v2 + uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 with: java-version: 11 distribution: 'zulu' @@ -11,7 +11,7 @@ third_party { type: GIT value: "https://github.com/google/truth.git" } - version: "release_1_1_3" - last_upgrade_date { year: 2022 month: 10 day: 13 } + version: "v1.1.5" + last_upgrade_date { year: 2023 month: 10 day: 7 } license_type: NOTICE } diff --git a/core/pom.xml b/core/pom.xml index f600f1c7..2afd00e5 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -43,11 +43,6 @@ <scope>test</scope> </dependency> <dependency> - <groupId>com.google.testing.compile</groupId> - <artifactId>compile-testing</artifactId> - <scope>test</scope> - </dependency> - <dependency> <groupId>com.google.errorprone</groupId> <artifactId>error_prone_annotations</artifactId> </dependency> @@ -66,6 +61,13 @@ <exclude>**/*.gwt.xml</exclude> </excludes> </resource> + <resource> + <directory>..</directory> + <includes> + <include>LICENSE</include> + </includes> + <targetPath>META-INF</targetPath> + </resource> </resources> <testResources> <testResource><directory>src/test/java</directory></testResource> @@ -110,18 +112,7 @@ <goals><goal>jar</goal></goals> <configuration> <classifier>gwt</classifier> - <classesDirectory>src/main/java</classesDirectory> - <includes> - <include>**/*.java</include> - <include>**/*.gwt.xml</include> - </includes> - <excludes> - <exclude>com/google/common/truth/ClassSubject.java</exclude> - <exclude>com/google/common/truth/Expect.java</exclude> - <exclude>com/google/common/truth/IteratingVerb.java</exclude> - <exclude>com/google/common/truth/ReflectionUtil.java</exclude> - <exclude>com/google/common/truth/codegen/**</exclude> - </excludes> + <classesDirectory>${project.build.directory}/gwt-sources</classesDirectory> </configuration> </execution> </executions> @@ -161,6 +152,74 @@ </execution> </executions> </plugin> + <!-- We need to strip "@Nullable" from the sources that we hand to GWT: + b/183648616. To do that, we have to make a copy of the original + source directory and add that directory as a Maven source root. But + the added root comes *after* the original root, so, in order to make + GWT choose those sources in preference to the originals, we need to + put them in a `super` directory. + + TODO(b/183648616): Once we can use @Nullable from GWT, generate the + GWT jar from the original sources instead of these sources that we + strip @Nullable from. --> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>build-helper-maven-plugin</artifactId> + <version>3.4.0</version> + <executions> + <execution> + <id>add-source</id> + <phase>generate-sources</phase> + <goals> + <goal>add-source</goal> + </goals> + <configuration> + <sources> + <source>${project.build.directory}/gwt-sources</source> + </sources> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <artifactId>maven-antrun-plugin</artifactId> + <version>3.1.0</version> + <executions> + <execution> + <id>copy-gwt-files</id> + <phase>generate-sources</phase> + <goals><goal>run</goal></goals> + <configuration> + <target name="copy-gwt-resources"> + <copy toDir="${project.build.directory}/gwt-sources"> + <fileset dir="${project.basedir}/src/main/java"> + <include name="**/super/**/*.java"/> + <include name="**/*.gwt.xml"/> + </fileset> + </copy> + <copy toDir="${project.build.directory}/gwt-sources/com/google/common/truth/super"> + <fileset dir="${project.basedir}/src/main/java"> + <!-- Don't put files under .../super/.../super/... --> + <exclude name="**/super/**/*.java"/> + <!-- Don't put the .gwt.xml under super --> + <exclude name="**/*.gwt.xml"/> + </fileset> + </copy> + <replace token="@Nullable" value=""> + <fileset dir="${project.build.directory}/gwt-sources"> + <include name="**/super/**/*.java"/> + </fileset> + </replace> + <replace token="@NonNull" value=""> + <fileset dir="${project.build.directory}/gwt-sources"> + <include name="**/super/**/*.java"/> + </fileset> + </replace> + </target> + </configuration> + </execution> + </executions> + </plugin> </plugins> </build> <reporting> @@ -168,7 +227,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-project-info-reports-plugin</artifactId> - <version>3.1.2</version> + <version>3.4.5</version> </plugin> </plugins> </reporting> diff --git a/core/src/main/java/com/google/common/truth/AbstractArraySubject.java b/core/src/main/java/com/google/common/truth/AbstractArraySubject.java index a0cd2fdc..8eff47a1 100644 --- a/core/src/main/java/com/google/common/truth/AbstractArraySubject.java +++ b/core/src/main/java/com/google/common/truth/AbstractArraySubject.java @@ -16,6 +16,7 @@ package com.google.common.truth; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Fact.simpleFact; import java.lang.reflect.Array; @@ -27,7 +28,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; * @author Christian Gruber (cgruber@israfil.net) */ abstract class AbstractArraySubject extends Subject { - private final Object actual; + private final @Nullable Object actual; AbstractArraySubject( FailureMetadata metadata, @Nullable Object actual, @Nullable String typeDescription) { @@ -55,11 +56,11 @@ abstract class AbstractArraySubject extends Subject { * @throws IllegalArgumentException if {@code length < 0} */ public final void hasLength(int length) { - checkArgument(length >= 0, "length (%s) must be >= 0"); + checkArgument(length >= 0, "length (%s) must be >= 0", length); check("length").that(length()).isEqualTo(length); } private int length() { - return Array.getLength(actual); + return Array.getLength(checkNotNull(actual)); } } diff --git a/core/src/main/java/com/google/common/truth/ActualValueInference.java b/core/src/main/java/com/google/common/truth/ActualValueInference.java index 5959bb60..db49c1da 100644 --- a/core/src/main/java/com/google/common/truth/ActualValueInference.java +++ b/core/src/main/java/com/google/common/truth/ActualValueInference.java @@ -15,6 +15,7 @@ package com.google.common.truth; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.getOnlyElement; import static java.lang.Thread.currentThread; @@ -25,12 +26,14 @@ import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Iterables; +import org.objectweb.asm.Opcodes; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import com.google.errorprone.annotations.CheckReturnValue; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Map.Entry; +import org.checkerframework.checker.nullness.qual.Nullable; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Handle; @@ -65,9 +68,10 @@ import org.objectweb.asm.Type; * this is all best-effort. */ @GwtIncompatible +@J2ktIncompatible final class ActualValueInference { /** <b>Call {@link Platform#inferDescription} rather than calling this directly.</b> */ - static String describeActualValue(String className, String methodName, int lineNumber) { + static @Nullable String describeActualValue(String className, String methodName, int lineNumber) { InferenceClassVisitor visitor; try { // TODO(cpovirk): Verify that methodName is correct for constructors and static initializers. @@ -89,7 +93,7 @@ final class ActualValueInference { try { stream = loader.getResourceAsStream(className.replace('.', '/') + ".class"); // TODO(cpovirk): Disable inference if the bytecode version is newer than we've tested on? - new ClassReader(stream).accept(visitor, /*parsingOptions=*/ 0); + new ClassReader(stream).accept(visitor, /* parsingOptions= */ 0); ImmutableSet<StackEntry> actualsAtLine = visitor.actualValueAtLine.build().get(lineNumber); /* * It's very unlikely that more than one assertion would happen on the same line _but with @@ -140,7 +144,7 @@ final class ActualValueInference { throw new ClassCastException(getClass().getName()); } - String description() { + @Nullable String description() { return null; } } @@ -149,6 +153,7 @@ final class ActualValueInference { @AutoValue @CopyAnnotations @GwtIncompatible + @J2ktIncompatible abstract static class OpaqueEntry extends StackEntry { @Override public final String toString() { @@ -168,6 +173,7 @@ final class ActualValueInference { @AutoValue @CopyAnnotations @GwtIncompatible + @J2ktIncompatible abstract static class DescribedEntry extends StackEntry { @Override abstract String description(); @@ -193,6 +199,7 @@ final class ActualValueInference { @AutoValue @CopyAnnotations @GwtIncompatible + @J2ktIncompatible abstract static class SubjectEntry extends StackEntry { @Override abstract StackEntry actualValue(); @@ -266,7 +273,7 @@ final class ActualValueInference { String name, String methodDescriptor, ImmutableSetMultimap.Builder<Integer, StackEntry> actualValueAtLine) { - super(Opcodes.ASM8); + super(Opcodes.ASM9); localVariableSlots = createInitialLocalVariableSlots(access, owner, name, methodDescriptor); previousFrame = FrameInfo.create( @@ -734,7 +741,7 @@ final class ActualValueInference { @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { - if (opcode == Opcodes.INVOKESPECIAL && "<init>".equals(name)) { + if (opcode == Opcodes.INVOKESPECIAL && name.equals("<init>")) { int argumentSize = (Type.getArgumentsAndReturnSizes(desc) >> 2); InferredType receiverType = getOperandFromTop(argumentSize - 1).type(); if (receiverType.isUninitialized()) { @@ -958,7 +965,7 @@ final class ActualValueInference { } private void pushDescriptor(String desc) { - pushDescriptorAndMaybeProcessMethodCall(desc, /*invocation=*/ null); + pushDescriptorAndMaybeProcessMethodCall(desc, /* invocation= */ null); } /** @@ -973,9 +980,11 @@ final class ActualValueInference { * @param invocation the method invocation being visited, or {@code null} if a non-method * descriptor is being visited */ - private void pushDescriptorAndMaybeProcessMethodCall(String desc, Invocation invocation) { + private void pushDescriptorAndMaybeProcessMethodCall( + String desc, @Nullable Invocation invocation) { if (invocation != null && invocation.isOnSubjectInstance()) { - actualValueAtLocation.put(labelsSeen.build(), invocation.receiver().actualValue()); + actualValueAtLocation.put( + labelsSeen.build(), checkNotNull(invocation.receiver()).actualValue()); } boolean hasParams = invocation != null && (Type.getArgumentsAndReturnSizes(desc) >> 2) > 1; @@ -1010,7 +1019,8 @@ final class ActualValueInference { } } - private void pushMaybeDescribed(InferredType type, Invocation invocation, boolean hasParams) { + private void pushMaybeDescribed( + InferredType type, @Nullable Invocation invocation, boolean hasParams) { push(invocation == null ? opaque(type) : invocation.deriveEntry(type, hasParams)); } @@ -1031,10 +1041,11 @@ final class ActualValueInference { operandStack, methodSignature); int expectedLastIndex = operandStack.size() - count - 1; - StackEntry lastPopped = null; - for (int i = operandStack.size() - 1; i > expectedLastIndex; --i) { - lastPopped = operandStack.remove(i); - } + + StackEntry lastPopped; + do { + lastPopped = operandStack.remove(operandStack.size() - 1); + } while (operandStack.size() - 1 > expectedLastIndex); return lastPopped; } @@ -1074,7 +1085,7 @@ final class ActualValueInference { } private StackEntry top() { - return operandStack.get(operandStack.size() - 1); + return Iterables.getLast(operandStack); } /** @@ -1219,6 +1230,7 @@ final class ActualValueInference { @AutoValue @CopyAnnotations @GwtIncompatible + @J2ktIncompatible abstract static class FrameInfo { static FrameInfo create(ImmutableList<StackEntry> locals, ImmutableList<StackEntry> stack) { @@ -1234,24 +1246,22 @@ final class ActualValueInference { @AutoValue @CopyAnnotations @GwtIncompatible + @J2ktIncompatible abstract static class Invocation { static Builder builder(String name) { return new AutoValue_ActualValueInference_Invocation.Builder().setName(name); } /** The receiver of this call, if it is an instance call. */ - @Nullable - abstract StackEntry receiver(); + abstract @Nullable StackEntry receiver(); /** The value being passed to this call if it is an {@code assertThat} or {@code that} call. */ - @Nullable - abstract StackEntry actualValue(); + abstract @Nullable StackEntry actualValue(); /** * The value being passed to this call if it is a boxing call (e.g., {@code Integer.valueOf}). */ - @Nullable - abstract StackEntry boxingInput(); + abstract @Nullable StackEntry boxingInput(); abstract String name(); @@ -1261,7 +1271,7 @@ final class ActualValueInference { } else if (actualValue() != null) { return subjectFor(type, actualValue()); } else if (isOnSubjectInstance()) { - return subjectFor(type, receiver().actualValue()); + return subjectFor(type, checkNotNull(receiver()).actualValue()); } else if (BORING_NAMES.contains(name())) { /* * TODO(cpovirk): For no-arg instance methods like get(), return "foo.get()," where "foo" is @@ -1278,7 +1288,6 @@ final class ActualValueInference { } @AutoValue.Builder - @CanIgnoreReturnValue abstract static class Builder { abstract Builder setReceiver(StackEntry receiver); @@ -1288,7 +1297,6 @@ final class ActualValueInference { abstract Builder setName(String name); - @CheckReturnValue abstract Invocation build(); } } @@ -1297,6 +1305,7 @@ final class ActualValueInference { @AutoValue @CopyAnnotations @GwtIncompatible + @J2ktIncompatible abstract static class InferredType { static final String UNINITIALIZED_PREFIX = "UNINIT@"; @@ -1320,7 +1329,7 @@ final class ActualValueInference { /** Create a type for a value. */ static InferredType create(String descriptor) { - if (UNINITIALIZED_PREFIX.equals(descriptor)) { + if (descriptor.equals(UNINITIALIZED_PREFIX)) { return UNINITIALIZED; } char firstChar = descriptor.charAt(0); @@ -1395,7 +1404,7 @@ final class ActualValueInference { private String className; InferenceClassVisitor(String methodNameToVisit) { - super(Opcodes.ASM7); + super(Opcodes.ASM9); this.methodNameToVisit = methodNameToVisit; } @@ -1411,7 +1420,7 @@ final class ActualValueInference { } @Override - public MethodVisitor visitMethod( + public @Nullable MethodVisitor visitMethod( int access, String name, String desc, String signature, String[] exceptions) { /* * Each InferenceMethodVisitor instance may be used only once. Still, it might seem like we @@ -1455,7 +1464,8 @@ final class ActualValueInference { */ return (owner.equals("com/google/common/truth/Truth") && name.equals("assertThat")) || (owner.equals("com/google/common/truth/StandardSubjectBuilder") && name.equals("that")) - || (owner.equals("com/google/common/truth/SimpleSubjectBuilder") && name.equals("that")); + || (owner.equals("com/google/common/truth/SimpleSubjectBuilder") && name.equals("that")) + || (owner.equals("com/google/common/truth/Expect") && name.equals("that")); } private static boolean isBoxing(String owner, String name, String desc) { @@ -1496,7 +1506,7 @@ final class ActualValueInference { return (flags & bitmask) == bitmask; } - private static void closeQuietly(InputStream stream) { + private static void closeQuietly(@Nullable InputStream stream) { if (stream == null) { return; } @@ -1507,14 +1517,5 @@ final class ActualValueInference { } } - /* - * TODO(cpovirk): Switch to using Checker Framework @Nullable. The problem I see if I try to - * switch now is that the AutoValue-generated code is `@Nullable ActualValueInference.StackEntry` - * rather than `ActualValueInference.@Nullable StackEntry`. AutoValue normally gets this right - * (b/29530042), so I think the failure here is because we use `-source 7`. That might still be - * fine, except that j2objc compilation appears to then use `-source 8`. - */ - @interface Nullable {} - private ActualValueInference() {} } diff --git a/core/src/main/java/com/google/common/truth/AssertionErrorWithFacts.java b/core/src/main/java/com/google/common/truth/AssertionErrorWithFacts.java index db465057..cc5d36d9 100644 --- a/core/src/main/java/com/google/common/truth/AssertionErrorWithFacts.java +++ b/core/src/main/java/com/google/common/truth/AssertionErrorWithFacts.java @@ -47,13 +47,13 @@ final class AssertionErrorWithFacts extends AssertionError implements ErrorWithF @Override @SuppressWarnings("UnsynchronizedOverridesSynchronized") - public Throwable getCause() { + public @Nullable Throwable getCause() { return cause; } @Override public String toString() { - return getLocalizedMessage(); + return checkNotNull(getLocalizedMessage()); } @Override diff --git a/core/src/main/java/com/google/common/truth/BigDecimalSubject.java b/core/src/main/java/com/google/common/truth/BigDecimalSubject.java index 64cf8236..dbba5917 100644 --- a/core/src/main/java/com/google/common/truth/BigDecimalSubject.java +++ b/core/src/main/java/com/google/common/truth/BigDecimalSubject.java @@ -15,6 +15,7 @@ */ package com.google.common.truth; +import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Fact.fact; import static com.google.common.truth.Fact.simpleFact; @@ -27,7 +28,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; * @author Kurt Alfred Kluever */ public final class BigDecimalSubject extends ComparableSubject<BigDecimal> { - private final BigDecimal actual; + private final @Nullable BigDecimal actual; BigDecimalSubject(FailureMetadata metadata, @Nullable BigDecimal actual) { super(metadata, actual); @@ -88,12 +89,12 @@ public final class BigDecimalSubject extends ComparableSubject<BigDecimal> { * #isEqualTo(Object)}. */ @Override - public void isEquivalentAccordingToCompareTo(BigDecimal expected) { + public void isEquivalentAccordingToCompareTo(@Nullable BigDecimal expected) { compareValues(expected); } - private void compareValues(BigDecimal expected) { - if (actual.compareTo(expected) != 0) { + private void compareValues(@Nullable BigDecimal expected) { + if (checkNotNull(actual).compareTo(checkNotNull(expected)) != 0) { failWithoutActual(fact("expected", expected), butWas(), simpleFact("(scale is ignored)")); } } diff --git a/core/src/main/java/com/google/common/truth/BooleanSubject.java b/core/src/main/java/com/google/common/truth/BooleanSubject.java index 528cbc62..4e7f3da0 100644 --- a/core/src/main/java/com/google/common/truth/BooleanSubject.java +++ b/core/src/main/java/com/google/common/truth/BooleanSubject.java @@ -25,7 +25,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; * @author Christian Gruber (cgruber@israfil.net) */ public final class BooleanSubject extends Subject { - private final Boolean actual; + private final @Nullable Boolean actual; BooleanSubject(FailureMetadata metadata, @Nullable Boolean actual) { super(metadata, actual); diff --git a/core/src/main/java/com/google/common/truth/ClassSubject.java b/core/src/main/java/com/google/common/truth/ClassSubject.java index a2ca5c48..1ef1b950 100644 --- a/core/src/main/java/com/google/common/truth/ClassSubject.java +++ b/core/src/main/java/com/google/common/truth/ClassSubject.java @@ -15,6 +15,8 @@ */ package com.google.common.truth; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.common.annotations.GwtIncompatible; import org.checkerframework.checker.nullness.qual.Nullable; @@ -24,8 +26,9 @@ import org.checkerframework.checker.nullness.qual.Nullable; * @author Kurt Alfred Kluever */ @GwtIncompatible("reflection") +@J2ktIncompatible public final class ClassSubject extends Subject { - private final Class<?> actual; + private final @Nullable Class<?> actual; ClassSubject(FailureMetadata metadata, @Nullable Class<?> o) { super(metadata, o); @@ -37,7 +40,7 @@ public final class ClassSubject extends Subject { * class or interface. */ public void isAssignableTo(Class<?> clazz) { - if (!clazz.isAssignableFrom(actual)) { + if (!clazz.isAssignableFrom(checkNotNull(actual))) { failWithActual("expected to be assignable to", clazz.getName()); } } diff --git a/core/src/main/java/com/google/common/truth/ComparableSubject.java b/core/src/main/java/com/google/common/truth/ComparableSubject.java index ed769734..857f2149 100644 --- a/core/src/main/java/com/google/common/truth/ComparableSubject.java +++ b/core/src/main/java/com/google/common/truth/ComparableSubject.java @@ -15,6 +15,8 @@ */ package com.google.common.truth; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.common.collect.Range; import org.checkerframework.checker.nullness.qual.Nullable; @@ -24,12 +26,13 @@ import org.checkerframework.checker.nullness.qual.Nullable; * @author Kurt Alfred Kluever * @param <T> the type of the object being tested by this {@code ComparableSubject} */ -public abstract class ComparableSubject<T extends Comparable> extends Subject { +// TODO(b/136040841): Consider further tightening this to the proper `extends Comparable<? super T>` +public abstract class ComparableSubject<T extends Comparable<?>> extends Subject { /** * Constructor for use by subclasses. If you want to create an instance of this class itself, call * {@link Subject#check(String, Object...) check(...)}{@code .that(actual)}. */ - private final T actual; + private final @Nullable T actual; protected ComparableSubject(FailureMetadata metadata, @Nullable T actual) { super(metadata, actual); @@ -38,14 +41,14 @@ public abstract class ComparableSubject<T extends Comparable> extends Subject { /** Checks that the subject is in {@code range}. */ public final void isIn(Range<T> range) { - if (!range.contains(actual)) { + if (!range.contains(checkNotNull(actual))) { failWithActual("expected to be in range", range); } } /** Checks that the subject is <i>not</i> in {@code range}. */ public final void isNotIn(Range<T> range) { - if (range.contains(actual)) { + if (range.contains(checkNotNull(actual))) { failWithActual("expected not to be in range", range); } } @@ -57,8 +60,9 @@ public abstract class ComparableSubject<T extends Comparable> extends Subject { * <p><b>Note:</b> Do not use this method for checking object equality. Instead, use {@link * #isEqualTo(Object)}. */ - public void isEquivalentAccordingToCompareTo(T expected) { - if (actual.compareTo(expected) != 0) { + @SuppressWarnings("unchecked") + public void isEquivalentAccordingToCompareTo(@Nullable T expected) { + if (checkNotNull((Comparable<Object>) actual).compareTo(checkNotNull(expected)) != 0) { failWithActual("expected value that sorts equal to", expected); } } @@ -69,8 +73,9 @@ public abstract class ComparableSubject<T extends Comparable> extends Subject { * <p>To check that the subject is greater than <i>or equal to</i> {@code other}, use {@link * #isAtLeast}. */ - public final void isGreaterThan(T other) { - if (actual.compareTo(other) <= 0) { + @SuppressWarnings("unchecked") + public final void isGreaterThan(@Nullable T other) { + if (checkNotNull((Comparable<Object>) actual).compareTo(checkNotNull(other)) <= 0) { failWithActual("expected to be greater than", other); } } @@ -81,8 +86,9 @@ public abstract class ComparableSubject<T extends Comparable> extends Subject { * <p>To check that the subject is less than <i>or equal to</i> {@code other}, use {@link * #isAtMost}. */ - public final void isLessThan(T other) { - if (actual.compareTo(other) >= 0) { + @SuppressWarnings("unchecked") + public final void isLessThan(@Nullable T other) { + if (checkNotNull((Comparable<Object>) actual).compareTo(checkNotNull(other)) >= 0) { failWithActual("expected to be less than", other); } } @@ -93,8 +99,9 @@ public abstract class ComparableSubject<T extends Comparable> extends Subject { * <p>To check that the subject is <i>strictly</i> less than {@code other}, use {@link * #isLessThan}. */ - public final void isAtMost(T other) { - if (actual.compareTo(other) > 0) { + @SuppressWarnings("unchecked") + public final void isAtMost(@Nullable T other) { + if (checkNotNull((Comparable<Object>) actual).compareTo(checkNotNull(other)) > 0) { failWithActual("expected to be at most", other); } } @@ -105,8 +112,9 @@ public abstract class ComparableSubject<T extends Comparable> extends Subject { * <p>To check that the subject is <i>strictly</i> greater than {@code other}, use {@link * #isGreaterThan}. */ - public final void isAtLeast(T other) { - if (actual.compareTo(other) < 0) { + @SuppressWarnings("unchecked") + public final void isAtLeast(@Nullable T other) { + if (checkNotNull((Comparable<Object>) actual).compareTo(checkNotNull(other)) < 0) { failWithActual("expected to be at least", other); } } diff --git a/core/src/main/java/com/google/common/truth/ComparisonFailureWithFacts.java b/core/src/main/java/com/google/common/truth/ComparisonFailureWithFacts.java index dcb4477f..47608db2 100644 --- a/core/src/main/java/com/google/common/truth/ComparisonFailureWithFacts.java +++ b/core/src/main/java/com/google/common/truth/ComparisonFailureWithFacts.java @@ -30,6 +30,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; final class ComparisonFailureWithFacts extends PlatformComparisonFailure implements ErrorWithFacts { private final ImmutableList<Fact> facts; + @UsedByReflection ComparisonFailureWithFacts( ImmutableList<String> messages, ImmutableList<Fact> facts, diff --git a/core/src/main/java/com/google/common/truth/Correspondence.java b/core/src/main/java/com/google/common/truth/Correspondence.java index b4a04b0e..704a9e6c 100644 --- a/core/src/main/java/com/google/common/truth/Correspondence.java +++ b/core/src/main/java/com/google/common/truth/Correspondence.java @@ -65,7 +65,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; * * @author Pete Gillin */ -public abstract class Correspondence<A, E> { +public abstract class Correspondence<A extends @Nullable Object, E extends @Nullable Object> { /** * Constructs a {@link Correspondence} that compares actual and expected elements using the given @@ -93,7 +93,7 @@ public abstract class Correspondence<A, E> { * class MyRecordTestHelper { * static final Correspondence<MyRecord, MyRecord> EQUIVALENCE = * Correspondence.from(MyRecordTestHelper::recordsEquivalent, "is equivalent to"); - * static boolean recordsEquivalent(@Nullable MyRecord actual, @Nullable MyRecord expected) { + * static boolean recordsEquivalent(MyRecord actual, MyRecord expected) { * // code to check whether records should be considered equivalent for testing purposes * } * } @@ -112,7 +112,7 @@ public abstract class Correspondence<A, E> { * <some actual element> is an element that <description> <some expected element>"}, e.g. * {@code "contains"}, {@code "is an instance of"}, or {@code "is equivalent to"} */ - public static <A, E> Correspondence<A, E> from( + public static <A extends @Nullable Object, E extends @Nullable Object> Correspondence<A, E> from( BinaryPredicate<A, E> predicate, String description) { return new FromBinaryPredicate<>(predicate, description); } @@ -126,16 +126,18 @@ public abstract class Correspondence<A, E> { * you should almost never see {@code BinaryPredicate} used as the type of a field or variable, or * a return type. */ - public interface BinaryPredicate<A, E> { + public interface BinaryPredicate<A extends @Nullable Object, E extends @Nullable Object> { /** * Returns whether or not the actual and expected values satisfy the condition defined by this * predicate. */ - boolean apply(@Nullable A actual, @Nullable E expected); + boolean apply(A actual, E expected); } - private static final class FromBinaryPredicate<A, E> extends Correspondence<A, E> { + private static final class FromBinaryPredicate< + A extends @Nullable Object, E extends @Nullable Object> + extends Correspondence<A, E> { private final BinaryPredicate<A, E> predicate; private final String description; @@ -145,7 +147,7 @@ public abstract class Correspondence<A, E> { } @Override - public boolean compare(@Nullable A actual, @Nullable E expected) { + public boolean compare(A actual, E expected) { return predicate.apply(actual, expected); } @@ -191,8 +193,9 @@ public abstract class Correspondence<A, E> { * <some actual element> is an element that <description> <some expected element>"}, e.g. * {@code "has an ID of"} */ - public static <A, E> Correspondence<A, E> transforming( - Function<A, ? extends E> actualTransform, String description) { + public static <A extends @Nullable Object, E extends @Nullable Object> + Correspondence<A, E> transforming( + Function<A, ? extends E> actualTransform, String description) { return new Transforming<>(actualTransform, identity(), description); } @@ -238,12 +241,14 @@ public abstract class Correspondence<A, E> { * <some actual element> is an element that <description> <some expected element>"}, e.g. * {@code "has the same ID as"} */ - public static <A, E> Correspondence<A, E> transforming( - Function<A, ?> actualTransform, Function<E, ?> expectedTransform, String description) { + public static <A extends @Nullable Object, E extends @Nullable Object> + Correspondence<A, E> transforming( + Function<A, ?> actualTransform, Function<E, ?> expectedTransform, String description) { return new Transforming<>(actualTransform, expectedTransform, description); } - private static final class Transforming<A, E> extends Correspondence<A, E> { + private static final class Transforming<A extends @Nullable Object, E extends @Nullable Object> + extends Correspondence<A, E> { private final Function<? super A, ?> actualTransform; private final Function<? super E, ?> expectedTransform; @@ -259,7 +264,7 @@ public abstract class Correspondence<A, E> { } @Override - public boolean compare(@Nullable A actual, @Nullable E expected) { + public boolean compare(A actual, E expected) { return Objects.equal(actualTransform.apply(actual), expectedTransform.apply(expected)); } @@ -373,10 +378,10 @@ public abstract class Correspondence<A, E> { * static final Correspondence<MyRecord, MyRecord> EQUIVALENCE = * Correspondence.from(MyRecordTestHelper::recordsEquivalent, "is equivalent to") * .formattingDiffsUsing(MyRecordTestHelper::formatRecordDiff); - * static boolean recordsEquivalent(@Nullable MyRecord actual, @Nullable MyRecord expected) { + * static boolean recordsEquivalent(MyRecord actual, MyRecord expected) { * // code to check whether records should be considered equivalent for testing purposes * } - * static String formatRecordDiff(@Nullable MyRecord actual, @Nullable MyRecord expected) { + * static String formatRecordDiff(MyRecord actual, MyRecord expected) { * // code to format the diff between the records * } * } @@ -395,17 +400,18 @@ public abstract class Correspondence<A, E> { * Correspondence#formattingDiffsUsing}. As a result, you should almost never see {@code * DiffFormatter} used as the type of a field or variable, or a return type. */ - public interface DiffFormatter<A, E> { + public interface DiffFormatter<A extends @Nullable Object, E extends @Nullable Object> { /** * Returns a {@link String} describing the difference between the {@code actual} and {@code * expected} values, if possible, or {@code null} if not. */ @Nullable - String formatDiff(@Nullable A actual, @Nullable E expected); + String formatDiff(A actual, E expected); } - private static class FormattingDiffs<A, E> extends Correspondence<A, E> { + private static class FormattingDiffs<A extends @Nullable Object, E extends @Nullable Object> + extends Correspondence<A, E> { private final Correspondence<A, E> delegate; private final DiffFormatter<? super A, ? super E> formatter; @@ -416,12 +422,12 @@ public abstract class Correspondence<A, E> { } @Override - public boolean compare(@Nullable A actual, @Nullable E expected) { + public boolean compare(A actual, E expected) { return delegate.compare(actual, expected); } @Override - public @Nullable String formatDiff(@Nullable A actual, @Nullable E expected) { + public @Nullable String formatDiff(A actual, E expected) { return formatter.formatDiff(actual, expected); } @@ -517,7 +523,7 @@ public abstract class Correspondence<A, E> { * returned false. (Note that, in the latter case at least, it is likely that the test author's * intention was <i>not</i> for the test to pass with these values.) */ - public abstract boolean compare(@Nullable A actual, @Nullable E expected); + public abstract boolean compare(A actual, E expected); private static class StoredException { @@ -525,9 +531,10 @@ public abstract class Correspondence<A, E> { private final Exception exception; private final String methodName; - private final List<Object> methodArguments; + private final List<@Nullable Object> methodArguments; - StoredException(Exception exception, String methodName, List<Object> methodArguments) { + StoredException( + Exception exception, String methodName, List<@Nullable Object> methodArguments) { this.exception = checkNotNull(exception); this.methodName = checkNotNull(methodName); this.methodArguments = checkNotNull(methodArguments); @@ -552,9 +559,9 @@ public abstract class Correspondence<A, E> { static final class ExceptionStore { private final String argumentLabel; - private StoredException firstCompareException = null; - private StoredException firstPairingException = null; - private StoredException firstFormatDiffException = null; + private @Nullable StoredException firstCompareException = null; + private @Nullable StoredException firstPairingException = null; + private @Nullable StoredException firstFormatDiffException = null; static ExceptionStore forIterable() { return new ExceptionStore("elements"); @@ -580,7 +587,10 @@ public abstract class Correspondence<A, E> { * exception was encountered */ void addCompareException( - Class<?> callingClass, Exception exception, Object actual, Object expected) { + Class<?> callingClass, + Exception exception, + @Nullable Object actual, + @Nullable Object expected) { if (firstCompareException == null) { truncateStackTrace(exception, callingClass); firstCompareException = new StoredException(exception, "compare", asList(actual, expected)); @@ -597,7 +607,8 @@ public abstract class Correspondence<A, E> { * @param actual The {@code actual} argument to the {@code apply} call during which the * exception was encountered */ - void addActualKeyFunctionException(Class<?> callingClass, Exception exception, Object actual) { + void addActualKeyFunctionException( + Class<?> callingClass, Exception exception, @Nullable Object actual) { if (firstPairingException == null) { truncateStackTrace(exception, callingClass); firstPairingException = @@ -616,7 +627,7 @@ public abstract class Correspondence<A, E> { * exception was encountered */ void addExpectedKeyFunctionException( - Class<?> callingClass, Exception exception, Object expected) { + Class<?> callingClass, Exception exception, @Nullable Object expected) { if (firstPairingException == null) { truncateStackTrace(exception, callingClass); firstPairingException = @@ -636,7 +647,10 @@ public abstract class Correspondence<A, E> { * exception was encountered */ void addFormatDiffException( - Class<?> callingClass, Exception exception, Object actual, Object expected) { + Class<?> callingClass, + Exception exception, + @Nullable Object actual, + @Nullable Object expected) { if (firstFormatDiffException == null) { truncateStackTrace(exception, callingClass); firstFormatDiffException = @@ -718,7 +732,7 @@ public abstract class Correspondence<A, E> { * returns false. This method can help with implementing the exception-handling policy described * above, but note that assertions using it <i>must</i> fail later if an exception was stored. */ - final boolean safeCompare(@Nullable A actual, @Nullable E expected, ExceptionStore exceptions) { + final boolean safeCompare(A actual, E expected, ExceptionStore exceptions) { try { return compare(actual, expected); } catch (RuntimeException e) { @@ -744,7 +758,7 @@ public abstract class Correspondence<A, E> { * exception and continue to describe the original failure as if this method had returned null, * mentioning the failure from this method as additional information. */ - public @Nullable String formatDiff(@Nullable A actual, @Nullable E expected) { + public @Nullable String formatDiff(A actual, E expected) { return null; } @@ -753,8 +767,7 @@ public abstract class Correspondence<A, E> { * the result. If it does throw, adds the exception to the given {@link ExceptionStore} and * returns null. */ - final @Nullable String safeFormatDiff( - @Nullable A actual, @Nullable E expected, ExceptionStore exceptions) { + final @Nullable String safeFormatDiff(A actual, E expected, ExceptionStore exceptions) { try { return formatDiff(actual, expected); } catch (RuntimeException e) { diff --git a/core/src/main/java/com/google/common/truth/CustomSubjectBuilder.java b/core/src/main/java/com/google/common/truth/CustomSubjectBuilder.java index 3b4f6b11..0b84ef88 100644 --- a/core/src/main/java/com/google/common/truth/CustomSubjectBuilder.java +++ b/core/src/main/java/com/google/common/truth/CustomSubjectBuilder.java @@ -18,6 +18,7 @@ package com.google.common.truth; import static com.google.common.base.Preconditions.checkNotNull; + /** * In a fluent assertion chain, exposes one or more "custom" {@code that} methods, which accept a * value under test and return a {@link Subject}. @@ -69,5 +70,5 @@ public abstract class CustomSubjectBuilder { return metadata; } - // TODO(user): Better enforce that subclasses implement a that() method. + // TODO(user,cgruber): Better enforce that subclasses implement a that() method. } diff --git a/core/src/main/java/com/google/common/truth/DiffUtils.java b/core/src/main/java/com/google/common/truth/DiffUtils.java index 43248a72..b8b4122e 100644 --- a/core/src/main/java/com/google/common/truth/DiffUtils.java +++ b/core/src/main/java/com/google/common/truth/DiffUtils.java @@ -36,10 +36,10 @@ final class DiffUtils { private final List<String> stringList = new ArrayList<>(); // A map to record each unique string and its incremental id. private final Map<String, Integer> stringToId = new HashMap<>(); - private int[] original; - private int[] revised; + private int[] original = new int[0]; + private int[] revised = new int[0]; // lcs[i][j] is the length of the longest common sequence of original[1..i] and revised[1..j]. - private int[][] lcs; + private int[][] lcs = new int[0][0]; private final List<Character> unifiedDiffType = new ArrayList<>(); private final List<Integer> unifiedDiffContentId = new ArrayList<>(); private final List<String> reducedUnifiedDiff = new ArrayList<>(); diff --git a/core/src/main/java/com/google/common/truth/DoubleSubject.java b/core/src/main/java/com/google/common/truth/DoubleSubject.java index a198bff8..58cd50d6 100644 --- a/core/src/main/java/com/google/common/truth/DoubleSubject.java +++ b/core/src/main/java/com/google/common/truth/DoubleSubject.java @@ -36,7 +36,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; public final class DoubleSubject extends ComparableSubject<Double> { private static final long NEG_ZERO_BITS = doubleToLongBits(-0.0); - private final Double actual; + private final @Nullable Double actual; DoubleSubject(FailureMetadata metadata, @Nullable Double actual) { super(metadata, actual); @@ -104,7 +104,7 @@ public final class DoubleSubject extends ComparableSubject<Double> { * allowed by the check, which must be a non-negative finite value, i.e. not {@link * Double#NaN}, {@link Double#POSITIVE_INFINITY}, or negative, including {@code -0.0} */ - public TolerantDoubleComparison isWithin(final double tolerance) { + public TolerantDoubleComparison isWithin(double tolerance) { return new TolerantDoubleComparison() { @Override public void of(double expected) { @@ -143,7 +143,7 @@ public final class DoubleSubject extends ComparableSubject<Double> { * allowed by the check, which must be a non-negative finite value, i.e. not {@code * Double.NaN}, {@code Double.POSITIVE_INFINITY}, or negative, including {@code -0.0} */ - public TolerantDoubleComparison isNotWithin(final double tolerance) { + public TolerantDoubleComparison isNotWithin(double tolerance) { return new TolerantDoubleComparison() { @Override public void of(double expected) { @@ -201,7 +201,7 @@ public final class DoubleSubject extends ComparableSubject<Double> { */ @Override @Deprecated - public final void isEquivalentAccordingToCompareTo(Double other) { + public final void isEquivalentAccordingToCompareTo(@Nullable Double other) { super.isEquivalentAccordingToCompareTo(other); } diff --git a/core/src/main/java/com/google/common/truth/Expect.java b/core/src/main/java/com/google/common/truth/Expect.java index 5acc31d9..4e7b77d7 100644 --- a/core/src/main/java/com/google/common/truth/Expect.java +++ b/core/src/main/java/com/google/common/truth/Expect.java @@ -82,11 +82,12 @@ import org.junit.runners.model.Statement; * <p>For more on this class, see <a href="https://truth.dev/expect">the documentation page</a>. */ @GwtIncompatible("JUnit4") +@J2ktIncompatible public final class Expect extends StandardSubjectBuilder implements TestRule { private static final class ExpectationGatherer implements FailureStrategy { @GuardedBy("this") - private final List<AssertionError> failures = new ArrayList<AssertionError>(); + private final List<AssertionError> failures = new ArrayList<>(); @GuardedBy("this") private TestPhase inRuleContext = BEFORE; @@ -136,8 +137,10 @@ public final class Expect extends StandardSubjectBuilder implements TestRule { } int numFailures = failures.size(); StringBuilder message = - new StringBuilder( - numFailures + (numFailures > 1 ? " expectations" : " expectation") + " failed:\n"); + new StringBuilder() + .append(numFailures) + .append(numFailures > 1 ? " expectations" : " expectation") + .append(" failed:\n"); int countLength = String.valueOf(failures.size() + 1).length(); int count = 0; for (AssertionError failure : failures) { @@ -159,6 +162,8 @@ public final class Expect extends StandardSubjectBuilder implements TestRule { return message.toString(); } + // String.repeat is not available under Java 8 and old versions of Android. + @SuppressWarnings({"StringsRepeat", "InlineMeInliner"}) private static void appendIndented(int countLength, StringBuilder builder, String toAppend) { int indent = countLength + 4; // " " and ". " builder.append(toAppend.replace("\n", "\n" + repeat(" ", indent))); @@ -245,7 +250,7 @@ public final class Expect extends StandardSubjectBuilder implements TestRule { } @Override - public Statement apply(final Statement base, Description description) { + public Statement apply(Statement base, Description description) { checkNotNull(base); checkNotNull(description); return new Statement() { diff --git a/core/src/main/java/com/google/common/truth/ExpectFailure.java b/core/src/main/java/com/google/common/truth/ExpectFailure.java index 970f5261..5b036085 100644 --- a/core/src/main/java/com/google/common/truth/ExpectFailure.java +++ b/core/src/main/java/com/google/common/truth/ExpectFailure.java @@ -23,6 +23,7 @@ import static com.google.common.truth.TruthFailureSubject.truthFailures; import com.google.common.annotations.GwtIncompatible; import com.google.common.truth.Truth.SimpleAssertionError; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.runner.Description; import org.junit.runners.model.Statement; @@ -66,14 +67,6 @@ import org.junit.runners.model.Statement; * {@link FailureStrategy#fail} only once. */ public final class ExpectFailure implements Platform.JUnitTestRule { - private final FailureStrategy strategy = - new FailureStrategy() { - @Override - public void fail(AssertionError failure) { - captureFailure(failure); - } - }; - private boolean inRuleContext = false; private boolean failureExpected = false; private @Nullable AssertionError failure = null; @@ -101,7 +94,7 @@ public final class ExpectFailure implements Platform.JUnitTestRule { "ExpectFailure.whenTesting() called previously, but did not capture a failure."); } failureExpected = true; - return StandardSubjectBuilder.forCustomFailureStrategy(strategy); + return StandardSubjectBuilder.forCustomFailureStrategy(this::captureFailure); } /** @@ -161,6 +154,7 @@ public final class ExpectFailure implements Platform.JUnitTestRule { * <p>{@code AssertionError failure = expectFailure(whenTesting -> * whenTesting.that(4).isNotEqualTo(4));} */ + @CanIgnoreReturnValue public static AssertionError expectFailure(StandardSubjectBuilderCallback assertionCallback) { ExpectFailure expectFailure = new ExpectFailure(); expectFailure.enterRuleContext(); // safe since this instance doesn't leave this method @@ -175,30 +169,25 @@ public final class ExpectFailure implements Platform.JUnitTestRule { * <p>{@code AssertionError failure = expectFailureAbout(myTypes(), whenTesting -> * whenTesting.that(myType).hasProperty());} */ + @CanIgnoreReturnValue public static <S extends Subject, A> AssertionError expectFailureAbout( - final Subject.Factory<S, A> factory, - final SimpleSubjectBuilderCallback<S, A> assertionCallback) { - // whenTesting -> assertionCallback.invokeAssertion(whenTesting.about(factory)) + Subject.Factory<S, A> factory, SimpleSubjectBuilderCallback<S, A> assertionCallback) { return expectFailure( - new StandardSubjectBuilderCallback() { - @Override - public void invokeAssertion(StandardSubjectBuilder whenTesting) { - assertionCallback.invokeAssertion(whenTesting.about(factory)); - } - }); + whenTesting -> assertionCallback.invokeAssertion(whenTesting.about(factory))); } /** * Creates a subject for asserting about the given {@link AssertionError}, usually one produced by * Truth. */ - public static TruthFailureSubject assertThat(AssertionError actual) { + public static TruthFailureSubject assertThat(@Nullable AssertionError actual) { return assertAbout(truthFailures()).that(actual); } @Override @GwtIncompatible("org.junit.rules.TestRule") - public Statement apply(final Statement base, Description description) { + @J2ktIncompatible + public Statement apply(Statement base, Description description) { checkNotNull(base); checkNotNull(description); return new Statement() { diff --git a/core/src/main/java/com/google/common/truth/Fact.java b/core/src/main/java/com/google/common/truth/Fact.java index 02e2bea4..b2dadad2 100644 --- a/core/src/main/java/com/google/common/truth/Fact.java +++ b/core/src/main/java/com/google/common/truth/Fact.java @@ -135,6 +135,6 @@ public final class Fact implements Serializable { // We don't want to indent with \t because the text would align exactly with the stack trace. // We don't want to indent with \t\t because it would be very far for people with 8-space tabs. // Let's compromise and indent by 4 spaces, which is different than both 2- and 8-space tabs. - return " " + value.replaceAll("\n", "\n "); + return " " + value.replace("\n", "\n "); } } diff --git a/core/src/main/java/com/google/common/truth/FailureMetadata.java b/core/src/main/java/com/google/common/truth/FailureMetadata.java index 00c890cf..e6d86a74 100644 --- a/core/src/main/java/com/google/common/truth/FailureMetadata.java +++ b/core/src/main/java/com/google/common/truth/FailureMetadata.java @@ -68,7 +68,7 @@ public final class FailureMetadata { } static Step checkCall( - OldAndNewValuesAreSimilar valuesAreSimilar, + @Nullable OldAndNewValuesAreSimilar valuesAreSimilar, @Nullable Function<String, String> descriptionUpdate) { return new Step(null, descriptionUpdate, valuesAreSimilar); } @@ -161,13 +161,12 @@ public final class FailureMetadata { * to set a message is {@code check(...).withMessage(...).that(...)} (for calls from within a * {@code Subject}) or {@link Truth#assertWithMessage} (for most other calls). */ - FailureMetadata withMessage(String format, /*@Nullable*/ Object[] args) { + FailureMetadata withMessage(String format, @Nullable Object[] args) { ImmutableList<LazyMessage> messages = append(this.messages, new LazyMessage(format, args)); return derive(messages, steps); } void failEqualityCheck( - ImmutableList<Fact> headFacts, ImmutableList<Fact> tailFacts, String expected, String actual) { @@ -175,10 +174,7 @@ public final class FailureMetadata { makeComparisonFailure( evaluateAll(messages), makeComparisonFailureFacts( - concat(description(), headFacts), - concat(tailFacts, rootUnlessThrowable()), - expected, - actual), + description(), concat(tailFacts, rootUnlessThrowable()), expected, actual), expected, actual, rootCause())); @@ -242,7 +238,7 @@ public final class FailureMetadata { } if (description == null) { - description = step.subject.typeDescription(); + description = checkNotNull(step.subject).typeDescription(); } } return descriptionIsInteresting @@ -291,7 +287,7 @@ public final class FailureMetadata { } if (rootSubject == null) { - if (step.subject.actual() instanceof Throwable) { + if (checkNotNull(step.subject).actual() instanceof Throwable) { /* * We'll already include the Throwable as a cause of the AssertionError (see rootCause()), * so we don't need to include it again in the message. @@ -310,8 +306,9 @@ public final class FailureMetadata { ? ImmutableList.of( fact( // TODO(cpovirk): Use inferDescription() here when appropriate? But it can be long. - rootSubject.subject.typeDescription() + " was", - rootSubject.subject.actualCustomStringRepresentationForPackageMembersToCall())) + checkNotNull(checkNotNull(rootSubject).subject).typeDescription() + " was", + checkNotNull(checkNotNull(rootSubject).subject) + .actualCustomStringRepresentationForPackageMembersToCall())) : ImmutableList.<Fact>of(); } @@ -321,7 +318,7 @@ public final class FailureMetadata { */ private @Nullable Throwable rootCause() { for (Step step : steps) { - if (!step.isCheckCall() && step.subject.actual() instanceof Throwable) { + if (!step.isCheckCall() && checkNotNull(step.subject).actual() instanceof Throwable) { return (Throwable) step.subject.actual(); } } diff --git a/core/src/main/java/com/google/common/truth/FailureStrategy.java b/core/src/main/java/com/google/common/truth/FailureStrategy.java index 81f1092a..b10d5509 100644 --- a/core/src/main/java/com/google/common/truth/FailureStrategy.java +++ b/core/src/main/java/com/google/common/truth/FailureStrategy.java @@ -15,6 +15,7 @@ */ package com.google.common.truth; + /** * Defines what to do when a check fails. * diff --git a/core/src/main/java/com/google/common/truth/FloatSubject.java b/core/src/main/java/com/google/common/truth/FloatSubject.java index 765b3844..19dff124 100644 --- a/core/src/main/java/com/google/common/truth/FloatSubject.java +++ b/core/src/main/java/com/google/common/truth/FloatSubject.java @@ -36,7 +36,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; public final class FloatSubject extends ComparableSubject<Float> { private static final int NEG_ZERO_BITS = floatToIntBits(-0.0f); - private final Float actual; + private final @Nullable Float actual; private final DoubleSubject asDouble; FloatSubject(FailureMetadata metadata, @Nullable Float actual) { @@ -112,7 +112,7 @@ public final class FloatSubject extends ComparableSubject<Float> { * allowed by the check, which must be a non-negative finite value, i.e. not {@link * Float#NaN}, {@link Float#POSITIVE_INFINITY}, or negative, including {@code -0.0f} */ - public TolerantFloatComparison isWithin(final float tolerance) { + public TolerantFloatComparison isWithin(float tolerance) { return new TolerantFloatComparison() { @Override public void of(float expected) { @@ -151,7 +151,7 @@ public final class FloatSubject extends ComparableSubject<Float> { * allowed by the check, which must be a non-negative finite value, i.e. not {@code * Float.NaN}, {@code Float.POSITIVE_INFINITY}, or negative, including {@code -0.0f} */ - public TolerantFloatComparison isNotWithin(final float tolerance) { + public TolerantFloatComparison isNotWithin(float tolerance) { return new TolerantFloatComparison() { @Override public void of(float expected) { @@ -209,7 +209,7 @@ public final class FloatSubject extends ComparableSubject<Float> { */ @Override @Deprecated - public final void isEquivalentAccordingToCompareTo(Float other) { + public final void isEquivalentAccordingToCompareTo(@Nullable Float other) { super.isEquivalentAccordingToCompareTo(other); } diff --git a/core/src/main/java/com/google/common/truth/GraphMatching.java b/core/src/main/java/com/google/common/truth/GraphMatching.java index 6a266f5d..ef21fef5 100644 --- a/core/src/main/java/com/google/common/truth/GraphMatching.java +++ b/core/src/main/java/com/google/common/truth/GraphMatching.java @@ -15,6 +15,8 @@ */ package com.google.common.truth; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.common.base.Optional; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; @@ -142,7 +144,7 @@ final class GraphMatching { // Now proceed with the BFS. while (!queue.isEmpty()) { U lhs = queue.remove(); - int layer = layers.get(lhs); + int layer = checkNotNull(layers.get(lhs)); // If the BFS has proceeded past a layer in which a free RHS vertex was found, stop. if (freeRhsVertexLayer.isPresent() && layer > freeRhsVertexLayer.get()) { break; @@ -164,7 +166,7 @@ final class GraphMatching { // that new LHS vertex yet, add it to the next layer. (If the edge from the LHS to the // RHS was matched then the matched edge from the RHS to the LHS will lead back to the // current LHS vertex, which has definitely been visited, so we correctly do nothing.) - U nextLhs = matching.inverse().get(rhs); + U nextLhs = checkNotNull(matching.inverse().get(rhs)); if (!layers.containsKey(nextLhs)) { layers.put(nextLhs, layer + 1); queue.add(nextLhs); @@ -223,7 +225,7 @@ final class GraphMatching { // rather than using all the paths at the end of the phase. As explained above, the effect of // this is that we automatically find only the disjoint set of paths, as required. This is, // fact, the approach taken in the pseudocode of the wikipedia article (at time of writing). - int layer = layers.get(lhs); + int layer = checkNotNull(layers.get(lhs)); if (layer > freeRhsVertexLayer) { // We've gone past the target layer, so we're not going to find what we're looking for. return false; @@ -240,7 +242,7 @@ final class GraphMatching { } else { // We found a non-free RHS vertex. Follow the matched edge from that RHS vertex to find // the next LHS vertex. - U nextLhs = matching.inverse().get(rhs); + U nextLhs = checkNotNull(matching.inverse().get(rhs)); if (layers.containsKey(nextLhs) && layers.get(nextLhs) == layer + 1) { // The next LHS vertex is in the next layer of the BFS, so we can use this path for our // DFS. Recurse into the DFS. diff --git a/core/src/main/java/com/google/common/truth/GuavaOptionalSubject.java b/core/src/main/java/com/google/common/truth/GuavaOptionalSubject.java index bbaf8053..d65448d6 100644 --- a/core/src/main/java/com/google/common/truth/GuavaOptionalSubject.java +++ b/core/src/main/java/com/google/common/truth/GuavaOptionalSubject.java @@ -30,7 +30,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; * @author Christian Gruber */ public final class GuavaOptionalSubject extends Subject { - private final Optional<?> actual; + private final @Nullable Optional<?> actual; GuavaOptionalSubject( FailureMetadata metadata, @Nullable Optional<?> actual, @Nullable String typeDescription) { @@ -67,7 +67,7 @@ public final class GuavaOptionalSubject extends Subject { * assertThat(myOptional.get()).contains("foo"); * }</pre> */ - public void hasValue(Object expected) { + public void hasValue(@Nullable Object expected) { if (expected == null) { throw new NullPointerException("Optional cannot have a null value."); } diff --git a/core/src/main/java/com/google/common/truth/IntegerSubject.java b/core/src/main/java/com/google/common/truth/IntegerSubject.java index 2f892379..bf144640 100644 --- a/core/src/main/java/com/google/common/truth/IntegerSubject.java +++ b/core/src/main/java/com/google/common/truth/IntegerSubject.java @@ -33,10 +33,12 @@ public class IntegerSubject extends ComparableSubject<Integer> { super(metadata, integer); } - /** @deprecated Use {@link #isEqualTo} instead. Integer comparison is consistent with equality. */ + /** + * @deprecated Use {@link #isEqualTo} instead. Integer comparison is consistent with equality. + */ @Override @Deprecated - public final void isEquivalentAccordingToCompareTo(Integer other) { + public final void isEquivalentAccordingToCompareTo(@Nullable Integer other) { super.isEquivalentAccordingToCompareTo(other); } } diff --git a/core/src/main/java/com/google/common/truth/IterableSubject.java b/core/src/main/java/com/google/common/truth/IterableSubject.java index bfb5dad2..bdafca93 100644 --- a/core/src/main/java/com/google/common/truth/IterableSubject.java +++ b/core/src/main/java/com/google/common/truth/IterableSubject.java @@ -58,6 +58,7 @@ import com.google.common.collect.Sets; import com.google.common.truth.Correspondence.DiffFormatter; import com.google.common.truth.SubjectUtils.DuplicateGroupedAndTyped; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.DoNotCall; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; @@ -89,7 +90,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; // Can't be final since MultisetSubject and SortedSetSubject extend it public class IterableSubject extends Subject { - private final Iterable<?> actual; + private final @Nullable Iterable<?> actual; /** * Constructor for use by subclasses. If you want to create an instance of this class itself, call @@ -140,14 +141,14 @@ public class IterableSubject extends Subject { /** Fails if the subject is not empty. */ public final void isEmpty() { - if (!Iterables.isEmpty(actual)) { + if (!Iterables.isEmpty(checkNotNull(actual))) { failWithActual(simpleFact("expected to be empty")); } } /** Fails if the subject is empty. */ public final void isNotEmpty() { - if (Iterables.isEmpty(actual)) { + if (Iterables.isEmpty(checkNotNull(actual))) { failWithoutActual(simpleFact("expected not to be empty")); } } @@ -155,14 +156,14 @@ public class IterableSubject extends Subject { /** Fails if the subject does not have the given size. */ public final void hasSize(int expectedSize) { checkArgument(expectedSize >= 0, "expectedSize(%s) must be >= 0", expectedSize); - int actualSize = size(actual); + int actualSize = size(checkNotNull(actual)); check("size()").that(actualSize).isEqualTo(expectedSize); } /** Checks (with a side-effect failure) that the subject contains the supplied item. */ public final void contains(@Nullable Object element) { - if (!Iterables.contains(actual, element)) { - List<Object> elementList = newArrayList(element); + if (!Iterables.contains(checkNotNull(actual), element)) { + List<@Nullable Object> elementList = newArrayList(element); if (hasMatchingToStringPair(actual, elementList)) { failWithoutActual( fact("expected to contain", element), @@ -171,7 +172,7 @@ public class IterableSubject extends Subject { fact( "though it did contain", countDuplicatesAndAddTypeInfo( - retainMatchingToString(actual, elementList /* itemsToCheck */))), + retainMatchingToString(actual, /* itemsToCheck= */ elementList))), fullContents()); } else { failWithActual("expected to contain", element); @@ -181,7 +182,7 @@ public class IterableSubject extends Subject { /** Checks (with a side-effect failure) that the subject does not contain the supplied item. */ public final void doesNotContain(@Nullable Object element) { - if (Iterables.contains(actual, element)) { + if (Iterables.contains(checkNotNull(actual), element)) { failWithActual("expected not to contain", element); } } @@ -189,7 +190,7 @@ public class IterableSubject extends Subject { /** Checks that the subject does not contain duplicate elements. */ public final void containsNoDuplicates() { List<Multiset.Entry<?>> duplicates = newArrayList(); - for (Multiset.Entry<?> entry : LinkedHashMultiset.create(actual).entrySet()) { + for (Multiset.Entry<?> entry : LinkedHashMultiset.create(checkNotNull(actual)).entrySet()) { if (entry.getCount() > 1) { duplicates.add(entry); } @@ -204,7 +205,7 @@ public class IterableSubject extends Subject { /** Checks that the subject contains at least one of the provided objects or fails. */ public final void containsAnyOf( - @Nullable Object first, @Nullable Object second, @Nullable Object /*@Nullable*/... rest) { + @Nullable Object first, @Nullable Object second, @Nullable Object @Nullable ... rest) { containsAnyIn(accumulate(first, second, rest)); } @@ -213,8 +214,9 @@ public class IterableSubject extends Subject { * collection or fails. */ // TODO(cpovirk): Consider using makeElementFacts-style messages here, in contains(), etc. - public final void containsAnyIn(Iterable<?> expected) { - Collection<?> actual = iterableToCollection(this.actual); + public final void containsAnyIn(@Nullable Iterable<?> expected) { + checkNotNull(expected); + Collection<?> actual = iterableToCollection(checkNotNull(this.actual)); for (Object item : expected) { if (actual.contains(item)) { return; @@ -227,7 +229,7 @@ public class IterableSubject extends Subject { fact( "though it did contain", countDuplicatesAndAddTypeInfo( - retainMatchingToString(this.actual, expected /* itemsToCheck */))), + retainMatchingToString(checkNotNull(this.actual), /* itemsToCheck= */ expected))), fullContents()); } else { failWithActual("expected to contain any of", expected); @@ -238,6 +240,7 @@ public class IterableSubject extends Subject { * Checks that the subject contains at least one of the objects contained in the provided array or * fails. */ + @SuppressWarnings("AvoidObjectArrays") public final void containsAnyIn(Object[] expected) { containsAnyIn(asList(expected)); } @@ -255,7 +258,7 @@ public class IterableSubject extends Subject { public final Ordered containsAtLeast( @Nullable Object firstExpected, @Nullable Object secondExpected, - @Nullable Object /*@Nullable*/... restOfExpected) { + @Nullable Object @Nullable ... restOfExpected) { return containsAtLeastElementsIn(accumulate(firstExpected, secondExpected, restOfExpected)); } @@ -270,11 +273,11 @@ public class IterableSubject extends Subject { */ @CanIgnoreReturnValue public final Ordered containsAtLeastElementsIn(Iterable<?> expectedIterable) { - List<?> actual = Lists.newLinkedList(this.actual); - final Collection<?> expected = iterableToCollection(expectedIterable); + List<?> actual = Lists.newLinkedList(checkNotNull(this.actual)); + Collection<?> expected = iterableToCollection(expectedIterable); - List<Object> missing = newArrayList(); - List<Object> actualNotInOrder = newArrayList(); + List<@Nullable Object> missing = newArrayList(); + List<@Nullable Object> actualNotInOrder = newArrayList(); boolean ordered = true; // step through the expected elements... @@ -298,22 +301,23 @@ public class IterableSubject extends Subject { return failAtLeast(expected, missing); } - /* - * TODO(cpovirk): In the NotInOrder case, also include a Fact that shows _only_ the required - * elements (that is, without any extras) but in the order they were actually found. That should - * make it easier for users to compare the actual order of the required elements to the expected - * order. Or, if that's too much trouble, at least try to find a better title for the full - * actual iterable than the default of "but was," which may _sound_ like it should show only the - * required elements, rather than the full actual iterable. - */ return ordered ? IN_ORDER : new Ordered() { @Override public void inOrder() { - failWithActual( - simpleFact("required elements were all found, but order was wrong"), - fact("expected order for required elements", expected)); + ImmutableList.Builder<Fact> facts = ImmutableList.builder(); + facts.add(simpleFact("required elements were all found, but order was wrong")); + facts.add(fact("expected order for required elements", expected)); + List<Object> actualOrder = + Lists.newArrayList(checkNotNull(IterableSubject.this.actual)); + if (actualOrder.retainAll(expected)) { + facts.add(fact("but order was", actualOrder)); + facts.add(fullContents()); + failWithoutActual(facts.build()); + } else { + failWithActual(facts.build()); + } } }; } @@ -328,13 +332,14 @@ public class IterableSubject extends Subject { * within the actual elements, but they are not required to be consecutive. */ @CanIgnoreReturnValue + @SuppressWarnings("AvoidObjectArrays") public final Ordered containsAtLeastElementsIn(Object[] expected) { return containsAtLeastElementsIn(asList(expected)); } private Ordered failAtLeast(Collection<?> expected, Collection<?> missingRawObjects) { - Collection<?> nearMissRawObjects = - retainMatchingToString(actual, missingRawObjects /* itemsToCheck */); + List<?> nearMissRawObjects = + retainMatchingToString(checkNotNull(actual), /* itemsToCheck= */ missingRawObjects); ImmutableList.Builder<Fact> facts = ImmutableList.builder(); facts.addAll( @@ -359,7 +364,8 @@ public class IterableSubject extends Subject { * Removes at most the given number of available elements from the input list and adds them to the * given output collection. */ - private static void moveElements(List<?> input, Collection<Object> output, int maxElements) { + private static void moveElements( + List<?> input, Collection<@Nullable Object> output, int maxElements) { for (int i = 0; i < maxElements; i++) { output.add(input.remove(0)); } @@ -379,8 +385,9 @@ public class IterableSubject extends Subject { * elements, not an element itself. This helps human readers and avoids a compiler warning. */ @CanIgnoreReturnValue - public final Ordered containsExactly(@Nullable Object /*@Nullable*/... varargs) { - List<Object> expected = (varargs == null) ? newArrayList((Object) null) : asList(varargs); + public final Ordered containsExactly(@Nullable Object @Nullable ... varargs) { + List<@Nullable Object> expected = + (varargs == null) ? newArrayList((@Nullable Object) null) : asList(varargs); return containsExactlyElementsIn( expected, varargs != null && varargs.length == 1 && varargs[0] instanceof Iterable); } @@ -396,7 +403,7 @@ public class IterableSubject extends Subject { * on the object returned by this method. */ @CanIgnoreReturnValue - public final Ordered containsExactlyElementsIn(Iterable<?> expected) { + public final Ordered containsExactlyElementsIn(@Nullable Iterable<?> expected) { return containsExactlyElementsIn(expected, false); } @@ -410,13 +417,19 @@ public class IterableSubject extends Subject { * on the object returned by this method. */ @CanIgnoreReturnValue - public final Ordered containsExactlyElementsIn(Object[] expected) { - return containsExactlyElementsIn(asList(expected)); + @SuppressWarnings({ + "AvoidObjectArrays", + "ContainsExactlyElementsInUnnecessaryWrapperAroundArray", + "ContainsExactlyElementsInWithVarArgsToExactly" + }) + public final Ordered containsExactlyElementsIn(@Nullable Object @Nullable [] expected) { + return containsExactlyElementsIn(asList(checkNotNull(expected))); } private Ordered containsExactlyElementsIn( - final Iterable<?> required, boolean addElementsInWarning) { - Iterator<?> actualIter = actual.iterator(); + @Nullable Iterable<?> required, boolean addElementsInWarning) { + checkNotNull(required); + Iterator<?> actualIter = checkNotNull(actual).iterator(); Iterator<?> requiredIter = required.iterator(); if (!requiredIter.hasNext()) { @@ -462,12 +475,12 @@ public class IterableSubject extends Subject { return ALREADY_FAILED; } // Missing elements; elements that are not missing will be removed as we iterate. - Collection<Object> missing = newArrayList(); + List<@Nullable Object> missing = newArrayList(); missing.add(requiredElement); Iterators.addAll(missing, requiredIter); // Extra elements that the subject had but shouldn't have. - Collection<Object> extra = newArrayList(); + List<@Nullable Object> extra = newArrayList(); // Remove all actual elements from missing, and add any that weren't in missing // to extra. @@ -694,7 +707,7 @@ public class IterableSubject extends Subject { public final void containsNoneOf( @Nullable Object firstExcluded, @Nullable Object secondExcluded, - @Nullable Object /*@Nullable*/... restOfExcluded) { + @Nullable Object @Nullable ... restOfExcluded) { containsNoneIn(accumulate(firstExcluded, secondExcluded, restOfExcluded)); } @@ -704,8 +717,8 @@ public class IterableSubject extends Subject { * elements equal any of the excluded.) */ public final void containsNoneIn(Iterable<?> excluded) { - Collection<?> actual = iterableToCollection(this.actual); - Collection<Object> present = new ArrayList<>(); + Collection<?> actual = iterableToCollection(checkNotNull(this.actual)); + List<@Nullable Object> present = new ArrayList<>(); for (Object item : Sets.newLinkedHashSet(excluded)) { if (actual.contains(item)) { present.add(item); @@ -724,25 +737,16 @@ public class IterableSubject extends Subject { * or fails. (Duplicates are irrelevant to this test, which fails if any of the actual elements * equal any of the excluded.) */ - public final void containsNoneIn(Object[] excluded) { + @SuppressWarnings("AvoidObjectArrays") + public final void containsNoneIn(@Nullable Object[] excluded) { containsNoneIn(asList(excluded)); } /** Ordered implementation that does nothing because it's already known to be true. */ - @SuppressWarnings("UnnecessaryAnonymousClass") // for Java 7 compatibility - private static final Ordered IN_ORDER = - new Ordered() { - @Override - public void inOrder() {} - }; + private static final Ordered IN_ORDER = () -> {}; /** Ordered implementation that does nothing because an earlier check already caused a failure. */ - @SuppressWarnings("UnnecessaryAnonymousClass") // for Java 7 compatibility - private static final Ordered ALREADY_FAILED = - new Ordered() { - @Override - public void inOrder() {} - }; + private static final Ordered ALREADY_FAILED = () -> {}; /** * Fails if the iterable is not strictly ordered, according to the natural ordering of its @@ -774,14 +778,14 @@ public class IterableSubject extends Subject { * @throws ClassCastException if any pair of elements is not mutually Comparable */ @SuppressWarnings({"unchecked"}) - public final void isInStrictOrder(final Comparator<?> comparator) { + public final void isInStrictOrder(Comparator<?> comparator) { checkNotNull(comparator); pairwiseCheck( "expected to be in strict order", new PairwiseChecker() { @Override - public boolean check(Object prev, Object next) { - return ((Comparator<Object>) comparator).compare(prev, next) < 0; + public boolean check(@Nullable Object prev, @Nullable Object next) { + return ((Comparator<@Nullable Object>) comparator).compare(prev, next) < 0; } }); } @@ -806,24 +810,24 @@ public class IterableSubject extends Subject { * @throws ClassCastException if any pair of elements is not mutually Comparable */ @SuppressWarnings({"unchecked"}) - public final void isInOrder(final Comparator<?> comparator) { + public final void isInOrder(Comparator<?> comparator) { checkNotNull(comparator); pairwiseCheck( "expected to be in order", new PairwiseChecker() { @Override - public boolean check(Object prev, Object next) { - return ((Comparator<Object>) comparator).compare(prev, next) <= 0; + public boolean check(@Nullable Object prev, @Nullable Object next) { + return ((Comparator<@Nullable Object>) comparator).compare(prev, next) <= 0; } }); } private interface PairwiseChecker { - boolean check(Object prev, Object next); + boolean check(@Nullable Object prev, @Nullable Object next); } private void pairwiseCheck(String expectedFact, PairwiseChecker checker) { - Iterator<?> iterator = actual.iterator(); + Iterator<?> iterator = checkNotNull(actual).iterator(); if (iterator.hasNext()) { Object prev = iterator.next(); while (iterator.hasNext()) { @@ -841,22 +845,27 @@ public class IterableSubject extends Subject { } } - /** @deprecated You probably meant to call {@link #containsNoneOf} instead. */ + /** + * @deprecated You probably meant to call {@link #containsNoneOf} instead. + */ @Override @Deprecated public void isNoneOf( - @Nullable Object first, @Nullable Object second, @Nullable Object /*@Nullable*/... rest) { + @Nullable Object first, @Nullable Object second, @Nullable Object @Nullable ... rest) { super.isNoneOf(first, second, rest); } - /** @deprecated You probably meant to call {@link #containsNoneIn} instead. */ + /** + * @deprecated You probably meant to call {@link #containsNoneIn} instead. + */ @Override @Deprecated - public void isNotIn(Iterable<?> iterable) { + public void isNotIn(@Nullable Iterable<?> iterable) { + checkNotNull(iterable); if (Iterables.contains(iterable, actual)) { failWithActual("expected not to be any of", iterable); } - List<Object> nonIterables = new ArrayList<>(); + List<@Nullable Object> nonIterables = new ArrayList<>(); for (Object element : iterable) { if (!(element instanceof Iterable<?>)) { nonIterables.add(element); @@ -896,8 +905,9 @@ public class IterableSubject extends Subject { * <p>Any of the methods on the returned object may throw {@link ClassCastException} if they * encounter an actual element that is not of type {@code A}. */ - public <A, E> UsingCorrespondence<A, E> comparingElementsUsing( - Correspondence<? super A, ? super E> correspondence) { + public <A extends @Nullable Object, E extends @Nullable Object> + UsingCorrespondence<A, E> comparingElementsUsing( + Correspondence<? super A, ? super E> correspondence) { return new UsingCorrespondence<>(this, correspondence); } @@ -941,7 +951,7 @@ public class IterableSubject extends Subject { * expected elements are of type {@code E}. Call methods on this object to actually execute the * check. */ - public static class UsingCorrespondence<A, E> { + public static class UsingCorrespondence<A extends @Nullable Object, E extends @Nullable Object> { private final IterableSubject subject; private final Correspondence<? super A, ? super E> correspondence; @@ -964,13 +974,54 @@ public class IterableSubject extends Subject { } /** + * @throws UnsupportedOperationException always + * @deprecated {@link Object#equals(Object)} is not supported on Truth subjects or intermediate + * classes. If you are writing a test assertion (actual vs. expected), use methods liks + * {@link #containsExactlyElementsIn(Iterable)} instead. + */ + @DoNotCall( + "UsingCorrespondence.equals() is not supported. Did you mean to call" + + " containsExactlyElementsIn(expected) instead of equals(expected)?") + @Deprecated + @Override + public final boolean equals(@Nullable Object o) { + throw new UnsupportedOperationException( + "UsingCorrespondence.equals() is not supported. Did you mean to call" + + " containsExactlyElementsIn(expected) instead of equals(expected)?"); + } + + /** + * @throws UnsupportedOperationException always + * @deprecated {@link Object#hashCode()} is not supported on Truth types. + */ + @DoNotCall("UsingCorrespondence.hashCode() is not supported.") + @Deprecated + @Override + public final int hashCode() { + throw new UnsupportedOperationException("UsingCorrespondence.hashCode() is not supported."); + } + + /** + * @throws UnsupportedOperationException always + * @deprecated {@link Object#toString()} is not supported on Truth subjects. + */ + @Deprecated + @DoNotCall("UsingCorrespondence.toString() is not supported.") + @Override + public final String toString() { + throw new UnsupportedOperationException( + "UsingCorrespondence.toString() is not supported. Did you mean to call" + + " assertThat(foo.toString()) instead of assertThat(foo).toString()?"); + } + + /** * Specifies a way to pair up unexpected and missing elements in the message when an assertion * fails. For example: * * <pre>{@code * assertThat(actualRecords) * .comparingElementsUsing(RECORD_CORRESPONDENCE) - * .displayingDiffsPairedBy(Record::getId) + * .displayingDiffsPairedBy(MyRecord::getId) * .containsExactlyElementsIn(expectedRecords); * }</pre> * @@ -1056,7 +1107,12 @@ public class IterableSubject extends Subject { * Checks that the subject contains at least one element that corresponds to the given expected * element. */ - public void contains(@Nullable E expected) { + /* + * TODO(cpovirk): Do we want @Nullable on usages of E? Probably not, since it could throw errors + * during comparisons? Or maybe we should take the risk for user convenience? If we make + * changes, also make them in MapSubject, MultimapSubject, and possibly others. + */ + public void contains(E expected) { Correspondence.ExceptionStore exceptions = Correspondence.ExceptionStore.forIterable(); for (A actual : getCastActual()) { if (correspondence.safeCompare(actual, expected, exceptions)) { @@ -1106,7 +1162,7 @@ public class IterableSubject extends Subject { } /** Checks that none of the actual elements correspond to the given element. */ - public void doesNotContain(@Nullable E excluded) { + public void doesNotContain(E excluded) { Correspondence.ExceptionStore exceptions = Correspondence.ExceptionStore.forIterable(); List<A> matchingElements = new ArrayList<>(); for (A actual : getCastActual()) { @@ -1153,7 +1209,7 @@ public class IterableSubject extends Subject { */ @SafeVarargs @CanIgnoreReturnValue - public final Ordered containsExactly(@Nullable E /*@Nullable*/... expected) { + public final Ordered containsExactly(@Nullable E @Nullable ... expected) { return containsExactlyElementsIn( (expected == null) ? newArrayList((E) null) : asList(expected)); } @@ -1167,9 +1223,9 @@ public class IterableSubject extends Subject { * on the object returned by this method. */ @CanIgnoreReturnValue - public Ordered containsExactlyElementsIn(final Iterable<? extends E> expected) { + public Ordered containsExactlyElementsIn(@Nullable Iterable<? extends E> expected) { List<A> actualList = iterableToList(getCastActual()); - List<? extends E> expectedList = iterableToList(expected); + List<? extends E> expectedList = iterableToList(checkNotNull(expected)); if (expectedList.isEmpty()) { if (actualList.isEmpty()) { @@ -1244,8 +1300,9 @@ public class IterableSubject extends Subject { * on the object returned by this method. */ @CanIgnoreReturnValue - public Ordered containsExactlyElementsIn(E[] expected) { - return containsExactlyElementsIn(asList(expected)); + @SuppressWarnings("AvoidObjectArrays") + public Ordered containsExactlyElementsIn(E @Nullable [] expected) { + return containsExactlyElementsIn(asList(checkNotNull(expected))); } /** @@ -1330,7 +1387,7 @@ public class IterableSubject extends Subject { List<? extends A> extra, Correspondence.ExceptionStore exceptions) { if (pairer.isPresent()) { - @Nullable Pairing pairing = pairer.get().pair(missing, extra, exceptions); + Pairing pairing = pairer.get().pair(missing, extra, exceptions); if (pairing != null) { return describeMissingOrExtraWithPairing(pairing, exceptions); } else { @@ -1383,11 +1440,11 @@ public class IterableSubject extends Subject { E missing, List<? extends A> extras, Correspondence.ExceptionStore exceptions) { - List<String> diffs = new ArrayList<>(extras.size()); + List<@Nullable String> diffs = new ArrayList<>(extras.size()); boolean hasDiffs = false; for (int i = 0; i < extras.size(); i++) { A extra = extras.get(i); - @Nullable String diff = correspondence.safeFormatDiff(extra, missing, exceptions); + String diff = correspondence.safeFormatDiff(extra, missing, exceptions); diffs.add(diff); if (diff != null) { hasDiffs = true; @@ -1414,7 +1471,8 @@ public class IterableSubject extends Subject { * Returns all the elements of the given list other than those with the given indexes. Assumes * that all the given indexes really are valid indexes into the list. */ - private <T> List<T> findNotIndexed(List<T> list, Set<Integer> indexes) { + private <T extends @Nullable Object> List<T> findNotIndexed( + List<T> list, Set<Integer> indexes) { if (indexes.size() == list.size()) { // If there are as many distinct valid indexes are there are elements in the list then every // index must be in there once. @@ -1503,8 +1561,7 @@ public class IterableSubject extends Subject { */ @SafeVarargs @CanIgnoreReturnValue - public final Ordered containsAtLeast( - @Nullable E first, @Nullable E second, @Nullable E /*@Nullable*/... rest) { + public final Ordered containsAtLeast(E first, E second, E @Nullable ... rest) { return containsAtLeastElementsIn(accumulate(first, second, rest)); } @@ -1518,7 +1575,7 @@ public class IterableSubject extends Subject { * subject, but they are not required to be consecutive. */ @CanIgnoreReturnValue - public Ordered containsAtLeastElementsIn(final Iterable<? extends E> expected) { + public Ordered containsAtLeastElementsIn(Iterable<? extends E> expected) { List<A> actualList = iterableToList(getCastActual()); List<? extends E> expectedList = iterableToList(expected); // Check if the expected elements correspond in order to any subset of the actual elements. @@ -1584,6 +1641,7 @@ public class IterableSubject extends Subject { * subject, but they are not required to be consecutive. */ @CanIgnoreReturnValue + @SuppressWarnings("AvoidObjectArrays") public Ordered containsAtLeastElementsIn(E[] expected) { return containsAtLeastElementsIn(asList(expected)); } @@ -1667,7 +1725,7 @@ public class IterableSubject extends Subject { List<? extends A> extra, Correspondence.ExceptionStore exceptions) { if (pairer.isPresent()) { - @Nullable Pairing pairing = pairer.get().pair(missing, extra, exceptions); + Pairing pairing = pairer.get().pair(missing, extra, exceptions); if (pairing != null) { return describeMissingWithPairing(pairing, exceptions); } else { @@ -1752,8 +1810,7 @@ public class IterableSubject extends Subject { * expected elements. */ @SafeVarargs - public final void containsAnyOf( - @Nullable E first, @Nullable E second, @Nullable E /*@Nullable*/... rest) { + public final void containsAnyOf(E first, E second, E @Nullable ... rest) { containsAnyIn(accumulate(first, second, rest)); } @@ -1834,6 +1891,7 @@ public class IterableSubject extends Subject { * Checks that the subject contains at least one element that corresponds to at least one of the * expected elements. */ + @SuppressWarnings("AvoidObjectArrays") public void containsAnyIn(E[] expected) { containsAnyIn(asList(expected)); } @@ -1859,9 +1917,7 @@ public class IterableSubject extends Subject { */ @SafeVarargs public final void containsNoneOf( - @Nullable E firstExcluded, - @Nullable E secondExcluded, - @Nullable E /*@Nullable*/... restOfExcluded) { + E firstExcluded, E secondExcluded, E @Nullable ... restOfExcluded) { containsNoneIn(accumulate(firstExcluded, secondExcluded, restOfExcluded)); } @@ -1916,13 +1972,14 @@ public class IterableSubject extends Subject { * (Duplicates are irrelevant to this test, which fails if any of the subject elements * correspond to any of the given elements.) */ + @SuppressWarnings("AvoidObjectArrays") public void containsNoneIn(E[] excluded) { containsNoneIn(asList(excluded)); } @SuppressWarnings("unchecked") // throwing ClassCastException is the correct behaviour private Iterable<A> getCastActual() { - return (Iterable<A>) subject.actual; + return (Iterable<A>) checkNotNull(subject.actual); } // TODO(b/69154276): Consider commoning up some of the logic between IterableSubject.Pairer, @@ -1950,8 +2007,7 @@ public class IterableSubject extends Subject { * Returns a {@link Pairing} of the given expected and actual values, or {@code null} if the * expected values are not uniquely keyed. */ - @Nullable - Pairing pair( + @Nullable Pairing pair( List<? extends E> expectedValues, List<? extends A> actualValues, Correspondence.ExceptionStore exceptions) { @@ -1959,7 +2015,7 @@ public class IterableSubject extends Subject { // Populate expectedKeys with the keys of the corresponding elements of expectedValues. // We do this ahead of time to avoid invoking the key function twice for each element. - List<Object> expectedKeys = new ArrayList<>(expectedValues.size()); + List<@Nullable Object> expectedKeys = new ArrayList<>(expectedValues.size()); for (E expected : expectedValues) { expectedKeys.add(expectedKey(expected, exceptions)); } @@ -1968,7 +2024,7 @@ public class IterableSubject extends Subject { // We will remove the unpaired keys later. Return null if we find a duplicate key. for (int i = 0; i < expectedValues.size(); i++) { E expected = expectedValues.get(i); - @Nullable Object key = expectedKeys.get(i); + Object key = expectedKeys.get(i); if (key != null) { if (pairing.pairedKeysToExpectedValues.containsKey(key)) { return null; @@ -1980,9 +2036,9 @@ public class IterableSubject extends Subject { // Populate pairedKeysToActualValues and unpairedActualValues. for (A actual : actualValues) { - @Nullable Object key = actualKey(actual, exceptions); + Object key = actualKey(actual, exceptions); if (pairing.pairedKeysToExpectedValues.containsKey(key)) { - pairing.pairedKeysToActualValues.put(key, actual); + pairing.pairedKeysToActualValues.put(checkNotNull(key), actual); } else { pairing.unpairedActualValues.add(actual); } @@ -1991,7 +2047,7 @@ public class IterableSubject extends Subject { // Populate unpairedExpectedValues and remove unpaired keys from pairedKeysToExpectedValues. for (int i = 0; i < expectedValues.size(); i++) { E expected = expectedValues.get(i); - @Nullable Object key = expectedKeys.get(i); + Object key = expectedKeys.get(i); if (!pairing.pairedKeysToActualValues.containsKey(key)) { pairing.unpairedExpectedValues.add(expected); pairing.pairedKeysToExpectedValues.remove(key); @@ -2005,7 +2061,7 @@ public class IterableSubject extends Subject { E expectedValue, Iterable<? extends A> actualValues, Correspondence.ExceptionStore exceptions) { - @Nullable Object key = expectedKey(expectedValue, exceptions); + Object key = expectedKey(expectedValue, exceptions); List<A> matches = new ArrayList<>(); if (key != null) { for (A actual : actualValues) { @@ -2038,7 +2094,7 @@ public class IterableSubject extends Subject { } } - /** An description of a pairing between expected and actual values. N.B. This is mutable. */ + /** A description of a pairing between expected and actual values. N.B. This is mutable. */ private final class Pairing { /** diff --git a/core/src/main/java/com/google/common/truth/J2ktIncompatible.java b/core/src/main/java/com/google/common/truth/J2ktIncompatible.java new file mode 100644 index 00000000..45e14be0 --- /dev/null +++ b/core/src/main/java/com/google/common/truth/J2ktIncompatible.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 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.common.truth; + +import com.google.common.annotations.GwtCompatible; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The presence of this annotation on an API indicates that the method may <em>not</em> be used with + * J2kt. This can be removed once we can safely depend on a Guava release that contains it. + */ +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD}) +@GwtCompatible +@interface J2ktIncompatible {} diff --git a/core/src/main/java/com/google/common/truth/LazyMessage.java b/core/src/main/java/com/google/common/truth/LazyMessage.java index aa5b3c54..2e6861a7 100644 --- a/core/src/main/java/com/google/common/truth/LazyMessage.java +++ b/core/src/main/java/com/google/common/truth/LazyMessage.java @@ -20,19 +20,22 @@ import static com.google.common.base.Strings.lenientFormat; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; +import org.checkerframework.checker.nullness.qual.Nullable; final class LazyMessage { - private static final String PLACEHOLDER_ERR = - "Incorrect number of args (%s) for the given placeholders (%s) in string template:\"%s\""; - private final String format; - private final Object[] args; + private final @Nullable Object[] args; - LazyMessage(String format, /*@Nullable*/ Object... args) { + LazyMessage(String format, @Nullable Object... args) { this.format = format; this.args = args; int placeholders = countPlaceholders(format); - checkArgument(placeholders == args.length, PLACEHOLDER_ERR, args.length, placeholders, format); + checkArgument( + placeholders == args.length, + "Incorrect number of args (%s) for the given placeholders (%s) in string template:\"%s\"", + args.length, + placeholders, + format); } @Override diff --git a/core/src/main/java/com/google/common/truth/LongSubject.java b/core/src/main/java/com/google/common/truth/LongSubject.java index 27f99f54..d56de24d 100644 --- a/core/src/main/java/com/google/common/truth/LongSubject.java +++ b/core/src/main/java/com/google/common/truth/LongSubject.java @@ -33,10 +33,12 @@ public class LongSubject extends ComparableSubject<Long> { super(metadata, actual); } - /** @deprecated Use {@link #isEqualTo} instead. Long comparison is consistent with equality. */ + /** + * @deprecated Use {@link #isEqualTo} instead. Long comparison is consistent with equality. + */ @Override @Deprecated - public final void isEquivalentAccordingToCompareTo(Long other) { + public final void isEquivalentAccordingToCompareTo(@Nullable Long other) { super.isEquivalentAccordingToCompareTo(other); } diff --git a/core/src/main/java/com/google/common/truth/MapSubject.java b/core/src/main/java/com/google/common/truth/MapSubject.java index 6315a8e8..a74f1752 100644 --- a/core/src/main/java/com/google/common/truth/MapSubject.java +++ b/core/src/main/java/com/google/common/truth/MapSubject.java @@ -51,7 +51,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; * @author Kurt Alfred Kluever */ public class MapSubject extends Subject { - private final Map<?, ?> actual; + private final @Nullable Map<?, ?> actual; /** * Constructor for use by subclasses. If you want to create an instance of this class itself, call @@ -80,14 +80,14 @@ public class MapSubject extends Subject { /** Fails if the map is not empty. */ public final void isEmpty() { - if (!actual.isEmpty()) { + if (!checkNotNull(actual).isEmpty()) { failWithActual(simpleFact("expected to be empty")); } } /** Fails if the map is empty. */ public final void isNotEmpty() { - if (actual.isEmpty()) { + if (checkNotNull(actual).isEmpty()) { failWithoutActual(simpleFact("expected not to be empty")); } } @@ -95,25 +95,26 @@ public class MapSubject extends Subject { /** Fails if the map does not have the given size. */ public final void hasSize(int expectedSize) { checkArgument(expectedSize >= 0, "expectedSize (%s) must be >= 0", expectedSize); - check("size()").that(actual.size()).isEqualTo(expectedSize); + check("size()").that(checkNotNull(actual).size()).isEqualTo(expectedSize); } /** Fails if the map does not contain the given key. */ public final void containsKey(@Nullable Object key) { - check("keySet()").that(actual.keySet()).contains(key); + check("keySet()").that(checkNotNull(actual).keySet()).contains(key); } /** Fails if the map contains the given key. */ public final void doesNotContainKey(@Nullable Object key) { - check("keySet()").that(actual.keySet()).doesNotContain(key); + check("keySet()").that(checkNotNull(actual).keySet()).doesNotContain(key); } /** Fails if the map does not contain the given entry. */ public final void containsEntry(@Nullable Object key, @Nullable Object value) { - Map.Entry<Object, Object> entry = immutableEntry(key, value); + Map.Entry<@Nullable Object, @Nullable Object> entry = immutableEntry(key, value); + checkNotNull(actual); if (!actual.entrySet().contains(entry)) { - List<Object> keyList = singletonList(key); - List<Object> valueList = singletonList(value); + List<@Nullable Object> keyList = singletonList(key); + List<@Nullable Object> valueList = singletonList(value); if (actual.containsKey(key)) { Object actualValue = actual.get(key); /* @@ -138,7 +139,7 @@ public class MapSubject extends Subject { retainMatchingToString(actual.keySet(), /* itemsToCheck= */ keyList))), fact("full contents", actualCustomStringRepresentationForPackageMembersToCall())); } else if (actual.containsValue(value)) { - Set<Object> keys = new LinkedHashSet<>(); + Set<@Nullable Object> keys = new LinkedHashSet<>(); for (Map.Entry<?, ?> actualEntry : actual.entrySet()) { if (Objects.equal(actualEntry.getValue(), value)) { keys.add(actualEntry.getKey()); @@ -168,7 +169,7 @@ public class MapSubject extends Subject { /** Fails if the map contains the given entry. */ public final void doesNotContainEntry(@Nullable Object key, @Nullable Object value) { checkNoNeedToDisplayBothValues("entrySet()") - .that(actual.entrySet()) + .that(checkNotNull(actual).entrySet()) .doesNotContain(immutableEntry(key, value)); } @@ -188,27 +189,27 @@ public class MapSubject extends Subject { */ @CanIgnoreReturnValue public final Ordered containsExactly( - @Nullable Object k0, @Nullable Object v0, /*@Nullable*/ Object... rest) { + @Nullable Object k0, @Nullable Object v0, @Nullable Object... rest) { return containsExactlyEntriesIn(accumulateMap("containsExactly", k0, v0, rest)); } @CanIgnoreReturnValue public final Ordered containsAtLeast( - @Nullable Object k0, @Nullable Object v0, /*@Nullable*/ Object... rest) { + @Nullable Object k0, @Nullable Object v0, @Nullable Object... rest) { return containsAtLeastEntriesIn(accumulateMap("containsAtLeast", k0, v0, rest)); } - private static Map<Object, Object> accumulateMap( - String functionName, @Nullable Object k0, @Nullable Object v0, /*@Nullable*/ Object... rest) { + private static Map<@Nullable Object, @Nullable Object> accumulateMap( + String functionName, @Nullable Object k0, @Nullable Object v0, @Nullable Object... rest) { checkArgument( rest.length % 2 == 0, "There must be an equal number of key/value pairs " + "(i.e., the number of key/value parameters (%s) must be even).", rest.length + 2); - Map<Object, Object> expectedMap = Maps.newLinkedHashMap(); + Map<@Nullable Object, @Nullable Object> expectedMap = Maps.newLinkedHashMap(); expectedMap.put(k0, v0); - Multiset<Object> keys = LinkedHashMultiset.create(); + Multiset<@Nullable Object> keys = LinkedHashMultiset.create(); keys.add(k0); for (int i = 0; i < rest.length; i += 2) { Object key = rest[i]; @@ -227,7 +228,7 @@ public class MapSubject extends Subject { @CanIgnoreReturnValue public final Ordered containsExactlyEntriesIn(Map<?, ?> expectedMap) { if (expectedMap.isEmpty()) { - if (actual.isEmpty()) { + if (checkNotNull(actual).isEmpty()) { return IN_ORDER; } else { isEmpty(); // fails @@ -236,8 +237,7 @@ public class MapSubject extends Subject { } boolean containsAnyOrder = containsEntriesInAnyOrder(expectedMap, /* allowUnexpected= */ false); if (containsAnyOrder) { - return new MapInOrder( - expectedMap, /* allowUnexpected = */ false, /* correspondence = */ null); + return new MapInOrder(expectedMap, /* allowUnexpected= */ false, /* correspondence= */ null); } else { return ALREADY_FAILED; } @@ -251,7 +251,7 @@ public class MapSubject extends Subject { } boolean containsAnyOrder = containsEntriesInAnyOrder(expectedMap, /* allowUnexpected= */ true); if (containsAnyOrder) { - return new MapInOrder(expectedMap, /* allowUnexpected = */ true, /* correspondence = */ null); + return new MapInOrder(expectedMap, /* allowUnexpected= */ true, /* correspondence= */ null); } else { return ALREADY_FAILED; } @@ -259,8 +259,8 @@ public class MapSubject extends Subject { @CanIgnoreReturnValue private boolean containsEntriesInAnyOrder(Map<?, ?> expectedMap, boolean allowUnexpected) { - MapDifference<Object, Object, Object> diff = - MapDifference.create(actual, expectedMap, allowUnexpected, EQUALITY); + MapDifference<@Nullable Object, @Nullable Object, @Nullable Object> diff = + MapDifference.create(checkNotNull(actual), expectedMap, allowUnexpected, Objects::equal); if (diff.isEmpty()) { return true; } @@ -276,7 +276,7 @@ public class MapSubject extends Subject { // present with the wrong value, which may be the closest we currently get to this.) failWithoutActual( ImmutableList.<Fact>builder() - .addAll(diff.describe(/* differ = */ null)) + .addAll(diff.describe(/* differ= */ null)) .add(simpleFact("---")) .add(fact(allowUnexpected ? "expected to contain at least" : "expected", expectedMap)) .add(butWas()) @@ -284,37 +284,30 @@ public class MapSubject extends Subject { return false; } - private interface ValueTester<A, E> { - boolean test(@Nullable A actualValue, @Nullable E expectedValue); + private interface ValueTester<A extends @Nullable Object, E extends @Nullable Object> { + boolean test(A actualValue, E expectedValue); } - @SuppressWarnings("UnnecessaryAnonymousClass") // for Java 7 compatibility - private static final ValueTester<Object, Object> EQUALITY = - new ValueTester<Object, Object>() { - @Override - public boolean test(@Nullable Object actualValue, @Nullable Object expectedValue) { - return Objects.equal(actualValue, expectedValue); - } - }; - - private interface Differ<A, E> { - String diff(A actual, E expected); + private interface Differ<A extends @Nullable Object, E extends @Nullable Object> { + @Nullable String diff(A actual, E expected); } // This is mostly like the MapDifference code in com.google.common.collect, generalized to remove // the requirement that the values of the two maps are of the same type and are compared with a // symmetric Equivalence. - private static class MapDifference<K, A, E> { + private static class MapDifference< + K extends @Nullable Object, A extends @Nullable Object, E extends @Nullable Object> { private final Map<K, E> missing; private final Map<K, A> unexpected; private final Map<K, ValueDifference<A, E>> wrongValues; private final Set<K> allKeys; - static <K, A, E> MapDifference<K, A, E> create( - Map<? extends K, ? extends A> actual, - Map<? extends K, ? extends E> expected, - boolean allowUnexpected, - ValueTester<? super A, ? super E> valueTester) { + static <K extends @Nullable Object, A extends @Nullable Object, E extends @Nullable Object> + MapDifference<K, A, E> create( + Map<? extends K, ? extends A> actual, + Map<? extends K, ? extends E> expected, + boolean allowUnexpected, + ValueTester<? super A, ? super E> valueTester) { Map<K, A> unexpected = new LinkedHashMap<>(actual); Map<K, E> missing = new LinkedHashMap<>(); Map<K, ValueDifference<A, E>> wrongValues = new LinkedHashMap<>(); @@ -322,7 +315,8 @@ public class MapSubject extends Subject { K expectedKey = expectedEntry.getKey(); E expectedValue = expectedEntry.getValue(); if (actual.containsKey(expectedKey)) { - A actualValue = unexpected.remove(expectedKey); + @SuppressWarnings("UnnecessaryCast") // needed by nullness checker + A actualValue = (A) unexpected.remove(expectedKey); if (!valueTester.test(actualValue, expectedValue)) { wrongValues.put(expectedKey, new ValueDifference<>(actualValue, expectedValue)); } @@ -390,11 +384,11 @@ public class MapSubject extends Subject { } } - private static class ValueDifference<A, E> { + private static class ValueDifference<A extends @Nullable Object, E extends @Nullable Object> { private final A actual; private final E expected; - ValueDifference(@Nullable A actual, @Nullable E expected) { + ValueDifference(A actual, E expected) { this.actual = actual; this.expected = expected; } @@ -417,7 +411,7 @@ public class MapSubject extends Subject { } } - private static String maybeAddType(Object object, boolean includeTypes) { + private static String maybeAddType(@Nullable Object object, boolean includeTypes) { return includeTypes ? lenientFormat("%s (%s)", object, objectToTypeName(object)) : String.valueOf(object); @@ -447,6 +441,7 @@ public class MapSubject extends Subject { @Override public void inOrder() { // We're using the fact that Sets.intersection keeps the order of the first set. + checkNotNull(actual); List<?> expectedKeyOrder = Lists.newArrayList(Sets.intersection(expectedMap.keySet(), actual.keySet())); List<?> actualKeyOrder = @@ -472,20 +467,10 @@ public class MapSubject extends Subject { } /** Ordered implementation that does nothing because it's already known to be true. */ - @SuppressWarnings("UnnecessaryAnonymousClass") // for Java 7 compatibility - private static final Ordered IN_ORDER = - new Ordered() { - @Override - public void inOrder() {} - }; + private static final Ordered IN_ORDER = () -> {}; /** Ordered implementation that does nothing because an earlier check already caused a failure. */ - @SuppressWarnings("UnnecessaryAnonymousClass") // for Java 7 compatibility - private static final Ordered ALREADY_FAILED = - new Ordered() { - @Override - public void inOrder() {} - }; + private static final Ordered ALREADY_FAILED = () -> {}; /** * Starts a method chain for a check in which the actual values (i.e. the values of the {@link @@ -509,8 +494,9 @@ public class MapSubject extends Subject { * encounter an actual value that is not of type {@code A} or an expected value that is not of * type {@code E}. */ - public final <A, E> UsingCorrespondence<A, E> comparingValuesUsing( - Correspondence<? super A, ? super E> correspondence) { + public final <A extends @Nullable Object, E extends @Nullable Object> + UsingCorrespondence<A, E> comparingValuesUsing( + Correspondence<? super A, ? super E> correspondence) { return new UsingCorrespondence<>(correspondence); } @@ -552,7 +538,7 @@ public class MapSubject extends Subject { * * <p>Note that keys will always be compared with regular object equality ({@link Object#equals}). */ - public final class UsingCorrespondence<A, E> { + public final class UsingCorrespondence<A extends @Nullable Object, E extends @Nullable Object> { private final Correspondence<? super A, ? super E> correspondence; @@ -564,18 +550,19 @@ public class MapSubject extends Subject { * Fails if the map does not contain an entry with the given key and a value that corresponds to * the given value. */ - public void containsEntry(@Nullable Object expectedKey, @Nullable E expectedValue) { - if (actual.containsKey(expectedKey)) { + @SuppressWarnings("UnnecessaryCast") // needed by nullness checker + public void containsEntry(@Nullable Object expectedKey, E expectedValue) { + if (checkNotNull(actual).containsKey(expectedKey)) { // Found matching key. A actualValue = getCastSubject().get(expectedKey); Correspondence.ExceptionStore exceptions = Correspondence.ExceptionStore.forMapValues(); - if (correspondence.safeCompare(actualValue, expectedValue, exceptions)) { + if (correspondence.safeCompare((A) actualValue, expectedValue, exceptions)) { // The expected key had the expected value. There's no need to check exceptions here, // because if Correspondence.compare() threw then safeCompare() would return false. return; } // Found matching key with non-matching value. - String diff = correspondence.safeFormatDiff(actualValue, expectedValue, exceptions); + String diff = correspondence.safeFormatDiff((A) actualValue, expectedValue, exceptions); if (diff != null) { failWithoutActual( ImmutableList.<Fact>builder() @@ -600,7 +587,7 @@ public class MapSubject extends Subject { } } else { // Did not find matching key. Look for the matching value with a different key. - Set<Object> keys = new LinkedHashSet<>(); + Set<@Nullable Object> keys = new LinkedHashSet<>(); Correspondence.ExceptionStore exceptions = Correspondence.ExceptionStore.forMapValues(); for (Map.Entry<?, A> actualEntry : getCastSubject().entrySet()) { if (correspondence.safeCompare(actualEntry.getValue(), expectedValue, exceptions)) { @@ -638,12 +625,13 @@ public class MapSubject extends Subject { * Fails if the map contains an entry with the given key and a value that corresponds to the * given value. */ - public void doesNotContainEntry(@Nullable Object excludedKey, @Nullable E excludedValue) { - if (actual.containsKey(excludedKey)) { + @SuppressWarnings("UnnecessaryCast") // needed by nullness checker + public void doesNotContainEntry(@Nullable Object excludedKey, E excludedValue) { + if (checkNotNull(actual).containsKey(excludedKey)) { // Found matching key. Fail if the value matches, too. A actualValue = getCastSubject().get(excludedKey); Correspondence.ExceptionStore exceptions = Correspondence.ExceptionStore.forMapValues(); - if (correspondence.safeCompare(actualValue, excludedValue, exceptions)) { + if (correspondence.safeCompare((A) actualValue, excludedValue, exceptions)) { // The matching key had a matching value. There's no need to check exceptions here, // because if Correspondence.compare() threw then safeCompare() would return false. failWithoutActual( @@ -682,8 +670,7 @@ public class MapSubject extends Subject { // TODO(b/25744307): Can we add an error-prone check that rest.length % 2 == 0? // For bonus points, checking that the even-numbered values are of type E would be sweet. @CanIgnoreReturnValue - public Ordered containsExactly( - @Nullable Object k0, @Nullable E v0, /*@Nullable*/ Object... rest) { + public Ordered containsExactly(@Nullable Object k0, @Nullable E v0, @Nullable Object... rest) { @SuppressWarnings("unchecked") // throwing ClassCastException is the correct behaviour Map<Object, E> expectedMap = (Map<Object, E>) accumulateMap("containsExactly", k0, v0, rest); return containsExactlyEntriesIn(expectedMap); @@ -702,8 +689,7 @@ public class MapSubject extends Subject { // TODO(b/25744307): Can we add an error-prone check that rest.length % 2 == 0? // For bonus points, checking that the even-numbered values are of type E would be sweet. @CanIgnoreReturnValue - public Ordered containsAtLeast( - @Nullable Object k0, @Nullable E v0, /*@Nullable*/ Object... rest) { + public Ordered containsAtLeast(@Nullable Object k0, @Nullable E v0, @Nullable Object... rest) { @SuppressWarnings("unchecked") // throwing ClassCastException is the correct behaviour Map<Object, E> expectedMap = (Map<Object, E>) accumulateMap("containsAtLeast", k0, v0, rest); return containsAtLeastEntriesIn(expectedMap); @@ -716,14 +702,14 @@ public class MapSubject extends Subject { @CanIgnoreReturnValue public Ordered containsExactlyEntriesIn(Map<?, ? extends E> expectedMap) { if (expectedMap.isEmpty()) { - if (actual.isEmpty()) { + if (checkNotNull(actual).isEmpty()) { return IN_ORDER; } else { isEmpty(); // fails return ALREADY_FAILED; } } - return internalContainsEntriesIn(expectedMap, /* allowUnexpected = */ false); + return internalContainsEntriesIn(expectedMap, /* allowUnexpected= */ false); } /** @@ -735,13 +721,13 @@ public class MapSubject extends Subject { if (expectedMap.isEmpty()) { return IN_ORDER; } - return internalContainsEntriesIn(expectedMap, /* allowUnexpected = */ true); + return internalContainsEntriesIn(expectedMap, /* allowUnexpected= */ true); } - private <K, V extends E> Ordered internalContainsEntriesIn( + private <K extends @Nullable Object, V extends E> Ordered internalContainsEntriesIn( Map<K, V> expectedMap, boolean allowUnexpected) { - final Correspondence.ExceptionStore exceptions = Correspondence.ExceptionStore.forMapValues(); - MapDifference<Object, A, V> diff = + Correspondence.ExceptionStore exceptions = Correspondence.ExceptionStore.forMapValues(); + MapDifference<@Nullable Object, A, V> diff = MapDifference.create( getCastSubject(), expectedMap, @@ -770,19 +756,13 @@ public class MapSubject extends Subject { return ALREADY_FAILED; } - @SuppressWarnings("UnnecessaryAnonymousClass") // for Java 7 compatibility - private <V extends E> Differ<A, V> differ(final Correspondence.ExceptionStore exceptions) { - return new Differ<A, V>() { - @Override - public String diff(A actual, V expected) { - return correspondence.safeFormatDiff(actual, expected, exceptions); - } - }; + private <V extends E> Differ<A, V> differ(Correspondence.ExceptionStore exceptions) { + return (actual, expected) -> correspondence.safeFormatDiff(actual, expected, exceptions); } @SuppressWarnings("unchecked") // throwing ClassCastException is the correct behaviour private Map<?, A> getCastSubject() { - return (Map<?, A>) actual; + return (Map<?, A>) checkNotNull(actual); } } } diff --git a/core/src/main/java/com/google/common/truth/MultimapSubject.java b/core/src/main/java/com/google/common/truth/MultimapSubject.java index 332da4ae..4a02216f 100644 --- a/core/src/main/java/com/google/common/truth/MultimapSubject.java +++ b/core/src/main/java/com/google/common/truth/MultimapSubject.java @@ -40,6 +40,7 @@ import com.google.common.collect.Sets; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; @@ -56,14 +57,9 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class MultimapSubject extends Subject { /** Ordered implementation that does nothing because an earlier check already caused a failure. */ - @SuppressWarnings("UnnecessaryAnonymousClass") // for Java 7 compatibility - private static final Ordered ALREADY_FAILED = - new Ordered() { - @Override - public void inOrder() {} - }; + private static final Ordered ALREADY_FAILED = () -> {}; - private final Multimap<?, ?> actual; + private final @Nullable Multimap<?, ?> actual; /** * Constructor for use by subclasses. If you want to create an instance of this class itself, call @@ -83,14 +79,14 @@ public class MultimapSubject extends Subject { /** Fails if the multimap is not empty. */ public final void isEmpty() { - if (!actual.isEmpty()) { + if (!checkNotNull(actual).isEmpty()) { failWithActual(simpleFact("expected to be empty")); } } /** Fails if the multimap is empty. */ public final void isNotEmpty() { - if (actual.isEmpty()) { + if (checkNotNull(actual).isEmpty()) { failWithoutActual(simpleFact("expected not to be empty")); } } @@ -98,25 +94,27 @@ public class MultimapSubject extends Subject { /** Fails if the multimap does not have the given size. */ public final void hasSize(int expectedSize) { checkArgument(expectedSize >= 0, "expectedSize(%s) must be >= 0", expectedSize); - check("size()").that(actual.size()).isEqualTo(expectedSize); + check("size()").that(checkNotNull(actual).size()).isEqualTo(expectedSize); } /** Fails if the multimap does not contain the given key. */ public final void containsKey(@Nullable Object key) { - check("keySet()").that(actual.keySet()).contains(key); + check("keySet()").that(checkNotNull(actual).keySet()).contains(key); } /** Fails if the multimap contains the given key. */ public final void doesNotContainKey(@Nullable Object key) { - check("keySet()").that(actual.keySet()).doesNotContain(key); + check("keySet()").that(checkNotNull(actual).keySet()).doesNotContain(key); } /** Fails if the multimap does not contain the given entry. */ public final void containsEntry(@Nullable Object key, @Nullable Object value) { // TODO(kak): Can we share any of this logic w/ MapSubject.containsEntry()? + checkNotNull(actual); if (!actual.containsEntry(key, value)) { - Map.Entry<Object, Object> entry = immutableEntry(key, value); - List<Map.Entry<Object, Object>> entryList = ImmutableList.of(entry); + Map.Entry<@Nullable Object, @Nullable Object> entry = immutableEntry(key, value); + ImmutableList<Map.Entry<@Nullable Object, @Nullable Object>> entryList = + ImmutableList.of(entry); // TODO(cpovirk): If the key is present but not with the right value, we could fail using // something like valuesForKey(key).contains(value). Consider whether this is worthwhile. if (hasMatchingToStringPair(actual.entries(), entryList)) { @@ -136,7 +134,7 @@ public class MultimapSubject extends Subject { fact("though it did contain values with that key", actual.asMap().get(key)), fact("full contents", actualCustomStringRepresentationForPackageMembersToCall())); } else if (actual.containsValue(value)) { - Set<Object> keys = new LinkedHashSet<>(); + Set<@Nullable Object> keys = new LinkedHashSet<>(); for (Map.Entry<?, ?> actualEntry : actual.entries()) { if (Objects.equal(actualEntry.getValue(), value)) { keys.add(actualEntry.getKey()); @@ -156,7 +154,7 @@ public class MultimapSubject extends Subject { /** Fails if the multimap contains the given entry. */ public final void doesNotContainEntry(@Nullable Object key, @Nullable Object value) { checkNoNeedToDisplayBothValues("entries()") - .that(actual.entries()) + .that(checkNotNull(actual).entries()) .doesNotContain(immutableEntry(key, value)); } @@ -177,7 +175,8 @@ public class MultimapSubject extends Subject { * currently they must perform it _after_. */ public IterableSubject valuesForKey(@Nullable Object key) { - return check("valuesForKey(%s)", key).that(((Multimap<Object, Object>) actual).get(key)); + return check("valuesForKey(%s)", key) + .that(((Multimap<@Nullable Object, @Nullable Object>) checkNotNull(actual)).get(key)); } @Override @@ -202,9 +201,9 @@ public class MultimapSubject extends Subject { lenientFormat( "a %s cannot equal a %s if either is non-empty", actualType, otherType))); } else if (actual instanceof ListMultimap) { - containsExactlyEntriesIn((Multimap<?, ?>) other).inOrder(); + containsExactlyEntriesIn((Multimap<?, ?>) checkNotNull(other)).inOrder(); } else if (actual instanceof SetMultimap) { - containsExactlyEntriesIn((Multimap<?, ?>) other); + containsExactlyEntriesIn((Multimap<?, ?>) checkNotNull(other)); } else { super.isEqualTo(other); } @@ -221,6 +220,7 @@ public class MultimapSubject extends Subject { @CanIgnoreReturnValue public final Ordered containsExactlyEntriesIn(Multimap<?, ?> expectedMultimap) { checkNotNull(expectedMultimap, "expectedMultimap"); + checkNotNull(actual); ListMultimap<?, ?> missing = difference(expectedMultimap, actual); ListMultimap<?, ?> extra = difference(actual, expectedMultimap); @@ -276,6 +276,7 @@ public class MultimapSubject extends Subject { @CanIgnoreReturnValue public final Ordered containsAtLeastEntriesIn(Multimap<?, ?> expectedMultimap) { checkNotNull(expectedMultimap, "expectedMultimap"); + checkNotNull(actual); ListMultimap<?, ?> missing = difference(expectedMultimap, actual); // TODO(kak): Possible enhancement: Include "[1 copy]" if the element does appear in @@ -293,8 +294,9 @@ public class MultimapSubject extends Subject { /** Fails if the multimap is not empty. */ @CanIgnoreReturnValue + @SuppressWarnings("deprecation") // TODO(b/134064106): design an alternative to no-arg check() public final Ordered containsExactly() { - return check().about(iterableEntries()).that(actual.entries()).containsExactly(); + return check().about(iterableEntries()).that(checkNotNull(actual).entries()).containsExactly(); } /** @@ -305,7 +307,7 @@ public class MultimapSubject extends Subject { */ @CanIgnoreReturnValue public final Ordered containsExactly( - @Nullable Object k0, @Nullable Object v0, /*@Nullable*/ Object... rest) { + @Nullable Object k0, @Nullable Object v0, @Nullable Object... rest) { return containsExactlyEntriesIn(accumulateMultimap(k0, v0, rest)); } @@ -317,19 +319,20 @@ public class MultimapSubject extends Subject { */ @CanIgnoreReturnValue public final Ordered containsAtLeast( - @Nullable Object k0, @Nullable Object v0, /*@Nullable*/ Object... rest) { + @Nullable Object k0, @Nullable Object v0, @Nullable Object... rest) { return containsAtLeastEntriesIn(accumulateMultimap(k0, v0, rest)); } - private static Multimap<Object, Object> accumulateMultimap( - @Nullable Object k0, @Nullable Object v0, /*@Nullable*/ Object... rest) { + private static ListMultimap<@Nullable Object, @Nullable Object> accumulateMultimap( + @Nullable Object k0, @Nullable Object v0, @Nullable Object... rest) { checkArgument( rest.length % 2 == 0, "There must be an equal number of key/value pairs " + "(i.e., the number of key/value parameters (%s) must be even).", rest.length + 2); - LinkedListMultimap<Object, Object> expectedMultimap = LinkedListMultimap.create(); + LinkedListMultimap<@Nullable Object, @Nullable Object> expectedMultimap = + LinkedListMultimap.create(); expectedMultimap.put(k0, v0); for (int i = 0; i < rest.length; i += 2) { expectedMultimap.put(rest[i], rest[i + 1]); @@ -340,8 +343,8 @@ public class MultimapSubject extends Subject { private Factory<IterableSubject, Iterable<?>> iterableEntries() { return new Factory<IterableSubject, Iterable<?>>() { @Override - public IterableSubject createSubject(FailureMetadata metadata, Iterable<?> actual) { - return new IterableEntries(metadata, MultimapSubject.this, actual); + public IterableSubject createSubject(FailureMetadata metadata, @Nullable Iterable<?> actual) { + return new IterableEntries(metadata, MultimapSubject.this, checkNotNull(actual)); } }; } @@ -352,7 +355,7 @@ public class MultimapSubject extends Subject { IterableEntries(FailureMetadata metadata, MultimapSubject multimapSubject, Iterable<?> actual) { super(metadata, actual); // We want to use the multimap's toString() instead of the iterable of entries' toString(): - this.stringRepresentation = multimapSubject.actual.toString(); + this.stringRepresentation = String.valueOf(multimapSubject.actual); } @Override @@ -379,18 +382,19 @@ public class MultimapSubject extends Subject { @Override public void inOrder() { // We use the fact that Sets.intersection's result has the same order as the first parameter + checkNotNull(actual); boolean keysInOrder = Lists.newArrayList(Sets.intersection(actual.keySet(), expectedMultimap.keySet())) .equals(Lists.newArrayList(expectedMultimap.keySet())); - LinkedHashSet<Object> keysWithValuesOutOfOrder = Sets.newLinkedHashSet(); + LinkedHashSet<@Nullable Object> keysWithValuesOutOfOrder = Sets.newLinkedHashSet(); for (Object key : expectedMultimap.keySet()) { List<?> actualVals = Lists.newArrayList(get(actual, key)); List<?> expectedVals = Lists.newArrayList(get(expectedMultimap, key)); Iterator<?> actualIterator = actualVals.iterator(); for (Object value : expectedVals) { if (!advanceToFind(actualIterator, value)) { - keysWithValuesOutOfOrder.add(key); + boolean unused = keysWithValuesOutOfOrder.add(key); break; } } @@ -432,7 +436,7 @@ public class MultimapSubject extends Subject { * where the contract explicitly states that the iterator isn't advanced beyond the value if the * value is found. */ - private static boolean advanceToFind(Iterator<?> iterator, Object value) { + private static boolean advanceToFind(Iterator<?> iterator, @Nullable Object value) { while (iterator.hasNext()) { if (Objects.equal(iterator.next(), value)) { return true; @@ -441,16 +445,18 @@ public class MultimapSubject extends Subject { return false; } - private static <V> Collection<V> get(Multimap<?, V> multimap, @Nullable Object key) { + @SuppressWarnings("EmptyList") // ImmutableList doesn't support nullable types + private static <V extends @Nullable Object> Collection<V> get( + Multimap<?, V> multimap, @Nullable Object key) { if (multimap.containsKey(key)) { - return multimap.asMap().get(key); + return checkNotNull(multimap.asMap().get(key)); } else { - return ImmutableList.of(); + return Collections.emptyList(); } } private static ListMultimap<?, ?> difference(Multimap<?, ?> minuend, Multimap<?, ?> subtrahend) { - ListMultimap<Object, Object> difference = LinkedListMultimap.create(); + ListMultimap<@Nullable Object, @Nullable Object> difference = LinkedListMultimap.create(); for (Object key : minuend.keySet()) { List<?> valDifference = difference( @@ -461,8 +467,9 @@ public class MultimapSubject extends Subject { } private static List<?> difference(List<?> minuend, List<?> subtrahend) { - LinkedHashMultiset<Object> remaining = LinkedHashMultiset.<Object>create(subtrahend); - List<Object> difference = Lists.newArrayList(); + LinkedHashMultiset<@Nullable Object> remaining = + LinkedHashMultiset.<@Nullable Object>create(subtrahend); + List<@Nullable Object> difference = Lists.newArrayList(); for (Object elem : minuend) { if (!remaining.remove(elem)) { difference.add(elem); @@ -492,11 +499,15 @@ public class MultimapSubject extends Subject { */ private static Multimap<?, ?> annotateEmptyStringsMultimap(Multimap<?, ?> multimap) { if (multimap.containsKey("") || multimap.containsValue("")) { - ListMultimap<Object, Object> annotatedMultimap = LinkedListMultimap.create(); + ListMultimap<@Nullable Object, @Nullable Object> annotatedMultimap = + LinkedListMultimap.create(); for (Map.Entry<?, ?> entry : multimap.entries()) { - Object key = "".equals(entry.getKey()) ? HUMAN_UNDERSTANDABLE_EMPTY_STRING : entry.getKey(); + Object key = + Objects.equal(entry.getKey(), "") ? HUMAN_UNDERSTANDABLE_EMPTY_STRING : entry.getKey(); Object value = - "".equals(entry.getValue()) ? HUMAN_UNDERSTANDABLE_EMPTY_STRING : entry.getValue(); + Objects.equal(entry.getValue(), "") + ? HUMAN_UNDERSTANDABLE_EMPTY_STRING + : entry.getValue(); annotatedMultimap.put(key, value); } return annotatedMultimap; @@ -526,8 +537,9 @@ public class MultimapSubject extends Subject { * <p>Any of the methods on the returned object may throw {@link ClassCastException} if they * encounter an actual value that is not of type {@code A}. */ - public <A, E> UsingCorrespondence<A, E> comparingValuesUsing( - Correspondence<? super A, ? super E> correspondence) { + public <A extends @Nullable Object, E extends @Nullable Object> + UsingCorrespondence<A, E> comparingValuesUsing( + Correspondence<? super A, ? super E> correspondence) { return new UsingCorrespondence<>(correspondence); } @@ -542,7 +554,7 @@ public class MultimapSubject extends Subject { * * <p>Note that keys will always be compared with regular object equality ({@link Object#equals}). */ - public final class UsingCorrespondence<A, E> { + public final class UsingCorrespondence<A extends @Nullable Object, E extends @Nullable Object> { private final Correspondence<? super A, ? super E> correspondence; @@ -554,10 +566,10 @@ public class MultimapSubject extends Subject { * Fails if the multimap does not contain an entry with the given key and a value that * corresponds to the given value. */ - public void containsEntry(@Nullable Object expectedKey, @Nullable E expectedValue) { - if (actual.containsKey(expectedKey)) { + public void containsEntry(@Nullable Object expectedKey, E expectedValue) { + if (checkNotNull(actual).containsKey(expectedKey)) { // Found matching key. - Collection<A> actualValues = getCastActual().asMap().get(expectedKey); + Collection<A> actualValues = checkNotNull(getCastActual().asMap().get(expectedKey)); Correspondence.ExceptionStore exceptions = Correspondence.ExceptionStore.forMapValues(); for (A actualValue : actualValues) { if (correspondence.safeCompare(actualValue, expectedValue, exceptions)) { @@ -647,9 +659,9 @@ public class MultimapSubject extends Subject { * Fails if the multimap contains an entry with the given key and a value that corresponds to * the given value. */ - public void doesNotContainEntry(@Nullable Object excludedKey, @Nullable E excludedValue) { - if (actual.containsKey(excludedKey)) { - Collection<A> actualValues = getCastActual().asMap().get(excludedKey); + public void doesNotContainEntry(@Nullable Object excludedKey, E excludedValue) { + if (checkNotNull(actual).containsKey(excludedKey)) { + Collection<A> actualValues = checkNotNull(getCastActual().asMap().get(excludedKey)); List<A> matchingValues = new ArrayList<>(); Correspondence.ExceptionStore exceptions = Correspondence.ExceptionStore.forMapValues(); for (A actualValue : actualValues) { @@ -714,7 +726,8 @@ public class MultimapSubject extends Subject { * public containsExactlyEntriesIn method. This is recommended by Effective Java item 31 (3rd * edition). */ - private <K, V extends E> Ordered internalContainsExactlyEntriesIn( + @SuppressWarnings("deprecation") // TODO(b/134064106): design an alternative to no-arg check() + private <K extends @Nullable Object, V extends E> Ordered internalContainsExactlyEntriesIn( Multimap<K, V> expectedMultimap) { // Note: The non-fuzzy MultimapSubject.containsExactlyEntriesIn has a custom implementation // and produces somewhat better failure messages simply asserting about the iterables of @@ -724,8 +737,8 @@ public class MultimapSubject extends Subject { // complexity for little gain. return check() .about(iterableEntries()) - .that(actual.entries()) - .comparingElementsUsing(new EntryCorrespondence<K, A, V>(correspondence)) + .that(checkNotNull(actual).entries()) + .comparingElementsUsing(MultimapSubject.<K, A, V>entryCorrespondence(correspondence)) .containsExactlyElementsIn(expectedMultimap.entries()); } @@ -748,7 +761,8 @@ public class MultimapSubject extends Subject { * public containsAtLeastEntriesIn method. This is recommended by Effective Java item 31 (3rd * edition). */ - private <K, V extends E> Ordered internalContainsAtLeastEntriesIn( + @SuppressWarnings("deprecation") // TODO(b/134064106): design an alternative to no-arg check() + private <K extends @Nullable Object, V extends E> Ordered internalContainsAtLeastEntriesIn( Multimap<K, V> expectedMultimap) { // Note: The non-fuzzy MultimapSubject.containsAtLeastEntriesIn has a custom implementation // and produces somewhat better failure messages simply asserting about the iterables of @@ -758,8 +772,8 @@ public class MultimapSubject extends Subject { // complexity for little gain. return check() .about(iterableEntries()) - .that(actual.entries()) - .comparingElementsUsing(new EntryCorrespondence<K, A, V>(correspondence)) + .that(checkNotNull(actual).entries()) + .comparingElementsUsing(MultimapSubject.<K, A, V>entryCorrespondence(correspondence)) .containsAtLeastElementsIn(expectedMultimap.entries()); } @@ -770,8 +784,7 @@ public class MultimapSubject extends Subject { * key/value pairs at compile time. Please make sure you provide varargs in key/value pairs! */ @CanIgnoreReturnValue - public Ordered containsExactly( - @Nullable Object k0, @Nullable E v0, /*@Nullable*/ Object... rest) { + public Ordered containsExactly(@Nullable Object k0, @Nullable E v0, @Nullable Object... rest) { @SuppressWarnings("unchecked") Multimap<?, E> expectedMultimap = (Multimap<?, E>) accumulateMultimap(k0, v0, rest); return containsExactlyEntriesIn(expectedMultimap); @@ -790,8 +803,7 @@ public class MultimapSubject extends Subject { * key/value pairs at compile time. Please make sure you provide varargs in key/value pairs! */ @CanIgnoreReturnValue - public Ordered containsAtLeast( - @Nullable Object k0, @Nullable E v0, /*@Nullable*/ Object... rest) { + public Ordered containsAtLeast(@Nullable Object k0, @Nullable E v0, @Nullable Object... rest) { @SuppressWarnings("unchecked") Multimap<?, E> expectedMultimap = (Multimap<?, E>) accumulateMultimap(k0, v0, rest); return containsAtLeastEntriesIn(expectedMultimap); @@ -799,30 +811,20 @@ public class MultimapSubject extends Subject { @SuppressWarnings("unchecked") // throwing ClassCastException is the correct behaviour private Multimap<?, A> getCastActual() { - return (Multimap<?, A>) actual; + return (Multimap<?, A>) checkNotNull(actual); } } - private static final class EntryCorrespondence<K, A, E> - extends Correspondence<Map.Entry<K, A>, Map.Entry<K, E>> { - - private final Correspondence<? super A, ? super E> valueCorrespondence; - - EntryCorrespondence(Correspondence<? super A, ? super E> valueCorrespondence) { - this.valueCorrespondence = valueCorrespondence; - } - - @Override - public boolean compare(Map.Entry<K, A> actual, Map.Entry<K, E> expected) { - return Objects.equal(actual.getKey(), expected.getKey()) - && valueCorrespondence.compare(actual.getValue(), expected.getValue()); - } - - @Override - public String toString() { - return lenientFormat( - "has a key that is equal to and a value that %s the key and value of", - valueCorrespondence); - } + private static < + K extends @Nullable Object, A extends @Nullable Object, E extends @Nullable Object> + Correspondence<Map.Entry<K, A>, Map.Entry<K, E>> entryCorrespondence( + Correspondence<? super A, ? super E> valueCorrespondence) { + return Correspondence.from( + (Map.Entry<K, A> actual, Map.Entry<K, E> expected) -> + Objects.equal(actual.getKey(), expected.getKey()) + && valueCorrespondence.compare(actual.getValue(), expected.getValue()), + lenientFormat( + "has a key that is equal to and a value that %s the key and value of", + valueCorrespondence)); } } diff --git a/core/src/main/java/com/google/common/truth/MultisetSubject.java b/core/src/main/java/com/google/common/truth/MultisetSubject.java index 40037fe3..9e8a6315 100644 --- a/core/src/main/java/com/google/common/truth/MultisetSubject.java +++ b/core/src/main/java/com/google/common/truth/MultisetSubject.java @@ -16,6 +16,7 @@ package com.google.common.truth; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.collect.Multiset; import org.checkerframework.checker.nullness.qual.Nullable; @@ -27,7 +28,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; */ public final class MultisetSubject extends IterableSubject { - private final Multiset<?> actual; + private final @Nullable Multiset<?> actual; MultisetSubject(FailureMetadata metadata, @Nullable Multiset<?> multiset) { super(metadata, multiset); @@ -37,7 +38,7 @@ public final class MultisetSubject extends IterableSubject { /** Fails if the element does not have the given count. */ public final void hasCount(@Nullable Object element, int expectedCount) { checkArgument(expectedCount >= 0, "expectedCount(%s) must be >= 0", expectedCount); - int actualCount = ((Multiset<?>) actual).count(element); + int actualCount = checkNotNull(actual).count(element); check("count(%s)", element).that(actualCount).isEqualTo(expectedCount); } } diff --git a/core/src/main/java/com/google/common/truth/ObjectArraySubject.java b/core/src/main/java/com/google/common/truth/ObjectArraySubject.java index cd6b42b5..daa287d2 100644 --- a/core/src/main/java/com/google/common/truth/ObjectArraySubject.java +++ b/core/src/main/java/com/google/common/truth/ObjectArraySubject.java @@ -15,6 +15,8 @@ */ package com.google.common.truth; +import static com.google.common.base.Preconditions.checkNotNull; + import java.util.Arrays; import org.checkerframework.checker.nullness.qual.Nullable; @@ -23,16 +25,16 @@ import org.checkerframework.checker.nullness.qual.Nullable; * * @author Christian Gruber */ -public final class ObjectArraySubject<T> extends AbstractArraySubject { - private final T[] actual; +public final class ObjectArraySubject<T extends @Nullable Object> extends AbstractArraySubject { + private final @Nullable T @Nullable [] actual; ObjectArraySubject( - FailureMetadata metadata, @Nullable T /*@Nullable*/[] o, @Nullable String typeDescription) { + FailureMetadata metadata, @Nullable T @Nullable [] o, @Nullable String typeDescription) { super(metadata, o, typeDescription); this.actual = o; } public IterableSubject asList() { - return checkNoNeedToDisplayBothValues("asList()").that(Arrays.asList(actual)); + return checkNoNeedToDisplayBothValues("asList()").that(Arrays.asList(checkNotNull(actual))); } } diff --git a/core/src/main/java/com/google/common/truth/Ordered.java b/core/src/main/java/com/google/common/truth/Ordered.java index 00ed0cc3..2f7efed3 100644 --- a/core/src/main/java/com/google/common/truth/Ordered.java +++ b/core/src/main/java/com/google/common/truth/Ordered.java @@ -15,6 +15,7 @@ */ package com.google.common.truth; + /** * Returned by calls like {@link IterableSubject#containsExactly}, {@code Ordered} lets the caller * additionally check that the expected elements were present in the order they were passed to the diff --git a/core/src/main/java/com/google/common/truth/Platform.java b/core/src/main/java/com/google/common/truth/Platform.java index 698e4bd2..bc4301a2 100644 --- a/core/src/main/java/com/google/common/truth/Platform.java +++ b/core/src/main/java/com/google/common/truth/Platform.java @@ -15,12 +15,16 @@ */ package com.google.common.truth; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Suppliers.memoize; import static com.google.common.base.Throwables.throwIfUnchecked; import static com.google.common.truth.DiffUtils.generateUnifiedDiff; import static com.google.common.truth.Fact.fact; import com.google.common.base.Joiner; import com.google.common.base.Splitter; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import java.lang.reflect.Constructor; @@ -59,13 +63,16 @@ final class Platform { static Throwable[] getSuppressed(Throwable throwable) { try { Method getSuppressed = throwable.getClass().getMethod("getSuppressed"); - return (Throwable[]) getSuppressed.invoke(throwable); + return (Throwable[]) checkNotNull(getSuppressed.invoke(throwable)); } catch (NoSuchMethodException e) { return new Throwable[0]; } catch (IllegalAccessException e) { - throw new RuntimeException(e); + // We're calling a public method on a public class. + throw newLinkageError(e); } catch (InvocationTargetException e) { - throw new RuntimeException(e); + throwIfUnchecked(e.getCause()); + // getSuppressed has no `throws` clause. + throw newLinkageError(e); } } @@ -78,7 +85,9 @@ final class Platform { * the value passed to {@code assertThat} or {@code that}, as distinct from any later actual * values produced by chaining calls like {@code hasMessageThat}. */ - static String inferDescription() { + // Checker complains that first invoke argument is null. + @SuppressWarnings("argument.type.incompatible") + static @Nullable String inferDescription() { if (isInferDescriptionDisabled()) { return null; } @@ -181,7 +190,7 @@ final class Platform { @Override @SuppressWarnings("UnsynchronizedOverridesSynchronized") - public final Throwable getCause() { + public final @Nullable Throwable getCause() { return cause; } @@ -189,7 +198,7 @@ final class Platform { // TODO(cpovirk): Write a test that fails without this. Ditto for SimpleAssertionError. @Override public final String toString() { - return getLocalizedMessage(); + return checkNotNull(getLocalizedMessage()); } } @@ -201,6 +210,11 @@ final class Platform { return Float.toString(value); } + /** Turns a non-double, non-float object into a string. */ + static String stringValueOfNonFloatingPoint(@Nullable Object o) { + return String.valueOf(o); + } + /** Returns a human readable string representation of the throwable's stack trace. */ static String getStackTraceAsString(Throwable throwable) { return Throwables.getStackTraceAsString(throwable); @@ -208,7 +222,7 @@ final class Platform { /** Tests if current platform is Android. */ static boolean isAndroid() { - return System.getProperty("java.runtime.name").contains("Android"); + return checkNotNull(System.getProperty("java.runtime.name", "")).contains("Android"); } /** @@ -294,4 +308,60 @@ final class Platform { error.initCause(cause); return error; } + + static boolean isKotlinRange(Iterable<?> iterable) { + return closedRangeClassIfAvailable.get() != null + && closedRangeClassIfAvailable.get().isInstance(iterable); + // (If the class isn't available, then nothing could be an instance of ClosedRange.) + } + + // Not using lambda here because of wrong nullability type inference in this case. + private static final Supplier<@Nullable Class<?>> closedRangeClassIfAvailable = + Suppliers.<@Nullable Class<?>>memoize( + () -> { + try { + return Class.forName("kotlin.ranges.ClosedRange"); + /* + * TODO(cpovirk): Consider looking up the Method we'll need here, too: If it's not + * present (maybe because Proguard stripped it, similar to cl/462826082), then we + * don't want our caller to continue on to call kotlinRangeContains, since it won't + * be able to give an answer about what ClosedRange.contains will return. + * (Alternatively, we could make kotlinRangeContains contain its own fallback to + * Iterables.contains. Conceivably its first fallback could even be to try reading + * `start` and `endInclusive` from the ClosedRange instance, but even then, we'd + * want to check in advance whether we're able to access those.) + */ + } catch (ClassNotFoundException notAvailable) { + return null; + } + }); + + static boolean kotlinRangeContains(Iterable<?> haystack, @Nullable Object needle) { + try { + return (boolean) closedRangeContainsMethod.get().invoke(haystack, needle); + } catch (InvocationTargetException e) { + if (e.getCause() instanceof ClassCastException) { + // icky but no worse than what we normally do for isIn(Iterable) + return false; + } + throwIfUnchecked(e.getCause()); + // That method has no `throws` clause. + throw newLinkageError(e.getCause()); + } catch (IllegalAccessException e) { + // We're calling a public method on a public class. + throw newLinkageError(e); + } + } + + private static final Supplier<Method> closedRangeContainsMethod = + memoize( + () -> { + try { + return checkNotNull(closedRangeClassIfAvailable.get()) + .getMethod("contains", Comparable.class); + } catch (NoSuchMethodException e) { + // That method exists. (But see the discussion at closedRangeClassIfAvailable above.) + throw newLinkageError(e); + } + }); } diff --git a/core/src/main/java/com/google/common/truth/PrimitiveBooleanArraySubject.java b/core/src/main/java/com/google/common/truth/PrimitiveBooleanArraySubject.java index 47605bc6..33ff6d89 100644 --- a/core/src/main/java/com/google/common/truth/PrimitiveBooleanArraySubject.java +++ b/core/src/main/java/com/google/common/truth/PrimitiveBooleanArraySubject.java @@ -15,6 +15,8 @@ */ package com.google.common.truth; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.common.primitives.Booleans; import org.checkerframework.checker.nullness.qual.Nullable; @@ -24,15 +26,15 @@ import org.checkerframework.checker.nullness.qual.Nullable; * @author Christian Gruber (cgruber@israfil.net) */ public final class PrimitiveBooleanArraySubject extends AbstractArraySubject { - private final boolean[] actual; + private final boolean @Nullable [] actual; PrimitiveBooleanArraySubject( - FailureMetadata metadata, boolean /*@Nullable*/[] o, @Nullable String typeDescription) { + FailureMetadata metadata, boolean @Nullable [] o, @Nullable String typeDescription) { super(metadata, o, typeDescription); this.actual = o; } public IterableSubject asList() { - return checkNoNeedToDisplayBothValues("asList()").that(Booleans.asList(actual)); + return checkNoNeedToDisplayBothValues("asList()").that(Booleans.asList(checkNotNull(actual))); } } diff --git a/core/src/main/java/com/google/common/truth/PrimitiveByteArraySubject.java b/core/src/main/java/com/google/common/truth/PrimitiveByteArraySubject.java index 59a3c7dd..f8169ca4 100644 --- a/core/src/main/java/com/google/common/truth/PrimitiveByteArraySubject.java +++ b/core/src/main/java/com/google/common/truth/PrimitiveByteArraySubject.java @@ -15,6 +15,8 @@ */ package com.google.common.truth; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.common.primitives.Bytes; import org.checkerframework.checker.nullness.qual.Nullable; @@ -24,15 +26,15 @@ import org.checkerframework.checker.nullness.qual.Nullable; * @author Kurt Alfred Kluever */ public final class PrimitiveByteArraySubject extends AbstractArraySubject { - private final byte[] actual; + private final byte @Nullable [] actual; PrimitiveByteArraySubject( - FailureMetadata metadata, byte /*@Nullable*/[] o, @Nullable String typeDescription) { + FailureMetadata metadata, byte @Nullable [] o, @Nullable String typeDescription) { super(metadata, o, typeDescription); this.actual = o; } public IterableSubject asList() { - return checkNoNeedToDisplayBothValues("asList()").that(Bytes.asList(actual)); + return checkNoNeedToDisplayBothValues("asList()").that(Bytes.asList(checkNotNull(actual))); } } diff --git a/core/src/main/java/com/google/common/truth/PrimitiveCharArraySubject.java b/core/src/main/java/com/google/common/truth/PrimitiveCharArraySubject.java index 6e94b2cf..79e6f12e 100644 --- a/core/src/main/java/com/google/common/truth/PrimitiveCharArraySubject.java +++ b/core/src/main/java/com/google/common/truth/PrimitiveCharArraySubject.java @@ -15,6 +15,8 @@ */ package com.google.common.truth; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.common.primitives.Chars; import org.checkerframework.checker.nullness.qual.Nullable; @@ -24,15 +26,15 @@ import org.checkerframework.checker.nullness.qual.Nullable; * @author Christian Gruber (cgruber@israfil.net) */ public final class PrimitiveCharArraySubject extends AbstractArraySubject { - private final char[] actual; + private final char @Nullable [] actual; PrimitiveCharArraySubject( - FailureMetadata metadata, char /*@Nullable*/[] o, @Nullable String typeDescription) { + FailureMetadata metadata, char @Nullable [] o, @Nullable String typeDescription) { super(metadata, o, typeDescription); this.actual = o; } public IterableSubject asList() { - return checkNoNeedToDisplayBothValues("asList()").that(Chars.asList(actual)); + return checkNoNeedToDisplayBothValues("asList()").that(Chars.asList(checkNotNull(actual))); } } diff --git a/core/src/main/java/com/google/common/truth/PrimitiveDoubleArraySubject.java b/core/src/main/java/com/google/common/truth/PrimitiveDoubleArraySubject.java index dc265105..6fdb2750 100644 --- a/core/src/main/java/com/google/common/truth/PrimitiveDoubleArraySubject.java +++ b/core/src/main/java/com/google/common/truth/PrimitiveDoubleArraySubject.java @@ -31,10 +31,10 @@ import org.checkerframework.checker.nullness.qual.Nullable; * @author Christian Gruber (cgruber@israfil.net) */ public final class PrimitiveDoubleArraySubject extends AbstractArraySubject { - private final double[] actual; + private final double @Nullable [] actual; PrimitiveDoubleArraySubject( - FailureMetadata metadata, double /*@Nullable*/[] o, @Nullable String typeDescription) { + FailureMetadata metadata, double @Nullable [] o, @Nullable String typeDescription) { super(metadata, o, typeDescription); this.actual = o; } @@ -65,7 +65,7 @@ public final class PrimitiveDoubleArraySubject extends AbstractArraySubject { */ // TODO(cpovirk): Move some or all of this Javadoc to the supertype, maybe deleting this override? @Override - public void isEqualTo(Object expected) { + public void isEqualTo(@Nullable Object expected) { super.isEqualTo(expected); } @@ -84,7 +84,7 @@ public final class PrimitiveDoubleArraySubject extends AbstractArraySubject { * </ul> */ @Override - public void isNotEqualTo(Object expected) { + public void isNotEqualTo(@Nullable Object expected) { super.isNotEqualTo(expected); } @@ -234,7 +234,7 @@ public final class PrimitiveDoubleArraySubject extends AbstractArraySubject { private IterableSubject iterableSubject() { return checkNoNeedToDisplayBothValues("asList()") .about(iterablesWithCustomDoubleToString()) - .that(Doubles.asList(actual)); + .that(Doubles.asList(checkNotNull(actual))); } /* @@ -248,7 +248,7 @@ public final class PrimitiveDoubleArraySubject extends AbstractArraySubject { private Factory<IterableSubject, Iterable<?>> iterablesWithCustomDoubleToString() { return new Factory<IterableSubject, Iterable<?>>() { @Override - public IterableSubject createSubject(FailureMetadata metadata, Iterable<?> actual) { + public IterableSubject createSubject(FailureMetadata metadata, @Nullable Iterable<?> actual) { return new IterableSubjectWithInheritedToString(metadata, actual); } }; @@ -256,7 +256,7 @@ public final class PrimitiveDoubleArraySubject extends AbstractArraySubject { private final class IterableSubjectWithInheritedToString extends IterableSubject { - IterableSubjectWithInheritedToString(FailureMetadata metadata, Iterable<?> actual) { + IterableSubjectWithInheritedToString(FailureMetadata metadata, @Nullable Iterable<?> actual) { super(metadata, actual); } diff --git a/core/src/main/java/com/google/common/truth/PrimitiveFloatArraySubject.java b/core/src/main/java/com/google/common/truth/PrimitiveFloatArraySubject.java index ae106d5f..339a4d5b 100644 --- a/core/src/main/java/com/google/common/truth/PrimitiveFloatArraySubject.java +++ b/core/src/main/java/com/google/common/truth/PrimitiveFloatArraySubject.java @@ -31,10 +31,10 @@ import org.checkerframework.checker.nullness.qual.Nullable; * @author Christian Gruber (cgruber@israfil.net) */ public final class PrimitiveFloatArraySubject extends AbstractArraySubject { - private final float[] actual; + private final float @Nullable [] actual; PrimitiveFloatArraySubject( - FailureMetadata metadata, float /*@Nullable*/[] o, @Nullable String typeDescription) { + FailureMetadata metadata, float @Nullable [] o, @Nullable String typeDescription) { super(metadata, o, typeDescription); this.actual = o; } @@ -64,7 +64,7 @@ public final class PrimitiveFloatArraySubject extends AbstractArraySubject { * </ul> */ @Override - public void isEqualTo(Object expected) { + public void isEqualTo(@Nullable Object expected) { super.isEqualTo(expected); } @@ -83,7 +83,7 @@ public final class PrimitiveFloatArraySubject extends AbstractArraySubject { * </ul> */ @Override - public void isNotEqualTo(Object expected) { + public void isNotEqualTo(@Nullable Object expected) { super.isNotEqualTo(expected); } @@ -239,7 +239,7 @@ public final class PrimitiveFloatArraySubject extends AbstractArraySubject { private IterableSubject iterableSubject() { return checkNoNeedToDisplayBothValues("asList()") .about(iterablesWithCustomFloatToString()) - .that(Floats.asList(actual)); + .that(Floats.asList(checkNotNull(actual))); } /* @@ -253,7 +253,7 @@ public final class PrimitiveFloatArraySubject extends AbstractArraySubject { private Factory<IterableSubject, Iterable<?>> iterablesWithCustomFloatToString() { return new Factory<IterableSubject, Iterable<?>>() { @Override - public IterableSubject createSubject(FailureMetadata metadata, Iterable<?> actual) { + public IterableSubject createSubject(FailureMetadata metadata, @Nullable Iterable<?> actual) { return new IterableSubjectWithInheritedToString(metadata, actual); } }; @@ -261,7 +261,7 @@ public final class PrimitiveFloatArraySubject extends AbstractArraySubject { private final class IterableSubjectWithInheritedToString extends IterableSubject { - IterableSubjectWithInheritedToString(FailureMetadata metadata, Iterable<?> actual) { + IterableSubjectWithInheritedToString(FailureMetadata metadata, @Nullable Iterable<?> actual) { super(metadata, actual); } diff --git a/core/src/main/java/com/google/common/truth/PrimitiveIntArraySubject.java b/core/src/main/java/com/google/common/truth/PrimitiveIntArraySubject.java index f2c4abb5..b6c64634 100644 --- a/core/src/main/java/com/google/common/truth/PrimitiveIntArraySubject.java +++ b/core/src/main/java/com/google/common/truth/PrimitiveIntArraySubject.java @@ -15,6 +15,8 @@ */ package com.google.common.truth; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.common.primitives.Ints; import org.checkerframework.checker.nullness.qual.Nullable; @@ -24,15 +26,15 @@ import org.checkerframework.checker.nullness.qual.Nullable; * @author Christian Gruber (cgruber@israfil.net) */ public final class PrimitiveIntArraySubject extends AbstractArraySubject { - private final int[] actual; + private final int @Nullable [] actual; PrimitiveIntArraySubject( - FailureMetadata metadata, int /*@Nullable*/[] o, @Nullable String typeDescription) { + FailureMetadata metadata, int @Nullable [] o, @Nullable String typeDescription) { super(metadata, o, typeDescription); this.actual = o; } public IterableSubject asList() { - return checkNoNeedToDisplayBothValues("asList()").that(Ints.asList(actual)); + return checkNoNeedToDisplayBothValues("asList()").that(Ints.asList(checkNotNull(actual))); } } diff --git a/core/src/main/java/com/google/common/truth/PrimitiveLongArraySubject.java b/core/src/main/java/com/google/common/truth/PrimitiveLongArraySubject.java index 1d5aec0c..07441086 100644 --- a/core/src/main/java/com/google/common/truth/PrimitiveLongArraySubject.java +++ b/core/src/main/java/com/google/common/truth/PrimitiveLongArraySubject.java @@ -15,6 +15,8 @@ */ package com.google.common.truth; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.common.primitives.Longs; import org.checkerframework.checker.nullness.qual.Nullable; @@ -24,15 +26,15 @@ import org.checkerframework.checker.nullness.qual.Nullable; * @author Christian Gruber (cgruber@israfil.net) */ public final class PrimitiveLongArraySubject extends AbstractArraySubject { - private final long[] actual; + private final long @Nullable [] actual; PrimitiveLongArraySubject( - FailureMetadata metadata, long /*@Nullable*/[] o, @Nullable String typeDescription) { + FailureMetadata metadata, long @Nullable [] o, @Nullable String typeDescription) { super(metadata, o, typeDescription); this.actual = o; } public IterableSubject asList() { - return checkNoNeedToDisplayBothValues("asList()").that(Longs.asList(actual)); + return checkNoNeedToDisplayBothValues("asList()").that(Longs.asList(checkNotNull(actual))); } } diff --git a/core/src/main/java/com/google/common/truth/PrimitiveShortArraySubject.java b/core/src/main/java/com/google/common/truth/PrimitiveShortArraySubject.java index cd17c874..dcefab47 100644 --- a/core/src/main/java/com/google/common/truth/PrimitiveShortArraySubject.java +++ b/core/src/main/java/com/google/common/truth/PrimitiveShortArraySubject.java @@ -15,6 +15,8 @@ */ package com.google.common.truth; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.common.primitives.Shorts; import org.checkerframework.checker.nullness.qual.Nullable; @@ -24,15 +26,15 @@ import org.checkerframework.checker.nullness.qual.Nullable; * @author Christian Gruber (cgruber@israfil.net) */ public final class PrimitiveShortArraySubject extends AbstractArraySubject { - private final short[] actual; + private final short @Nullable [] actual; PrimitiveShortArraySubject( - FailureMetadata metadata, short /*@Nullable*/[] o, @Nullable String typeDescription) { + FailureMetadata metadata, short @Nullable [] o, @Nullable String typeDescription) { super(metadata, o, typeDescription); this.actual = o; } public IterableSubject asList() { - return checkNoNeedToDisplayBothValues("asList()").that(Shorts.asList(actual)); + return checkNoNeedToDisplayBothValues("asList()").that(Shorts.asList(checkNotNull(actual))); } } diff --git a/core/src/main/java/com/google/common/truth/SimpleSubjectBuilder.java b/core/src/main/java/com/google/common/truth/SimpleSubjectBuilder.java index 4451ee46..80caa364 100644 --- a/core/src/main/java/com/google/common/truth/SimpleSubjectBuilder.java +++ b/core/src/main/java/com/google/common/truth/SimpleSubjectBuilder.java @@ -44,4 +44,5 @@ public final class SimpleSubjectBuilder<SubjectT extends Subject, ActualT> { public SubjectT that(@Nullable ActualT actual) { return subjectFactory.createSubject(metadata, actual); } + } diff --git a/core/src/main/java/com/google/common/truth/StackTraceCleaner.java b/core/src/main/java/com/google/common/truth/StackTraceCleaner.java index 04ac3980..5efc43a0 100644 --- a/core/src/main/java/com/google/common/truth/StackTraceCleaner.java +++ b/core/src/main/java/com/google/common/truth/StackTraceCleaner.java @@ -16,6 +16,7 @@ package com.google.common.truth; import static com.google.common.base.MoreObjects.firstNonNull; +import static com.google.common.base.Preconditions.checkNotNull; import static java.lang.Thread.currentThread; import com.google.common.annotations.GwtIncompatible; @@ -26,9 +27,11 @@ import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import java.util.Set; +import org.checkerframework.checker.nullness.qual.Nullable; /** Utility that cleans stack traces to remove noise from common frameworks. */ @GwtIncompatible +@J2ktIncompatible final class StackTraceCleaner { static final String CLEANER_LINK = "https://goo.gl/aH3UyP"; @@ -48,8 +51,8 @@ final class StackTraceCleaner { private final Throwable throwable; private final List<StackTraceElementWrapper> cleanedStackTrace = new ArrayList<>(); - private StackTraceElementWrapper lastStackFrameElementWrapper = null; - private StackFrameType currentStreakType = null; + private @Nullable StackTraceElementWrapper lastStackFrameElementWrapper = null; + private @Nullable StackFrameType currentStreakType = null; private int currentStreakLength = 0; /** @@ -63,6 +66,7 @@ final class StackTraceCleaner { // TODO(b/135924708): Add this to the test runners so that we clean all stack traces, not just // those of exceptions originating in Truth. /** Cleans the stack trace on {@code throwable}, replacing the trace that was originally on it. */ + @SuppressWarnings("SetAll") // not available under old versions of Android private void clean(Set<Throwable> seenThrowables) { // Stack trace cleaning can be disabled using a system property. if (isStackTraceCleaningDisabled()) { @@ -100,7 +104,7 @@ final class StackTraceCleaner { * frames. Keep those frames around (though much of JUnit itself and related startup frames will * still be removed by the remainder of this method) so that the user sees a useful stack. */ - if (!(stackIndex < endIndex)) { + if (stackIndex >= endIndex) { endIndex = stackFrames.length; } @@ -174,10 +178,11 @@ final class StackTraceCleaner { if (currentStreakLength == 1) { // A single frame isn't a streak. Just include the frame as-is in the result. - cleanedStackTrace.add(lastStackFrameElementWrapper); + cleanedStackTrace.add(checkNotNull(lastStackFrameElementWrapper)); } else { // Add a single frame to the result summarizing the streak of framework frames - cleanedStackTrace.add(createStreakReplacementFrame(currentStreakType, currentStreakLength)); + cleanedStackTrace.add( + createStreakReplacementFrame(checkNotNull(currentStreakType), currentStreakLength)); } clearStreak(); @@ -190,7 +195,8 @@ final class StackTraceCleaner { } private static final ImmutableSet<String> SUBJECT_CLASS = - ImmutableSet.of(Subject.class.getCanonicalName()); + ImmutableSet.of( + Subject.class.getCanonicalName()); private static final ImmutableSet<String> STANDARD_SUBJECT_BUILDER_CLASS = ImmutableSet.of(StandardSubjectBuilder.class.getCanonicalName()); @@ -252,8 +258,8 @@ final class StackTraceCleaner { return false; } - private static boolean isSubtypeOf(Class<?> subclass, String superclass) { - for (; subclass != null; subclass = subclass.getSuperclass()) { + private static boolean isSubtypeOf(@Nullable Class<?> subclass, String superclass) { + for (; subclass != null; subclass = checkNotNull(subclass).getSuperclass()) { if (subclass.getCanonicalName() != null && subclass.getCanonicalName().equals(superclass)) { return true; } @@ -352,6 +358,8 @@ final class StackTraceCleaner { "Testing framework", "junit", "org.junit", + "androidx.test.internal.runner", + "com.github.bazel_contrib.contrib_rules_jvm.junit5", "com.google.testing.junit", "com.google.testing.testsize", "com.google.testing.util"), @@ -369,7 +377,9 @@ final class StackTraceCleaner { // TODO(cpovirk): This is really only for tests in Truth itself, so this doesn't matter yet, // but.... If the Truth tests someday start calling into nested classes, we may want to add: // || fullyQualifiedClassName.contains("Test$") - if (fullyQualifiedClassName.endsWith("Test")) { + if (fullyQualifiedClassName.endsWith("Test") + && !fullyQualifiedClassName.equals( + "androidx.test.internal.runner.junit3.NonLeakyTestSuite$NonLeakyTest")) { return StackFrameType.NEVER_REMOVE; } diff --git a/core/src/main/java/com/google/common/truth/StandardSubjectBuilder.java b/core/src/main/java/com/google/common/truth/StandardSubjectBuilder.java index 512de7e8..f41fb486 100644 --- a/core/src/main/java/com/google/common/truth/StandardSubjectBuilder.java +++ b/core/src/main/java/com/google/common/truth/StandardSubjectBuilder.java @@ -61,10 +61,9 @@ public class StandardSubjectBuilder { this.metadataDoNotReferenceDirectly = checkNotNull(metadata); } - @SuppressWarnings({"unchecked", "rawtypes"}) public final <ComparableT extends Comparable<?>> ComparableSubject<ComparableT> that( @Nullable ComparableT actual) { - return new ComparableSubject(metadata(), actual) {}; + return new ComparableSubject<ComparableT>(metadata(), actual) {}; } public final BigDecimalSubject that(@Nullable BigDecimal actual) { @@ -76,6 +75,7 @@ public class StandardSubjectBuilder { } @GwtIncompatible("ClassSubject.java") + @J2ktIncompatible public final ClassSubject that(@Nullable Class<?> actual) { return new ClassSubject(metadata(), actual); } @@ -112,39 +112,40 @@ public class StandardSubjectBuilder { return new IterableSubject(metadata(), actual); } - public final <T> ObjectArraySubject<T> that(@Nullable T /*@Nullable*/[] actual) { + @SuppressWarnings("AvoidObjectArrays") + public final <T> ObjectArraySubject<T> that(@Nullable T @Nullable [] actual) { return new ObjectArraySubject<>(metadata(), actual, "array"); } - public final PrimitiveBooleanArraySubject that(boolean /*@Nullable*/[] actual) { + public final PrimitiveBooleanArraySubject that(boolean @Nullable [] actual) { return new PrimitiveBooleanArraySubject(metadata(), actual, "array"); } - public final PrimitiveShortArraySubject that(short /*@Nullable*/[] actual) { + public final PrimitiveShortArraySubject that(short @Nullable [] actual) { return new PrimitiveShortArraySubject(metadata(), actual, "array"); } - public final PrimitiveIntArraySubject that(int /*@Nullable*/[] actual) { + public final PrimitiveIntArraySubject that(int @Nullable [] actual) { return new PrimitiveIntArraySubject(metadata(), actual, "array"); } - public final PrimitiveLongArraySubject that(long /*@Nullable*/[] actual) { + public final PrimitiveLongArraySubject that(long @Nullable [] actual) { return new PrimitiveLongArraySubject(metadata(), actual, "array"); } - public final PrimitiveCharArraySubject that(char /*@Nullable*/[] actual) { + public final PrimitiveCharArraySubject that(char @Nullable [] actual) { return new PrimitiveCharArraySubject(metadata(), actual, "array"); } - public final PrimitiveByteArraySubject that(byte /*@Nullable*/[] actual) { + public final PrimitiveByteArraySubject that(byte @Nullable [] actual) { return new PrimitiveByteArraySubject(metadata(), actual, "array"); } - public final PrimitiveFloatArraySubject that(float /*@Nullable*/[] actual) { + public final PrimitiveFloatArraySubject that(float @Nullable [] actual) { return new PrimitiveFloatArraySubject(metadata(), actual, "array"); } - public final PrimitiveDoubleArraySubject that(double /*@Nullable*/[] actual) { + public final PrimitiveDoubleArraySubject that(double @Nullable [] actual) { return new PrimitiveDoubleArraySubject(metadata(), actual, "array"); } @@ -189,7 +190,7 @@ public class StandardSubjectBuilder { * @throws IllegalArgumentException if the number of placeholders in the format string does not * equal the number of given arguments */ - public final StandardSubjectBuilder withMessage(String format, /*@Nullable*/ Object... args) { + public final StandardSubjectBuilder withMessage(String format, @Nullable Object... args) { return new StandardSubjectBuilder(metadata().withMessage(format, args)); } diff --git a/core/src/main/java/com/google/common/truth/StringSubject.java b/core/src/main/java/com/google/common/truth/StringSubject.java index d3a10d2f..dc5b12f7 100644 --- a/core/src/main/java/com/google/common/truth/StringSubject.java +++ b/core/src/main/java/com/google/common/truth/StringSubject.java @@ -32,7 +32,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; * @author Christian Gruber (cgruber@israfil.net) */ public class StringSubject extends ComparableSubject<String> { - private final String actual; + private final @Nullable String actual; /** * Constructor for use by subclasses. If you want to create an instance of this class itself, call @@ -43,23 +43,25 @@ public class StringSubject extends ComparableSubject<String> { this.actual = string; } - /** @deprecated Use {@link #isEqualTo} instead. String comparison is consistent with equality. */ + /** + * @deprecated Use {@link #isEqualTo} instead. String comparison is consistent with equality. + */ @Override @Deprecated - public final void isEquivalentAccordingToCompareTo(String other) { + public final void isEquivalentAccordingToCompareTo(@Nullable String other) { super.isEquivalentAccordingToCompareTo(other); } /** Fails if the string does not have the given length. */ public void hasLength(int expectedLength) { checkArgument(expectedLength >= 0, "expectedLength(%s) must be >= 0", expectedLength); - check("length()").that(actual.length()).isEqualTo(expectedLength); + check("length()").that(checkNotNull(actual).length()).isEqualTo(expectedLength); } /** Fails if the string is not equal to the zero-length "empty string." */ public void isEmpty() { if (actual == null) { - failWithActual(simpleFact("expected empty string")); + failWithActual(simpleFact("expected an empty string")); } else if (!actual.isEmpty()) { failWithActual(simpleFact("expected to be empty")); } @@ -68,14 +70,14 @@ public class StringSubject extends ComparableSubject<String> { /** Fails if the string is equal to the zero-length "empty string." */ public void isNotEmpty() { if (actual == null) { - failWithActual(simpleFact("expected nonempty string")); + failWithActual(simpleFact("expected a non-empty string")); } else if (actual.isEmpty()) { failWithoutActual(simpleFact("expected not to be empty")); } } /** Fails if the string does not contain the given sequence. */ - public void contains(CharSequence string) { + public void contains(@Nullable CharSequence string) { checkNotNull(string); if (actual == null) { failWithActual("expected a string that contains", string); @@ -85,7 +87,7 @@ public class StringSubject extends ComparableSubject<String> { } /** Fails if the string contains the given sequence. */ - public void doesNotContain(CharSequence string) { + public void doesNotContain(@Nullable CharSequence string) { checkNotNull(string); if (actual == null) { failWithActual("expected a string that does not contain", string); @@ -95,7 +97,7 @@ public class StringSubject extends ComparableSubject<String> { } /** Fails if the string does not start with the given string. */ - public void startsWith(String string) { + public void startsWith(@Nullable String string) { checkNotNull(string); if (actual == null) { failWithActual("expected a string that starts with", string); @@ -105,7 +107,7 @@ public class StringSubject extends ComparableSubject<String> { } /** Fails if the string does not end with the given string. */ - public void endsWith(String string) { + public void endsWith(@Nullable String string) { checkNotNull(string); if (actual == null) { failWithActual("expected a string that ends with", string); @@ -115,7 +117,7 @@ public class StringSubject extends ComparableSubject<String> { } /** Fails if the string does not match the given regex. */ - public void matches(String regex) { + public void matches(@Nullable String regex) { checkNotNull(regex); if (actual == null) { failWithActual("expected a string that matches", regex); @@ -133,7 +135,8 @@ public class StringSubject extends ComparableSubject<String> { /** Fails if the string does not match the given regex. */ @GwtIncompatible("java.util.regex.Pattern") - public void matches(Pattern regex) { + @J2ktIncompatible + public void matches(@Nullable Pattern regex) { checkNotNull(regex); if (actual == null) { failWithActual("expected a string that matches", regex); @@ -152,7 +155,7 @@ public class StringSubject extends ComparableSubject<String> { } /** Fails if the string matches the given regex. */ - public void doesNotMatch(String regex) { + public void doesNotMatch(@Nullable String regex) { checkNotNull(regex); if (actual == null) { failWithActual("expected a string that does not match", regex); @@ -163,7 +166,8 @@ public class StringSubject extends ComparableSubject<String> { /** Fails if the string matches the given regex. */ @GwtIncompatible("java.util.regex.Pattern") - public void doesNotMatch(Pattern regex) { + @J2ktIncompatible + public void doesNotMatch(@Nullable Pattern regex) { checkNotNull(regex); if (actual == null) { failWithActual("expected a string that does not match", regex); @@ -174,7 +178,8 @@ public class StringSubject extends ComparableSubject<String> { /** Fails if the string does not contain a match on the given regex. */ @GwtIncompatible("java.util.regex.Pattern") - public void containsMatch(Pattern regex) { + @J2ktIncompatible + public void containsMatch(@Nullable Pattern regex) { checkNotNull(regex); if (actual == null) { failWithActual("expected a string that contains a match for", regex); @@ -184,7 +189,7 @@ public class StringSubject extends ComparableSubject<String> { } /** Fails if the string does not contain a match on the given regex. */ - public void containsMatch(String regex) { + public void containsMatch(@Nullable String regex) { checkNotNull(regex); if (actual == null) { failWithActual("expected a string that contains a match for", regex); @@ -195,7 +200,8 @@ public class StringSubject extends ComparableSubject<String> { /** Fails if the string contains a match on the given regex. */ @GwtIncompatible("java.util.regex.Pattern") - public void doesNotContainMatch(Pattern regex) { + @J2ktIncompatible + public void doesNotContainMatch(@Nullable Pattern regex) { checkNotNull(regex); if (actual == null) { failWithActual("expected a string that does not contain a match for", regex); @@ -211,7 +217,7 @@ public class StringSubject extends ComparableSubject<String> { } /** Fails if the string contains a match on the given regex. */ - public void doesNotContainMatch(String regex) { + public void doesNotContainMatch(@Nullable String regex) { checkNotNull(regex); if (actual == null) { failWithActual("expected a string that does not contain a match for", regex); @@ -232,6 +238,7 @@ public class StringSubject extends ComparableSubject<String> { } /** Case insensitive propositions for string subjects. */ + @SuppressWarnings("Casing_StringEqualsIgnoreCase") // intentional choice from API Review public final class CaseInsensitiveStringComparison { private CaseInsensitiveStringComparison() {} @@ -246,7 +253,7 @@ public class StringSubject extends ComparableSubject<String> { * * <p>Example: "abc" is equal to "ABC", but not to "abcd". */ - public void isEqualTo(String expected) { + public void isEqualTo(@Nullable String expected) { if (actual == null) { if (expected != null) { failWithoutActual( @@ -268,7 +275,7 @@ public class StringSubject extends ComparableSubject<String> { * Fails if the subject is equal to the given string (while ignoring case). The meaning of * equality is the same as for the {@link #isEqualTo} method. */ - public void isNotEqualTo(String unexpected) { + public void isNotEqualTo(@Nullable String unexpected) { if (actual == null) { if (unexpected == null) { failWithoutActual( @@ -284,7 +291,7 @@ public class StringSubject extends ComparableSubject<String> { } /** Fails if the string does not contain the given sequence (while ignoring case). */ - public void contains(CharSequence expectedSequence) { + public void contains(@Nullable CharSequence expectedSequence) { checkNotNull(expectedSequence); String expected = expectedSequence.toString(); if (actual == null) { @@ -299,7 +306,7 @@ public class StringSubject extends ComparableSubject<String> { } /** Fails if the string contains the given sequence (while ignoring case). */ - public void doesNotContain(CharSequence expectedSequence) { + public void doesNotContain(@Nullable CharSequence expectedSequence) { checkNotNull(expectedSequence); String expected = expectedSequence.toString(); if (actual == null) { @@ -313,21 +320,22 @@ public class StringSubject extends ComparableSubject<String> { } } - private boolean containsIgnoreCase(String string) { + private boolean containsIgnoreCase(@Nullable String string) { + checkNotNull(string); if (string.isEmpty()) { // TODO(b/79459427): Fix for J2CL discrepancy when string is empty return true; } - String subject = actual; + String subject = checkNotNull(actual); for (int subjectOffset = 0; subjectOffset <= subject.length() - string.length(); subjectOffset++) { if (subject.regionMatches( - /* ignoreCase = */ true, - /* toffset = */ subjectOffset, - /* other = */ string, - /* ooffset = */ 0, - /* len = */ string.length())) { + /* ignoreCase= */ true, + /* toffset= */ subjectOffset, + /* other= */ string, + /* ooffset= */ 0, + /* len= */ string.length())) { return true; } } diff --git a/core/src/main/java/com/google/common/truth/Subject.java b/core/src/main/java/com/google/common/truth/Subject.java index 0d1436a6..94d9e169 100644 --- a/core/src/main/java/com/google/common/truth/Subject.java +++ b/core/src/main/java/com/google/common/truth/Subject.java @@ -19,11 +19,16 @@ import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_CAMEL; import static com.google.common.base.CharMatcher.whitespace; import static com.google.common.base.MoreObjects.firstNonNull; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.lenientFormat; import static com.google.common.truth.Fact.fact; import static com.google.common.truth.Fact.simpleFact; import static com.google.common.truth.Platform.doubleToString; import static com.google.common.truth.Platform.floatToString; +import static com.google.common.truth.Platform.isKotlinRange; +import static com.google.common.truth.Platform.kotlinRangeContains; +import static com.google.common.truth.Platform.stringValueOfNonFloatingPoint; import static com.google.common.truth.Subject.EqualityCheck.SAME_INSTANCE; import static com.google.common.truth.SubjectUtils.accumulate; import static com.google.common.truth.SubjectUtils.append; @@ -31,7 +36,6 @@ import static com.google.common.truth.SubjectUtils.concat; import static com.google.common.truth.SubjectUtils.sandwich; import static java.util.Arrays.asList; -import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; @@ -84,18 +88,11 @@ public class Subject { */ public interface Factory<SubjectT extends Subject, ActualT> { /** Creates a new {@link Subject}. */ - SubjectT createSubject(FailureMetadata metadata, ActualT actual); + SubjectT createSubject(FailureMetadata metadata, @Nullable ActualT actual); } - private static final FailureStrategy IGNORE_STRATEGY = - new FailureStrategy() { - @Override - public void fail(AssertionError failure) {} - }; - - private final FailureMetadata metadata; - private final Object actual; - private String customName = null; + private final @Nullable FailureMetadata metadata; + private final @Nullable Object actual; private final @Nullable String typeDescriptionOverride; /** @@ -103,7 +100,7 @@ public class Subject { * {@link Subject#check(String, Object...) check(...)}{@code .that(actual)}. */ protected Subject(FailureMetadata metadata, @Nullable Object actual) { - this(metadata, actual, /*typeDescriptionOverride=*/ null); + this(metadata, actual, /* typeDescriptionOverride= */ null); } /** @@ -118,8 +115,10 @@ public class Subject { * obfuscated names. */ Subject( - FailureMetadata metadata, @Nullable Object actual, @Nullable String typeDescriptionOverride) { - this.metadata = metadata.updateForSubject(this); + @Nullable FailureMetadata metadata, + @Nullable Object actual, + @Nullable String typeDescriptionOverride) { + this.metadata = metadata == null ? null : metadata.updateForSubject(this); this.actual = actual; this.typeDescriptionOverride = typeDescriptionOverride; } @@ -299,7 +298,7 @@ public class Subject { failWithActual("expected instance of", clazz.getName()); return; } - if (!Platform.isInstanceOfType(actual, clazz)) { + if (!isInstanceOfType(actual, clazz)) { if (classMetadataUnsupported()) { throw new UnsupportedOperationException( actualCustomStringRepresentation() @@ -328,7 +327,7 @@ public class Subject { if (actual == null) { return; // null is not an instance of clazz. } - if (Platform.isInstanceOfType(actual, clazz)) { + if (isInstanceOfType(actual, clazz)) { failWithActual("expected not to be an instance of", clazz.getName()); /* * TODO(cpovirk): Consider including actual.getClass() if it's not clazz itself but only a @@ -337,21 +336,42 @@ public class Subject { } } + private static boolean isInstanceOfType(Object instance, Class<?> clazz) { + checkArgument( + !clazz.isPrimitive(), + "Cannot check instanceof for primitive type %s. Pass the wrapper class instead.", + clazz.getSimpleName()); + /* + * TODO(cpovirk): Make the message include `Primitives.wrap(clazz).getSimpleName()` once that + * method is available in a public guava-gwt release that we depend on. + */ + return Platform.isInstanceOfType(instance, clazz); + } + /** Fails unless the subject is equal to any element in the given iterable. */ - public void isIn(Iterable<?> iterable) { - if (!Iterables.contains(iterable, actual)) { + public void isIn(@Nullable Iterable<?> iterable) { + checkNotNull(iterable); + if (!contains(iterable, actual)) { failWithActual("expected any of", iterable); } } + private static boolean contains(Iterable<?> haystack, @Nullable Object needle) { + if (isKotlinRange(haystack)) { + return kotlinRangeContains(haystack, needle); + } + return Iterables.contains(haystack, needle); + } + /** Fails unless the subject is equal to any of the given elements. */ public void isAnyOf( - @Nullable Object first, @Nullable Object second, @Nullable Object /*@Nullable*/... rest) { + @Nullable Object first, @Nullable Object second, @Nullable Object @Nullable ... rest) { isIn(accumulate(first, second, rest)); } /** Fails if the subject is equal to any element in the given iterable. */ - public void isNotIn(Iterable<?> iterable) { + public void isNotIn(@Nullable Iterable<?> iterable) { + checkNotNull(iterable); if (Iterables.contains(iterable, actual)) { failWithActual("expected not to be any of", iterable); } @@ -359,12 +379,12 @@ public class Subject { /** Fails if the subject is equal to any of the given elements. */ public void isNoneOf( - @Nullable Object first, @Nullable Object second, @Nullable Object /*@Nullable*/... rest) { + @Nullable Object first, @Nullable Object second, @Nullable Object @Nullable ... rest) { isNotIn(accumulate(first, second, rest)); } /** Returns the actual value under test. */ - final Object actual() { + final @Nullable Object actual() { return actual; } @@ -401,14 +421,19 @@ public class Subject { if (o instanceof byte[]) { return base16((byte[]) o); } else if (o != null && o.getClass().isArray()) { - String wrapped = Iterables.toString(stringableIterable(new Object[] {o})); - return wrapped.substring(1, wrapped.length() - 1); + return String.valueOf(arrayAsListRecursively(o)); } else if (o instanceof Double) { return doubleToString((Double) o); } else if (o instanceof Float) { return floatToString((Float) o); } else { - return String.valueOf(o); + // TODO(cpovirk): Consider renaming the called method to mention "NonArray." + /* + * TODO(cpovirk): Should the called method and arrayAsListRecursively(...) both call back into + * formatActualOrExpected for its handling of byte[] and float/double? Or is there some other + * restructuring of this set of methods that we should undertake? + */ + return stringValueOfNonFloatingPoint(o); } } @@ -423,40 +448,30 @@ public class Subject { private static final char[] hexDigits = "0123456789ABCDEF".toCharArray(); - private static Iterable<?> stringableIterable(Object[] array) { - return Iterables.transform(asList(array), STRINGIFY); - } - - private static final Function<Object, Object> STRINGIFY = - new Function<Object, Object>() { - @Override - public Object apply(@Nullable Object input) { - if (input != null && input.getClass().isArray()) { - Iterable<?> iterable; - if (input.getClass() == boolean[].class) { - iterable = Booleans.asList((boolean[]) input); - } else if (input.getClass() == int[].class) { - iterable = Ints.asList((int[]) input); - } else if (input.getClass() == long[].class) { - iterable = Longs.asList((long[]) input); - } else if (input.getClass() == short[].class) { - iterable = Shorts.asList((short[]) input); - } else if (input.getClass() == byte[].class) { - iterable = Bytes.asList((byte[]) input); - } else if (input.getClass() == double[].class) { - iterable = doubleArrayAsString((double[]) input); - } else if (input.getClass() == float[].class) { - iterable = floatArrayAsString((float[]) input); - } else if (input.getClass() == char[].class) { - iterable = Chars.asList((char[]) input); - } else { - iterable = Arrays.asList((Object[]) input); - } - return Iterables.transform(iterable, STRINGIFY); - } - return input; - } - }; + private static @Nullable Object arrayAsListRecursively(@Nullable Object input) { + if (input instanceof Object[]) { + return Lists.<@Nullable Object, @Nullable Object>transform( + asList((@Nullable Object[]) input), Subject::arrayAsListRecursively); + } else if (input instanceof boolean[]) { + return Booleans.asList((boolean[]) input); + } else if (input instanceof int[]) { + return Ints.asList((int[]) input); + } else if (input instanceof long[]) { + return Longs.asList((long[]) input); + } else if (input instanceof short[]) { + return Shorts.asList((short[]) input); + } else if (input instanceof byte[]) { + return Bytes.asList((byte[]) input); + } else if (input instanceof double[]) { + return doubleArrayAsString((double[]) input); + } else if (input instanceof float[]) { + return floatArrayAsString((float[]) input); + } else if (input instanceof char[]) { + return Chars.asList((char[]) input); + } else { + return input; + } + } /** * The result of comparing two objects for equality. This includes both the "equal"/"not-equal" @@ -492,7 +507,7 @@ public class Subject { private final @Nullable ImmutableList<Fact> facts; - private ComparisonResult(ImmutableList<Fact> facts) { + private ComparisonResult(@Nullable ImmutableList<Fact> facts) { this.facts = facts; } @@ -597,7 +612,7 @@ public class Subject { } } - private static boolean gwtSafeObjectEquals(Object actual, Object expected) { + private static boolean gwtSafeObjectEquals(@Nullable Object actual, @Nullable Object expected) { if (actual instanceof Double && expected instanceof Double) { return Double.doubleToLongBits((Double) actual) == Double.doubleToLongBits((Double) expected); } else if (actual instanceof Float && expected instanceof Float) { @@ -633,7 +648,7 @@ public class Subject { */ @Deprecated final StandardSubjectBuilder check() { - return new StandardSubjectBuilder(metadata.updateForCheckCall()); + return new StandardSubjectBuilder(checkNotNull(metadata).updateForCheckCall()); } /** @@ -664,28 +679,24 @@ public class Subject { * @param format a template with {@code %s} placeholders * @param args the arguments to be inserted into those placeholders */ - protected final StandardSubjectBuilder check(String format, Object... args) { + protected final StandardSubjectBuilder check(String format, @Nullable Object... args) { return doCheck(OldAndNewValuesAreSimilar.DIFFERENT, format, args); } // TODO(b/134064106): Figure out a public API for this. - final StandardSubjectBuilder checkNoNeedToDisplayBothValues(String format, Object... args) { + final StandardSubjectBuilder checkNoNeedToDisplayBothValues( + String format, @Nullable Object... args) { return doCheck(OldAndNewValuesAreSimilar.SIMILAR, format, args); } private StandardSubjectBuilder doCheck( - OldAndNewValuesAreSimilar valuesAreSimilar, String format, Object[] args) { - final LazyMessage message = new LazyMessage(format, args); - Function<String, String> descriptionUpdate = - new Function<String, String>() { - @Override - public String apply(String input) { - return input + "." + message; - } - }; + OldAndNewValuesAreSimilar valuesAreSimilar, String format, @Nullable Object[] args) { + LazyMessage message = new LazyMessage(format, args); return new StandardSubjectBuilder( - metadata.updateForCheckCall(valuesAreSimilar, descriptionUpdate)); + checkNotNull(metadata) + .updateForCheckCall( + valuesAreSimilar, /* descriptionUpdate= */ input -> input + "." + message)); } /** @@ -697,7 +708,7 @@ public class Subject { * returns {@code ignoreCheck().that(... a dummy exception ...)}. */ protected final StandardSubjectBuilder ignoreCheck() { - return StandardSubjectBuilder.forCustomFailureStrategy(IGNORE_STRATEGY); + return StandardSubjectBuilder.forCustomFailureStrategy(failure -> {}); } /** @@ -782,7 +793,7 @@ public class Subject { * message as a migration aid, you can inline this method. */ @Deprecated - final void fail(String verb, Object... messageParts) { + final void fail(String verb, @Nullable Object... messageParts) { StringBuilder message = new StringBuilder("Not true that <"); message.append(actualCustomStringRepresentation()).append("> ").append(verb); for (Object part : messageParts) { @@ -806,12 +817,12 @@ public class Subject { * Special version of {@link #failEqualityCheck} for use from {@link IterableSubject}, documented * further there. */ - final void failEqualityCheckForEqualsWithoutDescription(Object expected) { + final void failEqualityCheckForEqualsWithoutDescription(@Nullable Object expected) { failEqualityCheck(EqualityCheck.EQUAL, expected, ComparisonResult.differentNoDescription()); } private void failEqualityCheck( - EqualityCheck equalityCheck, Object expected, ComparisonResult difference) { + EqualityCheck equalityCheck, @Nullable Object expected, ComparisonResult difference) { String actualString = actualCustomStringRepresentation(); String expectedString = formatActualOrExpected(expected); String actualClass = actual == null ? "(null reference)" : actual.getClass().getName(); @@ -859,8 +870,8 @@ public class Subject { } } else { if (equalityCheck == EqualityCheck.EQUAL && actual != null && expected != null) { - metadata.failEqualityCheck( - nameAsFacts(), difference.factsOrEmpty(), expectedString, actualString); + checkNotNull(metadata) + .failEqualityCheck(difference.factsOrEmpty(), expectedString, actualString); } else { failEqualityCheckNoComparisonFailure( difference, @@ -874,7 +885,7 @@ public class Subject { * Checks whether the actual and expected values are strings that match except for trailing * whitespace. If so, reports a failure and returns true. */ - private boolean tryFailForTrailingWhitespaceOnly(Object expected) { + private boolean tryFailForTrailingWhitespaceOnly(@Nullable Object expected) { if (!(actual instanceof String) || !(expected instanceof String)) { return false; } @@ -943,7 +954,7 @@ public class Subject { * Checks whether the actual and expected values are empty strings. If so, reports a failure and * returns true. */ - private boolean tryFailForEmptyString(Object expected) { + private boolean tryFailForEmptyString(@Nullable Object expected) { if (!(actual instanceof String) || !(expected instanceof String)) { return false; } @@ -1038,8 +1049,7 @@ public class Subject { */ @Deprecated final void failWithoutSubject(String check) { - String strSubject = this.customName == null ? "the subject" : "\"" + customName + "\""; - failWithoutActual(simpleFact(lenientFormat("Not true that %s %s", strSubject, check))); + failWithoutActual(simpleFact(lenientFormat("Not true that the subject %s", check))); } /** @@ -1183,16 +1193,6 @@ public class Subject { } private void doFail(ImmutableList<Fact> facts) { - metadata.fail(prependNameIfAny(facts)); - } - - private ImmutableList<Fact> prependNameIfAny(ImmutableList<Fact> facts) { - return concat(nameAsFacts(), facts); - } - - private ImmutableList<Fact> nameAsFacts() { - return customName == null - ? ImmutableList.<Fact>of() - : ImmutableList.of(fact("name", customName)); + checkNotNull(metadata).fail(facts); } } diff --git a/core/src/main/java/com/google/common/truth/SubjectUtils.java b/core/src/main/java/com/google/common/truth/SubjectUtils.java index 07ef011f..ae55c066 100644 --- a/core/src/main/java/com/google/common/truth/SubjectUtils.java +++ b/core/src/main/java/com/google/common/truth/SubjectUtils.java @@ -22,21 +22,21 @@ import static com.google.common.collect.Multisets.immutableEntry; import com.google.common.base.Equivalence; import com.google.common.base.Equivalence.Wrapper; -import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.base.Optional; -import com.google.common.collect.HashMultimap; +import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedHashMultiset; +import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Multiset; -import com.google.common.collect.SetMultimap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Utility methods used in {@code Subject} implementors. @@ -49,15 +49,15 @@ final class SubjectUtils { static final String HUMAN_UNDERSTANDABLE_EMPTY_STRING = "\"\" (empty String)"; - static <T> List<T> accumulate(T first, T second, T... rest) { + static <T extends @Nullable Object> List<T> accumulate(T first, T second, T @Nullable ... rest) { // rest should never be deliberately null, so assume that the caller passed null // in the third position but intended it to be the third element in the array of values. // Javac makes the opposite inference, so handle that here. - List<T> items = new ArrayList<T>(2 + ((rest == null) ? 1 : rest.length)); + List<T> items = new ArrayList<>(2 + ((rest == null) ? 1 : rest.length)); items.add(first); items.add(second); if (rest == null) { - items.add(null); + items.add((T) null); } else { items.addAll(Arrays.asList(rest)); } @@ -89,7 +89,8 @@ final class SubjectUtils { return (count > 1) ? item + " [" + count + " copies]" : item; } - private static <T> NonHashingMultiset<T> countDuplicatesToMultiset(Iterable<T> items) { + private static <T extends @Nullable Object> NonHashingMultiset<T> countDuplicatesToMultiset( + Iterable<T> items) { // We use avoid hashing in case the elements don't have a proper // .hashCode() method (e.g., MessageSet from old versions of protobuf). NonHashingMultiset<T> multiset = new NonHashingMultiset<>(); @@ -138,26 +139,23 @@ final class SubjectUtils { } } - private static final class NonHashingMultiset<E> { - // This ought to be static, but the generics are easier when I can refer to <E>. - private final Function<Multiset.Entry<Wrapper<E>>, Multiset.Entry<?>> unwrapKey = - new Function<Multiset.Entry<Wrapper<E>>, Multiset.Entry<?>>() { - @Override - public Multiset.Entry<?> apply(Multiset.Entry<Wrapper<E>> input) { - return immutableEntry(input.getElement().get(), input.getCount()); - } - }; + private static final class NonHashingMultiset<E extends @Nullable Object> { + /* + * This ought to be static, but the generics are easier when I can refer to <E>. We still want + * an Entry<?> so that entrySet() can return Iterable<Entry<?>> instead of Iterable<Entry<E>>. + * That way, it can be returned directly from DuplicateGroupedAndTyped.entrySet() without our + * having to generalize *its* return type to Iterable<? extends Entry<?>>. + */ + private Multiset.Entry<?> unwrapKey(Multiset.Entry<Wrapper<E>> input) { + return immutableEntry(input.getElement().get(), input.getCount()); + } - private final Multiset<Equivalence.Wrapper<E>> contents = LinkedHashMultiset.create(); + private final Multiset<Wrapper<E>> contents = LinkedHashMultiset.create(); void add(E element) { contents.add(EQUALITY_WITHOUT_USING_HASH_CODE.wrap(element)); } - boolean remove(E element) { - return contents.remove(EQUALITY_WITHOUT_USING_HASH_CODE.wrap(element)); - } - int totalCopies() { return contents.size(); } @@ -167,7 +165,7 @@ final class SubjectUtils { } Iterable<Multiset.Entry<?>> entrySet() { - return transform(contents.entrySet(), unwrapKey); + return transform(contents.entrySet(), this::unwrapKey); } String toStringWithBrackets() { @@ -261,13 +259,14 @@ final class SubjectUtils { * * <p>Example: {@code retainMatchingToString([1L, 2L, 2L], [2, 3]) == [2L, 2L]} */ - static List<Object> retainMatchingToString(Iterable<?> items, Iterable<?> itemsToCheck) { - SetMultimap<String, Object> stringValueToItemsToCheck = HashMultimap.create(); + static List<@Nullable Object> retainMatchingToString( + Iterable<?> items, Iterable<?> itemsToCheck) { + ListMultimap<String, @Nullable Object> stringValueToItemsToCheck = ArrayListMultimap.create(); for (Object itemToCheck : itemsToCheck) { stringValueToItemsToCheck.put(String.valueOf(itemToCheck), itemToCheck); } - List<Object> result = Lists.newArrayList(); + List<@Nullable Object> result = Lists.newArrayList(); for (Object item : items) { for (Object itemToCheck : stringValueToItemsToCheck.get(String.valueOf(item))) { if (!Objects.equal(itemToCheck, item)) { @@ -292,7 +291,7 @@ final class SubjectUtils { return !retainMatchingToString(items1, items2).isEmpty(); } - static String objectToTypeName(Object item) { + static String objectToTypeName(@Nullable Object item) { // TODO(cpovirk): Merge this with the code in Subject.failEqualityCheck(). if (item == null) { // The name "null type" comes from the interface javax.lang.model.type.NullType. @@ -342,7 +341,7 @@ final class SubjectUtils { return itemsWithTypeInfo; } - static <T> Collection<T> iterableToCollection(Iterable<T> iterable) { + static <T extends @Nullable Object> Collection<T> iterableToCollection(Iterable<T> iterable) { if (iterable instanceof Collection) { // Should be safe to assume that any Iterable implementing Collection isn't a one-shot // iterable, right? I sure hope so. @@ -352,7 +351,7 @@ final class SubjectUtils { } } - static <T> List<T> iterableToList(Iterable<T> iterable) { + static <T extends @Nullable Object> List<T> iterableToList(Iterable<T> iterable) { if (iterable instanceof List) { return (List<T>) iterable; } else { @@ -366,7 +365,7 @@ final class SubjectUtils { * * <p>Returns the given iterable if it contains no empty strings. */ - static <T> Iterable<T> annotateEmptyStrings(Iterable<T> items) { + static <T extends @Nullable Object> Iterable<T> annotateEmptyStrings(Iterable<T> items) { if (Iterables.contains(items, "")) { List<T> annotatedItems = Lists.newArrayList(); for (T item : items) { diff --git a/core/src/main/java/com/google/common/truth/TableSubject.java b/core/src/main/java/com/google/common/truth/TableSubject.java index 87fcd69f..9ab8f5c3 100644 --- a/core/src/main/java/com/google/common/truth/TableSubject.java +++ b/core/src/main/java/com/google/common/truth/TableSubject.java @@ -17,6 +17,7 @@ package com.google.common.truth; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.truth.Fact.fact; import static com.google.common.truth.Fact.simpleFact; import com.google.common.collect.Table; @@ -30,7 +31,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; * @author Kurt Alfred Kluever */ public final class TableSubject extends Subject { - private final Table<?, ?, ?> actual; + private final @Nullable Table<?, ?, ?> actual; TableSubject(FailureMetadata metadata, @Nullable Table<?, ?, ?> table) { super(metadata, table); @@ -39,14 +40,14 @@ public final class TableSubject extends Subject { /** Fails if the table is not empty. */ public void isEmpty() { - if (!actual.isEmpty()) { + if (!checkNotNull(actual).isEmpty()) { failWithActual(simpleFact("expected to be empty")); } } /** Fails if the table is empty. */ public void isNotEmpty() { - if (actual.isEmpty()) { + if (checkNotNull(actual).isEmpty()) { failWithoutActual(simpleFact("expected not to be empty")); } } @@ -54,59 +55,77 @@ public final class TableSubject extends Subject { /** Fails if the table does not have the given size. */ public final void hasSize(int expectedSize) { checkArgument(expectedSize >= 0, "expectedSize(%s) must be >= 0", expectedSize); - check("size()").that(actual.size()).isEqualTo(expectedSize); + check("size()").that(checkNotNull(actual).size()).isEqualTo(expectedSize); } /** Fails if the table does not contain a mapping for the given row key and column key. */ public void contains(@Nullable Object rowKey, @Nullable Object columnKey) { - if (!actual.contains(rowKey, columnKey)) { - fail("contains mapping for row/column", rowKey, columnKey); + if (!checkNotNull(actual).contains(rowKey, columnKey)) { + /* + * TODO(cpovirk): Consider including information about whether any cell with the given row + * *or* column was present. + */ + failWithActual( + simpleFact("expected to contain mapping for row-column key pair"), + fact("row key", rowKey), + fact("column key", columnKey)); } } /** Fails if the table contains a mapping for the given row key and column key. */ public void doesNotContain(@Nullable Object rowKey, @Nullable Object columnKey) { - if (actual.contains(rowKey, columnKey)) { - fail("does not contain mapping for row/column", rowKey, columnKey); + if (checkNotNull(actual).contains(rowKey, columnKey)) { + failWithoutActual( + simpleFact("expected not to contain mapping for row-column key pair"), + fact("row key", rowKey), + fact("column key", columnKey), + fact("but contained value", actual.get(rowKey, columnKey)), + fact("full contents", actual)); } } /** Fails if the table does not contain the given cell. */ public void containsCell( @Nullable Object rowKey, @Nullable Object colKey, @Nullable Object value) { - containsCell(Tables.<Object, Object, Object>immutableCell(rowKey, colKey, value)); + containsCell( + Tables.<@Nullable Object, @Nullable Object, @Nullable Object>immutableCell( + rowKey, colKey, value)); } /** Fails if the table does not contain the given cell. */ public void containsCell(Cell<?, ?, ?> cell) { checkNotNull(cell); - checkNoNeedToDisplayBothValues("cellSet()").that(actual.cellSet()).contains(cell); + checkNoNeedToDisplayBothValues("cellSet()").that(checkNotNull(actual).cellSet()).contains(cell); } /** Fails if the table contains the given cell. */ public void doesNotContainCell( @Nullable Object rowKey, @Nullable Object colKey, @Nullable Object value) { - doesNotContainCell(Tables.<Object, Object, Object>immutableCell(rowKey, colKey, value)); + doesNotContainCell( + Tables.<@Nullable Object, @Nullable Object, @Nullable Object>immutableCell( + rowKey, colKey, value)); } /** Fails if the table contains the given cell. */ public void doesNotContainCell(Cell<?, ?, ?> cell) { checkNotNull(cell); - checkNoNeedToDisplayBothValues("cellSet()").that(actual.cellSet()).doesNotContain(cell); + checkNoNeedToDisplayBothValues("cellSet()") + .that(checkNotNull(actual).cellSet()) + .doesNotContain(cell); } /** Fails if the table does not contain the given row key. */ public void containsRow(@Nullable Object rowKey) { - check("rowKeySet()").that(actual.rowKeySet()).contains(rowKey); + check("rowKeySet()").that(checkNotNull(actual).rowKeySet()).contains(rowKey); } /** Fails if the table does not contain the given column key. */ public void containsColumn(@Nullable Object columnKey) { - check("columnKeySet()").that(actual.columnKeySet()).contains(columnKey); + check("columnKeySet()").that(checkNotNull(actual).columnKeySet()).contains(columnKey); } /** Fails if the table does not contain the given value. */ public void containsValue(@Nullable Object value) { - check("values()").that(actual.values()).contains(value); + check("values()").that(checkNotNull(actual).values()).contains(value); } } diff --git a/core/src/main/java/com/google/common/truth/ThrowableSubject.java b/core/src/main/java/com/google/common/truth/ThrowableSubject.java index 8e0761da..e85476d2 100644 --- a/core/src/main/java/com/google/common/truth/ThrowableSubject.java +++ b/core/src/main/java/com/google/common/truth/ThrowableSubject.java @@ -15,6 +15,8 @@ */ package com.google.common.truth; +import static com.google.common.base.Preconditions.checkNotNull; + import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -23,7 +25,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; * @author Kurt Alfred Kluever */ public class ThrowableSubject extends Subject { - private final Throwable actual; + private final @Nullable Throwable actual; /** * Constructor for use by subclasses. If you want to create an instance of this class itself, call @@ -53,7 +55,7 @@ public class ThrowableSubject extends Subject { "(Note from Truth: When possible, instead of asserting on the full message, assert" + " about individual facts by using ExpectFailure.assertThat.)"); } - return check.that(actual.getMessage()); + return check.that(checkNotNull(actual).getMessage()); } /** @@ -61,6 +63,8 @@ public class ThrowableSubject extends Subject { * cause. This method can be invoked repeatedly (e.g. {@code * assertThat(e).hasCauseThat().hasCauseThat()....} to assert on a particular indirect cause. */ + // Any Throwable is fine, and we use plain Throwable to emphasize that it's not used "for real." + @SuppressWarnings("ShouldNotSubclass") public final ThrowableSubject hasCauseThat() { // provides a more helpful error message if hasCauseThat() methods are chained too deep // e.g. assertThat(new Exception()).hCT().hCT().... @@ -74,6 +78,7 @@ public class ThrowableSubject extends Subject { .that( new Throwable() { @Override + @SuppressWarnings("UnsynchronizedOverridesSynchronized") public Throwable fillInStackTrace() { setStackTrace(new StackTraceElement[0]); // for old versions of Android return this; diff --git a/core/src/main/java/com/google/common/truth/Truth.gwt.xml b/core/src/main/java/com/google/common/truth/Truth.gwt.xml index e0be07a6..98e2232f 100644 --- a/core/src/main/java/com/google/common/truth/Truth.gwt.xml +++ b/core/src/main/java/com/google/common/truth/Truth.gwt.xml @@ -5,19 +5,26 @@ </source> <!-- - We used to set this only for packages that had manual supersource. - That worked everywhere that I know of except for one place: - when running the GWT util.concurrent tests under Guava. - The problem is that GWT responds poorly to two .gwt.xml files in the same Java package: - https://goo.gl/pRV3Yn - The summary is that it ignores one file in favor of the other. - util.concurrent, like nearly all our packages, has two .gwt.xml files: one for prod and one for tests. - util.concurrent, unlike our other packages, has, as of this writing, test supersource but no prod supersource. - GWT happens to use the prod .gwt.xml, so it looks for no supersource for tests, either. - This causes it to fail to find AtomicLongMapTest. - Our workaround is to tell GWT that util.concurrent and all other packages have prod supersource, even if they have none. - GWT is happy to ignore us when we specify a nonexistent path. - (I hope that this workaround does not cause its own problems in the future.) + We used to set this only for packages that had manual supersource. That + worked everywhere that I know of except for one place: when running the GWT + util.concurrent tests under Guava. + + The problem is that GWT responds poorly to two .gwt.xml files in the same + Java package; see https://goo.gl/pRV3Yn for details. + + The summary is that it ignores one file in favor of the other. + util.concurrent, like nearly all our packages, has two .gwt.xml files: one + for prod and one for tests. However, unlike our other packages, as of this + writing it has test supersource but no prod supersource. + + GWT happens to use the prod .gwt.xml, so it looks for no supersource for + tests, either. This causes it to fail to find AtomicLongMapTest. + + Our workaround is to tell GWT that util.concurrent and all other packages + have prod supersource, even if they have none. GWT is happy to ignore us + when we specify a nonexistent path. + + (I hope that this workaround does not cause its own problems in the future.) --> <super-source path="super"/> diff --git a/core/src/main/java/com/google/common/truth/Truth.java b/core/src/main/java/com/google/common/truth/Truth.java index fa9b5aee..1afbccaa 100644 --- a/core/src/main/java/com/google/common/truth/Truth.java +++ b/core/src/main/java/com/google/common/truth/Truth.java @@ -73,21 +73,18 @@ import org.checkerframework.checker.nullness.qual.Nullable; public final class Truth { private Truth() {} - private static final FailureStrategy THROW_ASSERTION_ERROR = - new FailureStrategy() { - @Override - public void fail(AssertionError failure) { - throw failure; - } - }; - + @SuppressWarnings("ConstantCaseForConstants") // Despite the "Builder" name, it's not mutable. private static final StandardSubjectBuilder ASSERT = - StandardSubjectBuilder.forCustomFailureStrategy(THROW_ASSERTION_ERROR); + StandardSubjectBuilder.forCustomFailureStrategy( + failure -> { + throw failure; + }); /** * Begins a call chain with the fluent Truth API. If the check made by the chain fails, it will * throw {@link AssertionError}. */ + @SuppressWarnings("MemberName") // The underscore is a weird but intentional choice. public static StandardSubjectBuilder assert_() { return ASSERT; } @@ -101,7 +98,7 @@ public final class Truth { * StandardSubjectBuilder#about about(...)}, as discussed in <a * href="https://truth.dev/faq#java8">this FAQ entry</a>. */ - public static StandardSubjectBuilder assertWithMessage(String messageToPrepend) { + public static StandardSubjectBuilder assertWithMessage(@Nullable String messageToPrepend) { return assert_().withMessage(messageToPrepend); } @@ -121,7 +118,7 @@ public final class Truth { * @throws IllegalArgumentException if the number of placeholders in the format string does not * equal the number of given arguments */ - public static StandardSubjectBuilder assertWithMessage(String format, Object... args) { + public static StandardSubjectBuilder assertWithMessage(String format, @Nullable Object... args) { return assert_().withMessage(format, args); } @@ -144,7 +141,8 @@ public final class Truth { return assert_().about(factory); } - public static <T extends Comparable<?>> ComparableSubject<T> assertThat(@Nullable T actual) { + public static <ComparableT extends Comparable<?>> ComparableSubject<ComparableT> assertThat( + @Nullable ComparableT actual) { return assert_().that(actual); } @@ -157,6 +155,7 @@ public final class Truth { } @GwtIncompatible("ClassSubject.java") + @J2ktIncompatible public static ClassSubject assertThat(@Nullable Class<?> actual) { return assert_().that(actual); } @@ -193,39 +192,40 @@ public final class Truth { return assert_().that(actual); } - public static <T> ObjectArraySubject<T> assertThat(@Nullable T /*@Nullable*/[] actual) { + @SuppressWarnings("AvoidObjectArrays") + public static <T> ObjectArraySubject<T> assertThat(@Nullable T @Nullable [] actual) { return assert_().that(actual); } - public static PrimitiveBooleanArraySubject assertThat(boolean /*@Nullable*/[] actual) { + public static PrimitiveBooleanArraySubject assertThat(boolean @Nullable [] actual) { return assert_().that(actual); } - public static PrimitiveShortArraySubject assertThat(short /*@Nullable*/[] actual) { + public static PrimitiveShortArraySubject assertThat(short @Nullable [] actual) { return assert_().that(actual); } - public static PrimitiveIntArraySubject assertThat(int /*@Nullable*/[] actual) { + public static PrimitiveIntArraySubject assertThat(int @Nullable [] actual) { return assert_().that(actual); } - public static PrimitiveLongArraySubject assertThat(long /*@Nullable*/[] actual) { + public static PrimitiveLongArraySubject assertThat(long @Nullable [] actual) { return assert_().that(actual); } - public static PrimitiveByteArraySubject assertThat(byte /*@Nullable*/[] actual) { + public static PrimitiveByteArraySubject assertThat(byte @Nullable [] actual) { return assert_().that(actual); } - public static PrimitiveCharArraySubject assertThat(char /*@Nullable*/[] actual) { + public static PrimitiveCharArraySubject assertThat(char @Nullable [] actual) { return assert_().that(actual); } - public static PrimitiveFloatArraySubject assertThat(float /*@Nullable*/[] actual) { + public static PrimitiveFloatArraySubject assertThat(float @Nullable [] actual) { return assert_().that(actual); } - public static PrimitiveDoubleArraySubject assertThat(double /*@Nullable*/[] actual) { + public static PrimitiveDoubleArraySubject assertThat(double @Nullable [] actual) { return assert_().that(actual); } @@ -296,18 +296,18 @@ public final class Truth { } static SimpleAssertionError createWithNoStack(String message) { - return createWithNoStack(message, /*cause=*/ null); + return createWithNoStack(message, /* cause= */ null); } @Override @SuppressWarnings("UnsynchronizedOverridesSynchronized") - public Throwable getCause() { + public @Nullable Throwable getCause() { return cause; } @Override public String toString() { - return getLocalizedMessage(); + return checkNotNull(getLocalizedMessage()); } } } diff --git a/core/src/main/java/com/google/common/truth/TruthFailureSubject.java b/core/src/main/java/com/google/common/truth/TruthFailureSubject.java index 8724f991..eb9f9387 100644 --- a/core/src/main/java/com/google/common/truth/TruthFailureSubject.java +++ b/core/src/main/java/com/google/common/truth/TruthFailureSubject.java @@ -55,12 +55,13 @@ public final class TruthFailureSubject extends ThrowableSubject { private static final Factory<TruthFailureSubject, AssertionError> FACTORY = new Factory<TruthFailureSubject, AssertionError>() { @Override - public TruthFailureSubject createSubject(FailureMetadata metadata, AssertionError actual) { + public TruthFailureSubject createSubject( + FailureMetadata metadata, @Nullable AssertionError actual) { return new TruthFailureSubject(metadata, actual, "failure"); } }; - private final AssertionError actual; + private final @Nullable AssertionError actual; TruthFailureSubject( FailureMetadata metadata, @Nullable AssertionError actual, @Nullable String typeDescription) { @@ -71,7 +72,7 @@ public final class TruthFailureSubject extends ThrowableSubject { /** Returns a subject for the list of fact keys. */ public IterableSubject factKeys() { if (!(actual instanceof ErrorWithFacts)) { - failWithActual(simpleFact("expected a failure thrown by Truth's new failure API")); + failWithActual(simpleFact("expected a failure thrown by Truth's failure API")); return ignoreCheck().that(ImmutableList.of()); } ErrorWithFacts error = (ErrorWithFacts) actual; @@ -124,7 +125,7 @@ public final class TruthFailureSubject extends ThrowableSubject { private StringSubject doFactValue(String key, @Nullable Integer index) { checkNotNull(key); if (!(actual instanceof ErrorWithFacts)) { - failWithActual(simpleFact("expected a failure thrown by Truth's new failure API")); + failWithActual(simpleFact("expected a failure thrown by Truth's failure API")); return ignoreCheck().that(""); } ErrorWithFacts error = (ErrorWithFacts) actual; diff --git a/core/src/main/java/com/google/common/truth/TruthJUnit.java b/core/src/main/java/com/google/common/truth/TruthJUnit.java index 3c70d887..ad82efb2 100644 --- a/core/src/main/java/com/google/common/truth/TruthJUnit.java +++ b/core/src/main/java/com/google/common/truth/TruthJUnit.java @@ -16,7 +16,7 @@ package com.google.common.truth; import com.google.common.annotations.GwtIncompatible; -import org.junit.internal.AssumptionViolatedException; +import org.junit.AssumptionViolatedException; /** * Provides a way to use Truth to perform JUnit "assumptions." An assumption is a check that, if @@ -41,20 +41,17 @@ import org.junit.internal.AssumptionViolatedException; * @author Christian Gruber (cgruber@israfil.net) */ @GwtIncompatible("JUnit4") +@J2ktIncompatible public final class TruthJUnit { - private static final FailureStrategy THROW_ASSUMPTION_ERROR = - new FailureStrategy() { - @Override - public void fail(AssertionError failure) { - ThrowableAssumptionViolatedException assumptionViolated = - new ThrowableAssumptionViolatedException(failure.getMessage(), failure.getCause()); - assumptionViolated.setStackTrace(failure.getStackTrace()); - throw assumptionViolated; - } - }; - + @SuppressWarnings("ConstantCaseForConstants") // Despite the "Builder" name, it's not mutable. private static final StandardSubjectBuilder ASSUME = - StandardSubjectBuilder.forCustomFailureStrategy(THROW_ASSUMPTION_ERROR); + StandardSubjectBuilder.forCustomFailureStrategy( + failure -> { + AssumptionViolatedException assumptionViolated = + new AssumptionViolatedException(failure.getMessage(), failure.getCause()); + assumptionViolated.setStackTrace(failure.getStackTrace()); + throw assumptionViolated; + }); /** * Begins a call chain with the fluent Truth API. If the check made by the chain fails, it will @@ -64,13 +61,5 @@ public final class TruthJUnit { return ASSUME; } - // TODO(diamondm): remove this and use org.junit.AssumptionViolatedException once we're on v4.12 - private static class ThrowableAssumptionViolatedException extends AssumptionViolatedException { - public ThrowableAssumptionViolatedException(String message, Throwable throwable) { - super(message); - if (throwable != null) initCause(throwable); - } - } - private TruthJUnit() {} } diff --git a/core/src/main/java/com/google/common/truth/UsedByReflection.java b/core/src/main/java/com/google/common/truth/UsedByReflection.java new file mode 100644 index 00000000..ce41dfd8 --- /dev/null +++ b/core/src/main/java/com/google/common/truth/UsedByReflection.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2013 Google, Inc. + * + * 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.common.truth; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; + +import java.lang.annotation.Target; + +@Target({METHOD, FIELD, CONSTRUCTOR}) +@interface UsedByReflection {} diff --git a/core/src/main/java/com/google/common/truth/super/com/google/common/truth/Platform.java b/core/src/main/java/com/google/common/truth/super/com/google/common/truth/Platform.java index ed2eb2c4..4642f8ab 100644 --- a/core/src/main/java/com/google/common/truth/super/com/google/common/truth/Platform.java +++ b/core/src/main/java/com/google/common/truth/super/com/google/common/truth/Platform.java @@ -20,10 +20,12 @@ import static java.lang.Float.parseFloat; import static jsinterop.annotations.JsPackage.GLOBAL; import com.google.common.collect.ImmutableList; +import jsinterop.annotations.JsMethod; import jsinterop.annotations.JsProperty; import jsinterop.annotations.JsType; import org.checkerframework.checker.nullness.qual.Nullable; + /** * Extracted routines that need to be swapped in for GWT, to allow for minimal deltas between the * GWT and non-GWT version. @@ -50,21 +52,6 @@ final class Platform { return false; } - abstract static class PlatformComparisonFailure extends AssertionError { - PlatformComparisonFailure( - String message, - String unusedUnderGwtExpected, - String unusedUnderGwtActual, - Throwable cause) { - super(message, cause); - } - - @Override - public final String toString() { - return getLocalizedMessage(); - } - } - /** Determines if the given subject contains a match for the given regex. */ static boolean containsMatch(String subject, String regex) { return compile(regex).test(subject); @@ -82,7 +69,7 @@ final class Platform { // Do nothing. See notes in StackTraceCleanerTest. } - static String inferDescription() { + static @Nullable String inferDescription() { return null; } @@ -97,6 +84,21 @@ final class Platform { return null; } + abstract static class PlatformComparisonFailure extends AssertionError { + PlatformComparisonFailure( + String message, + String unusedUnderGwtExpected, + String unusedUnderGwtActual, + @Nullable Throwable cause) { + super(message, cause); + } + + @Override + public final String toString() { + return "" + getLocalizedMessage(); + } + } + static String doubleToString(double value) { // This probably doesn't match Java perfectly, but we do our best. if (value == Double.POSITIVE_INFINITY) { @@ -136,9 +138,29 @@ final class Platform { return ((NativeNumber) (Object) value).toLocaleString("en-US", JavaLikeOptions.INSTANCE); } - /** Tests if current platform is Android which is always false. */ - static boolean isAndroid() { - return false; + @JsType(isNative = true, namespace = "proto.im") + private static class Message { + public native String serialize(); + } + + @JsMethod(namespace = "proto.im.debug") + private static native Object dump(Message msg) /*-{ + // Emtpy stub to make GWT happy. This will never get executed under GWT. + throw new Error(); + }-*/; + + /** Turns a non-double, non-float object into a string. */ + static String stringValueOfNonFloatingPoint(@Nullable Object o) { + // Check if we are in J2CL mode by probing a system property that only exists in GWT. + boolean inJ2clMode = "doesntexist".equals(System.getProperty("superdevmode", "doesntexist")); + if (inJ2clMode && o instanceof Message) { + Message msg = (Message) o; + boolean dumpAvailable = + "true".equals(System.getProperty("goog.DEBUG", "true")) + && !"true".equals(System.getProperty("COMPILED", "false")); + return dumpAvailable ? dump(msg).toString() : msg.serialize(); + } + return String.valueOf(o); } /** Returns a human readable string representation of the throwable's stack trace. */ @@ -147,6 +169,11 @@ final class Platform { return throwable.toString(); } + /** Tests if current platform is Android which is always false. */ + static boolean isAndroid() { + return false; + } + /** * A GWT-swapped version of test rule interface that does nothing. All methods extended from * {@link org.junit.rules.TestRule} needs to be stripped. @@ -170,9 +197,9 @@ final class Platform { @JsType(isNative = true, name = "RegExp", namespace = GLOBAL) private static class NativeRegExp { - public NativeRegExp(String pattern) {} + public NativeRegExp(@Nullable String pattern) {} - public native boolean test(String input); + public native boolean test(@Nullable String input); } @JsType(isNative = true, name = "Number", namespace = GLOBAL) @@ -233,4 +260,13 @@ final class Platform { */ return new ComparisonFailureWithFacts(messages, facts, expected, actual, cause); } + + static boolean isKotlinRange(Iterable<?> iterable) { + return false; + } + + static boolean kotlinRangeContains(Iterable<?> haystack, @Nullable Object needle) { + throw new AssertionError(); // never called under GWT because isKotlinRange returns false + } } + diff --git a/core/src/test/java/com/google/common/truth/ActualValueInferenceTest.java b/core/src/test/java/com/google/common/truth/ActualValueInferenceTest.java index cc0a441b..9bee28aa 100644 --- a/core/src/test/java/com/google/common/truth/ActualValueInferenceTest.java +++ b/core/src/test/java/com/google/common/truth/ActualValueInferenceTest.java @@ -17,17 +17,26 @@ package com.google.common.truth; import static com.google.common.truth.ExpectFailure.assertThat; import static com.google.common.truth.ExpectFailure.expectFailure; +import static org.junit.Assert.assertThrows; +import static org.junit.runner.Description.createTestDescription; import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.ImmutableList; -import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.junit.runners.model.Statement; /** Tests for {@link ActualValueInference}. */ @GwtIncompatible // Inference doesn't work under GWT. @RunWith(JUnit4.class) +/* + * We declare a single `failure` variable in each method, and many methods assign to it multiple + * times. We declare it without initializing it so that every assignment to it can look the same as + * every other (rather than having an initial combined initialization+assignment that looks slightly + * different. + */ +@SuppressWarnings("InitializeInline") public final class ActualValueInferenceTest { @Test public void simple() { @@ -115,9 +124,7 @@ public final class ActualValueInferenceTest { failure = expectFailure( - whenTesting -> { - whenTesting.that(makeException()).hasMessageThat().isEqualTo("b"); - }); + whenTesting -> whenTesting.that(makeException()).hasMessageThat().isEqualTo("b")); assertThat(failure).factValue("value of").isEqualTo("makeException().getMessage()"); } @@ -180,6 +187,26 @@ public final class ActualValueInferenceTest { assertThat(failure).factKeys().doesNotContain("value of"); } + @Test + public void expect() { + Expect expect = Expect.create(); + Statement testMethod = + new Statement() { + @Override + public void evaluate() { + expect.that(staticNoArg()).isEqualTo("b"); + } + }; + Statement wrapped = expect.apply(testMethod, createTestDescription("MyTest", "myMethod")); + AssertionError failure = assertThrows(AssertionError.class, wrapped::evaluate); + /* + * We can't use factValue here because Expect throws a plain wrapper AssertionError, not the + * original ErrorWithFacts. We could in theory change that someday, perhaps as part of a + * followup to https://github.com/google/truth/issues/543, but it seems unlikely. + */ + assertThat(failure).hasMessageThat().contains("staticNoArg()"); + } + static String staticNoArg() { return "a"; } @@ -196,7 +223,7 @@ public final class ActualValueInferenceTest { return "a"; } - List<Integer> oneTwoThree() { + ImmutableList<Integer> oneTwoThree() { return ImmutableList.of(1, 2, 3); } diff --git a/core/src/test/java/com/google/common/truth/ChainingTest.java b/core/src/test/java/com/google/common/truth/ChainingTest.java index 7d958fa7..318c6877 100644 --- a/core/src/test/java/com/google/common/truth/ChainingTest.java +++ b/core/src/test/java/com/google/common/truth/ChainingTest.java @@ -211,6 +211,7 @@ public final class ChainingTest extends BaseSubjectTestCase { @Test public void badFormat() { try { + @SuppressWarnings("LenientFormatStringValidation") // Intentional for testing. Object unused = assertThat("root").check("%s %s", 1, 2, 3); assert_().fail(); } catch (IllegalArgumentException expected) { diff --git a/core/src/test/java/com/google/common/truth/ComparableSubjectCompileTest.java b/core/src/test/java/com/google/common/truth/ComparableSubjectCompileTest.java deleted file mode 100644 index 767fbd0f..00000000 --- a/core/src/test/java/com/google/common/truth/ComparableSubjectCompileTest.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2014 Google, Inc. - * - * 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.common.truth; - -import static com.google.common.truth.Truth.assertAbout; -import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; - -import com.google.testing.compile.JavaFileObjects; -import javax.tools.JavaFileObject; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** - * Tests for {@link ComparableSubject} calls that should fail to compile. - * - * @author Kurt Alfred Kluever - */ -@RunWith(JUnit4.class) -public class ComparableSubjectCompileTest { - @Test - public void comparableMixedTypesDontCompile() { - JavaFileObject file = - JavaFileObjects.forSourceLines( - "test.MyTest", - "package test;", - "import static com.google.common.truth.Truth.assertThat;", - "class MyTest {", - " public void testFoo() {", - " assertThat(new ComparableType(3)).isLessThan(\"kak\");", - " }", - " private static final class ComparableType implements Comparable<ComparableType> {", - " private final int wrapped;", - " private ComparableType(int toWrap) {", - " this.wrapped = toWrap;", - " }", - " @Override public int compareTo(ComparableType other) {", - " return wrapped - ((ComparableType) other).wrapped;", - " }", - " }", - "}"); - - assertAbout(javaSource()) - .that(file) - // https://github.com/google/compile-testing/issues/149 - .withCompilerOptions("-sourcepath", "") - .failsToCompile() - .withErrorContaining("java.lang.String cannot be converted to test.MyTest.ComparableType") - .in(file) - .onLine(5); - } - - @Test - public void rawComparableTypeMixedTypes() { - JavaFileObject file = - JavaFileObjects.forSourceLines( - "test.MyTest", - "package test;", - "import static com.google.common.truth.Truth.assertThat;", - "class MyTest {", - " public void testFoo() {", - " assertThat(new RawComparableType(3)).isLessThan(\"kak\");", - " }", - " private static final class RawComparableType implements Comparable {", - " private final int wrapped;", - " private RawComparableType(int toWrap) {", - " this.wrapped = toWrap;", - " }", - " @Override public int compareTo(Object other) {", - " return wrapped - ((RawComparableType) other).wrapped;", - " }", - " }", - "}"); - assertAbout(javaSource()) - .that(file) - // https://github.com/google/compile-testing/issues/149 - .withCompilerOptions("-sourcepath", "") - .failsToCompile() - .withErrorContaining( - "java.lang.String cannot be converted to test.MyTest.RawComparableType") - .in(file) - .onLine(5); - } -} diff --git a/core/src/test/java/com/google/common/truth/ComparableSubjectTest.java b/core/src/test/java/com/google/common/truth/ComparableSubjectTest.java index 86aa41fc..d59bd348 100644 --- a/core/src/test/java/com/google/common/truth/ComparableSubjectTest.java +++ b/core/src/test/java/com/google/common/truth/ComparableSubjectTest.java @@ -22,7 +22,6 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.fail; import com.google.common.collect.Range; -import com.google.common.primitives.Ints; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -36,6 +35,8 @@ import org.junit.runners.JUnit4; public class ComparableSubjectTest extends BaseSubjectTestCase { @Test + // test of a mistaken call and of unnecessary use of isEquivalentAccordingToCompareTo + @SuppressWarnings({"deprecation", "IntegerComparison"}) public void testNulls() { try { assertThat(6).isEquivalentAccordingToCompareTo(null); @@ -105,7 +106,11 @@ public class ComparableSubjectTest extends BaseSubjectTestCase { @Override public int compareTo(StringComparedByLength other) { - return Ints.compare(value.length(), other.value.length()); + /* + * Even though Integer.compare was added in Java 7, we use it even under old versions of + * Android, as discussed in IterableSubjectTest. + */ + return Integer.compare(value.length(), other.value.length()); } @Override @@ -221,7 +226,7 @@ public class ComparableSubjectTest extends BaseSubjectTestCase { assertThat(new RawComparableType(3)).isLessThan(new RawComparableType(4)); } - @SuppressWarnings("ComparableType") + @SuppressWarnings({"ComparableType", "rawtypes"}) private static final class RawComparableType implements Comparable { private final int wrapped; diff --git a/core/src/test/java/com/google/common/truth/CorrespondenceExceptionStoreTest.java b/core/src/test/java/com/google/common/truth/CorrespondenceExceptionStoreTest.java index d6589216..bdcec87c 100644 --- a/core/src/test/java/com/google/common/truth/CorrespondenceExceptionStoreTest.java +++ b/core/src/test/java/com/google/common/truth/CorrespondenceExceptionStoreTest.java @@ -104,7 +104,8 @@ public final class CorrespondenceExceptionStoreTest extends BaseSubjectTestCase assertThat(second.key).isEqualTo("first exception"); assertThat(second.value) .matches( // an initial statement of the method that threw and the exception type: - "compare\\(null, 123\\) threw java.lang.NullPointerException" + "compare\\(null, 123\\) threw " + + "com.google.common.truth.TestCorrespondences\\$NullPointerExceptionFromWithin10Of" // some whitespace: + "\\s+" // the start of a stack trace, with the correct class: diff --git a/core/src/test/java/com/google/common/truth/CorrespondenceTest.java b/core/src/test/java/com/google/common/truth/CorrespondenceTest.java index ac29057e..781d9cba 100644 --- a/core/src/test/java/com/google/common/truth/CorrespondenceTest.java +++ b/core/src/test/java/com/google/common/truth/CorrespondenceTest.java @@ -22,9 +22,7 @@ import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; import static org.junit.Assert.fail; -import com.google.common.base.Function; import com.google.common.collect.ImmutableList; -import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -39,16 +37,7 @@ public final class CorrespondenceTest extends BaseSubjectTestCase { // Tests of the abstract base class (just assert that equals and hashCode throw). private static final Correspondence<Object, Object> INSTANCE = - Correspondence.from( - // If we were allowed to use lambdas, this would be: - // (a, e) -> false, - new Correspondence.BinaryPredicate<Object, Object>() { - @Override - public boolean apply(@Nullable Object actual, @Nullable Object expected) { - return false; - } - }, - "has example property"); + Correspondence.from((a, e) -> false, "has example property"); @Test @SuppressWarnings("deprecation") // testing deprecated method @@ -73,16 +62,7 @@ public final class CorrespondenceTest extends BaseSubjectTestCase { // Tests of the 'from' factory method. private static final Correspondence<String, String> STRING_PREFIX_EQUALITY = - // If we were allowed to use method references here, this would be: - // Correspondence.from(String::startsWith, "starts with"); - Correspondence.from( - new Correspondence.BinaryPredicate<String, String>() { - @Override - public boolean apply(String actual, String expected) { - return actual.startsWith(expected); - } - }, - "starts with"); + Correspondence.from(String::startsWith, "starts with"); @Test public void testFrom_compare() { @@ -149,33 +129,13 @@ public final class CorrespondenceTest extends BaseSubjectTestCase { // Tests of the 'transform' factory methods. private static final Correspondence<String, Integer> LENGTHS = - // If we were allowed to use method references here, this would be: - // Correspondence.transforming(String::length, "has a length of"); - Correspondence.transforming( - new Function<String, Integer>() { - @Override - public Integer apply(String str) { - return str.length(); - } - }, - "has a length of"); + Correspondence.transforming(String::length, "has a length of"); private static final Correspondence<String, Integer> HYPHEN_INDEXES = - // If we were allowed to use lambdas here, this would be: - // Correspondence.transforming( - // str -> { - // int index = str.indexOf('-'); - // return (index >= 0) ? index : null; - // }, - // "has a hyphen at an index of"); - // (Or else perhaps we'd pull out a method for the lambda body and use a method reference?) Correspondence.transforming( - new Function<String, Integer>() { - @Override - public @Nullable Integer apply(String str) { - int index = str.indexOf('-'); - return (index >= 0) ? index : null; - } + str -> { + int index = str.indexOf('-'); + return (index >= 0) ? index : null; }, "has a hyphen at an index of"); @@ -269,32 +229,14 @@ public final class CorrespondenceTest extends BaseSubjectTestCase { } private static final Correspondence<String, String> HYPHENS_MATCH_COLONS = - // If we were allowed to use lambdas here, this would be: - // Correspondence.transforming( - // str -> { - // int index = str.indexOf('-'); - // return (index >= 0) ? index : null; - // }, - // str -> { - // int index = str.indexOf(':'); - // return (index >= 0) ? index : null; - // }, - // "has a hyphen at the same index as the colon in"); - // (Or else perhaps we'd pull out a method for the lambda bodies?) Correspondence.transforming( - new Function<String, Integer>() { - @Override - public @Nullable Integer apply(String str) { - int index = str.indexOf('-'); - return (index >= 0) ? index : null; - } + str -> { + int index = str.indexOf('-'); + return (index >= 0) ? index : null; }, - new Function<String, Integer>() { - @Override - public @Nullable Integer apply(String str) { - int index = str.indexOf(':'); - return (index >= 0) ? index : null; - } + str -> { + int index = str.indexOf(':'); + return (index >= 0) ? index : null; }, "has a hyphen at the same index as the colon in"); @@ -567,24 +509,8 @@ public final class CorrespondenceTest extends BaseSubjectTestCase { // Tests of formattingDiffsUsing. private static final Correspondence<String, Integer> LENGTHS_WITH_DIFF = - // If we were allowed to use method references and lambdas here, this would be: - // Correspondence.transforming(String::length, "has a length of") - // .formattingDiffsUsing((a, e) -> Integer.toString(a.length() - e)); - Correspondence.transforming( - new Function<String, Integer>() { - @Override - public Integer apply(String str) { - return str.length(); - } - }, - "has a length of") - .formattingDiffsUsing( - new Correspondence.DiffFormatter<String, Integer>() { - @Override - public String formatDiff(String actualString, Integer expectedLength) { - return Integer.toString(actualString.length() - expectedLength); - } - }); + Correspondence.transforming(String::length, "has a length of") + .formattingDiffsUsing((a, e) -> Integer.toString(a.length() - e)); @Test public void testFormattingDiffsUsing_compare() { diff --git a/core/src/test/java/com/google/common/truth/CustomFailureMessageTest.java b/core/src/test/java/com/google/common/truth/CustomFailureMessageTest.java index 39910b32..6a702595 100644 --- a/core/src/test/java/com/google/common/truth/CustomFailureMessageTest.java +++ b/core/src/test/java/com/google/common/truth/CustomFailureMessageTest.java @@ -29,6 +29,7 @@ import org.junit.runners.JUnit4; * * @author Christian Gruber (cgruber@israfil.net) */ +@SuppressWarnings("LenientFormatStringValidation") // Intentional for testing @RunWith(JUnit4.class) public class CustomFailureMessageTest extends BaseSubjectTestCase { diff --git a/core/src/test/java/com/google/common/truth/DoubleSubjectTest.java b/core/src/test/java/com/google/common/truth/DoubleSubjectTest.java index f96b474e..e6535584 100644 --- a/core/src/test/java/com/google/common/truth/DoubleSubjectTest.java +++ b/core/src/test/java/com/google/common/truth/DoubleSubjectTest.java @@ -38,8 +38,8 @@ public class DoubleSubjectTest extends BaseSubjectTestCase { private static final double NEARLY_MAX = 1.7976931348623155E308; private static final double NEGATIVE_NEARLY_MAX = -1.7976931348623155E308; - private static final double OVER_MIN = 1.0E-323; - private static final double UNDER_NEGATIVE_MIN = -1.0E-323; + private static final double OVER_MIN = 9.9E-324; + private static final double UNDER_NEGATIVE_MIN = -9.9E-324; private static final double GOLDEN = 1.23; private static final double OVER_GOLDEN = 1.2300000000000002; @@ -96,8 +96,7 @@ public class DoubleSubjectTest extends BaseSubjectTestCase { assertThatIsWithinFails(Double.NaN, 1000.0, 2.0); } - private static void assertThatIsWithinFails( - final double actual, final double tolerance, final double expected) { + private static void assertThatIsWithinFails(double actual, double tolerance, double expected) { ExpectFailure.SimpleSubjectBuilderCallback<DoubleSubject, Double> callback = new ExpectFailure.SimpleSubjectBuilderCallback<DoubleSubject, Double>() { @Override @@ -129,8 +128,7 @@ public class DoubleSubjectTest extends BaseSubjectTestCase { assertThatIsNotWithinFails(Double.NaN, 1000.0, 2.0); } - private static void assertThatIsNotWithinFails( - final double actual, final double tolerance, final double expected) { + private static void assertThatIsNotWithinFails(double actual, double tolerance, double expected) { ExpectFailure.SimpleSubjectBuilderCallback<DoubleSubject, Double> callback = new ExpectFailure.SimpleSubjectBuilderCallback<DoubleSubject, Double>() { @Override @@ -371,7 +369,7 @@ public class DoubleSubjectTest extends BaseSubjectTestCase { assertThat(1.0).isEqualTo(1); } - private static void assertThatIsEqualToFails(final double actual, final double expected) { + private static void assertThatIsEqualToFails(double actual, double expected) { ExpectFailure.SimpleSubjectBuilderCallback<DoubleSubject, Double> callback = new ExpectFailure.SimpleSubjectBuilderCallback<DoubleSubject, Double>() { @Override @@ -394,7 +392,7 @@ public class DoubleSubjectTest extends BaseSubjectTestCase { assertThat(1.0).isNotEqualTo(2); } - private static void assertThatIsNotEqualToFails(final @Nullable Double value) { + private static void assertThatIsNotEqualToFails(@Nullable Double value) { ExpectFailure.SimpleSubjectBuilderCallback<DoubleSubject, Double> callback = new ExpectFailure.SimpleSubjectBuilderCallback<DoubleSubject, Double>() { @Override @@ -416,7 +414,7 @@ public class DoubleSubjectTest extends BaseSubjectTestCase { assertThatIsZeroFails(null); } - private static void assertThatIsZeroFails(final @Nullable Double value) { + private static void assertThatIsZeroFails(@Nullable Double value) { ExpectFailure.SimpleSubjectBuilderCallback<DoubleSubject, Double> callback = new ExpectFailure.SimpleSubjectBuilderCallback<DoubleSubject, Double>() { @Override @@ -439,7 +437,7 @@ public class DoubleSubjectTest extends BaseSubjectTestCase { assertThatIsNonZeroFails(null, "expected a double other than zero"); } - private static void assertThatIsNonZeroFails(final @Nullable Double value, String factKey) { + private static void assertThatIsNonZeroFails(@Nullable Double value, String factKey) { ExpectFailure.SimpleSubjectBuilderCallback<DoubleSubject, Double> callback = new ExpectFailure.SimpleSubjectBuilderCallback<DoubleSubject, Double>() { @Override @@ -460,7 +458,7 @@ public class DoubleSubjectTest extends BaseSubjectTestCase { assertThatIsPositiveInfinityFails(null); } - private static void assertThatIsPositiveInfinityFails(final @Nullable Double value) { + private static void assertThatIsPositiveInfinityFails(@Nullable Double value) { ExpectFailure.SimpleSubjectBuilderCallback<DoubleSubject, Double> callback = new ExpectFailure.SimpleSubjectBuilderCallback<DoubleSubject, Double>() { @Override @@ -480,7 +478,7 @@ public class DoubleSubjectTest extends BaseSubjectTestCase { assertThatIsNegativeInfinityFails(null); } - private static void assertThatIsNegativeInfinityFails(final @Nullable Double value) { + private static void assertThatIsNegativeInfinityFails(@Nullable Double value) { ExpectFailure.SimpleSubjectBuilderCallback<DoubleSubject, Double> callback = new ExpectFailure.SimpleSubjectBuilderCallback<DoubleSubject, Double>() { @Override @@ -500,7 +498,7 @@ public class DoubleSubjectTest extends BaseSubjectTestCase { assertThatIsNaNFails(null); } - private static void assertThatIsNaNFails(final @Nullable Double value) { + private static void assertThatIsNaNFails(@Nullable Double value) { ExpectFailure.SimpleSubjectBuilderCallback<DoubleSubject, Double> callback = new ExpectFailure.SimpleSubjectBuilderCallback<DoubleSubject, Double>() { @Override @@ -522,7 +520,7 @@ public class DoubleSubjectTest extends BaseSubjectTestCase { assertThatIsFiniteFails(null); } - private static void assertThatIsFiniteFails(final @Nullable Double value) { + private static void assertThatIsFiniteFails(@Nullable Double value) { ExpectFailure.SimpleSubjectBuilderCallback<DoubleSubject, Double> callback = new ExpectFailure.SimpleSubjectBuilderCallback<DoubleSubject, Double>() { @Override diff --git a/core/src/test/java/com/google/common/truth/ExpectFailureNonRuleTest.java b/core/src/test/java/com/google/common/truth/ExpectFailureNonRuleTest.java index 1e8f464d..460c35b3 100644 --- a/core/src/test/java/com/google/common/truth/ExpectFailureNonRuleTest.java +++ b/core/src/test/java/com/google/common/truth/ExpectFailureNonRuleTest.java @@ -35,7 +35,7 @@ public class ExpectFailureNonRuleTest { @Test public void testExpect_userThrowExceptionInSubject_shouldPropagate() throws Exception { - final List<Failure> reportedFailure = Lists.newArrayList(); + List<Failure> reportedFailure = Lists.newArrayList(); RunNotifier runNotifier = new RunNotifier(); runNotifier.addListener( new RunListener() { @@ -59,7 +59,7 @@ public class ExpectFailureNonRuleTest { @Test public void testExpect_userThrowExceptionAfterSubject_shouldPropagate() throws Exception { - final List<Failure> reportedFailure = Lists.newArrayList(); + List<Failure> reportedFailure = Lists.newArrayList(); RunNotifier runNotifier = new RunNotifier(); runNotifier.addListener( new RunListener() { diff --git a/core/src/test/java/com/google/common/truth/ExpectFailureRuleTest.java b/core/src/test/java/com/google/common/truth/ExpectFailureRuleTest.java index d79d75b7..5938ce25 100644 --- a/core/src/test/java/com/google/common/truth/ExpectFailureRuleTest.java +++ b/core/src/test/java/com/google/common/truth/ExpectFailureRuleTest.java @@ -24,7 +24,7 @@ import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** Tests for {@link ExpectFailure} used as JUnit {@link Rule).*/ +/** Tests for {@link ExpectFailure} used as JUnit {@link Rule}. */ @RunWith(JUnit4.class) @GwtIncompatible("org.junit.Rule") public class ExpectFailureRuleTest { diff --git a/core/src/test/java/com/google/common/truth/ExpectFailureWithStackTraceTest.java b/core/src/test/java/com/google/common/truth/ExpectFailureWithStackTraceTest.java index 3542475c..8ac3e561 100644 --- a/core/src/test/java/com/google/common/truth/ExpectFailureWithStackTraceTest.java +++ b/core/src/test/java/com/google/common/truth/ExpectFailureWithStackTraceTest.java @@ -44,7 +44,7 @@ public class ExpectFailureWithStackTraceTest { @Override public Statement apply(Statement base, Description description) { - final Statement s = delegate.apply(base, description); + Statement s = delegate.apply(base, description); return new Statement() { @Override public void evaluate() throws Throwable { diff --git a/core/src/test/java/com/google/common/truth/ExpectTest.java b/core/src/test/java/com/google/common/truth/ExpectTest.java index e079ee14..f7371c45 100644 --- a/core/src/test/java/com/google/common/truth/ExpectTest.java +++ b/core/src/test/java/com/google/common/truth/ExpectTest.java @@ -32,7 +32,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TestRule; -import org.junit.runner.Description; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.junit.runners.model.Statement; @@ -44,6 +43,8 @@ import org.junit.runners.model.Statement; * @author Christian Gruber (cgruber@israfil.net) */ @RunWith(JUnit4.class) +// We use ExpectedException so that we can test our code that runs after the test method completes. +@SuppressWarnings({"ExpectedExceptionChecker", "deprecation"}) public class ExpectTest { private final Expect oopsNotARule = Expect.create(); @@ -51,10 +52,8 @@ public class ExpectTest { private final ExpectedException thrown = ExpectedException.none(); private final TestRule postTestWait = - new TestRule() { - @Override - public Statement apply(final Statement base, Description description) { - return new Statement() { + (base, description) -> + new Statement() { @Override public void evaluate() throws Throwable { base.evaluate(); @@ -62,8 +61,6 @@ public class ExpectTest { taskToAwait.get(); } }; - } - }; private final CountDownLatch testMethodComplete = new CountDownLatch(1); @@ -74,14 +71,11 @@ public class ExpectTest { @Rule public final TestRule wrapper = - new TestRule() { - @Override - public Statement apply(Statement statement, Description description) { - statement = expect.apply(statement, description); - statement = postTestWait.apply(statement, description); - statement = thrown.apply(statement, description); - return statement; - } + (statement, description) -> { + statement = expect.apply(statement, description); + statement = postTestWait.apply(statement, description); + statement = thrown.apply(statement, description); + return statement; }; @Test @@ -189,13 +183,7 @@ public class ExpectTest { @Test public void bash() throws Exception { - Runnable task = - new Runnable() { - @Override - public void run() { - expect.that(3).isEqualTo(4); - } - }; + Runnable task = () -> expect.that(3).isEqualTo(4); List<Future<?>> results = new ArrayList<>(); ExecutorService executor = newFixedThreadPool(10); for (int i = 0; i < 1000; i++) { @@ -213,15 +201,12 @@ public class ExpectTest { ExecutorService executor = newSingleThreadExecutor(); taskToAwait = executor.submit( - new Runnable() { - @Override - public void run() { - awaitUninterruptibly(testMethodComplete); - try { - expect.that(3); - fail(); - } catch (IllegalStateException expected) { - } + () -> { + awaitUninterruptibly(testMethodComplete); + try { + expect.that(3); + fail(); + } catch (IllegalStateException expected) { } }); executor.shutdown(); @@ -235,19 +220,16 @@ public class ExpectTest { * expect.that(3).isEqualTo(4), we would always either fail the test or throw an * IllegalStateException, not record a "failure" that we never read. */ - final IntegerSubject expectThat3 = expect.that(3); + IntegerSubject expectThat3 = expect.that(3); taskToAwait = executor.submit( - new Runnable() { - @Override - public void run() { - awaitUninterruptibly(testMethodComplete); - try { - expectThat3.isEqualTo(4); - fail(); - } catch (IllegalStateException expected) { - assertThat(expected).hasCauseThat().isInstanceOf(AssertionError.class); - } + () -> { + awaitUninterruptibly(testMethodComplete); + try { + expectThat3.isEqualTo(4); + fail(); + } catch (IllegalStateException expected) { + assertThat(expected).hasCauseThat().isInstanceOf(AssertionError.class); } }); executor.shutdown(); diff --git a/core/src/test/java/com/google/common/truth/ExpectWithStackTest.java b/core/src/test/java/com/google/common/truth/ExpectWithStackTest.java index 528568c8..ae2fcb1a 100644 --- a/core/src/test/java/com/google/common/truth/ExpectWithStackTest.java +++ b/core/src/test/java/com/google/common/truth/ExpectWithStackTest.java @@ -123,7 +123,7 @@ public class ExpectWithStackTest { private static final class TestRuleVerifier implements TestRule { private final TestRule ruleToVerify; - private ErrorVerifier errorVerifier = NO_VERIFIER; + private ErrorVerifier errorVerifier = error -> {}; TestRuleVerifier(TestRule ruleToVerify) { this.ruleToVerify = ruleToVerify; @@ -134,7 +134,7 @@ public class ExpectWithStackTest { } @Override - public Statement apply(final Statement base, final Description description) { + public Statement apply(Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { @@ -151,10 +151,4 @@ public class ExpectWithStackTest { interface ErrorVerifier { void verify(AssertionError error); } - - private static final ErrorVerifier NO_VERIFIER = - new ErrorVerifier() { - @Override - public void verify(AssertionError expected) {} - }; } diff --git a/core/src/test/java/com/google/common/truth/FloatSubjectTest.java b/core/src/test/java/com/google/common/truth/FloatSubjectTest.java index c404a731..b528ab09 100644 --- a/core/src/test/java/com/google/common/truth/FloatSubjectTest.java +++ b/core/src/test/java/com/google/common/truth/FloatSubjectTest.java @@ -96,8 +96,7 @@ public class FloatSubjectTest extends BaseSubjectTestCase { assertThatIsWithinFails(Float.NaN, 1000.0f, 2.0f); } - private static void assertThatIsWithinFails( - final float actual, final float tolerance, final float expected) { + private static void assertThatIsWithinFails(float actual, float tolerance, float expected) { ExpectFailure.SimpleSubjectBuilderCallback<FloatSubject, Float> callback = new ExpectFailure.SimpleSubjectBuilderCallback<FloatSubject, Float>() { @Override @@ -129,8 +128,7 @@ public class FloatSubjectTest extends BaseSubjectTestCase { assertThatIsNotWithinFails(Float.NaN, 1000.0f, 2.0f); } - private static void assertThatIsNotWithinFails( - final float actual, final float tolerance, final float expected) { + private static void assertThatIsNotWithinFails(float actual, float tolerance, float expected) { ExpectFailure.SimpleSubjectBuilderCallback<FloatSubject, Float> callback = new ExpectFailure.SimpleSubjectBuilderCallback<FloatSubject, Float>() { @Override @@ -371,7 +369,7 @@ public class FloatSubjectTest extends BaseSubjectTestCase { assertThat(1.0f).isEqualTo(1); } - private static void assertThatIsEqualToFails(final float actual, final float expected) { + private static void assertThatIsEqualToFails(float actual, float expected) { ExpectFailure.SimpleSubjectBuilderCallback<FloatSubject, Float> callback = new ExpectFailure.SimpleSubjectBuilderCallback<FloatSubject, Float>() { @Override @@ -394,7 +392,7 @@ public class FloatSubjectTest extends BaseSubjectTestCase { assertThat(1.0f).isNotEqualTo(2); } - private static void assertThatIsNotEqualToFails(final @Nullable Float value) { + private static void assertThatIsNotEqualToFails(@Nullable Float value) { ExpectFailure.SimpleSubjectBuilderCallback<FloatSubject, Float> callback = new ExpectFailure.SimpleSubjectBuilderCallback<FloatSubject, Float>() { @Override @@ -416,7 +414,7 @@ public class FloatSubjectTest extends BaseSubjectTestCase { assertThatIsZeroFails(null); } - private static void assertThatIsZeroFails(final @Nullable Float value) { + private static void assertThatIsZeroFails(@Nullable Float value) { ExpectFailure.SimpleSubjectBuilderCallback<FloatSubject, Float> callback = new ExpectFailure.SimpleSubjectBuilderCallback<FloatSubject, Float>() { @Override @@ -439,7 +437,7 @@ public class FloatSubjectTest extends BaseSubjectTestCase { assertThatIsNonZeroFails(null, "expected a float other than zero"); } - private static void assertThatIsNonZeroFails(final @Nullable Float value, String factKey) { + private static void assertThatIsNonZeroFails(@Nullable Float value, String factKey) { ExpectFailure.SimpleSubjectBuilderCallback<FloatSubject, Float> callback = new ExpectFailure.SimpleSubjectBuilderCallback<FloatSubject, Float>() { @Override @@ -460,7 +458,7 @@ public class FloatSubjectTest extends BaseSubjectTestCase { assertThatIsPositiveInfinityFails(null); } - private static void assertThatIsPositiveInfinityFails(final @Nullable Float value) { + private static void assertThatIsPositiveInfinityFails(@Nullable Float value) { ExpectFailure.SimpleSubjectBuilderCallback<FloatSubject, Float> callback = new ExpectFailure.SimpleSubjectBuilderCallback<FloatSubject, Float>() { @Override @@ -480,7 +478,7 @@ public class FloatSubjectTest extends BaseSubjectTestCase { assertThatIsNegativeInfinityFails(null); } - private static void assertThatIsNegativeInfinityFails(final @Nullable Float value) { + private static void assertThatIsNegativeInfinityFails(@Nullable Float value) { ExpectFailure.SimpleSubjectBuilderCallback<FloatSubject, Float> callback = new ExpectFailure.SimpleSubjectBuilderCallback<FloatSubject, Float>() { @Override @@ -500,7 +498,7 @@ public class FloatSubjectTest extends BaseSubjectTestCase { assertThatIsNaNFails(null); } - private static void assertThatIsNaNFails(final @Nullable Float value) { + private static void assertThatIsNaNFails(@Nullable Float value) { ExpectFailure.SimpleSubjectBuilderCallback<FloatSubject, Float> callback = new ExpectFailure.SimpleSubjectBuilderCallback<FloatSubject, Float>() { @Override @@ -522,7 +520,7 @@ public class FloatSubjectTest extends BaseSubjectTestCase { assertThatIsFiniteFails(null); } - private static void assertThatIsFiniteFails(final @Nullable Float value) { + private static void assertThatIsFiniteFails(@Nullable Float value) { ExpectFailure.SimpleSubjectBuilderCallback<FloatSubject, Float> callback = new ExpectFailure.SimpleSubjectBuilderCallback<FloatSubject, Float>() { @Override diff --git a/core/src/test/java/com/google/common/truth/IntegerSubjectTest.java b/core/src/test/java/com/google/common/truth/IntegerSubjectTest.java index 46103043..bc6a5a02 100644 --- a/core/src/test/java/com/google/common/truth/IntegerSubjectTest.java +++ b/core/src/test/java/com/google/common/truth/IntegerSubjectTest.java @@ -234,8 +234,8 @@ public class IntegerSubjectTest extends BaseSubjectTestCase { } ImmutableSet<Object> fortyTwosNoChar = ImmutableSet.<Object>of(byte42, short42, int42, long42); - for (final Object actual : fortyTwosNoChar) { - for (final Object expected : fortyTwosNoChar) { + for (Object actual : fortyTwosNoChar) { + for (Object expected : fortyTwosNoChar) { ExpectFailure.SimpleSubjectBuilderCallback<Subject, Object> actualFirst = new ExpectFailure.SimpleSubjectBuilderCallback<Subject, Object>() { @Override diff --git a/core/src/test/java/com/google/common/truth/IterableSubjectCorrespondenceTest.java b/core/src/test/java/com/google/common/truth/IterableSubjectCorrespondenceTest.java index bb075e2e..f9e3986d 100644 --- a/core/src/test/java/com/google/common/truth/IterableSubjectCorrespondenceTest.java +++ b/core/src/test/java/com/google/common/truth/IterableSubjectCorrespondenceTest.java @@ -32,9 +32,10 @@ import static com.google.common.truth.TestCorrespondences.STRING_PARSES_TO_INTEG import static com.google.common.truth.TestCorrespondences.WITHIN_10_OF; import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; +import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; -import com.google.common.truth.TestCorrespondences.Record; +import com.google.common.truth.TestCorrespondences.MyRecord; import java.util.Arrays; import java.util.List; import org.junit.Test; @@ -55,6 +56,41 @@ import org.junit.runners.JUnit4; public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test + // test of a mistaken call + @SuppressWarnings({"EqualsIncompatibleType", "DoNotCall", "deprecation"}) + public void equalsThrowsUSOE() { + try { + boolean unused = + assertThat(ImmutableList.of(42.0)) + .comparingElementsUsing(tolerance(10e-5)) + .equals(ImmutableList.of(0.0)); + } catch (UnsupportedOperationException expected) { + assertThat(expected) + .hasMessageThat() + .isEqualTo( + "UsingCorrespondence.equals() is not supported. Did you mean to call" + + " containsExactlyElementsIn(expected) instead of equals(expected)?"); + return; + } + fail("Should have thrown."); + } + + @Test + @SuppressWarnings({"DoNotCall", "deprecation"}) // test of a mistaken call + public void hashCodeThrowsUSOE() { + try { + int unused = + assertThat(ImmutableList.of(42.0)).comparingElementsUsing(tolerance(10e-5)).hashCode(); + } catch (UnsupportedOperationException expected) { + assertThat(expected) + .hasMessageThat() + .isEqualTo("UsingCorrespondence.hashCode() is not supported."); + return; + } + fail("Should have thrown."); + } + + @Test public void contains_success() { ImmutableList<String> actual = ImmutableList.of("not a number", "+123", "+456", "+789"); assertThat(actual) @@ -123,14 +159,14 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test public void displayingDiffsPairedBy_1arg_contains() { - Record expected = Record.create(2, 200); - ImmutableList<Record> actual = + MyRecord expected = MyRecord.create(2, 200); + ImmutableList<MyRecord> actual = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 211), - Record.create(4, 400), - Record.create(2, 189), - Record.createWithoutId(999)); + MyRecord.create(1, 100), + MyRecord.create(2, 211), + MyRecord.create(4, 400), + MyRecord.create(2, 189), + MyRecord.createWithoutId(999)); expectFailure .whenTesting() .that(actual) @@ -156,14 +192,14 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test public void displayingDiffsPairedBy_1arg_contains_noDiff() { - Record expected = Record.create(2, 200); - ImmutableList<Record> actual = + MyRecord expected = MyRecord.create(2, 200); + ImmutableList<MyRecord> actual = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 211), - Record.create(4, 400), - Record.create(2, 189), - Record.createWithoutId(999)); + MyRecord.create(1, 100), + MyRecord.create(2, 211), + MyRecord.create(4, 400), + MyRecord.create(2, 189), + MyRecord.createWithoutId(999)); expectFailure .whenTesting() .that(actual) @@ -182,8 +218,8 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test public void displayingDiffsPairedBy_1arg_contains_handlesActualKeyerExceptions() { - Record expected = Record.create(0, 999); - List<Record> actual = asList(Record.create(1, 100), null, Record.create(4, 400)); + MyRecord expected = MyRecord.create(0, 999); + List<MyRecord> actual = asList(MyRecord.create(1, 100), null, MyRecord.create(4, 400)); expectFailure .whenTesting() .that(actual) @@ -203,8 +239,8 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test public void displayingDiffsPairedBy_1arg_contains_handlesExpectedKeyerExceptions() { - List<Record> actual = - asList(Record.create(1, 100), Record.create(2, 200), Record.create(4, 400)); + List<MyRecord> actual = + asList(MyRecord.create(1, 100), MyRecord.create(2, 200), MyRecord.create(4, 400)); expectFailure .whenTesting() .that(actual) @@ -224,8 +260,8 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test public void displayingDiffsPairedBy_1arg_contains_handlesFormatDiffExceptions() { - Record expected = Record.create(0, 999); - List<Record> actual = asList(Record.create(1, 100), null, Record.create(4, 400)); + MyRecord expected = MyRecord.create(0, 999); + List<MyRecord> actual = asList(MyRecord.create(1, 100), null, MyRecord.create(4, 400)); expectFailure .whenTesting() .that(actual) @@ -622,18 +658,18 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test public void displayingDiffsPairedBy_1arg_containsExactlyElementsIn() { - ImmutableList<Record> expected = + ImmutableList<MyRecord> expected = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 200), - Record.create(3, 300), - Record.createWithoutId(900)); - ImmutableList<Record> actual = + MyRecord.create(1, 100), + MyRecord.create(2, 200), + MyRecord.create(3, 300), + MyRecord.createWithoutId(900)); + ImmutableList<MyRecord> actual = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 211), - Record.create(4, 400), - Record.createWithoutId(999)); + MyRecord.create(1, 100), + MyRecord.create(2, 211), + MyRecord.create(4, 400), + MyRecord.createWithoutId(999)); expectFailure .whenTesting() .that(actual) @@ -666,12 +702,12 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test public void displayingDiffsPairedBy_2arg_containsExactlyElementsIn() { - ImmutableList<Record> expected = + ImmutableList<MyRecord> expected = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 200), - Record.create(3, 300), - Record.createWithoutId(900)); + MyRecord.create(1, 100), + MyRecord.create(2, 200), + MyRecord.create(3, 300), + MyRecord.createWithoutId(900)); ImmutableList<String> actual = ImmutableList.of("1/100", "2/211", "4/400", "none/999"); expectFailure .whenTesting() @@ -705,18 +741,18 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test public void displayingDiffsPairedBy_containsExactlyElementsIn_onlyKeyed() { - ImmutableList<Record> expected = + ImmutableList<MyRecord> expected = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 200), - Record.create(3, 300), - Record.createWithoutId(999)); - ImmutableList<Record> actual = + MyRecord.create(1, 100), + MyRecord.create(2, 200), + MyRecord.create(3, 300), + MyRecord.createWithoutId(999)); + ImmutableList<MyRecord> actual = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 211), - Record.create(3, 303), - Record.createWithoutId(999)); + MyRecord.create(1, 100), + MyRecord.create(2, 211), + MyRecord.create(3, 303), + MyRecord.createWithoutId(999)); expectFailure .whenTesting() .that(actual) @@ -741,18 +777,18 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test public void displayingDiffsPairedBy_containsExactlyElementsIn_noKeyed() { - ImmutableList<Record> expected = + ImmutableList<MyRecord> expected = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 200), - Record.create(3, 300), - Record.createWithoutId(900)); - ImmutableList<Record> actual = + MyRecord.create(1, 100), + MyRecord.create(2, 200), + MyRecord.create(3, 300), + MyRecord.createWithoutId(900)); + ImmutableList<MyRecord> actual = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 201), - Record.create(4, 400), - Record.createWithoutId(999)); + MyRecord.create(1, 100), + MyRecord.create(2, 201), + MyRecord.create(4, 400), + MyRecord.createWithoutId(999)); expectFailure .whenTesting() .that(actual) @@ -773,18 +809,18 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test public void displayingDiffsPairedBy_containsExactlyElementsIn_noDiffs() { - ImmutableList<Record> expected = + ImmutableList<MyRecord> expected = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 200), - Record.create(3, 300), - Record.createWithoutId(999)); - ImmutableList<Record> actual = + MyRecord.create(1, 100), + MyRecord.create(2, 200), + MyRecord.create(3, 300), + MyRecord.createWithoutId(999)); + ImmutableList<MyRecord> actual = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 211), - Record.create(3, 303), - Record.createWithoutId(999)); + MyRecord.create(1, 100), + MyRecord.create(2, 211), + MyRecord.create(3, 303), + MyRecord.createWithoutId(999)); expectFailure .whenTesting() .that(actual) @@ -816,19 +852,19 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { public void displayingDiffsPairedBy_containsExactlyElementsIn_notUnique() { // The missing elements here are not uniquely keyed by the key function, so the key function // should be ignored, but a warning about this should be appended to the failure message. - ImmutableList<Record> expected = + ImmutableList<MyRecord> expected = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 200), - Record.create(3, 300), - Record.create(3, 301), - Record.createWithoutId(900)); - ImmutableList<Record> actual = + MyRecord.create(1, 100), + MyRecord.create(2, 200), + MyRecord.create(3, 300), + MyRecord.create(3, 301), + MyRecord.createWithoutId(900)); + ImmutableList<MyRecord> actual = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 211), - Record.create(4, 400), - Record.createWithoutId(999)); + MyRecord.create(1, 100), + MyRecord.create(2, 211), + MyRecord.create(4, 400), + MyRecord.createWithoutId(999)); expectFailure .whenTesting() .that(actual) @@ -850,9 +886,9 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test public void displayingDiffsPairedBy_containsExactlyElementsIn_handlesActualKeyerExceptions() { - ImmutableList<Record> expected = - ImmutableList.of(Record.create(1, 100), Record.create(2, 200), Record.create(4, 400)); - List<Record> actual = asList(Record.create(1, 101), Record.create(2, 211), null); + ImmutableList<MyRecord> expected = + ImmutableList.of(MyRecord.create(1, 100), MyRecord.create(2, 200), MyRecord.create(4, 400)); + List<MyRecord> actual = asList(MyRecord.create(1, 101), MyRecord.create(2, 211), null); expectFailure .whenTesting() .that(actual) @@ -882,9 +918,9 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test public void displayingDiffsPairedBy_containsExactlyElementsIn_handlesExpectedKeyerExceptions() { - List<Record> expected = asList(Record.create(1, 100), Record.create(2, 200), null); - List<Record> actual = - asList(Record.create(1, 101), Record.create(2, 211), Record.create(4, 400)); + List<MyRecord> expected = asList(MyRecord.create(1, 100), MyRecord.create(2, 200), null); + List<MyRecord> actual = + asList(MyRecord.create(1, 101), MyRecord.create(2, 211), MyRecord.create(4, 400)); expectFailure .whenTesting() .that(actual) @@ -914,9 +950,9 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test public void displayingDiffsPairedBy_containsExactlyElementsIn_handlesFormatDiffExceptions() { - ImmutableList<Record> expected = - ImmutableList.of(Record.create(1, 100), Record.create(2, 200), Record.create(0, 999)); - List<Record> actual = asList(Record.create(1, 101), Record.create(2, 211), null); + ImmutableList<MyRecord> expected = + ImmutableList.of(MyRecord.create(1, 100), MyRecord.create(2, 200), MyRecord.create(0, 999)); + List<MyRecord> actual = asList(MyRecord.create(1, 101), MyRecord.create(2, 211), null); expectFailure .whenTesting() .that(actual) @@ -1275,15 +1311,16 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test public void displayingElementsPairedBy_containsAtLeastElementsIn() { - ImmutableList<Record> expected = - ImmutableList.of(Record.create(1, 100), Record.create(2, 200), Record.createWithoutId(999)); - ImmutableList<Record> actual = + ImmutableList<MyRecord> expected = + ImmutableList.of( + MyRecord.create(1, 100), MyRecord.create(2, 200), MyRecord.createWithoutId(999)); + ImmutableList<MyRecord> actual = ImmutableList.of( - Record.create(1, 101), - Record.create(2, 211), - Record.create(2, 222), - Record.create(3, 303), - Record.createWithoutId(888)); + MyRecord.create(1, 101), + MyRecord.create(2, 211), + MyRecord.create(2, 222), + MyRecord.create(3, 303), + MyRecord.createWithoutId(888)); expectFailure .whenTesting() .that(actual) @@ -1318,14 +1355,15 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test public void displayingElementsPairedBy_containsAtLeastElementsIn_notUnique() { - ImmutableList<Record> expected = + ImmutableList<MyRecord> expected = + ImmutableList.of( + MyRecord.create(1, 100), + MyRecord.create(2, 200), + MyRecord.create(2, 201), + MyRecord.createWithoutId(999)); + ImmutableList<MyRecord> actual = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 200), - Record.create(2, 201), - Record.createWithoutId(999)); - ImmutableList<Record> actual = - ImmutableList.of(Record.create(1, 101), Record.create(3, 303), Record.createWithoutId(999)); + MyRecord.create(1, 101), MyRecord.create(3, 303), MyRecord.createWithoutId(999)); expectFailure .whenTesting() .that(actual) @@ -1345,10 +1383,10 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test public void displayingElementsPairedBy_containsAtLeastElementsIn_handlesFormatDiffExceptions() { - ImmutableList<Record> expected = - ImmutableList.of(Record.create(1, 100), Record.create(2, 200), Record.create(0, 999)); - List<Record> actual = - asList(Record.create(1, 101), Record.create(2, 211), Record.create(3, 303), null); + ImmutableList<MyRecord> expected = + ImmutableList.of(MyRecord.create(1, 100), MyRecord.create(2, 200), MyRecord.create(0, 999)); + List<MyRecord> actual = + asList(MyRecord.create(1, 101), MyRecord.create(2, 211), MyRecord.create(3, 303), null); expectFailure .whenTesting() .that(actual) @@ -1647,19 +1685,19 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test public void displayingDiffsPairedBy_containsAnyIn_withKeyMatches() { - ImmutableList<Record> expected = + ImmutableList<MyRecord> expected = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 200), - Record.create(3, 300), - Record.createWithoutId(999)); - ImmutableList<Record> actual = + MyRecord.create(1, 100), + MyRecord.create(2, 200), + MyRecord.create(3, 300), + MyRecord.createWithoutId(999)); + ImmutableList<MyRecord> actual = ImmutableList.of( - Record.create(3, 311), - Record.create(2, 211), - Record.create(2, 222), - Record.create(4, 404), - Record.createWithoutId(888)); + MyRecord.create(3, 311), + MyRecord.create(2, 211), + MyRecord.create(2, 222), + MyRecord.create(4, 404), + MyRecord.createWithoutId(888)); expectFailure .whenTesting() .that(actual) @@ -1700,10 +1738,12 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test public void displayingDiffsPairedBy_containsAnyIn_withoutKeyMatches() { - ImmutableList<Record> expected = - ImmutableList.of(Record.create(1, 100), Record.create(2, 200), Record.createWithoutId(999)); - ImmutableList<Record> actual = - ImmutableList.of(Record.create(3, 300), Record.create(4, 411), Record.createWithoutId(888)); + ImmutableList<MyRecord> expected = + ImmutableList.of( + MyRecord.create(1, 100), MyRecord.create(2, 200), MyRecord.createWithoutId(999)); + ImmutableList<MyRecord> actual = + ImmutableList.of( + MyRecord.create(3, 300), MyRecord.create(4, 411), MyRecord.createWithoutId(888)); expectFailure .whenTesting() .that(actual) @@ -1719,14 +1759,15 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test public void displayingDiffsPairedBy_containsAnyIn_notUnique() { - ImmutableList<Record> expected = + ImmutableList<MyRecord> expected = + ImmutableList.of( + MyRecord.create(1, 100), + MyRecord.create(2, 200), + MyRecord.create(2, 250), + MyRecord.createWithoutId(999)); + ImmutableList<MyRecord> actual = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 200), - Record.create(2, 250), - Record.createWithoutId(999)); - ImmutableList<Record> actual = - ImmutableList.of(Record.create(3, 300), Record.create(2, 211), Record.createWithoutId(888)); + MyRecord.create(3, 300), MyRecord.create(2, 211), MyRecord.createWithoutId(888)); expectFailure .whenTesting() .that(actual) @@ -1743,9 +1784,9 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test public void displayingDiffsPairedBy_containsAnyIn_handlesFormatDiffExceptions() { - ImmutableList<Record> expected = - ImmutableList.of(Record.create(1, 100), Record.create(2, 200), Record.create(0, 999)); - List<Record> actual = asList(Record.create(3, 311), Record.create(4, 404), null); + ImmutableList<MyRecord> expected = + ImmutableList.of(MyRecord.create(1, 100), MyRecord.create(2, 200), MyRecord.create(0, 999)); + List<MyRecord> actual = asList(MyRecord.create(3, 311), MyRecord.create(4, 404), null); expectFailure .whenTesting() .that(actual) @@ -1993,28 +2034,28 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test public void formattingDiffsUsing_success() { - ImmutableList<Record> actual = - ImmutableList.of(Record.create(3, 300), Record.create(2, 200), Record.create(1, 100)); + ImmutableList<MyRecord> actual = + ImmutableList.of(MyRecord.create(3, 300), MyRecord.create(2, 200), MyRecord.create(1, 100)); assertThat(actual) .formattingDiffsUsing(RECORD_DIFF_FORMATTER) .displayingDiffsPairedBy(RECORD_ID) - .containsExactly(Record.create(1, 100), Record.create(2, 200), Record.create(3, 300)); + .containsExactly(MyRecord.create(1, 100), MyRecord.create(2, 200), MyRecord.create(3, 300)); } @Test public void formattingDiffsUsing_failure() { - ImmutableList<Record> actual = + ImmutableList<MyRecord> actual = ImmutableList.of( - Record.create(3, 300), - Record.create(2, 201), - Record.create(1, 100), - Record.create(2, 199)); + MyRecord.create(3, 300), + MyRecord.create(2, 201), + MyRecord.create(1, 100), + MyRecord.create(2, 199)); expectFailure .whenTesting() .that(actual) .formattingDiffsUsing(RECORD_DIFF_FORMATTER) .displayingDiffsPairedBy(RECORD_ID) - .containsExactly(Record.create(1, 100), Record.create(2, 200), Record.create(3, 300)); + .containsExactly(MyRecord.create(1, 100), MyRecord.create(2, 200), MyRecord.create(3, 300)); assertFailureKeys( "for key", "missing", diff --git a/core/src/test/java/com/google/common/truth/IterableSubjectTest.java b/core/src/test/java/com/google/common/truth/IterableSubjectTest.java index beab5ff7..aaa20465 100644 --- a/core/src/test/java/com/google/common/truth/IterableSubjectTest.java +++ b/core/src/test/java/com/google/common/truth/IterableSubjectTest.java @@ -21,9 +21,7 @@ import static java.util.Arrays.asList; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; -import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.List; @@ -40,6 +38,8 @@ import org.junit.runners.JUnit4; * @author Christian Gruber (cgruber@israfil.net) */ @RunWith(JUnit4.class) +// "Iterable" is specific enough to establish that we're testing IterableSubject. +@SuppressWarnings("PreferredInterfaceType") public class IterableSubjectTest extends BaseSubjectTestCase { @Test @@ -48,6 +48,7 @@ public class IterableSubjectTest extends BaseSubjectTestCase { } @Test + @SuppressWarnings({"TruthIterableIsEmpty", "IsEmptyTruth"}) public void hasSizeZero() { assertThat(ImmutableList.of()).hasSize(0); } @@ -204,16 +205,10 @@ public class IterableSubjectTest extends BaseSubjectTestCase { @Test public void iterableContainsAnyOfWithOneShotIterable() { - final Iterator<Object> iterator = asList((Object) 2, 1, "b").iterator(); - Iterable<Object> iterable = - new Iterable<Object>() { - @Override - public Iterator<Object> iterator() { - return iterator; - } - }; + List<Object> contents = asList(2, 1, "b"); + Iterable<Object> oneShot = new OneShotIterable<>(contents.iterator(), "OneShotIterable"); - assertThat(iterable).containsAnyOf(3, "a", 7, "b", 0); + assertThat(oneShot).containsAnyOf(3, "a", 7, "b", 0); } @Test @@ -390,50 +385,62 @@ public class IterableSubjectTest extends BaseSubjectTestCase { "expected order for required elements", "but was"); assertFailureValue("expected order for required elements", "[null, 1, 3]"); + assertFailureValue("but was", "[1, null, 3]"); } @Test - public void iterableContainsAtLeastInOrderWithOneShotIterable() { - final Iterable<Object> iterable = Arrays.<Object>asList(2, 1, null, 4, "a", 3, "b"); - final Iterator<Object> iterator = iterable.iterator(); - Iterable<Object> oneShot = - new Iterable<Object>() { - @Override - public Iterator<Object> iterator() { - return iterator; - } + public void iterableContainsAtLeastInOrderWithFailureWithActualOrder() { + expectFailureWhenTestingThat(asList(1, 2, null, 3, 4)).containsAtLeast(null, 1, 3).inOrder(); + assertFailureKeys( + "required elements were all found, but order was wrong", + "expected order for required elements", + "but order was", + "full contents"); + assertFailureValue("expected order for required elements", "[null, 1, 3]"); + assertFailureValue("but order was", "[1, null, 3]"); + assertFailureValue("full contents", "[1, 2, null, 3, 4]"); + } - @Override - public String toString() { - return Iterables.toString(iterable); - } - }; + @Test + public void iterableContainsAtLeastInOrderWithOneShotIterable() { + List<Object> contents = asList(2, 1, null, 4, "a", 3, "b"); + Iterable<Object> oneShot = new OneShotIterable<>(contents.iterator(), contents.toString()); assertThat(oneShot).containsAtLeast(1, null, 3).inOrder(); } @Test public void iterableContainsAtLeastInOrderWithOneShotIterableWrongOrder() { - final Iterator<Object> iterator = asList((Object) 2, 1, null, 4, "a", 3, "b").iterator(); - Iterable<Object> iterable = - new Iterable<Object>() { - @Override - public Iterator<Object> iterator() { - return iterator; - } - - @Override - public String toString() { - return "BadIterable"; - } - }; + List<Object> contents = asList(2, 1, null, 4, "a", 3, "b"); + Iterable<Object> oneShot = new OneShotIterable<>(contents.iterator(), "BadIterable"); - expectFailureWhenTestingThat(iterable).containsAtLeast(1, 3, (Object) null).inOrder(); + expectFailureWhenTestingThat(oneShot).containsAtLeast(1, 3, (Object) null).inOrder(); assertFailureKeys( "required elements were all found, but order was wrong", "expected order for required elements", "but was"); assertFailureValue("expected order for required elements", "[1, 3, null]"); + assertFailureValue("but was", "BadIterable"); // TODO(b/231966021): Output its elements. + } + + private static final class OneShotIterable<E> implements Iterable<E> { + private final Iterator<E> iterator; + private final String toString; + + OneShotIterable(Iterator<E> iterator, String toString) { + this.iterator = iterator; + this.toString = toString; + } + + @Override + public Iterator<E> iterator() { + return iterator; + } + + @Override + public String toString() { + return toString; + } } @Test @@ -442,6 +449,7 @@ public class IterableSubjectTest extends BaseSubjectTestCase { } @Test + @SuppressWarnings("ContainsAllElementsInWithVarArgsToContainsAtLeast") public void iterableContainsAtLeastElementsInIterable() { assertThat(asList(1, 2, 3)).containsAtLeastElementsIn(asList(1, 2)); @@ -452,6 +460,7 @@ public class IterableSubjectTest extends BaseSubjectTestCase { } @Test + @SuppressWarnings("ContainsAllElementsInWithVarArgsToContainsAtLeast") public void iterableContainsAtLeastElementsInCanUseFactPerElement() { expectFailureWhenTestingThat(asList("abc")) .containsAtLeastElementsIn(asList("123\n456", "789")); @@ -507,6 +516,7 @@ public class IterableSubjectTest extends BaseSubjectTestCase { } @Test + @SuppressWarnings("ContainsNoneInWithVarArgsToContainsNoneOf") public void iterableContainsNoneInIterable() { assertThat(asList(1, 2, 3)).containsNoneIn(asList(4, 5, 6)); expectFailureWhenTestingThat(asList(1, 2, 3)).containsNoneIn(asList(1, 2, 4)); @@ -531,6 +541,8 @@ public class IterableSubjectTest extends BaseSubjectTestCase { } @Test + // We tell people to call containsExactlyElementsIn, but we still test containsExactly. + @SuppressWarnings("ContainsExactlyVariadic") public void arrayContainsExactly() { ImmutableList<String> iterable = ImmutableList.of("a", "b"); String[] array = {"a", "b"}; @@ -652,6 +664,7 @@ public class IterableSubjectTest extends BaseSubjectTestCase { } @Test + @SuppressWarnings("ContainsExactlyElementsInWithVarArgsToExactly") public void iterableContainsExactlyWithElementsThatThrowWhenYouCallHashCode() { HashCodeThrower one = new HashCodeThrower(); HashCodeThrower two = new HashCodeThrower(); @@ -660,10 +673,24 @@ public class IterableSubjectTest extends BaseSubjectTestCase { assertThat(asList(one, two)).containsExactly(one, two).inOrder(); assertThat(asList(one, two)).containsExactlyElementsIn(asList(two, one)); assertThat(asList(one, two)).containsExactlyElementsIn(asList(one, two)).inOrder(); + } + + @Test + public void iterableContainsExactlyWithElementsThatThrowWhenYouCallHashCodeFailureTooMany() { + HashCodeThrower one = new HashCodeThrower(); + HashCodeThrower two = new HashCodeThrower(); expectFailureWhenTestingThat(asList(one, two)).containsExactly(one); } + @Test + public void iterableContainsExactlyWithElementsThatThrowWhenYouCallHashCodeOneMismatch() { + HashCodeThrower one = new HashCodeThrower(); + HashCodeThrower two = new HashCodeThrower(); + + expectFailureWhenTestingThat(asList(one, one)).containsExactly(one, two); + } + private static class HashCodeThrower { @Override public boolean equals(Object other) { @@ -682,17 +709,20 @@ public class IterableSubjectTest extends BaseSubjectTestCase { } @Test + @SuppressWarnings("ContainsExactlyNone") public void iterableContainsExactlyElementsInInOrderPassesWithEmptyExpectedAndActual() { assertThat(ImmutableList.of()).containsExactlyElementsIn(ImmutableList.of()).inOrder(); } @Test + @SuppressWarnings("ContainsExactlyNone") public void iterableContainsExactlyElementsInWithEmptyExpected() { expectFailureWhenTestingThat(asList("foo")).containsExactlyElementsIn(ImmutableList.of()); assertFailureKeys("expected to be empty", "but was"); } @Test + @SuppressWarnings("ContainsExactlyElementsInWithVarArgsToExactly") public void iterableContainsExactlyElementsInErrorMessageIsInOrder() { expectFailureWhenTestingThat(asList("foo OR bar")) .containsExactlyElementsIn(asList("foo", "bar")); @@ -842,6 +872,7 @@ public class IterableSubjectTest extends BaseSubjectTestCase { } @Test + @SuppressWarnings("ContainsExactlyElementsInWithVarArgsToExactly") public void iterableContainsExactlyElementsInWithOneIterableDoesNotGiveWarning() { expectFailureWhenTestingThat(asList(1, 2, 3, 4)).containsExactlyElementsIn(asList(1, 2, 3)); assertFailureValue("unexpected (1)", "4"); @@ -885,7 +916,7 @@ public class IterableSubjectTest extends BaseSubjectTestCase { @Test public void iterableContainsExactlyInOrderWithOneShotIterable() { - final Iterator<Object> iterator = asList((Object) 1, null, 3).iterator(); + Iterator<Object> iterator = asList((Object) 1, null, 3).iterator(); Iterable<Object> iterable = new Iterable<Object>() { @Override @@ -898,7 +929,7 @@ public class IterableSubjectTest extends BaseSubjectTestCase { @Test public void iterableContainsExactlyInOrderWithOneShotIterableWrongOrder() { - final Iterator<Object> iterator = asList((Object) 1, null, 3).iterator(); + Iterator<Object> iterator = asList((Object) 1, null, 3).iterator(); Iterable<Object> iterable = new Iterable<Object>() { @Override @@ -932,6 +963,7 @@ public class IterableSubjectTest extends BaseSubjectTestCase { } @Test + @SuppressWarnings("ContainsExactlyElementsInWithVarArgsToExactly") public void iterableContainsExactlyElementsInIterable() { assertThat(asList(1, 2)).containsExactlyElementsIn(asList(1, 2)); @@ -948,6 +980,7 @@ public class IterableSubjectTest extends BaseSubjectTestCase { } @Test + @SuppressWarnings("UndefinedEquals") // Iterable equality isn't defined, but null equality is public void nullEqualToNull() { assertThat((Iterable<?>) null).isEqualTo(null); } @@ -1107,13 +1140,9 @@ public class IterableSubjectTest extends BaseSubjectTestCase { assertFailureValue("full contents", "[1, 10, 2, 20]"); } + @SuppressWarnings("CompareProperty") // avoiding Java 8 API under Android private static final Comparator<String> COMPARE_AS_DECIMAL = - new Comparator<String>() { - @Override - public int compare(String a, String b) { - return Integer.valueOf(a).compareTo(Integer.valueOf(b)); - } - }; + (a, b) -> Integer.valueOf(a).compareTo(Integer.valueOf(b)); private static class Foo { private final int x; @@ -1129,13 +1158,20 @@ public class IterableSubjectTest extends BaseSubjectTestCase { } } - private static final Comparator<Foo> FOO_COMPARATOR = - new Comparator<Foo>() { - @Override - public int compare(Foo a, Foo b) { - return (a.x < b.x) ? -1 : ((a.x > b.x) ? 1 : 0); - } - }; + // We can't use Comparators.comparing under old versions of Android. + @SuppressWarnings({ + "CompareProperty", + "DoubleProperty_ExtractTernaryHead", + "FloatProperty_ExtractTernaryHead", + "IntegerProperty_ExtractTernaryHead", + "LongProperty_ExtractTernaryHead", + }) + // Even though Integer.compare was added in Java 7, we use it even under old versions of Android, + // even without library desugaring on: It and a few other APIs are *always* desguared: + // https://r8.googlesource.com/r8/+/a7563f86014d44f961f40fc109ab1c1073f2ee4e/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java + // Now, if this code weren't in Truth's *tests*, then it would cause Animal Sniffer to complain. + // In that case, we might fall back to the deprecated Guava Ints.compare. + private static final Comparator<Foo> FOO_COMPARATOR = (a, b) -> Integer.compare(a.x, b.x); @Test public void iterableOrderedByBaseClassComparator() { @@ -1155,6 +1191,7 @@ public class IterableSubjectTest extends BaseSubjectTestCase { } @Test + @SuppressWarnings("deprecation") // test of a mistaken call public void isNotIn() { ImmutableList<String> actual = ImmutableList.of("a"); @@ -1180,7 +1217,7 @@ public class IterableSubjectTest extends BaseSubjectTestCase { } @Test - @SuppressWarnings("IncompatibleArgumentType") + @SuppressWarnings({"IncompatibleArgumentType", "deprecation"}) // test of a mistaken call public void isNoneOf() { ImmutableList<String> actual = ImmutableList.of("a"); diff --git a/core/src/test/java/com/google/common/truth/MapSubjectTest.java b/core/src/test/java/com/google/common/truth/MapSubjectTest.java index 6e28fdb8..83382e6f 100644 --- a/core/src/test/java/com/google/common/truth/MapSubjectTest.java +++ b/core/src/test/java/com/google/common/truth/MapSubjectTest.java @@ -1261,7 +1261,9 @@ public class MapSubjectTest extends BaseSubjectTestCase { "first exception"); assertThatFailure() .factValue("first exception", 0) - .startsWith("compare(null, 60) threw java.lang.NullPointerException"); + .startsWith( + "compare(null, 60) threw" + + " com.google.common.truth.TestCorrespondences$NullPointerExceptionFromWithin10Of"); assertThatFailure() .factValue("first exception", 1) .startsWith("formatDiff(null, 60) threw java.lang.NullPointerException"); @@ -1710,7 +1712,9 @@ public class MapSubjectTest extends BaseSubjectTestCase { "first exception"); assertThatFailure() .factValue("first exception", 0) - .startsWith("compare(null, 60) threw java.lang.NullPointerException"); + .startsWith( + "compare(null, 60) threw" + + " com.google.common.truth.TestCorrespondences$NullPointerExceptionFromWithin10Of"); assertThatFailure() .factValue("first exception", 1) .startsWith("formatDiff(null, 60) threw java.lang.NullPointerException"); @@ -2068,7 +2072,9 @@ public class MapSubjectTest extends BaseSubjectTestCase { "first exception"); assertThatFailure() .factValue("first exception", 0) - .startsWith("compare(null, 60) threw java.lang.NullPointerException"); + .startsWith( + "compare(null, 60) threw" + + " com.google.common.truth.TestCorrespondences$NullPointerExceptionFromWithin10Of"); assertThatFailure() .factValue("first exception", 1) .startsWith("formatDiff(null, 60) threw java.lang.NullPointerException"); diff --git a/core/src/test/java/com/google/common/truth/MathUtilTest.java b/core/src/test/java/com/google/common/truth/MathUtilTest.java index 292bacd8..1200fafe 100644 --- a/core/src/test/java/com/google/common/truth/MathUtilTest.java +++ b/core/src/test/java/com/google/common/truth/MathUtilTest.java @@ -107,6 +107,6 @@ public class MathUtilTest { assertThat(equalWithinTolerance(1.3f, 1.3d, 0.00000000000001f)).isFalse(); } - // TODO(user): More complicated ways to break float/double casting to make sure. + // TODO(cgruber): More complicated ways to break float/double casting to make sure. } diff --git a/core/src/test/java/com/google/common/truth/ObjectArraySubjectTest.java b/core/src/test/java/com/google/common/truth/ObjectArraySubjectTest.java index d33682a8..b1f7c35f 100644 --- a/core/src/test/java/com/google/common/truth/ObjectArraySubjectTest.java +++ b/core/src/test/java/com/google/common/truth/ObjectArraySubjectTest.java @@ -40,7 +40,7 @@ public class ObjectArraySubjectTest extends BaseSubjectTestCase { @SuppressWarnings("TruthSelfEquals") @Test - public void isEqualTo_Same() { + public void isEqualTo_same() { Object[] same = objectArray("A", 5L); assertThat(same).isEqualTo(same); } @@ -104,20 +104,20 @@ public class ObjectArraySubjectTest extends BaseSubjectTestCase { } @Test - public void isEqualTo_Fail_UnequalOrdering() { + public void isEqualTo_fail_unequalOrdering() { expectFailureWhenTestingThat(objectArray("A", 5L)).isEqualTo(objectArray(5L, "A")); assertFailureValue("differs at index", "[0]"); } @Test - public void isEqualTo_Fail_UnequalOrderingMultiDimensional_00() { + public void isEqualTo_fail_unequalOrderingMultiDimensional_00() { expectFailureWhenTestingThat(new Object[][] {{"A"}, {5L}}) .isEqualTo(new Object[][] {{5L}, {"A"}}); assertFailureValue("differs at index", "[0][0]"); } @Test - public void isEqualTo_Fail_UnequalOrderingMultiDimensional_01() { + public void isEqualTo_fail_unequalOrderingMultiDimensional_01() { expectFailureWhenTestingThat(new Object[][] {{"A", "B"}, {5L}}) .isEqualTo(new Object[][] {{"A"}, {5L}}); assertFailureValue("wrong length for index", "[0]"); @@ -126,7 +126,7 @@ public class ObjectArraySubjectTest extends BaseSubjectTestCase { } @Test - public void isEqualTo_Fail_UnequalOrderingMultiDimensional_11() { + public void isEqualTo_fail_unequalOrderingMultiDimensional_11() { expectFailureWhenTestingThat(new Object[][] {{"A"}, {5L}}) .isEqualTo(new Object[][] {{"A"}, {5L, 6L}}); assertFailureValue("wrong length for index", "[1]"); @@ -135,35 +135,35 @@ public class ObjectArraySubjectTest extends BaseSubjectTestCase { } @Test - public void isEqualTo_Fail_NotAnArray() { + public void isEqualTo_fail_notAnArray() { expectFailureWhenTestingThat(objectArray("A", 5L)).isEqualTo(new Object()); } @Test - public void isNotEqualTo_SameLengths() { + public void isNotEqualTo_sameLengths() { assertThat(objectArray("A", 5L)).isNotEqualTo(objectArray("C", 5L)); assertThat(new Object[][] {{"A"}, {5L}}).isNotEqualTo(new Object[][] {{"C"}, {5L}}); } @Test - public void isNotEqualTo_DifferentLengths() { + public void isNotEqualTo_differentLengths() { assertThat(objectArray("A", 5L)).isNotEqualTo(objectArray("A", 5L, "c")); assertThat(new Object[][] {{"A"}, {5L}}).isNotEqualTo(new Object[][] {{"A", "c"}, {5L}}); assertThat(new Object[][] {{"A"}, {5L}}).isNotEqualTo(new Object[][] {{"A"}, {5L}, {"C"}}); } @Test - public void isNotEqualTo_DifferentTypes() { + public void isNotEqualTo_differentTypes() { assertThat(objectArray("A", 5L)).isNotEqualTo(new Object()); } @Test - public void isNotEqualTo_FailEquals() { + public void isNotEqualTo_failEquals() { expectFailureWhenTestingThat(objectArray("A", 5L)).isNotEqualTo(objectArray("A", 5L)); } @Test - public void isNotEqualTo_FailEqualsMultiDimensional() { + public void isNotEqualTo_failEqualsMultiDimensional() { expectFailureWhenTestingThat(new Object[][] {{"A"}, {5L}}) .isNotEqualTo(new Object[][] {{"A"}, {5L}}); assertFailureValue("expected not to be", "[[A], [5]]"); @@ -171,14 +171,14 @@ public class ObjectArraySubjectTest extends BaseSubjectTestCase { @SuppressWarnings("TruthSelfEquals") @Test - public void isNotEqualTo_FailSame() { + public void isNotEqualTo_failSame() { Object[] same = objectArray("A", 5L); expectFailureWhenTestingThat(same).isNotEqualTo(same); } @SuppressWarnings("TruthSelfEquals") @Test - public void isNotEqualTo_FailSameMultiDimensional() { + public void isNotEqualTo_failSameMultiDimensional() { Object[][] same = new Object[][] {{"A"}, {5L}}; expectFailureWhenTestingThat(same).isNotEqualTo(same); } @@ -201,7 +201,7 @@ public class ObjectArraySubjectTest extends BaseSubjectTestCase { } @Test - public void stringArrayIsEqualTo_Fail_UnequalLength() { + public void stringArrayIsEqualTo_fail_unequalLength() { expectFailureWhenTestingThat(objectArray("A", "B")).isEqualTo(objectArray("B")); assertFailureKeys("expected", "but was", "wrong length", "expected", "but was"); assertFailureValueIndexed("expected", 1, "1"); @@ -209,7 +209,7 @@ public class ObjectArraySubjectTest extends BaseSubjectTestCase { } @Test - public void stringArrayIsEqualTo_Fail_UnequalLengthMultiDimensional() { + public void stringArrayIsEqualTo_fail_unequalLengthMultiDimensional() { expectFailureWhenTestingThat(new String[][] {{"A"}, {"B"}}).isEqualTo(new String[][] {{"A"}}); assertFailureKeys("expected", "but was", "wrong length", "expected", "but was"); assertFailureValueIndexed("expected", 1, "1"); @@ -217,20 +217,20 @@ public class ObjectArraySubjectTest extends BaseSubjectTestCase { } @Test - public void stringArrayIsEqualTo_Fail_UnequalOrdering() { + public void stringArrayIsEqualTo_fail_unequalOrdering() { expectFailureWhenTestingThat(objectArray("A", "B")).isEqualTo(objectArray("B", "A")); assertFailureValue("differs at index", "[0]"); } @Test - public void stringArrayIsEqualTo_Fail_UnequalOrderingMultiDimensional() { + public void stringArrayIsEqualTo_fail_unequalOrderingMultiDimensional() { expectFailureWhenTestingThat(new String[][] {{"A"}, {"B"}}) .isEqualTo(new String[][] {{"B"}, {"A"}}); assertFailureValue("differs at index", "[0][0]"); } @Test - public void setArrayIsEqualTo_Fail_UnequalOrdering() { + public void setArrayIsEqualTo_fail_unequalOrdering() { expectFailureWhenTestingThat(objectArray(ImmutableSet.of("A"), ImmutableSet.of("B"))) .isEqualTo(objectArray(ImmutableSet.of("B"), ImmutableSet.of("A"))); assertFailureValue("differs at index", "[0]"); @@ -245,7 +245,7 @@ public class ObjectArraySubjectTest extends BaseSubjectTestCase { } @Test - public void primitiveMultiDimensionalArrayIsEqualTo_Fail_UnequalOrdering() { + public void primitiveMultiDimensionalArrayIsEqualTo_fail_unequalOrdering() { expectFailureWhenTestingThat(new int[][] {{1, 2}, {3}, {4, 5, 6}}) .isEqualTo(new int[][] {{1, 2}, {3}, {4, 5, 6, 7}}); assertFailureValue("wrong length for index", "[2]"); @@ -260,7 +260,7 @@ public class ObjectArraySubjectTest extends BaseSubjectTestCase { } @Test - public void primitiveMultiDimensionalArrayIsNotEqualTo_Fail_Equal() { + public void primitiveMultiDimensionalArrayIsNotEqualTo_fail_equal() { expectFailureWhenTestingThat(new int[][] {{1, 2}, {3}, {4, 5, 6}}) .isNotEqualTo(new int[][] {{1, 2}, {3}, {4, 5, 6}}); } @@ -282,7 +282,7 @@ public class ObjectArraySubjectTest extends BaseSubjectTestCase { return ts; } - private static Set[] objectArray(Set... ts) { + private static Set<?>[] objectArray(Set<?>... ts) { return ts; } diff --git a/core/src/test/java/com/google/common/truth/StackTraceCleanerJUnit3Test.java b/core/src/test/java/com/google/common/truth/StackTraceCleanerJUnit3Test.java new file mode 100644 index 00000000..ab410d9d --- /dev/null +++ b/core/src/test/java/com/google/common/truth/StackTraceCleanerJUnit3Test.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017 Google, Inc. + * + * 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.common.truth; + +import static com.google.common.truth.Truth.assertThat; + +import junit.framework.TestCase; + +/** + * JUnit3 tests for {@link StackTraceCleaner}. + * + * <p>The "main" tests are in {@link StackTraceCleanerTest}. + */ +public class StackTraceCleanerJUnit3Test extends TestCase { + public void testSimple() { + try { + assertThat(0).isEqualTo(1); + throw new Error(); + } catch (AssertionError failure) { + assertThat(failure.getStackTrace()).hasLength(1); + } + } +} diff --git a/core/src/test/java/com/google/common/truth/StackTraceCleanerTest.java b/core/src/test/java/com/google/common/truth/StackTraceCleanerTest.java index f42eee1f..fc92cec0 100644 --- a/core/src/test/java/com/google/common/truth/StackTraceCleanerTest.java +++ b/core/src/test/java/com/google/common/truth/StackTraceCleanerTest.java @@ -97,7 +97,7 @@ public class StackTraceCleanerTest extends BaseSubjectTestCase { } @Test - public void assertionsActuallyUseCleaner_ComparisonFailure() { + public void assertionsActuallyUseCleaner_comparisonFailure() { expectFailure.whenTesting().that("1").isEqualTo("2"); assertThat(expectFailure.getFailure().getStackTrace()[0].getClassName()) .isEqualTo(getClass().getName()); @@ -453,8 +453,7 @@ public class StackTraceCleanerTest extends BaseSubjectTestCase { 0); } - private static class SelfReferencingThrowable extends Throwable { - + private static class SelfReferencingThrowable extends Exception { SelfReferencingThrowable(String... classNames) { setStackTrace(createStackTrace(classNames)); } diff --git a/core/src/test/java/com/google/common/truth/StringSubjectTest.java b/core/src/test/java/com/google/common/truth/StringSubjectTest.java index dbb89112..ec247402 100644 --- a/core/src/test/java/com/google/common/truth/StringSubjectTest.java +++ b/core/src/test/java/com/google/common/truth/StringSubjectTest.java @@ -73,7 +73,7 @@ public class StringSubjectTest extends BaseSubjectTestCase { @Test public void stringIsEmptyFailNull() { expectFailureWhenTestingThat(null).isEmpty(); - assertFailureKeys("expected empty string", "but was"); + assertFailureKeys("expected an empty string", "but was"); } @Test @@ -90,7 +90,7 @@ public class StringSubjectTest extends BaseSubjectTestCase { @Test public void stringIsNotEmptyFailNull() { expectFailureWhenTestingThat(null).isNotEmpty(); - assertFailureKeys("expected nonempty string", "but was"); + assertFailureKeys("expected a non-empty string", "but was"); } @Test diff --git a/core/src/test/java/com/google/common/truth/SubjectTest.java b/core/src/test/java/com/google/common/truth/SubjectTest.java index 32919ce4..9544f52f 100644 --- a/core/src/test/java/com/google/common/truth/SubjectTest.java +++ b/core/src/test/java/com/google/common/truth/SubjectTest.java @@ -18,9 +18,11 @@ package com.google.common.truth; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.ExpectFailure.assertThat; import static com.google.common.truth.Fact.simpleFact; +import static com.google.common.truth.SubjectTest.ForbidsEqualityChecksSubject.objectsForbiddingEqualityCheck; import static com.google.common.truth.TestPlatform.isGwt; import static com.google.common.truth.Truth.assertAbout; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.TruthJUnit.assume; import static org.junit.Assert.fail; import com.google.common.annotations.GwtIncompatible; @@ -35,7 +37,6 @@ import com.google.common.collect.ImmutableTable; import com.google.common.collect.Iterators; import com.google.common.primitives.UnsignedInteger; import com.google.common.testing.NullPointerTester; -import com.google.common.truth.Subject.Factory; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigDecimal; @@ -58,7 +59,16 @@ public class SubjectTest extends BaseSubjectTestCase { @Test @GwtIncompatible("NullPointerTester") @SuppressWarnings("GoogleInternalApi") + /* + * TODO(cpovirk): Reenable these tests publicly. Currently, we depend on guava-android, whose + * NullPointerTester doesn't yet recognize type-use @Nullable annotations. And we can't mix the + * -jre version of guava-testlib with the -android version of guava because the NullPointerTester + * feature we need requires a -jre-only API. + */ + @org.junit.Ignore public void nullPointerTester() { + assume().that(isAndroid()).isFalse(); // type-annotation @Nullable is not available + NullPointerTester npTester = new NullPointerTester(); npTester.setDefault(Fact.class, simpleFact("fact")); @@ -88,7 +98,10 @@ public class SubjectTest extends BaseSubjectTestCase { @Test @GwtIncompatible("NullPointerTester") + @org.junit.Ignore // TODO(cpovirk): Reenable publicly. (See nullPointerTester().) public void allAssertThatOverloadsAcceptNull() throws Exception { + assume().that(isAndroid()).isFalse(); // type-annotation @Nullable is not available + NullPointerTester npTester = new NullPointerTester(); npTester.setDefault(Fact.class, simpleFact("fact")); for (Method method : Truth.class.getDeclaredMethods()) { @@ -447,6 +460,10 @@ public class SubjectTest extends BaseSubjectTestCase { assertThat(a).isNotEqualTo(b); } + @SuppressWarnings({ + "BoxedPrimitiveConstructor", + "deprecation" + }) // intentional check on non-identity objects @Test public void isNotEqualToFailureWithObjects() { Object o = new Integer(1); @@ -481,16 +498,19 @@ public class SubjectTest extends BaseSubjectTestCase { expectFailure.whenTesting().that(o).isNotEqualTo(o); } + @SuppressWarnings("IsInstanceString") // test is an intentional trivially true check @Test public void isInstanceOfExactType() { assertThat("a").isInstanceOf(String.class); } + @SuppressWarnings("IsInstanceInteger") // test is an intentional trivially true check @Test public void isInstanceOfSuperclass() { assertThat(3).isInstanceOf(Number.class); } + @SuppressWarnings("IsInstanceString") // test is an intentional trivially true check @Test public void isInstanceOfImplementedInterface() { if (isGwt()) { @@ -540,6 +560,17 @@ public class SubjectTest extends BaseSubjectTestCase { expectFailure.whenTesting().that((Object) null).isInstanceOf(CharSequence.class); } + // false positive; actually an intentional trivially *false* check + @SuppressWarnings("IsInstanceInteger") + @Test + public void isInstanceOfPrimitiveType() { + try { + assertThat(1).isInstanceOf(int.class); + fail(); + } catch (IllegalArgumentException expected) { + } + } + @Test public void isNotInstanceOfUnrelatedClass() { assertThat("a").isNotInstanceOf(Long.class); @@ -586,6 +617,15 @@ public class SubjectTest extends BaseSubjectTestCase { } @Test + public void isNotInstanceOfPrimitiveType() { + try { + assertThat(1).isNotInstanceOf(int.class); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + @Test public void isIn() { assertThat("b").isIn(oneShotIterable("a", "b", "c")); } @@ -707,7 +747,8 @@ public class SubjectTest extends BaseSubjectTestCase { } @Test - @SuppressWarnings({"EqualsIncompatibleType", "DoNotCall"}) + // test of a mistaken call + @SuppressWarnings({"EqualsIncompatibleType", "DoNotCall", "deprecation"}) public void equalsThrowsUSOE() { try { boolean unused = assertThat(5).equals(5); @@ -724,7 +765,8 @@ public class SubjectTest extends BaseSubjectTestCase { } @Test - @SuppressWarnings("DoNotCall") + // test of a mistaken call + @SuppressWarnings({"DoNotCall", "deprecation"}) public void hashCodeThrowsUSOE() { try { int unused = assertThat(5).hashCode(); @@ -740,8 +782,8 @@ public class SubjectTest extends BaseSubjectTestCase { assertThat((Object) null).ignoreCheck().that("foo").isNull(); } - private static <T> Iterable<T> oneShotIterable(final T... values) { - final Iterator<T> iterator = Iterators.forArray(values); + private static <T> Iterable<T> oneShotIterable(T... values) { + Iterator<T> iterator = Iterators.forArray(values); return new Iterable<T>() { @Override public Iterator<T> iterator() { @@ -756,6 +798,7 @@ public class SubjectTest extends BaseSubjectTestCase { } @Test + @SuppressWarnings("TruthIncompatibleType") // test of a mistaken call public void disambiguationWithSameToString() { expectFailure.whenTesting().that(new StringBuilder("foo")).isEqualTo(new StringBuilder("foo")); assertFailureKeys("expected", "but was"); @@ -784,7 +827,11 @@ public class SubjectTest extends BaseSubjectTestCase { } } - private static final class ForbidsEqualityChecksSubject extends Subject { + static final class ForbidsEqualityChecksSubject extends Subject { + static Factory<ForbidsEqualityChecksSubject, Object> objectsForbiddingEqualityCheck() { + return ForbidsEqualityChecksSubject::new; + } + ForbidsEqualityChecksSubject(FailureMetadata metadata, @Nullable Object actual) { super(metadata, actual); } @@ -802,13 +849,7 @@ public class SubjectTest extends BaseSubjectTestCase { } } - private static Subject.Factory<ForbidsEqualityChecksSubject, Object> - objectsForbiddingEqualityCheck() { - return new Factory<ForbidsEqualityChecksSubject, Object>() { - @Override - public ForbidsEqualityChecksSubject createSubject(FailureMetadata metadata, Object actual) { - return new ForbidsEqualityChecksSubject(metadata, actual); - } - }; + private static boolean isAndroid() { + return System.getProperty("java.runtime.name").contains("Android"); } } diff --git a/core/src/test/java/com/google/common/truth/TableSubjectTest.java b/core/src/test/java/com/google/common/truth/TableSubjectTest.java index aea450c2..b96fd6eb 100644 --- a/core/src/test/java/com/google/common/truth/TableSubjectTest.java +++ b/core/src/test/java/com/google/common/truth/TableSubjectTest.java @@ -15,6 +15,7 @@ */ package com.google.common.truth; +import static com.google.common.truth.ExpectFailure.assertThat; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; @@ -88,10 +89,16 @@ public class TableSubjectTest extends BaseSubjectTestCase { @Test public void containsFailure() { ImmutableTable<String, String, String> table = ImmutableTable.of("row", "col", "val"); - expectFailureWhenTestingThat(table).contains("row", "row"); + expectFailureWhenTestingThat(table).contains("row", "otherCol"); assertThat(expectFailure.getFailure()) - .hasMessageThat() - .isEqualTo("Not true that <{row={col=val}}> contains mapping for row/column <row> <row>"); + .factKeys() + .containsExactly( + "expected to contain mapping for row-column key pair", + "row key", + "column key", + "but was"); + assertThat(expectFailure.getFailure()).factValue("row key").isEqualTo("row"); + assertThat(expectFailure.getFailure()).factValue("column key").isEqualTo("otherCol"); } @Test @@ -108,10 +115,16 @@ public class TableSubjectTest extends BaseSubjectTestCase { ImmutableTable<String, String, String> table = ImmutableTable.of("row", "col", "val"); expectFailureWhenTestingThat(table).doesNotContain("row", "col"); assertThat(expectFailure.getFailure()) - .hasMessageThat() - .isEqualTo( - "Not true that <{row={col=val}}> does not contain mapping for " - + "row/column <row> <col>"); + .factKeys() + .containsExactly( + "expected not to contain mapping for row-column key pair", + "row key", + "column key", + "but contained value", + "full contents"); + assertThat(expectFailure.getFailure()).factValue("row key").isEqualTo("row"); + assertThat(expectFailure.getFailure()).factValue("column key").isEqualTo("col"); + assertThat(expectFailure.getFailure()).factValue("but contained value").isEqualTo("val"); } @Test @@ -158,7 +171,7 @@ public class TableSubjectTest extends BaseSubjectTestCase { return Tables.immutableCell(row, col, val); } - private TableSubject expectFailureWhenTestingThat(Table actual) { + private TableSubject expectFailureWhenTestingThat(Table<?, ?, ?> actual) { return expectFailure.whenTesting().that(actual); } } diff --git a/core/src/test/java/com/google/common/truth/TestCorrespondences.java b/core/src/test/java/com/google/common/truth/TestCorrespondences.java index d9cca3e1..d2488d5c 100644 --- a/core/src/test/java/com/google/common/truth/TestCorrespondences.java +++ b/core/src/test/java/com/google/common/truth/TestCorrespondences.java @@ -33,16 +33,7 @@ final class TestCorrespondences { * correspond to null only. */ static final Correspondence<String, Integer> STRING_PARSES_TO_INTEGER_CORRESPONDENCE = - Correspondence.from( - // If we were allowed to use method references, this would be: - // TestCorrespondences::stringParsesToInteger, - new Correspondence.BinaryPredicate<String, Integer>() { - @Override - public boolean apply(@Nullable String actual, @Nullable Integer expected) { - return stringParsesToInteger(actual, expected); - } - }, - "parses to"); + Correspondence.from(TestCorrespondences::stringParsesToInteger, "parses to"); private static boolean stringParsesToInteger( @Nullable String actual, @Nullable Integer expected) { @@ -64,14 +55,7 @@ final class TestCorrespondences { /** A formatter for the diffs between integers. */ static final Correspondence.DiffFormatter<Integer, Integer> INT_DIFF_FORMATTER = - // If we were allowed to use lambdas, this would be: - // (a, e) -> Integer.toString(a - e)); - new Correspondence.DiffFormatter<Integer, Integer>() { - @Override - public String formatDiff(Integer actual, Integer expected) { - return Integer.toString(actual - expected); - } - }; + (a, e) -> Integer.toString(a - e); /** * A correspondence between integers which tests whether they are within 10 of each other. Smart @@ -80,31 +64,23 @@ final class TestCorrespondences { */ static final Correspondence<Integer, Integer> WITHIN_10_OF = Correspondence.from( - // If we were allowed to use lambdas, this would be: - // (Integer a, Integer e) -> Math.abs(a - e) <= 10, - new Correspondence.BinaryPredicate<Integer, Integer>() { - @Override - public boolean apply(Integer actual, Integer expected) { - return Math.abs(actual - expected) <= 10; + (Integer actual, Integer expected) -> { + if (actual == null || expected == null) { + throw new NullPointerExceptionFromWithin10Of(); } + return Math.abs(actual - expected) <= 10; }, "is within 10 of") .formattingDiffsUsing(INT_DIFF_FORMATTER); + private static final class NullPointerExceptionFromWithin10Of extends NullPointerException {} + /** * A correspondence between strings which tests for case-insensitive equality. Supports null * expected elements, but throws {@link NullPointerException} on null actual elements. */ static final Correspondence<String, String> CASE_INSENSITIVE_EQUALITY = - Correspondence.from( - // If we were allowed to use method references, this would be String::equalsIgnoreCase. - new Correspondence.BinaryPredicate<String, String>() { - @Override - public boolean apply(String actual, String expected) { - return actual.equalsIgnoreCase(expected); - } - }, - "equals (ignoring case)"); + Correspondence.from(String::equalsIgnoreCase, "equals (ignoring case)"); /** * A correspondence between strings which tests for case-insensitive equality, with a broken @@ -114,16 +90,13 @@ final class TestCorrespondences { */ static final Correspondence<String, String> CASE_INSENSITIVE_EQUALITY_HALF_NULL_SAFE = Correspondence.from( - // If we were allowed to use method references, this would be: - // TestCorrespondences::equalsIgnoreCaseHalfNullSafe, - new Correspondence.BinaryPredicate<String, String>() { - @Override - public boolean apply(String actual, String expected) { - return equalsIgnoreCaseHalfNullSafe(actual, expected); - } - }, - "equals (ignoring case)"); + TestCorrespondences::equalsIgnoreCaseHalfNullSafe, "equals (ignoring case)"); + /* + * This is just an example for a test, and it's a convenient way to demonstrate the specific null + * behavior documented below. + */ + @SuppressWarnings("Casing_StringEqualsIgnoreCase") private static boolean equalsIgnoreCaseHalfNullSafe(String actual, String expected) { if (actual == null && expected == null) { return true; @@ -136,22 +109,22 @@ final class TestCorrespondences { * An example value object. It has an optional {@code id} field and a required {@code score} * field, both positive integers. */ - static final class Record { + static final class MyRecord { private final int id; private final int score; - static Record create(int id, int score) { + static MyRecord create(int id, int score) { checkState(id >= 0); checkState(score > 0); - return new Record(id, score); + return new MyRecord(id, score); } - static Record createWithoutId(int score) { + static MyRecord createWithoutId(int score) { checkState(score >= 0); - return new Record(-1, score); + return new MyRecord(-1, score); } - Record(int id, int score) { + MyRecord(int id, int score) { this.id = id; this.score = score; } @@ -169,14 +142,14 @@ final class TestCorrespondences { return score; } - boolean hasSameId(Record that) { + boolean hasSameId(MyRecord that) { return this.id == that.id; } @Override public boolean equals(@Nullable Object o) { - if (o instanceof Record) { - Record that = (Record) o; + if (o instanceof MyRecord) { + MyRecord that = (MyRecord) o; return this.id == that.id && this.score == that.score; } return false; @@ -200,56 +173,42 @@ final class TestCorrespondences { * If the argument is the string form of a record, returns that record; otherwise returns {@code * null}. */ - static @Nullable Record parse(String str) { + static @Nullable MyRecord parse(String str) { List<String> parts = Splitter.on('/').splitToList(str); if (parts.size() != 2) { return null; } - @Nullable Integer id = parts.get(0).equals("none") ? -1 : Ints.tryParse(parts.get(0)); - @Nullable Integer score = Ints.tryParse(parts.get(1)); + Integer id = parts.get(0).equals("none") ? -1 : Ints.tryParse(parts.get(0)); + Integer score = Ints.tryParse(parts.get(1)); if (id == null || score == null) { return null; } - return new Record(id, score); + return new MyRecord(id, score); } } /** - * A correspondence between {@link Record} instances which tests whether their {@code id} values + * A correspondence between {@link MyRecord} instances which tests whether their {@code id} values * are equal and their {@code score} values are within 10 of each other. Smart diffing is not * supported. * * <p>The {@link Correspondence#compare} implementation support nulls, such that null corresponds * to null only. The {@link Correspondence#formatDiff} implementation does not support nulls. */ - static final Correspondence<Record, Record> RECORDS_EQUAL_WITH_SCORE_TOLERANCE_10_NO_DIFF = + static final Correspondence<MyRecord, MyRecord> RECORDS_EQUAL_WITH_SCORE_TOLERANCE_10_NO_DIFF = Correspondence.from( - // If we were allowed to use method references, this would be: - // TestCorrespondences::recordsAreCloseEnough, - new Correspondence.BinaryPredicate<Record, Record>() { - @Override - public boolean apply(Record actual, Record expected) { - return recordsAreCloseEnough(actual, expected); - } - }, + TestCorrespondences::recordsAreCloseEnough, "has the same id as and a score within 10 of"); /** * A formatter for diffs between records. If the records have the same key, it gives a string of * the form {@code "score:<score_diff>"}. If they have different keys, it gives null. */ - static final Correspondence.DiffFormatter<Record, Record> RECORD_DIFF_FORMATTER = - // If we were allowed to use method references, this would be: - // TestCorrespondences::formatRecordDiff); - new Correspondence.DiffFormatter<Record, Record>() { - @Override - public String formatDiff(Record actual, Record expected) { - return formatRecordDiff(actual, expected); - } - }; + static final Correspondence.DiffFormatter<MyRecord, MyRecord> RECORD_DIFF_FORMATTER = + TestCorrespondences::formatRecordDiff; /** - * A correspondence between {@link Record} instances which tests whether their {@code id} values + * A correspondence between {@link MyRecord} instances which tests whether their {@code id} values * are equal and their {@code score} values are within 10 of each other. Smart diffing is enabled * for records with equal {@code id} values, with a formatted diff showing the actual {@code * score} value less the expected {@code score} value preceded by the literal {@code score:}. @@ -257,7 +216,7 @@ final class TestCorrespondences { * <p>The {@link Correspondence#compare} implementation support nulls, such that null corresponds * to null only. The {@link Correspondence#formatDiff} implementation does not support nulls. */ - static final Correspondence<Record, Record> RECORDS_EQUAL_WITH_SCORE_TOLERANCE_10 = + static final Correspondence<MyRecord, MyRecord> RECORDS_EQUAL_WITH_SCORE_TOLERANCE_10 = RECORDS_EQUAL_WITH_SCORE_TOLERANCE_10_NO_DIFF.formattingDiffsUsing(RECORD_DIFF_FORMATTER); /** @@ -265,36 +224,21 @@ final class TestCorrespondences { * values are strings which will be parsed before comparing. If the string does not parse to a * record then it does not correspond and is not diffed. Does not support null strings or records. */ - static final Correspondence<String, Record> PARSED_RECORDS_EQUAL_WITH_SCORE_TOLERANCE_10 = + static final Correspondence<String, MyRecord> PARSED_RECORDS_EQUAL_WITH_SCORE_TOLERANCE_10 = Correspondence.from( - // If we were allowed to use lambdas, this would be: - // (String a, Record e) -> { - // @Nullable Record actualRecord = Record.parse(a); - // return actualRecord != null && recordsAreCloseEnough(actualRecord, e); - // }, - new Correspondence.BinaryPredicate<String, Record>() { - @Override - public boolean apply(String actual, Record expected) { - @Nullable Record actualRecord = Record.parse(actual); - return actualRecord != null && recordsAreCloseEnough(actualRecord, expected); - } + (String a, MyRecord e) -> { + MyRecord actualRecord = MyRecord.parse(a); + return actualRecord != null && recordsAreCloseEnough(actualRecord, e); }, "parses to a record that " + RECORDS_EQUAL_WITH_SCORE_TOLERANCE_10) .formattingDiffsUsing( - // If we were allowe to use lambdas, this would be: - // (a, e) -> { - // @Nullable Record actualRecord = Record.parse(a); - // return actualRecord != null ? formatRecordDiff(actualRecord, e) : null; - // }); - new Correspondence.DiffFormatter<String, Record>() { - @Override - public String formatDiff(String actual, Record expected) { - @Nullable Record actualRecord = Record.parse(actual); - return actualRecord != null ? formatRecordDiff(actualRecord, expected) : null; - } + (a, e) -> { + MyRecord actualRecord = MyRecord.parse(a); + return actualRecord != null ? formatRecordDiff(actualRecord, e) : null; }); - private static boolean recordsAreCloseEnough(@Nullable Record actual, @Nullable Record expected) { + private static boolean recordsAreCloseEnough( + @Nullable MyRecord actual, @Nullable MyRecord expected) { if (actual == null) { return expected == null; } @@ -304,7 +248,7 @@ final class TestCorrespondences { return actual.hasSameId(expected) && Math.abs(actual.getScore() - expected.getScore()) <= 10; } - private static String formatRecordDiff(Record actual, Record expected) { + private static @Nullable String formatRecordDiff(MyRecord actual, MyRecord expected) { if (actual.hasId() && expected.hasId() && actual.getId() == expected.getId()) { return "score:" + (actual.getScore() - expected.getScore()); } else { @@ -313,47 +257,33 @@ final class TestCorrespondences { } /** - * A key function for {@link Record} instances that keys records by their {@code id} values. The + * A key function for {@link MyRecord} instances that keys records by their {@code id} values. The * key is null if the record has no {@code id}. Does not support null records. */ - static final Function<Record, Integer> RECORD_ID = - new Function<Record, Integer>() { - - @Override - public @Nullable Integer apply(Record record) { - return record.hasId() ? record.getId() : null; - } - }; + static final Function<MyRecord, Integer> RECORD_ID = + record -> record.hasId() ? record.getId() : null; /** - * A key function for {@link Record} instances that keys records by their {@code id} values. The + * A key function for {@link MyRecord} instances that keys records by their {@code id} values. The * key is null if the record has no {@code id}. Does not support null records. */ - static final Function<Record, Integer> NULL_SAFE_RECORD_ID = - new Function<Record, Integer>() { - - @Override - public @Nullable Integer apply(Record record) { - if (record == null) { - return 0; - } - return record.hasId() ? record.getId() : null; + static final Function<MyRecord, Integer> NULL_SAFE_RECORD_ID = + record -> { + if (record == null) { + return 0; } + return record.hasId() ? record.getId() : null; }; /** - * A key function for {@link String} instances that attempts to parse them as {@link Record} + * A key function for {@link String} instances that attempts to parse them as {@link MyRecord} * instances and keys records by their {@code id} values. The key is null if the string does not * parse or the record has no {@code id}. Does not support null strings. */ static final Function<String, Integer> PARSED_RECORD_ID = - new Function<String, Integer>() { - - @Override - public @Nullable Integer apply(String str) { - Record record = Record.parse(str); - return record != null ? RECORD_ID.apply(record) : null; - } + str -> { + MyRecord record = MyRecord.parse(str); + return record != null ? RECORD_ID.apply(record) : null; }; private TestCorrespondences() {} diff --git a/core/src/test/java/com/google/common/truth/TruthAssertThatTest.java b/core/src/test/java/com/google/common/truth/TruthAssertThatTest.java index 90769484..cb75b934 100644 --- a/core/src/test/java/com/google/common/truth/TruthAssertThatTest.java +++ b/core/src/test/java/com/google/common/truth/TruthAssertThatTest.java @@ -18,10 +18,8 @@ package com.google.common.truth; import static com.google.common.truth.Truth.assert_; import static java.util.Arrays.asList; -import com.google.common.base.Function; -import com.google.common.base.Predicate; import com.google.common.collect.FluentIterable; -import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import com.google.common.collect.Ordering; import com.google.common.reflect.TypeToken; @@ -38,38 +36,23 @@ import org.junit.runners.JUnit4; */ @RunWith(JUnit4.class) public class TruthAssertThatTest { - private static final Function<Method, TypeToken<?>> METHOD_TO_RETURN_TYPE_TOKEN = - new Function<Method, TypeToken<?>>() { - @Override - public TypeToken<?> apply(Method input) { - return TypeToken.of(Iterables.getOnlyElement(asList(input.getParameterTypes()))); - } - }; + private static TypeToken<?> methodToReturnTypeToken(Method input) { + return TypeToken.of(Iterables.getOnlyElement(asList(input.getParameterTypes()))); + } @Test public void staticAssertThatMethodsMatchStandardSubjectBuilderInstanceMethods() { - ImmutableSet<TypeToken<?>> verbTypes = + ImmutableSortedSet<TypeToken<?>> verbTypes = FluentIterable.from(asList(StandardSubjectBuilder.class.getMethods())) - .filter( - new Predicate<Method>() { - @Override - public boolean apply(Method input) { - return input.getName().equals("that"); - } - }) - .transform(METHOD_TO_RETURN_TYPE_TOKEN) + .filter(input -> input.getName().equals("that")) + .transform(TruthAssertThatTest::methodToReturnTypeToken) .toSortedSet(Ordering.usingToString()); - ImmutableSet<TypeToken<?>> truthTypes = + ImmutableSortedSet<TypeToken<?>> truthTypes = FluentIterable.from(asList(Truth.class.getMethods())) .filter( - new Predicate<Method>() { - @Override - public boolean apply(Method input) { - return input.getName().equals("assertThat") - && Modifier.isStatic(input.getModifiers()); - } - }) - .transform(METHOD_TO_RETURN_TYPE_TOKEN) + input -> + input.getName().equals("assertThat") && Modifier.isStatic(input.getModifiers())) + .transform(TruthAssertThatTest::methodToReturnTypeToken) .toSortedSet(Ordering.usingToString()); assert_().that(verbTypes).isNotEmpty(); diff --git a/core/src/test/java/com/google/common/truth/TruthFailureSubjectTest.java b/core/src/test/java/com/google/common/truth/TruthFailureSubjectTest.java index cb9d7976..d4546cdc 100644 --- a/core/src/test/java/com/google/common/truth/TruthFailureSubjectTest.java +++ b/core/src/test/java/com/google/common/truth/TruthFailureSubjectTest.java @@ -157,13 +157,13 @@ public class TruthFailureSubjectTest extends BaseSubjectTestCase { @Test public void nonTruthErrorFactKeys() { Object unused = expectFailureWhenTestingThat(new AssertionError()).factKeys(); - assertFailureKeys("expected a failure thrown by Truth's new failure API", "but was"); + assertFailureKeys("expected a failure thrown by Truth's failure API", "but was"); } @Test public void nonTruthErrorFactValue() { Object unused = expectFailureWhenTestingThat(new AssertionError()).factValue("foo"); - assertFailureKeys("expected a failure thrown by Truth's new failure API", "but was"); + assertFailureKeys("expected a failure thrown by Truth's failure API", "but was"); } private TruthFailureSubject assertThat(Fact... facts) { diff --git a/core/src/test/java/com/google/common/truth/extension/EmployeeSubject.java b/core/src/test/java/com/google/common/truth/extension/EmployeeSubject.java index 854afb06..224bf653 100644 --- a/core/src/test/java/com/google/common/truth/extension/EmployeeSubject.java +++ b/core/src/test/java/com/google/common/truth/extension/EmployeeSubject.java @@ -18,7 +18,10 @@ package com.google.common.truth.extension; import static com.google.common.truth.Fact.simpleFact; import static com.google.common.truth.Truth.assertAbout; +import com.google.common.truth.ComparableSubject; import com.google.common.truth.FailureMetadata; +import com.google.common.truth.LongSubject; +import com.google.common.truth.StringSubject; import com.google.common.truth.Subject; import org.checkerframework.checker.nullness.qual.Nullable; @@ -31,18 +34,14 @@ public final class EmployeeSubject extends Subject { // User-defined entry point public static EmployeeSubject assertThat(@Nullable Employee employee) { - return assertAbout(EMPLOYEE_SUBJECT_FACTORY).that(employee); + return assertAbout(employees()).that(employee); } // Static method for getting the subject factory (for use with assertAbout()) public static Subject.Factory<EmployeeSubject, Employee> employees() { - return EMPLOYEE_SUBJECT_FACTORY; + return EmployeeSubject::new; } - // Boiler-plate Subject.Factory for EmployeeSubject - private static final Subject.Factory<EmployeeSubject, Employee> EMPLOYEE_SUBJECT_FACTORY = - EmployeeSubject::new; - private final Employee actual; private EmployeeSubject(FailureMetadata failureMetadata, @Nullable Employee subject) { @@ -53,19 +52,19 @@ public final class EmployeeSubject extends Subject { // User-defined test assertion SPI below this point public void hasName(String name) { - check("name()").that(actual.name()).isEqualTo(name); + name().isEqualTo(name); } public void hasUsername(String username) { - check("username()").that(actual.username()).isEqualTo(username); + username().isEqualTo(username); } public void hasId(long id) { - check("id()").that(actual.id()).isEqualTo(id); + id().isEqualTo(id); } public void hasLocation(Employee.Location location) { - check("location()").that(actual.location()).isEqualTo(location); + location().isEqualTo(location); } public void isCeo() { @@ -80,7 +79,21 @@ public final class EmployeeSubject extends Subject { } } - // TODO(kak): Add methods that return other subjects. E.g., - // public StringSubject username() {} - // public IterableSubject languages() {} + // Chained subjects methods below this point + + public StringSubject name() { + return check("name()").that(actual.name()); + } + + public StringSubject username() { + return check("username()").that(actual.username()); + } + + public LongSubject id() { + return check("id()").that(actual.id()); + } + + public ComparableSubject<Employee.Location> location() { + return check("location()").that(actual.location()); + } } diff --git a/core/src/test/java/com/google/common/truth/extension/EmployeeSubjectTest.java b/core/src/test/java/com/google/common/truth/extension/EmployeeSubjectTest.java index 8b60963d..36e916cf 100644 --- a/core/src/test/java/com/google/common/truth/extension/EmployeeSubjectTest.java +++ b/core/src/test/java/com/google/common/truth/extension/EmployeeSubjectTest.java @@ -17,12 +17,12 @@ package com.google.common.truth.extension; import static com.google.common.truth.ExpectFailure.assertThat; import static com.google.common.truth.ExpectFailure.expectFailureAbout; -import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.extension.EmployeeSubject.assertThat; import static com.google.common.truth.extension.EmployeeSubject.employees; import com.google.common.truth.ExpectFailure.SimpleSubjectBuilderCallback; import com.google.common.truth.extension.Employee.Location; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -57,6 +57,7 @@ public final class EmployeeSubjectTest { assertThat(failure).factValue("value of").isEqualTo("employee.username()"); } + @CanIgnoreReturnValue private static AssertionError expectFailure( SimpleSubjectBuilderCallback<EmployeeSubject, Employee> callback) { return expectFailureAbout(employees(), callback); diff --git a/core/src/test/java/com/google/common/truth/gwt/Inventory.java b/core/src/test/java/com/google/common/truth/gwt/Inventory.java index 49006f98..63e2fbcc 100644 --- a/core/src/test/java/com/google/common/truth/gwt/Inventory.java +++ b/core/src/test/java/com/google/common/truth/gwt/Inventory.java @@ -53,7 +53,7 @@ public class Inventory { BigDecimalSubject bigDecimalSubject; BooleanSubject booleanSubject; ClassSubject classSubject; - ComparableSubject comparableSubject; + ComparableSubject<?> comparableSubject; DoubleSubject doubleSubject; FailureStrategy failureStrategy; FloatSubject floatSubject; @@ -64,7 +64,7 @@ public class Inventory { MapSubject mapSubject; MultimapSubject multimapSubject; MultisetSubject multisetSubject; - ObjectArraySubject objectArraySubject; + ObjectArraySubject<?> objectArraySubject; Ordered ordered; PrimitiveBooleanArraySubject primitiveBooleanArraySubject; PrimitiveByteArraySubject primitiveByteArraySubject; @@ -75,7 +75,7 @@ public class Inventory { PrimitiveLongArraySubject primitiveLongArraySubject; PrimitiveShortArraySubject primitiveShortArraySubject; StringSubject stringSubject; - Subject.Factory subjectFactory; + Subject.Factory<?, ?> subjectFactory; Subject subject; TableSubject tableSubject; ThrowableSubject throwableSubject; diff --git a/core/src/test/java/com/google/common/truth/gwt/TruthGwtTest.java b/core/src/test/java/com/google/common/truth/gwt/TruthGwtTest.java index 4a10e414..55d4aa91 100644 --- a/core/src/test/java/com/google/common/truth/gwt/TruthGwtTest.java +++ b/core/src/test/java/com/google/common/truth/gwt/TruthGwtTest.java @@ -38,7 +38,7 @@ public class TruthGwtTest extends com.google.gwt.junit.client.GWTTestCase { } public void testBuildClasses() { - new Inventory().toString(); // force invocation. + String unused = new Inventory().toString(); // force invocation. } public void testBoolean() { @@ -133,10 +133,11 @@ public class TruthGwtTest extends com.google.gwt.junit.client.GWTTestCase { } public void testObjectArray() { - Set[] setOfString = {new HashSet<String>(asList("foo", "bar", "bash"))}; + Set<?>[] setOfString = {new HashSet<String>(asList("foo", "bar", "bash"))}; assertThat(setOfString).asList().contains(new HashSet<String>(asList("foo", "bar", "bash"))); } + @SuppressWarnings("IsInstanceIterable") // test of an intentionally trivially true assertion public void testDefault() { assertThat(new Object()).isNotNull(); assertThat(new ArrayList<String>()).isInstanceOf(AbstractList.class); diff --git a/extensions/java8/pom.xml b/extensions/java8/pom.xml index f3dfec9f..951dc43a 100644 --- a/extensions/java8/pom.xml +++ b/extensions/java8/pom.xml @@ -25,16 +25,21 @@ </dependency> </dependencies> <build> + <resources> + <resource> + <directory>../..</directory> + <includes> + <include>LICENSE</include> + </includes> + <targetPath>META-INF</targetPath> + </resource> + </resources> <plugins> <plugin> <artifactId>maven-javadoc-plugin</artifactId> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> - <configuration> - <source>1.8</source> - <target>1.8</target> - </configuration> </plugin> <plugin> <artifactId>maven-jar-plugin</artifactId> diff --git a/extensions/java8/src/main/java/com/google/common/truth/IntStreamSubject.java b/extensions/java8/src/main/java/com/google/common/truth/IntStreamSubject.java index cd898d07..45699bce 100644 --- a/extensions/java8/src/main/java/com/google/common/truth/IntStreamSubject.java +++ b/extensions/java8/src/main/java/com/google/common/truth/IntStreamSubject.java @@ -40,6 +40,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; * * @author Kurt Alfred Kluever */ +@SuppressWarnings("deprecation") // TODO(b/134064106): design an alternative to no-arg check() public final class IntStreamSubject extends Subject { private final List<?> actualList; @@ -145,7 +146,7 @@ public final class IntStreamSubject extends Subject { */ @CanIgnoreReturnValue public Ordered containsExactly(int... varargs) { - return check().that(actualList).containsExactly(box(varargs)); + return check().that(actualList).containsExactlyElementsIn(box(varargs)); } /** diff --git a/extensions/java8/src/main/java/com/google/common/truth/LongStreamSubject.java b/extensions/java8/src/main/java/com/google/common/truth/LongStreamSubject.java index 1e6b5029..0a5c2255 100644 --- a/extensions/java8/src/main/java/com/google/common/truth/LongStreamSubject.java +++ b/extensions/java8/src/main/java/com/google/common/truth/LongStreamSubject.java @@ -40,6 +40,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; * * @author Kurt Alfred Kluever */ +@SuppressWarnings("deprecation") // TODO(b/134064106): design an alternative to no-arg check() public final class LongStreamSubject extends Subject { private final List<?> actualList; @@ -145,7 +146,7 @@ public final class LongStreamSubject extends Subject { */ @CanIgnoreReturnValue public Ordered containsExactly(long... varargs) { - return check().that(actualList).containsExactly(box(varargs)); + return check().that(actualList).containsExactlyElementsIn(box(varargs)); } /** diff --git a/extensions/java8/src/main/java/com/google/common/truth/OptionalSubject.java b/extensions/java8/src/main/java/com/google/common/truth/OptionalSubject.java index 1b9fb5c9..65ca4dae 100644 --- a/extensions/java8/src/main/java/com/google/common/truth/OptionalSubject.java +++ b/extensions/java8/src/main/java/com/google/common/truth/OptionalSubject.java @@ -27,7 +27,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; * @author Christian Gruber */ public final class OptionalSubject extends Subject { - private final Optional<?> actual; + private final @Nullable Optional<?> actual; OptionalSubject( FailureMetadata failureMetadata, @@ -68,7 +68,7 @@ public final class OptionalSubject extends Subject { * assertThat(myOptional.get()).contains("foo"); * }</pre> */ - public void hasValue(Object expected) { + public void hasValue(@Nullable Object expected) { if (expected == null) { throw new NullPointerException("Optional cannot have a null value."); } diff --git a/extensions/java8/src/main/java/com/google/common/truth/StreamSubject.java b/extensions/java8/src/main/java/com/google/common/truth/StreamSubject.java index 4957686d..f3e90cd6 100644 --- a/extensions/java8/src/main/java/com/google/common/truth/StreamSubject.java +++ b/extensions/java8/src/main/java/com/google/common/truth/StreamSubject.java @@ -18,6 +18,7 @@ package com.google.common.truth; import static java.util.stream.Collectors.toCollection; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.DoNotCall; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -39,6 +40,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; * * @author Kurt Alfred Kluever */ +@SuppressWarnings("deprecation") // TODO(b/134064106): design an alternative to no-arg check() public final class StreamSubject extends Subject { private final List<?> actualList; @@ -54,7 +56,7 @@ public final class StreamSubject extends Subject { } public static Subject.Factory<StreamSubject, Stream<?>> streams() { - return (metadata, subject) -> new StreamSubject(metadata, subject); + return StreamSubject::new; } /** Fails if the subject is not empty. */ @@ -144,6 +146,11 @@ public final class StreamSubject extends Subject { * on the object returned by this method. */ @CanIgnoreReturnValue + /* + * We need to call containsExactly, not containsExactlyElementsIn, to get the handling we want for + * containsExactly(null). + */ + @SuppressWarnings("ContainsExactlyVariadic") public Ordered containsExactly(@Nullable Object @Nullable ... varargs) { return check().that(actualList).containsExactly(varargs); } @@ -223,7 +230,35 @@ public final class StreamSubject extends Subject { check().that(actualList).isInOrder(comparator); } - // TODO(user): Do we want to override + deprecate isEqualTo/isNotEqualTo? + /** + * @deprecated {@code streamA.isEqualTo(streamB)} always fails, except when passed the exact same + * stream reference + */ + @Override + @DoNotCall( + "StreamSubject.isEqualTo() is not supported because Streams do not have well-defined" + + " equality semantics") + @Deprecated + public void isEqualTo(@Nullable Object expected) { + throw new UnsupportedOperationException( + "StreamSubject.isEqualTo() is not supported because Streams do not have well-defined" + + " equality semantics"); + } + + /** + * @deprecated {@code streamA.isNotEqualTo(streamB)} always passes, except when passed the exact + * same stream reference + */ + @Override + @DoNotCall( + "StreamSubject.isNotEqualTo() is not supported because Streams do not have well-defined" + + " equality semantics") + @Deprecated + public void isNotEqualTo(@Nullable Object unexpected) { + throw new UnsupportedOperationException( + "StreamSubject.isNotEqualTo() is not supported because Streams do not have well-defined" + + " equality semantics"); + } // TODO(user): Do we want to support comparingElementsUsing() on StreamSubject? } diff --git a/extensions/java8/src/main/java/com/google/common/truth/Truth8.gwt.xml b/extensions/java8/src/main/java/com/google/common/truth/Truth8.gwt.xml index a3ac1443..f9aa5adf 100644 --- a/extensions/java8/src/main/java/com/google/common/truth/Truth8.gwt.xml +++ b/extensions/java8/src/main/java/com/google/common/truth/Truth8.gwt.xml @@ -5,19 +5,26 @@ </source> <!-- - We used to set this only for packages that had manual supersource. - That worked everywhere that I know of except for one place: - when running the GWT util.concurrent tests under Guava. - The problem is that GWT responds poorly to two .gwt.xml files in the same Java package: - https://goo.gl/pRV3Yn - The summary is that it ignores one file in favor of the other. - util.concurrent, like nearly all our packages, has two .gwt.xml files: one for prod and one for tests. - util.concurrent, unlike our other packages, has, as of this writing, test supersource but no prod supersource. - GWT happens to use the prod .gwt.xml, so it looks for no supersource for tests, either. - This causes it to fail to find AtomicLongMapTest. - Our workaround is to tell GWT that util.concurrent and all other packages have prod supersource, even if they have none. - GWT is happy to ignore us when we specify a nonexistent path. - (I hope that this workaround does not cause its own problems in the future.) + We used to set this only for packages that had manual supersource. That + worked everywhere that I know of except for one place: when running the GWT + util.concurrent tests under Guava. + + The problem is that GWT responds poorly to two .gwt.xml files in the same + Java package; see https://goo.gl/pRV3Yn for details. + + The summary is that it ignores one file in favor of the other. + util.concurrent, like nearly all our packages, has two .gwt.xml files: one + for prod and one for tests. However, unlike our other packages, as of this + writing it has test supersource but no prod supersource. + + GWT happens to use the prod .gwt.xml, so it looks for no supersource for + tests, either. This causes it to fail to find AtomicLongMapTest. + + Our workaround is to tell GWT that util.concurrent and all other packages + have prod supersource, even if they have none. GWT is happy to ignore us + when we specify a nonexistent path. + + (I hope that this workaround does not cause its own problems in the future.) --> <super-source path="super"/> diff --git a/extensions/java8/src/main/java/com/google/common/truth/Truth8.java b/extensions/java8/src/main/java/com/google/common/truth/Truth8.java index 31f0c6d9..c156c54b 100644 --- a/extensions/java8/src/main/java/com/google/common/truth/Truth8.java +++ b/extensions/java8/src/main/java/com/google/common/truth/Truth8.java @@ -44,6 +44,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; * behavior/{@code Subject} type?</a> in the Truth FAQ. */ public final class Truth8 { + @SuppressWarnings("AssertAboutOptionals") // suggests infinite recursion public static OptionalSubject assertThat(@Nullable Optional<?> target) { return assertAbout(OptionalSubject.optionals()).that(target); } diff --git a/extensions/java8/src/test/java/com/google/common/truth/IntStreamSubjectTest.java b/extensions/java8/src/test/java/com/google/common/truth/IntStreamSubjectTest.java index bcb11bd9..9fc8ee41 100644 --- a/extensions/java8/src/test/java/com/google/common/truth/IntStreamSubjectTest.java +++ b/extensions/java8/src/test/java/com/google/common/truth/IntStreamSubjectTest.java @@ -202,17 +202,16 @@ public final class IntStreamSubjectTest { @Test public void testContainsAtLeast_inOrder_fails() throws Exception { - try { - assertThat(IntStream.of(42, 43)).containsAtLeast(43, 42).inOrder(); - fail(); - } catch (AssertionError expected) { - assertFailureKeys( - expected, - "required elements were all found, but order was wrong", - "expected order for required elements", - "but was"); - assertFailureValue(expected, "expected order for required elements", "[43, 42]"); - } + AssertionError expected = + expectFailure( + whenTesting -> + whenTesting.that(IntStream.of(42, 43)).containsAtLeast(43, 42).inOrder()); + assertFailureKeys( + expected, + "required elements were all found, but order was wrong", + "expected order for required elements", + "but was"); + assertFailureValue(expected, "expected order for required elements", "[43, 42]"); } @Test @@ -237,17 +236,19 @@ public final class IntStreamSubjectTest { @Test public void testContainsAtLeastElementsIn_inOrder_fails() throws Exception { - try { - assertThat(IntStream.of(42, 43)).containsAtLeastElementsIn(asList(43, 42)).inOrder(); - fail(); - } catch (AssertionError expected) { - assertFailureKeys( - expected, - "required elements were all found, but order was wrong", - "expected order for required elements", - "but was"); - assertFailureValue(expected, "expected order for required elements", "[43, 42]"); - } + AssertionError expected = + expectFailure( + whenTesting -> + whenTesting + .that(IntStream.of(42, 43)) + .containsAtLeastElementsIn(asList(43, 42)) + .inOrder()); + assertFailureKeys( + expected, + "required elements were all found, but order was wrong", + "expected order for required elements", + "but was"); + assertFailureValue(expected, "expected order for required elements", "[43, 42]"); } @Test @@ -257,13 +258,10 @@ public final class IntStreamSubjectTest { @Test public void testContainsExactly_fails() throws Exception { - try { - assertThat(IntStream.of(42, 43)).containsExactly(42); - fail(); - } catch (AssertionError expected) { - assertFailureKeys(expected, "unexpected (1)", "---", "expected", "but was"); - assertFailureValue(expected, "expected", "[42]"); - } + AssertionError expected = + expectFailure(whenTesting -> whenTesting.that(IntStream.of(42, 43)).containsExactly(42)); + assertFailureKeys(expected, "unexpected (1)", "---", "expected", "but was"); + assertFailureValue(expected, "expected", "[42]"); } @Test @@ -273,13 +271,12 @@ public final class IntStreamSubjectTest { @Test public void testContainsExactly_inOrder_fails() throws Exception { - try { - assertThat(IntStream.of(42, 43)).containsExactly(43, 42).inOrder(); - fail(); - } catch (AssertionError expected) { - assertFailureKeys(expected, "contents match, but order was wrong", "expected", "but was"); - assertFailureValue(expected, "expected", "[43, 42]"); - } + AssertionError expected = + expectFailure( + whenTesting -> + whenTesting.that(IntStream.of(42, 43)).containsExactly(43, 42).inOrder()); + assertFailureKeys(expected, "contents match, but order was wrong", "expected", "but was"); + assertFailureValue(expected, "expected", "[43, 42]"); } @Test @@ -290,13 +287,12 @@ public final class IntStreamSubjectTest { @Test public void testContainsExactlyElementsIn_fails() throws Exception { - try { - assertThat(IntStream.of(42, 43)).containsExactlyElementsIn(asList(42)); - fail(); - } catch (AssertionError expected) { - assertFailureKeys(expected, "unexpected (1)", "---", "expected", "but was"); - assertFailureValue(expected, "expected", "[42]"); - } + AssertionError expected = + expectFailure( + whenTesting -> + whenTesting.that(IntStream.of(42, 43)).containsExactlyElementsIn(asList(42))); + assertFailureKeys(expected, "unexpected (1)", "---", "expected", "but was"); + assertFailureValue(expected, "expected", "[42]"); } @Test @@ -306,13 +302,15 @@ public final class IntStreamSubjectTest { @Test public void testContainsExactlyElementsIn_inOrder_fails() throws Exception { - try { - assertThat(IntStream.of(42, 43)).containsExactlyElementsIn(asList(43, 42)).inOrder(); - fail(); - } catch (AssertionError expected) { - assertFailureKeys(expected, "contents match, but order was wrong", "expected", "but was"); - assertFailureValue(expected, "expected", "[43, 42]"); - } + AssertionError expected = + expectFailure( + whenTesting -> + whenTesting + .that(IntStream.of(42, 43)) + .containsExactlyElementsIn(asList(43, 42)) + .inOrder()); + assertFailureKeys(expected, "contents match, but order was wrong", "expected", "but was"); + assertFailureValue(expected, "expected", "[43, 42]"); } @Test diff --git a/extensions/java8/src/test/java/com/google/common/truth/LongStreamSubjectTest.java b/extensions/java8/src/test/java/com/google/common/truth/LongStreamSubjectTest.java index 9908b354..52c36ea0 100644 --- a/extensions/java8/src/test/java/com/google/common/truth/LongStreamSubjectTest.java +++ b/extensions/java8/src/test/java/com/google/common/truth/LongStreamSubjectTest.java @@ -203,17 +203,16 @@ public final class LongStreamSubjectTest { @Test public void testContainsAtLeast_inOrder_fails() throws Exception { - try { - assertThat(LongStream.of(42, 43)).containsAtLeast(43, 42).inOrder(); - fail(); - } catch (AssertionError expected) { - assertFailureKeys( - expected, - "required elements were all found, but order was wrong", - "expected order for required elements", - "but was"); - assertFailureValue(expected, "expected order for required elements", "[43, 42]"); - } + AssertionError expected = + expectFailure( + whenTesting -> + whenTesting.that(LongStream.of(42, 43)).containsAtLeast(43, 42).inOrder()); + assertFailureKeys( + expected, + "required elements were all found, but order was wrong", + "expected order for required elements", + "but was"); + assertFailureValue(expected, "expected order for required elements", "[43, 42]"); } @Test @@ -248,17 +247,19 @@ public final class LongStreamSubjectTest { @Test public void testContainsAtLeastElementsIn_inOrder_fails() throws Exception { - try { - assertThat(LongStream.of(42, 43)).containsAtLeastElementsIn(asList(43L, 42L)).inOrder(); - fail(); - } catch (AssertionError expected) { - assertFailureKeys( - expected, - "required elements were all found, but order was wrong", - "expected order for required elements", - "but was"); - assertFailureValue(expected, "expected order for required elements", "[43, 42]"); - } + AssertionError expected = + expectFailure( + whenTesting -> + whenTesting + .that(LongStream.of(42, 43)) + .containsAtLeastElementsIn(asList(43L, 42L)) + .inOrder()); + assertFailureKeys( + expected, + "required elements were all found, but order was wrong", + "expected order for required elements", + "but was"); + assertFailureValue(expected, "expected order for required elements", "[43, 42]"); } @Test @@ -279,13 +280,10 @@ public final class LongStreamSubjectTest { @Test public void testContainsExactly_fails() throws Exception { - try { - assertThat(LongStream.of(42, 43)).containsExactly(42); - fail(); - } catch (AssertionError expected) { - assertFailureKeys(expected, "unexpected (1)", "---", "expected", "but was"); - assertFailureValue(expected, "expected", "[42]"); - } + AssertionError expected = + expectFailure(whenTesting -> whenTesting.that(LongStream.of(42, 43)).containsExactly(42)); + assertFailureKeys(expected, "unexpected (1)", "---", "expected", "but was"); + assertFailureValue(expected, "expected", "[42]"); } @Test @@ -295,13 +293,12 @@ public final class LongStreamSubjectTest { @Test public void testContainsExactly_inOrder_fails() throws Exception { - try { - assertThat(LongStream.of(42, 43)).containsExactly(43, 42).inOrder(); - fail(); - } catch (AssertionError expected) { - assertFailureKeys(expected, "contents match, but order was wrong", "expected", "but was"); - assertFailureValue(expected, "expected", "[43, 42]"); - } + AssertionError expected = + expectFailure( + whenTesting -> + whenTesting.that(LongStream.of(42, 43)).containsExactly(43, 42).inOrder()); + assertFailureKeys(expected, "contents match, but order was wrong", "expected", "but was"); + assertFailureValue(expected, "expected", "[43, 42]"); } @Test @@ -312,13 +309,12 @@ public final class LongStreamSubjectTest { @Test public void testContainsExactlyElementsIn_fails() throws Exception { - try { - assertThat(LongStream.of(42, 43)).containsExactlyElementsIn(asList(42L)); - fail(); - } catch (AssertionError expected) { - assertFailureKeys(expected, "unexpected (1)", "---", "expected", "but was"); - assertFailureValue(expected, "expected", "[42]"); - } + AssertionError expected = + expectFailure( + whenTesting -> + whenTesting.that(LongStream.of(42, 43)).containsExactlyElementsIn(asList(42L))); + assertFailureKeys(expected, "unexpected (1)", "---", "expected", "but was"); + assertFailureValue(expected, "expected", "[42]"); } @Test @@ -336,13 +332,15 @@ public final class LongStreamSubjectTest { @Test public void testContainsExactlyElementsIn_inOrder_fails() throws Exception { - try { - assertThat(LongStream.of(42, 43)).containsExactlyElementsIn(asList(43L, 42L)).inOrder(); - fail(); - } catch (AssertionError expected) { - assertFailureKeys(expected, "contents match, but order was wrong", "expected", "but was"); - assertFailureValue(expected, "expected", "[43, 42]"); - } + AssertionError expected = + expectFailure( + whenTesting -> + whenTesting + .that(LongStream.of(42, 43)) + .containsExactlyElementsIn(asList(43L, 42L)) + .inOrder()); + assertFailureKeys(expected, "contents match, but order was wrong", "expected", "but was"); + assertFailureValue(expected, "expected", "[43, 42]"); } @Test diff --git a/extensions/java8/src/test/java/com/google/common/truth/StreamSubjectTest.java b/extensions/java8/src/test/java/com/google/common/truth/StreamSubjectTest.java index b685e71b..3efc4866 100644 --- a/extensions/java8/src/test/java/com/google/common/truth/StreamSubjectTest.java +++ b/extensions/java8/src/test/java/com/google/common/truth/StreamSubjectTest.java @@ -20,9 +20,9 @@ import static com.google.common.truth.FailureAssertions.assertFailureValue; import static com.google.common.truth.StreamSubject.streams; import static com.google.common.truth.Truth8.assertThat; import static java.util.Arrays.asList; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; -import java.util.List; import java.util.stream.Stream; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,17 +36,19 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public final class StreamSubjectTest { + @SuppressWarnings({"DoNotCall", "deprecation"}) // test of a mistaken call @Test public void testIsEqualTo() throws Exception { Stream<String> stream = Stream.of("hello"); - assertThat(stream).isEqualTo(stream); + assertThrows(UnsupportedOperationException.class, () -> assertThat(stream).isEqualTo(stream)); } + @SuppressWarnings({"DoNotCall", "deprecation"}) // test of a mistaken call @Test - public void testIsEqualToList() throws Exception { + public void testIsNotEqualTo() throws Exception { Stream<String> stream = Stream.of("hello"); - List<String> list = asList("hello"); - AssertionError unused = expectFailure(whenTesting -> whenTesting.that(stream).isEqualTo(list)); + assertThrows( + UnsupportedOperationException.class, () -> assertThat(stream).isNotEqualTo(stream)); } @Test @@ -210,17 +212,19 @@ public final class StreamSubjectTest { @Test public void testContainsAtLeast_inOrder_fails() throws Exception { - try { - assertThat(Stream.of("hell", "hello")).containsAtLeast("hello", "hell").inOrder(); - fail(); - } catch (AssertionError expected) { - assertFailureKeys( - expected, - "required elements were all found, but order was wrong", - "expected order for required elements", - "but was"); - assertFailureValue(expected, "expected order for required elements", "[hello, hell]"); - } + AssertionError expected = + expectFailure( + whenTesting -> + whenTesting + .that(Stream.of("hell", "hello")) + .containsAtLeast("hello", "hell") + .inOrder()); + assertFailureKeys( + expected, + "required elements were all found, but order was wrong", + "expected order for required elements", + "but was"); + assertFailureValue(expected, "expected order for required elements", "[hello, hell]"); } @Test @@ -247,19 +251,19 @@ public final class StreamSubjectTest { @Test public void testContainsAtLeastElementsIn_inOrder_fails() throws Exception { - try { - assertThat(Stream.of("hell", "hello")) - .containsAtLeastElementsIn(asList("hello", "hell")) - .inOrder(); - fail(); - } catch (AssertionError expected) { - assertFailureKeys( - expected, - "required elements were all found, but order was wrong", - "expected order for required elements", - "but was"); - assertFailureValue(expected, "expected order for required elements", "[hello, hell]"); - } + AssertionError expected = + expectFailure( + whenTesting -> + whenTesting + .that(Stream.of("hell", "hello")) + .containsAtLeastElementsIn(asList("hello", "hell")) + .inOrder()); + assertFailureKeys( + expected, + "required elements were all found, but order was wrong", + "expected order for required elements", + "but was"); + assertFailureValue(expected, "expected order for required elements", "[hello, hell]"); } @Test @@ -269,14 +273,18 @@ public final class StreamSubjectTest { } @Test + public void testContainsExactly_null() throws Exception { + assertThat(Stream.of((Object) null)).containsExactly((Object) null); + assertThat(Stream.of((Object) null)).containsExactly((Object[]) null); + } + + @Test public void testContainsExactly_fails() throws Exception { - try { - assertThat(Stream.of("hell", "hello")).containsExactly("hell"); - fail(); - } catch (AssertionError expected) { - assertFailureKeys(expected, "unexpected (1)", "---", "expected", "but was"); - assertFailureValue(expected, "expected", "[hell]"); - } + AssertionError expected = + expectFailure( + whenTesting -> whenTesting.that(Stream.of("hell", "hello")).containsExactly("hell")); + assertFailureKeys(expected, "unexpected (1)", "---", "expected", "but was"); + assertFailureValue(expected, "expected", "[hell]"); } @Test @@ -286,13 +294,15 @@ public final class StreamSubjectTest { @Test public void testContainsExactly_inOrder_fails() throws Exception { - try { - assertThat(Stream.of("hell", "hello")).containsExactly("hello", "hell").inOrder(); - fail(); - } catch (AssertionError expected) { - assertFailureKeys(expected, "contents match, but order was wrong", "expected", "but was"); - assertFailureValue(expected, "expected", "[hello, hell]"); - } + AssertionError expected = + expectFailure( + whenTesting -> + whenTesting + .that(Stream.of("hell", "hello")) + .containsExactly("hello", "hell") + .inOrder()); + assertFailureKeys(expected, "contents match, but order was wrong", "expected", "but was"); + assertFailureValue(expected, "expected", "[hello, hell]"); } @Test @@ -303,13 +313,14 @@ public final class StreamSubjectTest { @Test public void testContainsExactlyElementsIn_fails() throws Exception { - try { - assertThat(Stream.of("hell", "hello")).containsExactlyElementsIn(asList("hell")); - fail(); - } catch (AssertionError expected) { - assertFailureKeys(expected, "unexpected (1)", "---", "expected", "but was"); - assertFailureValue(expected, "expected", "[hell]"); - } + AssertionError expected = + expectFailure( + whenTesting -> + whenTesting + .that(Stream.of("hell", "hello")) + .containsExactlyElementsIn(asList("hell"))); + assertFailureKeys(expected, "unexpected (1)", "---", "expected", "but was"); + assertFailureValue(expected, "expected", "[hell]"); } @Test @@ -321,15 +332,15 @@ public final class StreamSubjectTest { @Test public void testContainsExactlyElementsIn_inOrder_fails() throws Exception { - try { - assertThat(Stream.of("hell", "hello")) - .containsExactlyElementsIn(asList("hello", "hell")) - .inOrder(); - fail(); - } catch (AssertionError expected) { - assertFailureKeys(expected, "contents match, but order was wrong", "expected", "but was"); - assertFailureValue(expected, "expected", "[hello, hell]"); - } + AssertionError expected = + expectFailure( + whenTesting -> + whenTesting + .that(Stream.of("hell", "hello")) + .containsExactlyElementsIn(asList("hello", "hell")) + .inOrder()); + assertFailureKeys(expected, "contents match, but order was wrong", "expected", "but was"); + assertFailureValue(expected, "expected", "[hello, hell]"); } @Test diff --git a/extensions/liteproto/pom.xml b/extensions/liteproto/pom.xml index 82bdc46d..7474ee5a 100644 --- a/extensions/liteproto/pom.xml +++ b/extensions/liteproto/pom.xml @@ -44,6 +44,15 @@ </dependency> </dependencies> <build> + <resources> + <resource> + <directory>../..</directory> + <includes> + <include>LICENSE</include> + </includes> + <targetPath>META-INF</targetPath> + </resource> + </resources> <extensions> <extension> <groupId>kr.motd.maven</groupId> diff --git a/extensions/liteproto/src/main/java/com/google/common/truth/extensions/proto/LiteProtoSubject.java b/extensions/liteproto/src/main/java/com/google/common/truth/extensions/proto/LiteProtoSubject.java index 8bee5946..3a0774ee 100644 --- a/extensions/liteproto/src/main/java/com/google/common/truth/extensions/proto/LiteProtoSubject.java +++ b/extensions/liteproto/src/main/java/com/google/common/truth/extensions/proto/LiteProtoSubject.java @@ -139,10 +139,31 @@ public class LiteProtoSubject extends Subject { /** * @deprecated A Builder can never compare equal to a MessageLite instance. Use {@code build()}, - * or {@code buildPartial()} on the argument to get a MessageLite for comparison instead. + * or {@code buildPartial()} on the argument to get a MessageLite for comparison instead. Or, + * if you are passing {@code null}, use {@link #isNull()}. + */ + /* + * TODO(cpovirk): Consider @DoNotCall -- or probably some other static analysis, given the problem + * discussed in the rest of this comment. + * + * The problem: isEqualTo(null) resolves to this overload (since this overload is more specific + * than isEqualTo(Object)), so @DoNotCall would break all assertions of that form. + * + * To address that, we could try also adding something like `<NullT extends Impossible & + * MessageLite.Builder> void isEqualTo(NullT)` and hoping that isEqualTo(null) would resolve to + * that instead. That would also have the benefit of making isEqualTo(null) not produce a + * deprecation warning (though of course people "should" use isNull(): b/17294077). But yuck. + * + * Given the null issue, maybe we should never have added this overload in the first place, + * instead adding static analysis specific to MessageLite-MessageLite.Builder comparisons. (Sadly, + * we can't remove it now without breaking binary compatibility.) + * + * Still, we could add static analysis to produce a compile error for isEqualTo(Builder) this even + * today, even without using @DoNotCall. And then we could consider removing @Deprecated to stop + * spamming the people who call isEqualTo(null). */ @Deprecated - public void isEqualTo(MessageLite./*@Nullable*/ Builder builder) { + public void isEqualTo(MessageLite.@Nullable Builder builder) { isEqualTo((Object) builder); } @@ -169,10 +190,12 @@ public class LiteProtoSubject extends Subject { /** * @deprecated A Builder will never compare equal to a MessageLite instance. Use {@code build()}, - * or {@code buildPartial()} on the argument to get a MessageLite for comparison instead. + * or {@code buildPartial()} on the argument to get a MessageLite for comparison instead. Or, + * if you are passing {@code null}, use {@link #isNotNull()}. */ + // TODO(cpovirk): Consider @DoNotCall or other static analysis. (See isEqualTo(Builder).) @Deprecated - public void isNotEqualTo(MessageLite./*@Nullable*/ Builder builder) { + public void isNotEqualTo(MessageLite.@Nullable Builder builder) { isNotEqualTo((Object) builder); } diff --git a/extensions/liteproto/src/test/java/com/google/common/truth/extensions/proto/LiteProtoSubjectTest.java b/extensions/liteproto/src/test/java/com/google/common/truth/extensions/proto/LiteProtoSubjectTest.java index c3e0ed2d..372100e4 100644 --- a/extensions/liteproto/src/test/java/com/google/common/truth/extensions/proto/LiteProtoSubjectTest.java +++ b/extensions/liteproto/src/test/java/com/google/common/truth/extensions/proto/LiteProtoSubjectTest.java @@ -16,13 +16,15 @@ package com.google.common.truth.extensions.proto; import static com.google.common.truth.ExpectFailure.assertThat; +import static com.google.common.truth.ExpectFailure.expectFailureAbout; import static com.google.common.truth.extensions.proto.LiteProtoTruth.assertThat; -import static org.junit.Assert.fail; +import static com.google.common.truth.extensions.proto.LiteProtoTruth.liteProtos; import com.google.auto.value.AutoValue; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.truth.Expect; +import com.google.common.truth.ExpectFailure.SimpleSubjectBuilderCallback; import com.google.common.truth.Subject; import com.google.protobuf.MessageLite; import java.util.Arrays; @@ -174,34 +176,37 @@ public class LiteProtoSubjectTest { @Test public void testIsEqualTo_failure() { - try { - assertThat(config.nonEmptyMessage()).isEqualTo(config.nonEmptyMessageOfOtherValue()); - fail("Should have failed."); - } catch (AssertionError e) { - expectRegex(e, ".*expected:.*\"foo\".*"); - expectNoRegex(e, ".*but was:.*\"foo\".*"); - } - - try { - assertThat(config.nonEmptyMessage()).isEqualTo(config.nonEmptyMessageOfOtherType()); - fail("Should have failed."); - } catch (AssertionError e) { - expectRegex( - e, - "Not true that \\(.*\\) proto is equal to the expected \\(.*\\) object\\.\\s*" - + "They are not of the same class\\."); - } - - try { - assertThat(config.nonEmptyMessage()).isNotEqualTo(config.equivalentNonEmptyMessage()); - fail("Should have failed."); - } catch (AssertionError e) { - expectRegex( - e, - String.format( - "Not true that protos are different\\.\\s*Both are \\(%s\\) <.*optional_int: 3.*>\\.", - Pattern.quote(config.nonEmptyMessage().getClass().getName()))); - } + AssertionError e = + expectFailure( + whenTesting -> + whenTesting + .that(config.nonEmptyMessage()) + .isEqualTo(config.nonEmptyMessageOfOtherValue())); + expectRegex(e, ".*expected:.*\"foo\".*"); + expectNoRegex(e, ".*but was:.*\"foo\".*"); + + e = + expectFailure( + whenTesting -> + whenTesting + .that(config.nonEmptyMessage()) + .isEqualTo(config.nonEmptyMessageOfOtherType())); + expectRegex( + e, + "Not true that \\(.*\\) proto is equal to the expected \\(.*\\) object\\.\\s*" + + "They are not of the same class\\."); + + e = + expectFailure( + whenTesting -> + whenTesting + .that(config.nonEmptyMessage()) + .isNotEqualTo(config.equivalentNonEmptyMessage())); + expectRegex( + e, + String.format( + "Not true that protos are different\\.\\s*Both are \\(%s\\) <.*optional_int: 3.*>\\.", + Pattern.quote(config.nonEmptyMessage().getClass().getName()))); } @Test @@ -215,15 +220,16 @@ public class LiteProtoSubjectTest { return; } - try { - assertThat(config.messageWithoutRequiredFields().get()).hasAllRequiredFields(); - fail("Should have failed."); - } catch (AssertionError e) { - expectRegex( - e, - "expected to have all required fields set\\s*but was: .*\\(Lite runtime could not" - + " determine which fields were missing.\\)"); - } + AssertionError e = + expectFailure( + whenTesting -> + whenTesting + .that(config.messageWithoutRequiredFields().get()) + .hasAllRequiredFields()); + expectRegex( + e, + "expected to have all required fields set\\s*but was: .*\\(Lite runtime could not" + + " determine which fields were missing.\\)"); } @Test @@ -238,27 +244,24 @@ public class LiteProtoSubjectTest { @Test public void testDefaultInstance_failure() { - try { - assertThat(config.nonEmptyMessage()).isEqualToDefaultInstance(); - fail("Should have failed."); - } catch (AssertionError e) { - expectRegex( - e, - "Not true that <.*optional_int:\\s*3.*> is a default proto instance\\.\\s*" - + "It has set values\\."); - } - - try { - assertThat(config.defaultInstance()).isNotEqualToDefaultInstance(); - fail("Should have failed."); - } catch (AssertionError e) { - expectRegex( - e, - String.format( - "Not true that \\(%s\\) <.*\\[empty proto\\].*> is not a default " - + "proto instance\\.\\s*It has no set values\\.", - Pattern.quote(config.defaultInstance().getClass().getName()))); - } + AssertionError e = + expectFailure( + whenTesting -> whenTesting.that(config.nonEmptyMessage()).isEqualToDefaultInstance()); + expectRegex( + e, + "Not true that <.*optional_int:\\s*3.*> is a default proto instance\\.\\s*" + + "It has set values\\."); + + e = + expectFailure( + whenTesting -> + whenTesting.that(config.defaultInstance()).isNotEqualToDefaultInstance()); + expectRegex( + e, + String.format( + "Not true that \\(%s\\) <.*\\[empty proto\\].*> is not a default " + + "proto instance\\.\\s*It has no set values\\.", + Pattern.quote(config.defaultInstance().getClass().getName()))); } @Test @@ -272,21 +275,19 @@ public class LiteProtoSubjectTest { public void testSerializedSize_failure() { int size = config.nonEmptyMessage().getSerializedSize(); - try { - assertThat(config.nonEmptyMessage()).serializedSize().isGreaterThan(size); - fail("Should have failed."); - } catch (AssertionError e) { - assertThat(e).factValue("value of").isEqualTo("liteProto.getSerializedSize()"); - assertThat(e).factValue("liteProto was").containsMatch("optional_int:\\s*3"); - } - - try { - assertThat(config.defaultInstance()).serializedSize().isGreaterThan(0); - fail("Should have failed."); - } catch (AssertionError e) { - assertThat(e).factValue("value of").isEqualTo("liteProto.getSerializedSize()"); - assertThat(e).factValue("liteProto was").contains("[empty proto]"); - } + AssertionError e = + expectFailure( + whenTesting -> + whenTesting.that(config.nonEmptyMessage()).serializedSize().isGreaterThan(size)); + assertThat(e).factValue("value of").isEqualTo("liteProto.getSerializedSize()"); + assertThat(e).factValue("liteProto was").containsMatch("optional_int:\\s*3"); + + e = + expectFailure( + whenTesting -> + whenTesting.that(config.defaultInstance()).serializedSize().isGreaterThan(0)); + assertThat(e).factValue("value of").isEqualTo("liteProto.getSerializedSize()"); + assertThat(e).factValue("liteProto was").contains("[empty proto]"); } private void expectRegex(AssertionError e, String regex) { @@ -296,4 +297,9 @@ public class LiteProtoSubjectTest { private void expectNoRegex(AssertionError e, String regex) { expect.that(e).hasMessageThat().doesNotMatch(Pattern.compile(regex, Pattern.DOTALL)); } + + private static AssertionError expectFailure( + SimpleSubjectBuilderCallback<LiteProtoSubject, MessageLite> assertionCallback) { + return expectFailureAbout(liteProtos(), assertionCallback); + } } diff --git a/extensions/proto/pom.xml b/extensions/proto/pom.xml index b69b8e24..7c07acc5 100644 --- a/extensions/proto/pom.xml +++ b/extensions/proto/pom.xml @@ -46,6 +46,15 @@ </dependency> </dependencies> <build> + <resources> + <resource> + <directory>../..</directory> + <includes> + <include>LICENSE</include> + </includes> + <targetPath>META-INF</targetPath> + </resource> + </resources> <extensions> <extension> <groupId>kr.motd.maven</groupId> diff --git a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/DiffResult.java b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/DiffResult.java index a0ec9373..e6c0af64 100644 --- a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/DiffResult.java +++ b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/DiffResult.java @@ -24,7 +24,6 @@ import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Ordering; import com.google.common.collect.Sets; -import com.google.common.truth.extensions.proto.RecursableDiffEntity.WithResultCode.Result; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.ForOverride; import com.google.protobuf.Descriptors.FieldDescriptor; @@ -45,6 +44,7 @@ import java.util.Set; */ @AutoValue abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { + /** * Structural summary of the difference between two singular (non-repeated) fields. * @@ -54,7 +54,8 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { * to the default value, and the other did not. */ @AutoValue - abstract static class SingularField extends RecursableDiffEntity.WithResultCode { + abstract static class SingularField extends RecursableDiffEntity.WithResultCode + implements ProtoPrintable { /** The type information for this field. May be absent if result code is {@code IGNORED}. */ abstract Optional<SubScopeId> subScopeId(); @@ -108,9 +109,11 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { case ADDED: sb.append("added: ").append(fieldPrefix).append(": "); if (actual().get() instanceof Message) { - sb.append("\n").append(actual().get()); + sb.append("\n"); + printMessage((Message) actual().get(), sb); } else { - sb.append(valueString(subScopeId().get(), actual().get())).append("\n"); + printFieldValue(subScopeId().get(), actual().get(), sb); + sb.append("\n"); } return; case IGNORED: @@ -122,9 +125,9 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { sb.append("\n"); printChildContents(includeMatches, fieldPrefix, sb); } else { - sb.append(": ") - .append(valueString(subScopeId().get(), actualOrExpected())) - .append("\n"); + sb.append(": "); + printFieldValue(subScopeId().get(), actualOrExpected(), sb); + sb.append("\n"); } return; case MODIFIED: @@ -133,19 +136,21 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { sb.append("\n"); printChildContents(includeMatches, fieldPrefix, sb); } else { - sb.append(": ") - .append(valueString(subScopeId().get(), expected().get())) - .append(" -> ") - .append(valueString(subScopeId().get(), actual().get())) - .append("\n"); + sb.append(": "); + printFieldValue(subScopeId().get(), expected().get(), sb); + sb.append(" -> "); + printFieldValue(subScopeId().get(), actual().get(), sb); + sb.append("\n"); } return; case REMOVED: sb.append("deleted: ").append(fieldPrefix).append(": "); if (expected().get() instanceof Message) { - sb.append("\n").append(expected().get()); + sb.append("\n"); + printMessage((Message) expected().get(), sb); } else { - sb.append(valueString(subScopeId().get(), expected().get())).append("\n"); + printFieldValue(subScopeId().get(), expected().get(), sb); + sb.append("\n"); } return; default: @@ -159,7 +164,12 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { } static SingularField ignored(String fieldName) { - return newBuilder().setFieldName(fieldName).setResult(Result.IGNORED).build(); + return newBuilder() + .setFieldName(fieldName) + .setResult(Result.IGNORED) + // Ignored fields don't need a customized proto printer. + .setProtoPrinter(TextFormat.printer()) + .build(); } static Builder newBuilder() { @@ -167,7 +177,6 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { } /** Builder for {@link SingularField}. */ - @CanIgnoreReturnValue @AutoValue.Builder abstract static class Builder { abstract Builder setResult(Result result); @@ -184,6 +193,8 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { abstract Builder setUnknownsBreakdown(UnknownFieldSetDiff unknownsBreakdown); + abstract Builder setProtoPrinter(TextFormat.Printer value); + abstract SingularField build(); } } @@ -206,7 +217,8 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { * If both are present but the indexes differ, it represents a 'move'. */ @AutoValue - abstract static class PairResult extends RecursableDiffEntity.WithResultCode { + abstract static class PairResult extends RecursableDiffEntity.WithResultCode + implements ProtoPrintable { /** The {@link FieldDescriptor} describing the repeated field for this pair. */ abstract FieldDescriptor fieldDescriptor(); @@ -264,9 +276,11 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { case ADDED: sb.append("added: ").append(indexed(fieldPrefix, actualFieldIndex())).append(": "); if (isMessage()) { - sb.append("\n").append(actual().get()); + sb.append("\n"); + printMessage((Message) actual().get(), sb); } else { - sb.append(valueString(fieldDescriptor(), actual().get())).append("\n"); + printFieldValue(fieldDescriptor(), actual().get(), sb); + sb.append("\n"); } return; case IGNORED: @@ -286,7 +300,9 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { sb.append("\n"); printChildContents(includeMatches, indexed(fieldPrefix, actualFieldIndex()), sb); } else { - sb.append(" ").append(valueString(fieldDescriptor(), actual().get())).append("\n"); + sb.append(" "); + printFieldValue(fieldDescriptor(), actual().get(), sb); + sb.append("\n"); } return; case MATCHED: @@ -303,7 +319,9 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { sb.append("\n"); printChildContents(includeMatches, indexed(fieldPrefix, actualFieldIndex()), sb); } else { - sb.append(" ").append(valueString(fieldDescriptor(), actual().get())).append("\n"); + sb.append(" "); + printFieldValue(fieldDescriptor(), actual().get(), sb); + sb.append("\n"); } return; case MOVED_OUT_OF_ORDER: @@ -316,7 +334,9 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { sb.append("\n"); printChildContents(includeMatches, indexed(fieldPrefix, actualFieldIndex()), sb); } else { - sb.append(" ").append(valueString(fieldDescriptor(), actual().get())).append("\n"); + sb.append(" "); + printFieldValue(fieldDescriptor(), actual().get(), sb); + sb.append("\n"); } return; case MODIFIED: @@ -333,18 +353,20 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { sb.append("\n"); printChildContents(includeMatches, indexed(fieldPrefix, actualFieldIndex()), sb); } else { - sb.append(" ") - .append(valueString(fieldDescriptor(), expected().get())) - .append(" -> ") - .append(valueString(fieldDescriptor(), actual().get())); + sb.append(" "); + printFieldValue(fieldDescriptor(), expected().get(), sb); + sb.append(" -> "); + printFieldValue(fieldDescriptor(), actual().get(), sb); } return; case REMOVED: sb.append("deleted: ").append(indexed(fieldPrefix, expectedFieldIndex())).append(": "); if (isMessage()) { - sb.append("\n").append(expected().get()); + sb.append("\n"); + printMessage((Message) expected().get(), sb); } else { - sb.append(valueString(fieldDescriptor(), expected().get())).append("\n"); + printFieldValue(fieldDescriptor(), expected().get(), sb); + sb.append("\n"); } return; } @@ -362,7 +384,6 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { return new AutoValue_DiffResult_RepeatedField_PairResult.Builder(); } - @CanIgnoreReturnValue @AutoValue.Builder abstract static class Builder { abstract Builder setResult(Result result); @@ -377,6 +398,8 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { abstract Builder setExpected(Object expected); + abstract Builder setProtoPrinter(TextFormat.Printer value); + abstract Builder setBreakdown(DiffResult breakdown); abstract PairResult build(); @@ -422,7 +445,6 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { return new AutoValue_DiffResult_RepeatedField.Builder(); } - @CanIgnoreReturnValue @AutoValue.Builder abstract static class Builder { abstract Builder setFieldDescriptor(FieldDescriptor fieldDescriptor); @@ -434,6 +456,7 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { @ForOverride abstract ImmutableList.Builder<PairResult> pairResultsBuilder(); + @CanIgnoreReturnValue final Builder addPairResult(PairResult pairResult) { pairResultsBuilder().add(pairResult); return this; @@ -489,7 +512,6 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { return new AutoValue_DiffResult_UnknownFieldSetDiff.Builder(); } - @CanIgnoreReturnValue @AutoValue.Builder abstract static class Builder { abstract Builder setActual(UnknownFieldSet actual); @@ -499,11 +521,13 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { @ForOverride abstract ImmutableListMultimap.Builder<Integer, SingularField> singularFieldsBuilder(); + @CanIgnoreReturnValue final Builder addSingularField(int fieldNumber, SingularField singularField) { singularFieldsBuilder().put(fieldNumber, singularField); return this; } + @CanIgnoreReturnValue final Builder addAllSingularFields(int fieldNumber, Iterable<SingularField> singularFields) { singularFieldsBuilder().putAll(fieldNumber, singularFields); return this; @@ -513,6 +537,48 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { } } + /** Utilities to support printing messages and proto fields using a {@link TextFormat.Printer}. */ + interface ProtoPrintable { + TextFormat.Printer protoPrinter(); + + default void printMessage(Message m, StringBuilder sb) { + try { + protoPrinter().print(m, sb); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + default void printFieldValue(SubScopeId subScopeId, Object o, StringBuilder sb) { + switch (subScopeId.kind()) { + case FIELD_DESCRIPTOR: + printFieldValue(subScopeId.fieldDescriptor(), o, sb); + return; + case UNKNOWN_FIELD_DESCRIPTOR: + printFieldValue(subScopeId.unknownFieldDescriptor(), o, sb); + return; + } + throw new AssertionError(subScopeId.kind()); + } + + default void printFieldValue(FieldDescriptor field, Object value, StringBuilder sb) { + try { + protoPrinter().printFieldValue(field, value, sb); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + default void printFieldValue( + UnknownFieldDescriptor unknownField, Object value, StringBuilder sb) { + try { + TextFormat.printUnknownFieldValue(unknownField.type().wireType(), value, sb); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + } + /** The {@link Message} being tested. */ abstract Message actual(); @@ -599,37 +665,6 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { return rootFieldPrefix.isEmpty() ? toAdd : (rootFieldPrefix + "." + toAdd); } - private static String valueString(SubScopeId subScopeId, Object o) { - switch (subScopeId.kind()) { - case FIELD_DESCRIPTOR: - return valueString(subScopeId.fieldDescriptor(), o); - case UNKNOWN_FIELD_DESCRIPTOR: - return valueString(subScopeId.unknownFieldDescriptor(), o); - } - throw new AssertionError(subScopeId.kind()); - } - - private static String valueString(FieldDescriptor fieldDescriptor, Object o) { - StringBuilder sb = new StringBuilder(); - try { - TextFormat.printFieldValue(fieldDescriptor, o, sb); - return sb.toString(); - } catch (IOException impossible) { - throw new AssertionError(impossible); - } - } - - private static String valueString(UnknownFieldDescriptor unknownFieldDescriptor, Object o) { - StringBuilder sb = new StringBuilder(); - try { - TextFormat.printUnknownFieldValue(unknownFieldDescriptor.type().wireType(), o, sb); - return sb.toString(); - } catch (IOException impossible) { - throw new AssertionError(impossible); - } - } - - @CanIgnoreReturnValue @AutoValue.Builder abstract static class Builder { abstract Builder setActual(Message actual); @@ -639,11 +674,13 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { @ForOverride abstract ImmutableListMultimap.Builder<Integer, SingularField> singularFieldsBuilder(); + @CanIgnoreReturnValue final Builder addSingularField(int fieldNumber, SingularField singularField) { singularFieldsBuilder().put(fieldNumber, singularField); return this; } + @CanIgnoreReturnValue final Builder addAllSingularFields(int fieldNumber, Iterable<SingularField> singularFields) { singularFieldsBuilder().putAll(fieldNumber, singularFields); return this; @@ -652,6 +689,7 @@ abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode { @ForOverride abstract ImmutableListMultimap.Builder<Integer, RepeatedField> repeatedFieldsBuilder(); + @CanIgnoreReturnValue final Builder addRepeatedField(int fieldNumber, RepeatedField repeatedField) { repeatedFieldsBuilder().put(fieldNumber, repeatedField); return this; diff --git a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldDescriptorValidator.java b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldDescriptorValidator.java index a0d64ced..8c3c81fb 100644 --- a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldDescriptorValidator.java +++ b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldDescriptorValidator.java @@ -20,7 +20,6 @@ import static com.google.common.base.Preconditions.checkArgument; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor.JavaType; -import com.google.protobuf.Descriptors.FileDescriptor.Syntax; /** Various validators, to ensure that explicit comparison settings made by the user make sense. */ enum FieldDescriptorValidator { @@ -37,9 +36,8 @@ enum FieldDescriptorValidator { fieldDescriptor); checkArgument( - fieldDescriptor.getContainingType().getFile().getSyntax() != Syntax.PROTO3 - || fieldDescriptor.getJavaType() == JavaType.MESSAGE, - "%s is a primitive field in a Proto 3 message; it cannot be absent", + fieldDescriptor.hasPresence(), + "%s is a field without presence; it cannot be absent", fieldDescriptor); } }, diff --git a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldScopeUtil.java b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldScopeUtil.java index ae0b634c..c45b1c61 100644 --- a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldScopeUtil.java +++ b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldScopeUtil.java @@ -34,13 +34,8 @@ final class FieldScopeUtil { * @param fmt Format string that must contain exactly one '%s' and no other format parameters. */ static Function<Optional<Descriptor>, String> fieldNumbersFunction( - final String fmt, final Iterable<Integer> fieldNumbers) { - return new Function<Optional<Descriptor>, String>() { - @Override - public String apply(Optional<Descriptor> optDescriptor) { - return resolveFieldNumbers(optDescriptor, fmt, fieldNumbers); - } - }; + String fmt, Iterable<Integer> fieldNumbers) { + return optDescriptor -> resolveFieldNumbers(optDescriptor, fmt, fieldNumbers); } /** @@ -50,25 +45,15 @@ final class FieldScopeUtil { * @param fmt Format string that must contain exactly one '%s' and no other format parameters. */ static Function<Optional<Descriptor>, String> fieldScopeFunction( - final String fmt, final FieldScope fieldScope) { - return new Function<Optional<Descriptor>, String>() { - @Override - public String apply(Optional<Descriptor> optDescriptor) { - return String.format(fmt, fieldScope.usingCorrespondenceString(optDescriptor)); - } - }; + String fmt, FieldScope fieldScope) { + return optDescriptor -> String.format(fmt, fieldScope.usingCorrespondenceString(optDescriptor)); } /** Returns a function which concatenates the outputs of the two input functions. */ static Function<Optional<Descriptor>, String> concat( - final Function<? super Optional<Descriptor>, String> function1, - final Function<? super Optional<Descriptor>, String> function2) { - return new Function<Optional<Descriptor>, String>() { - @Override - public String apply(Optional<Descriptor> optDescriptor) { - return function1.apply(optDescriptor) + function2.apply(optDescriptor); - } - }; + Function<? super Optional<Descriptor>, String> function1, + Function<? super Optional<Descriptor>, String> function2) { + return optDescriptor -> function1.apply(optDescriptor) + function2.apply(optDescriptor); } /** diff --git a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FluentEqualityConfig.java b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FluentEqualityConfig.java index 1d5fff1d..25efe721 100644 --- a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FluentEqualityConfig.java +++ b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FluentEqualityConfig.java @@ -29,7 +29,6 @@ import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.truth.Correspondence; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import com.google.errorprone.annotations.CheckReturnValue; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.ExtensionRegistry; @@ -366,7 +365,7 @@ abstract class FluentEqualityConfig implements FieldScopeLogicContainer<FluentEq } final <M extends Message> Correspondence<M, M> toCorrespondence( - final Optional<Descriptor> optDescriptor) { + Optional<Descriptor> optDescriptor) { checkState(hasExpectedMessages(), "withExpectedMessages() not called"); return Correspondence.from( // If we were allowed lambdas, this would be: @@ -409,7 +408,6 @@ abstract class FluentEqualityConfig implements FieldScopeLogicContainer<FluentEq abstract Builder toBuilder(); - @CanIgnoreReturnValue @AutoValue.Builder abstract static class Builder { abstract Builder setIgnoreFieldAbsenceScope(FieldScopeLogic fieldScopeLogic); @@ -433,6 +431,7 @@ abstract class FluentEqualityConfig implements FieldScopeLogicContainer<FluentEq abstract Builder setReportMismatchesOnly(boolean reportMismatchesOnly); + @CanIgnoreReturnValue final Builder setUnpackingAnyUsing( TypeRegistry typeRegistry, ExtensionRegistry extensionRegistry) { setUseTypeRegistry(typeRegistry); @@ -444,7 +443,6 @@ abstract class FluentEqualityConfig implements FieldScopeLogicContainer<FluentEq abstract Builder setUseExtensionRegistry(ExtensionRegistry extensionRegistry); - @CheckReturnValue abstract Function<? super Optional<Descriptor>, String> usingCorrespondenceStringFunction(); abstract Builder setUsingCorrespondenceStringFunction( @@ -455,11 +453,13 @@ abstract class FluentEqualityConfig implements FieldScopeLogicContainer<FluentEq // Lazy formatting methods. // These allow us to print raw integer field numbers with meaningful names. + @CanIgnoreReturnValue final Builder addUsingCorrespondenceString(String string) { return setUsingCorrespondenceStringFunction( FieldScopeUtil.concat(usingCorrespondenceStringFunction(), Functions.constant(string))); } + @CanIgnoreReturnValue final Builder addUsingCorrespondenceFieldNumbersString( String fmt, Iterable<Integer> fieldNumbers) { return setUsingCorrespondenceStringFunction( @@ -468,6 +468,7 @@ abstract class FluentEqualityConfig implements FieldScopeLogicContainer<FluentEq FieldScopeUtil.fieldNumbersFunction(fmt, fieldNumbers))); } + @CanIgnoreReturnValue final Builder addUsingCorrespondenceFieldDescriptorsString( String fmt, Iterable<FieldDescriptor> fieldDescriptors) { return setUsingCorrespondenceStringFunction( @@ -476,6 +477,7 @@ abstract class FluentEqualityConfig implements FieldScopeLogicContainer<FluentEq Functions.constant(String.format(fmt, join(fieldDescriptors))))); } + @CanIgnoreReturnValue final Builder addUsingCorrespondenceFieldScopeString(String fmt, FieldScope fieldScope) { return setUsingCorrespondenceStringFunction( FieldScopeUtil.concat( diff --git a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/IterableOfProtosSubject.java b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/IterableOfProtosSubject.java index 0b9252a7..b5f342bb 100644 --- a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/IterableOfProtosSubject.java +++ b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/IterableOfProtosSubject.java @@ -29,7 +29,9 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; +import com.google.protobuf.TextFormat; import com.google.protobuf.TypeRegistry; +import java.io.IOException; import java.util.Arrays; import java.util.Comparator; import org.checkerframework.checker.nullness.qual.Nullable; @@ -64,6 +66,7 @@ public class IterableOfProtosSubject<M extends Message> extends IterableSubject private final FailureMetadata metadata; private final Iterable<M> actual; private final FluentEqualityConfig config; + private final TextFormat.Printer protoPrinter; protected IterableOfProtosSubject( FailureMetadata failureMetadata, @Nullable Iterable<M> messages) { @@ -78,6 +81,28 @@ public class IterableOfProtosSubject<M extends Message> extends IterableSubject this.metadata = failureMetadata; this.actual = messages; this.config = config; + this.protoPrinter = TextFormat.printer().usingTypeRegistry(config.useTypeRegistry()); + } + + @Override + protected String actualCustomStringRepresentation() { + if (actual == null) { + return "null"; + } + StringBuilder sb = new StringBuilder().append('['); + boolean first = true; + for (M element : actual) { + if (!first) { + sb.append(", "); + } + first = false; + try { + protoPrinter.print(element, sb); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + return sb.append(']').toString(); } /** @@ -735,7 +760,7 @@ public class IterableOfProtosSubject<M extends Message> extends IterableSubject @Override @CanIgnoreReturnValue - public Ordered containsExactly(/*@Nullable*/ M... expected) { + public Ordered containsExactly(@Nullable M... expected) { return delegate(Arrays.asList(expected)).containsExactly(expected); } @@ -753,7 +778,7 @@ public class IterableOfProtosSubject<M extends Message> extends IterableSubject @Override @CanIgnoreReturnValue - public Ordered containsAtLeast(@Nullable M first, @Nullable M second, /*@Nullable*/ M... rest) { + public Ordered containsAtLeast(@Nullable M first, @Nullable M second, @Nullable M... rest) { return delegate(Lists.asList(first, second, rest)).containsAtLeast(first, second, rest); } @@ -770,7 +795,7 @@ public class IterableOfProtosSubject<M extends Message> extends IterableSubject } @Override - public void containsAnyOf(@Nullable M first, @Nullable M second, /*@Nullable*/ M... rest) { + public void containsAnyOf(@Nullable M first, @Nullable M second, @Nullable M... rest) { delegate(Lists.asList(first, second, rest)).containsAnyOf(first, second, rest); } @@ -786,7 +811,7 @@ public class IterableOfProtosSubject<M extends Message> extends IterableSubject @Override public void containsNoneOf( - @Nullable M firstExcluded, @Nullable M secondExcluded, /*@Nullable*/ M... restOfExcluded) { + @Nullable M firstExcluded, @Nullable M secondExcluded, @Nullable M... restOfExcluded) { delegate(Lists.asList(firstExcluded, secondExcluded, restOfExcluded)) .containsNoneOf(firstExcluded, secondExcluded, restOfExcluded); } @@ -1029,7 +1054,7 @@ public class IterableOfProtosSubject<M extends Message> extends IterableSubject } @Override - public Ordered containsExactly(/*@Nullable*/ M... expected) { + public Ordered containsExactly(@Nullable M... expected) { return usingCorrespondence().containsExactly(expected); } @@ -1044,7 +1069,7 @@ public class IterableOfProtosSubject<M extends Message> extends IterableSubject } @Override - public Ordered containsAtLeast(@Nullable M first, @Nullable M second, /*@Nullable*/ M... rest) { + public Ordered containsAtLeast(@Nullable M first, @Nullable M second, @Nullable M... rest) { return usingCorrespondence().containsAtLeast(first, second, rest); } @@ -1059,7 +1084,7 @@ public class IterableOfProtosSubject<M extends Message> extends IterableSubject } @Override - public void containsAnyOf(@Nullable M first, @Nullable M second, /*@Nullable*/ M... rest) { + public void containsAnyOf(@Nullable M first, @Nullable M second, @Nullable M... rest) { usingCorrespondence().containsAnyOf(first, second, rest); } @@ -1075,7 +1100,7 @@ public class IterableOfProtosSubject<M extends Message> extends IterableSubject @Override public void containsNoneOf( - @Nullable M firstExcluded, @Nullable M secondExcluded, /*@Nullable*/ M... restOfExcluded) { + @Nullable M firstExcluded, @Nullable M secondExcluded, @Nullable M... restOfExcluded) { usingCorrespondence().containsNoneOf(firstExcluded, secondExcluded, restOfExcluded); } diff --git a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/IterableOfProtosUsingCorrespondence.java b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/IterableOfProtosUsingCorrespondence.java index 5a49369a..9c366f58 100644 --- a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/IterableOfProtosUsingCorrespondence.java +++ b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/IterableOfProtosUsingCorrespondence.java @@ -83,7 +83,7 @@ public interface IterableOfProtosUsingCorrespondence<M extends Message> { * elements, not an element itself. */ @CanIgnoreReturnValue - Ordered containsExactly(/*@Nullable*/ M... expected); + Ordered containsExactly(@Nullable M... expected); /** * Checks that subject contains exactly elements that correspond to the expected elements, i.e. @@ -117,7 +117,7 @@ public interface IterableOfProtosUsingCorrespondence<M extends Message> { * subject, but they are not required to be consecutive. */ @CanIgnoreReturnValue - Ordered containsAtLeast(@Nullable M first, @Nullable M second, /*@Nullable*/ M... rest); + Ordered containsAtLeast(@Nullable M first, @Nullable M second, @Nullable M... rest); /** * Checks that the subject contains elements that corresponds to all of the expected elements, @@ -147,7 +147,7 @@ public interface IterableOfProtosUsingCorrespondence<M extends Message> { * Checks that the subject contains at least one element that corresponds to at least one of the * expected elements. */ - void containsAnyOf(@Nullable M first, @Nullable M second, /*@Nullable*/ M... rest); + void containsAnyOf(@Nullable M first, @Nullable M second, @Nullable M... rest); /** * Checks that the subject contains at least one element that corresponds to at least one of the @@ -167,7 +167,7 @@ public interface IterableOfProtosUsingCorrespondence<M extends Message> { * to any of the given elements.) */ void containsNoneOf( - @Nullable M firstExcluded, @Nullable M secondExcluded, /*@Nullable*/ M... restOfExcluded); + @Nullable M firstExcluded, @Nullable M secondExcluded, @Nullable M... restOfExcluded); /** * Checks that the subject contains no elements that correspond to any of the given elements. diff --git a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/MapWithProtoValuesFluentAssertion.java b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/MapWithProtoValuesFluentAssertion.java index fee52423..f1aa7af1 100644 --- a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/MapWithProtoValuesFluentAssertion.java +++ b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/MapWithProtoValuesFluentAssertion.java @@ -524,7 +524,7 @@ public interface MapWithProtoValuesFluentAssertion<M extends Message> { * key/value pairs at compile time. Please make sure you provide varargs in key/value pairs! */ @CanIgnoreReturnValue - Ordered containsExactly(@Nullable Object k0, @Nullable M v0, /*@Nullable*/ Object... rest); + Ordered containsExactly(@Nullable Object k0, @Nullable M v0, @Nullable Object... rest); /** * Fails if the map does not contain exactly the keys in the given map, mapping to values that diff --git a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/MapWithProtoValuesSubject.java b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/MapWithProtoValuesSubject.java index 2f4fac81..9248843e 100644 --- a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/MapWithProtoValuesSubject.java +++ b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/MapWithProtoValuesSubject.java @@ -870,8 +870,7 @@ public class MapWithProtoValuesSubject<M extends Message> extends MapSubject { @Override @CanIgnoreReturnValue @SuppressWarnings("unchecked") // ClassCastException is fine - public Ordered containsExactly( - @Nullable Object k0, @Nullable M v0, /*@Nullable*/ Object... rest) { + public Ordered containsExactly(@Nullable Object k0, @Nullable M v0, @Nullable Object... rest) { List<M> expectedValues = new ArrayList<>(); expectedValues.add(v0); for (int i = 1; i < rest.length; i += 2) { diff --git a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/MultimapWithProtoValuesFluentAssertion.java b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/MultimapWithProtoValuesFluentAssertion.java index 224e3302..838389b4 100644 --- a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/MultimapWithProtoValuesFluentAssertion.java +++ b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/MultimapWithProtoValuesFluentAssertion.java @@ -536,7 +536,7 @@ public interface MultimapWithProtoValuesFluentAssertion<M extends Message> { * key/value pairs at compile time. Please make sure you provide varargs in key/value pairs! */ @CanIgnoreReturnValue - public Ordered containsExactly(@Nullable Object k0, @Nullable M v0, /*@Nullable*/ Object... rest); + public Ordered containsExactly(@Nullable Object k0, @Nullable M v0, @Nullable Object... rest); /** * @deprecated Do not call {@code equals()} on a {@code MultimapWithProtoValuesFluentAssertion}. diff --git a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/MultimapWithProtoValuesSubject.java b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/MultimapWithProtoValuesSubject.java index 8a1e5c0a..8b37ee95 100644 --- a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/MultimapWithProtoValuesSubject.java +++ b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/MultimapWithProtoValuesSubject.java @@ -21,6 +21,7 @@ import static com.google.common.collect.Lists.asList; import static com.google.common.truth.extensions.proto.FieldScopeUtil.asList; import static com.google.common.truth.extensions.proto.ProtoTruth.protos; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; import com.google.common.truth.FailureMetadata; import com.google.common.truth.MultimapSubject; @@ -33,7 +34,6 @@ import com.google.protobuf.Message; import com.google.protobuf.TypeRegistry; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; @@ -90,6 +90,12 @@ public class MultimapWithProtoValuesSubject<M extends Message> extends MultimapS * <p>This method performs no checks on its own and cannot cause test failures. Subsequent * assertions must be chained onto this method call to test properties of the {@link Multimap}. */ + /* + * This is mostly safe because we only read from the map. And if it produces NPE/CCE immediately, + * that's no worse than many existing Collection implementations.... + */ + @SuppressWarnings("unchecked") + @Override public IterableOfProtosSubject<M> valuesForKey(@Nullable Object key) { return check("valuesForKey(%s)", key) .about(protos()) @@ -910,14 +916,13 @@ public class MultimapWithProtoValuesSubject<M extends Message> extends MultimapS @Override @CanIgnoreReturnValue public Ordered containsExactly() { - return subject.usingCorrespondence(Collections.<M>emptyList()).containsExactly(); + return subject.usingCorrespondence(ImmutableList.of()).containsExactly(); } @Override @CanIgnoreReturnValue @SuppressWarnings("unchecked") // ClassCastException is fine - public Ordered containsExactly( - @Nullable Object k0, @Nullable M v0, /*@Nullable*/ Object... rest) { + public Ordered containsExactly(@Nullable Object k0, @Nullable M v0, @Nullable Object... rest) { List<M> expectedValues = new ArrayList<>(); expectedValues.add(v0); for (int i = 1; i < rest.length; i += 2) { @@ -926,14 +931,19 @@ public class MultimapWithProtoValuesSubject<M extends Message> extends MultimapS return subject.usingCorrespondence(expectedValues).containsExactly(k0, v0, rest); } - @SuppressWarnings("DoNotCall") + /* + * Calling this method is a mistake, so we delegate to a method whose implementation throws an + * exception to explain the mistake. + */ + @SuppressWarnings({"DoNotCall", "deprecation"}) @Override @Deprecated public boolean equals(Object o) { return subject.equals(o); } - @SuppressWarnings("DoNotCall") + // (see equals() just above) + @SuppressWarnings({"DoNotCall", "deprecation"}) @Override @Deprecated public int hashCode() { diff --git a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/ProtoTruthMessageDifferencer.java b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/ProtoTruthMessageDifferencer.java index d843a61d..5ccf6dc1 100644 --- a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/ProtoTruthMessageDifferencer.java +++ b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/ProtoTruthMessageDifferencer.java @@ -37,7 +37,6 @@ import com.google.protobuf.Any; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor.JavaType; -import com.google.protobuf.Descriptors.FileDescriptor.Syntax; import com.google.protobuf.Message; import com.google.protobuf.TextFormat; import com.google.protobuf.UnknownFieldSet; @@ -62,12 +61,14 @@ import org.checkerframework.checker.nullness.qual.Nullable; final class ProtoTruthMessageDifferencer { private final FluentEqualityConfig rootConfig; private final Descriptor rootDescriptor; + private final TextFormat.Printer protoPrinter; private ProtoTruthMessageDifferencer(FluentEqualityConfig rootConfig, Descriptor descriptor) { rootConfig.validate(descriptor, FieldDescriptorValidator.ALLOW_ALL); this.rootConfig = rootConfig; this.rootDescriptor = descriptor; + this.protoPrinter = TextFormat.printer().usingTypeRegistry(rootConfig.useTypeRegistry()); } /** Create a new {@link ProtoTruthMessageDifferencer} for the given config and descriptor. */ @@ -387,6 +388,7 @@ final class ProtoTruthMessageDifferencer { .setActual(actualList.get(i)) .setActualFieldIndex(i) .setFieldDescriptor(fieldDescriptor) + .setProtoPrinter(protoPrinter) .build()); } else { builder.addPairResult( @@ -481,6 +483,7 @@ final class ProtoTruthMessageDifferencer { .setFieldDescriptor(fieldDescriptor) .setExpected(expected) .setExpectedFieldIndex(expectedIndex) + .setProtoPrinter(protoPrinter) .build()); } } @@ -494,6 +497,7 @@ final class ProtoTruthMessageDifferencer { .setFieldDescriptor(fieldDescriptor) .setActual(actualList.get(index)) .setActualFieldIndex(index) + .setProtoPrinter(protoPrinter) .build()); } @@ -505,7 +509,7 @@ final class ProtoTruthMessageDifferencer { // Also removes the index for the matching value from actualIndicies. // // If there is no match, returns null. - private RepeatedField./*@Nullable*/ PairResult findMatchingPairResult( + private RepeatedField.@Nullable PairResult findMatchingPairResult( Deque<Integer> actualIndices, List<?> actualValues, int expectedIndex, @@ -555,7 +559,8 @@ final class ProtoTruthMessageDifferencer { RepeatedField.PairResult.Builder pairResultBuilder = RepeatedField.PairResult.newBuilder() .setResult(comparison.result()) - .setFieldDescriptor(fieldDescriptor); + .setFieldDescriptor(fieldDescriptor) + .setProtoPrinter(protoPrinter); if (actual != null) { pairResultBuilder.setActual(actual).setActualFieldIndex(actualFieldIndex); } @@ -687,7 +692,8 @@ final class ProtoTruthMessageDifferencer { SingularField.newBuilder() .setSubScopeId(SubScopeId.of(fieldDescriptor)) .setFieldName(fieldName) - .setResult(result.build()); + .setResult(result.build()) + .setProtoPrinter(protoPrinter); if (actual != null) { singularFieldBuilder.setActual(actual); } @@ -709,16 +715,12 @@ final class ProtoTruthMessageDifferencer { FluentEqualityConfig config) { Result.Builder result = Result.builder(); - // Use the default if it's set and we're ignoring field absence, or if it's a Proto3 primitive - // for which default is indistinguishable from unset. + // Use the default if it's set and we're ignoring field absence or if it's a field without + // presence for which default is indistinguishable from unset. SubScopeId subScopeId = SubScopeId.of(fieldDescriptor); - boolean isNonRepeatedProto3 = - !fieldDescriptor.isRepeated() - && fieldDescriptor.getContainingOneof() == null - && fieldDescriptor.getFile().getSyntax() == Syntax.PROTO3; + boolean hasPresence = fieldDescriptor.isRepeated() || fieldDescriptor.hasPresence(); boolean ignoreFieldAbsence = - isNonRepeatedProto3 - || config.ignoreFieldAbsenceScope().contains(rootDescriptor, subScopeId); + !hasPresence || config.ignoreFieldAbsenceScope().contains(rootDescriptor, subScopeId); actual = orIfIgnoringFieldAbsence(actual, defaultValue, ignoreFieldAbsence); expected = orIfIgnoringFieldAbsence(expected, defaultValue, ignoreFieldAbsence); @@ -750,7 +752,8 @@ final class ProtoTruthMessageDifferencer { SingularField.newBuilder() .setSubScopeId(SubScopeId.of(fieldDescriptor)) .setFieldName(fieldName) - .setResult(result.build()); + .setResult(result.build()) + .setProtoPrinter(protoPrinter); if (actual != null) { singularFieldBuilder.setActual(actual); } @@ -904,7 +907,8 @@ final class ProtoTruthMessageDifferencer { SingularField.newBuilder() .setSubScopeId(SubScopeId.of(unknownFieldDescriptor)) .setFieldName(fieldName) - .setResult(result.build()); + .setResult(result.build()) + .setProtoPrinter(protoPrinter); if (actual != null) { singularFieldBuilder.setActual(actual); } @@ -932,7 +936,8 @@ final class ProtoTruthMessageDifferencer { SingularField.newBuilder() .setSubScopeId(SubScopeId.of(unknownFieldDescriptor)) .setFieldName(fieldName) - .setResult(result.build()); + .setResult(result.build()) + .setProtoPrinter(protoPrinter); if (actual != null) { singularFieldBuilder.setActual(actual); } diff --git a/extensions/proto/src/test/java/com/google/common/truth/extensions/proto/IterableOfProtosSubjectTest.java b/extensions/proto/src/test/java/com/google/common/truth/extensions/proto/IterableOfProtosSubjectTest.java index 7a06845e..028601bc 100644 --- a/extensions/proto/src/test/java/com/google/common/truth/extensions/proto/IterableOfProtosSubjectTest.java +++ b/extensions/proto/src/test/java/com/google/common/truth/extensions/proto/IterableOfProtosSubjectTest.java @@ -16,6 +16,8 @@ package com.google.common.truth.extensions.proto; +import static java.util.Comparator.comparing; + import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.protobuf.Message; @@ -487,12 +489,7 @@ public class IterableOfProtosSubjectTest extends ProtoSubjectTestBase { Message expectedInt4 = parse("o_int: 4 r_string: 'qux'"); Function<Message, Integer> getInt = - new Function<Message, Integer>() { - @Override - public Integer apply(Message message) { - return (Integer) message.getField(getFieldDescriptor("o_int")); - } - }; + message -> (Integer) message.getField(getFieldDescriptor("o_int")); expectFailureWhenTesting() .that(listOf(actualInt3, actualInt4)) @@ -520,13 +517,6 @@ public class IterableOfProtosSubjectTest extends ProtoSubjectTestBase { } private Comparator<Message> compareByOIntAscending() { - return new Comparator<Message>() { - @Override - public int compare(Message message1, Message message2) { - return Integer.compare( - (Integer) message1.getField(getFieldDescriptor("o_int")), - (Integer) message2.getField(getFieldDescriptor("o_int"))); - } - }; + return comparing(message -> (Integer) message.getField(getFieldDescriptor("o_int"))); } } diff --git a/extensions/proto/src/test/java/com/google/common/truth/extensions/proto/ProtoSubjectTest.java b/extensions/proto/src/test/java/com/google/common/truth/extensions/proto/ProtoSubjectTest.java index 9b3c0a68..c9eb636b 100644 --- a/extensions/proto/src/test/java/com/google/common/truth/extensions/proto/ProtoSubjectTest.java +++ b/extensions/proto/src/test/java/com/google/common/truth/extensions/proto/ProtoSubjectTest.java @@ -208,7 +208,7 @@ public class ProtoSubjectTest extends ProtoSubjectTestBase { fail("Expected failure."); } catch (Exception e) { assertThat(e).hasMessageThat().contains("o_double"); - assertThat(e).hasMessageThat().contains("is a primitive field in a Proto 3 message"); + assertThat(e).hasMessageThat().contains("is a field without presence"); } } else { expectThat(message) @@ -248,8 +248,8 @@ public class ProtoSubjectTest extends ProtoSubjectTestBase { Message eqMessage = parse("r_string: \"bar\" r_string: \"foo\""); Message diffMessage = parse("r_string: \"foo\" r_string: \"foo\" r_string: \"bar\""); - expectThat(message).isEqualTo(message.toBuilder().build()); - expectThat(message).ignoringRepeatedFieldOrder().isEqualTo(message.toBuilder().build()); + expectThat(message).isEqualTo(clone(message)); + expectThat(message).ignoringRepeatedFieldOrder().isEqualTo(clone(message)); expectThat(diffMessage).isNotEqualTo(message); expectThat(diffMessage).ignoringRepeatedFieldOrder().isNotEqualTo(message); expectThat(eqMessage).isNotEqualTo(message); @@ -268,10 +268,8 @@ public class ProtoSubjectTest extends ProtoSubjectTestBase { "r_test_message: { o_int: 44 r_string: \"qux\" r_string: \"baz\" } " + "r_test_message: { o_int: 33 r_string: \"bar\" r_string: \"foo\" } "); - expectThat(nestedMessage).isEqualTo(nestedMessage.toBuilder().build()); - expectThat(nestedMessage) - .ignoringRepeatedFieldOrder() - .isEqualTo(nestedMessage.toBuilder().build()); + expectThat(nestedMessage).isEqualTo(clone(nestedMessage)); + expectThat(nestedMessage).ignoringRepeatedFieldOrder().isEqualTo(clone(nestedMessage)); expectThat(diffNestedMessage).isNotEqualTo(nestedMessage); expectThat(diffNestedMessage).ignoringRepeatedFieldOrder().isNotEqualTo(nestedMessage); expectThat(eqNestedMessage).isNotEqualTo(nestedMessage); @@ -786,6 +784,91 @@ public class ProtoSubjectTest extends ProtoSubjectTestBase { } @Test + public void testAnyMessage_notEqual_diffPrintsExpandedAny() { + String typeUrl = + isProto3() + ? "type.googleapis.com/com.google.common.truth.extensions.proto.SubTestMessage3" + : "type.googleapis.com/com.google.common.truth.extensions.proto.SubTestMessage2"; + Message msgWithAny = + parse("" + "o_int: 42 " + "o_any_message: { [" + typeUrl + "]: {r_string: \"foo\"} }"); + Message msgWithoutAny = parse("o_int: 42"); + + expectFailureWhenTesting() + .that(msgWithAny) + .unpackingAnyUsing(getTypeRegistry(), getExtensionRegistry()) + .isEqualTo(msgWithoutAny); + expectThatFailure() + .hasMessageThat() + .contains( + "added: o_any_message: \n" + "[" + typeUrl + "] {\n" + " r_string: \"foo\"\n" + "}\n"); + + expectFailureWhenTesting() + .that(msgWithoutAny) + .unpackingAnyUsing(getTypeRegistry(), getExtensionRegistry()) + .isEqualTo(msgWithAny); + expectThatFailure() + .hasMessageThat() + .contains( + "deleted: o_any_message: \n" + + "[" + + typeUrl + + "] {\n" + + " r_string: \"foo\"\n" + + "}\n"); + } + + @Test + public void testRepeatedAnyMessage_notEqual_diffPrintsExpandedAny() { + String typeUrl = + isProto3() + ? "type.googleapis.com/com.google.common.truth.extensions.proto.SubTestMessage3" + : "type.googleapis.com/com.google.common.truth.extensions.proto.SubTestMessage2"; + String fooSubMessage = "{ [" + typeUrl + "]: {r_string: \"foo\"} }"; + String barSubMessage = "{ [" + typeUrl + "]: {r_string: \"bar\"} }"; + String bazSubMessage = "{ [" + typeUrl + "]: {r_string: \"baz\"} }"; + Message msgWithFooBar = + parse( + "" + + "o_int: 42 " + + "r_any_message: " + + fooSubMessage + + "r_any_message: " + + barSubMessage); + Message msgWithBazFoo = + parse( + "" + + "o_int: 42 " + + "r_any_message: " + + bazSubMessage + + "r_any_message: " + + fooSubMessage); + + expectFailureWhenTesting() + .that(msgWithFooBar) + .unpackingAnyUsing(getTypeRegistry(), getExtensionRegistry()) + .ignoringRepeatedFieldOrder() + .isEqualTo(msgWithBazFoo); + + expectThatFailure() + .hasMessageThat() + .contains( + "" + + "moved: r_any_message[1] -> r_any_message[0]:\n" + + "added: r_any_message[1]: \n" + + "[" + + typeUrl + + "] {\n" + + " r_string: \"bar\"\n" + + "}\n" + + "deleted: r_any_message[0]: \n" + + "[" + + typeUrl + + "] {\n" + + " r_string: \"baz\"\n" + + "}"); + } + + @Test public void testAnyMessagesWithDifferentTypes() { String typeUrl = isProto3() @@ -870,8 +953,8 @@ public class ProtoSubjectTest extends ProtoSubjectTestBase { @Test public void testMapWithDefaultKeysAndValues() throws InvalidProtocolBufferException { Descriptor descriptor = getFieldDescriptor("o_int").getContainingType(); - final String defaultString = ""; - final int defaultInt32 = 0; + String defaultString = ""; + int defaultInt32 = 0; Message message = makeProtoMap(ImmutableMap.of(defaultString, 1, "foo", defaultInt32)); Message dynamicMessage = DynamicMessage.parseFrom( diff --git a/extensions/proto/src/test/java/com/google/common/truth/extensions/proto/ProtoSubjectTestBase.java b/extensions/proto/src/test/java/com/google/common/truth/extensions/proto/ProtoSubjectTestBase.java index a6d4e6b5..cc7fd0a3 100644 --- a/extensions/proto/src/test/java/com/google/common/truth/extensions/proto/ProtoSubjectTestBase.java +++ b/extensions/proto/src/test/java/com/google/common/truth/extensions/proto/ProtoSubjectTestBase.java @@ -141,6 +141,10 @@ public class ProtoSubjectTestBase { return extensionRegistry; } + protected final Message clone(Message in) { + return in.toBuilder().build(); + } + protected Message parse(String textProto) { try { Message.Builder builder = defaultInstance.toBuilder(); @@ -250,7 +254,7 @@ public class ProtoSubjectTestBase { for (int i = 0; i < rest.length; i += 2) { builder.put((K) rest[i], (V) rest[i + 1]); } - return builder.build(); + return builder.buildOrThrow(); } @SuppressWarnings("unchecked") diff --git a/extensions/re2j/pom.xml b/extensions/re2j/pom.xml index d95e4cd1..6ca4180c 100644 --- a/extensions/re2j/pom.xml +++ b/extensions/re2j/pom.xml @@ -25,6 +25,15 @@ </dependency> </dependencies> <build> + <resources> + <resource> + <directory>../..</directory> + <includes> + <include>LICENSE</include> + </includes> + <targetPath>META-INF</targetPath> + </resource> + </resources> <plugins> <plugin> <artifactId>maven-javadoc-plugin</artifactId> @@ -32,5 +41,3 @@ </plugins> </build> </project> - - diff --git a/extensions/re2j/src/main/java/com/google/common/truth/extensions/re2j/Re2jSubjects.java b/extensions/re2j/src/main/java/com/google/common/truth/extensions/re2j/Re2jSubjects.java index 67b1ed81..1dc387f5 100644 --- a/extensions/re2j/src/main/java/com/google/common/truth/extensions/re2j/Re2jSubjects.java +++ b/extensions/re2j/src/main/java/com/google/common/truth/extensions/re2j/Re2jSubjects.java @@ -15,10 +15,13 @@ */ package com.google.common.truth.extensions.re2j; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.common.annotations.GwtIncompatible; import com.google.common.truth.FailureMetadata; import com.google.common.truth.Subject; import com.google.re2j.Pattern; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Truth subjects for re2j regular expressions. @@ -51,26 +54,27 @@ public final class Re2jSubjects { private static final Subject.Factory<Re2jStringSubject, String> FACTORY = new Subject.Factory<Re2jStringSubject, String>() { @Override - public Re2jStringSubject createSubject(FailureMetadata failureMetadata, String target) { + public Re2jStringSubject createSubject( + FailureMetadata failureMetadata, @Nullable String target) { return new Re2jStringSubject(failureMetadata, target); } }; - private final String actual; + private final @Nullable String actual; - private Re2jStringSubject(FailureMetadata failureMetadata, String subject) { + private Re2jStringSubject(FailureMetadata failureMetadata, @Nullable String subject) { super(failureMetadata, subject); this.actual = subject; } @Override protected String actualCustomStringRepresentation() { - return quote(actual); + return quote(checkNotNull(actual)); } /** Fails if the string does not match the given regex. */ public void matches(String regex) { - if (!Pattern.matches(regex, actual)) { + if (!Pattern.matches(regex, checkNotNull(actual))) { failWithActual("expected to match ", regex); } } @@ -78,14 +82,14 @@ public final class Re2jSubjects { /** Fails if the string does not match the given regex. */ @GwtIncompatible("com.google.re2j.Pattern") public void matches(Pattern regex) { - if (!regex.matcher(actual).matches()) { + if (!regex.matcher(checkNotNull(actual)).matches()) { failWithActual("expected to match ", regex); } } /** Fails if the string matches the given regex. */ public void doesNotMatch(String regex) { - if (Pattern.matches(regex, actual)) { + if (Pattern.matches(regex, checkNotNull(actual))) { failWithActual("expected to fail to match", regex); } } @@ -93,7 +97,7 @@ public final class Re2jSubjects { /** Fails if the string matches the given regex. */ @GwtIncompatible("com.google.re2j.Pattern") public void doesNotMatch(Pattern regex) { - if (regex.matcher(actual).matches()) { + if (regex.matcher(checkNotNull(actual)).matches()) { failWithActual("expected to fail to match", regex); } } @@ -101,14 +105,14 @@ public final class Re2jSubjects { /** Fails if the string does not contain a match on the given regex. */ @GwtIncompatible("com.google.re2j.Pattern") public void containsMatch(Pattern pattern) { - if (!pattern.matcher(actual).find()) { + if (!pattern.matcher(checkNotNull(actual)).find()) { failWithActual("expected to contain a match for", pattern); } } /** Fails if the string does not contain a match on the given regex. */ public void containsMatch(String regex) { - if (!doContainsMatch(actual, regex)) { + if (!doContainsMatch(checkNotNull(actual), regex)) { failWithActual("expected to contain a match for", regex); } } @@ -116,14 +120,14 @@ public final class Re2jSubjects { /** Fails if the string contains a match on the given regex. */ @GwtIncompatible("com.google.re2j.Pattern") public void doesNotContainMatch(Pattern pattern) { - if (pattern.matcher(actual).find()) { + if (pattern.matcher(checkNotNull(actual)).find()) { failWithActual("expected not to contain a match for", pattern); } } /** Fails if the string contains a match on the given regex. */ public void doesNotContainMatch(String regex) { - if (doContainsMatch(actual, regex)) { + if (doContainsMatch(checkNotNull(actual), regex)) { failWithActual("expected not to contain a match for", regex); } } @@ -4,11 +4,6 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> - <parent> - <groupId>org.sonatype.oss</groupId> - <artifactId>oss-parent</artifactId> - <version>7</version> - </parent> <groupId>com.google.truth</groupId> <artifactId>truth-parent</artifactId> <version>HEAD-SNAPSHOT</version> @@ -18,18 +13,14 @@ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> - <!-- Properties for plugins for which pluginManagement hasn't been working for us. --> - <maven-javadoc-plugin.version>3.2.0</maven-javadoc-plugin.version> - <maven-enforcer-plugin.version>3.0.0-M3</maven-enforcer-plugin.version> - <!-- Properties for multiple-artifact deps. --> - <auto-value.version>1.8.1</auto-value.version> + <auto-value.version>1.10.1</auto-value.version> <!-- We have a separate property for each flavor of Guava (instead of a shared version without the -android and -jre suffixes) because that lets Dependabot update our Guava versions. --> - <guava.android.version>30.1.1-android</guava.android.version> + <guava.android.version>32.0.1-android</guava.android.version> <!-- Also, we have this comment in between the 2 flavors of Guava. That's also to smooth the Dependabot update process: Dependabot generates a @@ -37,15 +28,18 @@ time, one gets submitted before the other, and the other ends up with a merge conflict. That requires reapprovals. --> - <guava.jre.version>30.1.1-jre</guava.jre.version> + <guava.jre.version>32.0.0-jre</guava.jre.version> <gwt.version>2.9.0</gwt.version> - <protobuf.version>3.15.8</protobuf.version> + <protobuf.version>3.23.2</protobuf.version> <!-- Property for protobuf-lite protocArtifact, which isn't a "normal" Maven dep. --> <!-- TODO(cpovirk): Use protobuf.version instead. But that requires finding the new way to request the Lite runtime. --> <protobuf-lite.protoc.version>3.1.0</protobuf-lite.protoc.version> <!-- Property for an extension, since Maven doesn't have extensionManagement. --> - <os-maven-plugin.version>1.7.0</os-maven-plugin.version> + <os-maven-plugin.version>1.7.1</os-maven-plugin.version> + + <!-- Default to no additional options (for Java 8). Overridden by a profile. --> + <conditionalJavadoc9PlusOptions></conditionalJavadoc9PlusOptions> </properties> <dependencyManagement> <dependencies> @@ -79,7 +73,7 @@ <dependency> <groupId>org.checkerframework</groupId> <artifactId>checker-qual</artifactId> - <version>3.13.0</version> + <version>3.35.0</version> </dependency> <dependency> <groupId>junit</groupId> @@ -107,20 +101,9 @@ <version>${guava.android.version}</version> </dependency> <dependency> - <groupId>com.google.testing.compile</groupId> - <artifactId>compile-testing</artifactId> - <version>0.19</version> - <exclusions> - <exclusion> - <groupId>com.google.truth</groupId> - <artifactId>truth</artifactId> - </exclusion> - </exclusions> - </dependency> - <dependency> <groupId>com.google.errorprone</groupId> <artifactId>error_prone_annotations</artifactId> - <version>2.6.0</version> + <version>2.19.1</version> </dependency> <dependency> <groupId>com.google.protobuf</groupId> @@ -135,12 +118,12 @@ <dependency> <groupId>com.google.re2j</groupId> <artifactId>re2j</artifactId> - <version>1.6</version> + <version>1.7</version> </dependency> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> - <version>9.1</version> + <version>9.5</version> </dependency> <dependency> <groupId>com.google.jsinterop</groupId> @@ -217,6 +200,18 @@ <connection>scm:git:git@github.com:google/truth.git</connection> <url>scm:git:git@github.com:google/truth.git</url> </scm> + <distributionManagement> + <snapshotRepository> + <id>sonatype-nexus-snapshots</id> + <name>Sonatype Nexus Snapshots</name> + <url>https://oss.sonatype.org/content/repositories/snapshots/</url> + </snapshotRepository> + <repository> + <id>sonatype-nexus-staging</id> + <name>Nexus Release Repository</name> + <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url> + </repository> + </distributionManagement> <build> <pluginManagement> <plugins> @@ -224,24 +219,20 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-site-plugin</artifactId> - <version>3.9.1</version> + <version>3.12.1</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-project-info-reports-plugin</artifactId> - <version>3.1.2</version> + <version>3.4.5</version> </plugin> <plugin> <artifactId>maven-javadoc-plugin</artifactId> - <version>${maven-javadoc-plugin.version}</version> + <version>3.5.0</version> <configuration> <additionalOptions> - <additionalOption>--frames</additionalOption> - <additionalOption>-Xdoclint:-html</additionalOption> + <additionalOption>-Xdoclint:-html ${conditionalJavadoc9PlusOptions}</additionalOption> </additionalOptions> - <additionalJOptions> - <additionalJOption>--no-module-directories</additionalJOption> - </additionalJOptions> <doctitle>Truth ${project.version}</doctitle> <windowtitle>Truth ${project.version}</windowtitle> <quiet>true</quiet> @@ -254,11 +245,10 @@ <links> <!-- TODO(cpovirk): Link to the version that we depend on? --> <link>https://guava.dev/releases/snapshot-jre/api/docs</link> - <link>https://developers.google.com/protocol-buffers/docs/reference/java</link> + <link>https://protobuf.dev/reference/java/api-docs/</link> <link>https://junit.org/junit4/javadoc/latest/</link> <link>https://docs.oracle.com/javase/7/docs/api/</link> </links> - <source>8</source> <sourceFileExcludes> <sourceFileExclude>**/super/**/*.java</sourceFileExclude> </sourceFileExcludes> @@ -266,12 +256,12 @@ </plugin> <plugin> <artifactId>maven-jar-plugin</artifactId> - <version>3.2.0</version> <!-- work around ubuntu bug --> + <version>3.3.0</version> <!-- work around ubuntu bug --> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>animal-sniffer-maven-plugin</artifactId> - <version>1.20</version> + <version>1.23</version> <configuration> <signature> <groupId>org.codehaus.mojo.signature</groupId> @@ -291,61 +281,32 @@ </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> - <version>3.8.1</version> + <version>3.11.0</version> <configuration> - <source>1.7</source> - <target>1.7</target> - <testSource>1.8</testSource> - <testTarget>1.8</testTarget> + <source>1.8</source> + <target>1.8</target> </configuration> </plugin> <plugin> <artifactId>maven-source-plugin</artifactId> - <version>3.2.1</version> + <version>3.3.0</version> + </plugin> + <plugin> + <artifactId>maven-gpg-plugin</artifactId> + <version>3.1.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> - <version>2.22.2</version> + <version>3.1.2</version> </plugin> <plugin> <artifactId>maven-enforcer-plugin</artifactId> - <version>${maven-enforcer-plugin.version}</version> + <version>3.3.0</version> <executions> <execution> <id>enforce</id> <configuration> <rules> - <!-- - Perhaps surprisingly, requireUpperBoundDeps catches problems - that dependencyConvergence does not: If we use - dependencyManagement to force Maven to use an *old* version - of a dependency, that will satisfy dependencyConvergence - (because the version is now consistent), but it will not - satisfy requireUpperBoundDeps, which apparently still sees - the original request for the newer version. - requireUpperBoundDeps's behavior is probably a good thing. - - But, in what seems like a bug, dependencyConvergence catches - certain upper-bound problems that requireUpperBoundDeps does - not. To be clear, it's usually *not* a bug for - dependencyConvergence to give an error when - requireUpperBoundDeps does not: dependencyConvergence is in - some ways a stricter test than requireUpperBoundDeps. What - I'm seeing here is weirder: When I changed liteproto to - request checker-compat-qual 2.1.0 and - error_prone_annotations 2.0.9, both older versions than - those inherited through core Truth, dependencyConvergence - flagged both as expected, but requireUpperBoundDeps flagged - only error_prone_annotations. The reason for this may have - something to do with guava-25.1-android's dependency on the - even older checker-compat-qual 2.0.0: When I updated Truth - to depend on guava-26.0, which depends on - checker-compat-qual 2.5.3, then requireUpperBoundDeps - detected the problem. - - I filed a bug against Maven: - https://issues.apache.org/jira/browse/MENFORCER-316 - --> <requireUpperBoundDeps> <excludes> <!-- We have some deps on guava-android and others on guava-jre. --> @@ -353,19 +314,9 @@ </excludes> </requireUpperBoundDeps> <!-- - This should be a no-op for us, since we try to list - everything in dependencyManagement. But it should at least - make sure that we do remember to put new deps into - dependencyManagement. It might also flag conflicts that - exist only in transitive dependencies. If that becomes too - much of a pain, we can back this check out. - --> - <dependencyConvergence /> - <!-- - Note that neither of these rules would catch a conflict - between, say, java8 and liteproto, since no Truth module - depends on both of those. If we wanted, we could create - such a module. + Note that this rule would not catch a conflict between, say, + java8 and liteproto, since no Truth module depends on both + of those. If we wanted, we could create such a module. --> </rules> </configuration> @@ -387,26 +338,69 @@ </plugin> </plugins> </pluginManagement> - <plugins> - <!-- - Force a version >2.7 for this parent project. If we use the current - default of 2.7, Maven ignores this parent project's configuration when - running maven-javadoc-plugin in children during releases. - --> - <plugin> - <artifactId>maven-javadoc-plugin</artifactId> - <version>${maven-javadoc-plugin.version}</version> - </plugin> - <!-- - Similar. Without this, Maven tries to run maven-enforcer-plugin 1.0, - and it fails to construct an instance of the rule class, apparently - because of a mismatch between the new Maven APIs and the old Enforcer - APIs. - --> - <plugin> - <artifactId>maven-enforcer-plugin</artifactId> - <version>${maven-enforcer-plugin.version}</version> - </plugin> - </plugins> </build> + <profiles> + <profile> + <id>sonatype-oss-release</id> + <build> + <plugins> + <plugin> + <artifactId>maven-gpg-plugin</artifactId> + <executions> + <execution> + <id>sign-artifacts</id> + <phase>verify</phase> + <goals><goal>sign</goal></goals> + </execution> + </executions> + </plugin> + <plugin> + <artifactId>maven-source-plugin</artifactId> + <executions> + <execution> + <id>attach-sources</id> + <goals><goal>jar</goal></goals> + </execution> + </executions> + </plugin> + <plugin> + <artifactId>maven-javadoc-plugin</artifactId> + <executions> + <execution> + <id>attach-docs</id> + <goals><goal>jar</goal></goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + <profile> + <!-- + Passes JDK 11-12-specific `no-module-directories` flag to Javadoc tool, + which is required to make symbol search work correctly in the generated + pages. + + This flag does not exist on 9-10 and 13+ (https://bugs.openjdk.java.net/browse/JDK-8215582). + + Consider removing it once our release and test scripts are migrated to a recent JDK (17+). + --> + <id>javadocs-jdk11-12</id> + <activation> + <jdk>[11,13)</jdk> + </activation> + <properties> + <maven-javadoc-plugin.additionalJOptions>--no-module-directories</maven-javadoc-plugin.additionalJOptions> + </properties> + </profile> + <profile> + <id>javadocs-jdk9plus</id> + <activation> + <jdk>[9,)</jdk> + </activation> + <properties> + <conditionalJavadocOptions>--frames</conditionalJavadocOptions> + </properties> + </profile> + </profiles> </project> diff --git a/refactorings/src/main/java/com/google/common/truth/refactorings/CorrespondenceSubclassToFactoryCall.java b/refactorings/src/main/java/com/google/common/truth/refactorings/CorrespondenceSubclassToFactoryCall.java index 76a85502..c10b24ea 100644 --- a/refactorings/src/main/java/com/google/common/truth/refactorings/CorrespondenceSubclassToFactoryCall.java +++ b/refactorings/src/main/java/com/google/common/truth/refactorings/CorrespondenceSubclassToFactoryCall.java @@ -58,6 +58,7 @@ import com.google.errorprone.bugpatterns.BugChecker; import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher; import com.google.errorprone.fixes.SuggestedFix; import com.google.errorprone.matchers.Description; +import com.google.errorprone.suppliers.Supplier; import com.sun.source.tree.ClassTree; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.ExpressionStatementTree; @@ -80,8 +81,8 @@ import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; -import javax.annotation.Nullable; import javax.lang.model.element.Modifier; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Refactors some subclasses of {@code Correspondence} to instead call {@code Correspondence.from}. @@ -94,6 +95,12 @@ import javax.lang.model.element.Modifier; severity = SUGGESTION) public final class CorrespondenceSubclassToFactoryCall extends BugChecker implements ClassTreeMatcher { + + private static final String CORRESPONDENCE_CLASS = "com.google.common.truth.Correspondence"; + + private static final Supplier<Type> COM_GOOGLE_COMMON_TRUTH_CORRESPONDENCE = + VisitorState.memoize(state -> state.getTypeFromString(CORRESPONDENCE_CLASS)); + @Override public Description matchClass(ClassTree tree, VisitorState state) { if (!isCorrespondence(tree.getExtendsClause(), state)) { @@ -285,7 +292,7 @@ public final class CorrespondenceSubclassToFactoryCall extends BugChecker private ParentType parentType = ParentType.OTHER; @Override - public Void visitMethodInvocation(MethodInvocationTree node, Void unused) { + public @Nullable Void visitMethodInvocation(MethodInvocationTree node, Void unused) { boolean isComparingElementsUsing = Optional.of(node.getMethodSelect()) .filter(t -> t.getKind() == MEMBER_SELECT) @@ -304,7 +311,7 @@ public final class CorrespondenceSubclassToFactoryCall extends BugChecker } @Override - public Void visitNewClass(NewClassTree node, Void unused) { + public @Nullable Void visitNewClass(NewClassTree node, Void unused) { if (getSymbol(node.getIdentifier()).equals(classSymbol)) { calls.put(parentType, node); } @@ -324,7 +331,7 @@ public final class CorrespondenceSubclassToFactoryCall extends BugChecker Set<Tree> references = new HashSet<>(); new TreeScanner<Void, Void>() { @Override - public Void scan(Tree node, Void unused) { + public @Nullable Void scan(Tree node, Void unused) { if (equal(getSymbol(node), classSymbol) && getDeclaredSymbol(node) == null // Don't touch the ClassTree that we're replacing. ) { @@ -334,7 +341,7 @@ public final class CorrespondenceSubclassToFactoryCall extends BugChecker } @Override - public Void visitNewClass(NewClassTree node, Void aVoid) { + public @Nullable Void visitNewClass(NewClassTree node, Void aVoid) { scan(node.getEnclosingExpression(), null); // Do NOT scan node.getIdentifier(). scan(node.getTypeArguments(), null); @@ -445,7 +452,7 @@ public final class CorrespondenceSubclassToFactoryCall extends BugChecker * Converts the given method into a lambda, either expression or block, if "appropriate." For * details about the various cases, see implementation comments. */ - private static Tree maybeMakeLambdaBody(MethodTree compareMethod, VisitorState state) { + private static @Nullable Tree maybeMakeLambdaBody(MethodTree compareMethod, VisitorState state) { ExpressionTree comparison = returnExpression(compareMethod); if (comparison != null) { // compare() is defined as simply `return something;`. Create a lambda. @@ -472,7 +479,7 @@ public final class CorrespondenceSubclassToFactoryCall extends BugChecker boolean[] referenceFound = new boolean[1]; new TreeScanner<Void, Void>() { @Override - public Void scan(Tree node, Void aVoid) { + public @Nullable Void scan(Tree node, Void aVoid) { if (paramsOfEnclosingMethod.contains(getSymbol(node))) { referenceFound[0] = true; } @@ -498,7 +505,8 @@ public final class CorrespondenceSubclassToFactoryCall extends BugChecker } /** Like {@link VisitorState#findEnclosing} but doesn't consider the leaf to enclose itself. */ - private static <T extends Tree> T findStrictlyEnclosing(VisitorState state, Class<T> clazz) { + private static <T extends Tree> @Nullable T findStrictlyEnclosing( + VisitorState state, Class<T> clazz) { return stream(state.getPath().getParentPath()) .filter(clazz::isInstance) .map(clazz::cast) @@ -511,7 +519,7 @@ public final class CorrespondenceSubclassToFactoryCall extends BugChecker * path. For example, if called with {@code ClassTree}, it might return a {@code MethodTree} * inside the class. */ - private static Tree findChildOfStrictlyEnclosing( + private static @Nullable Tree findChildOfStrictlyEnclosing( VisitorState state, Class<? extends Tree> clazz) { Tree previous = state.getPath().getLeaf(); for (Tree t : state.getPath().getParentPath()) { @@ -570,7 +578,7 @@ public final class CorrespondenceSubclassToFactoryCall extends BugChecker abstract Optional<String> supportingMethodDefinition(); } - private static ExpressionTree returnExpression(MethodTree method) { + private static @Nullable ExpressionTree returnExpression(MethodTree method) { List<? extends StatementTree> statements = method.getBody().getStatements(); if (statements.size() != 1) { return null; @@ -646,13 +654,11 @@ public final class CorrespondenceSubclassToFactoryCall extends BugChecker } private static boolean isCorrespondence(Tree supertypeTree, VisitorState state) { - Type correspondenceType = state.getTypeFromString(CORRESPONDENCE_CLASS); + Type correspondenceType = COM_GOOGLE_COMMON_TRUTH_CORRESPONDENCE.get(state); if (correspondenceType == null) { return false; } return supertypeTree != null && state.getTypes().isSameType(getSymbol(supertypeTree).type, correspondenceType); } - - private static final String CORRESPONDENCE_CLASS = "com.google.common.truth.Correspondence"; } diff --git a/refactorings/src/main/java/com/google/common/truth/refactorings/FailWithFacts.java b/refactorings/src/main/java/com/google/common/truth/refactorings/FailWithFacts.java index d47b35cb..30cb5207 100644 --- a/refactorings/src/main/java/com/google/common/truth/refactorings/FailWithFacts.java +++ b/refactorings/src/main/java/com/google/common/truth/refactorings/FailWithFacts.java @@ -40,6 +40,7 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.MethodInvocationTree; import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Migrates Truth subjects from the old {@code fail(String, Object)} to the new {@code @@ -96,7 +97,7 @@ public final class FailWithFacts extends BugChecker implements MethodInvocationT return describeMatch(tree, fix.build()); } - private static String newVerb(String oldVerb) { + private static @Nullable String newVerb(String oldVerb) { List<String> old = Splitter.on(whitespace()).splitToList(oldVerb); String first = old.get(0); if (CAPITAL_LETTER.matchesAnyOf(first)) { diff --git a/refactorings/src/main/java/com/google/common/truth/refactorings/NamedToWithMessage.java b/refactorings/src/main/java/com/google/common/truth/refactorings/NamedToWithMessage.java deleted file mode 100644 index 411ce6dd..00000000 --- a/refactorings/src/main/java/com/google/common/truth/refactorings/NamedToWithMessage.java +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright (c) 2019 Google, Inc. - * - * 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.common.truth.refactorings; - -import static com.google.common.collect.ImmutableSet.toImmutableSet; -import static com.google.common.collect.Iterables.getOnlyElement; -import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION; -import static com.google.errorprone.matchers.Description.NO_MATCH; -import static com.google.errorprone.matchers.Matchers.anyOf; -import static com.google.errorprone.matchers.Matchers.instanceMethod; -import static com.google.errorprone.matchers.Matchers.staticMethod; -import static com.google.errorprone.util.ASTHelpers.getReceiver; -import static com.google.errorprone.util.ASTHelpers.getSymbol; -import static com.google.errorprone.util.ASTHelpers.isSubtype; -import static com.sun.source.tree.Tree.Kind.MEMBER_SELECT; -import static com.sun.source.tree.Tree.Kind.METHOD_INVOCATION; -import static java.lang.String.format; -import static java.util.stream.Stream.concat; - -import com.google.auto.value.AutoValue; -import com.google.common.collect.ImmutableSet; -import com.google.errorprone.BugPattern; -import com.google.errorprone.VisitorState; -import com.google.errorprone.bugpatterns.BugChecker; -import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher; -import com.google.errorprone.fixes.SuggestedFix; -import com.google.errorprone.matchers.Description; -import com.google.errorprone.matchers.Matcher; -import com.sun.source.tree.ExpressionTree; -import com.sun.source.tree.MemberSelectTree; -import com.sun.source.tree.MethodInvocationTree; -import com.sun.tools.javac.code.Symbol.MethodSymbol; -import javax.annotation.Nullable; - -/** - * Migrates assertions from {@code assertThat(...).named(...)} to {@code - * assertWithMessage(...).that(...)} (sometimes with an {@code about} call in there, and sometimes - * with {@code withMessage} if using a {@code FailureStrategy} other than assert). - * - * <p>Alternatively, if setting up the infrastructure to run this migration tool is too costly, - * consider running a Perl-compatible regex search-and-replace like: {@code - * s/assertThat[(](.*)[)]\s*[.]named[(]((?:[^"\n)]|"(?:[^\\\n]|\\.)*")*)[)]/assertWithMessage($2).that($1)/g}. - * Such a search will not handle as many cases as this tool, and it is more likely to produce code - * that does not compile (or that no longer uses custom {@code Subject} classes), but it will handle - * many simple cases. - */ -@BugPattern( - name = "NamedToWithMessage", - summary = "Use assertWithMessage(...)/withMessage(...) instead of the deprecated named(...).", - severity = SUGGESTION) -public final class NamedToWithMessage extends BugChecker implements MethodInvocationTreeMatcher { - @Override - public Description matchMethodInvocation(MethodInvocationTree namedCall, VisitorState state) { - if (!NAMED_METHOD.matches(namedCall, state)) { - return NO_MATCH; - } - MethodInvocationTree thatCall = findThatCall(namedCall, state); - if (thatCall == null) { - return NO_MATCH; - } - ExpressionTree namedReceiver = getReceiver(namedCall); - if (namedReceiver == null) { - return NO_MATCH; - } - String parensAndNamedArgs = - state - .getSourceCode() - .subSequence( - state.getEndPosition(namedCall.getMethodSelect()), state.getEndPosition(namedCall)) - .toString(); - - SuggestedFix.Builder fix = SuggestedFix.builder(); - // We want to do something like the following, but it overlaps with some other changes we make: - // fix.replace(namedCall, state.getSourceForNode(namedReceiver)); - fix.replace(state.getEndPosition(namedReceiver), state.getEndPosition(namedCall), ""); - - if (STANDARD_ASSERT_THAT.matches(thatCall, state)) { - fix.addStaticImport("com.google.common.truth.Truth.assertWithMessage"); - fix.replace( - thatCall.getMethodSelect(), format("assertWithMessage%s.that", parensAndNamedArgs)); - return describeMatch(namedCall, fix.build()); - } - - if (ANY_ASSERT_THAT.matches(thatCall, state)) { - FactoryMethodName factory = tryFindFactory(thatCall, state); - if (factory == null) { - if (ONLY_GENERATE_REFERENCES_TO_FACTORIES_THAT_ALREADY_EXIST) { - return NO_MATCH; - } - - // Guess at a good name for a factory, and rely on the user to create the factory later. - MethodSymbol assertThatSymbol = getSymbol(thatCall); - if (assertThatSymbol == null) { - return NO_MATCH; - } - String factoryMethodEnclosingClass = assertThatSymbol.owner.getQualifiedName().toString(); - // FooSubject -> Foos: - String factoryMethodName = - assertThatSymbol.owner.getSimpleName().toString().replaceFirst("Subject$", "s"); - // Foos -> foos: - factoryMethodName = - factoryMethodName.substring(0, 1).toLowerCase() + factoryMethodName.substring(1); - factory = FactoryMethodName.create(factoryMethodEnclosingClass, factoryMethodName); - } - fix.addStaticImport("com.google.common.truth.Truth.assertWithMessage"); - fix.addStaticImport(factory.clazz() + "." + factory.method()); - fix.replace( - thatCall.getMethodSelect(), - format("assertWithMessage%s.about(%s()).that", parensAndNamedArgs, factory.method())); - return describeMatch(namedCall, fix.build()); - } - - ExpressionTree thatReceiver = getReceiver(thatCall); - if (thatReceiver == null) { - return NO_MATCH; - } - - if (STANDARD_SUBJECT_BUILDER_THAT.matches(thatCall, state)) { - fix.postfixWith(thatReceiver, format(".withMessage%s", parensAndNamedArgs)); - return describeMatch(namedCall, fix.build()); - } - - if (OTHER_SUBJECT_BUILDER_THAT.matches(thatCall, state)) { - if (ASSERT_ABOUT.matches(thatReceiver, state)) { - if (thatReceiver.getKind() != METHOD_INVOCATION) { - return NO_MATCH; - } - ExpressionTree assertAboutSelect = ((MethodInvocationTree) thatReceiver).getMethodSelect(); - - fix.addStaticImport("com.google.common.truth.Truth.assertWithMessage"); - fix.replace(assertAboutSelect, format("assertWithMessage%s.about", parensAndNamedArgs)); - return describeMatch(namedCall, fix.build()); - } - - if (STANDARD_SUBJECT_BUILDER_ABOUT.matches(thatReceiver, state)) { - ExpressionTree aboutReceiver = getReceiver(thatReceiver); - if (aboutReceiver == null) { - return NO_MATCH; - } - - fix.postfixWith(aboutReceiver, format(".withMessage%s", parensAndNamedArgs)); - return describeMatch(namedCall, fix.build()); - } - } - - return NO_MATCH; - } - - @AutoValue - abstract static class FactoryMethodName { - static FactoryMethodName create(String clazz, String method) { - return new AutoValue_NamedToWithMessage_FactoryMethodName(clazz, method); - } - - static FactoryMethodName tryCreate(MethodSymbol symbol) { - return symbol.params.isEmpty() - ? create(symbol.owner.getQualifiedName().toString(), symbol.getSimpleName().toString()) - : null; - } - - abstract String clazz(); - - abstract String method(); - } - - @Nullable - private static FactoryMethodName tryFindFactory( - MethodInvocationTree assertThatCall, VisitorState state) { - MethodSymbol assertThatSymbol = getSymbol(assertThatCall); - if (assertThatSymbol == null) { - return null; - } - /* - * First, a special case for ProtoTruth.protos(). Usually the main case below finds it OK, but - * sometimes it misses it, I believe because it can't decide between that and - * IterableOfProtosSubject.iterableOfMessages. - */ - if (assertThatSymbol.owner.getQualifiedName().contentEquals(PROTO_TRUTH_CLASS)) { - return FactoryMethodName.create(PROTO_TRUTH_CLASS, "protos"); - } - ImmutableSet<MethodSymbol> factories = - concat( - // The class that assertThat is declared in: - assertThatSymbol.owner.getEnclosedElements().stream(), - // The Subject class (possibly the same; if so, toImmutableSet() will deduplicate): - assertThatSymbol.getReturnType().asElement().getEnclosedElements().stream()) - .filter(s -> s instanceof MethodSymbol) - .map(s -> (MethodSymbol) s) - .filter( - s -> - returns(s, SUBJECT_FACTORY_CLASS, state) - || returns(s, CUSTOM_SUBJECT_BUILDER_FACTORY_CLASS, state)) - .collect(toImmutableSet()); - return factories.size() == 1 ? FactoryMethodName.tryCreate(getOnlyElement(factories)) : null; - // TODO(cpovirk): If multiple factories exist, try filtering to visible ones only. - } - - private static boolean returns(MethodSymbol symbol, String returnType, VisitorState state) { - return isSubtype(symbol.getReturnType(), state.getTypeFromString(returnType), state); - } - - private static MethodInvocationTree findThatCall(MethodInvocationTree tree, VisitorState state) { - while (true) { - if (tree.getMethodSelect().getKind() != MEMBER_SELECT) { - return null; - } - MemberSelectTree methodSelect = (MemberSelectTree) tree.getMethodSelect(); - if (methodSelect.getExpression().getKind() != METHOD_INVOCATION) { - return null; - } - tree = (MethodInvocationTree) methodSelect.getExpression(); - if (ANY_ASSERT_THAT.matches(tree, state) - || STANDARD_SUBJECT_BUILDER_THAT.matches(tree, state) - || OTHER_SUBJECT_BUILDER_THAT.matches(tree, state)) { - return tree; - } - } - } - - private static final String TRUTH_CLASS = "com.google.common.truth.Truth"; - private static final String PROTO_TRUTH_CLASS = - "com.google.common.truth.extensions.proto.ProtoTruth"; - private static final String SUBJECT_CLASS = "com.google.common.truth.Subject"; - private static final String SUBJECT_FACTORY_CLASS = "com.google.common.truth.Subject.Factory"; - private static final String CUSTOM_SUBJECT_BUILDER_FACTORY_CLASS = - "com.google.common.truth.CustomSubjectBuilder.Factory"; - private static final String STANDARD_SUBJECT_BUILDER_CLASS = - "com.google.common.truth.StandardSubjectBuilder"; - private static final String CUSTOM_SUBJECT_BUILDER_CLASS = - "com.google.common.truth.CustomSubjectBuilder"; - private static final String SIMPLE_SUBJECT_BUILDER_CLASS = - "com.google.common.truth.SimpleSubjectBuilder"; - - private static final Matcher<ExpressionTree> STANDARD_ASSERT_THAT = - staticMethod().onClass(TRUTH_CLASS).named("assertThat"); - private static final Matcher<ExpressionTree> ANY_ASSERT_THAT = - staticMethod().anyClass().named("assertThat"); - private static final Matcher<ExpressionTree> ASSERT_ABOUT = - staticMethod().onClass(TRUTH_CLASS).named("assertAbout"); - - private static final Matcher<ExpressionTree> STANDARD_SUBJECT_BUILDER_THAT = - instanceMethod().onDescendantOf(STANDARD_SUBJECT_BUILDER_CLASS).named("that"); - private static final Matcher<ExpressionTree> STANDARD_SUBJECT_BUILDER_ABOUT = - instanceMethod().onDescendantOf(STANDARD_SUBJECT_BUILDER_CLASS).named("about"); - private static final Matcher<ExpressionTree> OTHER_SUBJECT_BUILDER_THAT = - anyOf( - instanceMethod().onDescendantOf(CUSTOM_SUBJECT_BUILDER_CLASS).named("that"), - instanceMethod().onDescendantOf(SIMPLE_SUBJECT_BUILDER_CLASS).named("that")); - private static final Matcher<ExpressionTree> NAMED_METHOD = - instanceMethod().onDescendantOf(SUBJECT_CLASS).named("named"); - - // TODO(cpovirk): Provide a flag for this. - private static final boolean ONLY_GENERATE_REFERENCES_TO_FACTORIES_THAT_ALREADY_EXIST = true; -} diff --git a/refactorings/src/main/java/com/google/common/truth/refactorings/StoreActualValueInField.java b/refactorings/src/main/java/com/google/common/truth/refactorings/StoreActualValueInField.java deleted file mode 100644 index ea2e7e21..00000000 --- a/refactorings/src/main/java/com/google/common/truth/refactorings/StoreActualValueInField.java +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright (c) 2019 Google, Inc. - * - * 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.common.truth.refactorings; - -import static com.google.common.collect.ImmutableSet.toImmutableSet; -import static com.google.common.collect.Iterables.getOnlyElement; -import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION; -import static com.google.errorprone.fixes.SuggestedFix.replace; -import static com.google.errorprone.matchers.Description.NO_MATCH; -import static com.google.errorprone.matchers.Matchers.anyOf; -import static com.google.errorprone.matchers.Matchers.constructor; -import static com.google.errorprone.matchers.Matchers.instanceMethod; -import static com.google.errorprone.matchers.Matchers.staticMethod; -import static com.google.errorprone.util.ASTHelpers.getType; -import static com.google.errorprone.util.ASTHelpers.isSameType; -import static com.google.errorprone.util.ASTHelpers.isSubtype; -import static com.sun.source.tree.Tree.Kind.CLASS; -import static com.sun.source.tree.Tree.Kind.IDENTIFIER; -import static com.sun.source.tree.Tree.Kind.MEMBER_SELECT; -import static com.sun.source.tree.Tree.Kind.METHOD_INVOCATION; -import static com.sun.source.tree.Tree.Kind.VARIABLE; -import static java.lang.String.format; -import static javax.lang.model.element.Modifier.STATIC; - -import com.google.common.collect.ImmutableSet; -import com.google.errorprone.BugPattern; -import com.google.errorprone.VisitorState; -import com.google.errorprone.bugpatterns.BugChecker; -import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher; -import com.google.errorprone.fixes.SuggestedFix; -import com.google.errorprone.matchers.Description; -import com.google.errorprone.matchers.Matcher; -import com.sun.source.tree.ClassTree; -import com.sun.source.tree.ExpressionTree; -import com.sun.source.tree.IdentifierTree; -import com.sun.source.tree.MemberSelectTree; -import com.sun.source.tree.MethodInvocationTree; -import com.sun.source.tree.MethodTree; -import com.sun.source.tree.Tree; -import com.sun.source.tree.VariableTree; -import com.sun.source.util.TreePath; -import com.sun.tools.javac.code.Symbol; -import com.sun.tools.javac.code.Type; -import com.sun.tools.javac.code.Types; -import com.sun.tools.javac.tree.JCTree; -import com.sun.tools.javac.tree.JCTree.JCVariableDecl; -import com.sun.tools.javac.tree.TreeScanner; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Stream; -import javax.lang.model.element.Name; - -/** - * Refactors callers of {@code Subject.actual()} and {@code Subject.getSubject()} to store their own - * copy of the actual value in a variable and use that instead. - */ -@BugPattern( - name = "StoreActualValueInField", - summary = - "Store the actual value locally instead of using the deprecated actual() and getSubject().", - severity = SUGGESTION) -public final class StoreActualValueInField extends BugChecker - implements MethodInvocationTreeMatcher { - @Override - public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { - ClassTree enclosingClass = state.findEnclosing(ClassTree.class); - if (enclosingClass == null) { - return NO_MATCH; - } - if (enclosingClass.getMembers().stream() - .filter(t -> t.getKind() == VARIABLE) - .map(t -> (VariableTree) t) - .anyMatch(t -> t.getName().contentEquals("actual"))) { - return NO_MATCH; - } - - if (ACTUAL_METHOD.matches(tree, state)) { - if (tree.getMethodSelect().getKind() == IDENTIFIER) { - if (varNamedActualInScope(state)) { - return describeMatch(tree, replace(tree, qualifierForThis(state) + "this.actual")); - } else { - return describeMatch(tree, replace(tree, "actual")); - } - } else if (tree.getMethodSelect().getKind() == MEMBER_SELECT) { - MemberSelectTree methodSelect = (MemberSelectTree) tree.getMethodSelect(); - return describeMatch( - tree, - replace( - tree, format("%s.actual", state.getSourceForNode(methodSelect.getExpression())))); - } else { - return NO_MATCH; - } - } - - if (!SUBJECT_CONSTRUCTOR_CALL.matches(tree, state)) { - return NO_MATCH; - } - if (tree.getMethodSelect().getKind() != IDENTIFIER - || !((IdentifierTree) tree.getMethodSelect()).getName().contentEquals("super")) { - return NO_MATCH; - } - IdentifierTree value = findActualArg(tree.getArguments(), state); - if (value == null) { - return NO_MATCH; - } - /* - * TODO(cpovirk): Before adding the field, scan the compilation unit for any usages of - * ThisType.actual(). (But this is moderately rare and usually easy to detect after the fact. - * Plus, adding the field in all cases is harmless enough.) - */ - SuggestedFix.Builder fix = SuggestedFix.builder(); - fix.postfixWith( - state.getPath().getParentPath().getLeaf(), - format("this.actual = %s;", state.getSourceForNode(value))); - - Tree type = findActualFormalType(value.getName(), state); - Tree putFieldBefore = - enclosingClass.getMembers().stream() - .map((Tree t) -> t) // Stream<? extends Tree> -> Stream<Tree> - .filter( - t -> - t.getKind() == VARIABLE - && !((VariableTree) t).getModifiers().getFlags().contains(STATIC)) - .findFirst() - .orElse(state.findEnclosing(MethodTree.class)); - fix.prefixWith( - putFieldBefore, format("private final %s actual;", state.getSourceForNode(type))); - - return describeMatch(tree, fix.build()); - } - - private static String qualifierForThis(VisitorState state) { - Type subjectBaseType = state.getTypeFromString("com.google.common.truth.Subject"); - - boolean seenClassInBetween = false; - for (Tree t : state.getPath()) { - if (t.getKind() != CLASS) { - continue; - } - Type enclosingType = getType(t); - if (isSubtype(enclosingType, subjectBaseType, state)) { - if (seenClassInBetween) { - return enclosingType.asElement().getSimpleName() + "."; - } else { - return ""; - } - } - seenClassInBetween = true; - } - return ""; // not sure what's going on, so let's try this - } - - // from an old copy of RenameField (Similar code now lives in FieldRenamer.) - private static boolean varNamedActualInScope(VisitorState state) { - final AtomicBoolean local = new AtomicBoolean(false); - - MethodTree outerMostMethod = null; - for (TreePath path = state.getPath(); path != null; path = path.getParentPath()) { - if (path.getLeaf() instanceof MethodTree) { - outerMostMethod = (MethodTree) path.getLeaf(); - } - } - if (outerMostMethod != null && outerMostMethod.getBody() != null) { - ((JCTree) outerMostMethod.getBody()) - .accept( - new TreeScanner() { - @Override - public void visitVarDef(JCVariableDecl tree) { - if (tree.getName().contentEquals("actual")) { - local.set(true); - } - super.visitVarDef(tree); - } - }); - } - return local.get(); - } - - // from AbstractCollectionIncompatibleTypeMatcher - private static Type extractTypeArgAsMemberOfSupertype( - Type type, Symbol superTypeSym, int typeArgIndex, Types types) { - Type collectionType = types.asSuper(type, superTypeSym); - if (collectionType == null) { - return null; - } - com.sun.tools.javac.util.List<Type> tyargs = collectionType.getTypeArguments(); - if (tyargs.size() <= typeArgIndex) { - // Collection is raw, nothing we can do. - return null; - } - - return tyargs.get(typeArgIndex); - } - - private static IdentifierTree findActualArg( - List<? extends ExpressionTree> args, VisitorState state) { - Type actualType = - extractTypeArgAsMemberOfSupertype( - getType(state.findEnclosing(ClassTree.class)), - state.getSymbolFromString("com.google.common.truth.Subject"), - 1, - state.getTypes()); - Type failureMetadataType = state.getTypeFromString("com.google.common.truth.FailureMetadata"); - ImmutableSet<IdentifierTree> candidates = - args.stream() - .flatMap(a -> maybeToIdentifier(a, state)) - .filter(a -> !isSameType(getType(a), failureMetadataType, state)) - .filter(a -> isSubtype(getType(a), actualType, state)) - .collect(toImmutableSet()); - if (candidates.size() == 1) { - return getOnlyElement(candidates); - } - - if (args.size() == 2 - && isSameType(getType(args.get(0)), failureMetadataType, state) - && args.get(1).getKind() == IDENTIFIER) { - return (IdentifierTree) args.get(1); - } - return null; - } - - private static Stream<IdentifierTree> maybeToIdentifier(ExpressionTree tree, VisitorState state) { - if (tree.getKind() == IDENTIFIER) { - return Stream.of((IdentifierTree) tree); - } else if (tree.getKind() == METHOD_INVOCATION && CHECK_NOT_NULL.matches(tree, state)) { - /* - * checkNotNull() is inadvisable (since it makes assertThat(foo) throw NPE for that type, even - * if the assertion is going to be something like isNull()). But people do it, albeit rarely. - */ - MethodInvocationTree invocation = (MethodInvocationTree) tree; - return maybeToIdentifier(invocation.getArguments().get(0), state); - } else { - return Stream.empty(); - } - } - - private static Tree findActualFormalType(Name name, VisitorState state) { - MethodTree method = state.findEnclosing(MethodTree.class); - if (method == null) { - return null; - } - return method.getParameters().stream() - .filter(p -> p.getName().equals(name)) - .findFirst() - .map(p -> p.getType()) - .orElse(null); - } - - private static final Matcher<ExpressionTree> SUBJECT_CONSTRUCTOR_CALL = - constructor() - .forClass( - (type, state) -> - isSubtype( - type, state.getTypeFromString("com.google.common.truth.Subject"), state)); - private static final Matcher<ExpressionTree> ACTUAL_METHOD = - anyOf( - instanceMethod() - .onDescendantOf("com.google.common.truth.Subject") - .named("actual") - .withParameters(), - instanceMethod() - .onDescendantOf("com.google.common.truth.Subject") - .named("getSubject") - .withParameters()); - private static final Matcher<ExpressionTree> CHECK_NOT_NULL = - staticMethod().onClass("com.google.common.base.Preconditions").named("checkNotNull"); -} diff --git a/util/generate-latest-docs.sh b/util/generate-latest-docs.sh index 6fc96f36..3e6cfcd5 100755 --- a/util/generate-latest-docs.sh +++ b/util/generate-latest-docs.sh @@ -20,10 +20,12 @@ if [ -n "${RELEASE_VERSION:-}" ]; then # Release version_subdir=api/${RELEASE_VERSION} commit_message="Release $RELEASE_VERSION javadoc pushed to gh-pages." + github_url="git@github.com:google/truth.git" else # CI version_subdir=api/latest commit_message="Latest javadoc on successful CI build auto-pushed to gh-pages." + github_url="https://x-access-token:${GITHUB_TOKEN}@github.com/google/truth.git" fi mvn javadoc:aggregate @@ -32,7 +34,7 @@ find target/site/apidocs -name '*.html' | xargs perl -077pi -e 's#<li class="blo target_dir="$(pwd)/target" cd ${target_dir} rm -rf gh-pages -git clone --quiet --branch=gh-pages "https://x-access-token:${GITHUB_TOKEN}@github.com/google/truth.git" gh-pages > /dev/null +git clone --quiet --branch=gh-pages "${github_url}" gh-pages > /dev/null cd gh-pages if [[ -z "${RELEASE_VERSION:-}" ]]; then diff --git a/util/mvn-deploy.sh b/util/mvn-deploy.sh deleted file mode 100755 index 30f93cb8..00000000 --- a/util/mvn-deploy.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash -keys="$(gpg --list-keys | grep ^pub | sed 's#/# #' | awk '{ print $3 }')" -key_count="$(echo ${keys} | wc -w)" - -seen="" -while [[ $# > 0 ]] ; do - param="$1" - if [[ $param == "--signing-key" ]]; then - # disambiguating or overriding key - key="$2" - shift - else - seen="${seen} ${param}" - fi - shift -done -params=${seen} - -if [[ ${key_count} -lt 1 ]]; then - echo "" - echo "You are attempting to deploy a maven release without a GPG signing key." - echo "You need to generate a signing key in accordance with the instructions" - echo "found at http://blog.sonatype.com/2010/01/how-to-generate-pgp-signatures-with-maven" - exit 1 -fi - -# if a key is specified, use that, else use the default, unless there are many -if [[ -n "${key}" ]]; then - #validate key - keystatus=$(gpg --list-keys | grep ${key} | awk '{print $1}') - if [ "${keystatus}" != "pub" ]; then - echo "" - echo "Could not find public key with label \"${key}\"" - echo "" - echo "Available keys from: " - gpg --list-keys | grep --invert-match '^sub' - exit 1 - fi - - key_param="-Dgpg.keyname=${key}" -elif [ ${key_count} -gt 1 ]; then - echo "" - echo "You are attempting to deploy a maven release but have more than one GPG" - echo "signing key and did not specify which one you wish to sign with." - echo "" - echo "usage $0 [--signing-key <ssl-key>] [<maven params> ...]" - echo "" - echo -n "Available keys from: " - gpg --list-keys | grep --invert-match '^sub' - exit 1; -fi - -mvn ${params} clean site:jar -P sonatype-oss-release ${key_param} deploy |