1 /*
2  * Copyright (C) 2021 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.app;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertNotSame;
21 import static org.junit.Assert.assertSame;
22 
23 import androidx.test.filters.SmallTest;
24 
25 import org.junit.After;
26 import org.junit.Test;
27 
28 /**
29  * Test for verifying the behavior of {@link PropertyInvalidatedCache}.  This test does
30  * not use any actual binder calls - it is entirely self-contained.  This test also relies
31  * on the test mode of {@link PropertyInvalidatedCache} because Android SELinux rules do
32  * not grant test processes the permission to set system properties.
33  * <p>
34  * Build/Install/Run:
35  *  atest FrameworksCoreTests:PropertyInvalidatedCacheTests
36  */
37 @SmallTest
38 public class PropertyInvalidatedCacheTests {
39 
40     // Configuration for creating caches
41     private static final String MODULE = PropertyInvalidatedCache.MODULE_TEST;
42     private static final String API = "testApi";
43 
44     // This class is a proxy for binder calls.  It contains a counter that increments
45     // every time the class is queried.
46     private static class ServerProxy {
47         // The number of times this class was queried.
48         private int mCount = 0;
49 
50         // A single query.  The key behavior is that the query count is incremented.
query(int x)51         boolean query(int x) {
52             mCount++;
53             return value(x);
54         }
55 
56         // Return the expected value of an input, without incrementing the query count.
value(int x)57         boolean value(int x) {
58             return x % 3 == 0;
59         }
60 
61         // Verify the count.
verify(int x)62         void verify(int x) {
63             assertEquals(x, mCount);
64         }
65     }
66 
67     // The functions for querying the server.
68     private static class ServerQuery
69             extends PropertyInvalidatedCache.QueryHandler<Integer, Boolean> {
70         private final ServerProxy mServer;
71 
ServerQuery(ServerProxy server)72         ServerQuery(ServerProxy server) {
73             mServer = server;
74         }
75 
76         @Override
apply(Integer x)77         public Boolean apply(Integer x) {
78             return mServer.query(x);
79         }
80         @Override
shouldBypassCache(Integer x)81         public boolean shouldBypassCache(Integer x) {
82             return x % 13 == 0;
83         }
84     }
85 
86     // Clear the test mode after every test, in case this process is used for other
87     // tests. This also resets the test property map.
88     @After
tearDown()89     public void tearDown() throws Exception {
90         PropertyInvalidatedCache.setTestMode(false);
91     }
92 
93     // This test is disabled pending an sepolicy change that allows any app to set the
94     // test property.
95     @Test
testBasicCache()96     public void testBasicCache() {
97 
98         // A stand-in for the binder.  The test verifies that calls are passed through to
99         // this class properly.
100         ServerProxy tester = new ServerProxy();
101 
102         // Create a cache that uses simple arithmetic to computer its values.
103         PropertyInvalidatedCache<Integer, Boolean> testCache =
104                 new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
105                         new ServerQuery(tester));
106 
107         PropertyInvalidatedCache.setTestMode(true);
108         testCache.testPropertyName();
109 
110         tester.verify(0);
111         assertEquals(tester.value(3), testCache.query(3));
112         tester.verify(1);
113         assertEquals(tester.value(3), testCache.query(3));
114         tester.verify(2);
115         testCache.invalidateCache();
116         assertEquals(tester.value(3), testCache.query(3));
117         tester.verify(3);
118         assertEquals(tester.value(5), testCache.query(5));
119         tester.verify(4);
120         assertEquals(tester.value(5), testCache.query(5));
121         tester.verify(4);
122         assertEquals(tester.value(3), testCache.query(3));
123         tester.verify(4);
124 
125         // Invalidate the cache, and verify that the next read on 3 goes to the server.
126         testCache.invalidateCache();
127         assertEquals(tester.value(3), testCache.query(3));
128         tester.verify(5);
129 
130         // Test bypass.  The query for 13 always bypasses the cache.
131         assertEquals(tester.value(12), testCache.query(12));
132         assertEquals(tester.value(13), testCache.query(13));
133         assertEquals(tester.value(14), testCache.query(14));
134         tester.verify(8);
135         assertEquals(tester.value(12), testCache.query(12));
136         assertEquals(tester.value(13), testCache.query(13));
137         assertEquals(tester.value(14), testCache.query(14));
138         tester.verify(9);
139     }
140 
141     @Test
testDisableCache()142     public void testDisableCache() {
143 
144         // A stand-in for the binder.  The test verifies that calls are passed through to
145         // this class properly.
146         ServerProxy tester = new ServerProxy();
147 
148         // Three caches, all using the same system property but one uses a different name.
149         PropertyInvalidatedCache<Integer, Boolean> cache1 =
150             new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
151                         new ServerQuery(tester));
152         PropertyInvalidatedCache<Integer, Boolean> cache2 =
153             new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
154                         new ServerQuery(tester));
155         PropertyInvalidatedCache<Integer, Boolean> cache3 =
156             new PropertyInvalidatedCache<>(4, MODULE, API, "cache3",
157                         new ServerQuery(tester));
158 
159         // Caches are enabled upon creation.
160         assertEquals(false, cache1.getDisabledState());
161         assertEquals(false, cache2.getDisabledState());
162         assertEquals(false, cache3.getDisabledState());
163 
164         // Disable the cache1 instance.  Only cache1 is disabled
165         cache1.disableInstance();
166         assertEquals(true, cache1.getDisabledState());
167         assertEquals(false, cache2.getDisabledState());
168         assertEquals(false, cache3.getDisabledState());
169 
170         // Disable cache1.  This will disable cache1 and cache2 because they share the
171         // same name.  cache3 has a different name and will not be disabled.
172         cache1.disableLocal();
173         assertEquals(true, cache1.getDisabledState());
174         assertEquals(true, cache2.getDisabledState());
175         assertEquals(false, cache3.getDisabledState());
176 
177         // Create a new cache1.  Verify that the new instance is disabled.
178         cache1 = new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
179                 new ServerQuery(tester));
180         assertEquals(true, cache1.getDisabledState());
181 
182         // Remove the record of caches being locally disabled.  This is a clean-up step.
183         cache1.forgetDisableLocal();
184         assertEquals(true, cache1.getDisabledState());
185         assertEquals(true, cache2.getDisabledState());
186         assertEquals(false, cache3.getDisabledState());
187 
188         // Create a new cache1.  Verify that the new instance is not disabled.
189         cache1 = new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
190                 new ServerQuery(tester));
191         assertEquals(false, cache1.getDisabledState());
192     }
193 
194     private static class TestQuery
195             extends PropertyInvalidatedCache.QueryHandler<Integer, String> {
196 
197         private int mRecomputeCount = 0;
198 
199         @Override
apply(Integer qv)200         public String apply(Integer qv) {
201             mRecomputeCount += 1;
202             return "foo" + qv.toString();
203         }
204 
getRecomputeCount()205         int getRecomputeCount() {
206             return mRecomputeCount;
207         }
208     }
209 
210     private static class TestCache extends PropertyInvalidatedCache<Integer, String> {
211         private final TestQuery mQuery;
212 
TestCache()213         TestCache() {
214             this(MODULE, API);
215         }
216 
TestCache(String module, String api)217         TestCache(String module, String api) {
218             this(module, api, new TestQuery());
219             setTestMode(true);
220             testPropertyName();
221         }
222 
TestCache(String module, String api, TestQuery query)223         TestCache(String module, String api, TestQuery query) {
224             super(4, module, api, api, query);
225             mQuery = query;
226             setTestMode(true);
227             testPropertyName();
228         }
229 
getRecomputeCount()230         public int getRecomputeCount() {
231             return mQuery.getRecomputeCount();
232         }
233 
234 
235     }
236 
237     @Test
testCacheRecompute()238     public void testCacheRecompute() {
239         TestCache cache = new TestCache();
240         cache.invalidateCache();
241         assertEquals(cache.isDisabled(), false);
242         assertEquals("foo5", cache.query(5));
243         assertEquals(1, cache.getRecomputeCount());
244         assertEquals("foo5", cache.query(5));
245         assertEquals(1, cache.getRecomputeCount());
246         assertEquals("foo6", cache.query(6));
247         assertEquals(2, cache.getRecomputeCount());
248         cache.invalidateCache();
249         assertEquals("foo5", cache.query(5));
250         assertEquals("foo5", cache.query(5));
251         assertEquals(3, cache.getRecomputeCount());
252         // Invalidate the cache with a direct call to the property.
253         PropertyInvalidatedCache.invalidateCache(MODULE, API);
254         assertEquals("foo5", cache.query(5));
255         assertEquals("foo5", cache.query(5));
256         assertEquals(4, cache.getRecomputeCount());
257     }
258 
259     @Test
testCacheInitialState()260     public void testCacheInitialState() {
261         TestCache cache = new TestCache();
262         assertEquals("foo5", cache.query(5));
263         assertEquals("foo5", cache.query(5));
264         assertEquals(2, cache.getRecomputeCount());
265         cache.invalidateCache();
266         assertEquals("foo5", cache.query(5));
267         assertEquals("foo5", cache.query(5));
268         assertEquals(3, cache.getRecomputeCount());
269     }
270 
271     @Test
testCachePropertyUnset()272     public void testCachePropertyUnset() {
273         final String UNSET_API = "otherApi";
274         TestCache cache = new TestCache(MODULE, UNSET_API);
275         assertEquals("foo5", cache.query(5));
276         assertEquals("foo5", cache.query(5));
277         assertEquals(2, cache.getRecomputeCount());
278     }
279 
280     @Test
testCacheDisableState()281     public void testCacheDisableState() {
282         TestCache cache = new TestCache();
283         assertEquals("foo5", cache.query(5));
284         assertEquals("foo5", cache.query(5));
285         assertEquals(2, cache.getRecomputeCount());
286         cache.invalidateCache();
287         assertEquals("foo5", cache.query(5));
288         assertEquals("foo5", cache.query(5));
289         assertEquals(3, cache.getRecomputeCount());
290         cache.disableSystemWide();
291         assertEquals("foo5", cache.query(5));
292         assertEquals("foo5", cache.query(5));
293         assertEquals(5, cache.getRecomputeCount());
294         cache.invalidateCache();  // Should not reenable
295         assertEquals("foo5", cache.query(5));
296         assertEquals("foo5", cache.query(5));
297         assertEquals(7, cache.getRecomputeCount());
298     }
299 
300     @Test
testRefreshSameObject()301     public void testRefreshSameObject() {
302         int[] refreshCount = new int[1];
303         TestCache cache = new TestCache() {
304             @Override
305             public String refresh(String oldResult, Integer query) {
306                 refreshCount[0] += 1;
307                 return oldResult;
308             }
309         };
310         cache.invalidateCache();
311         String result1 = cache.query(5);
312         assertEquals("foo5", result1);
313         String result2 = cache.query(5);
314         assertSame(result1, result2);
315         assertEquals(1, cache.getRecomputeCount());
316         assertEquals(1, refreshCount[0]);
317         assertEquals("foo5", cache.query(5));
318         assertEquals(2, refreshCount[0]);
319     }
320 
321     @Test
testRefreshInvalidateRace()322     public void testRefreshInvalidateRace() {
323         int[] refreshCount = new int[1];
324         TestCache cache = new TestCache() {
325             @Override
326             public String refresh(String oldResult, Integer query) {
327                 refreshCount[0] += 1;
328                 invalidateCache();
329                 return new String(oldResult);
330             }
331         };
332         cache.invalidateCache();
333         String result1 = cache.query(5);
334         assertEquals("foo5", result1);
335         String result2 = cache.query(5);
336         assertEquals(result1, result2);
337         assertNotSame(result1, result2);
338         assertEquals(2, cache.getRecomputeCount());
339     }
340 
341     @Test
testLocalProcessDisable()342     public void testLocalProcessDisable() {
343         TestCache cache = new TestCache();
344         assertEquals(cache.isDisabled(), false);
345         cache.invalidateCache();
346         assertEquals("foo5", cache.query(5));
347         assertEquals(1, cache.getRecomputeCount());
348         assertEquals("foo5", cache.query(5));
349         assertEquals(1, cache.getRecomputeCount());
350         assertEquals(cache.isDisabled(), false);
351         cache.disableLocal();
352         assertEquals(cache.isDisabled(), true);
353         assertEquals("foo5", cache.query(5));
354         assertEquals("foo5", cache.query(5));
355         assertEquals(3, cache.getRecomputeCount());
356     }
357 
358     @Test
testPropertyNames()359     public void testPropertyNames() {
360         String n1;
361         n1 = PropertyInvalidatedCache.createPropertyName(
362             PropertyInvalidatedCache.MODULE_SYSTEM, "getPackageInfo");
363         assertEquals(n1, "cache_key.system_server.get_package_info");
364         n1 = PropertyInvalidatedCache.createPropertyName(
365             PropertyInvalidatedCache.MODULE_SYSTEM, "get_package_info");
366         assertEquals(n1, "cache_key.system_server.get_package_info");
367         n1 = PropertyInvalidatedCache.createPropertyName(
368             PropertyInvalidatedCache.MODULE_BLUETOOTH, "getState");
369         assertEquals(n1, "cache_key.bluetooth.get_state");
370     }
371 
372     @Test
testOnTrimMemory()373     public void testOnTrimMemory() {
374         TestCache cache = new TestCache(MODULE, "trimMemoryTest");
375         // The cache is not active until it has been invalidated once.
376         cache.invalidateCache();
377         // Populate the cache with six entries.
378         for (int i = 0; i < 6; i++) {
379             cache.query(i);
380         }
381         // The maximum number of entries in TestCache is 4, so even though six entries were
382         // created, only four are retained.
383         assertEquals(4, cache.size());
384         PropertyInvalidatedCache.onTrimMemory();
385         assertEquals(0, cache.size());
386     }
387 }
388