Throw ComparisonFailure from assertThat(singleElementIterable).containsExactly(otherSingleElementIterable).
RELNOTES=Began throwing `ComparisonFailure` from `assertThat(singleElementIterable).containsExactly(otherSingleElementIterable)`.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=191909189
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 531e6d7..cd28958 100644
--- a/core/src/main/java/com/google/common/truth/IterableSubject.java
+++ b/core/src/main/java/com/google/common/truth/IterableSubject.java
@@ -366,6 +366,7 @@
}
// Step through both iterators comparing elements pairwise.
+ boolean isFirst = true;
while (actualIter.hasNext() && requiredIter.hasNext()) {
Object actualElement = actualIter.next();
Object requiredElement = requiredIter.next();
@@ -375,6 +376,28 @@
// Since any previous pairs of elements we iterated over were equal, they have no
// effect on the result now.
if (!Objects.equal(actualElement, requiredElement)) {
+ if (isFirst && !actualIter.hasNext() && !requiredIter.hasNext()) {
+ /*
+ * There's exactly one actual element and exactly one expected element, and they don't
+ * match, so throw a ComparisonFailure. The logical way to do that would be
+ * `check(...).that(actualElement).isEqualTo(requiredElement)`. But isEqualTo has magic
+ * behavior for arrays and primitives, behavior that's inconsistent with how this method
+ * otherwise behaves. For consistency, we want to rely only on the equal() call we've
+ * already made. So we expose a special method for this and call it from here.
+ *
+ * TODO(cpovirk): Consider always throwing ComparisonFailure if there is exactly one
+ * missing and exactly one extra element, even if there were additional (matching)
+ * elements. However, this will probably be useful less often, and it will be tricky to
+ * explain. First, what would we say, "value of: iterable.onlyElementThatDidNotMatch()?"
+ * And second, it feels weirder to call out a single element when the expected and actual
+ * values had multiple elements. Granted, Fuzzy Truth already does this, so maybe it's OK?
+ * But Fuzzy Truth doesn't (yet) make the mismatched value so prominent.
+ */
+ check("onlyElement()")
+ .that(actualElement)
+ .failEqualityCheckForEqualsWithoutDescription(requiredElement);
+ return ALREADY_FAILED;
+ }
// Missing elements; elements that are not missing will be removed as we iterate.
Collection<Object> missing = newArrayList();
missing.add(requiredElement);
@@ -404,6 +427,8 @@
}
return failExactly(required, addElementsInWarning, missing, extra);
}
+
+ isFirst = false;
}
// Here, we must have reached the end of one of the iterators without finding any
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 0fc9cc6..4f7f11b 100644
--- a/core/src/main/java/com/google/common/truth/Subject.java
+++ b/core/src/main/java/com/google/common/truth/Subject.java
@@ -814,7 +814,15 @@
}
}
- private void failEqualityCheck(
+ /**
+ * Special version of {@link #failEqualityCheck} for use from {@link IterableSubject}, documented
+ * further there.
+ */
+ final void failEqualityCheckForEqualsWithoutDescription(Object expected) {
+ failEqualityCheck(EqualityCheck.EQUAL, expected, ComparisonResult.differentNoDescription());
+ }
+
+ private final void failEqualityCheck(
EqualityCheck equalityCheck, Object expected, ComparisonResult difference) {
String actualString = actualCustomStringRepresentation();
String expectedString = formatActualOrExpected(expected);
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 2a5e881..a31cdc9 100644
--- a/core/src/test/java/com/google/common/truth/IterableSubjectTest.java
+++ b/core/src/test/java/com/google/common/truth/IterableSubjectTest.java
@@ -637,6 +637,20 @@
}
@Test
+ public void iterableContainsExactlySingleElement() {
+ assertThat(asList(1)).containsExactly(1);
+
+ expectFailureWhenTestingThat(asList(1)).containsExactly(2);
+ assertFailureValue("value of", "iterable.onlyElement()");
+ }
+
+ @Test
+ public void iterableContainsExactlySingleElementNoEqualsMagic() {
+ expectFailureWhenTestingThat(asList(1)).containsExactly(1L);
+ assertFailureValueIndexed("an instance of", 0, "java.lang.Long");
+ }
+
+ @Test
public void iterableContainsExactlyWithElementsThatThrowWhenYouCallHashCode() {
HashCodeThrower one = new HashCodeThrower();
HashCodeThrower two = new HashCodeThrower();
diff --git a/core/src/test/java/com/google/common/truth/MultimapSubjectTest.java b/core/src/test/java/com/google/common/truth/MultimapSubjectTest.java
index a23e8fb..fb8143f 100644
--- a/core/src/test/java/com/google/common/truth/MultimapSubjectTest.java
+++ b/core/src/test/java/com/google/common/truth/MultimapSubjectTest.java
@@ -169,25 +169,36 @@
* including eliminating named() itself. Or we could just special-case our logic to skip the
* name for non-throwables. For now, I'm not too worried about this.
*/
- assertThat(expectFailure.getFailure())
- .hasMessageThat()
- .isEqualTo(
- "value of: multymap.valuesForKey(1)\n"
- + "Not true that <[5]> contains exactly <[4]>. "
- + "It is missing <[4]> and has unexpected items <[5]>\n"
- + "multimap was: multymap ({1=[5]})");
+ assertFailureKeys("value of", "expected", "but was", "multimap was");
+ assertFailureValue("value of", "multymap.valuesForKey(1).onlyElement()");
+ assertFailureValue("multimap was", "multymap ({1=[5]})");
}
@Test
- public void valuesForKeyNamed() {
+ public void valuesForKeyNamedSingleElements() {
+ /*
+ * TODO(cpovirk): We fail to include the name "valuez" in the failure message here. Fortunately,
+ * I see only 1 usage of valuesForKey().named() in the depot. Also "fortunately," something like
+ * 1/3 of all assertion methods fail to include the name, so the right fix is probably going to
+ * be to delete named() entirely.
+ */
ImmutableMultimap<Integer, Integer> multimap = ImmutableMultimap.of(1, 5);
expectFailureWhenTestingThat(multimap).valuesForKey(1).named("valuez").containsExactly(4);
+ assertFailureKeys("value of", "expected", "but was", "multimap was");
+ assertFailureValue("value of", "multimap.valuesForKey(1).onlyElement()");
+ assertFailureValue("multimap was", "{1=[5]}");
+ }
+
+ @Test
+ public void valuesForKeyNamedMultipleElements() {
+ ImmutableMultimap<Integer, Integer> multimap = ImmutableMultimap.of(1, 5);
+ expectFailureWhenTestingThat(multimap).valuesForKey(1).named("valuez").containsExactly(3, 4);
assertThat(expectFailure.getFailure())
.hasMessageThat()
.isEqualTo(
"value of: multimap.valuesForKey(1)\n"
- + "Not true that valuez (<[5]>) contains exactly <[4]>. "
- + "It is missing <[4]> and has unexpected items <[5]>\n"
+ + "Not true that valuez (<[5]>) contains exactly <[3, 4]>. "
+ + "It is missing <[3, 4]> and has unexpected items <[5]>\n"
+ "multimap was: {1=[5]}");
}