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