• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
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 android.util.cts;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertSame;
22 import static org.junit.Assert.assertTrue;
23 import static org.junit.Assert.fail;
24 
25 import android.os.Bundle;
26 import android.os.Parcel;
27 import android.os.Parcelable;
28 import android.util.ArrayMap;
29 import android.util.Log;
30 
31 import androidx.test.filters.SmallTest;
32 import androidx.test.runner.AndroidJUnit4;
33 
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 
37 import java.lang.reflect.InvocationTargetException;
38 import java.lang.reflect.Method;
39 import java.util.AbstractMap;
40 import java.util.Arrays;
41 import java.util.Collection;
42 import java.util.Collections;
43 import java.util.HashMap;
44 import java.util.HashSet;
45 import java.util.Iterator;
46 import java.util.Map;
47 import java.util.NoSuchElementException;
48 import java.util.Set;
49 import java.util.function.BiFunction;
50 
51 @SmallTest
52 @RunWith(AndroidJUnit4.class)
53 public class ArrayMapTest {
54     static final boolean DEBUG = false;
55 
56     static final int OP_ADD = 1;
57     static final int OP_REM = 2;
58 
59     static int[] OPS = new int[] {
60             OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
61             OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
62             OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
63             OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
64 
65             OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
66             OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
67 
68             OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
69             OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
70 
71             OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
72             OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
73 
74             OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
75             OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
76             OP_ADD, OP_ADD, OP_ADD,
77             OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
78             OP_REM, OP_REM, OP_REM,
79             OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
80     };
81 
82     static int[] KEYS = new int[] {
83             // General adding and removing.
84               -1,   1900,    600,    200,   1200,   1500,   1800,    100,   1900,
85             2100,    300,    800,    600,   1100,   1300,   2000,   1000,   1400,
86              600,     -1,   1900,    600,    300,   2100,    200,    800,    800,
87             1800,   1500,   1300,   1100,   2000,   1400,   1000,   1200,   1900,
88 
89             // Shrink when removing item from end.
90              100,    200,    300,    400,    500,    600,    700,    800,    900,
91              900,    800,    700,    600,    500,    400,    300,    200,    100,
92 
93             // Shrink when removing item from middle.
94              100,    200,    300,    400,    500,    600,    700,    800,    900,
95              900,    800,    700,    600,    500,    400,    200,    300,    100,
96 
97             // Shrink when removing item from front.
98              100,    200,    300,    400,    500,    600,    700,    800,    900,
99              900,    800,    700,    600,    500,    400,    100,    200,    300,
100 
101             // Test hash collisions.
102              105,    106,    108,    104,    102,    102,    107,      5,    205,
103                4,    202,    203,      3,      5,    101,    109,    200,    201,
104                0,     -1,    100,
105              106,    108,    104,    102,    103,    105,    107,    101,    109,
106               -1,    100,      0,
107                4,      5,      3,      5,    200,    203,    202,    201,    205,
108     };
109 
110     public static class ControlledHash implements Parcelable {
111         final int mValue;
112 
ControlledHash(int value)113         ControlledHash(int value) {
114             mValue = value;
115         }
116 
117         @Override
equals(Object o)118         public final boolean equals(Object o) {
119             if (o == null) {
120                 return false;
121             }
122             return mValue == ((ControlledHash)o).mValue;
123         }
124 
125         @Override
hashCode()126         public final int hashCode() {
127             return mValue/100;
128         }
129 
130         @Override
toString()131         public final String toString() {
132             return Integer.toString(mValue);
133         }
134 
135         @Override
describeContents()136         public int describeContents() {
137             return 0;
138         }
139 
140         @Override
writeToParcel(Parcel dest, int flags)141         public void writeToParcel(Parcel dest, int flags) {
142             dest.writeInt(mValue);
143         }
144 
145         public static final Parcelable.Creator<ControlledHash> CREATOR
146                 = new Parcelable.Creator<ControlledHash>() {
147             public ControlledHash createFromParcel(Parcel in) {
148                 return new ControlledHash(in.readInt());
149             }
150 
151             public ControlledHash[] newArray(int size) {
152                 return new ControlledHash[size];
153             }
154         };
155     }
156 
compare(Object v1, Object v2)157     private static boolean compare(Object v1, Object v2) {
158         if (v1 == null) {
159             return v2 == null;
160         }
161         if (v2 == null) {
162             return false;
163         }
164         return v1.equals(v2);
165     }
166 
compareMaps(HashMap map, ArrayMap array)167     private static void compareMaps(HashMap map, ArrayMap array) {
168         if (map.size() != array.size()) {
169             fail("Bad size: expected " + map.size() + ", got " + array.size());
170         }
171 
172         Set<Map.Entry> mapSet = map.entrySet();
173         for (Map.Entry entry : mapSet) {
174             Object expValue = entry.getValue();
175             Object gotValue = array.get(entry.getKey());
176             if (!compare(expValue, gotValue)) {
177                 fail("Bad value: expected " + expValue + ", got " + gotValue
178                         + " at key " + entry.getKey());
179             }
180         }
181 
182         for (int i=0; i<array.size(); i++) {
183             Object gotValue = array.valueAt(i);
184             Object key = array.keyAt(i);
185             Object expValue = map.get(key);
186             if (!compare(expValue, gotValue)) {
187                 fail("Bad value: expected " + expValue + ", got " + gotValue
188                         + " at key " + key);
189             }
190         }
191 
192         if (map.entrySet().hashCode() != array.entrySet().hashCode()) {
193             fail("Entry set hash codes differ: map=0x"
194                     + Integer.toHexString(map.entrySet().hashCode()) + " array=0x"
195                     + Integer.toHexString(array.entrySet().hashCode()));
196         }
197 
198         if (!map.entrySet().equals(array.entrySet())) {
199             fail("Failed calling equals on map entry set against array set");
200         }
201 
202         if (!array.entrySet().equals(map.entrySet())) {
203             fail("Failed calling equals on array entry set against map set");
204         }
205 
206         if (map.keySet().hashCode() != array.keySet().hashCode()) {
207             fail("Key set hash codes differ: map=0x"
208                     + Integer.toHexString(map.keySet().hashCode()) + " array=0x"
209                     + Integer.toHexString(array.keySet().hashCode()));
210         }
211 
212         if (!map.keySet().equals(array.keySet())) {
213             fail("Failed calling equals on map key set against array set");
214         }
215 
216         if (!array.keySet().equals(map.keySet())) {
217             fail("Failed calling equals on array key set against map set");
218         }
219 
220         if (!map.keySet().containsAll(array.keySet())) {
221             fail("Failed map key set contains all of array key set");
222         }
223 
224         if (!array.keySet().containsAll(map.keySet())) {
225             fail("Failed array key set contains all of map key set");
226         }
227 
228         if (!array.containsAll(map.keySet())) {
229             fail("Failed array contains all of map key set");
230         }
231 
232         if (!map.entrySet().containsAll(array.entrySet())) {
233             fail("Failed map entry set contains all of array entry set");
234         }
235 
236         if (!array.entrySet().containsAll(map.entrySet())) {
237             fail("Failed array entry set contains all of map entry set");
238         }
239     }
240 
validateArrayMap(ArrayMap array)241     private static void validateArrayMap(ArrayMap array) {
242         Set<Map.Entry> entrySet = array.entrySet();
243         int index=0;
244         Iterator<Map.Entry> entryIt = entrySet.iterator();
245         while (entryIt.hasNext()) {
246             Map.Entry entry = entryIt.next();
247             Object value = entry.getKey();
248             Object realValue = array.keyAt(index);
249             if (!compare(realValue, value)) {
250                 fail("Bad array map entry set: expected key " + realValue
251                         + ", got " + value + " at index " + index);
252             }
253             value = entry.getValue();
254             realValue = array.valueAt(index);
255             if (!compare(realValue, value)) {
256                 fail("Bad array map entry set: expected value " + realValue
257                         + ", got " + value + " at index " + index);
258             }
259             index++;
260         }
261 
262         index = 0;
263         Set keySet = array.keySet();
264         Iterator keyIt = keySet.iterator();
265         while (keyIt.hasNext()) {
266             Object value = keyIt.next();
267             Object realValue = array.keyAt(index);
268             if (!compare(realValue, value)) {
269                 fail("Bad array map key set: expected key " + realValue
270                         + ", got " + value + " at index " + index);
271             }
272             index++;
273         }
274 
275         index = 0;
276         Collection valueCol = array.values();
277         Iterator valueIt = valueCol.iterator();
278         while (valueIt.hasNext()) {
279             Object value = valueIt.next();
280             Object realValue = array.valueAt(index);
281             if (!compare(realValue, value)) {
282                 fail("Bad array map value col: expected value " + realValue
283                         + ", got " + value + " at index " + index);
284             }
285             index++;
286         }
287     }
288 
compareBundles(Bundle bundle1, Bundle bundle2)289     private static void compareBundles(Bundle bundle1, Bundle bundle2) {
290         Set<String> keySet1 = bundle1.keySet();
291         Iterator<String> iterator1 = keySet1.iterator();
292         while (iterator1.hasNext()) {
293             String key = iterator1.next();
294             int value1 = bundle1.getInt(key);
295             if (bundle2.get(key) == null) {
296                 fail("Bad Bundle: bundle2 didn't have expected key " + key);
297             }
298             int value2 = bundle2.getInt(key);
299             if (value1 != value2) {
300                 fail("Bad Bundle: at key key " + key + " expected " + value1 + ", got " + value2);
301             }
302         }
303         Set<String> keySet2 = bundle2.keySet();
304         Iterator<String> iterator2 = keySet2.iterator();
305         while (iterator2.hasNext()) {
306             String key = iterator2.next();
307             if (bundle1.get(key) == null) {
308                 fail("Bad Bundle: bundle1 didn't have expected key " + key);
309             }
310             int value1 = bundle1.getInt(key);
311             int value2 = bundle2.getInt(key);
312             if (value1 != value2) {
313                 fail("Bad Bundle: at key key " + key + " expected " + value1 + ", got " + value2);
314             }
315         }
316     }
317 
dump(Map map, ArrayMap array)318     private static void dump(Map map, ArrayMap array) {
319         Log.e("test", "HashMap of " + map.size() + " entries:");
320         Set<Map.Entry> mapSet = map.entrySet();
321         for (Map.Entry entry : mapSet) {
322             Log.e("test", "    " + entry.getKey() + " -> " + entry.getValue());
323         }
324         Log.e("test", "ArrayMap of " + array.size() + " entries:");
325         for (int i=0; i<array.size(); i++) {
326             Log.e("test", "    " + array.keyAt(i) + " -> " + array.valueAt(i));
327         }
328     }
329 
dump(ArrayMap map1, ArrayMap map2)330     private static void dump(ArrayMap map1, ArrayMap map2) {
331         Log.e("test", "ArrayMap of " + map1.size() + " entries:");
332         for (int i=0; i<map1.size(); i++) {
333             Log.e("test", "    " + map1.keyAt(i) + " -> " + map1.valueAt(i));
334         }
335         Log.e("test", "ArrayMap of " + map2.size() + " entries:");
336         for (int i=0; i<map2.size(); i++) {
337             Log.e("test", "    " + map2.keyAt(i) + " -> " + map2.valueAt(i));
338         }
339     }
340 
dump(Bundle bundle1, Bundle bundle2)341     private static void dump(Bundle bundle1, Bundle bundle2) {
342         Log.e("test", "First Bundle of " + bundle1.size() + " entries:");
343         Set<String> keys1 = bundle1.keySet();
344         for (String key : keys1) {
345             Log.e("test", "    " + key + " -> " + bundle1.get(key));
346         }
347         Log.e("test", "Second Bundle of " + bundle2.size() + " entries:");
348         Set<String> keys2 = bundle2.keySet();
349         for (String key : keys2) {
350             Log.e("test", "    " + key + " -> " + bundle2.get(key));
351         }
352     }
353 
354     @Test
testBasicArrayMap()355     public void testBasicArrayMap() {
356         HashMap<ControlledHash, Integer> hashMap = new HashMap<>();
357         ArrayMap<ControlledHash, Integer> arrayMap = new ArrayMap<>();
358         Bundle bundle = new Bundle();
359 
360         for (int i=0; i<OPS.length; i++) {
361             Integer oldHash;
362             Integer oldArray;
363             ControlledHash key = KEYS[i] < 0 ? null : new ControlledHash(KEYS[i]);
364             String strKey = KEYS[i] < 0 ? null : Integer.toString(KEYS[i]);
365             switch (OPS[i]) {
366                 case OP_ADD:
367                     if (DEBUG) Log.i("test", "Adding key: " + key);
368                     oldHash = hashMap.put(key, i);
369                     oldArray = arrayMap.put(key, i);
370                     bundle.putInt(strKey, i);
371                     break;
372                 case OP_REM:
373                     if (DEBUG) Log.i("test", "Removing key: " + key);
374                     oldHash = hashMap.remove(key);
375                     oldArray = arrayMap.remove(key);
376                     bundle.remove(strKey);
377                     break;
378                 default:
379                     fail("Bad operation " + OPS[i] + " @ " + i);
380                     return;
381             }
382             if (!compare(oldHash, oldArray)) {
383                 String msg = "Bad result: expected " + oldHash + ", got " + oldArray;
384                 Log.e("test", msg);
385                 dump(hashMap, arrayMap);
386                 fail(msg);
387             }
388             try {
389                 validateArrayMap(arrayMap);
390             } catch (Throwable e) {
391                 Log.e("test", e.getMessage());
392                 dump(hashMap, arrayMap);
393                 throw e;
394             }
395             try {
396                 compareMaps(hashMap, arrayMap);
397             } catch (Throwable e) {
398                 Log.e("test", e.getMessage());
399                 dump(hashMap, arrayMap);
400                 throw e;
401             }
402             Parcel parcel = Parcel.obtain();
403             bundle.writeToParcel(parcel, 0);
404             parcel.setDataPosition(0);
405             Bundle bundle2 = parcel.readBundle();
406             try {
407                 compareBundles(bundle, bundle2);
408             } catch (Throwable e) {
409                 Log.e("test", e.getMessage());
410                 dump(bundle, bundle2);
411                 throw e;
412             }
413         }
414 
415         arrayMap.put(new ControlledHash(50000), 100);
416         ControlledHash lookup = new ControlledHash(50000);
417         Iterator<ControlledHash> it = arrayMap.keySet().iterator();
418         while (it.hasNext()) {
419             if (it.next().equals(lookup)) {
420                 it.remove();
421             }
422         }
423         if (arrayMap.containsKey(lookup)) {
424             String msg = "Bad map iterator: didn't remove test key";
425             Log.e("test", msg);
426             dump(hashMap, arrayMap);
427             fail(msg);
428         }
429     }
430 
431     @Test
432     public void testCopyArrayMap() {
433         // map copy constructor test
434         ArrayMap newMap = new ArrayMap<Integer, String>();
435         for (int i = 0; i < 10; ++i) {
436             newMap.put(i, String.valueOf(i));
437         }
438         ArrayMap mapCopy = new ArrayMap(newMap);
439         if (!compare(mapCopy, newMap)) {
440             String msg = "ArrayMap copy constructor failure: expected " +
441                     newMap + ", got " + mapCopy;
442             Log.e("test", msg);
443             dump(newMap, mapCopy);
444             fail(msg);
445             return;
446         }
447     }
448 
449     @Test
450     public void testEqualsArrayMap() {
451         ArrayMap<Integer, String> map1 = new ArrayMap<>();
452         ArrayMap<Integer, String> map2 = new ArrayMap<>();
453         HashMap<Integer, String> map3 = new HashMap<>();
454         if (!compare(map1, map2) || !compare(map1, map3) || !compare(map3, map2)) {
455             fail("ArrayMap equals failure for empty maps " + map1 + ", " +
456                     map2 + ", " + map3);
457         }
458 
459         for (int i = 0; i < 10; ++i) {
460             String value = String.valueOf(i);
461             map1.put(i, value);
462             map2.put(i, value);
463             map3.put(i, value);
464         }
465         if (!compare(map1, map2) || !compare(map1, map3) || !compare(map3, map2)) {
466             fail("ArrayMap equals failure for populated maps " + map1 + ", " +
467                     map2 + ", " + map3);
468         }
469 
470         map1.remove(0);
471         if (compare(map1, map2) || compare(map1, map3) || compare(map3, map1)) {
472             fail("ArrayMap equals failure for map size " + map1 + ", " +
473                     map2 + ", " + map3);
474         }
475 
476         map1.put(0, "-1");
477         if (compare(map1, map2) || compare(map1, map3) || compare(map3, map1)) {
478             fail("ArrayMap equals failure for map contents " + map1 + ", " +
479                     map2 + ", " + map3);
480         }
481     }
482 
483     /**
484      * Test creating a malformed array map with duplicated keys and that we will catch this
485      * when unparcelling.
486      *
487      * TODO: remove use of private APIs, see b/384599385
488     @Test
489     public void testDuplicateKeys() throws NoSuchMethodException,
490             InvocationTargetException, IllegalAccessException, NoSuchFieldException {
491         ArrayMap<String, Object> map1 = new ArrayMap(2);
492 
493         Method appendMethod = ArrayMap.class.getMethod("append", Object.class, Object.class);
494         appendMethod.invoke(map1, Integer.toString(100000), "foo");
495         appendMethod.invoke(map1, Integer.toString(100000), "bar");
496 
497         // Now parcel/unparcel, and verify we get the expected error.
498         Parcel parcel = Parcel.obtain();
499         Method writeArrayMapMethod = Parcel.class.getMethod("writeArrayMap", ArrayMap.class);
500         writeArrayMapMethod.invoke(parcel, map1);
501         parcel.setDataPosition(0);
502         ArrayMap<String, Object> map2 = new ArrayMap(2);
503 
504         try {
505             Parcel.class.getMethod("readArrayMap", ArrayMap.class, ClassLoader.class).invoke(
506                     parcel, map2, null);
507         } catch (InvocationTargetException e) {
508             Throwable cause = e.getCause();
509             if (cause instanceof IllegalArgumentException) {
510                 // Good!
511                 return;
512             }
513             throw e;
514         }
515 
516         String msg = "Didn't throw expected IllegalArgumentException";
517         Log.e("test", msg);
518         dump(map1, map2);
519         fail(msg);
520     }
521     */
522 
523     private static void checkEntrySetToArray(ArrayMap<?, ?> testMap) {
524         try {
525             testMap.entrySet().toArray();
526             fail();
527         } catch (UnsupportedOperationException expected) {}
528 
529         try {
530             Map.Entry<?, ?>[] entries = new Map.Entry[20];
531             testMap.entrySet().toArray(entries);
532             fail();
533         } catch (UnsupportedOperationException expected) {}
534     }
535 
536     // http://b/32294038, Test ArrayMap.entrySet().toArray()
537     @Test
538     public void testEntrySetArray() {
539         // Create
540         ArrayMap<Integer, String> testMap = new ArrayMap<>();
541 
542         // Test empty
543         checkEntrySetToArray(testMap);
544 
545         // Test non-empty
546         for (int i = 0; i < 10; ++i) {
547             testMap.put(i, String.valueOf(i));
548         }
549         checkEntrySetToArray(testMap);
550     }
551 
552     @Test
553     public void testCanNotIteratePastEnd_entrySetIterator() {
554         Map<String, String> map = new ArrayMap<>();
555         map.put("key 1", "value 1");
556         map.put("key 2", "value 2");
557         Set<Map.Entry<String, String>> expectedEntriesToIterate = new HashSet<>(Arrays.asList(
558                 entryOf("key 1", "value 1"),
559                 entryOf("key 2", "value 2")
560         ));
561         Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
562 
563         // Assert iteration over the expected two entries in any order
564         assertTrue(iterator.hasNext());
565         Map.Entry<String, String> firstEntry = copyOf(iterator.next());
566         assertTrue(expectedEntriesToIterate.remove(firstEntry));
567 
568         assertTrue(iterator.hasNext());
569         Map.Entry<String, String> secondEntry = copyOf(iterator.next());
570         assertTrue(expectedEntriesToIterate.remove(secondEntry));
571 
572         assertFalse(iterator.hasNext());
573 
574         try {
575             iterator.next();
576             fail();
577         } catch (NoSuchElementException expected) {
578         }
579     }
580 
581     private static<K, V> Map.Entry<K, V> entryOf(K key, V value) {
582         return new AbstractMap.SimpleEntry<>(key, value);
583     }
584 
585     private static<K, V> Map.Entry<K, V> copyOf(Map.Entry<K, V> entry) {
586         return entryOf(entry.getKey(), entry.getValue());
587     }
588 
589     @Test
590     public void testCanNotIteratePastEnd_keySetIterator() {
591         Map<String, String> map = new ArrayMap<>();
592         map.put("key 1", "value 1");
593         map.put("key 2", "value 2");
594         Set<String> expectedKeysToIterate = new HashSet<>(Arrays.asList("key 1", "key 2"));
595         Iterator<String> iterator = map.keySet().iterator();
596 
597         // Assert iteration over the expected two keys in any order
598         assertTrue(iterator.hasNext());
599         String firstKey = iterator.next();
600         assertTrue(expectedKeysToIterate.remove(firstKey));
601 
602         assertTrue(iterator.hasNext());
603         String secondKey = iterator.next();
604         assertTrue(expectedKeysToIterate.remove(secondKey));
605 
606         assertFalse(iterator.hasNext());
607 
608         try {
609             iterator.next();
610             fail();
611         } catch (NoSuchElementException expected) {
612         }
613     }
614 
615     @Test
616     public void testCanNotIteratePastEnd_valuesIterator() {
617         Map<String, String> map = new ArrayMap<>();
618         map.put("key 1", "value 1");
619         map.put("key 2", "value 2");
620         Set<String> expectedValuesToIterate = new HashSet<>(Arrays.asList("value 1", "value 2"));
621         Iterator<String> iterator = map.values().iterator();
622 
623         // Assert iteration over the expected two values in any order
624         assertTrue(iterator.hasNext());
625         String firstValue = iterator.next();
626         assertTrue(expectedValuesToIterate.remove(firstValue));
627 
628         assertTrue(iterator.hasNext());
629         String secondValue = iterator.next();
630         assertTrue(expectedValuesToIterate.remove(secondValue));
631 
632         assertFalse(iterator.hasNext());
633 
634         try {
635             iterator.next();
636             fail();
637         } catch (NoSuchElementException expected) {
638         }
639     }
640 
641     @Test
642     public void testForEach() {
643         ArrayMap<String, Integer> map = new ArrayMap<>();
644 
645         for (int i = 0; i < 50; ++i) {
646             map.put(Integer.toString(i), i * 10);
647         }
648 
649         // Make sure forEach goes through all of the elements.
650         HashMap<String, Integer> seen = new HashMap<>();
651         map.forEach(seen::put);
652         compareMaps(seen, map);
653     }
654 
655     /**
656      * The entrySet Iterator returns itself from each call to {@code next()}.
657      * This is unusual behavior for {@link Iterator#next()}; this test ensures that
658      * any future change to this behavior is deliberate.
659      */
660     @Test
661     public void testUnusualBehavior_eachEntryIsSameAsIterator_entrySetIterator() {
662         Map<String, String> map = new ArrayMap<>();
663         map.put("key 1", "value 1");
664         map.put("key 2", "value 2");
665         Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
666 
667         assertSame(iterator, iterator.next());
668         assertSame(iterator, iterator.next());
669     }
670 
671     @SuppressWarnings("SelfEquals")
672     @Test
673     public void testUnusualBehavior_equalsThrowsAfterRemove_entrySetIterator() {
674         Map<String, String> map = new ArrayMap<>();
675         map.put("key 1", "value 1");
676         map.put("key 2", "value 2");
677         Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
678         iterator.next();
679         iterator.remove();
680         try {
681             iterator.equals(iterator);
682             fail();
683         } catch (IllegalStateException expected) {
684         }
685     }
686 
687     private static<T> void assertEqualsBothWays(T a, T b) {
688         assertEquals(a, b);
689         assertEquals(b, a);
690         assertEquals(a.hashCode(), b.hashCode());
691     }
692 
693     @Test
694     public void testRemoveAll() {
695         final ArrayMap<Integer, String> map = new ArrayMap<>();
696         for (Integer i : Arrays.asList(0, 1, 2, 3, 4, 5)) {
697             map.put(i, i.toString());
698         }
699 
700         final ArrayMap<Integer, String> expectedMap = new ArrayMap<>();
701         for (Integer i :  Arrays.asList(2, 4)) {
702             expectedMap.put(i, String.valueOf(i));
703         }
704         map.removeAll(Arrays.asList(0, 1, 3, 5, 6));
705         if (!compare(map, expectedMap)) {
706             fail("ArrayMap removeAll failure, expect " + expectedMap + ", but " + map);
707         }
708 
709         map.removeAll(Collections.emptyList());
710         if (!compare(map, expectedMap)) {
711             fail("ArrayMap removeAll failure for empty maps, expect " + expectedMap + ", but " +
712                     map);
713         }
714 
715         map.removeAll(Arrays.asList(2, 4));
716         if (!map.isEmpty()) {
717             fail("ArrayMap removeAll failure, expect empty, but " + map);
718         }
719     }
720 
721     @Test
722     public void testReplaceAll() {
723         final ArrayMap<Integer, Integer> map = new ArrayMap<>();
724         final ArrayMap<Integer, Integer> expectedMap = new ArrayMap<>();
725         final BiFunction<Integer, Integer, Integer> function = (k, v) -> 2 * v;
726         for (Integer i : Arrays.asList(0, 1, 2, 3, 4, 5)) {
727             map.put(i, i);
728             expectedMap.put(i, 2 * i);
729         }
730 
731         map.replaceAll(function);
732         if (!compare(map, expectedMap)) {
733             fail("ArrayMap replaceAll failure, expect " + expectedMap + ", but " + map);
734         }
735     }
736 }
737