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