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