Test SparseArray contentEquals and contentHashCode

Bug: 181100924

Test: atest android.util.cts.SparseArrayTest

Change-Id: Ib141cf32b8808b8cbcebacb18e7aca3a2b9027ad
diff --git a/tests/tests/util/src/android/util/cts/SparseArrayTest.java b/tests/tests/util/src/android/util/cts/SparseArrayTest.java
index 28df0cf..ce4382b 100644
--- a/tests/tests/util/src/android/util/cts/SparseArrayTest.java
+++ b/tests/tests/util/src/android/util/cts/SparseArrayTest.java
@@ -18,9 +18,11 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import android.annotation.NonNull;
 import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
@@ -29,6 +31,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Objects;
+import java.util.function.BiFunction;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class SparseArrayTest {
@@ -189,4 +194,206 @@
         assertEquals(20L, sparseArray.valueAt(2).longValue());
         assertEquals(Long.MIN_VALUE, sparseArray.valueAt(3).longValue());
     }
+
+    @Test
+    public void testContentEquals() {
+        SparseArray<TestData> first = new SparseArray<>();
+        first.put(0, new TestData("0"));
+        first.put(1, new TestData("1"));
+        first.put(2, new TestData("2"));
+
+        SparseArray<SubTestData> second = new SparseArray<>();
+        second.put(2, new SubTestData("2"));
+        second.put(0, new SubTestData("0"));
+        second.put(1, new SubTestData("1"));
+
+        // Subclass succeeds
+        testContentEquals(first, second, SparseArray::contentEquals);
+
+        SparseArray<TestData2> noMatchParent = new SparseArray<>();
+        noMatchParent.put(2, new TestData2("2"));
+        noMatchParent.put(0, new TestData2("0"));
+        noMatchParent.put(1, new TestData2("1"));
+
+        // Non-matching parent class fails (as implemented in equals instanceof check)
+        testContentNotEquals(first, noMatchParent, SparseArray::contentEquals);
+
+        SparseArray<TestDataCustomEquals> customEqualsOne = new SparseArray<>();
+        customEqualsOne.put(0, new TestDataCustomEquals("0"));
+        customEqualsOne.put(1, new TestDataCustomEquals("1"));
+        customEqualsOne.put(2, new TestDataCustomEquals("2"));
+
+        SparseArray<TestDataCustomEquals2> customEqualsTwo = new SparseArray<>();
+        customEqualsTwo.put(2, new TestDataCustomEquals2("2"));
+        customEqualsTwo.put(0, new TestDataCustomEquals2("0"));
+        customEqualsTwo.put(1, new TestDataCustomEquals2("1"));
+
+        // Non-matching parent class succeeds (as implemented in custom equals check)
+        testContentEquals(customEqualsOne, customEqualsTwo, SparseArray::contentEquals);
+
+        // Null fails
+        assertFalse(first.contentEquals(null));
+    }
+
+    private <T> void testContentEquals(@NonNull SparseArray<?> first,
+            @NonNull SparseArray<T> second,
+            BiFunction<SparseArray<?>, SparseArray<?>, Boolean> block) {
+        // Assert mirrored equality
+        assertTrue(block.apply(first, second));
+        assertTrue(block.apply(second, first));
+
+        //noinspection unchecked
+        second.put(1, (T) first.valueAt(2));
+
+        // Non-matching data at index 1 fails
+        assertFalse(first.contentEquals(second));
+        assertFalse(second.contentEquals(first));
+
+        // Assert failure of normal Objects.equals maintained
+        assertNotEquals(first, second);
+        assertNotEquals(second, first);
+    }
+
+    private <T> void testContentNotEquals(@NonNull SparseArray<?> first,
+            @NonNull SparseArray<T> second,
+            BiFunction<SparseArray<?>, SparseArray<?>, Boolean> block) {
+        // Assert mirrored equality
+        assertFalse(block.apply(first, second));
+        assertFalse(block.apply(second, first));
+        assertFalse(second.contentEquals(first));
+
+        // Assert failure of normal Objects.equals maintained
+        assertNotEquals(first, second);
+        assertNotEquals(second, first);
+    }
+
+    @Test
+    public void testContentHashCode() {
+        SparseArray<TestData> first = new SparseArray<>();
+        first.put(0, new TestData("0"));
+        first.put(1, new TestData("1"));
+        first.put(2, new TestData("2"));
+
+        SparseArray<TestData2> second = new SparseArray<>();
+        second.put(2, new TestData2("2"));
+        second.put(0, new TestData2("0"));
+        second.put(1, new TestData2("1"));
+
+        // Non-equal classes that evaluate to the same hash code passes
+        assertEquals(first.contentHashCode(), second.contentHashCode());
+
+        // Assert failure of normal Objects.hashCode maintained
+        assertNotEquals(first.hashCode(), second.hashCode());
+
+        second.put(1, new TestData2("2"));
+
+        // Non-matching data at index 1 fails
+        assertNotEquals(first.contentHashCode(), second.contentHashCode());
+
+        // Assert failure of normal Objects.hashCode maintained
+        assertNotEquals(first.hashCode(), second.hashCode());
+    }
+
+    private static class TestData {
+
+        private final String data;
+
+        private TestData(@NonNull String data) {
+            this.data = data;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof TestData)) return false;
+            TestData testData = (TestData) o;
+            return Objects.equals(data, testData.data);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(data);
+        }
+    }
+
+    private static class TestData2 {
+
+        private final String data;
+
+        private TestData2(@NonNull String data) {
+            this.data = data;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof TestData2)) return false;
+            TestData2 testData2 = (TestData2) o;
+            return Objects.equals(data, testData2.data);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(data);
+        }
+    }
+
+    private static class TestDataCustomEquals {
+
+        private final String data;
+
+        private TestDataCustomEquals(@NonNull String data) {
+            this.data = data;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o instanceof TestDataCustomEquals) {
+                return Objects.equals(data, ((TestDataCustomEquals) o).data);
+            } else if (o instanceof TestDataCustomEquals2) {
+                return Objects.equals(data, ((TestDataCustomEquals2) o).data);
+            } else {
+                return false;
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(data);
+        }
+    }
+
+    private static class TestDataCustomEquals2 {
+
+        private final String data;
+
+        private TestDataCustomEquals2(@NonNull String data) {
+            this.data = data;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o instanceof TestDataCustomEquals) {
+                return Objects.equals(data, ((TestDataCustomEquals) o).data);
+            } else if (o instanceof TestDataCustomEquals2) {
+                return Objects.equals(data, ((TestDataCustomEquals2) o).data);
+            } else {
+                return false;
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(data);
+        }
+    }
+
+    private static class SubTestData extends TestData {
+
+        private SubTestData(@NonNull String data) {
+            super(data);
+        }
+    }
 }