Skip to content

Commit

Permalink
IterableSubject.contains() now adds type information when elements ha…
Browse files Browse the repository at this point in the history
…ve same toString()s.

Example:
assertThat(asList(1L, 2L)).contains(1);
--> <[1, 2]> should have contained <1 (java.lang.Integer)> but doesn't. However, it does contain <[1] (java.lang.Long)>

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=148821276
  • Loading branch information
nymanjens authored and cpovirk committed Mar 3, 2017
1 parent caad47e commit 4d6b7cf
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 18 deletions.
66 changes: 48 additions & 18 deletions core/src/main/java/com/google/common/truth/IterableSubject.java
Expand Up @@ -103,7 +103,18 @@ public final void hasSize(int expectedSize) {
/** Attests (with a side-effect failure) that the subject contains the supplied item. */
public final void contains(@Nullable Object element) {
if (!Iterables.contains(actual(), element)) {
failWithRawMessage("%s should have contained <%s>", actualAsString(), element);
List<Object> elementList = Lists.newArrayList(element);
if (hasMatchingToStringPair(actual(), elementList)) {
failWithRawMessage(
"%s should have contained <%s> but doesn't. However, it does contain <%s>.",
actualAsString(),
addTypeInfo(element),
countDuplicatesAndAddTypeInfo(
retainMatchingToString(actual(), elementList /* itemsToCheck */),
elementList /* itemsToCheckForMatchingToStrings */));
} else {
failWithRawMessage("%s should have contained <%s>", actualAsString(), element);
}
}
}

Expand Down Expand Up @@ -406,26 +417,40 @@ private static String countDuplicatesAndAddTypeInfo(
}

/**
* Returns true if there is a pair of an item from {@code items1} and one in {@code items2} that
* has the same {@code toString()} value without being equal.
* Returns a new collection containing all elements in {@code items} for which there exists at
* least one element in {@code itemsToCheck} that has the same {@code toString()} value without
* being equal.
*
* <p>Example: {@code hasMatchingToStringPair([1L, 2L], [1]) == true}
* <p>Example: {@code retainMatchingToString([1L, 2L, 2L], [2, 3]) == [2L, 2L]}
*/
private static boolean hasMatchingToStringPair(Iterable<?> items1, Iterable<?> items2) {
SetMultimap<String, Object> stringValueToItem1 =
private static List<Object> retainMatchingToString(
Iterable<?> items, Iterable<?> itemsToCheck) {
SetMultimap<String, Object> stringValueToItemsToCheck =
MultimapBuilder.hashKeys().hashSetValues().build();
for (Object item1 : items1) {
stringValueToItem1.put(String.valueOf(item1), item1);
for (Object itemToCheck : itemsToCheck) {
stringValueToItemsToCheck.put(String.valueOf(itemToCheck), itemToCheck);
}

for (Object item2 : items2) {
for (Object item1 : stringValueToItem1.get(String.valueOf(item2))) {
if (!item1.equals(item2)) {
return true;
List<Object> result = Lists.newArrayList();
for (Object item : items) {
for (Object itemToCheck : stringValueToItemsToCheck.get(String.valueOf(item))) {
if (!Objects.equal(itemToCheck, item)) {
result.add(item);
break;
}
}
}
return false;
return result;
}

/**
* Returns true if there is a pair of an item from {@code items1} and one in {@code items2} that
* has the same {@code toString()} value without being equal.
*
* <p>Example: {@code hasMatchingToStringPair([1L, 2L], [1]) == true}
*/
private static boolean hasMatchingToStringPair(Iterable<?> items1, Iterable<?> items2) {
return !retainMatchingToString(items1, items2).isEmpty();
}

/**
Expand All @@ -451,15 +476,20 @@ private static Optional<Class<?>> getHomogeneousClass(Iterable<?> items) {
private static List<String> addTypeInfoToEveryItem(Iterable<?> items) {
List<String> itemsWithTypeInfo = Lists.newArrayList();
for (Object item : items) {
if (item == null) {
itemsWithTypeInfo.add(null);
} else {
itemsWithTypeInfo.add(StringUtil.format("%s (%s)", item, item.getClass().getName()));
}
itemsWithTypeInfo.add(addTypeInfo(item));
}
return itemsWithTypeInfo;
}

/** Converts the argument's value to a String and appends the class name. */
private static String addTypeInfo(Object item) {
if (item == null) {
return "null";
} else {
return StringUtil.format("%s (%s)", item, item.getClass().getName());
}
}

/**
* Fails with the bad results and a suffix.
*
Expand Down
Expand Up @@ -82,6 +82,34 @@ public void iterableContainsWithNull() {
assertThat(asList(1, null, 3)).contains(null);
}

@Test
public void iterableContainsFailsWithSameToString() {
try {
assertThat(asList(1L, 2L, 3L, 2L)).contains(2);
fail("Should have thrown.");
} catch (AssertionError e) {
assertThat(e)
.hasMessageThat()
.isEqualTo(
"<[1, 2, 3, 2]> should have contained <2 (java.lang.Integer)> but doesn't. However, "
+ "it does contain <[2 [2 copies]] (java.lang.Long)>.");
}
}

@Test
public void iterableContainsFailsWithSameToStringAndNull() {
try {
assertThat(asList(1, "null")).contains(null);
fail("Should have thrown.");
} catch (AssertionError e) {
assertThat(e)
.hasMessageThat()
.isEqualTo(
"<[1, null]> should have contained <null> but doesn't. However, it does contain "
+ "<[null] (java.lang.String)>.");
}
}

@Test
public void iterableContainsFailure() {
try {
Expand Down

0 comments on commit 4d6b7cf

Please sign in to comment.