• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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