1 /* 2 * Copyright (C) 2011 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.testing; 18 19 import static com.google.common.base.Preconditions.checkNotNull; 20 21 import com.google.common.annotations.GwtCompatible; 22 import com.google.common.base.Equivalence; 23 import com.google.common.collect.ImmutableList; 24 import com.google.common.collect.Lists; 25 import com.google.errorprone.annotations.CanIgnoreReturnValue; 26 import java.util.List; 27 import junit.framework.AssertionFailedError; 28 29 /** 30 * Implementation helper for {@link EqualsTester} and {@link EquivalenceTester} that tests for 31 * equivalence classes. 32 * 33 * @author Gregory Kick 34 */ 35 @GwtCompatible 36 @ElementTypesAreNonnullByDefault 37 final class RelationshipTester<T> { 38 39 static class ItemReporter { reportItem(Item<?> item)40 String reportItem(Item<?> item) { 41 return item.toString(); 42 } 43 } 44 45 /** 46 * A word about using {@link Equivalence}, which automatically checks for {@code null} and 47 * identical inputs: This sounds like it ought to be a problem here, since the goals of this class 48 * include testing that {@code equals()} is reflexive and is tolerant of {@code null}. However, 49 * there's no problem. The reason: {@link EqualsTester} tests {@code null} and identical inputs 50 * directly against {@code equals()} rather than through the {@code Equivalence}. 51 */ 52 private final Equivalence<? super T> equivalence; 53 54 private final String relationshipName; 55 private final String hashName; 56 private final ItemReporter itemReporter; 57 private final List<ImmutableList<T>> groups = Lists.newArrayList(); 58 RelationshipTester( Equivalence<? super T> equivalence, String relationshipName, String hashName, ItemReporter itemReporter)59 RelationshipTester( 60 Equivalence<? super T> equivalence, 61 String relationshipName, 62 String hashName, 63 ItemReporter itemReporter) { 64 this.equivalence = checkNotNull(equivalence); 65 this.relationshipName = checkNotNull(relationshipName); 66 this.hashName = checkNotNull(hashName); 67 this.itemReporter = checkNotNull(itemReporter); 68 } 69 70 // TODO(cpovirk): should we reject null items, since the tests already check null automatically? 71 @CanIgnoreReturnValue addRelatedGroup(Iterable<? extends T> group)72 public RelationshipTester<T> addRelatedGroup(Iterable<? extends T> group) { 73 groups.add(ImmutableList.copyOf(group)); 74 return this; 75 } 76 test()77 public void test() { 78 for (int groupNumber = 0; groupNumber < groups.size(); groupNumber++) { 79 ImmutableList<T> group = groups.get(groupNumber); 80 for (int itemNumber = 0; itemNumber < group.size(); itemNumber++) { 81 // check related items in same group 82 for (int relatedItemNumber = 0; relatedItemNumber < group.size(); relatedItemNumber++) { 83 if (itemNumber != relatedItemNumber) { 84 assertRelated(groupNumber, itemNumber, relatedItemNumber); 85 } 86 } 87 // check unrelated items in all other groups 88 for (int unrelatedGroupNumber = 0; 89 unrelatedGroupNumber < groups.size(); 90 unrelatedGroupNumber++) { 91 if (groupNumber != unrelatedGroupNumber) { 92 ImmutableList<T> unrelatedGroup = groups.get(unrelatedGroupNumber); 93 for (int unrelatedItemNumber = 0; 94 unrelatedItemNumber < unrelatedGroup.size(); 95 unrelatedItemNumber++) { 96 assertUnrelated(groupNumber, itemNumber, unrelatedGroupNumber, unrelatedItemNumber); 97 } 98 } 99 } 100 } 101 } 102 } 103 assertRelated(int groupNumber, int itemNumber, int relatedItemNumber)104 private void assertRelated(int groupNumber, int itemNumber, int relatedItemNumber) { 105 Item<T> itemInfo = getItem(groupNumber, itemNumber); 106 Item<T> relatedInfo = getItem(groupNumber, relatedItemNumber); 107 108 T item = itemInfo.value; 109 T related = relatedInfo.value; 110 assertWithTemplate( 111 "$ITEM must be $RELATIONSHIP to $OTHER", 112 itemInfo, 113 relatedInfo, 114 equivalence.equivalent(item, related)); 115 116 int itemHash = equivalence.hash(item); 117 int relatedHash = equivalence.hash(related); 118 assertWithTemplate( 119 "the $HASH (" 120 + itemHash 121 + ") of $ITEM must be equal to the $HASH (" 122 + relatedHash 123 + ") of $OTHER", 124 itemInfo, 125 relatedInfo, 126 itemHash == relatedHash); 127 } 128 assertUnrelated( int groupNumber, int itemNumber, int unrelatedGroupNumber, int unrelatedItemNumber)129 private void assertUnrelated( 130 int groupNumber, int itemNumber, int unrelatedGroupNumber, int unrelatedItemNumber) { 131 Item<T> itemInfo = getItem(groupNumber, itemNumber); 132 Item<T> unrelatedInfo = getItem(unrelatedGroupNumber, unrelatedItemNumber); 133 134 assertWithTemplate( 135 "$ITEM must not be $RELATIONSHIP to $OTHER", 136 itemInfo, 137 unrelatedInfo, 138 !equivalence.equivalent(itemInfo.value, unrelatedInfo.value)); 139 } 140 assertWithTemplate(String template, Item<T> item, Item<T> other, boolean condition)141 private void assertWithTemplate(String template, Item<T> item, Item<T> other, boolean condition) { 142 if (!condition) { 143 throw new AssertionFailedError( 144 template 145 .replace("$RELATIONSHIP", relationshipName) 146 .replace("$HASH", hashName) 147 .replace("$ITEM", itemReporter.reportItem(item)) 148 .replace("$OTHER", itemReporter.reportItem(other))); 149 } 150 } 151 getItem(int groupNumber, int itemNumber)152 private Item<T> getItem(int groupNumber, int itemNumber) { 153 return new Item<>(groups.get(groupNumber).get(itemNumber), groupNumber, itemNumber); 154 } 155 156 static final class Item<T> { 157 final T value; 158 final int groupNumber; 159 final int itemNumber; 160 Item(T value, int groupNumber, int itemNumber)161 Item(T value, int groupNumber, int itemNumber) { 162 this.value = value; 163 this.groupNumber = groupNumber; 164 this.itemNumber = itemNumber; 165 } 166 167 @Override toString()168 public String toString() { 169 return value + " [group " + (groupNumber + 1) + ", item " + (itemNumber + 1) + ']'; 170 } 171 } 172 } 173