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 java.util.List; 26 import junit.framework.AssertionFailedError; 27 28 /** 29 * Implementation helper for {@link EqualsTester} and {@link EquivalenceTester} that tests for 30 * equivalence classes. 31 * 32 * @author Gregory Kick 33 */ 34 @GwtCompatible 35 final class RelationshipTester<T> { 36 37 static class ItemReporter { reportItem(Item<?> item)38 String reportItem(Item<?> item) { 39 return item.toString(); 40 } 41 } 42 43 /** 44 * A word about using {@link Equivalence}, which automatically checks for {@code null} and 45 * identical inputs: This sounds like it ought to be a problem here, since the goals of this class 46 * include testing that {@code equals()} is reflexive and is tolerant of {@code null}. However, 47 * there's no problem. The reason: {@link EqualsTester} tests {@code null} and identical inputs 48 * directly against {@code equals()} rather than through the {@code Equivalence}. 49 */ 50 private final Equivalence<? super T> equivalence; 51 52 private final String relationshipName; 53 private final String hashName; 54 private final ItemReporter itemReporter; 55 private final List<ImmutableList<T>> groups = Lists.newArrayList(); 56 RelationshipTester( Equivalence<? super T> equivalence, String relationshipName, String hashName, ItemReporter itemReporter)57 RelationshipTester( 58 Equivalence<? super T> equivalence, 59 String relationshipName, 60 String hashName, 61 ItemReporter itemReporter) { 62 this.equivalence = checkNotNull(equivalence); 63 this.relationshipName = checkNotNull(relationshipName); 64 this.hashName = checkNotNull(hashName); 65 this.itemReporter = checkNotNull(itemReporter); 66 } 67 68 // TODO(cpovirk): should we reject null items, since the tests already check null automatically? addRelatedGroup(Iterable<? extends T> group)69 public RelationshipTester<T> addRelatedGroup(Iterable<? extends T> group) { 70 groups.add(ImmutableList.copyOf(group)); 71 return this; 72 } 73 test()74 public void test() { 75 for (int groupNumber = 0; groupNumber < groups.size(); groupNumber++) { 76 ImmutableList<T> group = groups.get(groupNumber); 77 for (int itemNumber = 0; itemNumber < group.size(); itemNumber++) { 78 // check related items in same group 79 for (int relatedItemNumber = 0; relatedItemNumber < group.size(); relatedItemNumber++) { 80 if (itemNumber != relatedItemNumber) { 81 assertRelated(groupNumber, itemNumber, relatedItemNumber); 82 } 83 } 84 // check unrelated items in all other groups 85 for (int unrelatedGroupNumber = 0; 86 unrelatedGroupNumber < groups.size(); 87 unrelatedGroupNumber++) { 88 if (groupNumber != unrelatedGroupNumber) { 89 ImmutableList<T> unrelatedGroup = groups.get(unrelatedGroupNumber); 90 for (int unrelatedItemNumber = 0; 91 unrelatedItemNumber < unrelatedGroup.size(); 92 unrelatedItemNumber++) { 93 assertUnrelated(groupNumber, itemNumber, unrelatedGroupNumber, unrelatedItemNumber); 94 } 95 } 96 } 97 } 98 } 99 } 100 assertRelated(int groupNumber, int itemNumber, int relatedItemNumber)101 private void assertRelated(int groupNumber, int itemNumber, int relatedItemNumber) { 102 Item<T> itemInfo = getItem(groupNumber, itemNumber); 103 Item<T> relatedInfo = getItem(groupNumber, relatedItemNumber); 104 105 T item = itemInfo.value; 106 T related = relatedInfo.value; 107 assertWithTemplate( 108 "$ITEM must be $RELATIONSHIP to $OTHER", 109 itemInfo, 110 relatedInfo, 111 equivalence.equivalent(item, related)); 112 113 int itemHash = equivalence.hash(item); 114 int relatedHash = equivalence.hash(related); 115 assertWithTemplate( 116 "the $HASH (" 117 + itemHash 118 + ") of $ITEM must be equal to the $HASH (" 119 + relatedHash 120 + ") of $OTHER", 121 itemInfo, 122 relatedInfo, 123 itemHash == relatedHash); 124 } 125 assertUnrelated( int groupNumber, int itemNumber, int unrelatedGroupNumber, int unrelatedItemNumber)126 private void assertUnrelated( 127 int groupNumber, int itemNumber, int unrelatedGroupNumber, int unrelatedItemNumber) { 128 Item<T> itemInfo = getItem(groupNumber, itemNumber); 129 Item<T> unrelatedInfo = getItem(unrelatedGroupNumber, unrelatedItemNumber); 130 131 assertWithTemplate( 132 "$ITEM must not be $RELATIONSHIP to $OTHER", 133 itemInfo, 134 unrelatedInfo, 135 !equivalence.equivalent(itemInfo.value, unrelatedInfo.value)); 136 } 137 assertWithTemplate(String template, Item<T> item, Item<T> other, boolean condition)138 private void assertWithTemplate(String template, Item<T> item, Item<T> other, boolean condition) { 139 if (!condition) { 140 throw new AssertionFailedError( 141 template 142 .replace("$RELATIONSHIP", relationshipName) 143 .replace("$HASH", hashName) 144 .replace("$ITEM", itemReporter.reportItem(item)) 145 .replace("$OTHER", itemReporter.reportItem(other))); 146 } 147 } 148 getItem(int groupNumber, int itemNumber)149 private Item<T> getItem(int groupNumber, int itemNumber) { 150 return new Item<T>(groups.get(groupNumber).get(itemNumber), groupNumber, itemNumber); 151 } 152 153 static final class Item<T> { 154 final T value; 155 final int groupNumber; 156 final int itemNumber; 157 Item(T value, int groupNumber, int itemNumber)158 Item(T value, int groupNumber, int itemNumber) { 159 this.value = value; 160 this.groupNumber = groupNumber; 161 this.itemNumber = itemNumber; 162 } 163 164 @Override toString()165 public String toString() { 166 return value + " [group " + (groupNumber + 1) + ", item " + (itemNumber + 1) + ']'; 167 } 168 } 169 } 170