diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b76b89570..8519eb9ab 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,8 +3,18 @@ updates: - package-ecosystem: "maven" directory: "/" schedule: - interval: "daily" + interval: "weekly" + groups: + dependencies: + applies-to: version-updates + patterns: + - "*" - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + interval: "weekly" + groups: + github-actions: + applies-to: version-updates + patterns: + - "*" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a814d65a..21b3781cf 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.11.0 + uses: styfle/cancel-workflow-action@85880fa0301c86cca9da44039ee3bb12d3bedbfa with: access_token: ${{ github.token }} - name: 'Check out repository' - uses: actions/checkout@v3 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: 'Cache local Maven repository' - uses: actions/cache@v3 + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 with: path: ~/.m2/repository key: maven-${{ hashFiles('**/pom.xml') }} restore-keys: | maven- - name: 'Set up JDK ${{ matrix.java }}' - uses: actions/setup-java@v3 + uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 with: java-version: ${{ matrix.java }} distribution: 'zulu' @@ -52,16 +52,16 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Check out repository' - uses: actions/checkout@v3 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: 'Cache local Maven repository' - uses: actions/cache@v3 + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 with: path: ~/.m2/repository key: maven-${{ hashFiles('**/pom.xml') }} restore-keys: | maven- - name: 'Set up JDK 11' - uses: actions/setup-java@v3 + uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 with: java-version: 11 distribution: 'zulu' @@ -81,16 +81,16 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Check out repository' - uses: actions/checkout@v3 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: 'Cache local Maven repository' - uses: actions/cache@v3 + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 with: path: ~/.m2/repository key: maven-${{ hashFiles('**/pom.xml') }} restore-keys: | maven- - name: 'Set up JDK 11' - uses: actions/setup-java@v3 + uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 with: java-version: 11 distribution: 'zulu' diff --git a/core/pom.xml b/core/pom.xml index a30e84e1f..747a8cfed 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -15,8 +15,8 @@ guava - org.checkerframework - checker-qual + org.jspecify + jspecify junit @@ -24,7 +24,7 @@ - com.google.gwt + org.gwtproject gwt-user test @@ -61,6 +61,13 @@ **/*.gwt.xml + + .. + + LICENSE + + META-INF + src/test/java @@ -105,18 +112,7 @@ jar gwt - src/main/java - - **/*.java - **/*.gwt.xml - - - com/google/common/truth/ClassSubject.java - com/google/common/truth/Expect.java - com/google/common/truth/IteratingVerb.java - com/google/common/truth/ReflectionUtil.java - com/google/common/truth/codegen/** - + ${project.build.directory}/gwt-sources @@ -142,7 +138,7 @@ test htmlunit - FF38 + FF true -Xms3500m -Xmx3500m -Xss1024k @@ -156,6 +152,74 @@ + + + org.codehaus.mojo + build-helper-maven-plugin + 3.6.0 + + + add-source + generate-sources + + add-source + + + + ${project.build.directory}/gwt-sources + + + + + + + maven-antrun-plugin + 3.1.0 + + + copy-gwt-files + generate-sources + run + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -163,7 +227,7 @@ org.apache.maven.plugins maven-project-info-reports-plugin - 3.4.2 + 3.6.1 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 d3b01cff6..a08863a9a 100644 --- a/core/src/main/java/com/google/common/truth/AbstractArraySubject.java +++ b/core/src/main/java/com/google/common/truth/AbstractArraySubject.java @@ -16,10 +16,11 @@ 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; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * A common supertype for Array subjects, abstracting some common display and error infrastructure. @@ -27,7 +28,7 @@ * @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) { @@ -60,6 +61,6 @@ public final void hasLength(int 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 31dfd24a2..d90203c63 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 @@ 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,13 +26,14 @@ 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 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.jspecify.annotations.Nullable; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Handle; @@ -66,6 +68,7 @@ * this is all best-effort. */ @GwtIncompatible +@J2ktIncompatible final class ActualValueInference { /** Call {@link Platform#inferDescription} rather than calling this directly. */ static @Nullable String describeActualValue(String className, String methodName, int lineNumber) { @@ -90,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 actualsAtLine = visitor.actualValueAtLine.build().get(lineNumber); /* * It's very unlikely that more than one assertion would happen on the same line _but with @@ -150,6 +153,7 @@ StackEntry actualValue() { @AutoValue @CopyAnnotations @GwtIncompatible + @J2ktIncompatible abstract static class OpaqueEntry extends StackEntry { @Override public final String toString() { @@ -169,6 +173,7 @@ private static StackEntry opaque(InferredType type) { @AutoValue @CopyAnnotations @GwtIncompatible + @J2ktIncompatible abstract static class DescribedEntry extends StackEntry { @Override abstract String description(); @@ -194,6 +199,7 @@ private static StackEntry described(InferredType type, String description) { @AutoValue @CopyAnnotations @GwtIncompatible + @J2ktIncompatible abstract static class SubjectEntry extends StackEntry { @Override abstract StackEntry actualValue(); @@ -735,7 +741,7 @@ public void visitFieldInsn(int opcode, String owner, String name, String desc) { @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { - if (opcode == Opcodes.INVOKESPECIAL && "".equals(name)) { + if (opcode == Opcodes.INVOKESPECIAL && name.equals("")) { int argumentSize = (Type.getArgumentsAndReturnSizes(desc) >> 2); InferredType receiverType = getOperandFromTop(argumentSize - 1).type(); if (receiverType.isUninitialized()) { @@ -959,7 +965,7 @@ private void replaceUninitializedTypeInStack(InferredType oldType, InferredType } private void pushDescriptor(String desc) { - pushDescriptorAndMaybeProcessMethodCall(desc, /*invocation=*/ null); + pushDescriptorAndMaybeProcessMethodCall(desc, /* invocation= */ null); } /** @@ -974,9 +980,11 @@ private void pushDescriptor(String desc) { * @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; @@ -1011,7 +1019,8 @@ private void pushDescriptorAndMaybeProcessMethodCall(String desc, Invocation inv } } - 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)); } @@ -1032,10 +1041,11 @@ private StackEntry pop(int count) { 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; } @@ -1075,7 +1085,7 @@ private void setLocalVariable(int index, StackEntry entry) { } private StackEntry top() { - return operandStack.get(operandStack.size() - 1); + return Iterables.getLast(operandStack); } /** @@ -1220,6 +1230,7 @@ private ImmutableList convertTypesInStackMapFrame(int size, Object[] @AutoValue @CopyAnnotations @GwtIncompatible + @J2ktIncompatible abstract static class FrameInfo { static FrameInfo create(ImmutableList locals, ImmutableList stack) { @@ -1235,24 +1246,22 @@ static FrameInfo create(ImmutableList locals, ImmutableList facts; - /** Separate cause field, in case initCause() fails. */ - private final @Nullable Throwable cause; - AssertionErrorWithFacts( ImmutableList messages, ImmutableList facts, @Nullable Throwable cause) { - super(makeMessage(messages, facts)); + super(makeMessage(messages, facts), cause); this.facts = checkNotNull(facts); - - this.cause = cause; - try { - initCause(cause); - } catch (IllegalStateException alreadyInitializedBecauseOfHarmonyBug) { - // See Truth.SimpleAssertionError. - } - } - - @Override - @SuppressWarnings("UnsynchronizedOverridesSynchronized") - public 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 64cf82369..c98fa1cfe 100644 --- a/core/src/main/java/com/google/common/truth/BigDecimalSubject.java +++ b/core/src/main/java/com/google/common/truth/BigDecimalSubject.java @@ -15,11 +15,12 @@ */ 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; import java.math.BigDecimal; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Propositions for {@link BigDecimal} typed subjects. @@ -27,7 +28,7 @@ * @author Kurt Alfred Kluever */ public final class BigDecimalSubject extends ComparableSubject { - private final BigDecimal actual; + private final @Nullable BigDecimal actual; BigDecimalSubject(FailureMetadata metadata, @Nullable BigDecimal actual) { super(metadata, actual); @@ -88,12 +89,12 @@ public void isEqualTo(@Nullable Object expected) { * #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 528cbc626..ad0e17ba7 100644 --- a/core/src/main/java/com/google/common/truth/BooleanSubject.java +++ b/core/src/main/java/com/google/common/truth/BooleanSubject.java @@ -17,7 +17,7 @@ import static com.google.common.truth.Fact.simpleFact; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Propositions for boolean subjects. @@ -25,7 +25,7 @@ * @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 a2ca5c487..24f6283ce 100644 --- a/core/src/main/java/com/google/common/truth/ClassSubject.java +++ b/core/src/main/java/com/google/common/truth/ClassSubject.java @@ -15,8 +15,10 @@ */ 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; +import org.jspecify.annotations.Nullable; /** * Propositions for {@link Class} subjects. @@ -24,8 +26,9 @@ * @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 ed769734f..0d766ec1c 100644 --- a/core/src/main/java/com/google/common/truth/ComparableSubject.java +++ b/core/src/main/java/com/google/common/truth/ComparableSubject.java @@ -15,8 +15,10 @@ */ 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; +import org.jspecify.annotations.Nullable; /** * Propositions for {@link Comparable} typed subjects. @@ -24,12 +26,13 @@ * @author Kurt Alfred Kluever * @param the type of the object being tested by this {@code ComparableSubject} */ -public abstract class ComparableSubject extends Subject { +// TODO(b/136040841): Consider further tightening this to the proper `extends Comparable` +public abstract class ComparableSubject> 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 @@ protected ComparableSubject(FailureMetadata metadata, @Nullable T actual) { /** Checks that the subject is in {@code range}. */ public final void isIn(Range range) { - if (!range.contains(actual)) { + if (!range.contains(checkNotNull(actual))) { failWithActual("expected to be in range", range); } } /** Checks that the subject is not in {@code range}. */ public final void isNotIn(Range range) { - if (range.contains(actual)) { + if (range.contains(checkNotNull(actual))) { failWithActual("expected not to be in range", range); } } @@ -57,8 +60,9 @@ public final void isNotIn(Range range) { *

Note: 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) actual).compareTo(checkNotNull(expected)) != 0) { failWithActual("expected value that sorts equal to", expected); } } @@ -69,8 +73,9 @@ public void isEquivalentAccordingToCompareTo(T expected) { *

To check that the subject is greater than or equal to {@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) actual).compareTo(checkNotNull(other)) <= 0) { failWithActual("expected to be greater than", other); } } @@ -81,8 +86,9 @@ public final void isGreaterThan(T other) { *

To check that the subject is less than or equal to {@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) actual).compareTo(checkNotNull(other)) >= 0) { failWithActual("expected to be less than", other); } } @@ -93,8 +99,9 @@ public final void isLessThan(T other) { *

To check that the subject is strictly 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) actual).compareTo(checkNotNull(other)) > 0) { failWithActual("expected to be at most", other); } } @@ -105,8 +112,9 @@ public final void isAtMost(T other) { *

To check that the subject is strictly 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) 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 47608db2c..88d031c81 100644 --- a/core/src/main/java/com/google/common/truth/ComparisonFailureWithFacts.java +++ b/core/src/main/java/com/google/common/truth/ComparisonFailureWithFacts.java @@ -21,7 +21,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.truth.Platform.PlatformComparisonFailure; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * An {@link AssertionError} (usually a JUnit {@code ComparisonFailure}, but not under GWT) composed diff --git a/core/src/main/java/com/google/common/truth/ComparisonFailures.java b/core/src/main/java/com/google/common/truth/ComparisonFailures.java index 1e50f49c5..af7ad300c 100644 --- a/core/src/main/java/com/google/common/truth/ComparisonFailures.java +++ b/core/src/main/java/com/google/common/truth/ComparisonFailures.java @@ -26,7 +26,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Contains part of the code responsible for creating a JUnit {@code ComparisonFailure} (if 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 ae2b4bc97..5e5453b10 100644 --- a/core/src/main/java/com/google/common/truth/Correspondence.java +++ b/core/src/main/java/com/google/common/truth/Correspondence.java @@ -22,7 +22,7 @@ import static com.google.common.truth.Fact.fact; import static com.google.common.truth.Fact.simpleFact; import static com.google.common.truth.Platform.getStackTraceAsString; -import static java.util.Arrays.asList; +import static com.google.common.truth.SubjectUtils.asList; import com.google.common.base.Function; import com.google.common.base.Joiner; @@ -31,7 +31,7 @@ import com.google.common.collect.ImmutableList; import java.util.Arrays; import java.util.List; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Determines whether an instance of type {@code A} corresponds in some way to an instance of type @@ -65,7 +65,7 @@ * * @author Pete Gillin */ -public abstract class Correspondence { +public abstract class Correspondence { /** * Constructs a {@link Correspondence} that compares actual and expected elements using the given @@ -112,7 +112,7 @@ public abstract class Correspondence { * is an element that "}, e.g. * {@code "contains"}, {@code "is an instance of"}, or {@code "is equivalent to"} */ - public static Correspondence from( + public static Correspondence from( BinaryPredicate predicate, String description) { return new FromBinaryPredicate<>(predicate, description); } @@ -126,7 +126,7 @@ public static Correspondence from( * you should almost never see {@code BinaryPredicate} used as the type of a field or variable, or * a return type. */ - public interface BinaryPredicate { + public interface BinaryPredicate { /** * Returns whether or not the actual and expected values satisfy the condition defined by this @@ -135,7 +135,9 @@ public interface BinaryPredicate { boolean apply(A actual, E expected); } - private static final class FromBinaryPredicate extends Correspondence { + private static final class FromBinaryPredicate< + A extends @Nullable Object, E extends @Nullable Object> + extends Correspondence { private final BinaryPredicate predicate; private final String description; @@ -191,8 +193,9 @@ public String toString() { * is an element that "}, e.g. * {@code "has an ID of"} */ - public static Correspondence transforming( - Function actualTransform, String description) { + public static + Correspondence transforming( + Function actualTransform, String description) { return new Transforming<>(actualTransform, identity(), description); } @@ -238,12 +241,14 @@ public static Correspondence transforming( * is an element that "}, e.g. * {@code "has the same ID as"} */ - public static Correspondence transforming( - Function actualTransform, Function expectedTransform, String description) { + public static + Correspondence transforming( + Function actualTransform, Function expectedTransform, String description) { return new Transforming<>(actualTransform, expectedTransform, description); } - private static final class Transforming extends Correspondence { + private static final class Transforming + extends Correspondence { private final Function actualTransform; private final Function expectedTransform; @@ -318,13 +323,13 @@ public String toString() { * diff-formatting functionality to it. See e.g. {@link IterableSubject#formattingDiffsUsing}. */ @SuppressWarnings("unchecked") // safe covariant cast - static Correspondence equality() { + static Correspondence equality() { return (Equality) Equality.INSTANCE; } - private static final class Equality extends Correspondence { + private static final class Equality extends Correspondence { - private static final Equality INSTANCE = new Equality<>(); + private static final Equality<@Nullable Object> INSTANCE = new Equality<>(); @Override public boolean compare(T actual, T expected) { @@ -395,7 +400,7 @@ public Correspondence formattingDiffsUsing(DiffFormatter { + public interface DiffFormatter { /** * Returns a {@link String} describing the difference between the {@code actual} and {@code @@ -405,7 +410,8 @@ public interface DiffFormatter { String formatDiff(A actual, E expected); } - private static class FormattingDiffs extends Correspondence { + private static class FormattingDiffs + extends Correspondence { private final Correspondence delegate; private final DiffFormatter formatter; @@ -525,9 +531,10 @@ private static class StoredException { private final Exception exception; private final String methodName; - private final List methodArguments; + private final List<@Nullable Object> methodArguments; - StoredException(Exception exception, String methodName, List 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 @@ private String describe() { 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 @@ private ExceptionStore(String argumentLabel) { * 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 @@ void addCompareException( * @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 @@ void addActualKeyFunctionException(Class callingClass, Exception exception, O * 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 @@ void addExpectedKeyFunctionException( * 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 = 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 43248a72d..b8b4122e5 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 stringList = new ArrayList<>(); // A map to record each unique string and its incremental id. private final Map 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 unifiedDiffType = new ArrayList<>(); private final List unifiedDiffContentId = new ArrayList<>(); private final List 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 a198bff81..f1a87351f 100644 --- a/core/src/main/java/com/google/common/truth/DoubleSubject.java +++ b/core/src/main/java/com/google/common/truth/DoubleSubject.java @@ -26,7 +26,7 @@ import static java.lang.Double.NaN; import static java.lang.Double.doubleToLongBits; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Propositions for {@link Double} subjects. @@ -36,7 +36,7 @@ public final class DoubleSubject extends ComparableSubject { 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 int hashCode() { * 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 void of(double expected) { * 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 void isNotEqualTo(@Nullable Object other) { */ @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 5acc31d97..8577c14ff 100644 --- a/core/src/main/java/com/google/common/truth/Expect.java +++ b/core/src/main/java/com/google/common/truth/Expect.java @@ -30,7 +30,7 @@ import com.google.errorprone.annotations.concurrent.GuardedBy; import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; import org.junit.internal.AssumptionViolatedException; import org.junit.rules.ErrorCollector; import org.junit.rules.TestRule; @@ -73,7 +73,7 @@ * by a method like {@code executor.submit(...)}. It might also include checking for * unexpected log messages * or reading metrics that count failures.) If your tests already check for exceptions from a - * thread, then that will any cover exception from plain {@code assertThat}. + * thread, then that will cover any exception from plain {@code assertThat}. * * *

To record failures for the purpose of testing that an assertion fails when it should, see @@ -82,11 +82,12 @@ *

For more on this class, see the documentation page. */ @GwtIncompatible("JUnit4") +@J2ktIncompatible public final class Expect extends StandardSubjectBuilder implements TestRule { private static final class ExpectationGatherer implements FailureStrategy { @GuardedBy("this") - private final List failures = new ArrayList(); + private final List failures = new ArrayList<>(); @GuardedBy("this") private TestPhase inRuleContext = BEFORE; @@ -136,8 +137,10 @@ public synchronized String toString() { } 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 synchronized String toString() { 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 @@ void checkStatePreconditions() { } @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 2c6ec5893..98b625cc3 100644 --- a/core/src/main/java/com/google/common/truth/ExpectFailure.java +++ b/core/src/main/java/com/google/common/truth/ExpectFailure.java @@ -24,7 +24,7 @@ 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.jspecify.annotations.Nullable; import org.junit.runner.Description; import org.junit.runners.model.Statement; @@ -67,14 +67,6 @@ * {@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; @@ -102,7 +94,7 @@ public StandardSubjectBuilder whenTesting() { "ExpectFailure.whenTesting() called previously, but did not capture a failure."); } failureExpected = true; - return StandardSubjectBuilder.forCustomFailureStrategy(strategy); + return StandardSubjectBuilder.forCustomFailureStrategy(this::captureFailure); } /** @@ -179,29 +171,23 @@ public static AssertionError expectFailure(StandardSubjectBuilderCallback assert */ @CanIgnoreReturnValue public static AssertionError expectFailureAbout( - final Subject.Factory factory, - final SimpleSubjectBuilderCallback assertionCallback) { - // whenTesting -> assertionCallback.invokeAssertion(whenTesting.about(factory)) + Subject.Factory factory, SimpleSubjectBuilderCallback 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 02e2bea4b..289177901 100644 --- a/core/src/main/java/com/google/common/truth/Fact.java +++ b/core/src/main/java/com/google/common/truth/Fact.java @@ -22,7 +22,7 @@ import com.google.common.collect.ImmutableList; import java.io.Serializable; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * A string key-value pair in a failure message, such as "expected: abc" or "but was: xyz." @@ -135,6 +135,6 @@ private static String indent(String value) { // 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 7bc12bca1..6a9c90fdd 100644 --- a/core/src/main/java/com/google/common/truth/FailureMetadata.java +++ b/core/src/main/java/com/google/common/truth/FailureMetadata.java @@ -29,7 +29,7 @@ import com.google.common.base.Function; import com.google.common.collect.ImmutableList; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * An opaque, immutable object containing state from the previous calls in the fluent assertion @@ -68,7 +68,7 @@ static Step subjectCreation(Subject subject) { } static Step checkCall( - OldAndNewValuesAreSimilar valuesAreSimilar, + @Nullable OldAndNewValuesAreSimilar valuesAreSimilar, @Nullable Function descriptionUpdate) { return new Step(null, descriptionUpdate, valuesAreSimilar); } @@ -238,7 +238,7 @@ private ImmutableList description() { } if (description == null) { - description = step.subject.typeDescription(); + description = checkNotNull(step.subject).typeDescription(); } } return descriptionIsInteresting @@ -287,7 +287,7 @@ private ImmutableList rootUnlessThrowable() { } 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. @@ -306,8 +306,9 @@ private ImmutableList rootUnlessThrowable() { ? 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.of(); } @@ -317,7 +318,7 @@ private ImmutableList rootUnlessThrowable() { */ 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 81f1092a7..b10d5509a 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 765b3844b..c5d661185 100644 --- a/core/src/main/java/com/google/common/truth/FloatSubject.java +++ b/core/src/main/java/com/google/common/truth/FloatSubject.java @@ -26,7 +26,7 @@ import static java.lang.Float.NaN; import static java.lang.Float.floatToIntBits; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Propositions for {@link Float} subjects. @@ -36,7 +36,7 @@ public final class FloatSubject extends ComparableSubject { 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 int hashCode() { * 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 void of(float expected) { * 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 void isNotEqualTo(@Nullable Object other) { */ @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 6a266f5df..ef21fef5b 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 @@ private Optional breadthFirstSearch(BiMap matching, Map freeRhsVertexLayer.get()) { break; @@ -164,7 +166,7 @@ private Optional breadthFirstSearch(BiMap matching, Map 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 @@ private boolean depthFirstSearch( } 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 bbaf8053e..cd9776908 100644 --- a/core/src/main/java/com/google/common/truth/GuavaOptionalSubject.java +++ b/core/src/main/java/com/google/common/truth/GuavaOptionalSubject.java @@ -19,21 +19,24 @@ import static com.google.common.truth.Fact.simpleFact; import com.google.common.base.Optional; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Propositions for Guava {@link Optional} subjects. * - *

If you are looking for a {@code java.util.Optional} subject, please read - * faq#java8 + *

If you are looking for a {@code java.util.Optional} subject, see {@link OptionalSubject}. * * @author Christian Gruber */ public final class GuavaOptionalSubject extends Subject { - private final Optional actual; + @SuppressWarnings("NullableOptional") // Truth always accepts nulls, no matter the type + private final @Nullable Optional actual; GuavaOptionalSubject( - FailureMetadata metadata, @Nullable Optional actual, @Nullable String typeDescription) { + FailureMetadata metadata, + @SuppressWarnings("NullableOptional") // Truth always accepts nulls, no matter the type + @Nullable Optional actual, + @Nullable String typeDescription) { super(metadata, actual, typeDescription); this.actual = actual; } @@ -67,7 +70,7 @@ public void isAbsent() { * assertThat(myOptional.get()).contains("foo"); * } */ - 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/PathSubject.java b/core/src/main/java/com/google/common/truth/IgnoreJRERequirement.java similarity index 53% rename from extensions/java8/src/main/java/com/google/common/truth/PathSubject.java rename to core/src/main/java/com/google/common/truth/IgnoreJRERequirement.java index 0be8532e0..a2d484272 100644 --- a/extensions/java8/src/main/java/com/google/common/truth/PathSubject.java +++ b/core/src/main/java/com/google/common/truth/IgnoreJRERequirement.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 Google, Inc. + * 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. @@ -13,21 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.google.common.truth; -import com.google.common.annotations.GwtIncompatible; -import com.google.j2objc.annotations.J2ObjCIncompatible; -import java.nio.file.Path; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; -/** Assertions for {@link Path} instances. */ -@GwtIncompatible -@J2ObjCIncompatible -public final class PathSubject extends Subject { - private PathSubject(FailureMetadata failureMetadata, Path actual) { - super(failureMetadata, actual); - } +import java.lang.annotation.Target; - public static Subject.Factory paths() { - return PathSubject::new; - } -} +/** + * Disables Animal Sniffer's checking of compatibility with older versions of Java/Android. + */ +@Target({METHOD, CONSTRUCTOR, TYPE}) +@interface IgnoreJRERequirement {} diff --git a/extensions/java8/src/main/java/com/google/common/truth/IntStreamSubject.java b/core/src/main/java/com/google/common/truth/IntStreamSubject.java similarity index 78% rename from extensions/java8/src/main/java/com/google/common/truth/IntStreamSubject.java rename to core/src/main/java/com/google/common/truth/IntStreamSubject.java index cd898d074..3699e089e 100644 --- a/extensions/java8/src/main/java/com/google/common/truth/IntStreamSubject.java +++ b/core/src/main/java/com/google/common/truth/IntStreamSubject.java @@ -15,6 +15,7 @@ */ package com.google.common.truth; +import static com.google.common.base.Preconditions.checkNotNull; import static java.util.stream.Collectors.toCollection; import com.google.errorprone.annotations.CanIgnoreReturnValue; @@ -23,7 +24,7 @@ import java.util.List; import java.util.stream.IntStream; import java.util.stream.Stream; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Propositions for {@link IntStream} subjects. @@ -39,12 +40,18 @@ * stream before asserting on it. * * @author Kurt Alfred Kluever + * @since 1.3.0 (previously part of {@code truth-java8-extension}) */ +@SuppressWarnings({ + "deprecation", // TODO(b/134064106): design an alternative to no-arg check() + "Java7ApiChecker", // used only from APIs with Java 8 in their signatures +}) +@IgnoreJRERequirement public final class IntStreamSubject extends Subject { - private final List actualList; + private final @Nullable List actualList; - private IntStreamSubject(FailureMetadata failureMetadata, @Nullable IntStream stream) { + IntStreamSubject(FailureMetadata failureMetadata, @Nullable IntStream stream) { super(failureMetadata, stream); this.actualList = (stream == null) ? null : stream.boxed().collect(toCollection(ArrayList::new)); @@ -55,6 +62,17 @@ protected String actualCustomStringRepresentation() { return String.valueOf(actualList); } + /** + * Obsolete factory instance. This factory was previously necessary for assertions like {@code + * assertWithMessage(...).about(intStreams()).that(stream)....}. Now, you can perform assertions + * like that without the {@code about(...)} call. + * + * @deprecated Instead of {@code about(intStreams()).that(...)}, use just {@code that(...)}. + * Similarly, instead of {@code assertAbout(intStreams()).that(...)}, use just {@code + * assertThat(...)}. + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") // We want users to remove the surrounding call entirely. public static Factory intStreams() { return IntStreamSubject::new; } @@ -101,7 +119,7 @@ public void containsAnyOf(int first, int second, int... rest) { } /** Fails if the subject does not contain at least one of the given elements. */ - public void containsAnyIn(Iterable expected) { + public void containsAnyIn(@Nullable Iterable expected) { check().that(actualList).containsAnyIn(expected); } @@ -130,7 +148,7 @@ public Ordered containsAtLeast(int first, int second, int... rest) { * within the actual elements, but they are not required to be consecutive. */ @CanIgnoreReturnValue - public Ordered containsAtLeastElementsIn(Iterable expected) { + public Ordered containsAtLeastElementsIn(@Nullable Iterable expected) { return check().that(actualList).containsAtLeastElementsIn(expected); } @@ -144,8 +162,20 @@ public Ordered containsAtLeastElementsIn(Iterable expected) { * on the object returned by this method. */ @CanIgnoreReturnValue - public Ordered containsExactly(int... varargs) { - return check().that(actualList).containsExactly(box(varargs)); + public Ordered containsExactly(int @Nullable ... varargs) { + /* + * We declare a parameter type that lets callers pass a nullable array, even though the + * assertion will fail if the array is ever actually null. This can be convenient if the + * expected value comes from a nullable source (e.g., a map lookup): Users would otherwise have + * to use {@code requireNonNull} or {@code !!} or similar, all to address a compile error + * warning about a runtime failure that might never happen—a runtime failure that Truth could + * produce a better exception message for, since it could make the message express that the + * caller is performing a containsExactly assertion. + * + * TODO(cpovirk): Actually produce such a better exception message. + */ + checkNotNull(varargs); + return check().that(actualList).containsExactlyElementsIn(box(varargs)); } /** @@ -158,7 +188,7 @@ public Ordered containsExactly(int... varargs) { * on the object returned by this method. */ @CanIgnoreReturnValue - public Ordered containsExactlyElementsIn(Iterable expected) { + public Ordered containsExactlyElementsIn(@Nullable Iterable expected) { return check().that(actualList).containsExactlyElementsIn(expected); } @@ -175,7 +205,7 @@ public void containsNoneOf(int first, int second, int... rest) { * Fails if the subject contains any of the given elements. (Duplicates are irrelevant to this * test, which fails if any of the actual elements equal any of the excluded.) */ - public void containsNoneIn(Iterable excluded) { + public void containsNoneIn(@Nullable Iterable excluded) { check().that(actualList).containsNoneIn(excluded); } 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 2f892379b..99fd82fd5 100644 --- a/core/src/main/java/com/google/common/truth/IntegerSubject.java +++ b/core/src/main/java/com/google/common/truth/IntegerSubject.java @@ -15,7 +15,12 @@ */ package com.google.common.truth; -import org.checkerframework.checker.nullness.qual.Nullable; +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.MathUtil.equalWithinTolerance; + +import org.jspecify.annotations.Nullable; /** * Propositions for {@link Integer} subjects. @@ -25,18 +30,123 @@ * @author Kurt Alfred Kluever */ public class IntegerSubject extends ComparableSubject { + private final @Nullable Integer actual; + /** * 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)}. */ - protected IntegerSubject(FailureMetadata metadata, @Nullable Integer integer) { - super(metadata, integer); + protected IntegerSubject(FailureMetadata metadata, @Nullable Integer actual) { + super(metadata, actual); + this.actual = actual; + } + + /** + * A partially specified check about an approximate relationship to a {@code int} subject using a + * tolerance. + * + * @since 1.2 + */ + public abstract static class TolerantIntegerComparison { + + // Prevent subclassing outside of this class + private TolerantIntegerComparison() {} + + /** + * Fails if the subject was expected to be within the tolerance of the given value but was not + * or if it was expected not to be within the tolerance but was. The subject and + * tolerance are specified earlier in the fluent call chain. + */ + public abstract void of(int expectedInteger); + + /** + * @throws UnsupportedOperationException always + * @deprecated {@link Object#equals(Object)} is not supported on TolerantIntegerComparison. If + * you meant to compare ints, use {@link #of(int)} instead. + */ + @Deprecated + @Override + public boolean equals(@Nullable Object o) { + throw new UnsupportedOperationException( + "If you meant to compare ints, use .of(int) instead."); + } + + /** + * @throws UnsupportedOperationException always + * @deprecated {@link Object#hashCode()} is not supported on TolerantIntegerComparison + */ + @Deprecated + @Override + public int hashCode() { + throw new UnsupportedOperationException("Subject.hashCode() is not supported."); + } + } + + /** + * Prepares for a check that the subject is a number within the given tolerance of an expected + * value that will be provided in the next call in the fluent chain. + * + * @param tolerance an inclusive upper bound on the difference between the subject and object + * allowed by the check, which must be a non-negative value. + * @since 1.2 + */ + public TolerantIntegerComparison isWithin(int tolerance) { + return new TolerantIntegerComparison() { + @Override + public void of(int expected) { + Integer actual = IntegerSubject.this.actual; + checkNotNull( + actual, "actual value cannot be null. tolerance=%s expected=%s", tolerance, expected); + checkTolerance(tolerance); + + if (!equalWithinTolerance(actual, expected, tolerance)) { + failWithoutActual( + fact("expected", Integer.toString(expected)), + butWas(), + fact("outside tolerance", Integer.toString(tolerance))); + } + } + }; + } + + /** + * Prepares for a check that the subject is a number not within the given tolerance of an expected + * value that will be provided in the next call in the fluent chain. + * + * @param tolerance an exclusive lower bound on the difference between the subject and object + * allowed by the check, which must be a non-negative value. + * @since 1.2 + */ + public TolerantIntegerComparison isNotWithin(int tolerance) { + return new TolerantIntegerComparison() { + @Override + public void of(int expected) { + Integer actual = IntegerSubject.this.actual; + checkNotNull( + actual, "actual value cannot be null. tolerance=%s expected=%s", tolerance, expected); + checkTolerance(tolerance); + + if (equalWithinTolerance(actual, expected, tolerance)) { + failWithoutActual( + fact("expected not to be", Integer.toString(expected)), + butWas(), + fact("within tolerance", Integer.toString(tolerance))); + } + } + }; } - /** @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); } + + /** Ensures that the given tolerance is a non-negative value. */ + private static void checkTolerance(int tolerance) { + checkArgument(tolerance >= 0, "tolerance (%s) cannot be negative", tolerance); + } } 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 2fa67429d..b982014bc 100644 --- a/core/src/main/java/com/google/common/truth/IterableSubject.java +++ b/core/src/main/java/com/google/common/truth/IterableSubject.java @@ -27,6 +27,7 @@ import static com.google.common.truth.IterableSubject.ElementFactGrouping.FACT_PER_ELEMENT; import static com.google.common.truth.SubjectUtils.accumulate; import static com.google.common.truth.SubjectUtils.annotateEmptyStrings; +import static com.google.common.truth.SubjectUtils.asList; import static com.google.common.truth.SubjectUtils.countDuplicates; import static com.google.common.truth.SubjectUtils.countDuplicatesAndAddTypeInfo; import static com.google.common.truth.SubjectUtils.countDuplicatesAndMaybeAddTypeInfoReturnObject; @@ -36,7 +37,6 @@ import static com.google.common.truth.SubjectUtils.iterableToList; import static com.google.common.truth.SubjectUtils.objectToTypeName; import static com.google.common.truth.SubjectUtils.retainMatchingToString; -import static java.util.Arrays.asList; import com.google.common.base.Function; import com.google.common.base.Objects; @@ -67,7 +67,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Propositions for {@link Iterable} subjects. @@ -90,14 +90,22 @@ // 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 * {@link Subject#check(String, Object...) check(...)}{@code .that(actual)}. */ protected IterableSubject(FailureMetadata metadata, @Nullable Iterable iterable) { - super(metadata, iterable); + this(metadata, iterable, null); + } + + /** Constructor for use by package-private callers. */ + IterableSubject( + FailureMetadata metadata, + @Nullable Iterable iterable, + @Nullable String typeDescriptionOverride) { + super(metadata, iterable, typeDescriptionOverride); this.actual = iterable; } @@ -105,7 +113,8 @@ protected IterableSubject(FailureMetadata metadata, @Nullable Iterable iterab protected String actualCustomStringRepresentation() { if (actual != null) { // Check the value of iterable.toString() against the default Object.toString() implementation - // so we can avoid things like "com.google.common.graph.Traverser$GraphTraverser$1@5e316c74" + // so that we can avoid things like + // "com.google.common.graph.Traverser$GraphTraverser$1@5e316c74" String objectToString = actual.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(actual)); if (actual.toString().equals(objectToString)) { @@ -141,14 +150,14 @@ public void isEqualTo(@Nullable Object expected) { /** 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")); } } @@ -156,14 +165,14 @@ public final void isNotEmpty() { /** 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 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), @@ -172,7 +181,7 @@ public final void contains(@Nullable Object element) { fact( "though it did contain", countDuplicatesAndAddTypeInfo( - retainMatchingToString(actual, elementList /* itemsToCheck */))), + retainMatchingToString(actual, /* itemsToCheck= */ elementList))), fullContents()); } else { failWithActual("expected to contain", element); @@ -182,7 +191,7 @@ public final void contains(@Nullable Object element) { /** 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); } } @@ -190,7 +199,7 @@ public final void doesNotContain(@Nullable Object element) { /** Checks that the subject does not contain duplicate elements. */ public final void containsNoDuplicates() { List> 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); } @@ -214,8 +223,9 @@ public final void containsAnyOf( * 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; @@ -228,7 +238,7 @@ public final void containsAnyIn(Iterable expected) { 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); @@ -239,12 +249,13 @@ public final void containsAnyIn(Iterable expected) { * Checks that the subject contains at least one of the objects contained in the provided array or * fails. */ - public final void containsAnyIn(Object[] expected) { + @SuppressWarnings("AvoidObjectArrays") + public final void containsAnyIn(@Nullable Object[] expected) { containsAnyIn(asList(expected)); } /** - * Checks that the actual iterable contains at least all of the expected elements or fails. If an + * Checks that the actual iterable contains at least all the expected elements or fails. If an * element appears more than once in the expected elements to this call then it must appear at * least that number of times in the actual elements. * @@ -261,7 +272,7 @@ public final Ordered containsAtLeast( } /** - * Checks that the actual iterable contains at least all of the expected elements or fails. If an + * Checks that the actual iterable contains at least all the expected elements or fails. If an * element appears more than once in the expected elements then it must appear at least that * number of times in the actual elements. * @@ -270,12 +281,12 @@ public final Ordered containsAtLeast( * within the actual elements, but they are not required to be consecutive. */ @CanIgnoreReturnValue - public final Ordered containsAtLeastElementsIn(Iterable expectedIterable) { - List actual = Lists.newLinkedList(this.actual); - final Collection expected = iterableToCollection(expectedIterable); + public final Ordered containsAtLeastElementsIn(@Nullable Iterable expectedIterable) { + List actual = Lists.newLinkedList(checkNotNull(this.actual)); + Collection expected = iterableToCollection(expectedIterable); - List missing = newArrayList(); - List actualNotInOrder = newArrayList(); + List<@Nullable Object> missing = newArrayList(); + List<@Nullable Object> actualNotInOrder = newArrayList(); boolean ordered = true; // step through the expected elements... @@ -307,7 +318,8 @@ public void inOrder() { ImmutableList.Builder 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 actualOrder = Lists.newArrayList(IterableSubject.this.actual); + List actualOrder = + Lists.newArrayList(checkNotNull(IterableSubject.this.actual)); if (actualOrder.retainAll(expected)) { facts.add(fact("but order was", actualOrder)); facts.add(fullContents()); @@ -320,7 +332,7 @@ public void inOrder() { } /** - * Checks that the actual iterable contains at least all of the expected elements or fails. If an + * Checks that the actual iterable contains at least all the expected elements or fails. If an * element appears more than once in the expected elements then it must appear at least that * number of times in the actual elements. * @@ -329,13 +341,14 @@ public void inOrder() { * within the actual elements, but they are not required to be consecutive. */ @CanIgnoreReturnValue - public final Ordered containsAtLeastElementsIn(Object[] expected) { + @SuppressWarnings("AvoidObjectArrays") + public final Ordered containsAtLeastElementsIn(@Nullable 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 facts = ImmutableList.builder(); facts.addAll( @@ -360,7 +373,8 @@ private Ordered failAtLeast(Collection expected, Collection missingRawObje * 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 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)); } @@ -381,7 +395,8 @@ private static void moveElements(List input, Collection output, int m */ @CanIgnoreReturnValue public final Ordered containsExactly(@Nullable Object @Nullable ... varargs) { - List expected = (varargs == null) ? newArrayList((Object) null) : asList(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); } @@ -397,7 +412,7 @@ public final Ordered containsExactly(@Nullable Object @Nullable ... varargs) { * 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); } @@ -411,13 +426,19 @@ public final Ordered containsExactlyElementsIn(Iterable expected) { * 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()) { @@ -463,12 +484,12 @@ private Ordered containsExactlyElementsIn( return ALREADY_FAILED; } // Missing elements; elements that are not missing will be removed as we iterate. - Collection 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 extra = newArrayList(); + List<@Nullable Object> extra = newArrayList(); // Remove all actual elements from missing, and add any that weren't in missing // to extra. @@ -689,7 +710,7 @@ enum ElementFactGrouping { } /** - * Checks that a actual iterable contains none of the excluded objects or fails. (Duplicates are + * Checks that an actual iterable contains none of the excluded objects or fails. (Duplicates are * irrelevant to this test, which fails if any of the actual elements equal any of the excluded.) */ public final void containsNoneOf( @@ -704,9 +725,10 @@ public final void containsNoneOf( * iterable 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(Iterable excluded) { - Collection actual = iterableToCollection(this.actual); - Collection present = new ArrayList<>(); + public final void containsNoneIn(@Nullable Iterable excluded) { + Collection actual = iterableToCollection(checkNotNull(this.actual)); + checkNotNull(excluded); // TODO(cpovirk): Produce a better exception message. + List<@Nullable Object> present = new ArrayList<>(); for (Object item : Sets.newLinkedHashSet(excluded)) { if (actual.contains(item)) { present.add(item); @@ -725,25 +747,16 @@ public final void containsNoneIn(Iterable excluded) { * 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 @@ -775,14 +788,14 @@ public void isInStrictOrder() { * @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) comparator).compare(prev, next) < 0; + public boolean check(@Nullable Object prev, @Nullable Object next) { + return ((Comparator<@Nullable Object>) comparator).compare(prev, next) < 0; } }); } @@ -807,24 +820,24 @@ public void isInOrder() { * @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) 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()) { @@ -857,11 +870,12 @@ public void isNoneOf( */ @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 nonIterables = new ArrayList<>(); + List<@Nullable Object> nonIterables = new ArrayList<>(); for (Object element : iterable) { if (!(element instanceof Iterable)) { nonIterables.add(element); @@ -901,8 +915,9 @@ private Fact fullContents() { *

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 UsingCorrespondence comparingElementsUsing( - Correspondence correspondence) { + public + UsingCorrespondence comparingElementsUsing( + Correspondence correspondence) { return new UsingCorrespondence<>(this, correspondence); } @@ -935,7 +950,7 @@ public UsingCorrespondence comparingElementsUsing( * * @since 1.1 */ - public UsingCorrespondence formattingDiffsUsing( + public UsingCorrespondence formattingDiffsUsing( DiffFormatter formatter) { return comparingElementsUsing(Correspondence.equality().formattingDiffsUsing(formatter)); } @@ -946,7 +961,7 @@ public UsingCorrespondence formattingDiffsUsing( * expected elements are of type {@code E}. Call methods on this object to actually execute the * check. */ - public static class UsingCorrespondence { + public static class UsingCorrespondence { private final IterableSubject subject; private final Correspondence correspondence; @@ -1016,7 +1031,7 @@ public final String toString() { *

{@code
      * assertThat(actualRecords)
      *     .comparingElementsUsing(RECORD_CORRESPONDENCE)
-     *     .displayingDiffsPairedBy(Record::getId)
+     *     .displayingDiffsPairedBy(MyRecord::getId)
      *     .containsExactlyElementsIn(expectedRecords);
      * }
* @@ -1028,7 +1043,7 @@ public final String toString() { * *

On assertions where it makes sense to do so, the elements are paired as follows: they are * keyed by {@code keyFunction}, and if an unexpected element and a missing element have the - * same non-null key then the they are paired up. (Elements with null keys are not paired.) The + * same non-null key then they are paired up. (Elements with null keys are not paired.) The * failure message will show paired elements together, and a diff will be shown if the {@link * Correspondence#formatDiff} method returns non-null. * @@ -1070,8 +1085,8 @@ public UsingCorrespondence displayingDiffsPairedBy(Function *

On assertions where it makes sense to do so, the elements are paired as follows: the * unexpected elements are keyed by {@code actualKeyFunction}, the missing elements are keyed by * {@code expectedKeyFunction}, and if an unexpected element and a missing element have the same - * non-null key then the they are paired up. (Elements with null keys are not paired.) The - * failure message will show paired elements together, and a diff will be shown if the {@link + * non-null key then they are paired up. (Elements with null keys are not paired.) The failure + * message will show paired elements together, and a diff will be shown if the {@link * Correspondence#formatDiff} method returns non-null. * *

The expected elements given in the assertion should be uniquely keyed by {@code @@ -1107,7 +1122,7 @@ public UsingCorrespondence displayingDiffsPairedBy( * 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(@Nullable E expected) { + public void contains(E expected) { Correspondence.ExceptionStore exceptions = Correspondence.ExceptionStore.forIterable(); for (A actual : getCastActual()) { if (correspondence.safeCompare(actual, expected, exceptions)) { @@ -1157,7 +1172,7 @@ public void contains(@Nullable E expected) { } /** 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 matchingElements = new ArrayList<>(); for (A actual : getCastActual()) { @@ -1218,9 +1233,9 @@ public final Ordered containsExactly(@Nullable E @Nullable ... expected) { * on the object returned by this method. */ @CanIgnoreReturnValue - public Ordered containsExactlyElementsIn(final Iterable expected) { + public Ordered containsExactlyElementsIn(@Nullable Iterable expected) { List actualList = iterableToList(getCastActual()); - List expectedList = iterableToList(expected); + List expectedList = iterableToList(checkNotNull(expected)); if (expectedList.isEmpty()) { if (actualList.isEmpty()) { @@ -1295,8 +1310,9 @@ public void inOrder() { * 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))); } /** @@ -1381,7 +1397,7 @@ private ImmutableList describeMissingOrExtra( List 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 { @@ -1434,11 +1450,11 @@ private ImmutableList formatExtras( E missing, List extras, Correspondence.ExceptionStore exceptions) { - List 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; @@ -1465,7 +1481,8 @@ private ImmutableList formatExtras( * 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 List findNotIndexed(List list, Set indexes) { + private List findNotIndexed( + List list, Set 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. @@ -1544,8 +1561,8 @@ private boolean failIfOneToOneMappingHasMissingOrExtra( } /** - * Checks that the subject contains elements that corresponds to all of the expected elements, - * i.e. that there is a 1:1 mapping between any subset of the actual elements and the expected + * Checks that the subject contains elements that correspond to all the expected elements, i.e. + * that there is a 1:1 mapping between any subset of the actual elements and the expected * elements where each pair of elements correspond. * *

To also test that the contents appear in the given order, make a call to {@code inOrder()} @@ -1554,14 +1571,13 @@ private boolean failIfOneToOneMappingHasMissingOrExtra( */ @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)); } /** - * Checks that the subject contains elements that corresponds to all of the expected elements, - * i.e. that there is a 1:1 mapping between any subset of the actual elements and the expected + * Checks that the subject contains elements that correspond to all the expected elements, i.e. + * that there is a 1:1 mapping between any subset of the actual elements and the expected * elements where each pair of elements correspond. * *

To also test that the contents appear in the given order, make a call to {@code inOrder()} @@ -1569,7 +1585,7 @@ public final Ordered containsAtLeast( * subject, but they are not required to be consecutive. */ @CanIgnoreReturnValue - public Ordered containsAtLeastElementsIn(final Iterable expected) { + public Ordered containsAtLeastElementsIn(Iterable expected) { List actualList = iterableToList(getCastActual()); List expectedList = iterableToList(expected); // Check if the expected elements correspond in order to any subset of the actual elements. @@ -1626,8 +1642,8 @@ public void inOrder() { } /** - * Checks that the subject contains elements that corresponds to all of the expected elements, - * i.e. that there is a 1:1 mapping between any subset of the actual elements and the expected + * Checks that the subject contains elements that correspond to all the expected elements, i.e. + * that there is a 1:1 mapping between any subset of the actual elements and the expected * elements where each pair of elements correspond. * *

To also test that the contents appear in the given order, make a call to {@code inOrder()} @@ -1635,6 +1651,7 @@ public void inOrder() { * subject, but they are not required to be consecutive. */ @CanIgnoreReturnValue + @SuppressWarnings("AvoidObjectArrays") public Ordered containsAtLeastElementsIn(E[] expected) { return containsAtLeastElementsIn(asList(expected)); } @@ -1718,7 +1735,7 @@ private ImmutableList describeMissing( List 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 { @@ -1803,8 +1820,7 @@ private boolean failIfOneToOneMappingHasMissing( * 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)); } @@ -1885,6 +1901,7 @@ public void containsAnyIn(Iterable expected) { * 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)); } @@ -1910,9 +1927,7 @@ private ImmutableList describeAnyMatchesByKey( */ @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)); } @@ -1967,13 +1982,14 @@ public void containsNoneIn(Iterable excluded) { * (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 getCastActual() { - return (Iterable) subject.actual; + return (Iterable) checkNotNull(subject.actual); } // TODO(b/69154276): Consider commoning up some of the logic between IterableSubject.Pairer, @@ -2009,7 +2025,7 @@ private final class Pairer { // 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 expectedKeys = new ArrayList<>(expectedValues.size()); + List<@Nullable Object> expectedKeys = new ArrayList<>(expectedValues.size()); for (E expected : expectedValues) { expectedKeys.add(expectedKey(expected, exceptions)); } @@ -2018,7 +2034,7 @@ private final class Pairer { // 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; @@ -2030,9 +2046,9 @@ private final class Pairer { // 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); } @@ -2041,7 +2057,7 @@ private final class Pairer { // 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); @@ -2055,7 +2071,7 @@ List pairOne( E expectedValue, Iterable actualValues, Correspondence.ExceptionStore exceptions) { - @Nullable Object key = expectedKey(expectedValue, exceptions); + Object key = expectedKey(expectedValue, exceptions); List matches = new ArrayList<>(); if (key != null) { for (A actual : actualValues) { @@ -2088,7 +2104,7 @@ List pairOne( } } - /** 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 000000000..45e14be0c --- /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 not 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 3fd26c904..ef6d6cd4e 100644 --- a/core/src/main/java/com/google/common/truth/LazyMessage.java +++ b/core/src/main/java/com/google/common/truth/LazyMessage.java @@ -20,20 +20,22 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.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) { 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/extensions/java8/src/main/java/com/google/common/truth/LongStreamSubject.java b/core/src/main/java/com/google/common/truth/LongStreamSubject.java similarity index 78% rename from extensions/java8/src/main/java/com/google/common/truth/LongStreamSubject.java rename to core/src/main/java/com/google/common/truth/LongStreamSubject.java index 1e6b50290..2c8f3a041 100644 --- a/extensions/java8/src/main/java/com/google/common/truth/LongStreamSubject.java +++ b/core/src/main/java/com/google/common/truth/LongStreamSubject.java @@ -15,6 +15,7 @@ */ package com.google.common.truth; +import static com.google.common.base.Preconditions.checkNotNull; import static java.util.stream.Collectors.toCollection; import com.google.errorprone.annotations.CanIgnoreReturnValue; @@ -23,7 +24,7 @@ import java.util.List; import java.util.stream.LongStream; import java.util.stream.Stream; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Propositions for {@link LongStream} subjects. @@ -39,12 +40,18 @@ * stream before asserting on it. * * @author Kurt Alfred Kluever + * @since 1.3.0 (previously part of {@code truth-java8-extension}) */ +@SuppressWarnings({ + "deprecation", // TODO(b/134064106): design an alternative to no-arg check() + "Java7ApiChecker", // used only from APIs with Java 8 in their signatures +}) +@IgnoreJRERequirement public final class LongStreamSubject extends Subject { - private final List actualList; + private final @Nullable List actualList; - private LongStreamSubject(FailureMetadata failureMetadata, @Nullable LongStream stream) { + LongStreamSubject(FailureMetadata failureMetadata, @Nullable LongStream stream) { super(failureMetadata, stream); this.actualList = (stream == null) ? null : stream.boxed().collect(toCollection(ArrayList::new)); @@ -55,6 +62,17 @@ protected String actualCustomStringRepresentation() { return String.valueOf(actualList); } + /** + * Obsolete factory instance. This factory was previously necessary for assertions like {@code + * assertWithMessage(...).about(longStreams()).that(stream)....}. Now, you can perform assertions + * like that without the {@code about(...)} call. + * + * @deprecated Instead of {@code about(longStreams()).that(...)}, use just {@code that(...)}. + * Similarly, instead of {@code assertAbout(longStreams()).that(...)}, use just {@code + * assertThat(...)}. + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") // We want users to remove the surrounding call entirely. public static Factory longStreams() { return LongStreamSubject::new; } @@ -101,7 +119,7 @@ public void containsAnyOf(long first, long second, long... rest) { } /** Fails if the subject does not contain at least one of the given elements. */ - public void containsAnyIn(Iterable expected) { + public void containsAnyIn(@Nullable Iterable expected) { check().that(actualList).containsAnyIn(expected); } @@ -130,7 +148,7 @@ public Ordered containsAtLeast(long first, long second, long... rest) { * within the actual elements, but they are not required to be consecutive. */ @CanIgnoreReturnValue - public Ordered containsAtLeastElementsIn(Iterable expected) { + public Ordered containsAtLeastElementsIn(@Nullable Iterable expected) { return check().that(actualList).containsAtLeastElementsIn(expected); } @@ -144,8 +162,20 @@ public Ordered containsAtLeastElementsIn(Iterable expected) { * on the object returned by this method. */ @CanIgnoreReturnValue - public Ordered containsExactly(long... varargs) { - return check().that(actualList).containsExactly(box(varargs)); + public Ordered containsExactly(long @Nullable ... varargs) { + /* + * We declare a parameter type that lets callers pass a nullable array, even though the + * assertion will fail if the array is ever actually null. This can be convenient if the + * expected value comes from a nullable source (e.g., a map lookup): Users would otherwise have + * to use {@code requireNonNull} or {@code !!} or similar, all to address a compile error + * warning about a runtime failure that might never happen—a runtime failure that Truth could + * produce a better exception message for, since it could make the message express that the + * caller is performing a containsExactly assertion. + * + * TODO(cpovirk): Actually produce such a better exception message. + */ + checkNotNull(varargs); + return check().that(actualList).containsExactlyElementsIn(box(varargs)); } /** @@ -158,7 +188,7 @@ public Ordered containsExactly(long... varargs) { * on the object returned by this method. */ @CanIgnoreReturnValue - public Ordered containsExactlyElementsIn(Iterable expected) { + public Ordered containsExactlyElementsIn(@Nullable Iterable expected) { return check().that(actualList).containsExactlyElementsIn(expected); } @@ -175,7 +205,7 @@ public void containsNoneOf(long first, long second, long... rest) { * Fails if the subject contains any of the given elements. (Duplicates are irrelevant to this * test, which fails if any of the actual elements equal any of the excluded.) */ - public void containsNoneIn(Iterable excluded) { + public void containsNoneIn(@Nullable Iterable excluded) { check().that(actualList).containsNoneIn(excluded); } 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 27f99f546..746d3cc63 100644 --- a/core/src/main/java/com/google/common/truth/LongSubject.java +++ b/core/src/main/java/com/google/common/truth/LongSubject.java @@ -15,7 +15,12 @@ */ package com.google.common.truth; -import org.checkerframework.checker.nullness.qual.Nullable; +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.MathUtil.equalWithinTolerance; + +import org.jspecify.annotations.Nullable; /** * Propositions for {@code long} subjects. @@ -25,21 +30,127 @@ * @author Kurt Alfred Kluever */ public class LongSubject extends ComparableSubject { + + private final @Nullable Long actual; + /** * 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)}. */ protected LongSubject(FailureMetadata metadata, @Nullable Long actual) { super(metadata, actual); + this.actual = actual; + } + + /** + * A partially specified check about an approximate relationship to a {@code long} subject using a + * tolerance. + * + * @since 1.2 + */ + public abstract static class TolerantLongComparison { + + // Prevent subclassing outside of this class + private TolerantLongComparison() {} + + /** + * Fails if the subject was expected to be within the tolerance of the given value but was not + * or if it was expected not to be within the tolerance but was. The subject and + * tolerance are specified earlier in the fluent call chain. + */ + public abstract void of(long expectedLong); + + /** + * @throws UnsupportedOperationException always + * @deprecated {@link Object#equals(Object)} is not supported on TolerantLongComparison. If you + * meant to compare longs, use {@link #of(long)} instead. + */ + @Deprecated + @Override + public boolean equals(@Nullable Object o) { + throw new UnsupportedOperationException( + "If you meant to compare longs, use .of(long) instead."); + } + + /** + * @throws UnsupportedOperationException always + * @deprecated {@link Object#hashCode()} is not supported on TolerantLongComparison + */ + @Deprecated + @Override + public int hashCode() { + throw new UnsupportedOperationException("Subject.hashCode() is not supported."); + } + } + + /** + * Prepares for a check that the subject is a number within the given tolerance of an expected + * value that will be provided in the next call in the fluent chain. + * + * @param tolerance an inclusive upper bound on the difference between the subject and object + * allowed by the check, which must be a non-negative value. + * @since 1.2 + */ + public TolerantLongComparison isWithin(long tolerance) { + return new TolerantLongComparison() { + @Override + public void of(long expected) { + Long actual = LongSubject.this.actual; + checkNotNull( + actual, "actual value cannot be null. tolerance=%s expected=%s", tolerance, expected); + checkTolerance(tolerance); + + if (!equalWithinTolerance(actual, expected, tolerance)) { + failWithoutActual( + fact("expected", Long.toString(expected)), + butWas(), + fact("outside tolerance", Long.toString(tolerance))); + } + } + }; + } + + /** + * Prepares for a check that the subject is a number not within the given tolerance of an expected + * value that will be provided in the next call in the fluent chain. + * + * @param tolerance an exclusive lower bound on the difference between the subject and object + * allowed by the check, which must be a non-negative value. + * @since 1.2 + */ + public TolerantLongComparison isNotWithin(long tolerance) { + return new TolerantLongComparison() { + @Override + public void of(long expected) { + Long actual = LongSubject.this.actual; + checkNotNull( + actual, "actual value cannot be null. tolerance=%s expected=%s", tolerance, expected); + checkTolerance(tolerance); + + if (equalWithinTolerance(actual, expected, tolerance)) { + failWithoutActual( + fact("expected not to be", Long.toString(expected)), + butWas(), + fact("within tolerance", Long.toString(tolerance))); + } + } + }; } - /** @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); } + /** Ensures that the given tolerance is a non-negative value. */ + private static void checkTolerance(long tolerance) { + checkArgument(tolerance >= 0, "tolerance (%s) cannot be negative", tolerance); + } + /** * Checks that the subject is greater than {@code 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 2744d505f..2b0e496aa 100644 --- a/core/src/main/java/com/google/common/truth/MapSubject.java +++ b/core/src/main/java/com/google/common/truth/MapSubject.java @@ -42,7 +42,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Propositions for {@link Map} subjects. @@ -51,7 +51,7 @@ * @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 final void isEqualTo(@Nullable Object other) { /** 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 final void isNotEmpty() { /** 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 entry = immutableEntry(key, value); + Map.Entry<@Nullable Object, @Nullable Object> entry = immutableEntry(key, value); + checkNotNull(actual); if (!actual.entrySet().contains(entry)) { - List keyList = singletonList(key); - List 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 final void containsEntry(@Nullable Object key, @Nullable Object value) { retainMatchingToString(actual.keySet(), /* itemsToCheck= */ keyList))), fact("full contents", actualCustomStringRepresentationForPackageMembersToCall())); } else if (actual.containsValue(value)) { - Set 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 final void containsEntry(@Nullable Object key, @Nullable Object value) { /** 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)); } @@ -198,7 +199,7 @@ public final Ordered containsAtLeast( return containsAtLeastEntriesIn(accumulateMap("containsAtLeast", k0, v0, rest)); } - private static Map accumulateMap( + private static Map<@Nullable Object, @Nullable Object> accumulateMap( String functionName, @Nullable Object k0, @Nullable Object v0, @Nullable Object... rest) { checkArgument( rest.length % 2 == 0, @@ -206,9 +207,9 @@ private static Map accumulateMap( + "(i.e., the number of key/value parameters (%s) must be even).", rest.length + 2); - Map expectedMap = Maps.newLinkedHashMap(); + Map<@Nullable Object, @Nullable Object> expectedMap = Maps.newLinkedHashMap(); expectedMap.put(k0, v0); - Multiset 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 @@ private static Map accumulateMap( @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 final Ordered containsExactlyEntriesIn(Map expectedMap) { } 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 final Ordered containsAtLeastEntriesIn(Map expectedMap) { } 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 final Ordered containsAtLeastEntriesIn(Map expectedMap) { @CanIgnoreReturnValue private boolean containsEntriesInAnyOrder(Map expectedMap, boolean allowUnexpected) { - MapDifference 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 @@ private boolean containsEntriesInAnyOrder(Map expectedMap, boolean allowUn // present with the wrong value, which may be the closest we currently get to this.) failWithoutActual( ImmutableList.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 @@ private boolean containsEntriesInAnyOrder(Map expectedMap, boolean allowUn return false; } - private interface ValueTester { - boolean test(@Nullable A actualValue, @Nullable E expectedValue); + private interface ValueTester { + boolean test(A actualValue, E expectedValue); } - @SuppressWarnings("UnnecessaryAnonymousClass") // for Java 7 compatibility - private static final ValueTester EQUALITY = - new ValueTester() { - @Override - public boolean test(@Nullable Object actualValue, @Nullable Object expectedValue) { - return Objects.equal(actualValue, expectedValue); - } - }; - - private interface Differ { - String diff(A actual, E expected); + private interface Differ { + @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 { + private static class MapDifference< + K extends @Nullable Object, A extends @Nullable Object, E extends @Nullable Object> { private final Map missing; private final Map unexpected; private final Map> wrongValues; private final Set allKeys; - static MapDifference create( - Map actual, - Map expected, - boolean allowUnexpected, - ValueTester valueTester) { + static + MapDifference create( + Map actual, + Map expected, + boolean allowUnexpected, + ValueTester valueTester) { Map unexpected = new LinkedHashMap<>(actual); Map missing = new LinkedHashMap<>(); Map> wrongValues = new LinkedHashMap<>(); @@ -322,7 +315,8 @@ static MapDifference create( 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 @@ private boolean includeKeyTypes() { } } - private static class ValueDifference { + private static class ValueDifference { 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 @@ ImmutableList describe(@Nullable Differ differ) { } } - 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 @@ private class MapInOrder implements Ordered { @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 void inOrder() { } /** 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 void inOrder() {} * encounter an actual value that is not of type {@code A} or an expected value that is not of * type {@code E}. */ - public final UsingCorrespondence comparingValuesUsing( - Correspondence correspondence) { + public final + UsingCorrespondence comparingValuesUsing( + Correspondence correspondence) { return new UsingCorrespondence<>(correspondence); } @@ -540,7 +526,7 @@ public final UsingCorrespondence comparingValuesUsing( * * @since 1.1 */ - public final UsingCorrespondence formattingDiffsUsing( + public final UsingCorrespondence formattingDiffsUsing( DiffFormatter formatter) { return comparingValuesUsing(Correspondence.equality().formattingDiffsUsing(formatter)); } @@ -552,7 +538,7 @@ public final UsingCorrespondence formattingDiffsUsing( * *

Note that keys will always be compared with regular object equality ({@link Object#equals}). */ - public final class UsingCorrespondence { + public final class UsingCorrespondence { private final Correspondence correspondence; @@ -564,18 +550,19 @@ private UsingCorrespondence(Correspondence correspondence) * 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.builder() @@ -600,7 +587,7 @@ public void containsEntry(@Nullable Object expectedKey, @Nullable E expectedValu } } else { // Did not find matching key. Look for the matching value with a different key. - Set keys = new LinkedHashSet<>(); + Set<@Nullable Object> keys = new LinkedHashSet<>(); Correspondence.ExceptionStore exceptions = Correspondence.ExceptionStore.forMapValues(); for (Map.Entry actualEntry : getCastSubject().entrySet()) { if (correspondence.safeCompare(actualEntry.getValue(), expectedValue, exceptions)) { @@ -638,19 +625,24 @@ public void containsEntry(@Nullable Object expectedKey, @Nullable E expectedValu * 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( ImmutableList.builder() .add(fact("expected not to contain", immutableEntry(excludedKey, excludedValue))) .addAll(correspondence.describeForMapValues()) - .add(fact("but contained", immutableEntry(excludedKey, actualValue))) + .add( + fact( + "but contained", + Maps.<@Nullable Object, @Nullable A>immutableEntry( + excludedKey, actualValue))) .add(fact("full map", actualCustomStringRepresentationForPackageMembersToCall())) .addAll(exceptions.describeAsAdditionalInfo()) .build()); @@ -714,14 +706,14 @@ public Ordered containsAtLeast(@Nullable Object k0, @Nullable E v0, @Nullable Ob @CanIgnoreReturnValue public Ordered containsExactlyEntriesIn(Map 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); } /** @@ -733,13 +725,13 @@ public Ordered containsAtLeastEntriesIn(Map expectedMap) { if (expectedMap.isEmpty()) { return IN_ORDER; } - return internalContainsEntriesIn(expectedMap, /* allowUnexpected = */ true); + return internalContainsEntriesIn(expectedMap, /* allowUnexpected= */ true); } - private Ordered internalContainsEntriesIn( + private Ordered internalContainsEntriesIn( Map expectedMap, boolean allowUnexpected) { - final Correspondence.ExceptionStore exceptions = Correspondence.ExceptionStore.forMapValues(); - MapDifference diff = + Correspondence.ExceptionStore exceptions = Correspondence.ExceptionStore.forMapValues(); + MapDifference<@Nullable Object, A, V> diff = MapDifference.create( getCastSubject(), expectedMap, @@ -768,19 +760,13 @@ public boolean test(A actualValue, E expectedValue) { return ALREADY_FAILED; } - @SuppressWarnings("UnnecessaryAnonymousClass") // for Java 7 compatibility - private Differ differ(final Correspondence.ExceptionStore exceptions) { - return new Differ() { - @Override - public String diff(A actual, V expected) { - return correspondence.safeFormatDiff(actual, expected, exceptions); - } - }; + private Differ differ(Correspondence.ExceptionStore exceptions) { + return (actual, expected) -> correspondence.safeFormatDiff(actual, expected, exceptions); } @SuppressWarnings("unchecked") // throwing ClassCastException is the correct behaviour private Map getCastSubject() { - return (Map) actual; + return (Map) checkNotNull(actual); } } } diff --git a/core/src/main/java/com/google/common/truth/MathUtil.java b/core/src/main/java/com/google/common/truth/MathUtil.java index 791ac4d70..2fb44da5c 100644 --- a/core/src/main/java/com/google/common/truth/MathUtil.java +++ b/core/src/main/java/com/google/common/truth/MathUtil.java @@ -16,12 +16,46 @@ package com.google.common.truth; +import static java.lang.Math.subtractExact; + import com.google.common.primitives.Doubles; /** Math utilities to be shared by numeric subjects. */ final class MathUtil { private MathUtil() {} + /** + * Returns true iff {@code left} and {@code right} are values within {@code tolerance} of each + * other. + */ + /* package */ static boolean equalWithinTolerance(long left, long right, long tolerance) { + try { + // subtractExact is always desugared. + @SuppressWarnings("Java7ApiChecker") + long absDiff = Math.abs(subtractExact(left, right)); + return 0 <= absDiff && absDiff <= Math.abs(tolerance); + } catch (ArithmeticException e) { + // The numbers are so far apart their difference isn't even a long. + return false; + } + } + + /** + * Returns true iff {@code left} and {@code right} are values within {@code tolerance} of each + * other. + */ + /* package */ static boolean equalWithinTolerance(int left, int right, int tolerance) { + try { + // subtractExact is always desugared. + @SuppressWarnings("Java7ApiChecker") + int absDiff = Math.abs(subtractExact(left, right)); + return 0 <= absDiff && absDiff <= Math.abs(tolerance); + } catch (ArithmeticException e) { + // The numbers are so far apart their difference isn't even a int. + return false; + } + } + /** * Returns true iff {@code left} and {@code right} are finite values within {@code tolerance} of * each other. Note that both this method and {@link #notEqualWithinTolerance} returns false if 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 a80545ad2..96cef441b 100644 --- a/core/src/main/java/com/google/common/truth/MultimapSubject.java +++ b/core/src/main/java/com/google/common/truth/MultimapSubject.java @@ -40,12 +40,13 @@ 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; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Propositions for {@link Multimap} subjects. @@ -56,14 +57,9 @@ 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 @@ protected MultimapSubject(FailureMetadata metadata, @Nullable Multimap mul /** 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 final void isNotEmpty() { /** 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 entry = immutableEntry(key, value); - List> entryList = ImmutableList.of(entry); + Map.Entry<@Nullable Object, @Nullable Object> entry = immutableEntry(key, value); + ImmutableList> 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 final void containsEntry(@Nullable Object key, @Nullable Object value) { fact("though it did contain values with that key", actual.asMap().get(key)), fact("full contents", actualCustomStringRepresentationForPackageMembersToCall())); } else if (actual.containsValue(value)) { - Set 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 final void containsEntry(@Nullable Object key, @Nullable Object value) { /** 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 final void doesNotContainEntry(@Nullable Object key, @Nullable Object val * currently they must perform it _after_. */ public IterableSubject valuesForKey(@Nullable Object key) { - return check("valuesForKey(%s)", key).that(((Multimap) actual).get(key)); + return check("valuesForKey(%s)", key) + .that(((Multimap<@Nullable Object, @Nullable Object>) checkNotNull(actual)).get(key)); } @Override @@ -202,9 +201,9 @@ public final void isEqualTo(@Nullable Object other) { 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 final void isEqualTo(@Nullable Object other) { @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 final Ordered containsExactlyEntriesIn(Multimap expectedMultimap) { @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 final Ordered containsAtLeastEntriesIn(Multimap expectedMultimap) { /** 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(); } /** @@ -321,7 +323,7 @@ public final Ordered containsAtLeast( return containsAtLeastEntriesIn(accumulateMultimap(k0, v0, rest)); } - private static Multimap accumulateMultimap( + private static ListMultimap<@Nullable Object, @Nullable Object> accumulateMultimap( @Nullable Object k0, @Nullable Object v0, @Nullable Object... rest) { checkArgument( rest.length % 2 == 0, @@ -329,7 +331,8 @@ private static Multimap accumulateMultimap( + "(i.e., the number of key/value parameters (%s) must be even).", rest.length + 2); - LinkedListMultimap 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 @@ private static Multimap accumulateMultimap( private Factory> iterableEntries() { return new Factory>() { @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 @@ private static class IterableEntries extends IterableSubject { 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 @@ private class MultimapInOrder implements Ordered { @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 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 void inOrder() { * 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 @@ private static boolean advanceToFind(Iterator iterator, Object value) { return false; } - private static Collection get(Multimap multimap, @Nullable Object key) { + @SuppressWarnings("EmptyList") // ImmutableList doesn't support nullable types + private static Collection get( + Multimap 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 difference = LinkedListMultimap.create(); + ListMultimap<@Nullable Object, @Nullable Object> difference = LinkedListMultimap.create(); for (Object key : minuend.keySet()) { List valDifference = difference( @@ -461,8 +467,9 @@ private static Collection get(Multimap multimap, @Nullable Object k } private static List difference(List minuend, List subtrahend) { - LinkedHashMultiset remaining = LinkedHashMultiset.create(subtrahend); - List 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 @@ private static String countDuplicatesMultimap(Multimap multimap) { */ private static Multimap annotateEmptyStringsMultimap(Multimap multimap) { if (multimap.containsKey("") || multimap.containsValue("")) { - ListMultimap 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 @@ private static String countDuplicatesMultimap(Multimap multimap) { *

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 UsingCorrespondence comparingValuesUsing( - Correspondence correspondence) { + public + UsingCorrespondence comparingValuesUsing( + Correspondence correspondence) { return new UsingCorrespondence<>(correspondence); } @@ -542,7 +554,7 @@ public UsingCorrespondence comparingValuesUsing( * *

Note that keys will always be compared with regular object equality ({@link Object#equals}). */ - public final class UsingCorrespondence { + public final class UsingCorrespondence { private final Correspondence correspondence; @@ -554,10 +566,10 @@ private UsingCorrespondence(Correspondence correspondence) * 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 actualValues = getCastActual().asMap().get(expectedKey); + Collection 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 void containsEntry(@Nullable Object expectedKey, @Nullable E expectedValu * 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 actualValues = getCastActual().asMap().get(excludedKey); + public void doesNotContainEntry(@Nullable Object excludedKey, E excludedValue) { + if (checkNotNull(actual).containsKey(excludedKey)) { + Collection actualValues = checkNotNull(getCastActual().asMap().get(excludedKey)); List matchingValues = new ArrayList<>(); Correspondence.ExceptionStore exceptions = Correspondence.ExceptionStore.forMapValues(); for (A actualValue : actualValues) { @@ -714,7 +726,8 @@ public Ordered containsExactlyEntriesIn(Multimap expectedMultima * public containsExactlyEntriesIn method. This is recommended by Effective Java item 31 (3rd * edition). */ - private Ordered internalContainsExactlyEntriesIn( + @SuppressWarnings("deprecation") // TODO(b/134064106): design an alternative to no-arg check() + private Ordered internalContainsExactlyEntriesIn( Multimap 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 @@ private Ordered internalContainsExactlyEntriesIn( // complexity for little gain. return check() .about(iterableEntries()) - .that(actual.entries()) - .comparingElementsUsing(new EntryCorrespondence(correspondence)) + .that(checkNotNull(actual).entries()) + .comparingElementsUsing(MultimapSubject.entryCorrespondence(correspondence)) .containsExactlyElementsIn(expectedMultimap.entries()); } @@ -748,7 +761,8 @@ public Ordered containsAtLeastEntriesIn(Multimap expectedMultima * public containsAtLeastEntriesIn method. This is recommended by Effective Java item 31 (3rd * edition). */ - private Ordered internalContainsAtLeastEntriesIn( + @SuppressWarnings("deprecation") // TODO(b/134064106): design an alternative to no-arg check() + private Ordered internalContainsAtLeastEntriesIn( Multimap 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 @@ private Ordered internalContainsAtLeastEntriesIn( // complexity for little gain. return check() .about(iterableEntries()) - .that(actual.entries()) - .comparingElementsUsing(new EntryCorrespondence(correspondence)) + .that(checkNotNull(actual).entries()) + .comparingElementsUsing(MultimapSubject.entryCorrespondence(correspondence)) .containsAtLeastElementsIn(expectedMultimap.entries()); } @@ -797,30 +811,20 @@ public Ordered containsAtLeast(@Nullable Object k0, @Nullable E v0, @Nullable Ob @SuppressWarnings("unchecked") // throwing ClassCastException is the correct behaviour private Multimap getCastActual() { - return (Multimap) actual; + return (Multimap) checkNotNull(actual); } } - private static final class EntryCorrespondence - extends Correspondence, Map.Entry> { - - private final Correspondence valueCorrespondence; - - EntryCorrespondence(Correspondence valueCorrespondence) { - this.valueCorrespondence = valueCorrespondence; - } - - @Override - public boolean compare(Map.Entry actual, Map.Entry 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> entryCorrespondence( + Correspondence valueCorrespondence) { + return Correspondence.from( + (Map.Entry actual, Map.Entry 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 40037fe38..264b30c89 100644 --- a/core/src/main/java/com/google/common/truth/MultisetSubject.java +++ b/core/src/main/java/com/google/common/truth/MultisetSubject.java @@ -16,9 +16,10 @@ 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; +import org.jspecify.annotations.Nullable; /** * Propositions for {@link Multiset} subjects. @@ -27,17 +28,17 @@ */ public final class MultisetSubject extends IterableSubject { - private final Multiset actual; + private final @Nullable Multiset actual; MultisetSubject(FailureMetadata metadata, @Nullable Multiset multiset) { - super(metadata, multiset); + super(metadata, multiset, /* typeDescriptionOverride= */ "multiset"); this.actual = multiset; } /** 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 2b6184e42..712fc30a0 100644 --- a/core/src/main/java/com/google/common/truth/ObjectArraySubject.java +++ b/core/src/main/java/com/google/common/truth/ObjectArraySubject.java @@ -15,24 +15,25 @@ */ package com.google.common.truth; +import static com.google.common.base.Preconditions.checkNotNull; + import java.util.Arrays; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * A Subject for {@code Object[]} and more generically {@code T[]}. * * @author Christian Gruber */ -public final class ObjectArraySubject extends AbstractArraySubject { - private final T[] actual; +public final class ObjectArraySubject extends AbstractArraySubject { + private final T @Nullable [] actual; - ObjectArraySubject( - FailureMetadata metadata, @Nullable T @Nullable [] o, @Nullable String typeDescription) { + ObjectArraySubject(FailureMetadata metadata, 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/extensions/java8/src/main/java/com/google/common/truth/OptionalDoubleSubject.java b/core/src/main/java/com/google/common/truth/OptionalDoubleSubject.java similarity index 75% rename from extensions/java8/src/main/java/com/google/common/truth/OptionalDoubleSubject.java rename to core/src/main/java/com/google/common/truth/OptionalDoubleSubject.java index 4a9aa5f88..96403a4bc 100644 --- a/extensions/java8/src/main/java/com/google/common/truth/OptionalDoubleSubject.java +++ b/core/src/main/java/com/google/common/truth/OptionalDoubleSubject.java @@ -19,16 +19,19 @@ import static com.google.common.truth.Fact.simpleFact; import java.util.OptionalDouble; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Propositions for Java 8 {@link OptionalDouble} subjects. * * @author Ben Douglass + * @since 1.3.0 (previously part of {@code truth-java8-extension}) */ +@SuppressWarnings("Java7ApiChecker") // used only from APIs with Java 8 in their signatures +@IgnoreJRERequirement public final class OptionalDoubleSubject extends Subject { - private final OptionalDouble actual; + private final @Nullable OptionalDouble actual; OptionalDoubleSubject( FailureMetadata failureMetadata, @@ -78,7 +81,18 @@ public void hasValue(double expected) { } } - public static Subject.Factory optionalDoubles() { + /** + * Obsolete factory instance. This factory was previously necessary for assertions like {@code + * assertWithMessage(...).about(optionalDoubles()).that(optional)....}. Now, you can perform + * assertions like that without the {@code about(...)} call. + * + * @deprecated Instead of {@code about(optionalDoubles()).that(...)}, use just {@code that(...)}. + * Similarly, instead of {@code assertAbout(optionalDoubles()).that(...)}, use just {@code + * assertThat(...)}. + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") // We want users to remove the surrounding call entirely. + public static Factory optionalDoubles() { return (metadata, subject) -> new OptionalDoubleSubject(metadata, subject, "optionalDouble"); } } diff --git a/extensions/java8/src/main/java/com/google/common/truth/OptionalIntSubject.java b/core/src/main/java/com/google/common/truth/OptionalIntSubject.java similarity index 72% rename from extensions/java8/src/main/java/com/google/common/truth/OptionalIntSubject.java rename to core/src/main/java/com/google/common/truth/OptionalIntSubject.java index 957594f72..37921f8b4 100644 --- a/extensions/java8/src/main/java/com/google/common/truth/OptionalIntSubject.java +++ b/core/src/main/java/com/google/common/truth/OptionalIntSubject.java @@ -19,15 +19,18 @@ import static com.google.common.truth.Fact.simpleFact; import java.util.OptionalInt; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Propositions for Java 8 {@link OptionalInt} subjects. * * @author Ben Douglass + * @since 1.3.0 (previously part of {@code truth-java8-extension}) */ +@SuppressWarnings("Java7ApiChecker") // used only from APIs with Java 8 in their signatures +@IgnoreJRERequirement public final class OptionalIntSubject extends Subject { - private final OptionalInt actual; + private final @Nullable OptionalInt actual; OptionalIntSubject( FailureMetadata failureMetadata, @@ -71,7 +74,18 @@ public void hasValue(int expected) { } } - public static Subject.Factory optionalInts() { + /** + * Obsolete factory instance. This factory was previously necessary for assertions like {@code + * assertWithMessage(...).about(optionalInts()).that(optional)....}. Now, you can perform + * assertions like that without the {@code about(...)} call. + * + * @deprecated Instead of {@code about(optionalInts()).that(...)}, use just {@code that(...)}. + * Similarly, instead of {@code assertAbout(optionalInts()).that(...)}, use just {@code + * assertThat(...)}. + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") // We want users to remove the surrounding call entirely. + public static Factory optionalInts() { return (metadata, subject) -> new OptionalIntSubject(metadata, subject, "optionalInt"); } } diff --git a/extensions/java8/src/main/java/com/google/common/truth/OptionalLongSubject.java b/core/src/main/java/com/google/common/truth/OptionalLongSubject.java similarity index 72% rename from extensions/java8/src/main/java/com/google/common/truth/OptionalLongSubject.java rename to core/src/main/java/com/google/common/truth/OptionalLongSubject.java index 9b57b9a4a..237706b6c 100644 --- a/extensions/java8/src/main/java/com/google/common/truth/OptionalLongSubject.java +++ b/core/src/main/java/com/google/common/truth/OptionalLongSubject.java @@ -19,15 +19,18 @@ import static com.google.common.truth.Fact.simpleFact; import java.util.OptionalLong; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Propositions for Java 8 {@link OptionalLong} subjects. * * @author Ben Douglass + * @since 1.3.0 (previously part of {@code truth-java8-extension}) */ +@SuppressWarnings("Java7ApiChecker") // used only from APIs with Java 8 in their signatures +@IgnoreJRERequirement public final class OptionalLongSubject extends Subject { - private final OptionalLong actual; + private final @Nullable OptionalLong actual; OptionalLongSubject( FailureMetadata failureMetadata, @@ -71,7 +74,18 @@ public void hasValue(long expected) { } } - public static Subject.Factory optionalLongs() { + /** + * Obsolete factory instance. This factory was previously necessary for assertions like {@code + * assertWithMessage(...).about(optionalLongs()).that(optional)....}. Now, you can perform + * assertions like that without the {@code about(...)} call. + * + * @deprecated Instead of {@code about(optionalLongs()).that(...)}, use just {@code that(...)}. + * Similarly, instead of {@code assertAbout(optionalLongs()).that(...)}, use just {@code + * assertThat(...)}. + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") // We want users to remove the surrounding call entirely. + public static Factory optionalLongs() { return (metadata, subject) -> new OptionalLongSubject(metadata, subject, "optionalLong"); } } diff --git a/extensions/java8/src/main/java/com/google/common/truth/OptionalSubject.java b/core/src/main/java/com/google/common/truth/OptionalSubject.java similarity index 69% rename from extensions/java8/src/main/java/com/google/common/truth/OptionalSubject.java rename to core/src/main/java/com/google/common/truth/OptionalSubject.java index 1b9fb5c96..246888041 100644 --- a/extensions/java8/src/main/java/com/google/common/truth/OptionalSubject.java +++ b/core/src/main/java/com/google/common/truth/OptionalSubject.java @@ -19,19 +19,24 @@ import static com.google.common.truth.Fact.simpleFact; import java.util.Optional; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Propositions for Java 8 {@link Optional} subjects. * * @author Christian Gruber + * @since 1.3.0 (previously part of {@code truth-java8-extension}) */ +@SuppressWarnings("Java7ApiChecker") // used only from APIs with Java 8 in their signatures +@IgnoreJRERequirement public final class OptionalSubject extends Subject { - private final Optional actual; + @SuppressWarnings("NullableOptional") // Truth always accepts nulls, no matter the type + private final @Nullable Optional actual; OptionalSubject( FailureMetadata failureMetadata, - @Nullable Optional subject, + @SuppressWarnings("NullableOptional") // Truth always accepts nulls, no matter the type + @Nullable Optional subject, @Nullable String typeDescription) { super(failureMetadata, subject, typeDescription); this.actual = subject; @@ -68,7 +73,7 @@ public void isEmpty() { * assertThat(myOptional.get()).contains("foo"); * } */ - public void hasValue(Object expected) { + public void hasValue(@Nullable Object expected) { if (expected == null) { throw new NullPointerException("Optional cannot have a null value."); } @@ -81,7 +86,18 @@ public void hasValue(Object expected) { } } - public static Subject.Factory> optionals() { + /** + * Obsolete factory instance. This factory was previously necessary for assertions like {@code + * assertWithMessage(...).about(paths()).that(path)....}. Now, you can perform assertions like + * that without the {@code about(...)} call. + * + * @deprecated Instead of {@code about(optionals()).that(...)}, use just {@code that(...)}. + * Similarly, instead of {@code assertAbout(optionals()).that(...)}, use just {@code + * assertThat(...)}. + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") // We want users to remove the surrounding call entirely. + public static Factory> optionals() { return (metadata, subject) -> new OptionalSubject(metadata, subject, "optional"); } } 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 00ed0cc3c..2f7efed38 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/PathSubject.java b/core/src/main/java/com/google/common/truth/PathSubject.java new file mode 100644 index 000000000..5957779d1 --- /dev/null +++ b/core/src/main/java/com/google/common/truth/PathSubject.java @@ -0,0 +1,49 @@ +/* + * 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 com.google.common.annotations.GwtIncompatible; +import com.google.j2objc.annotations.J2ObjCIncompatible; +import java.nio.file.Path; +import org.jspecify.annotations.Nullable; + +/** + * Assertions for {@link Path} instances. + * + * @since 1.3.0 (previously part of {@code truth-java8-extension}) + */ +@GwtIncompatible +@J2ObjCIncompatible +@J2ktIncompatible +public final class PathSubject extends Subject { + PathSubject(FailureMetadata failureMetadata, @Nullable Path actual) { + super(failureMetadata, actual); + } + + /** + * Obsolete factory instance. This factory was previously necessary for assertions like {@code + * assertWithMessage(...).about(intStreams()).that(stream)....}. Now, you can perform assertions + * like that without the {@code about(...)} call. + * + * @deprecated Instead of {@code about(paths()).that(...)}, use just {@code that(...)}. Similarly, + * instead of {@code assertAbout(paths()).that(...)}, use just {@code assertThat(...)}. + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") // We want users to remove the surrounding call entirely. + public static Factory paths() { + return PathSubject::new; + } +} 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 c0719697a..f3e9059f6 100644 --- a/core/src/main/java/com/google/common/truth/Platform.java +++ b/core/src/main/java/com/google/common/truth/Platform.java @@ -15,6 +15,7 @@ */ 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; @@ -23,6 +24,7 @@ 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; @@ -30,7 +32,7 @@ import java.lang.reflect.Method; import java.util.List; import java.util.regex.Pattern; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; import org.junit.ComparisonFailure; import org.junit.rules.TestRule; @@ -54,20 +56,23 @@ static boolean containsMatch(String actual, String regex) { } /** - * Returns an array containing all of the exceptions that were suppressed to deliver the given + * Returns an array containing all the exceptions that were suppressed to deliver the given * exception. If suppressed exceptions are not supported (pre-Java 1.7), an empty array will be * returned. */ 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); } } @@ -160,20 +165,12 @@ private static ImmutableList splitLines(String s) { abstract static class PlatformComparisonFailure extends ComparisonFailure { private final String message; - /** Separate cause field, in case initCause() fails. */ - private final @Nullable Throwable cause; - PlatformComparisonFailure( String message, String expected, String actual, @Nullable Throwable cause) { super(message, expected, actual); this.message = message; - this.cause = cause; - try { - initCause(cause); - } catch (IllegalStateException alreadyInitializedBecauseOfHarmonyBug) { - // See Truth.SimpleAssertionError. - } + initCause(cause); } @Override @@ -181,17 +178,11 @@ public final String getMessage() { return message; } - @Override - @SuppressWarnings("UnsynchronizedOverridesSynchronized") - public final Throwable getCause() { - return cause; - } - // To avoid printing the class name before the message. // TODO(cpovirk): Write a test that fails without this. Ditto for SimpleAssertionError. @Override public final String toString() { - return getLocalizedMessage(); + return checkNotNull(getLocalizedMessage()); } } @@ -204,7 +195,7 @@ static String floatToString(float value) { } /** Turns a non-double, non-float object into a string. */ - static String stringValueOfNonFloatingPoint(Object o) { + static String stringValueOfNonFloatingPoint(@Nullable Object o) { return String.valueOf(o); } @@ -215,7 +206,7 @@ static String getStackTraceAsString(Throwable throwable) { /** 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"); } /** @@ -308,27 +299,28 @@ static boolean isKotlinRange(Iterable iterable) { // (If the class isn't available, then nothing could be an instance of ClosedRange.) } - private static final Supplier> closedRangeClassIfAvailable = - memoize( + // 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.) + * 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, Object needle) { + static boolean kotlinRangeContains(Iterable haystack, @Nullable Object needle) { try { return (boolean) closedRangeContainsMethod.get().invoke(haystack, needle); } catch (InvocationTargetException e) { @@ -349,10 +341,18 @@ static boolean kotlinRangeContains(Iterable haystack, Object needle) { memoize( () -> { try { - return closedRangeClassIfAvailable.get().getMethod("contains", Comparable.class); + return checkNotNull(closedRangeClassIfAvailable.get()) + .getMethod("contains", Comparable.class); } catch (NoSuchMethodException e) { // That method exists. (But see the discussion at closedRangeClassIfAvailable above.) throw newLinkageError(e); } }); + + static boolean classMetadataUnsupported() { + // https://github.com/google/truth/issues/198 + // TODO(cpovirk): Consider whether to remove instanceof tests under GWT entirely. + // TODO(cpovirk): Run more Truth tests under GWT, and add tests for this. + return false; + } } 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 a4cf61cf5..925ea571f 100644 --- a/core/src/main/java/com/google/common/truth/PrimitiveBooleanArraySubject.java +++ b/core/src/main/java/com/google/common/truth/PrimitiveBooleanArraySubject.java @@ -15,8 +15,10 @@ */ 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; +import org.jspecify.annotations.Nullable; /** * A Subject for {@code boolean[]}. @@ -24,7 +26,7 @@ * @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) { @@ -33,6 +35,6 @@ public final class PrimitiveBooleanArraySubject extends AbstractArraySubject { } 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 204ad3840..1f649c6e3 100644 --- a/core/src/main/java/com/google/common/truth/PrimitiveByteArraySubject.java +++ b/core/src/main/java/com/google/common/truth/PrimitiveByteArraySubject.java @@ -15,8 +15,10 @@ */ 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; +import org.jspecify.annotations.Nullable; /** * A Subject for {@code byte[]}. @@ -24,7 +26,7 @@ * @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) { @@ -33,6 +35,6 @@ public final class PrimitiveByteArraySubject extends AbstractArraySubject { } 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 5a89e71c4..0ee8c0577 100644 --- a/core/src/main/java/com/google/common/truth/PrimitiveCharArraySubject.java +++ b/core/src/main/java/com/google/common/truth/PrimitiveCharArraySubject.java @@ -15,8 +15,10 @@ */ 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; +import org.jspecify.annotations.Nullable; /** * A Subject for {@code char[]}. @@ -24,7 +26,7 @@ * @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) { @@ -33,6 +35,6 @@ public final class PrimitiveCharArraySubject extends AbstractArraySubject { } 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 4fb7ad455..ce22539f9 100644 --- a/core/src/main/java/com/google/common/truth/PrimitiveDoubleArraySubject.java +++ b/core/src/main/java/com/google/common/truth/PrimitiveDoubleArraySubject.java @@ -23,7 +23,7 @@ import com.google.common.primitives.Doubles; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Arrays; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * A Subject for {@code double[]}. @@ -31,7 +31,7 @@ * @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) { @@ -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 void isEqualTo(Object expected) { * */ @Override - public void isNotEqualTo(Object expected) { + public void isNotEqualTo(@Nullable Object expected) { super.isNotEqualTo(expected); } @@ -234,7 +234,7 @@ public void containsNoneOf(double[] excluded) { private IterableSubject iterableSubject() { return checkNoNeedToDisplayBothValues("asList()") .about(iterablesWithCustomDoubleToString()) - .that(Doubles.asList(actual)); + .that(Doubles.asList(checkNotNull(actual))); } /* @@ -248,7 +248,7 @@ private IterableSubject iterableSubject() { private Factory> iterablesWithCustomDoubleToString() { return new Factory>() { @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 IterableSubject createSubject(FailureMetadata metadata, Iterable actua 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 580ff4f41..9ce5a2894 100644 --- a/core/src/main/java/com/google/common/truth/PrimitiveFloatArraySubject.java +++ b/core/src/main/java/com/google/common/truth/PrimitiveFloatArraySubject.java @@ -23,7 +23,7 @@ import com.google.common.primitives.Floats; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Arrays; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * A Subject for {@code float[]}. @@ -31,7 +31,7 @@ * @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) { @@ -64,7 +64,7 @@ public final class PrimitiveFloatArraySubject extends AbstractArraySubject { * */ @Override - public void isEqualTo(Object expected) { + public void isEqualTo(@Nullable Object expected) { super.isEqualTo(expected); } @@ -83,7 +83,7 @@ public void isEqualTo(Object expected) { * */ @Override - public void isNotEqualTo(Object expected) { + public void isNotEqualTo(@Nullable Object expected) { super.isNotEqualTo(expected); } @@ -239,7 +239,7 @@ public void containsNoneOf(float[] excluded) { private IterableSubject iterableSubject() { return checkNoNeedToDisplayBothValues("asList()") .about(iterablesWithCustomFloatToString()) - .that(Floats.asList(actual)); + .that(Floats.asList(checkNotNull(actual))); } /* @@ -253,7 +253,7 @@ private IterableSubject iterableSubject() { private Factory> iterablesWithCustomFloatToString() { return new Factory>() { @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 IterableSubject createSubject(FailureMetadata metadata, Iterable actua 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 0dc669817..b8ee605a1 100644 --- a/core/src/main/java/com/google/common/truth/PrimitiveIntArraySubject.java +++ b/core/src/main/java/com/google/common/truth/PrimitiveIntArraySubject.java @@ -15,8 +15,10 @@ */ 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; +import org.jspecify.annotations.Nullable; /** * A Subject for {@code int[]}. @@ -24,7 +26,7 @@ * @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) { @@ -33,6 +35,6 @@ public final class PrimitiveIntArraySubject extends AbstractArraySubject { } 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 53c0d5963..8b34ddd4e 100644 --- a/core/src/main/java/com/google/common/truth/PrimitiveLongArraySubject.java +++ b/core/src/main/java/com/google/common/truth/PrimitiveLongArraySubject.java @@ -15,8 +15,10 @@ */ 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; +import org.jspecify.annotations.Nullable; /** * A Subject for {@code long[]}. @@ -24,7 +26,7 @@ * @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) { @@ -33,6 +35,6 @@ public final class PrimitiveLongArraySubject extends AbstractArraySubject { } 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 c96bf3f39..147eeb102 100644 --- a/core/src/main/java/com/google/common/truth/PrimitiveShortArraySubject.java +++ b/core/src/main/java/com/google/common/truth/PrimitiveShortArraySubject.java @@ -15,8 +15,10 @@ */ 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; +import org.jspecify.annotations.Nullable; /** * A Subject for {@code short[]}. @@ -24,7 +26,7 @@ * @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) { @@ -33,6 +35,6 @@ public final class PrimitiveShortArraySubject extends AbstractArraySubject { } 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 4451ee464..1bb1b2c5e 100644 --- a/core/src/main/java/com/google/common/truth/SimpleSubjectBuilder.java +++ b/core/src/main/java/com/google/common/truth/SimpleSubjectBuilder.java @@ -17,7 +17,7 @@ import static com.google.common.base.Preconditions.checkNotNull; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * In a fluent assertion chain, exposes the most common {@code that} method, which accepts a value @@ -44,4 +44,5 @@ public final class SimpleSubjectBuilder { 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 32841c853..6284a0168 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.List; import java.util.ListIterator; import java.util.Set; +import org.jspecify.annotations.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 @@ static void cleanStackTrace(Throwable throwable) { private final Throwable throwable; private final List 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 @@ private StackTraceCleaner(Throwable throwable) { // 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 seenThrowables) { // Stack trace cleaning can be disabled using a system property. if (isStackTraceCleaningDisabled()) { @@ -100,7 +104,7 @@ private void clean(Set seenThrowables) { * 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 @@ private void endStreak() { 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(); @@ -253,8 +258,8 @@ private static boolean isFromClassOrClassNestedInside( 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; } @@ -354,6 +359,7 @@ private enum StackFrameType { "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"), 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 baaaae67a..288662afe 100644 --- a/core/src/main/java/com/google/common/truth/StandardSubjectBuilder.java +++ b/core/src/main/java/com/google/common/truth/StandardSubjectBuilder.java @@ -18,14 +18,22 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtIncompatible; -import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; import com.google.common.collect.Multiset; import com.google.common.collect.Table; +import com.google.j2objc.annotations.J2ObjCIncompatible; import java.math.BigDecimal; +import java.nio.file.Path; import java.util.Map; -import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; /** * In a fluent assertion chain, an object with which you can do any of the following: @@ -61,10 +69,9 @@ public static StandardSubjectBuilder forCustomFailureStrategy(FailureStrategy fa this.metadataDoNotReferenceDirectly = checkNotNull(metadata); } - @SuppressWarnings({"unchecked", "rawtypes"}) public final > ComparableSubject that( @Nullable ComparableT actual) { - return new ComparableSubject(metadata(), actual) {}; + return new ComparableSubject(metadata(), actual) {}; } public final BigDecimalSubject that(@Nullable BigDecimal actual) { @@ -76,6 +83,7 @@ public final Subject that(@Nullable Object actual) { } @GwtIncompatible("ClassSubject.java") + @J2ktIncompatible public final ClassSubject that(@Nullable Class actual) { return new ClassSubject(metadata(), actual); } @@ -112,7 +120,8 @@ public final IterableSubject that(@Nullable Iterable actual) { return new IterableSubject(metadata(), actual); } - public final ObjectArraySubject that(@Nullable T @Nullable [] actual) { + @SuppressWarnings("AvoidObjectArrays") + public final ObjectArraySubject that(T @Nullable [] actual) { return new ObjectArraySubject<>(metadata(), actual, "array"); } @@ -148,7 +157,7 @@ public final PrimitiveDoubleArraySubject that(double @Nullable [] actual) { return new PrimitiveDoubleArraySubject(metadata(), actual, "array"); } - public final GuavaOptionalSubject that(@Nullable Optional actual) { + public final GuavaOptionalSubject that(com.google.common.base.@Nullable Optional actual) { return new GuavaOptionalSubject(metadata(), actual, "optional"); } @@ -168,6 +177,89 @@ public final TableSubject that(@Nullable Table actual) { return new TableSubject(metadata(), actual); } + /** + * @since 1.3.0 (with access to {@link OptionalSubject} previously part of {@code + * truth-java8-extension}) + */ + @SuppressWarnings({ + "Java7ApiChecker", // no more dangerous that wherever the user got the Optional + "NullableOptional", // Truth always accepts nulls, no matter the type + }) + public final OptionalSubject that(@Nullable Optional actual) { + return new OptionalSubject(metadata(), actual, "optional"); + } + + /** + * @since 1.4.0 (with access to {@link OptionalIntSubject} previously part of {@code + * truth-java8-extension}) + */ + @SuppressWarnings( + "Java7ApiChecker") // no more dangerous that wherever the user got the OptionalInt + public final OptionalIntSubject that(@Nullable OptionalInt actual) { + return new OptionalIntSubject(metadata(), actual, "optionalInt"); + } + + /** + * @since 1.4.0 (with access to {@link OptionalLongSubject} previously part of {@code + * truth-java8-extension}) + */ + @SuppressWarnings( + "Java7ApiChecker") // no more dangerous that wherever the user got the OptionalLong + public final OptionalLongSubject that(@Nullable OptionalLong actual) { + return new OptionalLongSubject(metadata(), actual, "optionalLong"); + } + + /** + * @since 1.4.0 (with access to {@link OptionalDoubleSubject} previously part of {@code + * truth-java8-extension}) + */ + @SuppressWarnings( + "Java7ApiChecker") // no more dangerous that wherever the user got the OptionalDouble + public final OptionalDoubleSubject that(@Nullable OptionalDouble actual) { + return new OptionalDoubleSubject(metadata(), actual, "optionalDouble"); + } + + /** + * @since 1.3.0 (with access to {@link StreamSubject} previously part of {@code + * truth-java8-extension}) + */ + @SuppressWarnings("Java7ApiChecker") // no more dangerous that wherever the user got the Stream + public final StreamSubject that(@Nullable Stream actual) { + return new StreamSubject(metadata(), actual); + } + + /** + * @since 1.4.0 (with access to {@link IntStreamSubject} previously part of {@code + * truth-java8-extension}) + */ + @SuppressWarnings("Java7ApiChecker") // no more dangerous that wherever the user got the IntStream + public final IntStreamSubject that(@Nullable IntStream actual) { + return new IntStreamSubject(metadata(), actual); + } + + /** + * @since 1.4.0 (with access to {@link LongStreamSubject} previously part of {@code + * truth-java8-extension}) + */ + @SuppressWarnings( + "Java7ApiChecker") // no more dangerous that wherever the user got the LongStream + public final LongStreamSubject that(@Nullable LongStream actual) { + return new LongStreamSubject(metadata(), actual); + } + + // TODO(b/64757353): Add support for DoubleStream? + + /** + * @since 1.4.0 (with access to {@link PathSubject} previously part of {@code + * truth-java8-extension}) + */ + @GwtIncompatible + @J2ObjCIncompatible + @J2ktIncompatible + public final PathSubject that(@Nullable Path actual) { + return new PathSubject(metadata(), actual); + } + /** * Returns a new instance that will output the given message before the main failure message. If * this method is called multiple times, the messages will appear in the order that they were diff --git a/core/src/main/java/com/google/common/truth/StreamSubject.java b/core/src/main/java/com/google/common/truth/StreamSubject.java new file mode 100644 index 000000000..0140d6dcd --- /dev/null +++ b/core/src/main/java/com/google/common/truth/StreamSubject.java @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2016 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.base.Suppliers.memoize; +import static com.google.common.truth.Fact.fact; +import static java.util.stream.Collectors.toCollection; + +import com.google.common.base.Supplier; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; + +/** + * Propositions for {@link Stream} subjects. + * + *

Note: When you perform an assertion based on the contents of the stream, or when + * any assertion fails, the wrapped stream will be drained immediately into a private + * collection to provide more readable failure messages. This consumes the stream. Take care if you + * intend to leave the stream un-consumed or if the stream is very large or infinite. + * + *

If you intend to make multiple assertions on the contents of the same stream, you should + * instead first collect the contents of the stream into a collection and then assert directly on + * that. + * + *

For very large or infinite streams you may want to first {@linkplain Stream#limit limit} the + * stream before asserting on it. + * + * @author Kurt Alfred Kluever + * @since 1.3.0 (previously part of {@code truth-java8-extension}) + */ +@SuppressWarnings("Java7ApiChecker") // used only from APIs with Java 8 in their signatures +@IgnoreJRERequirement +public final class StreamSubject extends Subject { + // Storing the FailureMetadata instance is not usually advisable. + private final FailureMetadata metadata; + private final @Nullable Stream actual; + private final Supplier<@Nullable List> listSupplier; + + StreamSubject( + FailureMetadata metadata, + @Nullable Stream actual, + Supplier<@Nullable List> listSupplier) { + super(metadata, actual); + this.metadata = metadata; + this.actual = actual; + this.listSupplier = listSupplier; + } + + StreamSubject(FailureMetadata metadata, @Nullable Stream actual) { + /* + * As discussed in the Javadoc, we're a *little* accommodating of streams that have already been + * collected (or are outright broken, like some mocks), and we avoid collecting the contents + * until we want them. So, if you want to perform an assertion like + * `assertThat(previousStream).isSameInstanceAs(firstStream)`, we'll let you do that, even if + * you've already collected the stream. This way, `assertThat(Stream)` works as well as + * `assertThat(Object)` for streams, following the usual rules of overloading. (This would also + * help if we someday make `assertThat(Object)` automatically delegate to `assertThat(Stream)` + * when passed a `Stream`.) + */ + this(metadata, actual, memoize(listCollector(actual))); + } + + @Override + protected String actualCustomStringRepresentation() { + List asList; + try { + asList = listSupplier.get(); + } catch (IllegalStateException e) { + return "Stream that has already been operated upon or closed: " + actual(); + } + return String.valueOf(asList); + } + + /** + * Obsolete factory instance. This factory was previously necessary for assertions like {@code + * assertWithMessage(...).about(streams()).that(stream)....}. Now, you can perform assertions like + * that without the {@code about(...)} call. + * + * @deprecated Instead of {@code about(streams()).that(...)}, use just {@code that(...)}. + * Similarly, instead of {@code assertAbout(streams()).that(...)}, use just {@code + * assertThat(...)}. + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") // We want users to remove the surrounding call entirely. + public static Factory> streams() { + return StreamSubject::new; + } + + /** Fails if the subject is not empty. */ + public void isEmpty() { + checkThatContentsList().isEmpty(); + } + + /** Fails if the subject is empty. */ + public void isNotEmpty() { + checkThatContentsList().isNotEmpty(); + } + + /** + * Fails if the subject does not have the given size. + * + *

If you'd like to check that your stream contains more than {@link Integer#MAX_VALUE} + * elements, use {@code assertThat(stream.count()).isEqualTo(...)}. + */ + public void hasSize(int expectedSize) { + checkThatContentsList().hasSize(expectedSize); + } + + /** Fails if the subject does not contain the given element. */ + public void contains(@Nullable Object element) { + checkThatContentsList().contains(element); + } + + /** Fails if the subject contains the given element. */ + public void doesNotContain(@Nullable Object element) { + checkThatContentsList().doesNotContain(element); + } + + /** Fails if the subject contains duplicate elements. */ + public void containsNoDuplicates() { + checkThatContentsList().containsNoDuplicates(); + } + + /** Fails if the subject does not contain at least one of the given elements. */ + public void containsAnyOf( + @Nullable Object first, @Nullable Object second, @Nullable Object @Nullable ... rest) { + checkThatContentsList().containsAnyOf(first, second, rest); + } + + /** Fails if the subject does not contain at least one of the given elements. */ + public void containsAnyIn(@Nullable Iterable expected) { + checkThatContentsList().containsAnyIn(expected); + } + + /** + * Fails if the subject does not contain all of the given elements. If an element appears more + * than once in the given elements, then it must appear at least that number of times in the + * actual elements. + * + *

To also test that the contents appear in the given order, make a call to {@code inOrder()} + * on the object returned by this method. The expected elements must appear in the given order + * within the actual elements, but they are not required to be consecutive. + */ + @CanIgnoreReturnValue + public Ordered containsAtLeast( + @Nullable Object first, @Nullable Object second, @Nullable Object @Nullable ... rest) { + return checkThatContentsList().containsAtLeast(first, second, rest); + } + + /** + * Fails if the subject does not contain all of the given elements. If an element appears more + * than once in the given elements, then it must appear at least that number of times in the + * actual elements. + * + *

To also test that the contents appear in the given order, make a call to {@code inOrder()} + * on the object returned by this method. The expected elements must appear in the given order + * within the actual elements, but they are not required to be consecutive. + */ + @CanIgnoreReturnValue + public Ordered containsAtLeastElementsIn(@Nullable Iterable expected) { + return checkThatContentsList().containsAtLeastElementsIn(expected); + } + + // TODO(cpovirk): Add array overload of contains*ElementsIn methods? Also for int and long stream. + + /** + * Fails if the subject does not contain exactly the given elements. + * + *

Multiplicity is respected. For example, an object duplicated exactly 3 times in the + * parameters asserts that the object must likewise be duplicated exactly 3 times in the subject. + * + *

To also test that the contents appear in the given order, make a call to {@code inOrder()} + * 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 checkThatContentsList().containsExactly(varargs); + } + + /** + * Fails if the subject does not contain exactly the given elements. + * + *

Multiplicity is respected. For example, an object duplicated exactly 3 times in the + * parameters asserts that the object must likewise be duplicated exactly 3 times in the subject. + * + *

To also test that the contents appear in the given order, make a call to {@code inOrder()} + * on the object returned by this method. + */ + @CanIgnoreReturnValue + public Ordered containsExactlyElementsIn(@Nullable Iterable expected) { + return checkThatContentsList().containsExactlyElementsIn(expected); + } + + /** + * Fails if the subject contains any of the given elements. (Duplicates are irrelevant to this + * test, which fails if any of the actual elements equal any of the excluded.) + */ + public void containsNoneOf( + @Nullable Object first, @Nullable Object second, @Nullable Object @Nullable ... rest) { + checkThatContentsList().containsNoneOf(first, second, rest); + } + + /** + * Fails if the subject contains any of the given elements. (Duplicates are irrelevant to this + * test, which fails if any of the actual elements equal any of the excluded.) + */ + public void containsNoneIn(@Nullable Iterable excluded) { + checkThatContentsList().containsNoneIn(excluded); + } + + /** + * Fails if the subject is not strictly ordered, according to the natural ordering of its + * elements. Strictly ordered means that each element in the stream is strictly greater + * than the element that preceded it. + * + * @throws ClassCastException if any pair of elements is not mutually Comparable + * @throws NullPointerException if any element is null + */ + public void isInStrictOrder() { + checkThatContentsList().isInStrictOrder(); + } + + /** + * Fails if the subject is not strictly ordered, according to the given comparator. Strictly + * ordered means that each element in the stream is strictly greater than the element that + * preceded it. + * + * @throws ClassCastException if any pair of elements is not mutually Comparable + */ + public void isInStrictOrder(Comparator comparator) { + checkThatContentsList().isInStrictOrder(comparator); + } + + /** + * Fails if the subject is not ordered, according to the natural ordering of its elements. Ordered + * means that each element in the stream is greater than or equal to the element that preceded it. + * + * @throws ClassCastException if any pair of elements is not mutually Comparable + * @throws NullPointerException if any element is null + */ + public void isInOrder() { + checkThatContentsList().isInOrder(); + } + + /** + * Fails if the subject is not ordered, according to the given comparator. Ordered means that each + * element in the stream is greater than or equal to the element that preceded it. + * + * @throws ClassCastException if any pair of elements is not mutually Comparable + */ + public void isInOrder(Comparator comparator) { + checkThatContentsList().isInOrder(comparator); + } + + /** + * @deprecated {@code streamA.isEqualTo(streamB)} always fails, except when passed the exact same + * stream reference. If you really want to test object identity, you can eliminate this + * deprecation warning by using {@link #isSameInstanceAs}. If you instead want to test the + * contents of the stream, use {@link #containsExactly} or similar methods. + */ + @Override + @Deprecated + public void isEqualTo(@Nullable Object expected) { + /* + * We add a warning about stream equality. Doing so is a bit of a pain. (There might be a better + * way.) + * + * Calling Subject constructors directly is not generally advisable. I'm not sure if the + * metadata munging we perform is advisable, either.... + * + * We do need to create a StreamSubject (rather than a plain Subject) in order to get our + * desired string representation (unless we edit Subject itself to create and expose a + * Supplier when given a Stream...). And we have to call a special constructor to avoid + * re-collecting the stream. + */ + new StreamSubject( + metadata.withMessage( + "%s", + new Object[] { + "Warning: Stream equality is based on object identity. To compare Stream" + + " contents, use methods like containsExactly." + }), + actual, + listSupplier) + .superIsEqualTo(expected); + } + + private void superIsEqualTo(@Nullable Object expected) { + super.isEqualTo(expected); + } + + /** + * @deprecated {@code streamA.isNotEqualTo(streamB)} always passes, except when passed the exact + * same stream reference. If you really want to test object identity, you can eliminate this + * deprecation warning by using {@link #isNotSameInstanceAs}. If you instead want to test the + * contents of the stream, collect both streams to lists and perform assertions like {@link + * IterableSubject#isNotEqualTo} on them. In some cases, you may be able to use {@link + * StreamSubject} assertions like {@link #doesNotContain}. + */ + @Override + @Deprecated + public void isNotEqualTo(@Nullable Object unexpected) { + if (actual() == unexpected) { + /* + * We override the supermethod's message: That method would ask for both + * `String.valueOf(stream)` (for `unexpected`) and `actualCustomStringRepresentation()` (for + * `actual()`). The two strings are almost certain to differ, since `valueOf` is normally + * based on identity and `actualCustomStringRepresentation()` is based on contents. That can + * lead to a confusing error message. + * + * We could include isEqualTo's warning about Stream's identity-based equality here, too. But + * it doesn't seem necessary: The people we really want to warn are the people whose + * assertions *pass*. And we've already attempted to do that with deprecation. + */ + failWithoutActual( + fact("expected not to be", actualCustomStringRepresentationForPackageMembersToCall())); + return; + } + /* + * But, if the objects aren't identical, we delegate to the supermethod (which checks equals()) + * just in case someone has decided to override Stream.equals in a strange way. (I haven't + * checked whether this comes up in Google's codebase. I hope that it doesn't.) + */ + super.isNotEqualTo(unexpected); + } + + // TODO(user): Do we want to support comparingElementsUsing() on StreamSubject? + + private IterableSubject checkThatContentsList() { + /* + * Calling Subject constructors directly is usually not advisable: It does not update the + * metadata, so the resultant failure message might say (for example) "value of: foo" when it + * should say "value of: foo.size()." However, in this specific case, that's exactly what we + * want: We're testing the contents of the stream, so we want a "value of" line for the stream, + * even though we happen to implement the contents check by delegating to IterableSubject. + */ + return new IterableSubject( + metadata, listSupplier.get(), /* typeDescriptionOverride= */ "stream"); + } + + private static Supplier<@Nullable List> listCollector(@Nullable Stream actual) { + return () -> actual == null ? null : actual.collect(toCollection(ArrayList::new)); + } +} 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 705bb96b8..8819c9218 100644 --- a/core/src/main/java/com/google/common/truth/StringSubject.java +++ b/core/src/main/java/com/google/common/truth/StringSubject.java @@ -23,7 +23,7 @@ import com.google.common.annotations.GwtIncompatible; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Propositions for string subjects. @@ -32,7 +32,7 @@ * @author Christian Gruber (cgruber@israfil.net) */ public class StringSubject extends ComparableSubject { - 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,17 +43,19 @@ protected StringSubject(FailureMetadata metadata, @Nullable String 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." */ @@ -75,7 +77,7 @@ public void isNotEmpty() { } /** 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 void contains(CharSequence 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 void doesNotContain(CharSequence 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 void startsWith(String 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 void endsWith(String 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); @@ -125,6 +127,11 @@ public void matches(String regex) { fact("expected to match", regex), fact("but was", actual), simpleFact("Looks like you want to use .isEqualTo() for an exact equality assertion.")); + } else if (Platform.containsMatch(actual, regex)) { + failWithoutActual( + fact("expected to match", regex), + fact("but was", actual), + simpleFact("Did you mean to call containsMatch() instead of match()?")); } else { failWithActual("expected to match", regex); } @@ -133,7 +140,7 @@ public void matches(String regex) { /** Fails if the string does not match the given regex. */ @GwtIncompatible("java.util.regex.Pattern") - public void matches(Pattern regex) { + public void matches(@Nullable Pattern regex) { checkNotNull(regex); if (actual == null) { failWithActual("expected a string that matches", regex); @@ -145,6 +152,11 @@ public void matches(Pattern regex) { simpleFact( "If you want an exact equality assertion you can escape your regex with" + " Pattern.quote().")); + } else if (regex.matcher(actual).find()) { + failWithoutActual( + fact("expected to match", regex), + fact("but was", actual), + simpleFact("Did you mean to call containsMatch() instead of match()?")); } else { failWithActual("expected to match", regex); } @@ -152,7 +164,7 @@ public void matches(Pattern regex) { } /** 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 +175,7 @@ public void doesNotMatch(String regex) { /** Fails if the string matches the given regex. */ @GwtIncompatible("java.util.regex.Pattern") - public void doesNotMatch(Pattern regex) { + public void doesNotMatch(@Nullable Pattern regex) { checkNotNull(regex); if (actual == null) { failWithActual("expected a string that does not match", regex); @@ -174,7 +186,7 @@ public void doesNotMatch(Pattern regex) { /** Fails if the string does not contain a match on the given regex. */ @GwtIncompatible("java.util.regex.Pattern") - public void containsMatch(Pattern regex) { + public void containsMatch(@Nullable Pattern regex) { checkNotNull(regex); if (actual == null) { failWithActual("expected a string that contains a match for", regex); @@ -184,7 +196,7 @@ public void containsMatch(Pattern regex) { } /** 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 +207,7 @@ public void containsMatch(String regex) { /** Fails if the string contains a match on the given regex. */ @GwtIncompatible("java.util.regex.Pattern") - public void doesNotContainMatch(Pattern regex) { + 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 +223,7 @@ public void doesNotContainMatch(Pattern regex) { } /** 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 +244,7 @@ public CaseInsensitiveStringComparison ignoringCase() { } /** Case insensitive propositions for string subjects. */ + @SuppressWarnings("Casing_StringEqualsIgnoreCase") // intentional choice from API Review public final class CaseInsensitiveStringComparison { private CaseInsensitiveStringComparison() {} @@ -246,7 +259,7 @@ private CaseInsensitiveStringComparison() {} * *

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 +281,7 @@ public void isEqualTo(String expected) { * 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 +297,7 @@ public void isNotEqualTo(String unexpected) { } /** 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 +312,7 @@ public void contains(CharSequence expectedSequence) { } /** 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 +326,22 @@ public void doesNotContain(CharSequence expectedSequence) { } } - 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 c571eecac..08e14bb93 100644 --- a/core/src/main/java/com/google/common/truth/Subject.java +++ b/core/src/main/java/com/google/common/truth/Subject.java @@ -20,6 +20,7 @@ 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; @@ -35,7 +36,6 @@ 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; @@ -53,7 +53,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * An object that lets you perform checks on the value under test. For example, {@code Subject} @@ -88,17 +88,11 @@ public class Subject { */ public interface Factory { /** 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 final @Nullable FailureMetadata metadata; + private final @Nullable Object actual; private final @Nullable String typeDescriptionOverride; /** @@ -106,7 +100,7 @@ public void fail(AssertionError failure) {} * {@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); } /** @@ -121,8 +115,10 @@ protected Subject(FailureMetadata metadata, @Nullable Object actual) { * 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; } @@ -303,7 +299,7 @@ public void isInstanceOf(Class clazz) { return; } if (!isInstanceOfType(actual, clazz)) { - if (classMetadataUnsupported()) { + if (Platform.classMetadataUnsupported()) { throw new UnsupportedOperationException( actualCustomStringRepresentation() + ", an instance of " @@ -324,7 +320,7 @@ public void isNotInstanceOf(Class clazz) { if (clazz == null) { throw new NullPointerException("clazz"); } - if (classMetadataUnsupported()) { + if (Platform.classMetadataUnsupported()) { throw new UnsupportedOperationException( "isNotInstanceOf is not supported under -XdisableClassMetadata"); } @@ -353,13 +349,14 @@ private static boolean isInstanceOfType(Object instance, Class clazz) { } /** Fails unless the subject is equal to any element in the given iterable. */ - public void isIn(Iterable iterable) { + public void isIn(@Nullable Iterable iterable) { + checkNotNull(iterable); if (!contains(iterable, actual)) { failWithActual("expected any of", iterable); } } - private static boolean contains(Iterable haystack, Object needle) { + private static boolean contains(Iterable haystack, @Nullable Object needle) { if (isKotlinRange(haystack)) { return kotlinRangeContains(haystack, needle); } @@ -373,7 +370,8 @@ public void isAnyOf( } /** 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); } @@ -386,7 +384,7 @@ public void isNoneOf( } /** Returns the actual value under test. */ - final Object actual() { + final @Nullable Object actual() { return actual; } @@ -423,13 +421,18 @@ private String formatActualOrExpected(@Nullable Object o) { 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 { + // 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); } } @@ -445,40 +448,30 @@ private static String base16(byte[] bytes) { private static final char[] hexDigits = "0123456789ABCDEF".toCharArray(); - private static Iterable stringableIterable(Object[] array) { - return Iterables.transform(asList(array), STRINGIFY); - } - - private static final Function STRINGIFY = - new Function() { - @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" @@ -514,7 +507,7 @@ static ComparisonResult differentNoDescription() { private final @Nullable ImmutableList facts; - private ComparisonResult(ImmutableList facts) { + private ComparisonResult(@Nullable ImmutableList facts) { this.facts = facts; } @@ -619,7 +612,7 @@ private static String arrayType(Object array) { } } - 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) { @@ -655,7 +648,7 @@ private static List floatArrayAsString(float[] items) { */ @Deprecated final StandardSubjectBuilder check() { - return new StandardSubjectBuilder(metadata.updateForCheckCall()); + return new StandardSubjectBuilder(checkNotNull(metadata).updateForCheckCall()); } /** @@ -686,28 +679,24 @@ final StandardSubjectBuilder check() { * @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 descriptionUpdate = - new Function() { - @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)); } /** @@ -719,7 +708,7 @@ public String apply(String input) { * returns {@code ignoreCheck().that(... a dummy exception ...)}. */ protected final StandardSubjectBuilder ignoreCheck() { - return StandardSubjectBuilder.forCustomFailureStrategy(IGNORE_STRATEGY); + return StandardSubjectBuilder.forCustomFailureStrategy(failure -> {}); } /** @@ -804,7 +793,7 @@ final void fail(String verb, Object other) { * 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) { @@ -828,12 +817,12 @@ enum EqualityCheck { * 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(); @@ -881,7 +870,8 @@ private void failEqualityCheck( } } else { if (equalityCheck == EqualityCheck.EQUAL && actual != null && expected != null) { - metadata.failEqualityCheck(difference.factsOrEmpty(), expectedString, actualString); + checkNotNull(metadata) + .failEqualityCheck(difference.factsOrEmpty(), expectedString, actualString); } else { failEqualityCheckNoComparisonFailure( difference, @@ -895,7 +885,7 @@ private void failEqualityCheck( * 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; } @@ -964,7 +954,7 @@ private static String escapeWhitespace(char c) { * 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; } @@ -1195,14 +1185,7 @@ private static String typeDescriptionOrGuess( return UPPER_CAMEL.to(LOWER_CAMEL, actualClass); } - private static boolean classMetadataUnsupported() { - // https://github.com/google/truth/issues/198 - // TODO(cpovirk): Consider whether to remove instanceof tests under GWT entirely. - // TODO(cpovirk): Run more Truth tests under GWT, and add tests for this. - return String.class.getSuperclass() == null; - } - private void doFail(ImmutableList facts) { - metadata.fail(facts); + 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 bbeec5974..84cf1f6ef 100644 --- a/core/src/main/java/com/google/common/truth/SubjectUtils.java +++ b/core/src/main/java/com/google/common/truth/SubjectUtils.java @@ -15,6 +15,7 @@ */ package com.google.common.truth; +import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.lenientFormat; import static com.google.common.collect.Iterables.isEmpty; import static com.google.common.collect.Iterables.transform; @@ -22,21 +23,21 @@ 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.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.ListMultimap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; /** * Utility methods used in {@code Subject} implementors. @@ -49,17 +50,17 @@ private SubjectUtils() {} static final String HUMAN_UNDERSTANDABLE_EMPTY_STRING = "\"\" (empty String)"; - static List accumulate(T first, T second, T... rest) { + static List 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 items = new ArrayList(2 + ((rest == null) ? 1 : rest.length)); + List 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)); + items.addAll(asList(rest)); } return items; } @@ -89,7 +90,8 @@ static String entryString(Multiset.Entry entry) { return (count > 1) ? item + " [" + count + " copies]" : item; } - private static NonHashingMultiset countDuplicatesToMultiset(Iterable items) { + private static NonHashingMultiset countDuplicatesToMultiset( + Iterable 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 multiset = new NonHashingMultiset<>(); @@ -138,26 +140,23 @@ static DuplicateGroupedAndTyped countDuplicatesAndMaybeAddTypeInfoReturnObject( } } - private static final class NonHashingMultiset { - // This ought to be static, but the generics are easier when I can refer to . - private final Function>, Multiset.Entry> unwrapKey = - new Function>, Multiset.Entry>() { - @Override - public Multiset.Entry apply(Multiset.Entry> input) { - return immutableEntry(input.getElement().get(), input.getCount()); - } - }; + private static final class NonHashingMultiset { + /* + * This ought to be static, but the generics are easier when I can refer to . We still want + * an Entry so that entrySet() can return Iterable> instead of Iterable>. + * That way, it can be returned directly from DuplicateGroupedAndTyped.entrySet() without our + * having to generalize *its* return type to Iterable>. + */ + private Multiset.Entry unwrapKey(Multiset.Entry> input) { + return immutableEntry(input.getElement().get(), input.getCount()); + } - private final Multiset> contents = LinkedHashMultiset.create(); + private final Multiset> 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 +166,7 @@ boolean isEmpty() { } Iterable> entrySet() { - return transform(contents.entrySet(), unwrapKey); + return transform(contents.entrySet(), this::unwrapKey); } String toStringWithBrackets() { @@ -261,13 +260,14 @@ static String iterableToStringWithTypeInfo(Iterable itemsIterable) { * *

Example: {@code retainMatchingToString([1L, 2L, 2L], [2, 3]) == [2L, 2L]} */ - static List retainMatchingToString(Iterable items, Iterable itemsToCheck) { - ListMultimap stringValueToItemsToCheck = ArrayListMultimap.create(); + static List<@Nullable Object> retainMatchingToString( + Iterable items, Iterable itemsToCheck) { + ListMultimap stringValueToItemsToCheck = ArrayListMultimap.create(); for (Object itemToCheck : itemsToCheck) { stringValueToItemsToCheck.put(String.valueOf(itemToCheck), itemToCheck); } - List 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 +292,7 @@ static boolean hasMatchingToStringPair(Iterable items1, Iterable items2) { 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 +342,10 @@ private static List addTypeInfoToEveryItem(Iterable items) { return itemsWithTypeInfo; } - static Collection iterableToCollection(Iterable iterable) { + static Collection iterableToCollection( + @Nullable Iterable iterable) { + // TODO(cpovirk): For null inputs, produce a better exception message (ideally in callers). + checkNotNull(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 +355,7 @@ static Collection iterableToCollection(Iterable iterable) { } } - static List iterableToList(Iterable iterable) { + static List iterableToList(Iterable iterable) { if (iterable instanceof List) { return (List) iterable; } else { @@ -366,7 +369,7 @@ static List iterableToList(Iterable iterable) { * *

Returns the given iterable if it contains no empty strings. */ - static Iterable annotateEmptyStrings(Iterable items) { + static Iterable annotateEmptyStrings(Iterable items) { if (Iterables.contains(items, "")) { List annotatedItems = Lists.newArrayList(); for (T item : items) { @@ -402,4 +405,9 @@ static ImmutableList append(ImmutableList list, E object) { static ImmutableList sandwich(E first, E[] array, E last) { return new ImmutableList.Builder().add(first).add(array).add(last).build(); } + + @SuppressWarnings("nullness") // TODO: b/316358623 - Remove suppression after fixing checker + static List asList(E... a) { + return Arrays.asList(a); + } } 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 87fcd69f0..61e2c2f0c 100644 --- a/core/src/main/java/com/google/common/truth/TableSubject.java +++ b/core/src/main/java/com/google/common/truth/TableSubject.java @@ -17,12 +17,13 @@ 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; import com.google.common.collect.Table.Cell; import com.google.common.collect.Tables; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Propositions for {@link Table} subjects. @@ -30,7 +31,7 @@ * @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 void isNotEmpty() { /** 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.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.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 8e0761da4..7a03a67a6 100644 --- a/core/src/main/java/com/google/common/truth/ThrowableSubject.java +++ b/core/src/main/java/com/google/common/truth/ThrowableSubject.java @@ -15,15 +15,31 @@ */ package com.google.common.truth; -import org.checkerframework.checker.nullness.qual.Nullable; +import static com.google.common.base.Preconditions.checkNotNull; + +import org.jspecify.annotations.Nullable; /** * Propositions for {@link Throwable} subjects. * + *

Truth does not provide its own support for calling a method and automatically catching an + * expected exception, only for asserting on the exception after it has been caught. To catch the + * exception, we suggest {@link org.junit.Assert#assertThrows(Class, + * org.junit.function.ThrowingRunnable) assertThrows} (JUnit), {@code + * assertFailsWith} ({@code kotlin.test}), or similar functionality from your testing library of + * choice. + * + *

+ * InvocationTargetException expected =
+ *     assertThrows(InvocationTargetException.class, () -> method.invoke(null));
+ * assertThat(expected).hasCauseThat().isInstanceOf(IOException.class);
+ * 
+ * * @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 +69,7 @@ public final StringSubject hasMessageThat() { "(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 +77,8 @@ public final StringSubject hasMessageThat() { * 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 +92,7 @@ public final ThrowableSubject hasCauseThat() { .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.java b/core/src/main/java/com/google/common/truth/Truth.java index 57620501e..407f6df63 100644 --- a/core/src/main/java/com/google/common/truth/Truth.java +++ b/core/src/main/java/com/google/common/truth/Truth.java @@ -18,13 +18,21 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtIncompatible; -import com.google.common.base.Optional; import com.google.common.collect.Multimap; import com.google.common.collect.Multiset; import com.google.common.collect.Table; +import com.google.j2objc.annotations.J2ObjCIncompatible; import java.math.BigDecimal; +import java.nio.file.Path; import java.util.Map; -import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; /** * The primary entry point for Truth, a library for fluent test @@ -73,21 +81,18 @@ 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 +106,7 @@ public static StandardSubjectBuilder assert_() { * StandardSubjectBuilder#about about(...)}, as discussed in this FAQ entry. */ - public static StandardSubjectBuilder assertWithMessage(String messageToPrepend) { + public static StandardSubjectBuilder assertWithMessage(@Nullable String messageToPrepend) { return assert_().withMessage(messageToPrepend); } @@ -121,7 +126,7 @@ public static StandardSubjectBuilder assertWithMessage(String messageToPrepend) * @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 +149,8 @@ CustomSubjectBuilderT assertAbout( return assert_().about(factory); } - public static > ComparableSubject assertThat(@Nullable T actual) { + public static > ComparableSubject assertThat( + @Nullable ComparableT actual) { return assert_().that(actual); } @@ -157,10 +163,28 @@ public static Subject assertThat(@Nullable Object actual) { } @GwtIncompatible("ClassSubject.java") + @J2ktIncompatible public static ClassSubject assertThat(@Nullable Class actual) { return assert_().that(actual); } + /** + * Begins an assertion about a {@link Throwable}. + * + *

Truth does not provide its own support for calling a method and automatically catching an + * expected exception, only for asserting on the exception after it has been caught. To catch the + * exception, we suggest {@link org.junit.Assert#assertThrows(Class, + * org.junit.function.ThrowingRunnable) assertThrows} (JUnit), {@code + * assertFailsWith} ({@code kotlin.test}), or similar functionality from your testing library + * of choice. + * + *

+   * InvocationTargetException expected =
+   *     assertThrows(InvocationTargetException.class, () -> method.invoke(null));
+   * assertThat(expected).hasCauseThat().isInstanceOf(IOException.class);
+   * 
+ */ public static ThrowableSubject assertThat(@Nullable Throwable actual) { return assert_().that(actual); } @@ -193,7 +217,9 @@ public static IterableSubject assertThat(@Nullable Iterable actual) { return assert_().that(actual); } - public static ObjectArraySubject assertThat(@Nullable T @Nullable [] actual) { + @SuppressWarnings("AvoidObjectArrays") + public static ObjectArraySubject assertThat( + T @Nullable [] actual) { return assert_().that(actual); } @@ -229,7 +255,8 @@ public static PrimitiveDoubleArraySubject assertThat(double @Nullable [] actual) return assert_().that(actual); } - public static GuavaOptionalSubject assertThat(@Nullable Optional actual) { + public static GuavaOptionalSubject assertThat( + com.google.common.base.@Nullable Optional actual) { return assert_().that(actual); } @@ -250,39 +277,85 @@ public static TableSubject assertThat(@Nullable Table actual) { } /** - * An {@code AssertionError} that (a) always supports a cause, even under old versions of Android - * and (b) omits "java.lang.AssertionError:" from the beginning of its toString() representation. + * @since 1.3.0 (present in {@link Truth8} since before 1.0) + */ + @SuppressWarnings({ + "Java7ApiChecker", // no more dangerous than wherever the user got the Optional + "NullableOptional", // Truth always accepts nulls, no matter the type + }) + public static OptionalSubject assertThat(@Nullable Optional actual) { + return assert_().that(actual); + } + + /** + * @since 1.3.0 (present in {@link Truth8} since before 1.0) + */ + @SuppressWarnings("Java7ApiChecker") // no more dangerous than wherever the user got the Stream + public static OptionalIntSubject assertThat(@Nullable OptionalInt actual) { + return assert_().that(actual); + } + + /** + * @since 1.4.0 (present in {@link Truth8} since before 1.0) + */ + @SuppressWarnings("Java7ApiChecker") // no more dangerous than wherever the user got the Stream + public static OptionalLongSubject assertThat(@Nullable OptionalLong actual) { + return assert_().that(actual); + } + + /** + * @since 1.4.0 (present in {@link Truth8} since before 1.0) + */ + @SuppressWarnings("Java7ApiChecker") // no more dangerous than wherever the user got the Stream + public static OptionalDoubleSubject assertThat(@Nullable OptionalDouble actual) { + return assert_().that(actual); + } + + /** + * @since 1.4.0 (present in {@link Truth8} since before 1.0) + */ + @SuppressWarnings("Java7ApiChecker") // no more dangerous than wherever the user got the Stream + public static StreamSubject assertThat(@Nullable Stream actual) { + return assert_().that(actual); + } + + /** + * @since 1.4.0 (present in {@link Truth8} since before 1.0) + */ + @SuppressWarnings("Java7ApiChecker") // no more dangerous than wherever the user got the Stream + public static IntStreamSubject assertThat(@Nullable IntStream actual) { + return assert_().that(actual); + } + + /** + * @since 1.4.0 (present in {@link Truth8} since before 1.0) + */ + @SuppressWarnings("Java7ApiChecker") // no more dangerous than wherever the user got the Stream + public static LongStreamSubject assertThat(@Nullable LongStream actual) { + return assert_().that(actual); + } + + // TODO(b/64757353): Add support for DoubleStream? + + /** + * @since 1.4.0 (present in {@link Truth8} since before 1.0) + */ + @GwtIncompatible + @J2ObjCIncompatible + @J2ktIncompatible + public static PathSubject assertThat(@Nullable Path actual) { + return assert_().that(actual); + } + + /** + * An {@code AssertionError} that omits "java.lang.AssertionError:" from the beginning of its + * toString() representation. */ // TODO(cpovirk): Consider eliminating this, adding its functionality to AssertionErrorWithFacts? @SuppressWarnings("OverrideThrowableToString") // We intentionally replace the normal format. static final class SimpleAssertionError extends AssertionError { - /** Separate cause field, in case initCause() fails. */ - private final @Nullable Throwable cause; - private SimpleAssertionError(String message, @Nullable Throwable cause) { - super(checkNotNull(message)); - this.cause = cause; - - try { - initCause(cause); - } catch (IllegalStateException alreadyInitializedBecauseOfHarmonyBug) { - /* - * initCause() throws under old versions of Android: - * https://issuetracker.google.com/issues/36945167 - * - * Yes, it's *nice* if initCause() works: - * - * - It ensures that, if someone tries to call initCause() later, the call will fail loudly - * rather than be silently ignored. - * - * - It populates the usual `Throwable.cause` field, where users of debuggers and other - * tools are likely to look first. - * - * But if it doesn't work, that's fine: Most consumers of the cause should be retrieving it - * through getCause(), which we've overridden to return *our* `cause` field, which we've - * populated with the correct value. - */ - } + super(checkNotNull(message), cause); } static SimpleAssertionError create(String message, @Nullable Throwable cause) { @@ -296,18 +369,12 @@ static SimpleAssertionError createWithNoStack(String message, @Nullable Throwabl } static SimpleAssertionError createWithNoStack(String message) { - return createWithNoStack(message, /*cause=*/ null); - } - - @Override - @SuppressWarnings("UnsynchronizedOverridesSynchronized") - public Throwable getCause() { - return cause; + return createWithNoStack(message, /* cause= */ null); } @Override public String toString() { - return getLocalizedMessage(); + return checkNotNull(getLocalizedMessage()); } } } diff --git a/extensions/java8/src/main/java/com/google/common/truth/Truth8.java b/core/src/main/java/com/google/common/truth/Truth8.java similarity index 52% rename from extensions/java8/src/main/java/com/google/common/truth/Truth8.java rename to core/src/main/java/com/google/common/truth/Truth8.java index 31f0c6d94..e28ad49e0 100644 --- a/extensions/java8/src/main/java/com/google/common/truth/Truth8.java +++ b/core/src/main/java/com/google/common/truth/Truth8.java @@ -15,8 +15,6 @@ */ package com.google.common.truth; -import static com.google.common.truth.Truth.assertAbout; - import com.google.common.annotations.GwtIncompatible; import com.google.j2objc.annotations.J2ObjCIncompatible; import java.nio.file.Path; @@ -27,59 +25,60 @@ import java.util.stream.IntStream; import java.util.stream.LongStream; import java.util.stream.Stream; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** - * The primary entry point for assertions about Java 8 types. - * - *

To use {@link Truth#assertWithMessage} with a Java 8 type, use {@code - * assertWithMessage(...).about(}{@link OptionalSubject#optionals optionals()}{@code ).that(...)} - * (or similarly for the other types). + * The obsolete entry point for assertions about Java 8 types. * - *

Likewise, to use different failure strategies like {@link Expect}, use {@code - * expect.about(}{@link OptionalSubject#optionals optionals()}{@code ).that(...)}. - * - *

For more information about combining different messages, failure strategies, and subjects, see - * How do I specify a custom message/failure - * behavior/{@code Subject} type? in the Truth FAQ. + * @deprecated Instead of this class's methods, use the identical methods declared in the main + * {@link Truth} class. In most cases, you can migrate your whole project + * mechanically: {@code git grep -l Truth8 | xargs perl -pi -e 's/\bTruth8\b/Truth/g;'} + * Migration is important if you static import {@code assertThat}: If you do not migrate, + * such static imports will become ambiguous in Truth 1.4.2, breaking your build. */ +@Deprecated +@SuppressWarnings({ + // The methods here are no more dangerous that wherever the user got the (e.g.) Stream. + "Java7ApiChecker", + // Replacing "Truth.assertThat" with "assertThat" would produce an infinite loop. + "StaticImportPreferred", +}) public final class Truth8 { + @SuppressWarnings("AssertAboutOptionals") // suggests infinite recursion public static OptionalSubject assertThat(@Nullable Optional target) { - return assertAbout(OptionalSubject.optionals()).that(target); + return Truth.assertThat(target); } public static OptionalIntSubject assertThat(@Nullable OptionalInt target) { - return assertAbout(OptionalIntSubject.optionalInts()).that(target); + return Truth.assertThat(target); } public static OptionalLongSubject assertThat(@Nullable OptionalLong target) { - return assertAbout(OptionalLongSubject.optionalLongs()).that(target); + return Truth.assertThat(target); } public static OptionalDoubleSubject assertThat(@Nullable OptionalDouble target) { - return assertAbout(OptionalDoubleSubject.optionalDoubles()).that(target); + return Truth.assertThat(target); } public static StreamSubject assertThat(@Nullable Stream target) { - return assertAbout(StreamSubject.streams()).that(target); + return Truth.assertThat(target); } public static IntStreamSubject assertThat(@Nullable IntStream target) { - return assertAbout(IntStreamSubject.intStreams()).that(target); + return Truth.assertThat(target); } public static LongStreamSubject assertThat(@Nullable LongStream target) { - return assertAbout(LongStreamSubject.longStreams()).that(target); + return Truth.assertThat(target); } - // TODO(b/64757353): Add support for DoubleStream? - - // Not actually a Java 8 feature, but for now this is the best option since core Truth still has - // to support Java environments without java.nio.file such as Android and J2CL. @GwtIncompatible @J2ObjCIncompatible + @J2ktIncompatible public static PathSubject assertThat(@Nullable Path target) { - return assertAbout(PathSubject.paths()).that(target); + return Truth.assertThat(target); } private Truth8() {} 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 80a0ead5b..6439cf7d8 100644 --- a/core/src/main/java/com/google/common/truth/TruthFailureSubject.java +++ b/core/src/main/java/com/google/common/truth/TruthFailureSubject.java @@ -23,7 +23,7 @@ import static com.google.common.truth.Fact.simpleFact; import com.google.common.collect.ImmutableList; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Subject for {@link AssertionError} objects thrown by Truth. {@code TruthFailureSubject} contains @@ -55,12 +55,13 @@ public static Factory truthFailures() { private static final Factory FACTORY = new Factory() { @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) { 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 3c70d8871..648d51236 100644 --- a/core/src/main/java/com/google/common/truth/TruthJUnit.java +++ b/core/src/main/java/com/google/common/truth/TruthJUnit.java @@ -15,8 +15,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 @@ -40,21 +39,17 @@ * @author David Saff * @author Christian Gruber (cgruber@israfil.net) */ -@GwtIncompatible("JUnit4") +@com.google.common.annotations.GwtIncompatible("JUnit4") 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 +59,5 @@ public static final StandardSubjectBuilder assume() { 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/package-info.java b/core/src/main/java/com/google/common/truth/package-info.java index 89839f730..99ee86a05 100644 --- a/core/src/main/java/com/google/common/truth/package-info.java +++ b/core/src/main/java/com/google/common/truth/package-info.java @@ -29,6 +29,8 @@ * other docs. */ @CheckReturnValue +@NullMarked package com.google.common.truth; import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; 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 615a26f11..ff273b277 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 @@ -23,7 +23,8 @@ import jsinterop.annotations.JsMethod; import jsinterop.annotations.JsProperty; import jsinterop.annotations.JsType; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** @@ -32,6 +33,7 @@ * * @author Christian Gruber (cgruber@google.com) */ +@NullMarked final class Platform { private Platform() {} @@ -58,7 +60,7 @@ static boolean containsMatch(String subject, String regex) { } /** - * Returns an array containing all of the exceptions that were suppressed to deliver the given + * Returns an array containing all the exceptions that were suppressed to deliver the given * exception. Delegates to the getSuppressed() method on Throwable that is available in Java 1.7+ */ static Throwable[] getSuppressed(Throwable throwable) { @@ -95,7 +97,7 @@ abstract static class PlatformComparisonFailure extends AssertionError { @Override public final String toString() { - return getLocalizedMessage(); + return "" + getLocalizedMessage(); } } @@ -150,9 +152,9 @@ private static native Object dump(Message msg) /*-{ }-*/; /** Turns a non-double, non-float object into a string. */ - static String stringValueOfNonFloatingPoint(Object o) { + 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 = System.getProperty("superdevmode", "doesntexist").equals("doesntexist"); + boolean inJ2clMode = "doesntexist".equals(System.getProperty("superdevmode", "doesntexist")); if (inJ2clMode && o instanceof Message) { Message msg = (Message) o; boolean dumpAvailable = @@ -197,9 +199,9 @@ private static NativeRegExp compile(String pattern) { @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) @@ -265,8 +267,12 @@ static boolean isKotlinRange(Iterable iterable) { return false; } - static boolean kotlinRangeContains(Iterable haystack, Object needle) { + static boolean kotlinRangeContains(Iterable haystack, @Nullable Object needle) { throw new AssertionError(); // never called under GWT because isKotlinRange returns false } + + static boolean classMetadataUnsupported() { + return String.class.getSuperclass() == null; + } } 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 ba88f9a44..9bee28aa5 100644 --- a/core/src/test/java/com/google/common/truth/ActualValueInferenceTest.java +++ b/core/src/test/java/com/google/common/truth/ActualValueInferenceTest.java @@ -22,7 +22,6 @@ 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; @@ -31,6 +30,13 @@ /** 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() { @@ -118,9 +124,7 @@ public void chaining() { failure = expectFailure( - whenTesting -> { - whenTesting.that(makeException()).hasMessageThat().isEqualTo("b"); - }); + whenTesting -> whenTesting.that(makeException()).hasMessageThat().isEqualTo("b")); assertThat(failure).factValue("value of").isEqualTo("makeException().getMessage()"); } @@ -219,7 +223,7 @@ String instanceOneArg(Object o) { return "a"; } - List oneTwoThree() { + ImmutableList oneTwoThree() { return ImmutableList.of(1, 2, 3); } 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 86aa41fc0..d59bd3489 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 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 @@ 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 @@ private static final class StringComparedByLength implements Comparable INSTANCE = - Correspondence.from( - // If we were allowed to use lambdas, this would be: - // (a, e) -> false, - new Correspondence.BinaryPredicate() { - @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 void testHashCode_throws() { // Tests of the 'from' factory method. private static final Correspondence 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() { - @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 void testFrom_viaIterableSubjectContainsExactly_null() { // Tests of the 'transform' factory methods. private static final Correspondence 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() { - @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 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() { - @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 void testTransforming_actual_viaIterableSubjectContainsExactly_nullTransf } private static final Correspondence 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() { - @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() { - @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 void testEquality_viaIterableSubjectContains_failure() { // Tests of formattingDiffsUsing. private static final Correspondence 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() { - @Override - public Integer apply(String str) { - return str.length(); - } - }, - "has a length of") - .formattingDiffsUsing( - new Correspondence.DiffFormatter() { - @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/DoubleSubjectTest.java b/core/src/test/java/com/google/common/truth/DoubleSubjectTest.java index 37eb8b183..f65e2603c 100644 --- a/core/src/test/java/com/google/common/truth/DoubleSubjectTest.java +++ b/core/src/test/java/com/google/common/truth/DoubleSubjectTest.java @@ -23,7 +23,7 @@ import com.google.common.annotations.GwtIncompatible; import com.google.common.truth.ExpectFailure.SimpleSubjectBuilderCallback; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -96,8 +96,7 @@ public void isWithinOf() { 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 callback = new ExpectFailure.SimpleSubjectBuilderCallback() { @Override @@ -129,8 +128,7 @@ public void isNotWithinOf() { 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 callback = new ExpectFailure.SimpleSubjectBuilderCallback() { @Override @@ -371,7 +369,7 @@ public void isEqualTo() { 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 callback = new ExpectFailure.SimpleSubjectBuilderCallback() { @Override @@ -394,7 +392,7 @@ public void isNotEqualTo() { assertThat(1.0).isNotEqualTo(2); } - private static void assertThatIsNotEqualToFails(final @Nullable Double value) { + private static void assertThatIsNotEqualToFails(@Nullable Double value) { ExpectFailure.SimpleSubjectBuilderCallback callback = new ExpectFailure.SimpleSubjectBuilderCallback() { @Override @@ -416,7 +414,7 @@ public void isZero() { assertThatIsZeroFails(null); } - private static void assertThatIsZeroFails(final @Nullable Double value) { + private static void assertThatIsZeroFails(@Nullable Double value) { ExpectFailure.SimpleSubjectBuilderCallback callback = new ExpectFailure.SimpleSubjectBuilderCallback() { @Override @@ -439,7 +437,7 @@ public void isNonZero() { 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 callback = new ExpectFailure.SimpleSubjectBuilderCallback() { @Override @@ -460,7 +458,7 @@ public void isPositiveInfinity() { assertThatIsPositiveInfinityFails(null); } - private static void assertThatIsPositiveInfinityFails(final @Nullable Double value) { + private static void assertThatIsPositiveInfinityFails(@Nullable Double value) { ExpectFailure.SimpleSubjectBuilderCallback callback = new ExpectFailure.SimpleSubjectBuilderCallback() { @Override @@ -480,7 +478,7 @@ public void isNegativeInfinity() { assertThatIsNegativeInfinityFails(null); } - private static void assertThatIsNegativeInfinityFails(final @Nullable Double value) { + private static void assertThatIsNegativeInfinityFails(@Nullable Double value) { ExpectFailure.SimpleSubjectBuilderCallback callback = new ExpectFailure.SimpleSubjectBuilderCallback() { @Override @@ -500,7 +498,7 @@ public void isNaN() { assertThatIsNaNFails(null); } - private static void assertThatIsNaNFails(final @Nullable Double value) { + private static void assertThatIsNaNFails(@Nullable Double value) { ExpectFailure.SimpleSubjectBuilderCallback callback = new ExpectFailure.SimpleSubjectBuilderCallback() { @Override @@ -522,7 +520,7 @@ public void isFinite() { assertThatIsFiniteFails(null); } - private static void assertThatIsFiniteFails(final @Nullable Double value) { + private static void assertThatIsFiniteFails(@Nullable Double value) { ExpectFailure.SimpleSubjectBuilderCallback callback = new ExpectFailure.SimpleSubjectBuilderCallback() { @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 1e8f464d0..060bc46c8 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 reportedFailure = Lists.newArrayList(); + List reportedFailure = Lists.newArrayList(); RunNotifier runNotifier = new RunNotifier(); runNotifier.addListener( new RunListener() { @@ -59,7 +59,7 @@ public void testFailure(Failure failure) throws Exception { @Test public void testExpect_userThrowExceptionAfterSubject_shouldPropagate() throws Exception { - final List reportedFailure = Lists.newArrayList(); + List reportedFailure = Lists.newArrayList(); RunNotifier runNotifier = new RunNotifier(); runNotifier.addListener( new RunListener() { @@ -125,6 +125,7 @@ public void ensureFailureCaught() { } @Test + @SuppressWarnings("TruthSelfEquals") public void testExpect_throwInSubject_shouldPropagate() { expectFailure.whenTesting().that(4).isEqualTo(4); // No failure being caught long unused = throwingMethod(); 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 5938ce254..01f67492b 100644 --- a/core/src/test/java/com/google/common/truth/ExpectFailureRuleTest.java +++ b/core/src/test/java/com/google/common/truth/ExpectFailureRuleTest.java @@ -38,11 +38,13 @@ public void expectFail_captureFailureAsExpected() { } @Test + @SuppressWarnings("TruthSelfEquals") public void expectFail_passesIfUnused() { assertThat(4).isEqualTo(4); } @Test + @SuppressWarnings("TruthSelfEquals") public void expectFail_failsAfterTest() { expectFailure.whenTesting().that(4).isEqualTo(4); thrown.expectMessage("ExpectFailure.whenTesting() invoked, but no failure was caught."); @@ -55,6 +57,7 @@ public void expectFail_throwInSubject_shouldPropagateOriginalException() { } @Test + @SuppressWarnings("TruthSelfEquals") public void expectFail_throwAfterSubject_shouldPropagateOriginalException() { expectFailure.whenTesting().that(2).isEqualTo(2); thrown.expectMessage("Throwing deliberately"); diff --git a/core/src/test/java/com/google/common/truth/ExpectFailureTest.java b/core/src/test/java/com/google/common/truth/ExpectFailureTest.java index 74dd80dde..06e1c4ef1 100644 --- a/core/src/test/java/com/google/common/truth/ExpectFailureTest.java +++ b/core/src/test/java/com/google/common/truth/ExpectFailureTest.java @@ -54,11 +54,13 @@ public void expectFail_about() { } @Test + @SuppressWarnings("TruthSelfEquals") public void expectFail_passesIfUnused() { assertThat(4).isEqualTo(4); } @Test + @SuppressWarnings("TruthSelfEquals") public void expectFail_failsOnSuccess() { expectFailure.whenTesting().that(4).isEqualTo(4); try { @@ -83,6 +85,7 @@ public void expectFail_failsOnMultipleFailures() { } @Test + @SuppressWarnings("TruthSelfEquals") public void expectFail_failsOnMultiplewhenTestings() { try { expectFailure.whenTesting().that(4).isEqualTo(4); @@ -108,6 +111,7 @@ public void expectFail_failsOnMultiplewhenTestings_thatFail() { } @Test + @SuppressWarnings("TruthSelfEquals") public void expectFail_failsAfterTest() { try { expectFailure.whenTesting().that(4).isEqualTo(4); @@ -121,6 +125,7 @@ public void expectFail_failsAfterTest() { } @Test + @SuppressWarnings("TruthSelfEquals") public void expectFail_whenTestingWithoutInContext_shouldFail() { ExpectFailure expectFailure = new ExpectFailure(); try { 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 3542475c2..496761151 100644 --- a/core/src/test/java/com/google/common/truth/ExpectFailureWithStackTraceTest.java +++ b/core/src/test/java/com/google/common/truth/ExpectFailureWithStackTraceTest.java @@ -33,6 +33,7 @@ public class ExpectFailureWithStackTraceTest { @Rule public final FailingExpect failToExpect = new FailingExpect(); @Test + @SuppressWarnings("TruthSelfEquals") public void expectTwoFailures() { failToExpect.delegate.that(4).isNotEqualTo(4); failToExpect.delegate.that("abc").contains("x"); @@ -44,7 +45,7 @@ public static class FailingExpect implements TestRule { @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 e079ee14f..958de6a1f 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.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 @@ * @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 void evaluate() throws Throwable { taskToAwait.get(); } }; - } - }; private final CountDownLatch testMethodComplete = new CountDownLatch(1); @@ -74,17 +71,15 @@ public void evaluate() throws Throwable { @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 + @SuppressWarnings("TruthSelfEquals") public void expectTrue() { expect.that(4).isEqualTo(4); } @@ -181,6 +176,7 @@ public void expectSuccessWithFailuresAfterAssume() { } @Test + @SuppressWarnings("TruthSelfEquals") public void warnWhenExpectIsNotRule() { String message = "assertion made on Expect instance, but it's not enabled as a @Rule."; thrown.expectMessage(message); @@ -189,13 +185,7 @@ public void warnWhenExpectIsNotRule() { @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> results = new ArrayList<>(); ExecutorService executor = newFixedThreadPool(10); for (int i = 0; i < 1000; i++) { @@ -213,15 +203,12 @@ public void failWhenCallingThatAfterTest() { 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 +222,16 @@ public void failWhenCallingFailingAssertionMethodAfterTest() { * 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 528568c84..ae2fcb1ac 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 @@ private static Exception getSecondException(String message, Throwable cause) { 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 @@ void setErrorVerifier(ErrorVerifier verifier) { } @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 void evaluate() throws Throwable { 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 c404a731b..b14da38fc 100644 --- a/core/src/test/java/com/google/common/truth/FloatSubjectTest.java +++ b/core/src/test/java/com/google/common/truth/FloatSubjectTest.java @@ -23,7 +23,7 @@ import com.google.common.annotations.GwtIncompatible; import com.google.common.truth.ExpectFailure.SimpleSubjectBuilderCallback; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -96,8 +96,7 @@ public void isWithinOf() { 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 callback = new ExpectFailure.SimpleSubjectBuilderCallback() { @Override @@ -129,8 +128,7 @@ public void isNotWithinOf() { 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 callback = new ExpectFailure.SimpleSubjectBuilderCallback() { @Override @@ -371,7 +369,7 @@ public void isEqualTo() { 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 callback = new ExpectFailure.SimpleSubjectBuilderCallback() { @Override @@ -394,7 +392,7 @@ public void isNotEqualTo() { assertThat(1.0f).isNotEqualTo(2); } - private static void assertThatIsNotEqualToFails(final @Nullable Float value) { + private static void assertThatIsNotEqualToFails(@Nullable Float value) { ExpectFailure.SimpleSubjectBuilderCallback callback = new ExpectFailure.SimpleSubjectBuilderCallback() { @Override @@ -416,7 +414,7 @@ public void isZero() { assertThatIsZeroFails(null); } - private static void assertThatIsZeroFails(final @Nullable Float value) { + private static void assertThatIsZeroFails(@Nullable Float value) { ExpectFailure.SimpleSubjectBuilderCallback callback = new ExpectFailure.SimpleSubjectBuilderCallback() { @Override @@ -439,7 +437,7 @@ public void isNonZero() { 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 callback = new ExpectFailure.SimpleSubjectBuilderCallback() { @Override @@ -460,7 +458,7 @@ public void isPositiveInfinity() { assertThatIsPositiveInfinityFails(null); } - private static void assertThatIsPositiveInfinityFails(final @Nullable Float value) { + private static void assertThatIsPositiveInfinityFails(@Nullable Float value) { ExpectFailure.SimpleSubjectBuilderCallback callback = new ExpectFailure.SimpleSubjectBuilderCallback() { @Override @@ -480,7 +478,7 @@ public void isNegativeInfinity() { assertThatIsNegativeInfinityFails(null); } - private static void assertThatIsNegativeInfinityFails(final @Nullable Float value) { + private static void assertThatIsNegativeInfinityFails(@Nullable Float value) { ExpectFailure.SimpleSubjectBuilderCallback callback = new ExpectFailure.SimpleSubjectBuilderCallback() { @Override @@ -500,7 +498,7 @@ public void isNaN() { assertThatIsNaNFails(null); } - private static void assertThatIsNaNFails(final @Nullable Float value) { + private static void assertThatIsNaNFails(@Nullable Float value) { ExpectFailure.SimpleSubjectBuilderCallback callback = new ExpectFailure.SimpleSubjectBuilderCallback() { @Override @@ -522,7 +520,7 @@ public void isFinite() { assertThatIsFiniteFails(null); } - private static void assertThatIsFiniteFails(final @Nullable Float value) { + private static void assertThatIsFiniteFails(@Nullable Float value) { ExpectFailure.SimpleSubjectBuilderCallback callback = new ExpectFailure.SimpleSubjectBuilderCallback() { @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 461030435..a9f0fef9b 100644 --- a/core/src/test/java/com/google/common/truth/IntegerSubjectTest.java +++ b/core/src/test/java/com/google/common/truth/IntegerSubjectTest.java @@ -15,9 +15,12 @@ */ 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; -import com.google.common.collect.ImmutableSet; +import com.google.common.truth.ExpectFailure.SimpleSubjectBuilderCallback; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -33,6 +36,7 @@ public class IntegerSubjectTest extends BaseSubjectTestCase { @Test + @SuppressWarnings("TruthSelfEquals") public void simpleEquality() { assertThat(4).isEqualTo(4); } @@ -116,160 +120,124 @@ public void overflowBetweenIntegerAndLong_shouldBeDifferent_max() { expectFailureWhenTestingThat(Integer.MAX_VALUE).isEqualTo(Long.MAX_VALUE); } - @SuppressWarnings("TruthSelfEquals") - @Test - public void testPrimitivesVsBoxedPrimitivesVsObject_int() { - int int42 = 42; - Integer integer42 = new Integer(42); - Object object42 = (Object) 42; - - assertThat(int42).isEqualTo(int42); - assertThat(integer42).isEqualTo(int42); - assertThat(object42).isEqualTo(int42); - - assertThat(int42).isEqualTo(integer42); - assertThat(integer42).isEqualTo(integer42); - assertThat(object42).isEqualTo(integer42); - - assertThat(int42).isEqualTo(object42); - assertThat(integer42).isEqualTo(object42); - assertThat(object42).isEqualTo(object42); - } - - @SuppressWarnings("TruthSelfEquals") - @Test - public void testPrimitivesVsBoxedPrimitivesVsObject_long() { - long longPrim42 = 42; - Long long42 = new Long(42); - Object object42 = (Object) 42L; - - assertThat(longPrim42).isEqualTo(longPrim42); - assertThat(long42).isEqualTo(longPrim42); - assertThat(object42).isEqualTo(longPrim42); - - assertThat(longPrim42).isEqualTo(long42); - assertThat(long42).isEqualTo(long42); - assertThat(object42).isEqualTo(long42); - - assertThat(longPrim42).isEqualTo(object42); - assertThat(long42).isEqualTo(object42); - assertThat(object42).isEqualTo(object42); - } - - @Test - public void testAllCombinations_pass() { - assertThat(42).isEqualTo(42L); - assertThat(42).isEqualTo(new Long(42L)); - assertThat(new Integer(42)).isEqualTo(42L); - assertThat(new Integer(42)).isEqualTo(new Long(42L)); - assertThat(42L).isEqualTo(42); - assertThat(42L).isEqualTo(new Integer(42)); - assertThat(new Long(42L)).isEqualTo(42); - assertThat(new Long(42L)).isEqualTo(new Integer(42)); - - assertThat(42).isEqualTo(42); - assertThat(42).isEqualTo(new Integer(42)); - assertThat(new Integer(42)).isEqualTo(42); - assertThat(new Integer(42)).isEqualTo(new Integer(42)); - assertThat(42L).isEqualTo(42L); - assertThat(42L).isEqualTo(new Long(42L)); - assertThat(new Long(42L)).isEqualTo(42L); - assertThat(new Long(42L)).isEqualTo(new Long(42L)); - } - @Test - public void testNumericTypeWithSameValue_shouldBeEqual_int_long() { - expectFailureWhenTestingThat(42).isNotEqualTo(42L); - } - - @Test - public void testNumericTypeWithSameValue_shouldBeEqual_int_int() { - expectFailureWhenTestingThat(42).isNotEqualTo(42); - } - - @Test - public void testNumericPrimitiveTypes_isNotEqual_shouldFail_intToChar() { - expectFailureWhenTestingThat(42).isNotEqualTo((char) 42); - // 42 in ASCII is '*' - assertFailureValue("expected not to be", "*"); - assertFailureValue("but was; string representation of actual value", "42"); + public void isWithinOf() { + assertThat(20000).isWithin(0).of(20000); + assertThat(20000).isWithin(1).of(20000); + assertThat(20000).isWithin(10000).of(20000); + assertThat(20000).isWithin(10000).of(30000); + assertThat(Integer.MIN_VALUE).isWithin(1).of(Integer.MIN_VALUE + 1); + assertThat(Integer.MAX_VALUE).isWithin(1).of(Integer.MAX_VALUE - 1); + assertThat(Integer.MAX_VALUE / 2).isWithin(Integer.MAX_VALUE).of(-Integer.MAX_VALUE / 2); + assertThat(-Integer.MAX_VALUE / 2).isWithin(Integer.MAX_VALUE).of(Integer.MAX_VALUE / 2); + + assertThatIsWithinFails(20000, 9999, 30000); + assertThatIsWithinFails(20000, 10000, 30001); + assertThatIsWithinFails(Integer.MIN_VALUE, 0, Integer.MAX_VALUE); + assertThatIsWithinFails(Integer.MAX_VALUE, 0, Integer.MIN_VALUE); + assertThatIsWithinFails(Integer.MIN_VALUE, 1, Integer.MIN_VALUE + 2); + assertThatIsWithinFails(Integer.MAX_VALUE, 1, Integer.MAX_VALUE - 2); + // Don't fall for rollover + assertThatIsWithinFails(Integer.MIN_VALUE, 1, Integer.MAX_VALUE); + assertThatIsWithinFails(Integer.MAX_VALUE, 1, Integer.MIN_VALUE); + } + + private static void assertThatIsWithinFails(int actual, int tolerance, int expected) { + ExpectFailure.SimpleSubjectBuilderCallback callback = + new ExpectFailure.SimpleSubjectBuilderCallback() { + @Override + public void invokeAssertion(SimpleSubjectBuilder expect) { + expect.that(actual).isWithin(tolerance).of(expected); + } + }; + AssertionError failure = expectFailure(callback); + assertThat(failure) + .factKeys() + .containsExactly("expected", "but was", "outside tolerance") + .inOrder(); + assertThat(failure).factValue("expected").isEqualTo(Integer.toString(expected)); + assertThat(failure).factValue("but was").isEqualTo(Integer.toString(actual)); + assertThat(failure).factValue("outside tolerance").isEqualTo(Integer.toString(tolerance)); + } + + @Test + public void isNotWithinOf() { + assertThatIsNotWithinFails(20000, 0, 20000); + assertThatIsNotWithinFails(20000, 1, 20000); + assertThatIsNotWithinFails(20000, 10000, 20000); + assertThatIsNotWithinFails(20000, 10000, 30000); + assertThatIsNotWithinFails(Integer.MIN_VALUE, 1, Integer.MIN_VALUE + 1); + assertThatIsNotWithinFails(Integer.MAX_VALUE, 1, Integer.MAX_VALUE - 1); + assertThatIsNotWithinFails(Integer.MAX_VALUE / 2, Integer.MAX_VALUE, -Integer.MAX_VALUE / 2); + assertThatIsNotWithinFails(-Integer.MAX_VALUE / 2, Integer.MAX_VALUE, Integer.MAX_VALUE / 2); + + assertThat(20000).isNotWithin(9999).of(30000); + assertThat(20000).isNotWithin(10000).of(30001); + assertThat(Integer.MIN_VALUE).isNotWithin(0).of(Integer.MAX_VALUE); + assertThat(Integer.MAX_VALUE).isNotWithin(0).of(Integer.MIN_VALUE); + assertThat(Integer.MIN_VALUE).isNotWithin(1).of(Integer.MIN_VALUE + 2); + assertThat(Integer.MAX_VALUE).isNotWithin(1).of(Integer.MAX_VALUE - 2); + // Don't fall for rollover + assertThat(Integer.MIN_VALUE).isNotWithin(1).of(Integer.MAX_VALUE); + assertThat(Integer.MAX_VALUE).isNotWithin(1).of(Integer.MIN_VALUE); + } + + private static void assertThatIsNotWithinFails(int actual, int tolerance, int expected) { + ExpectFailure.SimpleSubjectBuilderCallback callback = + new ExpectFailure.SimpleSubjectBuilderCallback() { + @Override + public void invokeAssertion(SimpleSubjectBuilder expect) { + expect.that(actual).isNotWithin(tolerance).of(expected); + } + }; + AssertionError failure = expectFailure(callback); + assertThat(failure).factValue("expected not to be").isEqualTo(Integer.toString(expected)); + assertThat(failure).factValue("within tolerance").isEqualTo(Integer.toString(tolerance)); + } + + @Test + public void isWithinNegativeTolerance() { + isWithinNegativeToleranceThrowsIAE(0, -10, 5); + isWithinNegativeToleranceThrowsIAE(0, -10, 20); + isNotWithinNegativeToleranceThrowsIAE(0, -10, 5); + isNotWithinNegativeToleranceThrowsIAE(0, -10, 20); + } + + private static void isWithinNegativeToleranceThrowsIAE(int actual, int tolerance, int expected) { + try { + assertThat(actual).isWithin(tolerance).of(expected); + fail("Expected IllegalArgumentException to be thrown but wasn't"); + } catch (IllegalArgumentException iae) { + assertThat(iae) + .hasMessageThat() + .isEqualTo("tolerance (" + tolerance + ") cannot be negative"); + } } - @Test - public void testNumericPrimitiveTypes_isNotEqual_shouldFail_charToInt() { - // Uses Object overload rather than Integer. - expectFailure.whenTesting().that((char) 42).isNotEqualTo(42); - // 42 in ASCII is '*' - assertFailureValue("expected not to be", "42"); - assertFailureValue("but was; string representation of actual value", "*"); + private static void isNotWithinNegativeToleranceThrowsIAE( + int actual, int tolerance, int expected) { + try { + assertThat(actual).isNotWithin(tolerance).of(expected); + fail("Expected IllegalArgumentException to be thrown but wasn't"); + } catch (IllegalArgumentException iae) { + assertThat(iae) + .hasMessageThat() + .isEqualTo("tolerance (" + tolerance + ") cannot be negative"); + } } - private static final Subject.Factory DEFAULT_SUBJECT_FACTORY = - new Subject.Factory() { + private static final Subject.Factory INTEGER_SUBJECT_FACTORY = + new Subject.Factory() { @Override - public Subject createSubject(FailureMetadata metadata, Object that) { - return new Subject(metadata, that); + public IntegerSubject createSubject(FailureMetadata metadata, Integer that) { + return new IntegerSubject(metadata, that); } }; - private static void expectFailure( - ExpectFailure.SimpleSubjectBuilderCallback callback) { - AssertionError unused = ExpectFailure.expectFailureAbout(DEFAULT_SUBJECT_FACTORY, callback); - } - - @Test - public void testNumericPrimitiveTypes() { - byte byte42 = (byte) 42; - short short42 = (short) 42; - char char42 = (char) 42; - int int42 = 42; - long long42 = (long) 42; - - ImmutableSet fortyTwos = - ImmutableSet.of(byte42, short42, char42, int42, long42); - for (Object actual : fortyTwos) { - for (Object expected : fortyTwos) { - assertThat(actual).isEqualTo(expected); - } - } - - ImmutableSet fortyTwosNoChar = ImmutableSet.of(byte42, short42, int42, long42); - for (final Object actual : fortyTwosNoChar) { - for (final Object expected : fortyTwosNoChar) { - ExpectFailure.SimpleSubjectBuilderCallback actualFirst = - new ExpectFailure.SimpleSubjectBuilderCallback() { - @Override - public void invokeAssertion(SimpleSubjectBuilder expect) { - expect.that(actual).isNotEqualTo(expected); - } - }; - ExpectFailure.SimpleSubjectBuilderCallback expectedFirst = - new ExpectFailure.SimpleSubjectBuilderCallback() { - @Override - public void invokeAssertion(SimpleSubjectBuilder expect) { - expect.that(expected).isNotEqualTo(actual); - } - }; - expectFailure(actualFirst); - expectFailure(expectedFirst); - } - } - - byte byte41 = (byte) 41; - short short41 = (short) 41; - char char41 = (char) 41; - int int41 = 41; - long long41 = (long) 41; - - ImmutableSet fortyOnes = - ImmutableSet.of(byte41, short41, char41, int41, long41); - - for (Object first : fortyTwos) { - for (Object second : fortyOnes) { - assertThat(first).isNotEqualTo(second); - assertThat(second).isNotEqualTo(first); - } - } + @CanIgnoreReturnValue + private static AssertionError expectFailure( + SimpleSubjectBuilderCallback callback) { + return ExpectFailure.expectFailureAbout(INTEGER_SUBJECT_FACTORY, callback); } private IntegerSubject expectFailureWhenTestingThat(Integer actual) { 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 6b0db173c..f9e3986d7 100644 --- a/core/src/test/java/com/google/common/truth/IterableSubjectCorrespondenceTest.java +++ b/core/src/test/java/com/google/common/truth/IterableSubjectCorrespondenceTest.java @@ -35,7 +35,7 @@ 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; @@ -56,6 +56,7 @@ public class IterableSubjectCorrespondenceTest extends BaseSubjectTestCase { @Test + // test of a mistaken call @SuppressWarnings({"EqualsIncompatibleType", "DoNotCall", "deprecation"}) public void equalsThrowsUSOE() { try { @@ -75,7 +76,7 @@ public void equalsThrowsUSOE() { } @Test - @SuppressWarnings({"DoNotCall", "deprecation"}) + @SuppressWarnings({"DoNotCall", "deprecation"}) // test of a mistaken call public void hashCodeThrowsUSOE() { try { int unused = @@ -158,14 +159,14 @@ public void contains_handlesExceptions_alwaysFails() { @Test public void displayingDiffsPairedBy_1arg_contains() { - Record expected = Record.create(2, 200); - ImmutableList actual = + MyRecord expected = MyRecord.create(2, 200); + ImmutableList 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) @@ -191,14 +192,14 @@ public void displayingDiffsPairedBy_1arg_contains() { @Test public void displayingDiffsPairedBy_1arg_contains_noDiff() { - Record expected = Record.create(2, 200); - ImmutableList actual = + MyRecord expected = MyRecord.create(2, 200); + ImmutableList 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) @@ -217,8 +218,8 @@ public void displayingDiffsPairedBy_1arg_contains_noDiff() { @Test public void displayingDiffsPairedBy_1arg_contains_handlesActualKeyerExceptions() { - Record expected = Record.create(0, 999); - List actual = asList(Record.create(1, 100), null, Record.create(4, 400)); + MyRecord expected = MyRecord.create(0, 999); + List actual = asList(MyRecord.create(1, 100), null, MyRecord.create(4, 400)); expectFailure .whenTesting() .that(actual) @@ -238,8 +239,8 @@ public void displayingDiffsPairedBy_1arg_contains_handlesActualKeyerExceptions() @Test public void displayingDiffsPairedBy_1arg_contains_handlesExpectedKeyerExceptions() { - List actual = - asList(Record.create(1, 100), Record.create(2, 200), Record.create(4, 400)); + List actual = + asList(MyRecord.create(1, 100), MyRecord.create(2, 200), MyRecord.create(4, 400)); expectFailure .whenTesting() .that(actual) @@ -259,8 +260,8 @@ public void displayingDiffsPairedBy_1arg_contains_handlesExpectedKeyerExceptions @Test public void displayingDiffsPairedBy_1arg_contains_handlesFormatDiffExceptions() { - Record expected = Record.create(0, 999); - List actual = asList(Record.create(1, 100), null, Record.create(4, 400)); + MyRecord expected = MyRecord.create(0, 999); + List actual = asList(MyRecord.create(1, 100), null, MyRecord.create(4, 400)); expectFailure .whenTesting() .that(actual) @@ -657,18 +658,18 @@ public void containsExactlyElementsIn_diffOneMissingSomeExtraCandidate() { @Test public void displayingDiffsPairedBy_1arg_containsExactlyElementsIn() { - ImmutableList expected = + ImmutableList expected = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 200), - Record.create(3, 300), - Record.createWithoutId(900)); - ImmutableList actual = + MyRecord.create(1, 100), + MyRecord.create(2, 200), + MyRecord.create(3, 300), + MyRecord.createWithoutId(900)); + ImmutableList 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) @@ -701,12 +702,12 @@ public void displayingDiffsPairedBy_1arg_containsExactlyElementsIn() { @Test public void displayingDiffsPairedBy_2arg_containsExactlyElementsIn() { - ImmutableList expected = + ImmutableList 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 actual = ImmutableList.of("1/100", "2/211", "4/400", "none/999"); expectFailure .whenTesting() @@ -740,18 +741,18 @@ public void displayingDiffsPairedBy_2arg_containsExactlyElementsIn() { @Test public void displayingDiffsPairedBy_containsExactlyElementsIn_onlyKeyed() { - ImmutableList expected = + ImmutableList expected = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 200), - Record.create(3, 300), - Record.createWithoutId(999)); - ImmutableList actual = + MyRecord.create(1, 100), + MyRecord.create(2, 200), + MyRecord.create(3, 300), + MyRecord.createWithoutId(999)); + ImmutableList 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) @@ -776,18 +777,18 @@ public void displayingDiffsPairedBy_containsExactlyElementsIn_onlyKeyed() { @Test public void displayingDiffsPairedBy_containsExactlyElementsIn_noKeyed() { - ImmutableList expected = + ImmutableList expected = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 200), - Record.create(3, 300), - Record.createWithoutId(900)); - ImmutableList actual = + MyRecord.create(1, 100), + MyRecord.create(2, 200), + MyRecord.create(3, 300), + MyRecord.createWithoutId(900)); + ImmutableList 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) @@ -808,18 +809,18 @@ public void displayingDiffsPairedBy_containsExactlyElementsIn_noKeyed() { @Test public void displayingDiffsPairedBy_containsExactlyElementsIn_noDiffs() { - ImmutableList expected = + ImmutableList expected = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 200), - Record.create(3, 300), - Record.createWithoutId(999)); - ImmutableList actual = + MyRecord.create(1, 100), + MyRecord.create(2, 200), + MyRecord.create(3, 300), + MyRecord.createWithoutId(999)); + ImmutableList 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) @@ -851,19 +852,19 @@ public void displayingDiffsPairedBy_containsExactlyElementsIn_passing() { 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 expected = + ImmutableList expected = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 200), - Record.create(3, 300), - Record.create(3, 301), - Record.createWithoutId(900)); - ImmutableList actual = + MyRecord.create(1, 100), + MyRecord.create(2, 200), + MyRecord.create(3, 300), + MyRecord.create(3, 301), + MyRecord.createWithoutId(900)); + ImmutableList 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) @@ -885,9 +886,9 @@ public void displayingDiffsPairedBy_containsExactlyElementsIn_notUnique() { @Test public void displayingDiffsPairedBy_containsExactlyElementsIn_handlesActualKeyerExceptions() { - ImmutableList expected = - ImmutableList.of(Record.create(1, 100), Record.create(2, 200), Record.create(4, 400)); - List actual = asList(Record.create(1, 101), Record.create(2, 211), null); + ImmutableList expected = + ImmutableList.of(MyRecord.create(1, 100), MyRecord.create(2, 200), MyRecord.create(4, 400)); + List actual = asList(MyRecord.create(1, 101), MyRecord.create(2, 211), null); expectFailure .whenTesting() .that(actual) @@ -917,9 +918,9 @@ public void displayingDiffsPairedBy_containsExactlyElementsIn_handlesActualKeyer @Test public void displayingDiffsPairedBy_containsExactlyElementsIn_handlesExpectedKeyerExceptions() { - List expected = asList(Record.create(1, 100), Record.create(2, 200), null); - List actual = - asList(Record.create(1, 101), Record.create(2, 211), Record.create(4, 400)); + List expected = asList(MyRecord.create(1, 100), MyRecord.create(2, 200), null); + List actual = + asList(MyRecord.create(1, 101), MyRecord.create(2, 211), MyRecord.create(4, 400)); expectFailure .whenTesting() .that(actual) @@ -949,9 +950,9 @@ public void displayingDiffsPairedBy_containsExactlyElementsIn_handlesExpectedKey @Test public void displayingDiffsPairedBy_containsExactlyElementsIn_handlesFormatDiffExceptions() { - ImmutableList expected = - ImmutableList.of(Record.create(1, 100), Record.create(2, 200), Record.create(0, 999)); - List actual = asList(Record.create(1, 101), Record.create(2, 211), null); + ImmutableList expected = + ImmutableList.of(MyRecord.create(1, 100), MyRecord.create(2, 200), MyRecord.create(0, 999)); + List actual = asList(MyRecord.create(1, 101), MyRecord.create(2, 211), null); expectFailure .whenTesting() .that(actual) @@ -1310,15 +1311,16 @@ public void containsAtLeastElementsIn_handlesExceptions_alwaysFails() { @Test public void displayingElementsPairedBy_containsAtLeastElementsIn() { - ImmutableList expected = - ImmutableList.of(Record.create(1, 100), Record.create(2, 200), Record.createWithoutId(999)); - ImmutableList actual = + ImmutableList expected = ImmutableList.of( - Record.create(1, 101), - Record.create(2, 211), - Record.create(2, 222), - Record.create(3, 303), - Record.createWithoutId(888)); + MyRecord.create(1, 100), MyRecord.create(2, 200), MyRecord.createWithoutId(999)); + ImmutableList actual = + ImmutableList.of( + MyRecord.create(1, 101), + MyRecord.create(2, 211), + MyRecord.create(2, 222), + MyRecord.create(3, 303), + MyRecord.createWithoutId(888)); expectFailure .whenTesting() .that(actual) @@ -1353,14 +1355,15 @@ public void displayingElementsPairedBy_containsAtLeastElementsIn() { @Test public void displayingElementsPairedBy_containsAtLeastElementsIn_notUnique() { - ImmutableList expected = + ImmutableList expected = + ImmutableList.of( + MyRecord.create(1, 100), + MyRecord.create(2, 200), + MyRecord.create(2, 201), + MyRecord.createWithoutId(999)); + ImmutableList actual = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 200), - Record.create(2, 201), - Record.createWithoutId(999)); - ImmutableList 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) @@ -1380,10 +1383,10 @@ public void displayingElementsPairedBy_containsAtLeastElementsIn_notUnique() { @Test public void displayingElementsPairedBy_containsAtLeastElementsIn_handlesFormatDiffExceptions() { - ImmutableList expected = - ImmutableList.of(Record.create(1, 100), Record.create(2, 200), Record.create(0, 999)); - List actual = - asList(Record.create(1, 101), Record.create(2, 211), Record.create(3, 303), null); + ImmutableList expected = + ImmutableList.of(MyRecord.create(1, 100), MyRecord.create(2, 200), MyRecord.create(0, 999)); + List actual = + asList(MyRecord.create(1, 101), MyRecord.create(2, 211), MyRecord.create(3, 303), null); expectFailure .whenTesting() .that(actual) @@ -1682,19 +1685,19 @@ public void containsAnyIn_failure() { @Test public void displayingDiffsPairedBy_containsAnyIn_withKeyMatches() { - ImmutableList expected = + ImmutableList expected = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 200), - Record.create(3, 300), - Record.createWithoutId(999)); - ImmutableList actual = + MyRecord.create(1, 100), + MyRecord.create(2, 200), + MyRecord.create(3, 300), + MyRecord.createWithoutId(999)); + ImmutableList 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) @@ -1735,10 +1738,12 @@ public void displayingDiffsPairedBy_containsAnyIn_withKeyMatches() { @Test public void displayingDiffsPairedBy_containsAnyIn_withoutKeyMatches() { - ImmutableList expected = - ImmutableList.of(Record.create(1, 100), Record.create(2, 200), Record.createWithoutId(999)); - ImmutableList actual = - ImmutableList.of(Record.create(3, 300), Record.create(4, 411), Record.createWithoutId(888)); + ImmutableList expected = + ImmutableList.of( + MyRecord.create(1, 100), MyRecord.create(2, 200), MyRecord.createWithoutId(999)); + ImmutableList actual = + ImmutableList.of( + MyRecord.create(3, 300), MyRecord.create(4, 411), MyRecord.createWithoutId(888)); expectFailure .whenTesting() .that(actual) @@ -1754,14 +1759,15 @@ public void displayingDiffsPairedBy_containsAnyIn_withoutKeyMatches() { @Test public void displayingDiffsPairedBy_containsAnyIn_notUnique() { - ImmutableList expected = + ImmutableList expected = + ImmutableList.of( + MyRecord.create(1, 100), + MyRecord.create(2, 200), + MyRecord.create(2, 250), + MyRecord.createWithoutId(999)); + ImmutableList actual = ImmutableList.of( - Record.create(1, 100), - Record.create(2, 200), - Record.create(2, 250), - Record.createWithoutId(999)); - ImmutableList 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) @@ -1778,9 +1784,9 @@ public void displayingDiffsPairedBy_containsAnyIn_notUnique() { @Test public void displayingDiffsPairedBy_containsAnyIn_handlesFormatDiffExceptions() { - ImmutableList expected = - ImmutableList.of(Record.create(1, 100), Record.create(2, 200), Record.create(0, 999)); - List actual = asList(Record.create(3, 311), Record.create(4, 404), null); + ImmutableList expected = + ImmutableList.of(MyRecord.create(1, 100), MyRecord.create(2, 200), MyRecord.create(0, 999)); + List actual = asList(MyRecord.create(3, 311), MyRecord.create(4, 404), null); expectFailure .whenTesting() .that(actual) @@ -2028,28 +2034,28 @@ public void containsNoneIn_array() { @Test public void formattingDiffsUsing_success() { - ImmutableList actual = - ImmutableList.of(Record.create(3, 300), Record.create(2, 200), Record.create(1, 100)); + ImmutableList 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 actual = + ImmutableList 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 2ae257606..80eb4f9b8 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 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 @@ * @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 void hasSize() { } @Test + @SuppressWarnings({"TruthIterableIsEmpty", "IsEmptyTruth"}) public void hasSizeZero() { assertThat(ImmutableList.of()).hasSize(0); } @@ -204,16 +205,10 @@ public void iterableContainsAnyOfFailsWithSameToStringAndNullInExpectation() { @Test public void iterableContainsAnyOfWithOneShotIterable() { - final Iterator iterator = asList((Object) 2, 1, "b").iterator(); - Iterable iterable = - new Iterable() { - @Override - public Iterator iterator() { - return iterator; - } - }; + List contents = asList(2, 1, "b"); + Iterable oneShot = new OneShotIterable<>(contents.iterator(), "OneShotIterable"); - assertThat(iterable).containsAnyOf(3, "a", 7, "b", 0); + assertThat(oneShot).containsAnyOf(3, "a", 7, "b", 0); } @Test @@ -408,41 +403,18 @@ public void iterableContainsAtLeastInOrderWithFailureWithActualOrder() { @Test public void iterableContainsAtLeastInOrderWithOneShotIterable() { - final Iterable iterable = Arrays.asList(2, 1, null, 4, "a", 3, "b"); - final Iterator iterator = iterable.iterator(); - Iterable oneShot = - new Iterable() { - @Override - public Iterator iterator() { - return iterator; - } - - @Override - public String toString() { - return Iterables.toString(iterable); - } - }; + List contents = asList(2, 1, null, 4, "a", 3, "b"); + Iterable oneShot = new OneShotIterable<>(contents.iterator(), contents.toString()); assertThat(oneShot).containsAtLeast(1, null, 3).inOrder(); } @Test public void iterableContainsAtLeastInOrderWithOneShotIterableWrongOrder() { - final Iterator iterator = asList((Object) 2, 1, null, 4, "a", 3, "b").iterator(); - Iterable iterable = - new Iterable() { - @Override - public Iterator iterator() { - return iterator; - } - - @Override - public String toString() { - return "BadIterable"; - } - }; + List contents = asList(2, 1, null, 4, "a", 3, "b"); + Iterable 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", @@ -451,12 +423,33 @@ public String toString() { assertFailureValue("but was", "BadIterable"); // TODO(b/231966021): Output its elements. } + private static final class OneShotIterable implements Iterable { + private final Iterator iterator; + private final String toString; + + OneShotIterable(Iterator iterator, String toString) { + this.iterator = iterator; + this.toString = toString; + } + + @Override + public Iterator iterator() { + return iterator; + } + + @Override + public String toString() { + return toString; + } + } + @Test public void iterableContainsAtLeastInOrderWrongOrderAndMissing() { expectFailureWhenTestingThat(asList(1, 2)).containsAtLeast(2, 1, 3).inOrder(); } @Test + @SuppressWarnings("ContainsAllElementsInWithVarArgsToContainsAtLeast") public void iterableContainsAtLeastElementsInIterable() { assertThat(asList(1, 2, 3)).containsAtLeastElementsIn(asList(1, 2)); @@ -467,6 +460,7 @@ public void iterableContainsAtLeastElementsInIterable() { } @Test + @SuppressWarnings("ContainsAllElementsInWithVarArgsToContainsAtLeast") public void iterableContainsAtLeastElementsInCanUseFactPerElement() { expectFailureWhenTestingThat(asList("abc")) .containsAtLeastElementsIn(asList("123\n456", "789")); @@ -522,6 +516,7 @@ public void iterableContainsNoneOfFailureWithEmptyString() { } @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)); @@ -546,6 +541,8 @@ public void iterableContainsExactlyArray() { } @Test + // We tell people to call containsExactlyElementsIn, but we still test containsExactly. + @SuppressWarnings("ContainsExactlyVariadic") public void arrayContainsExactly() { ImmutableList iterable = ImmutableList.of("a", "b"); String[] array = {"a", "b"}; @@ -667,6 +664,7 @@ public void iterableContainsExactlySingleElementNoEqualsMagic() { } @Test + @SuppressWarnings("ContainsExactlyElementsInWithVarArgsToExactly") public void iterableContainsExactlyWithElementsThatThrowWhenYouCallHashCode() { HashCodeThrower one = new HashCodeThrower(); HashCodeThrower two = new HashCodeThrower(); @@ -711,17 +709,20 @@ public String toString() { } @Test + @SuppressWarnings({"ContainsExactlyNone", "TruthSelfEquals"}) 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")); @@ -871,6 +872,7 @@ public void iterableContainsExactlyWithOneIterableGivesWarning() { } @Test + @SuppressWarnings("ContainsExactlyElementsInWithVarArgsToExactly") public void iterableContainsExactlyElementsInWithOneIterableDoesNotGiveWarning() { expectFailureWhenTestingThat(asList(1, 2, 3, 4)).containsExactlyElementsIn(asList(1, 2, 3)); assertFailureValue("unexpected (1)", "4"); @@ -914,7 +916,7 @@ public void iterableContainsExactlyInOrderWithFailure() { @Test public void iterableContainsExactlyInOrderWithOneShotIterable() { - final Iterator iterator = asList((Object) 1, null, 3).iterator(); + Iterator iterator = asList((Object) 1, null, 3).iterator(); Iterable iterable = new Iterable() { @Override @@ -927,7 +929,7 @@ public Iterator iterator() { @Test public void iterableContainsExactlyInOrderWithOneShotIterableWrongOrder() { - final Iterator iterator = asList((Object) 1, null, 3).iterator(); + Iterator iterator = asList((Object) 1, null, 3).iterator(); Iterable iterable = new Iterable() { @Override @@ -961,6 +963,7 @@ public Iterator iterator() { } @Test + @SuppressWarnings("ContainsExactlyElementsInWithVarArgsToExactly") public void iterableContainsExactlyElementsInIterable() { assertThat(asList(1, 2)).containsExactlyElementsIn(asList(1, 2)); @@ -977,6 +980,7 @@ public void iterableContainsExactlyElementsInArray() { } @Test + @SuppressWarnings("UndefinedEquals") // Iterable equality isn't defined, but null equality is public void nullEqualToNull() { assertThat((Iterable) null).isEqualTo(null); } @@ -1136,13 +1140,9 @@ public void iterableIsInOrderWithComparatorFailure() { assertFailureValue("full contents", "[1, 10, 2, 20]"); } + @SuppressWarnings("CompareProperty") // avoiding Java 8 API under Android private static final Comparator COMPARE_AS_DECIMAL = - new Comparator() { - @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; @@ -1158,13 +1158,20 @@ private Bar(int x) { } } - private static final Comparator FOO_COMPARATOR = - new Comparator() { - @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_COMPARATOR = (a, b) -> Integer.compare(a.x, b.x); @Test public void iterableOrderedByBaseClassComparator() { @@ -1184,6 +1191,7 @@ public void isIn() { } @Test + @SuppressWarnings("deprecation") // test of a mistaken call public void isNotIn() { ImmutableList actual = ImmutableList.of("a"); @@ -1209,7 +1217,7 @@ public void isAnyOf() { } @Test - @SuppressWarnings("IncompatibleArgumentType") + @SuppressWarnings({"IncompatibleArgumentType", "deprecation"}) // test of a mistaken call public void isNoneOf() { ImmutableList actual = ImmutableList.of("a"); diff --git a/core/src/test/java/com/google/common/truth/LongSubjectTest.java b/core/src/test/java/com/google/common/truth/LongSubjectTest.java index b3a3f4439..c2c2e15f3 100644 --- a/core/src/test/java/com/google/common/truth/LongSubjectTest.java +++ b/core/src/test/java/com/google/common/truth/LongSubjectTest.java @@ -15,8 +15,12 @@ */ 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; +import com.google.common.truth.ExpectFailure.SimpleSubjectBuilderCallback; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -32,6 +36,7 @@ public class LongSubjectTest extends BaseSubjectTestCase { @Test + @SuppressWarnings("TruthSelfEquals") public void simpleEquality() { assertThat(4L).isEqualTo(4L); } @@ -129,6 +134,140 @@ public void isAtMost_int() { assertThat(2L).isAtMost(3); } + @Test + public void isWithinOf() { + assertThat(20000L).isWithin(0L).of(20000L); + assertThat(20000L).isWithin(1L).of(20000L); + assertThat(20000L).isWithin(10000L).of(20000L); + assertThat(20000L).isWithin(10000L).of(30000L); + assertThat(Long.MIN_VALUE).isWithin(1L).of(Long.MIN_VALUE + 1); + assertThat(Long.MAX_VALUE).isWithin(1L).of(Long.MAX_VALUE - 1); + assertThat(Long.MAX_VALUE / 2).isWithin(Long.MAX_VALUE).of(-Long.MAX_VALUE / 2); + assertThat(-Long.MAX_VALUE / 2).isWithin(Long.MAX_VALUE).of(Long.MAX_VALUE / 2); + + assertThatIsWithinFails(20000L, 9999L, 30000L); + assertThatIsWithinFails(20000L, 10000L, 30001L); + assertThatIsWithinFails(Long.MIN_VALUE, 0L, Long.MAX_VALUE); + assertThatIsWithinFails(Long.MAX_VALUE, 0L, Long.MIN_VALUE); + assertThatIsWithinFails(Long.MIN_VALUE, 1L, Long.MIN_VALUE + 2); + assertThatIsWithinFails(Long.MAX_VALUE, 1L, Long.MAX_VALUE - 2); + // Don't fall for rollover + assertThatIsWithinFails(Long.MIN_VALUE, 1L, Long.MAX_VALUE); + assertThatIsWithinFails(Long.MAX_VALUE, 1L, Long.MIN_VALUE); + } + + private static void assertThatIsWithinFails(long actual, long tolerance, long expected) { + ExpectFailure.SimpleSubjectBuilderCallback callback = + new ExpectFailure.SimpleSubjectBuilderCallback() { + @Override + public void invokeAssertion(SimpleSubjectBuilder expect) { + expect.that(actual).isWithin(tolerance).of(expected); + } + }; + AssertionError failure = expectFailure(callback); + assertThat(failure) + .factKeys() + .containsExactly("expected", "but was", "outside tolerance") + .inOrder(); + assertThat(failure).factValue("expected").isEqualTo(Long.toString(expected)); + assertThat(failure).factValue("but was").isEqualTo(Long.toString(actual)); + assertThat(failure).factValue("outside tolerance").isEqualTo(Long.toString(tolerance)); + } + + @Test + public void isNotWithinOf() { + assertThatIsNotWithinFails(20000L, 0L, 20000L); + assertThatIsNotWithinFails(20000L, 1L, 20000L); + assertThatIsNotWithinFails(20000L, 10000L, 20000L); + assertThatIsNotWithinFails(20000L, 10000L, 30000L); + assertThatIsNotWithinFails(Long.MIN_VALUE, 1L, Long.MIN_VALUE + 1); + assertThatIsNotWithinFails(Long.MAX_VALUE, 1L, Long.MAX_VALUE - 1); + assertThatIsNotWithinFails(Long.MAX_VALUE / 2, Long.MAX_VALUE, -Long.MAX_VALUE / 2); + assertThatIsNotWithinFails(-Long.MAX_VALUE / 2, Long.MAX_VALUE, Long.MAX_VALUE / 2); + + assertThat(20000L).isNotWithin(9999L).of(30000L); + assertThat(20000L).isNotWithin(10000L).of(30001L); + assertThat(Long.MIN_VALUE).isNotWithin(0L).of(Long.MAX_VALUE); + assertThat(Long.MAX_VALUE).isNotWithin(0L).of(Long.MIN_VALUE); + assertThat(Long.MIN_VALUE).isNotWithin(1L).of(Long.MIN_VALUE + 2); + assertThat(Long.MAX_VALUE).isNotWithin(1L).of(Long.MAX_VALUE - 2); + // Don't fall for rollover + assertThat(Long.MIN_VALUE).isNotWithin(1L).of(Long.MAX_VALUE); + assertThat(Long.MAX_VALUE).isNotWithin(1L).of(Long.MIN_VALUE); + } + + private static void assertThatIsNotWithinFails(long actual, long tolerance, long expected) { + ExpectFailure.SimpleSubjectBuilderCallback callback = + new ExpectFailure.SimpleSubjectBuilderCallback() { + @Override + public void invokeAssertion(SimpleSubjectBuilder expect) { + expect.that(actual).isNotWithin(tolerance).of(expected); + } + }; + AssertionError failure = expectFailure(callback); + assertThat(failure).factValue("expected not to be").isEqualTo(Long.toString(expected)); + assertThat(failure).factValue("within tolerance").isEqualTo(Long.toString(tolerance)); + } + + @Test + public void isWithinIntegers() { + assertThat(20000L).isWithin(0).of(20000); + assertThat(20000L).isWithin(1).of(20000); + assertThat(20000L).isWithin(10000).of(20000); + assertThat(20000L).isWithin(10000).of(30000); + + assertThat(20000L).isNotWithin(0).of(200000); + assertThat(20000L).isNotWithin(1).of(200000); + assertThat(20000L).isNotWithin(10000).of(200000); + assertThat(20000L).isNotWithin(10000).of(300000); + } + + @Test + public void isWithinNegativeTolerance() { + isWithinNegativeToleranceThrowsIAE(0L, -10, 5); + isWithinNegativeToleranceThrowsIAE(0L, -10, 20); + isNotWithinNegativeToleranceThrowsIAE(0L, -10, 5); + isNotWithinNegativeToleranceThrowsIAE(0L, -10, 20); + } + + private static void isWithinNegativeToleranceThrowsIAE( + long actual, long tolerance, long expected) { + try { + assertThat(actual).isWithin(tolerance).of(expected); + fail("Expected IllegalArgumentException to be thrown but wasn't"); + } catch (IllegalArgumentException iae) { + assertThat(iae) + .hasMessageThat() + .isEqualTo("tolerance (" + tolerance + ") cannot be negative"); + } + } + + private static void isNotWithinNegativeToleranceThrowsIAE( + long actual, long tolerance, long expected) { + try { + assertThat(actual).isNotWithin(tolerance).of(expected); + fail("Expected IllegalArgumentException to be thrown but wasn't"); + } catch (IllegalArgumentException iae) { + assertThat(iae) + .hasMessageThat() + .isEqualTo("tolerance (" + tolerance + ") cannot be negative"); + } + } + + private static final Subject.Factory LONG_SUBJECT_FACTORY = + new Subject.Factory() { + @Override + public LongSubject createSubject(FailureMetadata metadata, Long that) { + return new LongSubject(metadata, that); + } + }; + + @CanIgnoreReturnValue + private static AssertionError expectFailure( + SimpleSubjectBuilderCallback callback) { + return ExpectFailure.expectFailureAbout(LONG_SUBJECT_FACTORY, callback); + } + private LongSubject expectFailureWhenTestingThat(Long actual) { return expectFailure.whenTesting().that(actual); } diff --git a/core/src/test/java/com/google/common/truth/NumericComparisonTest.java b/core/src/test/java/com/google/common/truth/NumericComparisonTest.java new file mode 100644 index 000000000..a2bc9b362 --- /dev/null +++ b/core/src/test/java/com/google/common/truth/NumericComparisonTest.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2011 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 com.google.common.collect.ImmutableSet; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Tests for comparisons between various integral types. + * + * @author David Saff + * @author Christian Gruber + * @author Kurt Alfred Kluever + */ +@RunWith(JUnit4.class) +public class NumericComparisonTest extends BaseSubjectTestCase { + + @SuppressWarnings("TruthSelfEquals") + @Test + public void testPrimitivesVsBoxedPrimitivesVsObject_int() { + int int42 = 42; + Integer integer42 = 42; + Object object42 = (Object) 42; + + assertThat(int42).isEqualTo(int42); + assertThat(integer42).isEqualTo(int42); + assertThat(object42).isEqualTo(int42); + + assertThat(int42).isEqualTo(integer42); + assertThat(integer42).isEqualTo(integer42); + assertThat(object42).isEqualTo(integer42); + + assertThat(int42).isEqualTo(object42); + assertThat(integer42).isEqualTo(object42); + assertThat(object42).isEqualTo(object42); + } + + @SuppressWarnings("TruthSelfEquals") + @Test + public void testPrimitivesVsBoxedPrimitivesVsObject_long() { + long longPrim42 = 42; + Long long42 = (long) 42; + Object object42 = (Object) 42L; + + assertThat(longPrim42).isEqualTo(longPrim42); + assertThat(long42).isEqualTo(longPrim42); + assertThat(object42).isEqualTo(longPrim42); + + assertThat(longPrim42).isEqualTo(long42); + assertThat(long42).isEqualTo(long42); + assertThat(object42).isEqualTo(long42); + + assertThat(longPrim42).isEqualTo(object42); + assertThat(long42).isEqualTo(object42); + assertThat(object42).isEqualTo(object42); + } + + @Test + @SuppressWarnings("TruthSelfEquals") + public void testAllCombinations_pass() { + assertThat(42).isEqualTo(42L); + assertThat(42).isEqualTo(Long.valueOf(42L)); + assertThat(Integer.valueOf(42)).isEqualTo(42L); + assertThat(Integer.valueOf(42)).isEqualTo(Long.valueOf(42L)); + assertThat(42L).isEqualTo(42); + assertThat(42L).isEqualTo(Integer.valueOf(42)); + assertThat(Long.valueOf(42L)).isEqualTo(42); + assertThat(Long.valueOf(42L)).isEqualTo(Integer.valueOf(42)); + + assertThat(42).isEqualTo(42); + assertThat(42).isEqualTo(Integer.valueOf(42)); + assertThat(Integer.valueOf(42)).isEqualTo(42); + assertThat(Integer.valueOf(42)).isEqualTo(Integer.valueOf(42)); + assertThat(42L).isEqualTo(42L); + assertThat(42L).isEqualTo(Long.valueOf(42L)); + assertThat(Long.valueOf(42L)).isEqualTo(42L); + assertThat(Long.valueOf(42L)).isEqualTo(Long.valueOf(42L)); + } + + @Test + public void testNumericTypeWithSameValue_shouldBeEqual_int_long() { + expectFailureWhenTestingThat(42).isNotEqualTo(42L); + } + + @Test + public void testNumericTypeWithSameValue_shouldBeEqual_int_int() { + expectFailureWhenTestingThat(42).isNotEqualTo(42); + } + + @Test + public void testNumericPrimitiveTypes_isNotEqual_shouldFail_intToChar() { + expectFailureWhenTestingThat(42).isNotEqualTo((char) 42); + // 42 in ASCII is '*' + assertFailureValue("expected not to be", "*"); + assertFailureValue("but was; string representation of actual value", "42"); + } + + @Test + public void testNumericPrimitiveTypes_isNotEqual_shouldFail_charToInt() { + // Uses Object overload rather than Integer. + expectFailure.whenTesting().that((char) 42).isNotEqualTo(42); + // 42 in ASCII is '*' + assertFailureValue("expected not to be", "42"); + assertFailureValue("but was; string representation of actual value", "*"); + } + + private static final Subject.Factory DEFAULT_SUBJECT_FACTORY = + new Subject.Factory() { + @Override + public Subject createSubject(FailureMetadata metadata, Object that) { + return new Subject(metadata, that); + } + }; + + private static void expectFailure( + ExpectFailure.SimpleSubjectBuilderCallback callback) { + AssertionError unused = ExpectFailure.expectFailureAbout(DEFAULT_SUBJECT_FACTORY, callback); + } + + @Test + public void testNumericPrimitiveTypes() { + byte byte42 = (byte) 42; + short short42 = (short) 42; + char char42 = (char) 42; + int int42 = 42; + long long42 = (long) 42; + + ImmutableSet fortyTwos = + ImmutableSet.of(byte42, short42, char42, int42, long42); + for (Object actual : fortyTwos) { + for (Object expected : fortyTwos) { + assertThat(actual).isEqualTo(expected); + } + } + + ImmutableSet fortyTwosNoChar = ImmutableSet.of(byte42, short42, int42, long42); + for (Object actual : fortyTwosNoChar) { + for (Object expected : fortyTwosNoChar) { + ExpectFailure.SimpleSubjectBuilderCallback actualFirst = + new ExpectFailure.SimpleSubjectBuilderCallback() { + @Override + public void invokeAssertion(SimpleSubjectBuilder expect) { + expect.that(actual).isNotEqualTo(expected); + } + }; + ExpectFailure.SimpleSubjectBuilderCallback expectedFirst = + new ExpectFailure.SimpleSubjectBuilderCallback() { + @Override + public void invokeAssertion(SimpleSubjectBuilder expect) { + expect.that(expected).isNotEqualTo(actual); + } + }; + expectFailure(actualFirst); + expectFailure(expectedFirst); + } + } + + byte byte41 = (byte) 41; + short short41 = (short) 41; + char char41 = (char) 41; + int int41 = 41; + long long41 = (long) 41; + + ImmutableSet fortyOnes = + ImmutableSet.of(byte41, short41, char41, int41, long41); + + for (Object first : fortyTwos) { + for (Object second : fortyOnes) { + assertThat(first).isNotEqualTo(second); + assertThat(second).isNotEqualTo(first); + } + } + } + + private IntegerSubject expectFailureWhenTestingThat(Integer actual) { + return expectFailure.whenTesting().that(actual); + } +} 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 d33682a8d..b1f7c35f3 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 void isEqualTo() { @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 void isNotEmptyFail() { } @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 void isEqualTo_Fail_UnequalOrderingMultiDimensional_01() { } @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 void isEqualTo_Fail_UnequalOrderingMultiDimensional_11() { } @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 void isNotEqualTo_FailEqualsMultiDimensional() { @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 void multiDimensionalStringArrayAsList() { } @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 void stringArrayIsEqualTo_Fail_UnequalLength() { } @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 void stringArrayIsEqualTo_Fail_UnequalLengthMultiDimensional() { } @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 void primitiveMultiDimensionalArrayIsEqualTo() { } @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 void primitiveMultiDimensionalArrayIsNotEqualTo() { } @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 @@ private static String[] objectArray(String... ts) { 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/StackTraceCleanerTest.java b/core/src/test/java/com/google/common/truth/StackTraceCleanerTest.java index f42eee1fc..fc92cec05 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 void assertionsActuallyUseCleaner() { } @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 @@ private static StackTraceElement createCollapsedStackTraceElement( 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 ec247402e..feb85d60d 100644 --- a/core/src/test/java/com/google/common/truth/StringSubjectTest.java +++ b/core/src/test/java/com/google/common/truth/StringSubjectTest.java @@ -128,6 +128,7 @@ public void stringDoesNotContainFail() { } @Test + @SuppressWarnings("TruthSelfEquals") public void stringEquality() { assertThat("abc").isEqualTo("abc"); } @@ -215,6 +216,16 @@ public void stringMatchesStringLiteralFail() { .contains("Looks like you want to use .isEqualTo() for an exact equality assertion."); } + @Test + public void stringMatchesStringLiteralFailButContainsMatchSuccess() { + expectFailureWhenTestingThat("aba").matches("[b]"); + assertFailureValue("expected to match", "[b]"); + assertFailureValue("but was", "aba"); + assertThat(expectFailure.getFailure()) + .factKeys() + .contains("Did you mean to call containsMatch() instead of match()?"); + } + @Test @GwtIncompatible("Pattern") public void stringMatchesPattern() { @@ -248,6 +259,17 @@ public void stringMatchesPatternLiteralFail() { + " Pattern.quote()."); } + @Test + @GwtIncompatible("Pattern") + public void stringMatchesPatternLiteralFailButContainsMatchSuccess() { + expectFailureWhenTestingThat("aba").matches(Pattern.compile("[b]")); + assertFailureValue("expected to match", "[b]"); + assertFailureValue("but was", "aba"); + assertThat(expectFailure.getFailure()) + .factKeys() + .contains("Did you mean to call containsMatch() instead of match()?"); + } + @Test public void stringDoesNotMatchString() { assertThat("abcaqadev").doesNotMatch(".*aaa.*"); 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 28caaa685..48b74b3dc 100644 --- a/core/src/test/java/com/google/common/truth/SubjectTest.java +++ b/core/src/test/java/com/google/common/truth/SubjectTest.java @@ -18,6 +18,7 @@ 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; @@ -36,13 +37,12 @@ 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; import java.util.Arrays; import java.util.Iterator; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -460,6 +460,10 @@ public void isNotEqualToWithObjects() { assertThat(a).isNotEqualTo(b); } + @SuppressWarnings({ + "BoxedPrimitiveConstructor", + "deprecation" + }) // intentional check on non-identity objects @Test public void isNotEqualToFailureWithObjects() { Object o = new Integer(1); @@ -494,16 +498,19 @@ public void isNotEqualToSameInstanceBadEqualsImplementation() { 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()) { @@ -553,6 +560,8 @@ public void isInstanceOfInterfaceForNull() { expectFailure.whenTesting().that((Object) null).isInstanceOf(CharSequence.class); } + // false positive; actually an intentional trivially *false* check + @SuppressWarnings("IsInstanceInteger") @Test public void isInstanceOfPrimitiveType() { try { @@ -738,7 +747,8 @@ public void isNoneOfNullFailure() { } @Test - @SuppressWarnings({"EqualsIncompatibleType", "DoNotCall"}) + // test of a mistaken call + @SuppressWarnings({"EqualsIncompatibleType", "DoNotCall", "deprecation"}) public void equalsThrowsUSOE() { try { boolean unused = assertThat(5).equals(5); @@ -755,7 +765,8 @@ public void equalsThrowsUSOE() { } @Test - @SuppressWarnings("DoNotCall") + // test of a mistaken call + @SuppressWarnings({"DoNotCall", "deprecation"}) public void hashCodeThrowsUSOE() { try { int unused = assertThat(5).hashCode(); @@ -771,8 +782,8 @@ public void ignoreCheckDiscardsFailures() { assertThat((Object) null).ignoreCheck().that("foo").isNull(); } - private static Iterable oneShotIterable(final T... values) { - final Iterator iterator = Iterators.forArray(values); + private static Iterable oneShotIterable(T... values) { + Iterator iterator = Iterators.forArray(values); return new Iterable() { @Override public Iterator iterator() { @@ -787,6 +798,7 @@ public String toString() { } @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"); @@ -815,7 +827,11 @@ public boolean equals(Object obj) { } } - private static final class ForbidsEqualityChecksSubject extends Subject { + static final class ForbidsEqualityChecksSubject extends Subject { + static Factory objectsForbiddingEqualityCheck() { + return ForbidsEqualityChecksSubject::new; + } + ForbidsEqualityChecksSubject(FailureMetadata metadata, @Nullable Object actual) { super(metadata, actual); } @@ -833,16 +849,6 @@ public void isNotEqualTo(@Nullable Object unexpected) { } } - private static Subject.Factory - objectsForbiddingEqualityCheck() { - return new Factory() { - @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 aea450c24..b96fd6eb4 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 void contains() { @Test public void containsFailure() { ImmutableTable 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 "); + .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 void doesNotContainFailure() { ImmutableTable 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 "); + .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 @@ private static Cell cell(R row, C col, V val) { 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 d4606c14e..0c5c3af7c 100644 --- a/core/src/test/java/com/google/common/truth/TestCorrespondences.java +++ b/core/src/test/java/com/google/common/truth/TestCorrespondences.java @@ -23,7 +23,7 @@ import com.google.common.base.Splitter; import com.google.common.primitives.Ints; import java.util.List; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** {@link Correspondence} implementations for testing purposes. */ final class TestCorrespondences { @@ -33,16 +33,7 @@ final class TestCorrespondences { * correspond to null only. */ static final Correspondence STRING_PARSES_TO_INTEGER_CORRESPONDENCE = - Correspondence.from( - // If we were allowed to use method references, this would be: - // TestCorrespondences::stringParsesToInteger, - new Correspondence.BinaryPredicate() { - @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 @@ private static boolean stringParsesToInteger( /** A formatter for the diffs between integers. */ static final Correspondence.DiffFormatter INT_DIFF_FORMATTER = - // If we were allowed to use lambdas, this would be: - // (a, e) -> Integer.toString(a - e)); - new Correspondence.DiffFormatter() { - @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,16 +64,11 @@ public String formatDiff(Integer actual, Integer expected) { */ static final Correspondence 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() { - @Override - public boolean apply(@Nullable Integer actual, @Nullable Integer expected) { - if (actual == null || expected == null) { - throw new NullPointerExceptionFromWithin10Of(); - } - 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); @@ -101,15 +80,7 @@ private static final class NullPointerExceptionFromWithin10Of extends NullPointe * expected elements, but throws {@link NullPointerException} on null actual elements. */ static final Correspondence CASE_INSENSITIVE_EQUALITY = - Correspondence.from( - // If we were allowed to use method references, this would be String::equalsIgnoreCase. - new Correspondence.BinaryPredicate() { - @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 @@ -119,16 +90,13 @@ public boolean apply(String actual, String expected) { */ static final Correspondence CASE_INSENSITIVE_EQUALITY_HALF_NULL_SAFE = Correspondence.from( - // If we were allowed to use method references, this would be: - // TestCorrespondences::equalsIgnoreCaseHalfNullSafe, - new Correspondence.BinaryPredicate() { - @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; @@ -141,22 +109,22 @@ private static boolean equalsIgnoreCaseHalfNullSafe(String actual, String expect * 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; } @@ -174,14 +142,14 @@ int getScore() { 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; @@ -205,7 +173,7 @@ public String toString() { * 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 parts = Splitter.on('/').splitToList(str); if (parts.size() != 2) { return null; @@ -215,46 +183,32 @@ public String toString() { 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. * *

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 RECORDS_EQUAL_WITH_SCORE_TOLERANCE_10_NO_DIFF = + static final Correspondence 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() { - @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:"}. If they have different keys, it gives null. */ - static final Correspondence.DiffFormatter RECORD_DIFF_FORMATTER = - // If we were allowed to use method references, this would be: - // TestCorrespondences::formatRecordDiff); - new Correspondence.DiffFormatter() { - @Override - public String formatDiff(Record actual, Record expected) { - return formatRecordDiff(actual, expected); - } - }; + static final Correspondence.DiffFormatter 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:}. @@ -262,7 +216,7 @@ public String formatDiff(Record actual, Record expected) { *

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 RECORDS_EQUAL_WITH_SCORE_TOLERANCE_10 = + static final Correspondence RECORDS_EQUAL_WITH_SCORE_TOLERANCE_10 = RECORDS_EQUAL_WITH_SCORE_TOLERANCE_10_NO_DIFF.formattingDiffsUsing(RECORD_DIFF_FORMATTER); /** @@ -270,36 +224,21 @@ public String formatDiff(Record actual, Record expected) { * 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 PARSED_RECORDS_EQUAL_WITH_SCORE_TOLERANCE_10 = + static final Correspondence 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() { - @Override - public boolean apply(String actual, Record expected) { - 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() { - @Override - public @Nullable String formatDiff(String actual, Record expected) { - 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; } @@ -309,7 +248,7 @@ private static boolean recordsAreCloseEnough(@Nullable Record actual, @Nullable return actual.hasSameId(expected) && Math.abs(actual.getScore() - expected.getScore()) <= 10; } - private static @Nullable 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 { @@ -318,47 +257,33 @@ private static boolean recordsAreCloseEnough(@Nullable Record actual, @Nullable } /** - * 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_ID = - new Function() { - - @Override - public @Nullable Integer apply(Record record) { - return record.hasId() ? record.getId() : null; - } - }; + static final Function 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 NULL_SAFE_RECORD_ID = - new Function() { - - @Override - public @Nullable Integer apply(Record record) { - if (record == null) { - return 0; - } - return record.hasId() ? record.getId() : null; + static final Function 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 PARSED_RECORD_ID = - new Function() { - - @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 907694841..cb75b934e 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 @@ 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 @@ */ @RunWith(JUnit4.class) public class TruthAssertThatTest { - private static final Function> METHOD_TO_RETURN_TYPE_TOKEN = - new Function>() { - @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> verbTypes = + ImmutableSortedSet> verbTypes = FluentIterable.from(asList(StandardSubjectBuilder.class.getMethods())) - .filter( - new Predicate() { - @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> truthTypes = + ImmutableSortedSet> truthTypes = FluentIterable.from(asList(Truth.class.getMethods())) .filter( - new Predicate() { - @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/extension/EmployeeSubject.java b/core/src/test/java/com/google/common/truth/extension/EmployeeSubject.java index bc14e130c..72c6a1778 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 @@ -23,7 +23,7 @@ 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; +import org.jspecify.annotations.Nullable; /** * A Truth subject for {@link Employee}. @@ -34,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 employees() { - return EMPLOYEE_SUBJECT_FACTORY; + return EmployeeSubject::new; } - // Boiler-plate Subject.Factory for EmployeeSubject - private static final Subject.Factory EMPLOYEE_SUBJECT_FACTORY = - EmployeeSubject::new; - private final Employee actual; private EmployeeSubject(FailureMetadata failureMetadata, @Nullable Employee subject) { 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 49006f98e..61002ddcf 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 @@ -23,13 +23,19 @@ import com.google.common.truth.FailureStrategy; import com.google.common.truth.FloatSubject; import com.google.common.truth.GuavaOptionalSubject; +import com.google.common.truth.IntStreamSubject; import com.google.common.truth.IntegerSubject; import com.google.common.truth.IterableSubject; +import com.google.common.truth.LongStreamSubject; import com.google.common.truth.LongSubject; import com.google.common.truth.MapSubject; import com.google.common.truth.MultimapSubject; import com.google.common.truth.MultisetSubject; import com.google.common.truth.ObjectArraySubject; +import com.google.common.truth.OptionalDoubleSubject; +import com.google.common.truth.OptionalIntSubject; +import com.google.common.truth.OptionalLongSubject; +import com.google.common.truth.OptionalSubject; import com.google.common.truth.Ordered; import com.google.common.truth.PrimitiveBooleanArraySubject; import com.google.common.truth.PrimitiveByteArraySubject; @@ -39,12 +45,13 @@ import com.google.common.truth.PrimitiveIntArraySubject; import com.google.common.truth.PrimitiveLongArraySubject; import com.google.common.truth.PrimitiveShortArraySubject; +import com.google.common.truth.StreamSubject; import com.google.common.truth.StringSubject; import com.google.common.truth.Subject; import com.google.common.truth.TableSubject; import com.google.common.truth.ThrowableSubject; import com.google.common.truth.Truth; -import com.google.common.truth.TruthJUnit; +import com.google.common.truth.Truth8; /** * Static references to a variety of classes to force their loading during the {@link TruthGwtTest}. @@ -53,18 +60,24 @@ public class Inventory { BigDecimalSubject bigDecimalSubject; BooleanSubject booleanSubject; ClassSubject classSubject; - ComparableSubject comparableSubject; + ComparableSubject comparableSubject; DoubleSubject doubleSubject; FailureStrategy failureStrategy; FloatSubject floatSubject; GuavaOptionalSubject guavaOptionalSubject; IntegerSubject integerSubject; + IntStreamSubject intStreamSubject; IterableSubject iterableSubject; LongSubject longSubject; + LongStreamSubject longStreamSubject; MapSubject mapSubject; MultimapSubject multimapSubject; MultisetSubject multisetSubject; - ObjectArraySubject objectArraySubject; + ObjectArraySubject objectArraySubject; + OptionalSubject optionalSubject; + OptionalDoubleSubject optionalDoubleSubject; + OptionalIntSubject optionalIntSubject; + OptionalLongSubject optionalLongSubject; Ordered ordered; PrimitiveBooleanArraySubject primitiveBooleanArraySubject; PrimitiveByteArraySubject primitiveByteArraySubject; @@ -74,11 +87,12 @@ public class Inventory { PrimitiveIntArraySubject primitiveIntArraySubject; PrimitiveLongArraySubject primitiveLongArraySubject; PrimitiveShortArraySubject primitiveShortArraySubject; + StreamSubject streamSubject; StringSubject stringSubject; - Subject.Factory subjectFactory; + Subject.Factory subjectFactory; Subject subject; TableSubject tableSubject; ThrowableSubject throwableSubject; Truth truth; - TruthJUnit truthJUnit; + Truth8 truth8; } 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 b66f3ed53..aab6aa12f 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 @@ -52,6 +52,7 @@ public void testBoolean() { } } + @SuppressWarnings("TruthSelfEquals") public void testInteger() { assertThat(457923).isEqualTo(457923); try { @@ -133,10 +134,11 @@ public void testList() { } public void testObjectArray() { - Set[] setOfString = {new HashSet(asList("foo", "bar", "bash"))}; + Set[] setOfString = {new HashSet(asList("foo", "bar", "bash"))}; assertThat(setOfString).asList().contains(new HashSet(asList("foo", "bar", "bash"))); } + @SuppressWarnings("IsInstanceIterable") // test of an intentionally trivially true assertion public void testDefault() { assertThat(new Object()).isNotNull(); assertThat(new ArrayList()).isInstanceOf(AbstractList.class); diff --git a/core/src/test/java/com/google/common/truth/gwt/TruthTest.gwt.xml b/core/src/test/java/com/google/common/truth/gwt/TruthTest.gwt.xml index ee9caccb9..a0362fbd8 100644 --- a/core/src/test/java/com/google/common/truth/gwt/TruthTest.gwt.xml +++ b/core/src/test/java/com/google/common/truth/gwt/TruthTest.gwt.xml @@ -3,6 +3,5 @@ - diff --git a/extensions/java8/pom.xml b/extensions/java8/pom.xml index d4e3fa792..b6f38c544 100644 --- a/extensions/java8/pom.xml +++ b/extensions/java8/pom.xml @@ -10,21 +10,26 @@ HEAD-SNAPSHOT truth-java8-extension - Truth Extension for Java8 + Obsolete Truth Extension for Java8 - An extension for the Truth test assertion framework supporting Java8 types and structures + Obsolete, empty artifact that merely pulls in the main `truth` artifact: Assertions for Java 8 types are now part of that main artifact. com.google.truth truth - - org.checkerframework - checker-qual - + + + ../.. + + LICENSE + + META-INF + + maven-javadoc-plugin 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 deleted file mode 100644 index 6f62a0f13..000000000 --- a/extensions/java8/src/main/java/com/google/common/truth/StreamSubject.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (c) 2016 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.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; -import java.util.stream.Stream; -import org.checkerframework.checker.nullness.qual.Nullable; - -/** - * Propositions for {@link Stream} subjects. - * - *

Note: the wrapped stream will be drained immediately into a private collection to - * provide more readable failure messages. You should not use this class if you intend to leave the - * stream un-consumed or if the stream is very large or infinite. - * - *

If you intend to make multiple assertions on the same stream of data you should instead first - * collect the contents of the stream into a collection, and then assert directly on that. - * - *

For very large or infinite streams you may want to first {@linkplain Stream#limit limit} the - * stream before asserting on it. - * - * @author Kurt Alfred Kluever - */ -public final class StreamSubject extends Subject { - - private final List actualList; - - private StreamSubject(FailureMetadata failureMetadata, @Nullable Stream stream) { - super(failureMetadata, stream); - this.actualList = (stream == null) ? null : stream.collect(toCollection(ArrayList::new)); - } - - @Override - protected String actualCustomStringRepresentation() { - return String.valueOf(actualList); - } - - public static Subject.Factory> streams() { - return (metadata, subject) -> new StreamSubject(metadata, subject); - } - - /** Fails if the subject is not empty. */ - public void isEmpty() { - check().that(actualList).isEmpty(); - } - - /** Fails if the subject is empty. */ - public void isNotEmpty() { - check().that(actualList).isNotEmpty(); - } - - /** - * Fails if the subject does not have the given size. - * - *

If you'd like to check that your stream contains more than {@link Integer#MAX_VALUE} - * elements, use {@code assertThat(stream.count()).isEqualTo(...)}. - */ - public void hasSize(int expectedSize) { - check().that(actualList).hasSize(expectedSize); - } - - /** Fails if the subject does not contain the given element. */ - public void contains(@Nullable Object element) { - check().that(actualList).contains(element); - } - - /** Fails if the subject contains the given element. */ - public void doesNotContain(@Nullable Object element) { - check().that(actualList).doesNotContain(element); - } - - /** Fails if the subject contains duplicate elements. */ - public void containsNoDuplicates() { - check().that(actualList).containsNoDuplicates(); - } - - /** Fails if the subject does not contain at least one of the given elements. */ - public void containsAnyOf( - @Nullable Object first, @Nullable Object second, @Nullable Object @Nullable ... rest) { - check().that(actualList).containsAnyOf(first, second, rest); - } - - /** Fails if the subject does not contain at least one of the given elements. */ - public void containsAnyIn(Iterable expected) { - check().that(actualList).containsAnyIn(expected); - } - - /** - * Fails if the subject does not contain all of the given elements. If an element appears more - * than once in the given elements, then it must appear at least that number of times in the - * actual elements. - * - *

To also test that the contents appear in the given order, make a call to {@code inOrder()} - * on the object returned by this method. The expected elements must appear in the given order - * within the actual elements, but they are not required to be consecutive. - */ - @CanIgnoreReturnValue - public Ordered containsAtLeast( - @Nullable Object first, @Nullable Object second, @Nullable Object @Nullable ... rest) { - return check().that(actualList).containsAtLeast(first, second, rest); - } - - /** - * Fails if the subject does not contain all of the given elements. If an element appears more - * than once in the given elements, then it must appear at least that number of times in the - * actual elements. - * - *

To also test that the contents appear in the given order, make a call to {@code inOrder()} - * on the object returned by this method. The expected elements must appear in the given order - * within the actual elements, but they are not required to be consecutive. - */ - @CanIgnoreReturnValue - public Ordered containsAtLeastElementsIn(Iterable expected) { - return check().that(actualList).containsAtLeastElementsIn(expected); - } - - // TODO(cpovirk): Add array overload of contains*ElementsIn methods? Also for int and long stream. - - /** - * Fails if the subject does not contain exactly the given elements. - * - *

Multiplicity is respected. For example, an object duplicated exactly 3 times in the - * parameters asserts that the object must likewise be duplicated exactly 3 times in the subject. - * - *

To also test that the contents appear in the given order, make a call to {@code inOrder()} - * on the object returned by this method. - */ - @CanIgnoreReturnValue - public Ordered containsExactly(@Nullable Object @Nullable ... varargs) { - return check().that(actualList).containsExactly(varargs); - } - - /** - * Fails if the subject does not contain exactly the given elements. - * - *

Multiplicity is respected. For example, an object duplicated exactly 3 times in the - * parameters asserts that the object must likewise be duplicated exactly 3 times in the subject. - * - *

To also test that the contents appear in the given order, make a call to {@code inOrder()} - * on the object returned by this method. - */ - @CanIgnoreReturnValue - public Ordered containsExactlyElementsIn(Iterable expected) { - return check().that(actualList).containsExactlyElementsIn(expected); - } - - /** - * Fails if the subject contains any of the given elements. (Duplicates are irrelevant to this - * test, which fails if any of the actual elements equal any of the excluded.) - */ - public void containsNoneOf( - @Nullable Object first, @Nullable Object second, @Nullable Object @Nullable ... rest) { - check().that(actualList).containsNoneOf(first, second, rest); - } - - /** - * Fails if the subject contains any of the given elements. (Duplicates are irrelevant to this - * test, which fails if any of the actual elements equal any of the excluded.) - */ - public void containsNoneIn(Iterable excluded) { - check().that(actualList).containsNoneIn(excluded); - } - - /** - * Fails if the subject is not strictly ordered, according to the natural ordering of its - * elements. Strictly ordered means that each element in the stream is strictly greater - * than the element that preceded it. - * - * @throws ClassCastException if any pair of elements is not mutually Comparable - * @throws NullPointerException if any element is null - */ - public void isInStrictOrder() { - check().that(actualList).isInStrictOrder(); - } - - /** - * Fails if the subject is not strictly ordered, according to the given comparator. Strictly - * ordered means that each element in the stream is strictly greater than the element that - * preceded it. - * - * @throws ClassCastException if any pair of elements is not mutually Comparable - */ - public void isInStrictOrder(Comparator comparator) { - check().that(actualList).isInStrictOrder(comparator); - } - - /** - * Fails if the subject is not ordered, according to the natural ordering of its elements. Ordered - * means that each element in the stream is greater than or equal to the element that preceded it. - * - * @throws ClassCastException if any pair of elements is not mutually Comparable - * @throws NullPointerException if any element is null - */ - public void isInOrder() { - check().that(actualList).isInOrder(); - } - - /** - * Fails if the subject is not ordered, according to the given comparator. Ordered means that each - * element in the stream is greater than or equal to the element that preceded it. - * - * @throws ClassCastException if any pair of elements is not mutually Comparable - */ - public void isInOrder(Comparator comparator) { - check().that(actualList).isInOrder(comparator); - } - - /** - * @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 deleted file mode 100644 index f9aa5adff..000000000 --- a/extensions/java8/src/main/java/com/google/common/truth/Truth8.gwt.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - 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 bcb11bd99..84a0e102e 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 @@ -18,7 +18,7 @@ import static com.google.common.truth.FailureAssertions.assertFailureKeys; import static com.google.common.truth.FailureAssertions.assertFailureValue; import static com.google.common.truth.IntStreamSubject.intStreams; -import static com.google.common.truth.Truth8.assertThat; +import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; import static org.junit.Assert.fail; @@ -37,6 +37,7 @@ public final class IntStreamSubjectTest { @Test + @SuppressWarnings("TruthSelfEquals") public void testIsEqualTo() throws Exception { IntStream stream = IntStream.of(42); assertThat(stream).isEqualTo(stream); @@ -66,6 +67,7 @@ public void testNullStreamIsNull() throws Exception { } @Test + @SuppressWarnings("TruthSelfEquals") public void testIsSameInstanceAs() throws Exception { IntStream stream = IntStream.of(1); assertThat(stream).isSameInstanceAs(stream); @@ -202,17 +204,16 @@ public void testContainsAtLeast_inOrder() throws Exception { @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 +238,19 @@ public void testContainsAtLeastElementsIn_inOrder() throws Exception { @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 +260,10 @@ public void testContainsExactly() throws Exception { @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 +273,12 @@ public void testContainsExactly_inOrder() throws Exception { @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 +289,12 @@ public void testContainsExactlyElementsIn() throws Exception { @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 +304,15 @@ public void testContainsExactlyElementsIn_inOrder() throws Exception { @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 9908b3548..41026046d 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 @@ -18,7 +18,7 @@ import static com.google.common.truth.FailureAssertions.assertFailureKeys; import static com.google.common.truth.FailureAssertions.assertFailureValue; import static com.google.common.truth.LongStreamSubject.longStreams; -import static com.google.common.truth.Truth8.assertThat; +import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; import static org.junit.Assert.fail; @@ -37,6 +37,7 @@ public final class LongStreamSubjectTest { @Test + @SuppressWarnings("TruthSelfEquals") public void testIsEqualTo() throws Exception { LongStream stream = LongStream.of(42); assertThat(stream).isEqualTo(stream); @@ -66,6 +67,7 @@ public void testNullStreamIsNull() throws Exception { } @Test + @SuppressWarnings("TruthSelfEquals") public void testIsSameInstanceAs() throws Exception { LongStream stream = LongStream.of(1); assertThat(stream).isSameInstanceAs(stream); @@ -203,17 +205,16 @@ public void testContainsAtLeast_inOrder() throws Exception { @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 +249,19 @@ public void testContainsAtLeastElementsIn_inOrder() throws Exception { @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 +282,10 @@ public void testContainsExactly() throws Exception { @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 +295,12 @@ public void testContainsExactly_inOrder() throws Exception { @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 +311,12 @@ public void testContainsExactlyElementsIn() throws Exception { @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 +334,15 @@ public void testContainsExactlyElementsIn_inOrder() throws Exception { @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/OptionalDoubleSubjectTest.java b/extensions/java8/src/test/java/com/google/common/truth/OptionalDoubleSubjectTest.java index 94490fa02..481547799 100644 --- a/extensions/java8/src/test/java/com/google/common/truth/OptionalDoubleSubjectTest.java +++ b/extensions/java8/src/test/java/com/google/common/truth/OptionalDoubleSubjectTest.java @@ -18,7 +18,6 @@ import static com.google.common.truth.ExpectFailure.assertThat; import static com.google.common.truth.OptionalDoubleSubject.optionalDoubles; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth8.assertThat; import java.util.OptionalDouble; import org.junit.Test; diff --git a/extensions/java8/src/test/java/com/google/common/truth/OptionalIntSubjectTest.java b/extensions/java8/src/test/java/com/google/common/truth/OptionalIntSubjectTest.java index 9c68a875f..53b2c7831 100644 --- a/extensions/java8/src/test/java/com/google/common/truth/OptionalIntSubjectTest.java +++ b/extensions/java8/src/test/java/com/google/common/truth/OptionalIntSubjectTest.java @@ -18,7 +18,6 @@ import static com.google.common.truth.ExpectFailure.assertThat; import static com.google.common.truth.OptionalIntSubject.optionalInts; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth8.assertThat; import java.util.OptionalInt; import org.junit.Test; diff --git a/extensions/java8/src/test/java/com/google/common/truth/OptionalLongSubjectTest.java b/extensions/java8/src/test/java/com/google/common/truth/OptionalLongSubjectTest.java index 211ed504d..dc3678892 100644 --- a/extensions/java8/src/test/java/com/google/common/truth/OptionalLongSubjectTest.java +++ b/extensions/java8/src/test/java/com/google/common/truth/OptionalLongSubjectTest.java @@ -18,7 +18,6 @@ import static com.google.common.truth.ExpectFailure.assertThat; import static com.google.common.truth.OptionalLongSubject.optionalLongs; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth8.assertThat; import java.util.OptionalLong; import org.junit.Test; diff --git a/extensions/java8/src/test/java/com/google/common/truth/OptionalSubjectTest.java b/extensions/java8/src/test/java/com/google/common/truth/OptionalSubjectTest.java index e1cdc5662..757af0bd6 100644 --- a/extensions/java8/src/test/java/com/google/common/truth/OptionalSubjectTest.java +++ b/extensions/java8/src/test/java/com/google/common/truth/OptionalSubjectTest.java @@ -18,7 +18,6 @@ import static com.google.common.truth.ExpectFailure.assertThat; import static com.google.common.truth.OptionalSubject.optionals; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth8.assertThat; import static org.junit.Assert.fail; import java.util.Optional; diff --git a/extensions/java8/src/test/java/com/google/common/truth/PathSubjectTest.java b/extensions/java8/src/test/java/com/google/common/truth/PathSubjectTest.java index 1e97f4d05..801dfd0cf 100644 --- a/extensions/java8/src/test/java/com/google/common/truth/PathSubjectTest.java +++ b/extensions/java8/src/test/java/com/google/common/truth/PathSubjectTest.java @@ -15,7 +15,7 @@ */ package com.google.common.truth; -import static com.google.common.truth.Truth8.assertThat; +import static com.google.common.truth.Truth.assertThat; import java.nio.file.Paths; import org.junit.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 09a767fc8..cc1037d80 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 @@ -15,12 +15,12 @@ */ package com.google.common.truth; +import static com.google.common.truth.ExpectFailure.assertThat; import static com.google.common.truth.FailureAssertions.assertFailureKeys; 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 com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; -import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import java.util.stream.Stream; @@ -33,22 +33,78 @@ * * @author Kurt Alfred Kluever */ +// TODO: b/113905249 - Move this and other tests from extensions to core @RunWith(JUnit4.class) public final class StreamSubjectTest { - @SuppressWarnings("DoNotCall") + @SuppressWarnings({"deprecation", "TruthSelfEquals"}) // test of a possibly mistaken call @Test - public void testIsEqualTo() throws Exception { + public void testIsEqualToSameInstancePreviouslyConsumed() throws Exception { Stream stream = Stream.of("hello"); - assertThrows(UnsupportedOperationException.class, () -> assertThat(stream).isEqualTo(stream)); + stream.forEach(e -> {}); // Consume it so that we can verify that isEqualTo still works + assertThat(stream).isEqualTo(stream); } - @SuppressWarnings("DoNotCall") + @SuppressWarnings({"deprecation", "TruthSelfEquals"}) // test of a possibly mistaken call @Test - public void testIsNotEqualTo() throws Exception { + public void testIsEqualToSameInstanceDoesNotConsume() throws Exception { Stream stream = Stream.of("hello"); - assertThrows( - UnsupportedOperationException.class, () -> assertThat(stream).isNotEqualTo(stream)); + assertThat(stream).isEqualTo(stream); + assertThat(stream).containsExactly("hello"); + } + + @SuppressWarnings({ + "deprecation", // test of a possibly mistaken call + "StreamToString", // not very useful but the best we can do + }) + @Test + public void testIsEqualToFailurePreviouslyConsumed() throws Exception { + Stream stream = Stream.of("hello"); + stream.forEach(e -> {}); // Consume it so that we can verify that isEqualTo still works + AssertionError failure = + expectFailure(whenTesting -> whenTesting.that(stream).isEqualTo(Stream.of("hello"))); + assertThat(failure) + .factValue("but was") + .isEqualTo("Stream that has already been operated upon or closed: " + stream); + assertThat(failure) + .hasMessageThat() + .contains("Warning: Stream equality is based on object identity."); + } + + @SuppressWarnings("deprecation") // test of a possibly mistaken call + @Test + public void testIsEqualToFailureNotPreviouslyConsumed() throws Exception { + Stream stream = Stream.of("hello"); + AssertionError failure = + expectFailure(whenTesting -> whenTesting.that(stream).isEqualTo(Stream.of("hello"))); + assertThat(failure).factValue("but was").isEqualTo("[hello]"); + assertThat(failure) + .hasMessageThat() + .contains("Warning: Stream equality is based on object identity."); + } + + @SuppressWarnings({ + "deprecation", // test of a possibly mistaken call + "StreamToString", // not very useful but the best we can do + }) + @Test + public void testIsNotEqualToSameInstance() throws Exception { + Stream stream = Stream.of("hello"); + stream.forEach(e -> {}); // Consume it so that we can verify that isNotEqualTo still works + AssertionError failure = + expectFailure(whenTesting -> whenTesting.that(stream).isNotEqualTo(stream)); + assertThat(failure).factKeys().containsExactly("expected not to be"); + assertThat(failure) + .factValue("expected not to be") + .isEqualTo("Stream that has already been operated upon or closed: " + stream); + } + + @SuppressWarnings("deprecation") // test of a possibly mistaken call + @Test + public void testIsNotEqualToOtherInstance() throws Exception { + Stream stream = Stream.of("hello"); + stream.forEach(e -> {}); // Consume it so that we can verify that isNotEqualTo still works + assertThat(stream).isNotEqualTo(Stream.of("hello")); } @Test @@ -68,6 +124,7 @@ public void testNullStreamIsNull() throws Exception { } @Test + @SuppressWarnings("TruthSelfEquals") public void testIsSameInstanceAs() throws Exception { Stream stream = Stream.of("hello"); assertThat(stream).isSameInstanceAs(stream); @@ -102,8 +159,9 @@ public void testHasSize() throws Exception { @Test public void testHasSize_fails() throws Exception { - AssertionError unused = + AssertionError failure = expectFailure(whenTesting -> whenTesting.that(Stream.of("hello")).hasSize(2)); + assertThat(failure).factValue("value of").isEqualTo("stream.size()"); } @Test @@ -212,17 +270,19 @@ public void testContainsAtLeast_inOrder() throws Exception { @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 @@ -249,19 +309,19 @@ public void testContainsAtLeastElementsIn_inOrder() throws Exception { @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 @@ -270,15 +330,19 @@ public void testContainsExactly() throws Exception { assertThat(Stream.of("hell", "hello")).containsExactly("hello", "hell"); } + @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 @@ -288,13 +352,15 @@ public void testContainsExactly_inOrder() throws Exception { @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 @@ -305,13 +371,14 @@ public void testContainsExactlyElementsIn() throws Exception { @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 @@ -323,15 +390,15 @@ public void testContainsExactlyElementsIn_inOrder() throws Exception { @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 82bdc46d8..a70faa5d8 100644 --- a/extensions/liteproto/pom.xml +++ b/extensions/liteproto/pom.xml @@ -25,8 +25,8 @@ guava - org.checkerframework - checker-qual + org.jspecify + jspecify com.google.auto.value @@ -44,6 +44,15 @@ + + + ../.. + + LICENSE + + META-INF + + kr.motd.maven 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 3a0774eed..0fcea474f 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 @@ -27,7 +27,7 @@ import com.google.errorprone.annotations.CheckReturnValue; import com.google.protobuf.MessageLite; import java.util.regex.Pattern; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Truth subjects for the Lite version of Protocol Buffers. diff --git a/extensions/liteproto/src/main/java/com/google/common/truth/extensions/proto/LiteProtoTruth.java b/extensions/liteproto/src/main/java/com/google/common/truth/extensions/proto/LiteProtoTruth.java index 94895d931..d3179b588 100644 --- a/extensions/liteproto/src/main/java/com/google/common/truth/extensions/proto/LiteProtoTruth.java +++ b/extensions/liteproto/src/main/java/com/google/common/truth/extensions/proto/LiteProtoTruth.java @@ -21,7 +21,7 @@ import com.google.common.truth.Subject; import com.google.errorprone.annotations.CheckReturnValue; import com.google.protobuf.MessageLite; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * A set of static methods to begin a Truth assertion chain for the lite version of protocol 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 c3e0ed2dc..eba52260c 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,19 +16,21 @@ 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; import java.util.Collection; import java.util.regex.Pattern; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -174,34 +176,37 @@ public void testIsEqualTo_success() { @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 void testHasAllRequiredFields_failures() { 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 void testDefaultInstance_success() { @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 void testSerializedSize_success() { 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 @@ private void expectRegex(AssertionError e, String regex) { private void expectNoRegex(AssertionError e, String regex) { expect.that(e).hasMessageThat().doesNotMatch(Pattern.compile(regex, Pattern.DOTALL)); } + + private static AssertionError expectFailure( + SimpleSubjectBuilderCallback assertionCallback) { + return expectFailureAbout(liteProtos(), assertionCallback); + } } diff --git a/extensions/proto/pom.xml b/extensions/proto/pom.xml index b69b8e243..d52855799 100644 --- a/extensions/proto/pom.xml +++ b/extensions/proto/pom.xml @@ -29,8 +29,8 @@ guava - org.checkerframework - checker-qual + org.jspecify + jspecify com.google.auto.value @@ -46,6 +46,15 @@ + + + ../.. + + LICENSE + + META-INF + + kr.motd.maven diff --git a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/AnyUtils.java b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/AnyUtils.java index 34e1e3e73..454f50d1a 100644 --- a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/AnyUtils.java +++ b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/AnyUtils.java @@ -69,8 +69,9 @@ static ExtensionRegistry defaultExtensionRegistry() { return DEFAULT_EXTENSION_REGISTRY; } - /** Unpack an `Any` proto using the TypeRegistry and ExtensionRegistry on `config`. */ - static Optional unpack(Message any, FluentEqualityConfig config) { + /** Unpack an `Any` proto using the given TypeRegistry and ExtensionRegistry. */ + static Optional unpack( + Message any, TypeRegistry typeRegistry, ExtensionRegistry extensionRegistry) { Preconditions.checkArgument( any.getDescriptorForType().equals(Any.getDescriptor()), "Expected type google.protobuf.Any, but was: %s", @@ -80,13 +81,12 @@ static Optional unpack(Message any, FluentEqualityConfig config) { ByteString value = (ByteString) any.getField(valueFieldDescriptor()); try { - Descriptor descriptor = config.useTypeRegistry().getDescriptorForTypeUrl(typeUrl); + Descriptor descriptor = typeRegistry.getDescriptorForTypeUrl(typeUrl); if (descriptor == null) { return Optional.absent(); } - Message defaultMessage = - DynamicMessage.parseFrom(descriptor, value, config.useExtensionRegistry()); + Message defaultMessage = DynamicMessage.parseFrom(descriptor, value, extensionRegistry); return Optional.of(defaultMessage); } catch (InvalidProtocolBufferException e) { return Optional.absent(); 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 e6c0af64a..990d350d1 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 @@ -557,6 +557,9 @@ default void printFieldValue(SubScopeId subScopeId, Object o, StringBuilder sb) case UNKNOWN_FIELD_DESCRIPTOR: printFieldValue(subScopeId.unknownFieldDescriptor(), o, sb); return; + case UNPACKED_ANY_VALUE_TYPE: + printFieldValue(AnyUtils.valueFieldDescriptor(), o, sb); + return; } throw new AssertionError(subScopeId.kind()); } diff --git a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldNumberTree.java b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldNumberTree.java index d33e2fee1..698b9d948 100644 --- a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldNumberTree.java +++ b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldNumberTree.java @@ -16,9 +16,12 @@ package com.google.common.truth.extensions.proto; +import com.google.common.base.Optional; import com.google.common.collect.Maps; import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; +import com.google.protobuf.TypeRegistry; import com.google.protobuf.UnknownFieldSet; import java.util.List; import java.util.Map; @@ -62,7 +65,8 @@ boolean hasChild(SubScopeId subScopeId) { return children.containsKey(subScopeId); } - static FieldNumberTree fromMessage(Message message) { + static FieldNumberTree fromMessage( + Message message, TypeRegistry typeRegistry, ExtensionRegistry extensionRegistry) { FieldNumberTree tree = new FieldNumberTree(); // Known fields. @@ -72,15 +76,25 @@ static FieldNumberTree fromMessage(Message message) { FieldNumberTree childTree = new FieldNumberTree(); tree.children.put(subScopeId, childTree); - Object fieldValue = knownFieldValues.get(field); - if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { - if (field.isRepeated()) { - List valueList = (List) fieldValue; - for (Object value : valueList) { - childTree.merge(fromMessage((Message) value)); + if (field.equals(AnyUtils.valueFieldDescriptor())) { + // Handle Any protos specially. + Optional unpackedAny = AnyUtils.unpack(message, typeRegistry, extensionRegistry); + if (unpackedAny.isPresent()) { + tree.children.put( + SubScopeId.ofUnpackedAnyValueType(unpackedAny.get().getDescriptorForType()), + fromMessage(unpackedAny.get(), typeRegistry, extensionRegistry)); + } + } else { + Object fieldValue = knownFieldValues.get(field); + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + if (field.isRepeated()) { + List valueList = (List) fieldValue; + for (Object value : valueList) { + childTree.merge(fromMessage((Message) value, typeRegistry, extensionRegistry)); + } + } else { + childTree.merge(fromMessage((Message) fieldValue, typeRegistry, extensionRegistry)); } - } else { - childTree.merge(fromMessage((Message) fieldValue)); } } } @@ -91,11 +105,14 @@ static FieldNumberTree fromMessage(Message message) { return tree; } - static FieldNumberTree fromMessages(Iterable messages) { + static FieldNumberTree fromMessages( + Iterable messages, + TypeRegistry typeRegistry, + ExtensionRegistry extensionRegistry) { FieldNumberTree tree = new FieldNumberTree(); for (Message message : messages) { if (message != null) { - tree.merge(fromMessage(message)); + tree.merge(fromMessage(message, typeRegistry, extensionRegistry)); } } return tree; diff --git a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldScopeImpl.java b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldScopeImpl.java index 4acf9916b..0eadd855c 100644 --- a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldScopeImpl.java +++ b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldScopeImpl.java @@ -28,7 +28,9 @@ import com.google.common.collect.Lists; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; +import com.google.protobuf.TypeRegistry; import java.util.List; /** @@ -62,13 +64,17 @@ private static FieldScope create( // Instantiation methods. ////////////////////////////////////////////////////////////////////////////////////////////////// - static FieldScope createFromSetFields(Message message) { + static FieldScope createFromSetFields( + Message message, TypeRegistry typeRegistry, ExtensionRegistry extensionRegistry) { return create( - FieldScopeLogic.partialScope(message), + FieldScopeLogic.partialScope(message, typeRegistry, extensionRegistry), Functions.constant(String.format("FieldScopes.fromSetFields({%s})", message.toString()))); } - static FieldScope createFromSetFields(Iterable messages) { + static FieldScope createFromSetFields( + Iterable messages, + TypeRegistry typeRegistry, + ExtensionRegistry extensionRegistry) { if (emptyOrAllNull(messages)) { return create( FieldScopeLogic.none(), @@ -82,7 +88,8 @@ static FieldScope createFromSetFields(Iterable messages) { getDescriptors(messages)); return create( - FieldScopeLogic.partialScope(messages, optDescriptor.get()), + FieldScopeLogic.partialScope( + messages, optDescriptor.get(), typeRegistry, extensionRegistry), Functions.constant(String.format("FieldScopes.fromSetFields(%s)", formatList(messages)))); } diff --git a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldScopeLogic.java b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldScopeLogic.java index 31fd0563b..dfca1f805 100644 --- a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldScopeLogic.java +++ b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldScopeLogic.java @@ -28,7 +28,9 @@ import com.google.errorprone.annotations.ForOverride; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; +import com.google.protobuf.TypeRegistry; import java.util.List; /** @@ -267,14 +269,21 @@ public String toString() { } } - static FieldScopeLogic partialScope(Message message) { + static FieldScopeLogic partialScope( + Message message, TypeRegistry typeRegistry, ExtensionRegistry extensionRegistry) { return new RootPartialScopeLogic( - FieldNumberTree.fromMessage(message), message.toString(), message.getDescriptorForType()); + FieldNumberTree.fromMessage(message, typeRegistry, extensionRegistry), + message.toString(), + message.getDescriptorForType()); } - static FieldScopeLogic partialScope(Iterable messages, Descriptor descriptor) { + static FieldScopeLogic partialScope( + Iterable messages, + Descriptor descriptor, + TypeRegistry typeRegistry, + ExtensionRegistry extensionRegistry) { return new RootPartialScopeLogic( - FieldNumberTree.fromMessages(messages), + FieldNumberTree.fromMessages(messages, typeRegistry, extensionRegistry), Joiner.on(", ").useForNull("null").join(messages), descriptor); } @@ -304,11 +313,18 @@ protected FieldMatcherLogicBase(boolean isRecursive) { @Override final FieldScopeResult policyFor(Descriptor rootDescriptor, SubScopeId subScopeId) { - if (subScopeId.kind() == SubScopeId.Kind.UNKNOWN_FIELD_DESCRIPTOR) { - return FieldScopeResult.EXCLUDED_RECURSIVELY; + FieldDescriptor fieldDescriptor = null; + switch (subScopeId.kind()) { + case FIELD_DESCRIPTOR: + fieldDescriptor = subScopeId.fieldDescriptor(); + break; + case UNPACKED_ANY_VALUE_TYPE: + fieldDescriptor = AnyUtils.valueFieldDescriptor(); + break; + case UNKNOWN_FIELD_DESCRIPTOR: + return FieldScopeResult.EXCLUDED_RECURSIVELY; } - FieldDescriptor fieldDescriptor = subScopeId.fieldDescriptor(); if (matchesFieldDescriptor(rootDescriptor, fieldDescriptor)) { return FieldScopeResult.of(/* included = */ true, isRecursive); } 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 ae0b634c2..c45b1c61e 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, String> fieldNumbersFunction( - final String fmt, final Iterable fieldNumbers) { - return new Function, String>() { - @Override - public String apply(Optional optDescriptor) { - return resolveFieldNumbers(optDescriptor, fmt, fieldNumbers); - } - }; + String fmt, Iterable fieldNumbers) { + return optDescriptor -> resolveFieldNumbers(optDescriptor, fmt, fieldNumbers); } /** @@ -50,25 +45,15 @@ public String apply(Optional optDescriptor) { * @param fmt Format string that must contain exactly one '%s' and no other format parameters. */ static Function, String> fieldScopeFunction( - final String fmt, final FieldScope fieldScope) { - return new Function, String>() { - @Override - public String apply(Optional 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, String> concat( - final Function, String> function1, - final Function, String> function2) { - return new Function, String>() { - @Override - public String apply(Optional optDescriptor) { - return function1.apply(optDescriptor) + function2.apply(optDescriptor); - } - }; + Function, String> function1, + Function, String> function2) { + return optDescriptor -> function1.apply(optDescriptor) + function2.apply(optDescriptor); } /** diff --git a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldScopes.java b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldScopes.java index 9b709e550..0ba6b4402 100644 --- a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldScopes.java +++ b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/FieldScopes.java @@ -19,7 +19,9 @@ import static com.google.common.truth.extensions.proto.FieldScopeUtil.asList; import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; +import com.google.protobuf.TypeRegistry; /** Factory class for {@link FieldScope} instances. */ public final class FieldScopes { @@ -66,7 +68,58 @@ public final class FieldScopes { // Alternatively II, add Scope.PARTIAL support to ProtoFluentEquals, but with a different name and // explicit documentation that it may cause issues with Proto 3. public static FieldScope fromSetFields(Message message) { - return FieldScopeImpl.createFromSetFields(message); + return fromSetFields( + message, AnyUtils.defaultTypeRegistry(), AnyUtils.defaultExtensionRegistry()); + } + + /** + * Returns a {@link FieldScope} which is constrained to precisely those specific field paths that + * are explicitly set in the message. Note that, for version 3 protobufs, such a {@link + * FieldScope} will omit fields in the provided message which are set to default values. + * + *

This can be used limit the scope of a comparison to a complex set of fields in a very brief + * statement. Often, {@code message} is the expected half of a comparison about to be performed. + * + *

Example usage: + * + *

{@code
+   * Foo actual = Foo.newBuilder().setBar(3).setBaz(4).build();
+   * Foo expected = Foo.newBuilder().setBar(3).setBaz(5).build();
+   * // Fails, because actual.getBaz() != expected.getBaz().
+   * assertThat(actual).isEqualTo(expected);
+   *
+   * Foo scope = Foo.newBuilder().setBar(2).build();
+   * // Succeeds, because only the field 'bar' is compared.
+   * assertThat(actual).withPartialScope(FieldScopes.fromSetFields(scope)).isEqualTo(expected);
+   *
+   * }
+ * + *

The returned {@link FieldScope} does not respect repeated field indices nor map keys. For + * example, if the provided message sets different field values for different elements of a + * repeated field, like so: + * + *

{@code
+   * sub_message: {
+   *   foo: "foo"
+   * }
+   * sub_message: {
+   *   bar: "bar"
+   * }
+   * }
+ * + *

The {@link FieldScope} will contain {@code sub_message.foo} and {@code sub_message.bar} for + * *all* repeated {@code sub_messages}, including those beyond index 1. + * + *

If there are {@code google.protobuf.Any} protos anywhere within these messages, they will be + * unpacked using the provided {@link TypeRegistry} and {@link ExtensionRegistry} to determine + * which fields within them should be compared. + * + * @see ProtoFluentAssertion#unpackingAnyUsing + * @since 1.2 + */ + public static FieldScope fromSetFields( + Message message, TypeRegistry typeRegistry, ExtensionRegistry extensionRegistry) { + return FieldScopeImpl.createFromSetFields(message, typeRegistry, extensionRegistry); } /** @@ -89,7 +142,29 @@ public static FieldScope fromSetFields( * or the {@link FieldScope} for the merge of all the messages. These are equivalent. */ public static FieldScope fromSetFields(Iterable messages) { - return FieldScopeImpl.createFromSetFields(messages); + return fromSetFields( + messages, AnyUtils.defaultTypeRegistry(), AnyUtils.defaultExtensionRegistry()); + } + + /** + * Creates a {@link FieldScope} covering the fields set in every message in the provided list of + * messages, with the same semantics as in {@link #fromSetFields(Message)}. + * + *

This can be thought of as the union of the {@link FieldScope}s for each individual message, + * or the {@link FieldScope} for the merge of all the messages. These are equivalent. + * + *

If there are {@code google.protobuf.Any} protos anywhere within these messages, they will be + * unpacked using the provided {@link TypeRegistry} and {@link ExtensionRegistry} to determine + * which fields within them should be compared. + * + * @see ProtoFluentAssertion#unpackingAnyUsing + * @since 1.2 + */ + public static FieldScope fromSetFields( + Iterable messages, + TypeRegistry typeRegistry, + ExtensionRegistry extensionRegistry) { + return FieldScopeImpl.createFromSetFields(messages, typeRegistry, extensionRegistry); } /** 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 657b53b86..b3c994a4e 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 @@ -34,7 +34,7 @@ import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; import com.google.protobuf.TypeRegistry; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * A specification for a {@link ProtoTruthMessageDifferencer} for comparing two individual @@ -273,7 +273,11 @@ final FluentEqualityConfig withExpectedMessages(Iterable mess Builder builder = toBuilder().setHasExpectedMessages(true); if (compareExpectedFieldsOnly()) { builder.setCompareFieldsScope( - FieldScopeLogic.and(compareFieldsScope(), FieldScopes.fromSetFields(messages).logic())); + FieldScopeLogic.and( + compareFieldsScope(), + FieldScopeImpl.createFromSetFields( + messages, useTypeRegistry(), useExtensionRegistry()) + .logic())); } return builder.build(); } @@ -365,7 +369,7 @@ final ProtoTruthMessageDifferencer toMessageDifferencer(Descriptor descriptor) { } final Correspondence toCorrespondence( - final Optional optDescriptor) { + Optional optDescriptor) { checkState(hasExpectedMessages(), "withExpectedMessages() not called"); return Correspondence.from( // If we were allowed lambdas, this would be: 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 013e4b869..427fe4b9f 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 @@ -34,7 +34,7 @@ import java.io.IOException; import java.util.Arrays; import java.util.Comparator; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Truth subject for the iterables of protocol buffers. @@ -86,6 +86,9 @@ protected IterableOfProtosSubject( @Override protected String actualCustomStringRepresentation() { + if (actual == null) { + return "null"; + } StringBuilder sb = new StringBuilder().append('['); boolean first = true; for (M element : actual) { 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 9c366f588..f7a15b35e 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 @@ -19,7 +19,7 @@ import com.google.common.truth.Ordered; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.protobuf.Message; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Comparison methods, which enforce the rules set in prior calls to {@link 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 f1aa7af10..f0ff0bed6 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 @@ -22,7 +22,7 @@ import com.google.protobuf.Message; import com.google.protobuf.TypeRegistry; import java.util.Map; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Fluent API to perform detailed, customizable comparison of maps containing protocol buffers as 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 9248843e9..662ddb0d9 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 @@ -32,7 +32,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Truth subject for maps with protocol buffers for values. 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 838389b4e..937bed8c3 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 @@ -22,7 +22,7 @@ import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; import com.google.protobuf.TypeRegistry; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Fluent API to perform detailed, customizable comparison of {@link Multimap}s containing protocol 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 b7afda3e9..00bf56050 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.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,9 +34,8 @@ 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; +import org.jspecify.annotations.Nullable; /** * Truth subject for {@link Multimap}s with protocol buffers for values. @@ -90,6 +90,12 @@ protected MultimapWithProtoValuesSubject( *

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 valuesForKey(@Nullable Object key) { return check("valuesForKey(%s)", key) .about(protos()) @@ -910,7 +916,7 @@ public Ordered containsExactlyEntriesIn(Multimap expectedMap) { @Override @CanIgnoreReturnValue public Ordered containsExactly() { - return subject.usingCorrespondence(Collections.emptyList()).containsExactly(); + return subject.usingCorrespondence(ImmutableList.of()).containsExactly(); } @Override @@ -925,14 +931,19 @@ public Ordered containsExactly(@Nullable Object k0, @Nullable M v0, @Nullable Ob 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/ProtoFluentAssertion.java b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/ProtoFluentAssertion.java index 3047caba0..6b5624f9c 100644 --- a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/ProtoFluentAssertion.java +++ b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/ProtoFluentAssertion.java @@ -19,7 +19,7 @@ import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; import com.google.protobuf.TypeRegistry; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Fluent API to perform detailed, customizable comparison of Protocol buffers. diff --git a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/ProtoSubject.java b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/ProtoSubject.java index ba96333d9..1388f9004 100644 --- a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/ProtoSubject.java +++ b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/ProtoSubject.java @@ -29,7 +29,7 @@ import com.google.protobuf.Message; import com.google.protobuf.TypeRegistry; import java.util.Arrays; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Truth subject for the full version of Protocol Buffers. diff --git a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/ProtoSubjectBuilder.java b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/ProtoSubjectBuilder.java index 0d813aa20..e7015c189 100644 --- a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/ProtoSubjectBuilder.java +++ b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/ProtoSubjectBuilder.java @@ -22,7 +22,7 @@ import com.google.protobuf.Message; import com.google.protobuf.MessageLite; import java.util.Map; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link CustomSubjectBuilder} which aggregates all Proto-related {@link diff --git a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/ProtoTruth.java b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/ProtoTruth.java index fbdaa9e01..7daa0d1dc 100644 --- a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/ProtoTruth.java +++ b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/ProtoTruth.java @@ -27,7 +27,7 @@ import com.google.protobuf.Message; import com.google.protobuf.MessageLite; import java.util.Map; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * A set of static methods to begin a Truth assertion chain for protocol buffers. 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 5ccf6dc16..d5ec1a803 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 @@ -49,7 +49,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Tool to differentiate two messages with the same {@link Descriptor}, subject to the rules set out @@ -221,8 +221,10 @@ private DiffResult diffAnyMessages( if (shouldCompareValue == FieldScopeResult.EXCLUDED_RECURSIVELY) { valueDiffResult = SingularField.ignored(name(AnyUtils.valueFieldDescriptor())); } else { - Optional unpackedActual = AnyUtils.unpack(actual, config); - Optional unpackedExpected = AnyUtils.unpack(expected, config); + Optional unpackedActual = + AnyUtils.unpack(actual, config.useTypeRegistry(), config.useExtensionRegistry()); + Optional unpackedExpected = + AnyUtils.unpack(expected, config.useTypeRegistry(), config.useExtensionRegistry()); if (unpackedActual.isPresent() && unpackedExpected.isPresent() && descriptorsMatch(unpackedActual.get(), unpackedExpected.get())) { @@ -235,7 +237,10 @@ && descriptorsMatch(unpackedActual.get(), unpackedExpected.get())) { shouldCompareValue == FieldScopeResult.EXCLUDED_NONRECURSIVELY, AnyUtils.valueFieldDescriptor(), name(AnyUtils.valueFieldDescriptor()), - config.subScope(rootDescriptor, AnyUtils.valueSubScopeId())); + config.subScope( + rootDescriptor, + SubScopeId.ofUnpackedAnyValueType( + unpackedActual.get().getDescriptorForType()))); } else { valueDiffResult = compareSingularValue( @@ -959,7 +964,7 @@ private static String indexedName( FieldDescriptor fieldDescriptor, Object key, FieldDescriptor keyFieldDescriptor) { StringBuilder sb = new StringBuilder(); try { - TextFormat.printFieldValue(keyFieldDescriptor, key, sb); + TextFormat.printer().printFieldValue(keyFieldDescriptor, key, sb); } catch (IOException impossible) { throw new AssertionError(impossible); } diff --git a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/SubScopeId.java b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/SubScopeId.java index 4860969e7..925c1569f 100644 --- a/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/SubScopeId.java +++ b/extensions/proto/src/main/java/com/google/common/truth/extensions/proto/SubScopeId.java @@ -17,13 +17,15 @@ package com.google.common.truth.extensions.proto; import com.google.auto.value.AutoOneOf; +import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; @AutoOneOf(SubScopeId.Kind.class) abstract class SubScopeId { enum Kind { FIELD_DESCRIPTOR, - UNKNOWN_FIELD_DESCRIPTOR; + UNKNOWN_FIELD_DESCRIPTOR, + UNPACKED_ANY_VALUE_TYPE; } abstract Kind kind(); @@ -32,6 +34,8 @@ enum Kind { abstract UnknownFieldDescriptor unknownFieldDescriptor(); + abstract Descriptor unpackedAnyValueType(); + /** Returns a short, human-readable version of this identifier. */ final String shortName() { switch (kind()) { @@ -41,6 +45,8 @@ final String shortName() { : fieldDescriptor().getName(); case UNKNOWN_FIELD_DESCRIPTOR: return String.valueOf(unknownFieldDescriptor().fieldNumber()); + case UNPACKED_ANY_VALUE_TYPE: + return AnyUtils.valueFieldDescriptor().getName(); } throw new AssertionError(kind()); } @@ -52,4 +58,8 @@ static SubScopeId of(FieldDescriptor fieldDescriptor) { static SubScopeId of(UnknownFieldDescriptor unknownFieldDescriptor) { return AutoOneOf_SubScopeId.unknownFieldDescriptor(unknownFieldDescriptor); } + + static SubScopeId ofUnpackedAnyValueType(Descriptor unpackedAnyValueType) { + return AutoOneOf_SubScopeId.unpackedAnyValueType(unpackedAnyValueType); + } } diff --git a/extensions/proto/src/test/java/com/google/common/truth/extensions/proto/FieldScopesTest.java b/extensions/proto/src/test/java/com/google/common/truth/extensions/proto/FieldScopesTest.java index fb0a07dec..f99e7554c 100644 --- a/extensions/proto/src/test/java/com/google/common/truth/extensions/proto/FieldScopesTest.java +++ b/extensions/proto/src/test/java/com/google/common/truth/extensions/proto/FieldScopesTest.java @@ -384,6 +384,96 @@ public void testIgnoringFieldOfAnyMessage() throws Exception { .contains("modified: o_any_message.value.r_string[0]: \"foo\" -> \"bar\""); } + @Test + public void testAnyMessageComparingExpectedFieldsOnly() throws Exception { + + String typeUrl = + isProto3() + ? "type.googleapis.com/com.google.common.truth.extensions.proto.SubTestMessage3" + : "type.googleapis.com/com.google.common.truth.extensions.proto.SubTestMessage2"; + + Message message = parse("o_any_message { [" + typeUrl + "]: { o_int: 2 } }"); + Message eqMessage = + parse("o_any_message { [" + typeUrl + "]: { o_int: 2 r_string: \"foo\" } }"); + Message diffMessage = + parse("o_any_message { [" + typeUrl + "]: { o_int: 3 r_string: \"bar\" } }"); + + expectThat(eqMessage) + .unpackingAnyUsing(getTypeRegistry(), getExtensionRegistry()) + .comparingExpectedFieldsOnly() + .isEqualTo(message); + expectThat(diffMessage) + .unpackingAnyUsing(getTypeRegistry(), getExtensionRegistry()) + .comparingExpectedFieldsOnly() + .isNotEqualTo(message); + } + + @Test + public void testInvalidAnyMessageComparingExpectedFieldsOnly() throws Exception { + + Message message = parse("o_any_message { type_url: 'invalid-type' value: 'abc123' }"); + Message eqMessage = parse("o_any_message { type_url: 'invalid-type' value: 'abc123' }"); + Message diffMessage = parse("o_any_message { type_url: 'invalid-type' value: 'def456' }"); + + expectThat(eqMessage) + .unpackingAnyUsing(getTypeRegistry(), getExtensionRegistry()) + .comparingExpectedFieldsOnly() + .isEqualTo(message); + expectThat(diffMessage) + .unpackingAnyUsing(getTypeRegistry(), getExtensionRegistry()) + .comparingExpectedFieldsOnly() + .isNotEqualTo(message); + } + + @Test + public void testDifferentAnyMessagesComparingExpectedFieldsOnly() throws Exception { + + // 'o_int' and 'o_float' have the same field numbers in both messages. However, to compare + // accurately, we incorporate the unpacked Descriptor type into the FieldNumberTree as well to + // disambiguate. + String typeUrl1 = + isProto3() + ? "type.googleapis.com/com.google.common.truth.extensions.proto.SubTestMessage3" + : "type.googleapis.com/com.google.common.truth.extensions.proto.SubTestMessage2"; + String typeUrl2 = + isProto3() + ? "type.googleapis.com/com.google.common.truth.extensions.proto.SubSubTestMessage3" + : "type.googleapis.com/com.google.common.truth.extensions.proto.SubSubTestMessage2"; + + Message message = + parse( + "r_any_message { [" + + typeUrl1 + + "]: { o_int: 2 } } r_any_message { [" + + typeUrl2 + + "]: { o_float: 3.1 } }"); + Message eqMessage = + parse( + "r_any_message { [" + + typeUrl1 + + "]: { o_int: 2 o_float: 1.9 } } r_any_message { [" + + typeUrl2 + + "]: { o_int: 5 o_float: 3.1 } }"); + Message diffMessage = + parse( + "r_any_message { [" + + typeUrl1 + + "]: { o_int: 5 o_float: 3.1 } } r_any_message { [" + + typeUrl2 + + "]: { o_int: 2 o_float: 1.9 } }"); + + expectThat(eqMessage) + .unpackingAnyUsing(getTypeRegistry(), getExtensionRegistry()) + .ignoringRepeatedFieldOrder() + .comparingExpectedFieldsOnly() + .isEqualTo(message); + expectThat(diffMessage) + .unpackingAnyUsing(getTypeRegistry(), getExtensionRegistry()) + .ignoringRepeatedFieldOrder() + .comparingExpectedFieldsOnly() + .isNotEqualTo(message); + } + @Test public void testIgnoringAllButOneFieldOfSubMessage() { // Consider all of TestMessage, but none of o_sub_test_message, except 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 7a06845e6..028601bce 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 void testDisplayingDiffsPairedBy() { Message expectedInt4 = parse("o_int: 4 r_string: 'qux'"); Function getInt = - new Function() { - @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 void testCompareMultipleMessageTypes() { } private Comparator compareByOIntAscending() { - return new Comparator() { - @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 66cd0f29b..c9eb636b4 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 @@ -953,8 +953,8 @@ public void testAnyMessageComparedWithDynamicMessage() throws InvalidProtocolBuf @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 cc7fd0a3e..e686a4f4a 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 @@ -45,7 +45,7 @@ import java.util.Map; import java.util.Set; import java.util.regex.Pattern; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; import org.junit.Rule; /** Base class for testing {@link ProtoSubject} methods. */ diff --git a/extensions/re2j/pom.xml b/extensions/re2j/pom.xml index d95e4cd1d..6ca4180ce 100644 --- a/extensions/re2j/pom.xml +++ b/extensions/re2j/pom.xml @@ -25,6 +25,15 @@ + + + ../.. + + LICENSE + + META-INF + + maven-javadoc-plugin @@ -32,5 +41,3 @@ - - 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 67b1ed81c..ecaa6a61d 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.jspecify.annotations.Nullable; /** * Truth subjects for re2j regular expressions. @@ -51,26 +54,27 @@ public static final class Re2jStringSubject extends Subject { private static final Subject.Factory FACTORY = new Subject.Factory() { @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 void matches(String regex) { /** 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 void doesNotMatch(String regex) { /** 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 void doesNotMatch(Pattern regex) { /** 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 void containsMatch(String regex) { /** 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); } } diff --git a/pom.xml b/pom.xml index 4b411b64a..a7c35dce3 100644 --- a/pom.xml +++ b/pom.xml @@ -14,13 +14,13 @@ UTF-8 - 1.10.1 + 1.11.0 - 31.1-android + 33.2.1-android - 31.1-jre - 2.9.0 - 3.22.2 + 33.2.1-jre + 2.10.0 + 4.27.2 3.1.0 @@ -71,9 +71,9 @@ ${guava.android.version} - org.checkerframework - checker-qual - 3.32.0 + org.jspecify + jspecify + 0.3.0 junit @@ -81,7 +81,7 @@ 4.13.2 - com.google.gwt + org.gwtproject gwt-user ${gwt.version} @@ -103,7 +103,7 @@ com.google.errorprone error_prone_annotations - 2.18.0 + 2.28.0 com.google.protobuf @@ -123,7 +123,7 @@ org.ow2.asm asm - 9.4 + 9.7 com.google.jsinterop @@ -224,11 +224,11 @@ org.apache.maven.plugins maven-project-info-reports-plugin - 3.4.2 + 3.6.1 maven-javadoc-plugin - 3.5.0 + 3.7.0 -Xdoclint:-html ${conditionalJavadoc9PlusOptions} @@ -256,17 +256,19 @@ maven-jar-plugin - 3.3.0 + 3.4.2 org.codehaus.mojo animal-sniffer-maven-plugin - 1.22 + 1.24 + com.google.common.truth.IgnoreJRERequirement - org.codehaus.mojo.signature - java16-sun - 1.10 + com.toasttab.android + gummy-bears-api-19 + 0.6.1 + @@ -281,27 +283,28 @@ maven-compiler-plugin - 3.11.0 + 3.13.0 1.8 1.8 + true maven-source-plugin - 3.2.1 + 3.3.1 maven-gpg-plugin - 3.0.1 + 3.2.4 maven-surefire-plugin - 2.22.2 + 3.3.0 maven-enforcer-plugin - 3.2.1 + 3.5.0 enforce 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 c10b24ea9..7bd0076cd 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 @@ -31,6 +31,7 @@ import static com.google.errorprone.fixes.SuggestedFixes.compilesWithFix; import static com.google.errorprone.matchers.Description.NO_MATCH; import static com.google.errorprone.util.ASTHelpers.getDeclaredSymbol; +import static com.google.errorprone.util.ASTHelpers.getEnclosedElements; import static com.google.errorprone.util.ASTHelpers.getSymbol; import static com.sun.source.tree.Tree.Kind.EXPRESSION_STATEMENT; import static com.sun.source.tree.Tree.Kind.IDENTIFIER; @@ -82,7 +83,7 @@ import java.util.Optional; import java.util.Set; import javax.lang.model.element.Modifier; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Refactors some subclasses of {@code Correspondence} to instead call {@code Correspondence.from}. @@ -644,7 +645,7 @@ static MemberType from(Tree tree, VisitorState state) { private static boolean overrides( MethodSymbol potentialOverrider, String clazz, String method, VisitorState state) { Symbol overridable = - state.getTypeFromString(clazz).tsym.getEnclosedElements().stream() + getEnclosedElements(state.getTypeFromString(clazz).tsym).stream() .filter(s -> s.getKind() == METHOD) .filter(m -> m.getSimpleName().contentEquals(method)) .collect(onlyElement()); 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 30cb5207d..f2fa8506a 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,7 +40,7 @@ import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.MethodInvocationTree; import java.util.List; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Migrates Truth subjects from the old {@code fail(String, Object)} to the new {@code diff --git a/util/mvn-deploy.sh b/util/mvn-deploy.sh deleted file mode 100755 index 2c27eab2e..000000000 --- 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 ] [ ...]" - 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 -Drelease ${key_param} deploy