1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15package android.util; 16 17import android.util.ArrayMap; 18 19import junit.framework.TestCase; 20import org.junit.Test; 21 22import java.util.ConcurrentModificationException; 23 24/** 25 * Unit tests for ArrayMap that don't belong in CTS. 26 */ 27public class ArrayMapTest extends TestCase { 28 private static final String TAG = "ArrayMapTest"; 29 ArrayMap<String, String> map = new ArrayMap<>(); 30 31 /** 32 * Attempt to generate a ConcurrentModificationException in ArrayMap. 33 * <p> 34 * ArrayMap is explicitly documented to be non-thread-safe, yet it's easy to accidentally screw 35 * this up; ArrayMap should (in the spirit of the core Java collection types) make an effort to 36 * catch this and throw ConcurrentModificationException instead of crashing somewhere in its 37 * internals. 38 * 39 * @throws Exception 40 */ 41 @Test 42 public void testConcurrentModificationException() throws Exception { 43 final int TEST_LEN_MS = 5000; 44 System.out.println("Starting ArrayMap concurrency test"); 45 new Thread(() -> { 46 int i = 0; 47 while (map != null) { 48 try { 49 map.put(String.format("key %d", i++), "B_DONT_DO_THAT"); 50 } catch (ArrayIndexOutOfBoundsException e) { 51 Log.e(TAG, "concurrent modification uncaught, causing indexing failure", e); 52 fail(); 53 } catch (ClassCastException e) { 54 Log.e(TAG, "concurrent modification uncaught, causing cache corruption", e); 55 fail(); 56 } catch (ConcurrentModificationException e) { 57 System.out.println("[successfully caught CME at put #" + i 58 + " size=" + (map == null ? "??" : String.valueOf(map.size())) + "]"); 59 } 60 if (i % 200 == 0) { 61 System.out.print("."); 62 } 63 } 64 }).start(); 65 for (int i = 0; i < (TEST_LEN_MS / 100); i++) { 66 try { 67 Thread.sleep(100); 68 map.clear(); 69 System.out.print("X"); 70 } catch (InterruptedException e) { 71 } catch (ArrayIndexOutOfBoundsException e) { 72 Log.e(TAG, "concurrent modification uncaught, causing indexing failure"); 73 fail(); 74 } catch (ClassCastException e) { 75 Log.e(TAG, "concurrent modification uncaught, causing cache corruption"); 76 fail(); 77 } catch (ConcurrentModificationException e) { 78 System.out.println( 79 "[successfully caught CME at clear #" 80 + i + " size=" + map.size() + "]"); 81 } 82 } 83 map = null; // will stop other thread 84 System.out.println(); 85 } 86 87 /** 88 * Check to make sure the same operations behave as expected in a single thread. 89 */ 90 @Test 91 public void testNonConcurrentAccesses() throws Exception { 92 for (int i = 0; i < 100000; i++) { 93 try { 94 map.put(String.format("key %d", i++), "B_DONT_DO_THAT"); 95 if (i % 200 == 0) { 96 System.out.print("."); 97 } 98 if (i % 500 == 0) { 99 map.clear(); 100 System.out.print("X"); 101 } 102 } catch (ConcurrentModificationException e) { 103 Log.e(TAG, "concurrent modification caught on single thread", e); 104 fail(); 105 } 106 } 107 } 108} 109