RelationshipTester.java revision 0888a09821a98ac0680fad765217302858e70fa4
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
17package com.google.common.testing;
18
19import static com.google.common.base.Preconditions.checkNotNull;
20
21import com.google.common.annotations.GwtCompatible;
22import com.google.common.base.Equivalence;
23import com.google.common.collect.ImmutableList;
24import com.google.common.collect.Lists;
25
26import junit.framework.AssertionFailedError;
27
28import 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
37final 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