Skip to content

Commit 974ef19

Browse files
cpovirkGoogle Java Core Libraries
authored andcommitted
[Avoid throwing](#207) for null and other "weird" inputs.
This includes making `isInstanceOf(int.class)` work like `isInstanceOf(Integer.class)`. This is particularly important for Kotlin callers: `isInstanceOf(Int::class.java)` behaves like `isInstanceOf(int.class)`. By changing this behavior, this CL revises what I originally did back in cl/478924507. The rest of the changes implement the usual approach of failing instead of throwing an exception. Plus, simplify `Subject.integralValue` now that b/419535511 is fixed. RELNOTES=Made Kotlin's `isInstanceOf(Int::class.java)` (and Java's `isInstanceOf(int.class)`) a valid way to check for `Int`/`Integer` instances. PiperOrigin-RevId: 773153958
1 parent aa734de commit 974ef19

File tree

8 files changed

+123
-58
lines changed

8 files changed

+123
-58
lines changed

core/src/main/java/com/google/common/truth/IntStreamSubject.java

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
*/
1616
package com.google.common.truth;
1717

18-
import static com.google.common.base.Preconditions.checkNotNull;
1918
import static com.google.common.base.Suppliers.memoize;
19+
import static com.google.common.truth.Fact.simpleFact;
2020
import static java.util.stream.Collectors.toCollection;
2121

