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.support.test.filters.LargeTest;
18import android.util.ArrayMap;
19
20import junit.framework.TestCase;
21import org.junit.Test;
22
23import java.util.ConcurrentModificationException;
24
25/**
26 * Unit tests for ArrayMap that don't belong in CTS.
27 */
28@LargeTest
29public class ArrayMapTest extends TestCase {
30    private static final String TAG = "ArrayMapTest";
31    ArrayMap<String, String> map = new ArrayMap<>();
32
33    /**
34     * Attempt to generate a ConcurrentModificationException in ArrayMap.
35     * <p>
36     * ArrayMap is explicitly documented to be non-thread-safe, yet it's easy to accidentally screw
37     * this up; ArrayMap should (in the spirit of the core Java collection types) make an effort to
38     * catch this and throw ConcurrentModificationException instead of crashing somewhere in its
39     * internals.
40     *
41     * @throws Exception
42     */
43    @Test
44    public void testConcurrentModificationException() throws Exception {
45        final int TEST_LEN_MS = 5000;
46        System.out.println("Starting ArrayMap concurrency test");
47        new Thread(() -> {
48            int i = 0;
49            while (map != null) {
50                try {
51                    map.put(String.format("key %d", i++), "B_DONT_DO_THAT");
52                } catch (ArrayIndexOutOfBoundsException e) {
53                    Log.e(TAG, "concurrent modification uncaught, causing indexing failure", e);
54                    fail();
55                } catch (ClassCastException e) {
56                    Log.e(TAG, "concurrent modification uncaught, causing cache corruption", e);
57                    fail();
58                } catch (ConcurrentModificationException e) {
59                    System.out.println("[successfully caught CME at put #" + i
60                            + " size=" + (map == null ? "??" : String.valueOf(map.size())) + "]");
61                }
62                if (i % 200 == 0) {
63                    System.out.print(".");
64                }
65            }
66        }).start();
67        for (int i = 0; i < (TEST_LEN_MS / 100); i++) {
68            try {
69                Thread.sleep(100);
70                map.clear();
71                System.out.print("X");
72            } catch (InterruptedException e) {
73            } catch (ArrayIndexOutOfBoundsException e) {
74                Log.e(TAG, "concurrent modification uncaught, causing indexing failure");
75                fail();
76            } catch (ClassCastException e) {
77                Log.e(TAG, "concurrent modification uncaught, causing cache corruption");
78                fail();
79            } catch (ConcurrentModificationException e) {
80                System.out.println(
81                        "[successfully caught CME at clear #"
82                                + i + " size=" + map.size() + "]");
83            }
84        }
85        map = null; // will stop other thread
86        System.out.println();
87    }
88
89    /**
90     * Check to make sure the same operations behave as expected in a single thread.
91     */
92    @Test
93    public void testNonConcurrentAccesses() throws Exception {
94        for (int i = 0; i < 100000; i++) {
95            try {
96                map.put(String.format("key %d", i++), "B_DONT_DO_THAT");
97                if (i % 200 == 0) {
98                    System.out.print(".");
99                }
100                if (i % 500 == 0) {
101                    map.clear();
102                    System.out.print("X");
103                }
104            } catch (ConcurrentModificationException e) {
105                Log.e(TAG, "concurrent modification caught on single thread", e);
106                fail();
107            }
108        }
109    }
110}
111