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