001/*
002 * Copyright (C) 2007 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.google.common.testing;
018
019import static com.google.common.base.Preconditions.checkNotNull;
020import static junit.framework.Assert.assertEquals;
021import static junit.framework.Assert.assertTrue;
022
023import com.google.common.annotations.GwtCompatible;
024import com.google.common.base.Equivalence;
025import com.google.common.collect.Iterables;
026import com.google.common.collect.Lists;
027import com.google.errorprone.annotations.CanIgnoreReturnValue;
028import java.util.ArrayList;
029import java.util.List;
030import org.checkerframework.checker.nullness.qual.Nullable;
031
032/**
033 * Tester for equals() and hashCode() methods of a class.
034 *
035 * <p>The simplest use case is:
036 *
037 * <pre>
038 * new EqualsTester().addEqualityGroup(foo).testEquals();
039 * </pre>
040 *
041 * <p>This tests {@code foo.equals(foo)}, {@code foo.equals(null)}, and a few other operations.
042 *
043 * <p>For more extensive testing, add multiple equality groups. Each group should contain objects
044 * that are equal to each other but unequal to the objects in any other group. For example:
045 *
046 * <pre>
047 * new EqualsTester()
048 *     .addEqualityGroup(new User("page"), new User("page"))
049 *     .addEqualityGroup(new User("sergey"))
050 *     .testEquals();
051 * </pre>
052 *
053 * <p>This tests:
054 *
055 * <ul>
056 *   <li>comparing each object against itself returns true
057 *   <li>comparing each object against null returns false
058 *   <li>comparing each object against an instance of an incompatible class returns false
059 *   <li>comparing each pair of objects within the same equality group returns true
060 *   <li>comparing each pair of objects from different equality groups returns false
061 *   <li>the hash codes of any two equal objects are equal
062 * </ul>
063 *
064 * <p>When a test fails, the error message labels the objects involved in the failed comparison as
065 * follows:
066 *
067 * <ul>
068 *   <li>"{@code [group }<i>i</i>{@code , item }<i>j</i>{@code ]}" refers to the
069 *       <i>j</i><sup>th</sup> item in the <i>i</i><sup>th</sup> equality group, where both equality
070 *       groups and the items within equality groups are numbered starting from 1. When either a
071 *       constructor argument or an equal object is provided, that becomes group 1.
072 * </ul>
073 *
074 * @author Jim McMaster
075 * @author Jige Yu
076 * @since 10.0
077 */
078@GwtCompatible
079@ElementTypesAreNonnullByDefault
080public final class EqualsTester {
081  private static final int REPETITIONS = 3;
082
083  private final List<List<Object>> equalityGroups = Lists.newArrayList();
084  private final RelationshipTester.ItemReporter itemReporter;
085
086  /** Constructs an empty EqualsTester instance */
087  public EqualsTester() {
088    this(new RelationshipTester.ItemReporter());
089  }
090
091  EqualsTester(RelationshipTester.ItemReporter itemReporter) {
092    this.itemReporter = checkNotNull(itemReporter);
093  }
094
095  /**
096   * Adds {@code equalityGroup} with objects that are supposed to be equal to each other and not
097   * equal to any other equality groups added to this tester.
098   */
099  @CanIgnoreReturnValue
100  public EqualsTester addEqualityGroup(@Nullable Object @Nullable ... equalityGroup) {
101    checkNotNull(equalityGroup);
102    List<Object> list = new ArrayList<>(equalityGroup.length);
103    for (int i = 0; i < equalityGroup.length; i++) {
104      Object element = equalityGroup[i];
105      if (element == null) {
106        throw new NullPointerException("at index " + i);
107      }
108      list.add(element);
109    }
110    equalityGroups.add(list);
111    return this;
112  }
113
114  /** Run tests on equals method, throwing a failure on an invalid test */
115  @CanIgnoreReturnValue
116  public EqualsTester testEquals() {
117    RelationshipTester<Object> delegate =
118        new RelationshipTester<>(
119            Equivalence.equals(), "Object#equals", "Object#hashCode", itemReporter);
120    for (List<Object> group : equalityGroups) {
121      delegate.addRelatedGroup(group);
122    }
123    for (int run = 0; run < REPETITIONS; run++) {
124      testItems();
125      delegate.test();
126    }
127    return this;
128  }
129
130  private void testItems() {
131    for (Object item : Iterables.concat(equalityGroups)) {
132      assertTrue(item + " must not be Object#equals to null", !item.equals(null));
133      assertTrue(
134          item + " must not be Object#equals to an arbitrary object of another class",
135          !item.equals(NotAnInstance.EQUAL_TO_NOTHING));
136      assertTrue(item + " must be Object#equals to itself", item.equals(item));
137      assertEquals(
138          "the Object#hashCode of " + item + " must be consistent",
139          item.hashCode(),
140          item.hashCode());
141      if (!(item instanceof String)) {
142        assertTrue(
143            item + " must not be Object#equals to its Object#toString representation",
144            !item.equals(item.toString()));
145      }
146    }
147  }
148
149  /**
150   * Class used to test whether equals() correctly handles an instance of an incompatible class.
151   * Since it is a private inner class, the invoker can never pass in an instance to the tester
152   */
153  private enum NotAnInstance {
154    EQUAL_TO_NOTHING;
155  }
156}