Skip to content

Commit 0e99a27

Browse files
java-team-github-botGoogle Java Core Libraries
authored and
Google Java Core Libraries
committedNov 28, 2023
Add isWithin().of() support to LongSubject.
RELNOTES=Added `isWithin().of()` support to `LongSubject`. PiperOrigin-RevId: 586097910
1 parent 6376c39 commit 0e99a27

File tree

3 files changed

+265
-0
lines changed

3 files changed

+265
-0
lines changed
 

‎core/src/main/java/com/google/common/truth/LongSubject.java

+109
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
*/
1616
package com.google.common.truth;
1717

18+
import static com.google.common.base.Preconditions.checkArgument;
19+
import static com.google.common.base.Preconditions.checkNotNull;
20+
import static com.google.common.truth.MathUtil.equalWithinTolerance;
21+
import static com.google.common.truth.Fact.fact;
22+
1823
import org.checkerframework.checker.nullness.qual.Nullable;
1924

2025
/**
@@ -25,12 +30,111 @@
2530
* @author Kurt Alfred Kluever
2631
*/
2732
public class LongSubject extends ComparableSubject<Long> {
33+
34+
private final @Nullable Long actual;
35+
2836
/**
2937
* Constructor for use by subclasses. If you want to create an instance of this class itself, call
3038
* {@link Subject#check(String, Object...) check(...)}{@code .that(actual)}.
3139
*/
3240
protected LongSubject(FailureMetadata metadata, @Nullable Long actual) {
3341
super(metadata, actual);
42+
this.actual = actual;
43+
}
44+
45+
/**
46+
* A partially specified check about an approximate relationship to a {@code long} subject using a
47+
* tolerance.
48+
*
49+
* @since 1.2
50+
*/
51+
public abstract static class TolerantLongComparison {
52+
53+
// Prevent subclassing outside of this class
54+
private TolerantLongComparison() {}
55+
56+
/**
57+
* Fails if the subject was expected to be within the tolerance of the given value but was not
58+
* <i>or</i> if it was expected <i>not</i> to be within the tolerance but was. The subject and
59+
* tolerance are specified earlier in the fluent call chain.
60+
*/
61+
public abstract void of(long expectedLong);
62+
63+
/**
64+
* @throws UnsupportedOperationException always
65+
* @deprecated {@link Object#equals(Object)} is not supported on TolerantLongComparison. If you
66+
* meant to compare longs, use {@link #of(long)} instead.
67+
*/
68+
@Deprecated
69+
@Override
70+
public boolean equals(@Nullable Object o) {
71+
throw new UnsupportedOperationException(
72+
"If you meant to compare longs, use .of(long) instead.");
73+
}
74+
75+
/**
76+
* @throws UnsupportedOperationException always
77+
* @deprecated {@link Object#hashCode()} is not supported on TolerantLongComparison
78+
*/
79+
@Deprecated
80+
@Override
81+
public int hashCode() {
82+
throw new UnsupportedOperationException("Subject.hashCode() is not supported.");
83+
}
84+
}
85+
86+
/**
87+
* Prepares for a check that the subject is a number within the given tolerance of an expected
88+
* value that will be provided in the next call in the fluent chain.
89+
*
90+
* @param tolerance an inclusive upper bound on the difference between the subject and object
91+
* allowed by the check, which must be a non-negative value.
92+
* @since 1.2
93+
*/
94+
public TolerantLongComparison isWithin(long tolerance) {
95+
return new TolerantLongComparison() {
96+
@Override
97+
public void of(long expected) {
98+
Long actual = LongSubject.this.actual;
99+
checkNotNull(
100+
actual, "actual value cannot be null. tolerance=%s expected=%s", tolerance, expected);
101+
checkTolerance(tolerance);
102+
103+
if (!equalWithinTolerance(actual, expected, tolerance)) {
104+
failWithoutActual(
105+
fact("expected", Long.toString(expected)),
106+
butWas(),
107+
fact("outside tolerance", Long.toString(tolerance)));
108+
}
109+
}
110+
};
111+
}
112+
113+
/**
114+
* Prepares for a check that the subject is a number not within the given tolerance of an expected
115+
* value that will be provided in the next call in the fluent chain.
116+
*
117+
* @param tolerance an exclusive lower bound on the difference between the subject and object
118+
* allowed by the check, which must be a non-negative value.
119+
* @since 1.2
120+
*/
121+
public TolerantLongComparison isNotWithin(long tolerance) {
122+
return new TolerantLongComparison() {
123+
@Override
124+
public void of(long expected) {
125+
Long actual = LongSubject.this.actual;
126+
checkNotNull(
127+
actual, "actual value cannot be null. tolerance=%s expected=%s", tolerance, expected);
128+
checkTolerance(tolerance);
129+
130+
if (equalWithinTolerance(actual, expected, tolerance)) {
131+
failWithoutActual(
132+
fact("expected not to be", Long.toString(expected)),
133+
butWas(),
134+
fact("within tolerance", Long.toString(tolerance)));
135+
}
136+
}
137+
};
34138
}
35139

36140
/**
@@ -42,6 +146,11 @@ public final void isEquivalentAccordingToCompareTo(@Nullable Long other) {
42146
super.isEquivalentAccordingToCompareTo(other);
43147
}
44148

149+
/** Ensures that the given tolerance is a non-negative value. */
150+
static void checkTolerance(long tolerance) {
151+
checkArgument(tolerance >= 0, "tolerance (%s) cannot be negative", tolerance);
152+
}
153+
45154
/**
46155
* Checks that the subject is greater than {@code other}.
47156
*

‎core/src/main/java/com/google/common/truth/MathUtil.java

+18
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,30 @@
1616

1717
package com.google.common.truth;
1818

19+
import static java.lang.Math.subtractExact;
20+
1921
import com.google.common.primitives.Doubles;
2022

2123
/** Math utilities to be shared by numeric subjects. */
2224
final class MathUtil {
2325
private MathUtil() {}
2426

27+
/**
28+
* Returns true iff {@code left} and {@code right} are values within {@code tolerance} of each
29+
* other.
30+
*/
31+
/* package */ static boolean equalWithinTolerance(long left, long right, long tolerance) {
32+
try {
33+
// subtractExact is always desugared.
34+
@SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"})
35+
long absDiff = Math.abs(subtractExact(left, right));
36+
return 0 <= absDiff && absDiff <= Math.abs(tolerance);
37+
} catch (ArithmeticException e) {
38+
// The numbers are so far apart their difference isn't even a long.
39+
return false;
40+
}
41+
}
42+
2543
/**
2644
* Returns true iff {@code left} and {@code right} are finite values within {@code tolerance} of
2745
* each other. Note that both this method and {@link #notEqualWithinTolerance} returns false if

‎core/src/test/java/com/google/common/truth/LongSubjectTest.java

+138
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@
1515
*/
1616
package com.google.common.truth;
1717

18+
import static com.google.common.truth.ExpectFailure.assertThat;
1819
import static com.google.common.truth.Truth.assertThat;
20+
import static org.junit.Assert.fail;
1921

22+
import com.google.common.truth.ExpectFailure.SimpleSubjectBuilderCallback;
23+
import com.google.errorprone.annotations.CanIgnoreReturnValue;
2024
import org.junit.Test;
2125
import org.junit.runner.RunWith;
2226
import org.junit.runners.JUnit4;
@@ -129,6 +133,140 @@ public void isAtMost_int() {
129133
assertThat(2L).isAtMost(3);
130134
}
131135

136+
@Test
137+
public void isWithinOf() {
138+
assertThat(20000L).isWithin(0L).of(20000L);
139+
assertThat(20000L).isWithin(1L).of(20000L);
140+
assertThat(20000L).isWithin(10000L).of(20000L);
141+
assertThat(20000L).isWithin(10000L).of(30000L);
142+
assertThat(Long.MIN_VALUE).isWithin(1L).of(Long.MIN_VALUE + 1);
143+
assertThat(Long.MAX_VALUE).isWithin(1L).of(Long.MAX_VALUE - 1);
144+
assertThat(Long.MAX_VALUE / 2).isWithin(Long.MAX_VALUE).of(-Long.MAX_VALUE / 2);
145+
assertThat(-Long.MAX_VALUE / 2).isWithin(Long.MAX_VALUE).of(Long.MAX_VALUE / 2);
146+
147+
assertThatIsWithinFails(20000L, 9999L, 30000L);
148+
assertThatIsWithinFails(20000L, 10000L, 30001L);
149+
assertThatIsWithinFails(Long.MIN_VALUE, 0L, Long.MAX_VALUE);
150+
assertThatIsWithinFails(Long.MAX_VALUE, 0L, Long.MIN_VALUE);
151+
assertThatIsWithinFails(Long.MIN_VALUE, 1L, Long.MIN_VALUE + 2);
152+
assertThatIsWithinFails(Long.MAX_VALUE, 1L, Long.MAX_VALUE - 2);
153+
// Don't fall for rollover
154+
assertThatIsWithinFails(Long.MIN_VALUE, 1L, Long.MAX_VALUE);
155+
assertThatIsWithinFails(Long.MAX_VALUE, 1L, Long.MIN_VALUE);
156+
}
157+
158+
private static void assertThatIsWithinFails(long actual, long tolerance, long expected) {
159+
ExpectFailure.SimpleSubjectBuilderCallback<LongSubject, Long> callback =
160+
new ExpectFailure.SimpleSubjectBuilderCallback<LongSubject, Long>() {
161+
@Override
162+
public void invokeAssertion(SimpleSubjectBuilder<LongSubject, Long> expect) {
163+
expect.that(actual).isWithin(tolerance).of(expected);
164+
}
165+
};
166+
AssertionError failure = expectFailure(callback);
167+
assertThat(failure)
168+
.factKeys()
169+
.containsExactly("expected", "but was", "outside tolerance")
170+
.inOrder();
171+
assertThat(failure).factValue("expected").isEqualTo(Long.toString(expected));
172+
assertThat(failure).factValue("but was").isEqualTo(Long.toString(actual));
173+
assertThat(failure).factValue("outside tolerance").isEqualTo(Long.toString(tolerance));
174+
}
175+
176+
@Test
177+
public void isNotWithinOf() {
178+
assertThatIsNotWithinFails(20000L, 0L, 20000L);
179+
assertThatIsNotWithinFails(20000L, 1L, 20000L);
180+
assertThatIsNotWithinFails(20000L, 10000L, 20000L);
181+
assertThatIsNotWithinFails(20000L, 10000L, 30000L);
182+
assertThatIsNotWithinFails(Long.MIN_VALUE, 1L, Long.MIN_VALUE + 1);
183+
assertThatIsNotWithinFails(Long.MAX_VALUE, 1L, Long.MAX_VALUE - 1);
184+
assertThatIsNotWithinFails(Long.MAX_VALUE / 2, Long.MAX_VALUE, -Long.MAX_VALUE / 2);
185+
assertThatIsNotWithinFails(-Long.MAX_VALUE / 2, Long.MAX_VALUE, Long.MAX_VALUE / 2);
186+
187+
assertThat(20000L).isNotWithin(9999L).of(30000L);
188+
assertThat(20000L).isNotWithin(10000L).of(30001L);
189+
assertThat(Long.MIN_VALUE).isNotWithin(0L).of(Long.MAX_VALUE);
190+
assertThat(Long.MAX_VALUE).isNotWithin(0L).of(Long.MIN_VALUE);
191+
assertThat(Long.MIN_VALUE).isNotWithin(1L).of(Long.MIN_VALUE + 2);
192+
assertThat(Long.MAX_VALUE).isNotWithin(1L).of(Long.MAX_VALUE - 2);
193+
// Don't fall for rollover
194+
assertThat(Long.MIN_VALUE).isNotWithin(1L).of(Long.MAX_VALUE);
195+
assertThat(Long.MAX_VALUE).isNotWithin(1L).of(Long.MIN_VALUE);
196+
}
197+
198+
private static void assertThatIsNotWithinFails(long actual, long tolerance, long expected) {
199+
ExpectFailure.SimpleSubjectBuilderCallback<LongSubject, Long> callback =
200+
new ExpectFailure.SimpleSubjectBuilderCallback<LongSubject, Long>() {
201+
@Override
202+
public void invokeAssertion(SimpleSubjectBuilder<LongSubject, Long> expect) {
203+
expect.that(actual).isNotWithin(tolerance).of(expected);
204+
}
205+
};
206+
AssertionError failure = expectFailure(callback);
207+
assertThat(failure).factValue("expected not to be").isEqualTo(Long.toString(expected));
208+
assertThat(failure).factValue("within tolerance").isEqualTo(Long.toString(tolerance));
209+
}
210+
211+
@Test
212+
public void isWithinIntegers() {
213+
assertThat(20000L).isWithin(0).of(20000);
214+
assertThat(20000L).isWithin(1).of(20000);
215+
assertThat(20000L).isWithin(10000).of(20000);
216+
assertThat(20000L).isWithin(10000).of(30000);
217+
218+
assertThat(20000L).isNotWithin(0).of(200000);
219+
assertThat(20000L).isNotWithin(1).of(200000);
220+
assertThat(20000L).isNotWithin(10000).of(200000);
221+
assertThat(20000L).isNotWithin(10000).of(300000);
222+
}
223+
224+
@Test
225+
public void isWithinNegativeTolerance() {
226+
isWithinNegativeToleranceThrowsIAE(0L, -10, 5);
227+
isWithinNegativeToleranceThrowsIAE(0L, -10, 20);
228+
isNotWithinNegativeToleranceThrowsIAE(0L, -10, 5);
229+
isNotWithinNegativeToleranceThrowsIAE(0L, -10, 20);
230+
}
231+
232+
private static void isWithinNegativeToleranceThrowsIAE(
233+
long actual, long tolerance, long expected) {
234+
try {
235+
assertThat(actual).isWithin(tolerance).of(expected);
236+
fail("Expected IllegalArgumentException to be thrown but wasn't");
237+
} catch (IllegalArgumentException iae) {
238+
assertThat(iae)
239+
.hasMessageThat()
240+
.isEqualTo("tolerance (" + tolerance + ") cannot be negative");
241+
}
242+
}
243+
244+
private static void isNotWithinNegativeToleranceThrowsIAE(
245+
long actual, long tolerance, long expected) {
246+
try {
247+
assertThat(actual).isNotWithin(tolerance).of(expected);
248+
fail("Expected IllegalArgumentException to be thrown but wasn't");
249+
} catch (IllegalArgumentException iae) {
250+
assertThat(iae)
251+
.hasMessageThat()
252+
.isEqualTo("tolerance (" + tolerance + ") cannot be negative");
253+
}
254+
}
255+
256+
private static final Subject.Factory<LongSubject, Long> LONG_SUBJECT_FACTORY =
257+
new Subject.Factory<LongSubject, Long>() {
258+
@Override
259+
public LongSubject createSubject(FailureMetadata metadata, Long that) {
260+
return new LongSubject(metadata, that);
261+
}
262+
};
263+
264+
@CanIgnoreReturnValue
265+
private static AssertionError expectFailure(
266+
SimpleSubjectBuilderCallback<LongSubject, Long> callback) {
267+
return ExpectFailure.expectFailureAbout(LONG_SUBJECT_FACTORY, callback);
268+
}
269+
132270
private LongSubject expectFailureWhenTestingThat(Long actual) {
133271
return expectFailure.whenTesting().that(actual);
134272
}

0 commit comments

Comments
 (0)
Please sign in to comment.