1/*
2 * Copyright (C) 2007 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
17package com.google.common.testing;
18
19import com.google.common.annotations.GwtCompatible;
20import com.google.common.base.Preconditions;
21import com.google.common.collect.ImmutableList;
22import com.google.common.collect.Sets;
23
24import junit.framework.AssertionFailedError;
25import junit.framework.TestCase;
26
27import java.util.Set;
28
29/**
30 * Unit tests for {@link EqualsTester}.
31 *
32 * @author Jim McMaster
33 */
34@GwtCompatible
35public class EqualsTesterTest extends TestCase {
36  private ValidTestObject reference;
37  private EqualsTester equalsTester;
38  private ValidTestObject equalObject1;
39  private ValidTestObject equalObject2;
40  private ValidTestObject notEqualObject1;
41
42  @Override
43  public void setUp() throws Exception {
44    super.setUp();
45    reference = new ValidTestObject(1, 2);
46    equalsTester = new EqualsTester();
47    equalObject1 = new ValidTestObject(1, 2);
48    equalObject2 = new ValidTestObject(1, 2);
49    notEqualObject1 = new ValidTestObject(0, 2);
50  }
51
52  /**
53   * Test null reference yields error
54   */
55  public void testAddNullReference() {
56    try {
57      equalsTester.addEqualityGroup((Object) null);
58      fail("Should fail on null reference");
59    } catch (NullPointerException e) {}
60  }
61
62  /**
63   * Test equalObjects after adding multiple instances at once with a null
64   */
65  public void testAddTwoEqualObjectsAtOnceWithNull() {
66    try {
67      equalsTester.addEqualityGroup(reference, equalObject1, null);
68      fail("Should fail on null equal object");
69    } catch (NullPointerException e) {}
70  }
71
72  /**
73   * Test adding null equal object yields error
74   */
75  public void testAddNullEqualObject() {
76    try {
77      equalsTester.addEqualityGroup(reference, (Object[]) null);
78      fail("Should fail on null equal object");
79    } catch (NullPointerException e) {}
80  }
81
82  /**
83   * Test adding objects only by addEqualityGroup, with no reference object
84   * specified in the constructor.
85   */
86  public void testAddEqualObjectWithOArgConstructor() {
87    equalsTester.addEqualityGroup(equalObject1, notEqualObject1);
88    try {
89      equalsTester.testEquals();
90    } catch (AssertionFailedError e) {
91      assertErrorMessage(
92        e,
93        equalObject1 + " [group 1, item 1] must be Object#equals to "
94            + notEqualObject1 + " [group 1, item 2]");
95      return;
96    }
97    fail("Should get not equal to equal object error");
98  }
99
100  /**
101   * Test EqualsTester with no equals or not equals objects.  This checks
102   * proper handling of null, incompatible class and reflexive tests
103   */
104  public void testTestEqualsEmptyLists() {
105    equalsTester.addEqualityGroup(reference);
106    equalsTester.testEquals();
107  }
108
109  /**
110   * Test EqualsTester after populating equalObjects.  This checks proper
111   * handling of equality and verifies hashCode for valid objects
112   */
113  public void testTestEqualsEqualsObjects() {
114    equalsTester.addEqualityGroup(reference, equalObject1, equalObject2);
115    equalsTester.testEquals();
116  }
117
118  /**
119   * Test proper handling of case where an object is not equal to itself
120   */
121  public void testNonreflexiveEquals() {
122    Object obj = new NonReflexiveObject();
123    equalsTester.addEqualityGroup(obj);
124    try {
125      equalsTester.testEquals();
126    } catch (AssertionFailedError e) {
127      assertErrorMessage(
128          e, obj + " must be Object#equals to itself");
129      return;
130    }
131    fail("Should get non-reflexive error");
132  }
133
134  /**
135   * Test proper handling where an object tests equal to null
136   */
137  public void testInvalidEqualsNull() {
138    Object obj = new InvalidEqualsNullObject();
139    equalsTester.addEqualityGroup(obj);
140    try {
141      equalsTester.testEquals();
142    } catch (AssertionFailedError e) {
143      assertErrorMessage(
144          e, obj + " must not be Object#equals to null");
145      return;
146    }
147    fail("Should get equal to null error");
148  }
149
150  /**
151   * Test proper handling where an object incorrectly tests for an
152   * incompatible class
153   */
154  public void testInvalidEqualsIncompatibleClass() {
155    Object obj = new InvalidEqualsIncompatibleClassObject();
156    equalsTester.addEqualityGroup(obj);
157    try {
158      equalsTester.testEquals();
159    } catch (AssertionFailedError e) {
160      assertErrorMessage(
161          e,
162          obj
163          + " must not be Object#equals to an arbitrary object of another class");
164      return;
165    }
166    fail("Should get equal to incompatible class error");
167  }
168
169  /**
170   * Test proper handling where an object is not equal to one the user has
171   * said should be equal
172   */
173  public void testInvalidNotEqualsEqualObject() {
174    equalsTester.addEqualityGroup(reference, notEqualObject1);
175    try {
176      equalsTester.testEquals();
177    } catch (AssertionFailedError e) {
178      assertErrorMessage(e, reference + " [group 1, item 1]");
179      assertErrorMessage(e, notEqualObject1 + " [group 1, item 2]");
180      return;
181    }
182    fail("Should get not equal to equal object error");
183  }
184
185  /**
186   * Test for an invalid hashCode method, i.e., one that returns different
187   * value for objects that are equal according to the equals method
188   */
189  public void testInvalidHashCode() {
190    Object a = new InvalidHashCodeObject(1, 2);
191    Object b = new InvalidHashCodeObject(1, 2);
192    equalsTester.addEqualityGroup(a, b);
193    try {
194      equalsTester.testEquals();
195    } catch (AssertionFailedError e) {
196      assertErrorMessage(
197          e, "the Object#hashCode (" + a.hashCode() + ") of " + a
198          + " [group 1, item 1] must be equal to the Object#hashCode ("
199          + b.hashCode() + ") of " + b);
200      return;
201    }
202    fail("Should get invalid hashCode error");
203  }
204
205  public void testNullEqualityGroup() {
206    EqualsTester tester = new EqualsTester();
207    try {
208      tester.addEqualityGroup((Object[]) null);
209      fail();
210    } catch (NullPointerException e) {}
211  }
212
213  public void testNullObjectInEqualityGroup() {
214    EqualsTester tester = new EqualsTester();
215    try {
216      tester.addEqualityGroup(1, null, 3);
217      fail();
218    } catch (NullPointerException e) {
219      assertErrorMessage(e, "at index 1");
220    }
221  }
222
223  public void testSymmetryBroken() {
224    EqualsTester tester = new EqualsTester()
225        .addEqualityGroup(named("foo").addPeers("bar"), named("bar"));
226    try {
227      tester.testEquals();
228    } catch (AssertionFailedError e) {
229      assertErrorMessage(
230          e,
231          "bar [group 1, item 2] must be Object#equals to foo [group 1, item 1]");
232      return;
233    }
234    fail("should failed because symmetry is broken");
235  }
236
237  public void testTransitivityBrokenInEqualityGroup() {
238    EqualsTester tester = new EqualsTester()
239        .addEqualityGroup(
240            named("foo").addPeers("bar", "baz"),
241            named("bar").addPeers("foo"),
242            named("baz").addPeers("foo"));
243    try {
244      tester.testEquals();
245    } catch (AssertionFailedError e) {
246      assertErrorMessage(
247          e,
248          "bar [group 1, item 2] must be Object#equals to baz [group 1, item 3]");
249      return;
250    }
251    fail("should failed because transitivity is broken");
252  }
253
254  public void testUnequalObjectsInEqualityGroup() {
255    EqualsTester tester = new EqualsTester()
256        .addEqualityGroup(named("foo"), named("bar"));
257    try {
258      tester.testEquals();
259    } catch (AssertionFailedError e) {
260      assertErrorMessage(
261          e,
262          "foo [group 1, item 1] must be Object#equals to bar [group 1, item 2]");
263      return;
264    }
265    fail("should failed because of unequal objects in the same equality group");
266  }
267
268  public void testTransitivityBrokenAcrossEqualityGroups() {
269    EqualsTester tester = new EqualsTester()
270        .addEqualityGroup(
271            named("foo").addPeers("bar"),
272            named("bar").addPeers("foo", "x"))
273        .addEqualityGroup(
274            named("baz").addPeers("x"),
275            named("x").addPeers("baz", "bar"));
276    try {
277      tester.testEquals();
278    } catch (AssertionFailedError e) {
279      assertErrorMessage(
280          e,
281          "bar [group 1, item 2] must not be Object#equals to x [group 2, item 2]");
282      return;
283    }
284    fail("should failed because transitivity is broken");
285  }
286
287  public void testEqualityGroups() {
288    new EqualsTester()
289        .addEqualityGroup(
290            named("foo").addPeers("bar"), named("bar").addPeers("foo"))
291        .addEqualityGroup(named("baz"), named("baz"))
292        .testEquals();
293  }
294
295  private static void assertErrorMessage(Throwable e, String message) {
296    // TODO(kevinb): use a Truth assertion here
297    if (!e.getMessage().contains(message)) {
298      fail("expected <" + e.getMessage() + "> to contain <" + message + ">");
299    }
300  }
301
302  /**
303   * Test class with valid equals and hashCode methods.  Testers created
304   * with instances of this class should always pass.
305   */
306  private static class ValidTestObject {
307    private int aspect1;
308    private int aspect2;
309
310    ValidTestObject(int aspect1, int aspect2) {
311      this.aspect1 = aspect1;
312      this.aspect2 = aspect2;
313    }
314
315    @Override public boolean equals(Object o) {
316      if (!(o instanceof ValidTestObject)) {
317        return false;
318      }
319      ValidTestObject other = (ValidTestObject) o;
320      if (aspect1 != other.aspect1) {
321        return false;
322      }
323      if (aspect2 != other.aspect2) {
324        return false;
325      }
326      return true;
327    }
328
329    @Override public int hashCode() {
330      int result = 17;
331      result = 37 * result + aspect1;
332      result = 37 * result + aspect2;
333      return result;
334    }
335  }
336
337  /** Test class with invalid hashCode method. */
338  private static class InvalidHashCodeObject {
339    private int aspect1;
340    private int aspect2;
341
342    InvalidHashCodeObject(int aspect1, int aspect2) {
343      this.aspect1 = aspect1;
344      this.aspect2 = aspect2;
345    }
346
347    @Override public boolean equals(Object o) {
348      if (!(o instanceof InvalidHashCodeObject)) {
349        return false;
350      }
351      InvalidHashCodeObject other = (InvalidHashCodeObject) o;
352      if (aspect1 != other.aspect1) {
353        return false;
354      }
355      if (aspect2 != other.aspect2) {
356        return false;
357      }
358      return true;
359    }
360  }
361
362  /** Test class that violates reflexitivity.  It is not equal to itself */
363  private static class NonReflexiveObject {
364
365    @Override public boolean equals(Object o) {
366      return false;
367    }
368
369    @Override public int hashCode() {
370      return super.hashCode();
371    }
372  }
373
374  /** Test class that returns true if the test object is null */
375  private static class InvalidEqualsNullObject {
376
377    @Override public boolean equals(Object o) {
378      return o == this || o == null;
379    }
380
381    @Override public int hashCode() {
382      return 0;
383    }
384  }
385
386  /**
387   * Test class that returns true even if the test object is of the wrong class
388   */
389  private static class InvalidEqualsIncompatibleClassObject {
390
391    @Override public boolean equals(Object o) {
392      if (o == null) {
393        return false;
394      }
395      return true;
396    }
397
398    @Override public int hashCode() {
399      return 0;
400    }
401  }
402
403  private static NamedObject named(String name) {
404    return new NamedObject(name);
405  }
406
407  private static class NamedObject {
408    private final Set<String> peerNames = Sets.newHashSet();
409
410    private final String name;
411
412    NamedObject(String name) {
413      this.name = Preconditions.checkNotNull(name);
414    }
415
416    NamedObject addPeers(String... names) {
417      peerNames.addAll(ImmutableList.copyOf(names));
418      return this;
419    }
420
421    @Override public boolean equals(Object obj) {
422      if (obj instanceof NamedObject) {
423        NamedObject that = (NamedObject) obj;
424        return name.equals(that.name) || peerNames.contains(that.name);
425      }
426      return false;
427    }
428
429    @Override public int hashCode() {
430      return 0;
431    }
432
433    @Override public String toString() {
434      return name;
435    }
436  }
437}
438