1 // Copyright 2020 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base; 6 7 import static org.junit.Assert.assertArrayEquals; 8 import static org.junit.Assert.assertEquals; 9 import static org.junit.Assert.assertFalse; 10 import static org.junit.Assert.assertNotEquals; 11 import static org.junit.Assert.assertNotNull; 12 import static org.junit.Assert.assertNull; 13 import static org.junit.Assert.assertTrue; 14 15 import android.os.Handler; 16 import android.os.Looper; 17 18 import org.junit.After; 19 import org.junit.Before; 20 import org.junit.Test; 21 import org.junit.runner.RunWith; 22 import org.robolectric.Shadows; 23 import org.robolectric.annotation.Config; 24 import org.robolectric.shadows.ShadowLooper; 25 26 import org.chromium.base.task.PostTask; 27 import org.chromium.base.task.TaskTraits; 28 import org.chromium.base.test.BaseRobolectricTestRunner; 29 import org.chromium.build.BuildConfig; 30 31 import java.util.ArrayList; 32 import java.util.List; 33 import java.util.concurrent.FutureTask; 34 35 /** Test class for {@link UnownedUserDataKey}, which also describes typical usage. */ 36 @RunWith(BaseRobolectricTestRunner.class) 37 @Config(manifest = Config.NONE) 38 public class UnownedUserDataKeyTest { forceGC()39 private static void forceGC() { 40 try { 41 // Run GC and finalizers a few times. 42 for (int i = 0; i < 10; ++i) { 43 System.gc(); 44 System.runFinalization(); 45 } 46 } catch (Exception e) { 47 // Do nothing. 48 } 49 } 50 51 private static class TestUnownedUserData implements UnownedUserData { 52 private List<UnownedUserDataHost> mDetachedHosts = new ArrayList<>(); 53 54 public boolean informOnDetachment = true; 55 56 @Override onDetachedFromHost(UnownedUserDataHost host)57 public void onDetachedFromHost(UnownedUserDataHost host) { 58 assertTrue( 59 "Should not detach when informOnDetachmentFromHost() return false.", 60 informOnDetachment); 61 mDetachedHosts.add(host); 62 } 63 64 @Override informOnDetachmentFromHost()65 public boolean informOnDetachmentFromHost() { 66 return informOnDetachment; 67 } 68 assertDetachedHostsMatch(UnownedUserDataHost... hosts)69 public void assertDetachedHostsMatch(UnownedUserDataHost... hosts) { 70 assertEquals(mDetachedHosts.size(), hosts.length); 71 assertArrayEquals(mDetachedHosts.toArray(), hosts); 72 } 73 74 /** 75 * Use this helper assert only when order of detachments can not be known, such as on 76 * invocations of {@link UnownedUserDataKey#detachFromAllHosts(UnownedUserData)}. 77 * 78 * @param hosts Which hosts it is required that the UnownedUserData has been detached from. 79 */ assertDetachedHostsMatchAnyOrder(UnownedUserDataHost... hosts)80 public void assertDetachedHostsMatchAnyOrder(UnownedUserDataHost... hosts) { 81 assertEquals(mDetachedHosts.size(), hosts.length); 82 for (UnownedUserDataHost host : hosts) { 83 assertTrue("Should have been detached from host", mDetachedHosts.contains(host)); 84 } 85 } 86 assertNoDetachedHosts()87 public void assertNoDetachedHosts() { 88 assertDetachedHostsMatch(); 89 } 90 } 91 92 private static class Foo extends TestUnownedUserData { 93 public static final UnownedUserDataKey<Foo> KEY = new UnownedUserDataKey<>(Foo.class); 94 } 95 96 private static class Bar extends TestUnownedUserData { 97 public static final UnownedUserDataKey<Bar> KEY = new UnownedUserDataKey<>(Bar.class); 98 } 99 100 private final Foo mFoo = new Foo(); 101 private final Bar mBar = new Bar(); 102 103 private UnownedUserDataHost mHost1; 104 private UnownedUserDataHost mHost2; 105 106 @Before setUp()107 public void setUp() { 108 ShadowLooper.pauseMainLooper(); 109 mHost1 = new UnownedUserDataHost(new Handler(Looper.getMainLooper())); 110 mHost2 = new UnownedUserDataHost(new Handler(Looper.getMainLooper())); 111 } 112 113 @After tearDown()114 public void tearDown() { 115 if (!mHost1.isDestroyed()) { 116 assertEquals(0, mHost1.getMapSize()); 117 mHost1.destroy(); 118 } 119 mHost1 = null; 120 if (!mHost2.isDestroyed()) { 121 assertEquals(0, mHost2.getMapSize()); 122 mHost2.destroy(); 123 } 124 mHost2 = null; 125 } 126 127 @Test testKeyEquality()128 public void testKeyEquality() { 129 assertEquals(Foo.KEY, Foo.KEY); 130 assertNotEquals(Foo.KEY, new UnownedUserDataKey<>(Foo.class)); 131 assertNotEquals(Foo.KEY, Bar.KEY); 132 assertNotEquals(Foo.KEY, null); 133 assertNotEquals(Foo.KEY, new Object()); 134 assertNotEquals(Bar.KEY, new UnownedUserDataKey<>(Bar.class)); 135 } 136 137 @Test testSingleItemSingleHost_retrievalReturnsNullBeforeAttachment()138 public void testSingleItemSingleHost_retrievalReturnsNullBeforeAttachment() { 139 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 140 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 141 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 142 } 143 144 @Test testSingleItemSingleHost_attachAndDetach()145 public void testSingleItemSingleHost_attachAndDetach() { 146 Foo.KEY.attachToHost(mHost1, mFoo); 147 148 assertTrue(Foo.KEY.isAttachedToHost(mHost1)); 149 assertTrue(Foo.KEY.isAttachedToAnyHost(mFoo)); 150 assertEquals(mFoo, Foo.KEY.retrieveDataFromHost(mHost1)); 151 152 Foo.KEY.detachFromHost(mHost1); 153 runUntilIdle(); 154 155 mFoo.assertDetachedHostsMatch(mHost1); 156 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 157 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 158 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 159 } 160 161 @Test testSingleItemSingleHost_attachAndGarbageCollectionReturnsNull()162 public void testSingleItemSingleHost_attachAndGarbageCollectionReturnsNull() { 163 Foo foo = new Foo(); 164 Foo.KEY.attachToHost(mHost1, foo); 165 166 // Intentionally null out `foo` to make it eligible for garbage collection. 167 foo = null; 168 forceGC(); 169 runUntilIdle(); 170 171 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 172 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 173 174 // NOTE: We can not verify anything using the `foo` variable here, since it has been 175 // garbage collected. 176 } 177 178 @Test testSingleItemSingleHost_attachAndDetachFromAllHosts()179 public void testSingleItemSingleHost_attachAndDetachFromAllHosts() { 180 Foo.KEY.attachToHost(mHost1, mFoo); 181 Foo.KEY.detachFromAllHosts(mFoo); 182 runUntilIdle(); 183 184 mFoo.assertDetachedHostsMatch(mHost1); 185 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 186 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 187 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 188 } 189 190 @Test testSingleItemSingleHost_attachAndDetachDetachmentCallbackIsPosted()191 public void testSingleItemSingleHost_attachAndDetachDetachmentCallbackIsPosted() { 192 Foo.KEY.attachToHost(mHost1, mFoo); 193 Foo.KEY.detachFromHost(mHost1); 194 mFoo.assertNoDetachedHosts(); 195 196 runUntilIdle(); 197 198 mFoo.assertDetachedHostsMatch(mHost1); 199 } 200 201 @Test testSingleItemSingleHost_attachAndDetachNoDetachmentCallback()202 public void testSingleItemSingleHost_attachAndDetachNoDetachmentCallback() { 203 mFoo.informOnDetachment = false; 204 Foo.KEY.attachToHost(mHost1, mFoo); 205 Foo.KEY.detachFromHost(mHost1); 206 runUntilIdle(); 207 208 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 209 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 210 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 211 } 212 213 @Test testSingleItemSingleHost_attachAndDetachFromAllHostsNoDetachmentCallback()214 public void testSingleItemSingleHost_attachAndDetachFromAllHostsNoDetachmentCallback() { 215 mFoo.informOnDetachment = false; 216 Foo.KEY.attachToHost(mHost1, mFoo); 217 Foo.KEY.detachFromAllHosts(mFoo); 218 runUntilIdle(); 219 220 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 221 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 222 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 223 } 224 225 @Test testSingleItemSingleHost_differentKeys()226 public void testSingleItemSingleHost_differentKeys() { 227 UnownedUserDataKey<Foo> extraKey = new UnownedUserDataKey<>(Foo.class); 228 UnownedUserDataKey<Foo> anotherExtraKey = new UnownedUserDataKey<>(Foo.class); 229 230 Foo.KEY.attachToHost(mHost1, mFoo); 231 extraKey.attachToHost(mHost1, mFoo); 232 anotherExtraKey.attachToHost(mHost1, mFoo); 233 runUntilIdle(); 234 235 mFoo.assertNoDetachedHosts(); 236 assertTrue(Foo.KEY.isAttachedToHost(mHost1)); 237 assertTrue(Foo.KEY.isAttachedToAnyHost(mFoo)); 238 assertEquals(mFoo, Foo.KEY.retrieveDataFromHost(mHost1)); 239 assertTrue(extraKey.isAttachedToHost(mHost1)); 240 assertTrue(extraKey.isAttachedToAnyHost(mFoo)); 241 assertEquals(mFoo, extraKey.retrieveDataFromHost(mHost1)); 242 assertTrue(anotherExtraKey.isAttachedToHost(mHost1)); 243 assertTrue(anotherExtraKey.isAttachedToAnyHost(mFoo)); 244 assertEquals(mFoo, anotherExtraKey.retrieveDataFromHost(mHost1)); 245 246 Foo.KEY.detachFromHost(mHost1); 247 runUntilIdle(); 248 249 mFoo.assertDetachedHostsMatch(mHost1); 250 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 251 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 252 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 253 assertTrue(extraKey.isAttachedToHost(mHost1)); 254 assertTrue(extraKey.isAttachedToAnyHost(mFoo)); 255 assertEquals(mFoo, extraKey.retrieveDataFromHost(mHost1)); 256 assertTrue(anotherExtraKey.isAttachedToHost(mHost1)); 257 assertTrue(anotherExtraKey.isAttachedToAnyHost(mFoo)); 258 assertEquals(mFoo, anotherExtraKey.retrieveDataFromHost(mHost1)); 259 260 extraKey.detachFromAllHosts(mFoo); 261 runUntilIdle(); 262 263 mFoo.assertDetachedHostsMatch(mHost1, mHost1); 264 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 265 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 266 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 267 assertFalse(extraKey.isAttachedToHost(mHost1)); 268 assertFalse(extraKey.isAttachedToAnyHost(mFoo)); 269 assertNull(extraKey.retrieveDataFromHost(mHost1)); 270 assertTrue(anotherExtraKey.isAttachedToHost(mHost1)); 271 assertTrue(anotherExtraKey.isAttachedToAnyHost(mFoo)); 272 assertEquals(mFoo, anotherExtraKey.retrieveDataFromHost(mHost1)); 273 274 anotherExtraKey.detachFromHost(mHost1); 275 runUntilIdle(); 276 277 mFoo.assertDetachedHostsMatch(mHost1, mHost1, mHost1); 278 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 279 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 280 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 281 assertFalse(extraKey.isAttachedToHost(mHost1)); 282 assertFalse(extraKey.isAttachedToAnyHost(mFoo)); 283 assertNull(extraKey.retrieveDataFromHost(mHost1)); 284 assertFalse(anotherExtraKey.isAttachedToHost(mHost1)); 285 assertFalse(anotherExtraKey.isAttachedToAnyHost(mFoo)); 286 assertNull(anotherExtraKey.retrieveDataFromHost(mHost1)); 287 } 288 289 @Test testSingleItemSingleHost_doubleAttachSingleDetach()290 public void testSingleItemSingleHost_doubleAttachSingleDetach() { 291 Foo.KEY.attachToHost(mHost1, mFoo); 292 Foo.KEY.attachToHost(mHost1, mFoo); 293 runUntilIdle(); 294 295 // Attaching using the same key and object, so no detachment should have happened. 296 mFoo.assertNoDetachedHosts(); 297 assertTrue(Foo.KEY.isAttachedToHost(mHost1)); 298 assertTrue(Foo.KEY.isAttachedToAnyHost(mFoo)); 299 assertEquals(mFoo, Foo.KEY.retrieveDataFromHost(mHost1)); 300 301 Foo.KEY.detachFromHost(mHost1); 302 runUntilIdle(); 303 304 mFoo.assertDetachedHostsMatch(mHost1); 305 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 306 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 307 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 308 } 309 310 @Test testSingleItemSingleHost_doubleAttachDetachFromAllHosts()311 public void testSingleItemSingleHost_doubleAttachDetachFromAllHosts() { 312 Foo.KEY.attachToHost(mHost1, mFoo); 313 Foo.KEY.attachToHost(mHost1, mFoo); 314 runUntilIdle(); 315 316 // Attaching using the same key and object, so no detachment should have happened. 317 mFoo.assertNoDetachedHosts(); 318 assertTrue(Foo.KEY.isAttachedToHost(mHost1)); 319 assertTrue(Foo.KEY.isAttachedToAnyHost(mFoo)); 320 assertEquals(mFoo, Foo.KEY.retrieveDataFromHost(mHost1)); 321 322 Foo.KEY.detachFromAllHosts(mFoo); 323 runUntilIdle(); 324 325 mFoo.assertDetachedHostsMatch(mHost1); 326 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 327 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 328 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 329 } 330 331 @Test testSingleItemSingleHost_doubleDetachIsIgnored()332 public void testSingleItemSingleHost_doubleDetachIsIgnored() { 333 Foo.KEY.attachToHost(mHost1, mFoo); 334 Foo.KEY.detachFromHost(mHost1); 335 runUntilIdle(); 336 337 mFoo.assertDetachedHostsMatch(mHost1); 338 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 339 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 340 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 341 342 Foo.KEY.detachFromHost(mHost1); 343 runUntilIdle(); 344 345 mFoo.assertDetachedHostsMatch(mHost1); 346 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 347 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 348 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 349 } 350 351 @Test testSingleItemSingleHost_doubleDetachFromAllHostsIsIgnored()352 public void testSingleItemSingleHost_doubleDetachFromAllHostsIsIgnored() { 353 Foo.KEY.attachToHost(mHost1, mFoo); 354 Foo.KEY.detachFromAllHosts(mFoo); 355 runUntilIdle(); 356 357 mFoo.assertDetachedHostsMatch(mHost1); 358 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 359 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 360 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 361 362 Foo.KEY.detachFromAllHosts(mFoo); 363 runUntilIdle(); 364 365 mFoo.assertDetachedHostsMatch(mHost1); 366 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 367 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 368 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 369 } 370 371 @Test testSingleItemMulitpleHosts_attachAndDetach()372 public void testSingleItemMulitpleHosts_attachAndDetach() { 373 Foo.KEY.attachToHost(mHost1, mFoo); 374 Foo.KEY.attachToHost(mHost2, mFoo); 375 runUntilIdle(); 376 377 mFoo.assertNoDetachedHosts(); 378 assertTrue(Foo.KEY.isAttachedToHost(mHost1)); 379 assertTrue(Foo.KEY.isAttachedToHost(mHost2)); 380 assertTrue(Foo.KEY.isAttachedToAnyHost(mFoo)); 381 assertEquals(mFoo, Foo.KEY.retrieveDataFromHost(mHost1)); 382 assertEquals(mFoo, Foo.KEY.retrieveDataFromHost(mHost2)); 383 384 Foo.KEY.detachFromHost(mHost1); 385 runUntilIdle(); 386 387 mFoo.assertDetachedHostsMatch(mHost1); 388 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 389 assertTrue(Foo.KEY.isAttachedToHost(mHost2)); 390 assertTrue(Foo.KEY.isAttachedToAnyHost(mFoo)); 391 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 392 assertEquals(mFoo, Foo.KEY.retrieveDataFromHost(mHost2)); 393 394 Foo.KEY.detachFromHost(mHost2); 395 runUntilIdle(); 396 397 mFoo.assertDetachedHostsMatch(mHost1, mHost2); 398 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 399 assertFalse(Foo.KEY.isAttachedToHost(mHost2)); 400 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 401 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 402 assertNull(Foo.KEY.retrieveDataFromHost(mHost2)); 403 } 404 405 @Test testSingleItemMultipleHosts_attachAndMultipleDetachesAreIgnored()406 public void testSingleItemMultipleHosts_attachAndMultipleDetachesAreIgnored() { 407 Foo.KEY.attachToHost(mHost1, mFoo); 408 Foo.KEY.attachToHost(mHost2, mFoo); 409 Foo.KEY.detachFromHost(mHost1); 410 Foo.KEY.detachFromHost(mHost1); 411 runUntilIdle(); 412 413 mFoo.assertDetachedHostsMatch(mHost1); 414 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 415 assertTrue(Foo.KEY.isAttachedToHost(mHost2)); 416 assertTrue(Foo.KEY.isAttachedToAnyHost(mFoo)); 417 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 418 assertEquals(mFoo, Foo.KEY.retrieveDataFromHost(mHost2)); 419 420 Foo.KEY.detachFromHost(mHost2); 421 runUntilIdle(); 422 423 mFoo.assertDetachedHostsMatch(mHost1, mHost2); 424 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 425 assertFalse(Foo.KEY.isAttachedToHost(mHost2)); 426 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 427 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 428 assertNull(Foo.KEY.retrieveDataFromHost(mHost2)); 429 430 Foo.KEY.detachFromHost(mHost2); 431 runUntilIdle(); 432 433 mFoo.assertDetachedHostsMatch(mHost1, mHost2); 434 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 435 assertFalse(Foo.KEY.isAttachedToHost(mHost2)); 436 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 437 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 438 assertNull(Foo.KEY.retrieveDataFromHost(mHost2)); 439 } 440 441 @Test testSingleItemMultipleHosts_attachAndDetachFromAllHosts()442 public void testSingleItemMultipleHosts_attachAndDetachFromAllHosts() { 443 Foo.KEY.attachToHost(mHost1, mFoo); 444 Foo.KEY.attachToHost(mHost2, mFoo); 445 Foo.KEY.detachFromAllHosts(mFoo); 446 runUntilIdle(); 447 448 mFoo.assertDetachedHostsMatchAnyOrder(mHost1, mHost2); 449 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 450 assertFalse(Foo.KEY.isAttachedToHost(mHost2)); 451 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 452 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 453 assertNull(Foo.KEY.retrieveDataFromHost(mHost2)); 454 } 455 456 @Test testSingleItemMultipleHosts_attachAndDoubleDetachFromAllHostsIsIgnored()457 public void testSingleItemMultipleHosts_attachAndDoubleDetachFromAllHostsIsIgnored() { 458 Foo.KEY.attachToHost(mHost1, mFoo); 459 Foo.KEY.attachToHost(mHost2, mFoo); 460 Foo.KEY.detachFromAllHosts(mFoo); 461 Foo.KEY.detachFromAllHosts(mFoo); 462 runUntilIdle(); 463 464 mFoo.assertDetachedHostsMatchAnyOrder(mHost1, mHost2); 465 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 466 assertFalse(Foo.KEY.isAttachedToHost(mHost2)); 467 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 468 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 469 assertNull(Foo.KEY.retrieveDataFromHost(mHost2)); 470 } 471 472 @Test testSingleItemMultipleHosts_attachAndDetachInSequence()473 public void testSingleItemMultipleHosts_attachAndDetachInSequence() { 474 Foo.KEY.attachToHost(mHost1, mFoo); 475 Foo.KEY.detachFromHost(mHost1); 476 Foo.KEY.attachToHost(mHost2, mFoo); 477 runUntilIdle(); 478 479 mFoo.assertDetachedHostsMatch(mHost1); 480 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 481 assertTrue(Foo.KEY.isAttachedToHost(mHost2)); 482 assertTrue(Foo.KEY.isAttachedToAnyHost(mFoo)); 483 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 484 assertEquals(mFoo, Foo.KEY.retrieveDataFromHost(mHost2)); 485 486 Foo.KEY.detachFromHost(mHost2); 487 runUntilIdle(); 488 489 mFoo.assertDetachedHostsMatch(mHost1, mHost2); 490 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 491 assertFalse(Foo.KEY.isAttachedToHost(mHost2)); 492 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 493 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 494 assertNull(Foo.KEY.retrieveDataFromHost(mHost2)); 495 } 496 497 @Test testSingleItemMultipleHosts_attachAndDetachFromAllHostsInSequence()498 public void testSingleItemMultipleHosts_attachAndDetachFromAllHostsInSequence() { 499 Foo.KEY.attachToHost(mHost1, mFoo); 500 Foo.KEY.detachFromAllHosts(mFoo); 501 Foo.KEY.attachToHost(mHost2, mFoo); 502 runUntilIdle(); 503 504 mFoo.assertDetachedHostsMatch(mHost1); 505 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 506 assertTrue(Foo.KEY.isAttachedToHost(mHost2)); 507 assertTrue(Foo.KEY.isAttachedToAnyHost(mFoo)); 508 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 509 assertEquals(mFoo, Foo.KEY.retrieveDataFromHost(mHost2)); 510 511 Foo.KEY.detachFromAllHosts(mFoo); 512 runUntilIdle(); 513 514 mFoo.assertDetachedHostsMatch(mHost1, mHost2); 515 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 516 assertFalse(Foo.KEY.isAttachedToHost(mHost2)); 517 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 518 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 519 assertNull(Foo.KEY.retrieveDataFromHost(mHost2)); 520 } 521 522 @Test testTwoSimilarItemsSingleHost_attachAndDetach()523 public void testTwoSimilarItemsSingleHost_attachAndDetach() { 524 Foo foo1 = new Foo(); 525 Foo foo2 = new Foo(); 526 527 Foo.KEY.attachToHost(mHost1, foo1); 528 runUntilIdle(); 529 530 foo1.assertNoDetachedHosts(); 531 foo2.assertNoDetachedHosts(); 532 assertTrue(Foo.KEY.isAttachedToHost(mHost1)); 533 assertTrue(Foo.KEY.isAttachedToAnyHost(foo1)); 534 assertFalse(Foo.KEY.isAttachedToAnyHost(foo2)); 535 assertEquals(foo1, Foo.KEY.retrieveDataFromHost(mHost1)); 536 537 Foo.KEY.attachToHost(mHost1, foo2); 538 runUntilIdle(); 539 540 foo1.assertDetachedHostsMatch(mHost1); 541 foo2.assertNoDetachedHosts(); 542 assertTrue(Foo.KEY.isAttachedToHost(mHost1)); 543 assertFalse(Foo.KEY.isAttachedToAnyHost(foo1)); 544 assertTrue(Foo.KEY.isAttachedToAnyHost(foo2)); 545 assertEquals(foo2, Foo.KEY.retrieveDataFromHost(mHost1)); 546 547 Foo.KEY.detachFromHost(mHost1); 548 runUntilIdle(); 549 550 foo1.assertDetachedHostsMatch(mHost1); 551 foo2.assertDetachedHostsMatch(mHost1); 552 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 553 assertFalse(Foo.KEY.isAttachedToAnyHost(foo1)); 554 assertFalse(Foo.KEY.isAttachedToAnyHost(foo2)); 555 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 556 } 557 558 @Test testTwoSimilarItemsSingleHost_attachAndDetachInSequence()559 public void testTwoSimilarItemsSingleHost_attachAndDetachInSequence() { 560 Foo foo1 = new Foo(); 561 Foo foo2 = new Foo(); 562 563 Foo.KEY.attachToHost(mHost1, foo1); 564 runUntilIdle(); 565 566 foo1.assertNoDetachedHosts(); 567 foo2.assertNoDetachedHosts(); 568 assertTrue(Foo.KEY.isAttachedToHost(mHost1)); 569 assertTrue(Foo.KEY.isAttachedToAnyHost(foo1)); 570 assertFalse(Foo.KEY.isAttachedToAnyHost(foo2)); 571 assertEquals(foo1, Foo.KEY.retrieveDataFromHost(mHost1)); 572 573 Foo.KEY.detachFromHost(mHost1); 574 runUntilIdle(); 575 576 foo1.assertDetachedHostsMatch(mHost1); 577 foo2.assertNoDetachedHosts(); 578 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 579 assertFalse(Foo.KEY.isAttachedToAnyHost(foo1)); 580 assertFalse(Foo.KEY.isAttachedToAnyHost(foo2)); 581 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 582 583 Foo.KEY.attachToHost(mHost1, foo2); 584 runUntilIdle(); 585 586 foo1.assertDetachedHostsMatch(mHost1); 587 foo2.assertNoDetachedHosts(); 588 assertTrue(Foo.KEY.isAttachedToHost(mHost1)); 589 assertFalse(Foo.KEY.isAttachedToAnyHost(foo1)); 590 assertTrue(Foo.KEY.isAttachedToAnyHost(foo2)); 591 assertEquals(foo2, Foo.KEY.retrieveDataFromHost(mHost1)); 592 593 Foo.KEY.detachFromHost(mHost1); 594 runUntilIdle(); 595 596 foo1.assertDetachedHostsMatch(mHost1); 597 foo2.assertDetachedHostsMatch(mHost1); 598 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 599 assertFalse(Foo.KEY.isAttachedToAnyHost(foo1)); 600 assertFalse(Foo.KEY.isAttachedToAnyHost(foo2)); 601 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 602 } 603 604 @Test testTwoSimilarItemsSingleHost_attachAndGarbageColletionReturnsNull()605 public void testTwoSimilarItemsSingleHost_attachAndGarbageColletionReturnsNull() { 606 Foo foo1 = new Foo(); 607 Foo foo2 = new Foo(); 608 609 Foo.KEY.attachToHost(mHost1, foo1); 610 Foo.KEY.attachToHost(mHost1, foo2); 611 612 // Intentionally null out `foo1` to make it eligible for garbage collection. 613 foo1 = null; 614 forceGC(); 615 runUntilIdle(); 616 617 assertTrue(Foo.KEY.isAttachedToHost(mHost1)); 618 assertTrue(Foo.KEY.isAttachedToAnyHost(foo2)); 619 assertEquals(foo2, Foo.KEY.retrieveDataFromHost(mHost1)); 620 621 // NOTE: We can not verify anything using the `foo1` variable here, since it has been 622 // garbage collected. 623 624 // Intentionally null out `foo2` to make it eligible for garbage collection. 625 foo2 = null; 626 forceGC(); 627 runUntilIdle(); 628 629 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 630 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 631 632 // NOTE: We can not verify anything using the `foo2` variable here, since it has been 633 // garbage collected. 634 } 635 636 @Test testTwoSimilarItemsMultipleHosts_destroyOnlyDetachesFromOneHost()637 public void testTwoSimilarItemsMultipleHosts_destroyOnlyDetachesFromOneHost() { 638 Foo foo1 = new Foo(); 639 Foo foo2 = new Foo(); 640 641 Foo.KEY.attachToHost(mHost1, foo1); 642 Foo.KEY.attachToHost(mHost1, foo2); 643 Foo.KEY.attachToHost(mHost2, foo2); 644 Foo.KEY.attachToHost(mHost2, foo1); 645 runUntilIdle(); 646 647 foo1.assertDetachedHostsMatch(mHost1); 648 foo2.assertDetachedHostsMatch(mHost2); 649 assertEquals(foo2, Foo.KEY.retrieveDataFromHost(mHost1)); 650 assertEquals(foo1, Foo.KEY.retrieveDataFromHost(mHost2)); 651 652 mHost1.destroy(); 653 runUntilIdle(); 654 655 foo1.assertDetachedHostsMatch(mHost1); 656 foo2.assertDetachedHostsMatch(mHost2, mHost1); 657 assertTrue(mHost1.isDestroyed()); 658 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 659 assertTrue(Foo.KEY.isAttachedToHost(mHost2)); 660 assertEquals(foo1, Foo.KEY.retrieveDataFromHost(mHost2)); 661 662 mHost2.destroy(); 663 runUntilIdle(); 664 665 foo1.assertDetachedHostsMatch(mHost1, mHost2); 666 foo2.assertDetachedHostsMatch(mHost2, mHost1); 667 assertTrue(mHost2.isDestroyed()); 668 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 669 assertFalse(Foo.KEY.isAttachedToHost(mHost2)); 670 } 671 672 @Test 673 public void testTwoSimilarItemsMultipleHosts_destroyShouldOnlyRemoveFromCurrentHostWithMultipleKeys()674 testTwoSimilarItemsMultipleHosts_destroyShouldOnlyRemoveFromCurrentHostWithMultipleKeys() { 675 Foo foo1 = new Foo(); 676 Foo foo2 = new Foo(); 677 678 UnownedUserDataKey<Foo> foo1key = new UnownedUserDataKey<>(Foo.class); 679 UnownedUserDataKey<Foo> foo2key = new UnownedUserDataKey<>(Foo.class); 680 681 foo1key.attachToHost(mHost1, foo1); 682 foo2key.attachToHost(mHost1, foo2); 683 runUntilIdle(); 684 685 foo1.assertNoDetachedHosts(); 686 foo2.assertNoDetachedHosts(); 687 assertTrue(foo1key.isAttachedToHost(mHost1)); 688 assertTrue(foo2key.isAttachedToHost(mHost1)); 689 assertTrue(foo1key.isAttachedToAnyHost(foo1)); 690 assertTrue(foo2key.isAttachedToAnyHost(foo2)); 691 assertEquals(foo1, foo1key.retrieveDataFromHost(mHost1)); 692 assertEquals(foo2, foo2key.retrieveDataFromHost(mHost1)); 693 694 // Since `foo1` is attached through `foo1key` and `foo2` is attached through `foo2key`, it 695 // should not be possible to look up whether an object not attached through is own key is 696 // attached to any host. 697 assertFalse(foo1key.isAttachedToAnyHost(foo2)); 698 assertFalse(foo2key.isAttachedToAnyHost(foo1)); 699 700 foo1key.attachToHost(mHost2, foo1); 701 foo2key.attachToHost(mHost2, foo2); 702 runUntilIdle(); 703 704 foo1.assertNoDetachedHosts(); 705 foo2.assertNoDetachedHosts(); 706 assertEquals(foo1, foo1key.retrieveDataFromHost(mHost2)); 707 assertEquals(foo2, foo2key.retrieveDataFromHost(mHost2)); 708 709 mHost1.destroy(); 710 runUntilIdle(); 711 712 foo1.assertDetachedHostsMatch(mHost1); 713 foo2.assertDetachedHostsMatch(mHost1); 714 assertTrue(mHost1.isDestroyed()); 715 assertFalse(foo1key.isAttachedToHost(mHost1)); 716 assertFalse(foo2key.isAttachedToHost(mHost1)); 717 assertTrue(foo1key.isAttachedToHost(mHost2)); 718 assertTrue(foo2key.isAttachedToHost(mHost2)); 719 720 mHost2.destroy(); 721 runUntilIdle(); 722 723 foo1.assertDetachedHostsMatch(mHost1, mHost2); 724 foo2.assertDetachedHostsMatch(mHost1, mHost2); 725 assertTrue(mHost2.isDestroyed()); 726 assertFalse(foo1key.isAttachedToHost(mHost1)); 727 assertFalse(foo2key.isAttachedToHost(mHost1)); 728 assertFalse(foo1key.isAttachedToHost(mHost2)); 729 assertFalse(foo2key.isAttachedToHost(mHost2)); 730 } 731 732 @Test testTwoDifferentItemsSingleHost_attachAndDetach()733 public void testTwoDifferentItemsSingleHost_attachAndDetach() { 734 Foo.KEY.attachToHost(mHost1, mFoo); 735 Bar.KEY.attachToHost(mHost1, mBar); 736 runUntilIdle(); 737 738 mFoo.assertNoDetachedHosts(); 739 mBar.assertNoDetachedHosts(); 740 assertTrue(Bar.KEY.isAttachedToHost(mHost1)); 741 assertTrue(Bar.KEY.isAttachedToAnyHost(mBar)); 742 assertEquals(mBar, Bar.KEY.retrieveDataFromHost(mHost1)); 743 744 Foo.KEY.detachFromHost(mHost1); 745 runUntilIdle(); 746 747 mFoo.assertDetachedHostsMatch(mHost1); 748 mBar.assertNoDetachedHosts(); 749 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 750 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 751 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 752 753 Bar.KEY.detachFromHost(mHost1); 754 runUntilIdle(); 755 756 mFoo.assertDetachedHostsMatch(mHost1); 757 mBar.assertDetachedHostsMatch(mHost1); 758 assertFalse(Bar.KEY.isAttachedToHost(mHost1)); 759 assertFalse(Bar.KEY.isAttachedToAnyHost(mBar)); 760 assertNull(Bar.KEY.retrieveDataFromHost(mHost1)); 761 } 762 763 @Test testTwoDifferentItemsSingleHost_attachAndGarbageCollectionReturnsNull()764 public void testTwoDifferentItemsSingleHost_attachAndGarbageCollectionReturnsNull() { 765 Foo foo = new Foo(); 766 Bar bar = new Bar(); 767 768 Foo.KEY.attachToHost(mHost1, foo); 769 Bar.KEY.attachToHost(mHost1, bar); 770 runUntilIdle(); 771 772 foo.assertNoDetachedHosts(); 773 bar.assertNoDetachedHosts(); 774 assertTrue(Foo.KEY.isAttachedToHost(mHost1)); 775 assertTrue(Foo.KEY.isAttachedToAnyHost(foo)); 776 assertEquals(foo, Foo.KEY.retrieveDataFromHost(mHost1)); 777 assertTrue(Bar.KEY.isAttachedToHost(mHost1)); 778 assertTrue(Bar.KEY.isAttachedToAnyHost(bar)); 779 assertEquals(bar, Bar.KEY.retrieveDataFromHost(mHost1)); 780 781 // Intentionally null out `foo` to make it eligible for garbage collection. 782 foo = null; 783 forceGC(); 784 runUntilIdle(); 785 786 bar.assertNoDetachedHosts(); 787 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 788 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 789 assertTrue(Bar.KEY.isAttachedToHost(mHost1)); 790 assertTrue(Bar.KEY.isAttachedToAnyHost(bar)); 791 assertEquals(bar, Bar.KEY.retrieveDataFromHost(mHost1)); 792 793 // NOTE: We can not verify anything using the `foo` variable here, since it has been 794 // garbage collected. 795 796 // Intentionally null out `bar` to make it eligible for garbage collection. 797 bar = null; 798 forceGC(); 799 runUntilIdle(); 800 801 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 802 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 803 assertFalse(Bar.KEY.isAttachedToHost(mHost1)); 804 assertNull(Bar.KEY.retrieveDataFromHost(mHost1)); 805 806 // NOTE: We can not verify anything using the `bar` variable here, since it has been 807 // garbage collected. 808 } 809 810 @Test testTwoDifferentItemsSingleHost_destroyWithMultipleEntriesLeft()811 public void testTwoDifferentItemsSingleHost_destroyWithMultipleEntriesLeft() { 812 Foo.KEY.attachToHost(mHost1, mFoo); 813 Bar.KEY.attachToHost(mHost1, mBar); 814 815 // Since destruction happens by iterating over all entries and letting themselves detach 816 // which results in removing themselves from the map, ensure that there are no issues with 817 // concurrent modifications during the iteration over the map. 818 mHost1.destroy(); 819 runUntilIdle(); 820 821 mFoo.assertDetachedHostsMatch(mHost1); 822 mBar.assertDetachedHostsMatch(mHost1); 823 assertTrue(mHost1.isDestroyed()); 824 assertFalse(Foo.KEY.isAttachedToHost(mHost1)); 825 assertFalse(Foo.KEY.isAttachedToAnyHost(mFoo)); 826 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 827 assertFalse(Bar.KEY.isAttachedToHost(mHost1)); 828 assertFalse(Bar.KEY.isAttachedToAnyHost(mBar)); 829 assertNull(Bar.KEY.retrieveDataFromHost(mHost1)); 830 } 831 832 @Test testSingleThreadPolicy()833 public void testSingleThreadPolicy() throws Exception { 834 Foo.KEY.attachToHost(mHost1, mFoo); 835 836 FutureTask<Void> getTask = 837 new FutureTask<>( 838 () -> assertAsserts(() -> Foo.KEY.retrieveDataFromHost(mHost1)), null); 839 PostTask.postTask(TaskTraits.USER_VISIBLE, getTask); 840 getTask.get(); 841 842 // Manual cleanup to ensure we can verify host map size during tear down. 843 Foo.KEY.detachFromAllHosts(mFoo); 844 } 845 846 @Test testNullKeyOrDataShouldBeDisallowed()847 public void testNullKeyOrDataShouldBeDisallowed() { 848 assertThrows(NullPointerException.class, () -> Foo.KEY.attachToHost(null, null)); 849 assertThrows(NullPointerException.class, () -> Foo.KEY.attachToHost(mHost1, null)); 850 assertThrows(NullPointerException.class, () -> Foo.KEY.attachToHost(null, mFoo)); 851 852 // Need a non-empty registry to avoid no-op. 853 Foo.KEY.attachToHost(mHost1, mFoo); 854 assertThrows(NullPointerException.class, () -> Foo.KEY.retrieveDataFromHost(null)); 855 856 assertThrows(NullPointerException.class, () -> Foo.KEY.detachFromHost(null)); 857 assertThrows(NullPointerException.class, () -> Foo.KEY.detachFromAllHosts(null)); 858 Foo.KEY.detachFromAllHosts(mFoo); 859 } 860 861 @Test testHost_operationsDisallowedAfterDestroy()862 public void testHost_operationsDisallowedAfterDestroy() { 863 Foo.KEY.attachToHost(mHost1, mFoo); 864 865 mHost1.destroy(); 866 runUntilIdle(); 867 868 mFoo.assertDetachedHostsMatch(mHost1); 869 assertTrue(mHost1.isDestroyed()); 870 871 assertThrows(AssertionError.class, () -> Foo.KEY.attachToHost(mHost1, mFoo)); 872 873 // The following operation gracefully returns null. 874 assertNull(Foo.KEY.retrieveDataFromHost(mHost1)); 875 876 // The following operations gracefully ignores the invocation. 877 Foo.KEY.detachFromHost(mHost1); 878 Foo.KEY.detachFromAllHosts(mFoo); 879 runUntilIdle(); 880 881 mFoo.assertDetachedHostsMatch(mHost1); 882 } 883 884 @Test testHost_garbageCollection()885 public void testHost_garbageCollection() { 886 UnownedUserDataHost extraHost = 887 new UnownedUserDataHost(new Handler(Looper.getMainLooper())); 888 889 Foo.KEY.attachToHost(mHost1, mFoo); 890 Foo.KEY.attachToHost(extraHost, mFoo); 891 892 // Intentionally null out `host` to make it eligible for garbage collection. 893 extraHost = null; 894 forceGC(); 895 896 // Should not fail to retrieve the object. 897 assertTrue(Foo.KEY.isAttachedToHost(mHost1)); 898 assertEquals(mFoo, Foo.KEY.retrieveDataFromHost(mHost1)); 899 // There should now only be 1 host attachment left after the retrieval. 900 assertEquals(1, Foo.KEY.getHostAttachmentCount(mFoo)); 901 902 // NOTE: We can not verify anything using the `extraHost` variable here, since it has been 903 // garbage collected. 904 905 // Manual cleanup to ensure we can verify host map size during tear down. 906 Foo.KEY.detachFromAllHosts(mFoo); 907 } 908 assertThrows(Class<E> exceptionType, Runnable runnable)909 private <E extends Throwable> void assertThrows(Class<E> exceptionType, Runnable runnable) { 910 Throwable actualException = null; 911 try { 912 runnable.run(); 913 } catch (Throwable e) { 914 actualException = e; 915 } 916 assertNotNull("Exception not thrown", actualException); 917 assertEquals(exceptionType, actualException.getClass()); 918 } 919 assertAsserts(Runnable runnable)920 private void assertAsserts(Runnable runnable) { 921 // When DCHECK is off, asserts are stripped. 922 if (!BuildConfig.ENABLE_ASSERTS) return; 923 924 try { 925 runnable.run(); 926 throw new RuntimeException("Assertion should fail."); 927 } catch (AssertionError e) { 928 // Ignore. We expect this to happen. 929 } 930 } 931 runUntilIdle()932 private static void runUntilIdle() { 933 Shadows.shadowOf(Looper.getMainLooper()).idle(); 934 } 935 } 936