2222
import com.google.common.base.Supplier;
@@ -175,18 +175,12 @@ public Ordered containsAtLeastElementsIn(@Nullable Iterable<?> expected) {
175175
*/
176176
@CanIgnoreReturnValue
177177
public Ordered containsExactly(int @Nullable ... expected) {
178-
/*
179-
* We declare a parameter type that lets callers pass a nullable array, even though the
180-
* assertion will fail if the array is ever actually null. This can be convenient if the
181-
* expected value comes from a nullable source (e.g., a map lookup): Users would otherwise have
182-
* to use {@code requireNonNull} or {@code !!} or similar, all to address a compile error
183-
* warning about a runtime failure that might never happen—a runtime failure that Truth could
184-
* produce a better exception message for, since it could make the message express that the
185-
* caller is performing a containsExactly assertion.
186-
*
187-
* TODO(cpovirk): Actually produce such a better exception message.
188-
*/
189-
checkNotNull(expected);
178+
if (expected == null) {
179+
failWithoutActual(
180+
simpleFact("could not perform containment check because expected array is null"),
181+
actualContents());
182+
return ALREADY_FAILED;
183+
}
190184
return checkThatContentsList().containsExactlyElementsIn(box(expected));
191185
}
192186

@@ -274,6 +268,13 @@ private static Object[] box(int[] rest) {
274268
return IntStream.of(rest).boxed().toArray(Integer[]::new);
275269
}
276270

271+
private Fact actualContents() {
272+
return actualValue("actual contents");
273+
}
274+
275+
/** Ordered implementation that does nothing because an earlier check already caused a failure. */
276+
private static final Ordered ALREADY_FAILED = () -> {};
277+
277278
// TODO: b/246961366 - Do we want to override + deprecate isEqualTo/isNotEqualTo?
278279

279280
// TODO(user): Do we want to support comparingElementsUsing() on StreamSubject?

core/src/main/java/com/google/common/truth/LongStreamSubject.java

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
*/
1616
package com.google.common.truth;
1717

18-
import static com.google.common.base.Preconditions.checkNotNull;
1918
import static com.google.common.base.Suppliers.memoize;
19+
import static com.google.common.truth.Fact.simpleFact;
2020
import static java.util.stream.Collectors.toCollection;
2121

2222
import com.google.common.base.Supplier;
@@ -175,18 +175,12 @@ public Ordered containsAtLeastElementsIn(@Nullable Iterable<?> expected) {
175175
*/
176176
@CanIgnoreReturnValue
177177
public Ordered containsExactly(long @Nullable ... expected) {
178-
/*
179-
* We declare a parameter type that lets callers pass a nullable array, even though the
180-
* assertion will fail if the array is ever actually null. This can be convenient if the
181-
* expected value comes from a nullable source (e.g., a map lookup): Users would otherwise have
182-
* to use {@code requireNonNull} or {@code !!} or similar, all to address a compile error
183-
* warning about a runtime failure that might never happen—a runtime failure that Truth could
184-
* produce a better exception message for, since it could make the message express that the
185-
* caller is performing a containsExactly assertion.
186-
*
187-
* TODO(cpovirk): Actually produce such a better exception message.
188-
*/
189-
checkNotNull(expected);
178+
if (expected == null) {
179+
failWithoutActual(
180+
simpleFact("could not perform containment check because expected array is null"),
181+
actualContents());
182+
return ALREADY_FAILED;
183+
}
190184
return checkThatContentsList().containsExactlyElementsIn(box(expected));
191185
}
192186

@@ -274,6 +268,13 @@ private static Object[] box(long[] rest) {
274268
return LongStream.of(rest).boxed().toArray(Long[]::new);
275269
}
276270

271+
private Fact actualContents() {
272+
return actualValue("actual contents");
273+
}
274+
275+
/** Ordered implementation that does nothing because an earlier check already caused a failure. */
276+
private static final Ordered ALREADY_FAILED = () -> {};
277+
277278
// TODO: b/246961366 - Do we want to override + deprecate isEqualTo/isNotEqualTo?
278279

279280
// TODO(user): Do we want to support comparingElementsUsing() on StreamSubject?

core/src/main/java/com/google/common/truth/Subject.java

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@
1919
import static com.google.common.base.CaseFormat.UPPER_CAMEL;
2020
import static com.google.common.base.CharMatcher.whitespace;
2121
import static com.google.common.base.MoreObjects.firstNonNull;
22-
import static com.google.common.base.Preconditions.checkArgument;
2322
import static com.google.common.truth.Fact.fact;
2423
import static com.google.common.truth.Fact.simpleFact;
24+
import static com.google.common.truth.Platform.classMetadataUnsupported;
2525
import static com.google.common.truth.Platform.doubleToString;
2626
import static com.google.common.truth.Platform.floatToString;
27+
import static com.google.common.truth.Platform.isInstanceOfType;
2728
import static com.google.common.truth.Platform.isKotlinRange;
2829
import static com.google.common.truth.Platform.kotlinRangeContains;
2930
import static com.google.common.truth.Platform.stringValueForFailure;
@@ -236,7 +237,7 @@ private static boolean isIntegralBoxedPrimitive(@Nullable Object o) {
236237

237238
private static long integralValue(Object o) {
238239
if (o instanceof Character) {
239-
return (long) ((Character) o).charValue();
240+
return (Character) o;
240241
} else if (o instanceof Number) {
241242
return ((Number) o).longValue();
242243
} else {
@@ -283,16 +284,20 @@ public final void isNotSameInstanceAs(@Nullable Object other) {
283284
}
284285

285286
/** Checks that the value under test is an instance of the given class. */
286-
public void isInstanceOf(Class<?> clazz) {
287+
public void isInstanceOf(@Nullable Class<?> clazz) {
287288
if (clazz == null) {
288-
throw new NullPointerException("clazz");
289+
failWithoutActual(
290+
simpleFact("could not perform instanceof check because expected type was null"),
291+
actualValue("value to check was"));
292+
return;
289293
}
294+
clazz = Primitives.wrap(clazz);
290295
if (actual == null) {
291296
failWithActual("expected instance of", clazz.getName());
292297
return;
293298
}
294299
if (!isInstanceOfType(actual, clazz)) {
295-
if (Platform.classMetadataUnsupported()) {
300+
if (classMetadataUnsupported()) {
296301
throw new UnsupportedOperationException(
297302
actualCustomStringRepresentation()
298303
+ ", an instance of "
@@ -309,11 +314,15 @@ public void isInstanceOf(Class<?> clazz) {
309314
}
310315

311316
/** Checks that the value under test is not an instance of the given class. */
312-
public void isNotInstanceOf(Class<?> clazz) {
317+
public void isNotInstanceOf(@Nullable Class<?> clazz) {
313318
if (clazz == null) {
314-
throw new NullPointerException("clazz");
319+
failWithoutActual(
320+
simpleFact("could not perform instanceof check because expected type was null"),
321+
actualValue("value to check was"));
322+
return;
315323
}
316-
if (Platform.classMetadataUnsupported()) {
324+
clazz = Primitives.wrap(clazz);
325+
if (classMetadataUnsupported()) {
317326
throw new UnsupportedOperationException(
318327
"isNotInstanceOf is not supported under -XdisableClassMetadata");
319328
}
@@ -329,15 +338,6 @@ public void isNotInstanceOf(Class<?> clazz) {
329338
}
330339
}
331340

332-
private static boolean isInstanceOfType(Object instance, Class<?> clazz) {
333-
checkArgument(
334-
!clazz.isPrimitive(),
335-
"Cannot check instanceof for primitive type %s. Pass the wrapper class %s instead.",
336-
clazz.getSimpleName(),
337-
Primitives.wrap(clazz).getSimpleName());
338-
return Platform.isInstanceOfType(instance, clazz);
339-
}
340-
341341
/** Checks that the value under test is equal to any element in the given iterable. */
342342
public void isIn(@Nullable Iterable<?> iterable) {
343343
if (iterable == null) {

core/src/main/java/com/google/common/truth/TruthFailureSubject.java

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
package com.google.common.truth;
1818

1919
import static com.google.common.base.MoreObjects.firstNonNull;
20-
import static com.google.common.base.Preconditions.checkArgument;
21-
import static com.google.common.base.Preconditions.checkNotNull;
2220
import static com.google.common.truth.Fact.fact;
2321
import static com.google.common.truth.Fact.simpleFact;
2422

@@ -98,7 +96,7 @@ private static ImmutableList<String> getFactKeys(ErrorWithFacts error) {
9896
* fail the test. To assert about such a failure, use {@linkplain #factValue(String, int) the
9997
* other overload} of {@code factValue}.
10098
*/
101-
public StringSubject factValue(String key) {
99+
public StringSubject factValue(@Nullable String key) {
102100
return doFactValue(key, null);
103101
}
104102

@@ -107,13 +105,24 @@ public StringSubject factValue(String key) {
107105
* name. Most Truth failures do not contain multiple facts with the same key, so most tests should
108106
* use {@linkplain #factValue(String) the other overload} of {@code factValue}.
109107
*/
110-
public StringSubject factValue(String key, int index) {
111-
checkArgument(index >= 0, "index must be nonnegative: %s", index);
108+
public StringSubject factValue(@Nullable String key, int index) {
109+
if (index < 0) {
110+
failWithoutActual(
111+
simpleFact("could not perform fact-value check because requested index is null"),
112+
fact("requested key", key),
113+
actualValue("for assertion about Truth failure"));
114+
return ignoreCheck().that("");
115+
}
112116
return doFactValue(key, index);
113117
}
114118

115-
private StringSubject doFactValue(String key, @Nullable Integer index) {
116-
checkNotNull(key);
119+
private StringSubject doFactValue(@Nullable String key, @Nullable Integer index) {
120+
if (key == null) {
121+
failWithoutActual(
122+
simpleFact("could not perform fact-value check because requested key is null"),
123+
actualValue("for assertion about Truth failure"));
124+
return ignoreCheck().that("");
125+
}
117126
if (!(actual instanceof ErrorWithFacts)) {
118127
failWithActual(simpleFact("expected a failure thrown by Truth's failure API"));
119128
return ignoreCheck().that("");

core/src/test/java/com/google/common/truth/IntStreamSubjectTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,17 @@ public void containsExactly_fails() {
245245
assertFailureValue(e, "expected", "[42]");
246246
}
247247

248+
@Test
249+
public void testContainsExactly_nullExpected() {
250+
AssertionError expected =
251+
expectFailure(
252+
whenTesting -> whenTesting.that(IntStream.of(42, 43)).containsExactly((int[]) null));
253+
assertFailureKeys(
254+
expected,
255+
"could not perform containment check because expected array is null",
256+
"actual contents");
257+
}
258+
248259
@Test
249260
public void containsExactly_inOrder() {
250261
assertThat(IntStream.of(42, 43)).containsExactly(42, 43).inOrder();

core/src/test/java/com/google/common/truth/LongStreamSubjectTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,17 @@ public void containsExactly_fails() {
265265
assertFailureValue(e, "expected", "[42]");
266266
}
267267

268+
@Test
269+
public void testContainsExactly_nullExpected() {
270+
AssertionError expected =
271+
expectFailure(
272+
whenTesting -> whenTesting.that(LongStream.of(42, 43)).containsExactly((long[]) null));
273+
assertFailureKeys(
274+
expected,
275+
"could not perform containment check because expected array is null",
276+
"actual contents");
277+
}
278+
268279
@Test
269280
public void containsExactly_inOrder() {
270281
assertThat(LongStream.of(42, 43)).containsExactly(42, 43).inOrder();

core/src/test/java/com/google/common/truth/SubjectTest.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -550,11 +550,19 @@ public void isInstanceOfInterfaceForNull() {
550550
expectFailure(whenTesting -> whenTesting.that((Object) null).isInstanceOf(CharSequence.class));
551551
}
552552

553-
// false positive; actually an intentional trivially *false* check
554-
@SuppressWarnings("IsInstanceInteger")
553+
@SuppressWarnings("IsInstanceInteger") // test is an intentional trivially true check
555554
@Test
556555
public void isInstanceOfPrimitiveType() {
557-
assertThrows(IllegalArgumentException.class, () -> assertThat(1).isInstanceOf(int.class));
556+
assertThat(1).isInstanceOf(int.class);
557+
}
558+
559+
@Test
560+
public void isInstanceOfNull() {
561+
AssertionError e = expectFailure(whenTesting -> whenTesting.that(1).isInstanceOf(null));
562+
assertFailureKeys(
563+
e,
564+
"could not perform instanceof check because expected type was null",
565+
"value to check was");
558566
}
559567

560568
@Test
@@ -601,7 +609,16 @@ public void isNotInstanceOfImplementedInterface() {
601609

602610
@Test
603611
public void isNotInstanceOfPrimitiveType() {
604-
assertThrows(IllegalArgumentException.class, () -> assertThat(1).isNotInstanceOf(int.class));
612+
expectFailure(whenTesting -> whenTesting.that(1).isNotInstanceOf(int.class));
613+
}
614+
615+
@Test
616+
public void isNotInstanceOfNull() {
617+
AssertionError e = expectFailure(whenTesting -> whenTesting.that(1).isNotInstanceOf(null));
618+
assertFailureKeys(
619+
e,
620+
"could not perform instanceof check because expected type was null",
621+
"value to check was");
605622
}
606623

607624
@Test

core/src/test/java/com/google/common/truth/TruthFailureSubjectTest.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import static com.google.common.truth.FailureAssertions.assertFailureValue;
2424
import static com.google.common.truth.TruthFailureSubject.HOW_TO_TEST_KEYS_WITHOUT_VALUES;
2525
import static com.google.common.truth.TruthFailureSubject.truthFailures;
26-
import static org.junit.Assert.assertThrows;
2726

2827
import com.google.common.collect.ImmutableList;
2928
import com.google.common.truth.ExpectFailure.SimpleSubjectBuilderCallback;
@@ -114,6 +113,16 @@ public void factValueFailNoValue() {
114113
assertFailureValue(e, "for key", "foo");
115114
}
116115

116+
@Test
117+
public void factValueFailNull() {
118+
AssertionError e =
119+
expectFailure(whenTesting -> whenTesting.that(failure(simpleFact("foo"))).factValue(null));
120+
assertFailureKeys(
121+
e,
122+
"could not perform fact-value check because requested key is null",
123+
"for assertion about Truth failure");
124+
}
125+
117126
// factValue(String, int)
118127

119128
@Test
@@ -130,9 +139,15 @@ public void factValueIntMultipleKeys() {
130139

131140
@Test
132141
public void factValueIntFailNegative() {
133-
assertThrows(
134-
IllegalArgumentException.class,
135-
() -> assertThat(fact("foo", "the foo")).factValue("foo", -1));
142+
AssertionError e =
143+
expectFailure(
144+
whenTesting -> whenTesting.that(failure(fact("foo", "the foo"))).factValue("foo", -1));
145+
assertFailureKeys(
146+
e,
147+
"could not perform fact-value check because requested index is null",
148+
"requested key",
149+
"for assertion about Truth failure");
150+
assertFailureValue(e, "requested key", "foo");
136151
}
137152

138153
@Test

0 commit comments

Comments
 (0)