/* * Copyright (C) 2011 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.common.testing; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; import com.google.common.base.Equivalence; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import java.util.List; import junit.framework.AssertionFailedError; /** * Implementation helper for {@link EqualsTester} and {@link EquivalenceTester} that tests for * equivalence classes. * * @author Gregory Kick */ @GwtCompatible final class RelationshipTester<T> { static class ItemReporter { String reportItem(Item<?> item) { return item.toString(); } } /** * A word about using {@link Equivalence}, which automatically checks for {@code null} and * identical inputs: This sounds like it ought to be a problem here, since the goals of this class * include testing that {@code equals()} is reflexive and is tolerant of {@code null}. However, * there's no problem. The reason: {@link EqualsTester} tests {@code null} and identical inputs * directly against {@code equals()} rather than through the {@code Equivalence}. */ private final Equivalence<? super T> equivalence; private final String relationshipName; private final String hashName; private final ItemReporter itemReporter; private final List<ImmutableList<T>> groups = Lists.newArrayList(); RelationshipTester(Equivalence<? super T> equivalence, String relationshipName, String hashName, ItemReporter itemReporter) { this.equivalence = checkNotNull(equivalence); this.relationshipName = checkNotNull(relationshipName); this.hashName = checkNotNull(hashName); this.itemReporter = checkNotNull(itemReporter); } // TODO(cpovirk): should we reject null items, since the tests already check null automatically? public RelationshipTester<T> addRelatedGroup(Iterable<? extends T> group) { groups.add(ImmutableList.copyOf(group)); return this; } public void test() { for (int groupNumber = 0; groupNumber < groups.size(); groupNumber++) { ImmutableList<T> group = groups.get(groupNumber); for (int itemNumber = 0; itemNumber < group.size(); itemNumber++) { // check related items in same group for (int relatedItemNumber = 0; relatedItemNumber < group.size(); relatedItemNumber++) { if (itemNumber != relatedItemNumber) { assertRelated(groupNumber, itemNumber, relatedItemNumber); } } // check unrelated items in all other groups for (int unrelatedGroupNumber = 0; unrelatedGroupNumber < groups.size(); unrelatedGroupNumber++) { if (groupNumber != unrelatedGroupNumber) { ImmutableList<T> unrelatedGroup = groups.get(unrelatedGroupNumber); for (int unrelatedItemNumber = 0; unrelatedItemNumber < unrelatedGroup.size(); unrelatedItemNumber++) { assertUnrelated(groupNumber, itemNumber, unrelatedGroupNumber, unrelatedItemNumber); } } } } } } private void assertRelated(int groupNumber, int itemNumber, int relatedItemNumber) { Item<T> itemInfo = getItem(groupNumber, itemNumber); Item<T> relatedInfo = getItem(groupNumber, relatedItemNumber); T item = itemInfo.value; T related = relatedInfo.value; assertWithTemplate("$ITEM must be $RELATIONSHIP to $OTHER", itemInfo, relatedInfo, equivalence.equivalent(item, related)); int itemHash = equivalence.hash(item); int relatedHash = equivalence.hash(related); assertWithTemplate("the $HASH (" + itemHash + ") of $ITEM must be equal to the $HASH (" + relatedHash + ") of $OTHER", itemInfo, relatedInfo, itemHash == relatedHash); } private void assertUnrelated(int groupNumber, int itemNumber, int unrelatedGroupNumber, int unrelatedItemNumber) { Item<T> itemInfo = getItem(groupNumber, itemNumber); Item<T> unrelatedInfo = getItem(unrelatedGroupNumber, unrelatedItemNumber); assertWithTemplate("$ITEM must not be $RELATIONSHIP to $OTHER", itemInfo, unrelatedInfo, !equivalence.equivalent(itemInfo.value, unrelatedInfo.value)); } private void assertWithTemplate(String template, Item<T> item, Item<T> other, boolean condition) { if (!condition) { throw new AssertionFailedError(template .replace("$RELATIONSHIP", relationshipName) .replace("$HASH", hashName) .replace("$ITEM", itemReporter.reportItem(item)) .replace("$OTHER", itemReporter.reportItem(other))); } } private Item<T> getItem(int groupNumber, int itemNumber) { return new Item<T>(groups.get(groupNumber).get(itemNumber), groupNumber, itemNumber); } static final class Item<T> { final T value; final int groupNumber; final int itemNumber; Item(T value, int groupNumber, int itemNumber) { this.value = value; this.groupNumber = groupNumber; this.itemNumber = itemNumber; } @Override public String toString() { return new StringBuilder() .append(value) .append(" [group ") .append(groupNumber + 1) .append(", item ") .append(itemNumber + 1) .append(']') .toString(); } } }