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