aboutsummaryrefslogtreecommitdiff
path: root/guava-testlib/src/com/google/common/testing/EqualsTester.java
blob: 5f02dba8485561ab895dc0668747fc0bca05da0d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/*
 * Copyright (C) 2007 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 static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;

import com.google.common.annotations.GwtCompatible;
import com.google.common.base.Equivalence;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.ArrayList;
import java.util.List;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
 * Tester for equals() and hashCode() methods of a class.
 *
 * <p>The simplest use case is:
 *
 * <pre>
 * new EqualsTester().addEqualityGroup(foo).testEquals();
 * </pre>
 *
 * <p>This tests {@code foo.equals(foo)}, {@code foo.equals(null)}, and a few other operations.
 *
 * <p>For more extensive testing, add multiple equality groups. Each group should contain objects
 * that are equal to each other but unequal to the objects in any other group. For example:
 *
 * <pre>
 * new EqualsTester()
 *     .addEqualityGroup(new User("page"), new User("page"))
 *     .addEqualityGroup(new User("sergey"))
 *     .testEquals();
 * </pre>
 *
 * <p>This tests:
 *
 * <ul>
 *   <li>comparing each object against itself returns true
 *   <li>comparing each object against null returns false
 *   <li>comparing each object against an instance of an incompatible class returns false
 *   <li>comparing each pair of objects within the same equality group returns true
 *   <li>comparing each pair of objects from different equality groups returns false
 *   <li>the hash codes of any two equal objects are equal
 * </ul>
 *
 * <p>When a test fails, the error message labels the objects involved in the failed comparison as
 * follows:
 *
 * <ul>
 *   <li>"{@code [group }<i>i</i>{@code , item }<i>j</i>{@code ]}" refers to the
 *       <i>j</i><sup>th</sup> item in the <i>i</i><sup>th</sup> equality group, where both equality
 *       groups and the items within equality groups are numbered starting from 1. When either a
 *       constructor argument or an equal object is provided, that becomes group 1.
 * </ul>
 *
 * @author Jim McMaster
 * @author Jige Yu
 * @since 10.0
 */
@GwtCompatible
@ElementTypesAreNonnullByDefault
public final class EqualsTester {
  private static final int REPETITIONS = 3;

  private final List<List<Object>> equalityGroups = Lists.newArrayList();
  private final RelationshipTester.ItemReporter itemReporter;

  /** Constructs an empty EqualsTester instance */
  public EqualsTester() {
    this(new RelationshipTester.ItemReporter());
  }

  EqualsTester(RelationshipTester.ItemReporter itemReporter) {
    this.itemReporter = checkNotNull(itemReporter);
  }

  /**
   * Adds {@code equalityGroup} with objects that are supposed to be equal to each other and not
   * equal to any other equality groups added to this tester.
   *
   * <p>The {@code @Nullable} annotations on the {@code equalityGroup} parameter imply that the
   * objects, and the array itself, can be null. That is for programmer convenience, when the
   * objects come from factory methods that are themselves {@code @Nullable}. In reality neither the
   * array nor its contents can be null, but it is not useful to force the use of {@code
   * requireNonNull} or the like just to assert that.
   *
   * <p>{@code EqualsTester} will always check that every object it is given returns false from
   * {@code equals(null)}, so it is neither useful nor allowed to include a null value in any
   * equality group.
   */
  @CanIgnoreReturnValue
  public EqualsTester addEqualityGroup(@Nullable Object @Nullable ... equalityGroup) {
    checkNotNull(equalityGroup);
    List<Object> list = new ArrayList<>(equalityGroup.length);
    for (int i = 0; i < equalityGroup.length; i++) {
      Object element = equalityGroup[i];
      if (element == null) {
        throw new NullPointerException("at index " + i);
      }
      list.add(element);
    }
    equalityGroups.add(list);
    return this;
  }

  /** Run tests on equals method, throwing a failure on an invalid test */
  @CanIgnoreReturnValue
  public EqualsTester testEquals() {
    RelationshipTester<Object> delegate =
        new RelationshipTester<>(
            Equivalence.equals(), "Object#equals", "Object#hashCode", itemReporter);
    for (List<Object> group : equalityGroups) {
      delegate.addRelatedGroup(group);
    }
    for (int run = 0; run < REPETITIONS; run++) {
      testItems();
      delegate.test();
    }
    return this;
  }

  private void testItems() {
    for (Object item : Iterables.concat(equalityGroups)) {
      assertTrue(item + " must not be Object#equals to null", !item.equals(null));
      assertTrue(
          item + " must not be Object#equals to an arbitrary object of another class",
          !item.equals(NotAnInstance.EQUAL_TO_NOTHING));
      assertTrue(item + " must be Object#equals to itself", item.equals(item));
      assertEquals(
          "the Object#hashCode of " + item + " must be consistent",
          item.hashCode(),
          item.hashCode());
      if (!(item instanceof String)) {
        assertTrue(
            item + " must not be Object#equals to its Object#toString representation",
            !item.equals(item.toString()));
      }
    }
  }

  /**
   * Class used to test whether equals() correctly handles an instance of an incompatible class.
   * Since it is a private inner class, the invoker can never pass in an instance to the tester
   */
  private enum NotAnInstance {
    EQUAL_TO_NOTHING;
  }
}