• 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.metrics;
6 
7 import static org.hamcrest.Matchers.greaterThan;
8 import static org.hamcrest.Matchers.is;
9 import static org.junit.Assert.assertEquals;
10 import static org.junit.Assert.assertThat;
11 import static org.junit.Assert.fail;
12 import static org.mockito.Mockito.times;
13 import static org.mockito.Mockito.verify;
14 import static org.mockito.Mockito.verifyNoMoreInteractions;
15 
16 import androidx.test.filters.MediumTest;
17 
18 import org.junit.Assert;
19 import org.junit.Before;
20 import org.junit.Test;
21 import org.junit.runner.RunWith;
22 import org.mockito.Mock;
23 import org.mockito.Mockito;
24 import org.mockito.MockitoAnnotations;
25 
26 import org.chromium.base.Callback;
27 import org.chromium.base.test.BaseRobolectricTestRunner;
28 
29 import java.time.Duration;
30 import java.time.Instant;
31 import java.util.List;
32 import java.util.concurrent.atomic.AtomicInteger;
33 import java.util.concurrent.atomic.AtomicIntegerArray;
34 import java.util.concurrent.locks.Lock;
35 import java.util.concurrent.locks.ReentrantLock;
36 
37 /** Unit tests for {@link CachingUmaRecorderTest}. */
38 @RunWith(BaseRobolectricTestRunner.class)
39 @SuppressWarnings("DoNotMock") // Ok to mock UmaRecorder since this is testing metrics.
40 public final class CachingUmaRecorderTest {
41     @Mock UmaRecorder mUmaRecorder;
42 
43     @Before
initMocks()44     public void initMocks() {
45         MockitoAnnotations.initMocks(this);
46     }
47 
48     @Test
testSetDelegateWithEmptyCache()49     public void testSetDelegateWithEmptyCache() {
50         CachingUmaRecorder cachingUmaRecorder = new CachingUmaRecorder();
51 
52         cachingUmaRecorder.setDelegate(mUmaRecorder);
53 
54         verifyNoMoreInteractions(mUmaRecorder);
55     }
56 
57     @Test
testRecordBooleanHistogramGetsFlushed()58     public void testRecordBooleanHistogramGetsFlushed() {
59         CachingUmaRecorder cachingUmaRecorder = new CachingUmaRecorder();
60 
61         cachingUmaRecorder.recordBooleanHistogram(
62                 "cachingUmaRecorderTest.recordBooleanHistogram", true);
63         cachingUmaRecorder.recordBooleanHistogram(
64                 "cachingUmaRecorderTest.recordBooleanHistogram", true);
65         cachingUmaRecorder.recordBooleanHistogram(
66                 "cachingUmaRecorderTest.recordBooleanHistogram", false);
67 
68         assertEquals(
69                 3,
70                 cachingUmaRecorder.getHistogramTotalCountForTesting(
71                         "cachingUmaRecorderTest.recordBooleanHistogram"));
72         assertEquals(
73                 2,
74                 cachingUmaRecorder.getHistogramValueCountForTesting(
75                         "cachingUmaRecorderTest.recordBooleanHistogram", 1));
76         assertEquals(
77                 1,
78                 cachingUmaRecorder.getHistogramValueCountForTesting(
79                         "cachingUmaRecorderTest.recordBooleanHistogram", 0));
80 
81         cachingUmaRecorder.setDelegate(mUmaRecorder);
82 
83         verify(mUmaRecorder, times(2))
84                 .recordBooleanHistogram("cachingUmaRecorderTest.recordBooleanHistogram", true);
85         verify(mUmaRecorder)
86                 .recordBooleanHistogram("cachingUmaRecorderTest.recordBooleanHistogram", false);
87     }
88 
89     @Test
testRecordExponentialHistogramGetsFlushed()90     public void testRecordExponentialHistogramGetsFlushed() {
91         CachingUmaRecorder cachingUmaRecorder = new CachingUmaRecorder();
92 
93         cachingUmaRecorder.recordExponentialHistogram(
94                 "cachingUmaRecorderTest.recordExponentialHistogram", 72, 1, 1000, 50);
95         assertEquals(
96                 1,
97                 cachingUmaRecorder.getHistogramTotalCountForTesting(
98                         "cachingUmaRecorderTest.recordExponentialHistogram"));
99         assertEquals(
100                 1,
101                 cachingUmaRecorder.getHistogramValueCountForTesting(
102                         "cachingUmaRecorderTest.recordExponentialHistogram", 72));
103         cachingUmaRecorder.setDelegate(mUmaRecorder);
104 
105         verify(mUmaRecorder)
106                 .recordExponentialHistogram(
107                         "cachingUmaRecorderTest.recordExponentialHistogram", 72, 1, 1000, 50);
108     }
109 
110     @Test
testRecordLinearHistogramGetsFlushed()111     public void testRecordLinearHistogramGetsFlushed() {
112         CachingUmaRecorder cachingUmaRecorder = new CachingUmaRecorder();
113 
114         cachingUmaRecorder.recordLinearHistogram(
115                 "cachingUmaRecorderTest.recordLinearHistogram", 72, 1, 1000, 50);
116         assertEquals(
117                 1,
118                 cachingUmaRecorder.getHistogramTotalCountForTesting(
119                         "cachingUmaRecorderTest.recordLinearHistogram"));
120         assertEquals(
121                 1,
122                 cachingUmaRecorder.getHistogramValueCountForTesting(
123                         "cachingUmaRecorderTest.recordLinearHistogram", 72));
124 
125         cachingUmaRecorder.setDelegate(mUmaRecorder);
126 
127         verify(mUmaRecorder)
128                 .recordLinearHistogram(
129                         "cachingUmaRecorderTest.recordLinearHistogram", 72, 1, 1000, 50);
130     }
131 
132     @Test
testRecordSparseHistogramGetsFlushed()133     public void testRecordSparseHistogramGetsFlushed() {
134         CachingUmaRecorder cachingUmaRecorder = new CachingUmaRecorder();
135 
136         cachingUmaRecorder.recordSparseHistogram(
137                 "cachingUmaRecorderTest.recordSparseHistogram", 72);
138         assertEquals(
139                 1,
140                 cachingUmaRecorder.getHistogramTotalCountForTesting(
141                         "cachingUmaRecorderTest.recordSparseHistogram"));
142         assertEquals(
143                 1,
144                 cachingUmaRecorder.getHistogramValueCountForTesting(
145                         "cachingUmaRecorderTest.recordSparseHistogram", 72));
146         cachingUmaRecorder.setDelegate(mUmaRecorder);
147 
148         verify(mUmaRecorder)
149                 .recordSparseHistogram("cachingUmaRecorderTest.recordSparseHistogram", 72);
150     }
151 
152     @Test
testRecordUserActionGetsFlushed()153     public void testRecordUserActionGetsFlushed() {
154         CachingUmaRecorder cachingUmaRecorder = new CachingUmaRecorder();
155 
156         cachingUmaRecorder.recordUserAction("FlushedActionTested", 72);
157         cachingUmaRecorder.setDelegate(mUmaRecorder);
158 
159         verify(mUmaRecorder).recordUserAction("FlushedActionTested", 72);
160     }
161 
162     @Test
testRecordUserActionGetsMultipleFlushed()163     public void testRecordUserActionGetsMultipleFlushed() {
164         CachingUmaRecorder cachingUmaRecorder = new CachingUmaRecorder();
165 
166         cachingUmaRecorder.recordUserAction("FlushedActionTested.A", 111);
167         cachingUmaRecorder.recordUserAction("FlushedActionTested.B", 222);
168         cachingUmaRecorder.recordUserAction("FlushedActionTested.B", 333);
169         cachingUmaRecorder.setDelegate(mUmaRecorder);
170 
171         verify(mUmaRecorder).recordUserAction("FlushedActionTested.A", 111);
172         verify(mUmaRecorder).recordUserAction("FlushedActionTested.B", 222);
173         verify(mUmaRecorder).recordUserAction("FlushedActionTested.B", 333);
174     }
175 
176     @Test
testRecordBooleanHistogramDelegated()177     public void testRecordBooleanHistogramDelegated() {
178         CachingUmaRecorder cachingUmaRecorder = new CachingUmaRecorder();
179         cachingUmaRecorder.setDelegate(mUmaRecorder);
180 
181         cachingUmaRecorder.recordBooleanHistogram(
182                 "CachingUmaRecorderTest.recordBooleanHistogram", true);
183 
184         verify(mUmaRecorder)
185                 .recordBooleanHistogram("CachingUmaRecorderTest.recordBooleanHistogram", true);
186         verifyNoMoreInteractions(mUmaRecorder);
187     }
188 
189     @Test
testRecordExponentialHistogramDelegated()190     public void testRecordExponentialHistogramDelegated() {
191         CachingUmaRecorder cachingUmaRecorder = new CachingUmaRecorder();
192         cachingUmaRecorder.setDelegate(mUmaRecorder);
193 
194         cachingUmaRecorder.recordExponentialHistogram(
195                 "CachingUmaRecorderTest.recordExponentialHistogram", 1, 2, 10, 5);
196 
197         verify(mUmaRecorder)
198                 .recordExponentialHistogram(
199                         "CachingUmaRecorderTest.recordExponentialHistogram", 1, 2, 10, 5);
200         verifyNoMoreInteractions(mUmaRecorder);
201     }
202 
203     @Test
testRecordLinearHistogramDelegated()204     public void testRecordLinearHistogramDelegated() {
205         CachingUmaRecorder cachingUmaRecorder = new CachingUmaRecorder();
206         cachingUmaRecorder.setDelegate(mUmaRecorder);
207 
208         cachingUmaRecorder.recordLinearHistogram(
209                 "CachingUmaRecorderTest.recordLinearHistogram", 1, 2, 10, 5);
210 
211         verify(mUmaRecorder)
212                 .recordLinearHistogram("CachingUmaRecorderTest.recordLinearHistogram", 1, 2, 10, 5);
213         verifyNoMoreInteractions(mUmaRecorder);
214     }
215 
216     @Test
testRecordSparseHistogramDelegated()217     public void testRecordSparseHistogramDelegated() {
218         CachingUmaRecorder cachingUmaRecorder = new CachingUmaRecorder();
219         cachingUmaRecorder.setDelegate(mUmaRecorder);
220 
221         cachingUmaRecorder.recordSparseHistogram(
222                 "CachingUmaRecorderTest.recordSparseHistogram", 72);
223 
224         verify(mUmaRecorder)
225                 .recordSparseHistogram("CachingUmaRecorderTest.recordSparseHistogram", 72);
226         verifyNoMoreInteractions(mUmaRecorder);
227     }
228 
229     @Test
testRecordUserActionDelegated()230     public void testRecordUserActionDelegated() {
231         CachingUmaRecorder cachingUmaRecorder = new CachingUmaRecorder();
232         cachingUmaRecorder.setDelegate(mUmaRecorder);
233 
234         cachingUmaRecorder.recordUserAction("ActionTested", 72);
235 
236         verify(mUmaRecorder).recordUserAction("ActionTested", 72);
237         verifyNoMoreInteractions(mUmaRecorder);
238     }
239 
240     @Test
testSetDelegateStopsOldDelegation()241     public void testSetDelegateStopsOldDelegation() {
242         CachingUmaRecorder cachingUmaRecorder = new CachingUmaRecorder();
243         cachingUmaRecorder.setDelegate(mUmaRecorder);
244 
245         cachingUmaRecorder.setDelegate(new NoopUmaRecorder());
246         cachingUmaRecorder.recordSparseHistogram("CachingUmaRecorderTest.stopOldDelegation", 72);
247 
248         verifyNoMoreInteractions(mUmaRecorder);
249     }
250 
251     @Test
testSetDelegateStartsNewDelegation()252     public void testSetDelegateStartsNewDelegation() {
253         CachingUmaRecorder cachingUmaRecorder = new CachingUmaRecorder();
254         cachingUmaRecorder.setDelegate(new NoopUmaRecorder());
255 
256         cachingUmaRecorder.setDelegate(mUmaRecorder);
257         cachingUmaRecorder.recordSparseHistogram("CachingUmaRecorderTest.startNewDelegation", 72);
258 
259         verify(mUmaRecorder).recordSparseHistogram("CachingUmaRecorderTest.startNewDelegation", 72);
260         verifyNoMoreInteractions(mUmaRecorder);
261     }
262 
263     /**
264      * {@link UmaRecorder} that has a public lock which can be used to block {@link
265      * #recordSparseHistogram(String, int)} }.
266      */
267     private static class BlockingUmaRecorder extends NoopUmaRecorder {
268         public Lock lock = new ReentrantLock();
269 
270         @SuppressWarnings("LockNotBeforeTry")
271         @Override
recordSparseHistogram(String name, int sample)272         public void recordSparseHistogram(String name, int sample) {
273             lock.lock();
274             lock.unlock();
275         }
276     }
277 
278     @Test
testSetDelegateBlocksUntilRecordingDone()279     public void testSetDelegateBlocksUntilRecordingDone() throws Exception {
280         CachingUmaRecorder cachingUmaRecorder = new CachingUmaRecorder();
281         BlockingUmaRecorder blockingUmaRecorder = new BlockingUmaRecorder();
282         cachingUmaRecorder.setDelegate(blockingUmaRecorder);
283 
284         Thread swappingThread;
285         Thread recordingThread;
286 
287         blockingUmaRecorder.lock.lock();
288         try {
289             recordingThread =
290                     new Thread(
291                             () -> {
292                                 cachingUmaRecorder.recordSparseHistogram(
293                                         "CachingUmaRecorderTest.blockUntilRecordingDone", 16);
294                             });
295             recordingThread.start();
296             awaitThreadBlocked(recordingThread, Duration.ofSeconds(1));
297 
298             swappingThread =
299                     new Thread(
300                             () -> {
301                                 cachingUmaRecorder.setDelegate(mUmaRecorder);
302                             });
303             swappingThread.start();
304             awaitThreadBlocked(swappingThread, Duration.ofSeconds(1));
305         } finally {
306             blockingUmaRecorder.lock.unlock();
307         }
308 
309         recordingThread.join();
310         swappingThread.join();
311         verifyNoMoreInteractions(mUmaRecorder);
312     }
313 
314     @SuppressWarnings("ThreadPriorityCheck")
awaitThreadBlocked(Thread thread, Duration timeLimit)315     private static void awaitThreadBlocked(Thread thread, Duration timeLimit) {
316         final Instant start = Instant.now();
317 
318         while (true) {
319             assertThat(timeLimit, greaterThan(Duration.between(start, Instant.now())));
320             switch (thread.getState()) {
321                 case BLOCKED:
322                 case WAITING:
323                 case TIMED_WAITING:
324                     return;
325                 case NEW:
326                 case RUNNABLE:
327                     {
328                         Thread.yield();
329                         continue;
330                     }
331                 case TERMINATED:
332                     fail("Thread unexpectedly terminated.");
333             }
334         }
335     }
336 
337     private static class HistogramTestingUmaRecorder implements UmaRecorder {
338         public final AtomicIntegerArray recordedSamples;
339 
HistogramTestingUmaRecorder(int sampleRange)340         HistogramTestingUmaRecorder(int sampleRange) {
341             recordedSamples = new AtomicIntegerArray(sampleRange);
342         }
343 
344         @Override
recordBooleanHistogram(String name, boolean sample)345         public void recordBooleanHistogram(String name, boolean sample) {
346             throw new UnsupportedOperationException();
347         }
348 
349         @Override
recordExponentialHistogram( String name, int sample, int min, int max, int numBuckets)350         public void recordExponentialHistogram(
351                 String name, int sample, int min, int max, int numBuckets) {
352             throw new UnsupportedOperationException();
353         }
354 
355         @Override
recordLinearHistogram( String name, int sample, int min, int max, int numBuckets)356         public void recordLinearHistogram(
357                 String name, int sample, int min, int max, int numBuckets) {
358             throw new UnsupportedOperationException();
359         }
360 
361         @Override
recordSparseHistogram(String name, int sample)362         public void recordSparseHistogram(String name, int sample) {
363             recordedSamples.addAndGet(sample, 1);
364         }
365 
366         @Override
recordUserAction(String name, long elapsedRealtimeMillis)367         public void recordUserAction(String name, long elapsedRealtimeMillis) {
368             throw new UnsupportedOperationException();
369         }
370 
371         @Override
getHistogramValueCountForTesting(String name, int sample)372         public int getHistogramValueCountForTesting(String name, int sample) {
373             throw new UnsupportedOperationException();
374         }
375 
376         @Override
getHistogramTotalCountForTesting(String name)377         public int getHistogramTotalCountForTesting(String name) {
378             throw new UnsupportedOperationException();
379         }
380 
381         @Override
getHistogramSamplesForTesting(String name)382         public List<HistogramBucket> getHistogramSamplesForTesting(String name) {
383             throw new UnsupportedOperationException();
384         }
385 
386         @Override
addUserActionCallbackForTesting(Callback<String> callback)387         public void addUserActionCallbackForTesting(Callback<String> callback) {
388             throw new UnsupportedOperationException();
389         }
390 
391         @Override
removeUserActionCallbackForTesting(Callback<String> callback)392         public void removeUserActionCallbackForTesting(Callback<String> callback) {
393             throw new UnsupportedOperationException();
394         }
395     }
396 
397     @Test
398     @MediumTest
399     @SuppressWarnings("ThreadPriorityCheck")
testStressParallelHistograms()400     public void testStressParallelHistograms() throws Exception {
401         final int numThreads = 16;
402         final int numRecorders = 16;
403         final int numSamples = CachingUmaRecorder.Histogram.MAX_SAMPLE_COUNT / numThreads;
404         CachingUmaRecorder cachingRecorder = new CachingUmaRecorder();
405 
406         HistogramTestingUmaRecorder[] testingRecorders =
407                 new HistogramTestingUmaRecorder[numRecorders];
408         for (int i = 0; i < numRecorders; i++) {
409             testingRecorders[i] = new HistogramTestingUmaRecorder(numThreads);
410         }
411         Thread[] threads = new Thread[numThreads];
412         // Each thread records a different sample value.
413         for (int i = 0; i < numThreads; i++) {
414             threads[i] = startHistogramRecordingThread(i, numSamples, cachingRecorder);
415         }
416         // Change recorders while threads are recording metrics.
417         for (HistogramTestingUmaRecorder recorder : testingRecorders) {
418             cachingRecorder.setDelegate(recorder);
419             Thread.yield();
420         }
421         for (Thread thread : threads) {
422             thread.join();
423         }
424         cachingRecorder.setDelegate(mUmaRecorder);
425         verifyNoMoreInteractions(mUmaRecorder);
426         for (int i = 0; i < numThreads; i++) {
427             int actualSamples = 0;
428             for (HistogramTestingUmaRecorder recorder : testingRecorders) {
429                 actualSamples += recorder.recordedSamples.get(i);
430             }
431             assertThat(String.format("thread[%d] total samples", i), actualSamples, is(numSamples));
432         }
433     }
434 
435     @SuppressWarnings("ThreadPriorityCheck")
startHistogramRecordingThread( int sample, int count, UmaRecorder recorder)436     private static Thread startHistogramRecordingThread(
437             int sample, int count, UmaRecorder recorder) {
438         Thread thread =
439                 new Thread(
440                         () -> {
441                             for (int i = 0; i < count; i++) {
442                                 recorder.recordSparseHistogram("StressTest", sample);
443                                 // Make it more likely this thread will be preempted.
444                                 Thread.yield();
445                             }
446                         });
447         thread.start();
448         return thread;
449     }
450 
451     private static class UserActionTestingUmaRecorder implements UmaRecorder {
452         public final AtomicIntegerArray recordedSamples;
453 
UserActionTestingUmaRecorder(int sampleRange)454         UserActionTestingUmaRecorder(int sampleRange) {
455             recordedSamples = new AtomicIntegerArray(sampleRange);
456         }
457 
458         @Override
recordBooleanHistogram(String name, boolean sample)459         public void recordBooleanHistogram(String name, boolean sample) {
460             throw new UnsupportedOperationException();
461         }
462 
463         @Override
recordExponentialHistogram( String name, int sample, int min, int max, int numBuckets)464         public void recordExponentialHistogram(
465                 String name, int sample, int min, int max, int numBuckets) {
466             throw new UnsupportedOperationException();
467         }
468 
469         @Override
recordLinearHistogram( String name, int sample, int min, int max, int numBuckets)470         public void recordLinearHistogram(
471                 String name, int sample, int min, int max, int numBuckets) {
472             throw new UnsupportedOperationException();
473         }
474 
475         @Override
recordSparseHistogram(String name, int sample)476         public void recordSparseHistogram(String name, int sample) {
477             throw new UnsupportedOperationException();
478         }
479 
480         @Override
recordUserAction(String name, long elapsedRealtimeMillis)481         public void recordUserAction(String name, long elapsedRealtimeMillis) {
482             recordedSamples.addAndGet((int) elapsedRealtimeMillis, 1);
483         }
484 
485         @Override
getHistogramValueCountForTesting(String name, int sample)486         public int getHistogramValueCountForTesting(String name, int sample) {
487             throw new UnsupportedOperationException();
488         }
489 
490         @Override
getHistogramTotalCountForTesting(String name)491         public int getHistogramTotalCountForTesting(String name) {
492             throw new UnsupportedOperationException();
493         }
494 
495         @Override
getHistogramSamplesForTesting(String name)496         public List<HistogramBucket> getHistogramSamplesForTesting(String name) {
497             throw new UnsupportedOperationException();
498         }
499 
500         @Override
addUserActionCallbackForTesting(Callback<String> callback)501         public void addUserActionCallbackForTesting(Callback<String> callback) {
502             throw new UnsupportedOperationException();
503         }
504 
505         @Override
removeUserActionCallbackForTesting(Callback<String> callback)506         public void removeUserActionCallbackForTesting(Callback<String> callback) {
507             throw new UnsupportedOperationException();
508         }
509     }
510 
511     @Test
512     @MediumTest
513     @SuppressWarnings("ThreadPriorityCheck")
testStressParallelUserActions()514     public void testStressParallelUserActions() throws Exception {
515         final int numThreads = 16;
516         final int numRecorders = 16;
517         final int numSamples = CachingUmaRecorder.MAX_USER_ACTION_COUNT / numThreads;
518         CachingUmaRecorder cachingRecorder = new CachingUmaRecorder();
519 
520         UserActionTestingUmaRecorder[] testingRecorders =
521                 new UserActionTestingUmaRecorder[numRecorders];
522         for (int i = 0; i < numRecorders; i++) {
523             testingRecorders[i] = new UserActionTestingUmaRecorder(numThreads);
524         }
525         Thread[] threads = new Thread[numThreads];
526         // Each thread records a different timestamp value.
527         for (int i = 0; i < numThreads; i++) {
528             threads[i] = startUserActionRecordingThread(i, numSamples, cachingRecorder);
529         }
530         // Change recorders while threads are recording metrics.
531         for (UserActionTestingUmaRecorder recorder : testingRecorders) {
532             cachingRecorder.setDelegate(recorder);
533             Thread.yield();
534         }
535         for (Thread thread : threads) {
536             thread.join();
537         }
538         cachingRecorder.setDelegate(mUmaRecorder);
539         verifyNoMoreInteractions(mUmaRecorder);
540         for (int i = 0; i < numThreads; i++) {
541             int actualSamples = 0;
542             for (UserActionTestingUmaRecorder recorder : testingRecorders) {
543                 actualSamples += recorder.recordedSamples.get(i);
544             }
545             assertThat(String.format("thread[%d] total samples", i), actualSamples, is(numSamples));
546         }
547     }
548 
549     @SuppressWarnings("ThreadPriorityCheck")
startUserActionRecordingThread( int sample, int count, UmaRecorder recorder)550     private static Thread startUserActionRecordingThread(
551             int sample, int count, UmaRecorder recorder) {
552         Thread thread =
553                 new Thread(
554                         () -> {
555                             for (int i = 0; i < count; i++) {
556                                 recorder.recordUserAction("StressTestUserAction." + i, sample);
557                                 // Make it more likely this thread will be preempted.
558                                 Thread.yield();
559                             }
560                         });
561         thread.start();
562         return thread;
563     }
564 
565     @Test
testUserActionCallbacksTriggeredWithoutDelegate()566     public void testUserActionCallbacksTriggeredWithoutDelegate() {
567         CachingUmaRecorder cachingRecorder = new CachingUmaRecorder();
568 
569         AtomicInteger callbackCount = new AtomicInteger();
570         Callback<String> testCallback1 = (result) -> callbackCount.incrementAndGet();
571         cachingRecorder.addUserActionCallbackForTesting(testCallback1);
572 
573         // Ensure that the callback is notified if attached to the caching recorder (will not
574         // be notified once a delegate is responsible).
575         Assert.assertEquals(0, callbackCount.get());
576         cachingRecorder.recordUserAction("TEST", 0);
577         Assert.assertEquals(1, callbackCount.get());
578         cachingRecorder.recordUserAction("TEST", 0);
579         Assert.assertEquals(2, callbackCount.get());
580 
581         // Remove the callback and ensure it is no longer called.
582         cachingRecorder.removeUserActionCallbackForTesting(testCallback1);
583         Assert.assertEquals(2, callbackCount.get());
584         cachingRecorder.recordUserAction("TEST", 0);
585         Assert.assertEquals(2, callbackCount.get());
586     }
587 
588     @Test
testUserActionCallbacksSwappedBetweenDelegates()589     public void testUserActionCallbacksSwappedBetweenDelegates() {
590         CachingUmaRecorder cachingRecorder = new CachingUmaRecorder();
591 
592         Callback<String> testCallback1 = (result) -> {};
593         Callback<String> testCallback2 = (result) -> {};
594         cachingRecorder.addUserActionCallbackForTesting(testCallback1);
595         cachingRecorder.addUserActionCallbackForTesting(testCallback2);
596 
597         // Validate that previously added callbacks are passed to the new delegate.
598         UmaRecorder delegate1 = Mockito.mock(UmaRecorder.class);
599         cachingRecorder.setDelegate(delegate1);
600         verify(delegate1).addUserActionCallbackForTesting(testCallback1);
601         verify(delegate1).addUserActionCallbackForTesting(testCallback2);
602 
603         // Validate that previously added callbacks are removed from the previous delegate, and
604         // passed to the new delegate.
605         UmaRecorder delegate2 = Mockito.mock(UmaRecorder.class);
606         cachingRecorder.setDelegate(delegate2);
607         verify(delegate1).removeUserActionCallbackForTesting(testCallback1);
608         verify(delegate1).removeUserActionCallbackForTesting(testCallback2);
609         verify(delegate2).addUserActionCallbackForTesting(testCallback1);
610         verify(delegate2).addUserActionCallbackForTesting(testCallback2);
611 
612         // Ensure a callback added later is also added correctly.
613         Callback<String> testCallback3 = (result) -> {};
614         cachingRecorder.addUserActionCallbackForTesting(testCallback3);
615         verifyNoMoreInteractions(delegate1);
616         verify(delegate2).addUserActionCallbackForTesting(testCallback3);
617     }
618 }
619