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