• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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