Skip to content

Commit

Permalink
Add isWithin().of() support to IntegerSubject.
Browse files Browse the repository at this point in the history
To make `IntegerSubjectTest` match `LongSubjectTest` more closely, I moved some of the existing `IntegerSubjectTest` tests into a new `NumericComparisonTest` class. This is arguably how they should always have been: The tests there don't exercise logic implemented in `IntegerSubject` but rather logic in `Subject` itself. (So yes, the tests could also go into `SubjectTest`. But that class is already quite long.)

This CL is otherwise a mechanical copy-paste of cl/586097910.

RELNOTES=Added `isWithin().of()` support to `IntegerSubject`.
PiperOrigin-RevId: 589914997
  • Loading branch information
cpovirk authored and Google Java Core Libraries committed Dec 11, 2023
1 parent 91f4bdc commit 6464cb5
Show file tree
Hide file tree
Showing 5 changed files with 435 additions and 150 deletions.
112 changes: 110 additions & 2 deletions core/src/main/java/com/google/common/truth/IntegerSubject.java
Expand Up @@ -15,6 +15,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.fact;
import static com.google.common.truth.MathUtil.equalWithinTolerance;

import org.checkerframework.checker.nullness.qual.Nullable;

/**
Expand All @@ -25,12 +30,110 @@
* @author Kurt Alfred Kluever
*/
public class IntegerSubject extends ComparableSubject<Integer> {
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
* <i>or</i> if it was expected <i>not</i> 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)));
}
}
};
}

/**
Expand All @@ -41,4 +144,9 @@ protected IntegerSubject(FailureMetadata metadata, @Nullable Integer integer) {
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);
}
}
4 changes: 2 additions & 2 deletions core/src/main/java/com/google/common/truth/LongSubject.java
Expand Up @@ -17,8 +17,8 @@

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.truth.MathUtil.equalWithinTolerance;
import static com.google.common.truth.Fact.fact;
import static com.google.common.truth.MathUtil.equalWithinTolerance;

import org.checkerframework.checker.nullness.qual.Nullable;

Expand Down Expand Up @@ -147,7 +147,7 @@ public final void isEquivalentAccordingToCompareTo(@Nullable Long other) {
}

/** Ensures that the given tolerance is a non-negative value. */
static void checkTolerance(long tolerance) {
private static void checkTolerance(long tolerance) {
checkArgument(tolerance >= 0, "tolerance (%s) cannot be negative", tolerance);
}

Expand Down
16 changes: 16 additions & 0 deletions core/src/main/java/com/google/common/truth/MathUtil.java
Expand Up @@ -40,6 +40,22 @@ private MathUtil() {}
}
}

/**
* 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({"AndroidJdkLibsChecker", "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
Expand Down

0 comments on commit 6464cb5

Please sign in to comment.