RelationshipTester.java revision 3c77433663281544363151bf284b0240dfd22a42
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.collect.ImmutableList;
23import com.google.common.collect.Lists;
24
25import junit.framework.AssertionFailedError;
26
27import java.util.List;
28
29/**
30 * Tests a collection of objects according to the rules specified in a
31 * {@link RelationshipAssertion}.
32 *
33 * @author Gregory Kick
34 */
35@GwtCompatible
36final class RelationshipTester<T> {
37
38  static class ItemReporter {
39    String reportItem(Item item) {
40      return item.toString();
41    }
42  }
43
44  private final List<ImmutableList<T>> groups = Lists.newArrayList();
45  private final RelationshipAssertion<T> assertion;
46  private final ItemReporter itemReporter;
47
48  RelationshipTester(RelationshipAssertion<T> assertion, ItemReporter itemReporter) {
49    this.assertion = checkNotNull(assertion);
50    this.itemReporter = checkNotNull(itemReporter);
51  }
52
53  RelationshipTester(RelationshipAssertion<T> assertion) {
54    this(assertion, new ItemReporter());
55  }
56
57  public RelationshipTester<T> addRelatedGroup(Iterable<? extends T> group) {
58    groups.add(ImmutableList.copyOf(group));
59    return this;
60  }
61
62  public void test() {
63    for (int groupNumber = 0; groupNumber < groups.size(); groupNumber++) {
64      ImmutableList<T> group = groups.get(groupNumber);
65      for (int itemNumber = 0; itemNumber < group.size(); itemNumber++) {
66        // check related items in same group
67        for (int relatedItemNumber = 0; relatedItemNumber < group.size(); relatedItemNumber++) {
68          if (itemNumber != relatedItemNumber) {
69            assertRelated(groupNumber, itemNumber, relatedItemNumber);
70          }
71        }
72        // check unrelated items in all other groups
73        for (int unrelatedGroupNumber = 0; unrelatedGroupNumber < groups.size();
74            unrelatedGroupNumber++) {
75          if (groupNumber != unrelatedGroupNumber) {
76            ImmutableList<T> unrelatedGroup = groups.get(unrelatedGroupNumber);
77            for (int unrelatedItemNumber = 0; unrelatedItemNumber < unrelatedGroup.size();
78                unrelatedItemNumber++) {
79              assertUnrelated(groupNumber, itemNumber, unrelatedGroupNumber, unrelatedItemNumber);
80            }
81          }
82        }
83      }
84    }
85  }
86
87  private void assertRelated(int groupNumber, int itemNumber, int relatedItemNumber) {
88    ImmutableList<T> group = groups.get(groupNumber);
89    T item = group.get(itemNumber);
90    T related = group.get(relatedItemNumber);
91    try {
92      assertion.assertRelated(item, related);
93    } catch (AssertionFailedError e) {
94      // TODO(gak): special handling for ComparisonFailure?
95      throw new AssertionFailedError(e.getMessage()
96          .replace("$ITEM", itemReporter.reportItem(new Item(item, groupNumber, itemNumber)))
97          .replace("$RELATED",
98              itemReporter.reportItem(new Item(related, groupNumber, relatedItemNumber))));
99    }
100  }
101
102  private void assertUnrelated(int groupNumber, int itemNumber, int unrelatedGroupNumber,
103      int unrelatedItemNumber) {
104    T item = groups.get(groupNumber).get(itemNumber);
105    T unrelated = groups.get(unrelatedGroupNumber).get(unrelatedItemNumber);
106    try {
107      assertion.assertUnrelated(item, unrelated);
108    } catch (AssertionFailedError e) {
109      // TODO(gak): special handling for ComparisonFailure?
110      throw new AssertionFailedError(e.getMessage()
111          .replace("$ITEM", itemReporter.reportItem(new Item(item, groupNumber, itemNumber)))
112          .replace("$UNRELATED", itemReporter.reportItem(
113              new Item(unrelated, unrelatedGroupNumber, unrelatedItemNumber))));
114    }
115  }
116
117  static final class Item {
118    final Object value;
119    final int groupNumber;
120    final int itemNumber;
121
122    Item(Object value, int groupNumber, int itemNumber) {
123      this.value = value;
124      this.groupNumber = groupNumber;
125      this.itemNumber = itemNumber;
126    }
127
128    @Override public String toString() {
129      return new StringBuilder()
130          .append(value)
131          .append(" [group ")
132          .append(groupNumber + 1)
133          .append(", item ")
134          .append(itemNumber + 1)
135          .append(']')
136          .toString();
137    }
138  }
139
140  /**
141   * A strategy for testing the relationship between objects.  Methods are expected to throw
142   * {@link AssertionFailedError} whenever the relationship is violated.
143   *
144   * <p>As a convenience, any occurrence of {@code $ITEM}, {@code $RELATED} or {@code $UNRELATED} in
145   * the error message will be replaced with a string that combines the {@link Object#toString()},
146   * item number and group number of the respective item.
147   *
148   */
149  static abstract class RelationshipAssertion<T> {
150    abstract void assertRelated(T item, T related);
151    abstract void assertUnrelated(T item, T unrelated);
152  }
153}
154