Home | History | Annotate | Download | only in testing
      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