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.jspecify.annotations.NullMarked;
031import org.checkerframework.checker.nullness.qual.Nullable;
032
033/**
034 * Tester for equals() and hashCode() methods of a class.
035 *
036 * <p>The simplest use case is:
037 *
038 * <pre>
039 * new EqualsTester().addEqualityGroup(foo).testEquals();
040 * </pre>
041 *
042 * <p>This tests {@code foo.equals(foo)}, {@code foo.equals(null)}, and a few other operations.
043 *
044 * <p>For more extensive testing, add multiple equality groups. Each group should contain objects
045 * that are equal to each other but unequal to the objects in any other group. For example:
046 *
047 * <pre>
048 * new EqualsTester()
049 *     .addEqualityGroup(new User("page"), new User("page"))
050 *     .addEqualityGroup(new User("sergey"))
051 *     .testEquals();
052 * </pre>
053 *
054 * <p>This tests:
055 *
056 * <ul>
057 *   <li>comparing each object against itself returns true
058 *   <li>comparing each object against null returns false
059 *   <li>comparing each object against an instance of an incompatible class returns false
060 *   <li>comparing each pair of objects within the same equality group returns true
061 *   <li>comparing each pair of objects from different equality groups returns false
062 *   <li>the hash codes of any two equal objects are equal
063 * </ul>
064 *
065 * <p>When a test fails, the error message labels the objects involved in the failed comparison as
066 * follows:
067 *
068 * <ul>
069 *   <li>"{@code [group }<i>i</i>{@code , item }<i>j</i>{@code ]}" refers to the
070 *       <i>j</i><sup>th</sup> item in the <i>i</i><sup>th</sup> equality group, where both equality
071 *       groups and the items within equality groups are numbered starting from 1. When either a
072 *       constructor argument or an equal object is provided, that becomes group 1.
073 * </ul>
074 *
075 * @author Jim McMaster
076 * @author Jige Yu
077 * @since 10.0
078 */
079@GwtCompatible
080@NullMarked
081public final class EqualsTester {
082  private static final int REPETITIONS = 3;
083
084  private final List<List<Object>> equalityGroups = Lists.newArrayList();
085  private final RelationshipTester.ItemReporter itemReporter;
086
087  /** Constructs an empty EqualsTester instance */
088  public EqualsTester() {
089    this(new RelationshipTester.ItemReporter());
090  }
091
092  EqualsTester(RelationshipTester.ItemReporter itemReporter) {
093    this.itemReporter = checkNotNull(itemReporter);
094  }
095
096  /**
097   * Adds {@code equalityGroup} with objects that are supposed to be equal to each other and not
098   * equal to any other equality groups added to this tester.
099   *
100   * <p>The {@code @Nullable} annotations on the {@code equalityGroup} parameter imply that the
101   * objects, and the array itself, can be null. That is for programmer convenience, when the
102   * objects come from factory methods that are themselves {@code @Nullable}. In reality neither the
103   * array nor its contents can be null, but it is not useful to force the use of {@code
104   * requireNonNull} or the like just to assert that.
105   *
106   * <p>{@code EqualsTester} will always check that every object it is given returns false from
107   * {@code equals(null)}, so it is neither useful nor allowed to include a null value in any
108   * equality group.
109   */
110  @CanIgnoreReturnValue
111  public EqualsTester addEqualityGroup(@Nullable Object @Nullable ... equalityGroup) {
112    checkNotNull(equalityGroup);
113    List<Object> list = new ArrayList<>(equalityGroup.length);
114    for (int i = 0; i < equalityGroup.length; i++) {
115      Object element = equalityGroup[i];
116      if (element == null) {
117        throw new NullPointerException("at index " + i);
118      }
119      list.add(element);
120    }
121    equalityGroups.add(list);
122    return this;
123  }
124
125  /** Run tests on equals method, throwing a failure on an invalid test */
126  @CanIgnoreReturnValue
127  public EqualsTester testEquals() {
128    RelationshipTester<Object> delegate =
129        new RelationshipTester<>(
130            Equivalence.equals(), "Object#equals", "Object#hashCode", itemReporter);
131    for (List<Object> group : equalityGroups) {
132      delegate.addRelatedGroup(group);
133    }
134    for (int run = 0; run < REPETITIONS; run++) {
135      testItems();
136      delegate.test();
137    }
138    return this;
139  }
140
141  private void testItems() {
142    for (Object item : Iterables.concat(equalityGroups)) {
143      assertTrue(item + " must not be Object#equals to null", !item.equals(null));
144      assertTrue(
145          item + " must not be Object#equals to an arbitrary object of another class",
146          !item.equals(NotAnInstance.EQUAL_TO_NOTHING));
147      assertTrue(item + " must be Object#equals to itself", item.equals(item));
148      assertEquals(
149          "the Object#hashCode of " + item + " must be consistent",
150          item.hashCode(),
151          item.hashCode());
152      if (!(item instanceof String)) {
153        assertTrue(
154            item + " must not be Object#equals to its Object#toString representation",
155            !item.equals(item.toString()));
156      }
157    }
158  }
159
160  /**
161   * Class used to test whether equals() correctly handles an instance of an incompatible class.
162   * Since it is a private inner class, the invoker can never pass in an instance to the tester
163   */
164  private enum NotAnInstance {
165    EQUAL_TO_NOTHING;
166  }
167}