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