1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.log.table 18 19 import androidx.test.filters.SmallTest 20 import com.android.systemui.SysuiTestCase 21 import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX 22 import com.android.systemui.util.mockito.mock 23 import com.android.systemui.util.time.FakeSystemClock 24 import com.google.common.truth.Truth.assertThat 25 import java.io.PrintWriter 26 import java.io.StringWriter 27 import kotlinx.coroutines.ExperimentalCoroutinesApi 28 import kotlinx.coroutines.flow.MutableStateFlow 29 import kotlinx.coroutines.flow.collect 30 import kotlinx.coroutines.flow.flow 31 import kotlinx.coroutines.flow.flowOf 32 import kotlinx.coroutines.launch 33 import kotlinx.coroutines.test.TestScope 34 import kotlinx.coroutines.test.UnconfinedTestDispatcher 35 import kotlinx.coroutines.test.runTest 36 import org.junit.Before 37 import org.junit.Test 38 39 @SmallTest 40 @OptIn(ExperimentalCoroutinesApi::class) 41 class LogDiffsForTableTest : SysuiTestCase() { 42 43 private val testDispatcher = UnconfinedTestDispatcher() 44 private val testScope = TestScope(testDispatcher) 45 46 private lateinit var systemClock: FakeSystemClock 47 private lateinit var tableLogBuffer: TableLogBuffer 48 49 @Before 50 fun setUp() { 51 systemClock = FakeSystemClock() 52 tableLogBuffer = 53 TableLogBuffer( 54 MAX_SIZE, 55 BUFFER_NAME, 56 systemClock, 57 mock(), 58 testDispatcher, 59 testScope.backgroundScope, 60 ) 61 } 62 63 // ---- Flow<Boolean> tests ---- 64 65 @Test 66 fun boolean_doesNotLogWhenNotCollected() { 67 val flow = flowOf(true, true, false) 68 69 flow.logDiffsForTable( 70 tableLogBuffer, 71 COLUMN_PREFIX, 72 COLUMN_NAME, 73 initialValue = false, 74 ) 75 76 val logs = dumpLog() 77 assertThat(logs).doesNotContain(COLUMN_PREFIX) 78 assertThat(logs).doesNotContain(COLUMN_NAME) 79 assertThat(logs).doesNotContain("false") 80 } 81 82 @Test 83 fun boolean_logsInitialWhenCollected() = 84 testScope.runTest { 85 val flow = flowOf(true, true, false) 86 87 val flowWithLogging = 88 flow.logDiffsForTable( 89 tableLogBuffer, 90 COLUMN_PREFIX, 91 COLUMN_NAME, 92 initialValue = false, 93 ) 94 95 systemClock.setCurrentTimeMillis(3000L) 96 val job = launch { flowWithLogging.collect() } 97 98 val logs = dumpLog() 99 assertThat(logs) 100 .contains( 101 TABLE_LOG_DATE_FORMAT.format(3000L) + 102 SEPARATOR + 103 FULL_NAME + 104 SEPARATOR + 105 IS_INITIAL_PREFIX + 106 "false" 107 ) 108 109 job.cancel() 110 } 111 112 @Test 113 fun boolean_logsUpdates() = 114 testScope.runTest { 115 systemClock.setCurrentTimeMillis(100L) 116 val flow = flow { 117 for (bool in listOf(true, false, true)) { 118 systemClock.advanceTime(100L) 119 emit(bool) 120 } 121 } 122 123 val flowWithLogging = 124 flow.logDiffsForTable( 125 tableLogBuffer, 126 COLUMN_PREFIX, 127 COLUMN_NAME, 128 initialValue = false, 129 ) 130 131 val job = launch { flowWithLogging.collect() } 132 133 val logs = dumpLog() 134 assertThat(logs) 135 .contains( 136 TABLE_LOG_DATE_FORMAT.format(100L) + 137 SEPARATOR + 138 FULL_NAME + 139 SEPARATOR + 140 IS_INITIAL_PREFIX + 141 "false" 142 ) 143 assertThat(logs) 144 .contains( 145 TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "true" 146 ) 147 assertThat(logs) 148 .contains( 149 TABLE_LOG_DATE_FORMAT.format(300L) + SEPARATOR + FULL_NAME + SEPARATOR + "false" 150 ) 151 assertThat(logs) 152 .contains( 153 TABLE_LOG_DATE_FORMAT.format(400L) + SEPARATOR + FULL_NAME + SEPARATOR + "true" 154 ) 155 156 job.cancel() 157 } 158 159 @Test 160 fun boolean_doesNotLogIfSameValue() = 161 testScope.runTest { 162 systemClock.setCurrentTimeMillis(100L) 163 val flow = flow { 164 for (bool in listOf(true, true, false, false, true)) { 165 systemClock.advanceTime(100L) 166 emit(bool) 167 } 168 } 169 170 val flowWithLogging = 171 flow.logDiffsForTable( 172 tableLogBuffer, 173 COLUMN_PREFIX, 174 COLUMN_NAME, 175 initialValue = true, 176 ) 177 178 val job = launch { flowWithLogging.collect() } 179 180 val logs = dumpLog() 181 // Input flow: true@100, true@200, true@300, false@400, false@500, true@600 182 // Output log: true@100, --------, --------, false@400, ---------, true@600 183 val expected1 = 184 TABLE_LOG_DATE_FORMAT.format(100L) + 185 SEPARATOR + 186 FULL_NAME + 187 SEPARATOR + 188 IS_INITIAL_PREFIX + 189 "true" 190 val expected4 = 191 TABLE_LOG_DATE_FORMAT.format(400L) + SEPARATOR + FULL_NAME + SEPARATOR + "false" 192 val expected6 = 193 TABLE_LOG_DATE_FORMAT.format(600L) + SEPARATOR + FULL_NAME + SEPARATOR + "true" 194 assertThat(logs).contains(expected1) 195 assertThat(logs).contains(expected4) 196 assertThat(logs).contains(expected6) 197 198 val unexpected2 = 199 TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "true" 200 val unexpected3 = 201 TABLE_LOG_DATE_FORMAT.format(300L) + SEPARATOR + FULL_NAME + SEPARATOR + "true" 202 val unexpected5 = 203 TABLE_LOG_DATE_FORMAT.format(500L) + SEPARATOR + FULL_NAME + SEPARATOR + "false" 204 assertThat(logs).doesNotContain(unexpected2) 205 assertThat(logs).doesNotContain(unexpected3) 206 assertThat(logs).doesNotContain(unexpected5) 207 208 job.cancel() 209 } 210 211 @Test 212 fun boolean_worksForStateFlows() = 213 testScope.runTest { 214 val flow = MutableStateFlow(false) 215 216 val flowWithLogging = 217 flow.logDiffsForTable( 218 tableLogBuffer, 219 COLUMN_PREFIX, 220 COLUMN_NAME, 221 initialValue = false, 222 ) 223 224 systemClock.setCurrentTimeMillis(50L) 225 val job = launch { flowWithLogging.collect() } 226 assertThat(dumpLog()) 227 .contains( 228 TABLE_LOG_DATE_FORMAT.format(50L) + 229 SEPARATOR + 230 FULL_NAME + 231 SEPARATOR + 232 IS_INITIAL_PREFIX + 233 "false" 234 ) 235 236 systemClock.setCurrentTimeMillis(100L) 237 flow.emit(true) 238 assertThat(dumpLog()) 239 .contains( 240 TABLE_LOG_DATE_FORMAT.format(100L) + SEPARATOR + FULL_NAME + SEPARATOR + "true" 241 ) 242 243 systemClock.setCurrentTimeMillis(200L) 244 flow.emit(false) 245 assertThat(dumpLog()) 246 .contains( 247 TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "false" 248 ) 249 250 // Doesn't log duplicates 251 systemClock.setCurrentTimeMillis(300L) 252 flow.emit(false) 253 assertThat(dumpLog()) 254 .doesNotContain( 255 TABLE_LOG_DATE_FORMAT.format(300L) + SEPARATOR + FULL_NAME + SEPARATOR + "false" 256 ) 257 258 job.cancel() 259 } 260 261 // ---- Flow<Int> tests ---- 262 263 @Test 264 fun int_doesNotLogWhenNotCollected() { 265 val flow = flowOf(5, 6, 7) 266 267 flow.logDiffsForTable( 268 tableLogBuffer, 269 COLUMN_PREFIX, 270 COLUMN_NAME, 271 initialValue = 1234, 272 ) 273 274 val logs = dumpLog() 275 assertThat(logs).doesNotContain(COLUMN_PREFIX) 276 assertThat(logs).doesNotContain(COLUMN_NAME) 277 assertThat(logs).doesNotContain("1234") 278 } 279 280 @Test 281 fun int_logsInitialWhenCollected() = 282 testScope.runTest { 283 val flow = flowOf(5, 6, 7) 284 285 val flowWithLogging = 286 flow.logDiffsForTable( 287 tableLogBuffer, 288 COLUMN_PREFIX, 289 COLUMN_NAME, 290 initialValue = 1234, 291 ) 292 293 systemClock.setCurrentTimeMillis(3000L) 294 val job = launch { flowWithLogging.collect() } 295 296 val logs = dumpLog() 297 assertThat(logs) 298 .contains( 299 TABLE_LOG_DATE_FORMAT.format(3000L) + 300 SEPARATOR + 301 FULL_NAME + 302 SEPARATOR + 303 IS_INITIAL_PREFIX + 304 "1234" 305 ) 306 307 job.cancel() 308 } 309 310 @Test 311 fun intNullable_logsNull() = 312 testScope.runTest { 313 systemClock.setCurrentTimeMillis(100L) 314 val flow = flow { 315 for (int in listOf(null, 6, null, 8)) { 316 systemClock.advanceTime(100L) 317 emit(int) 318 } 319 } 320 321 val flowWithLogging = 322 flow.logDiffsForTable( 323 tableLogBuffer, 324 COLUMN_PREFIX, 325 COLUMN_NAME, 326 initialValue = 1234, 327 ) 328 329 val job = launch { flowWithLogging.collect() } 330 331 val logs = dumpLog() 332 assertThat(logs) 333 .contains( 334 TABLE_LOG_DATE_FORMAT.format(100L) + 335 SEPARATOR + 336 FULL_NAME + 337 SEPARATOR + 338 IS_INITIAL_PREFIX + 339 "1234" 340 ) 341 assertThat(logs) 342 .contains( 343 TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "null" 344 ) 345 assertThat(logs) 346 .contains( 347 TABLE_LOG_DATE_FORMAT.format(300L) + SEPARATOR + FULL_NAME + SEPARATOR + "6" 348 ) 349 assertThat(logs) 350 .contains( 351 TABLE_LOG_DATE_FORMAT.format(400L) + SEPARATOR + FULL_NAME + SEPARATOR + "null" 352 ) 353 assertThat(logs) 354 .contains( 355 TABLE_LOG_DATE_FORMAT.format(500L) + SEPARATOR + FULL_NAME + SEPARATOR + "8" 356 ) 357 358 job.cancel() 359 } 360 361 @Test 362 fun int_logsUpdates() = 363 testScope.runTest { 364 systemClock.setCurrentTimeMillis(100L) 365 val flow = flow { 366 for (int in listOf(2, 3, 4)) { 367 systemClock.advanceTime(100L) 368 emit(int) 369 } 370 } 371 372 val flowWithLogging = 373 flow.logDiffsForTable( 374 tableLogBuffer, 375 COLUMN_PREFIX, 376 COLUMN_NAME, 377 initialValue = 1, 378 ) 379 380 val job = launch { flowWithLogging.collect() } 381 382 val logs = dumpLog() 383 assertThat(logs) 384 .contains( 385 TABLE_LOG_DATE_FORMAT.format(100L) + 386 SEPARATOR + 387 FULL_NAME + 388 SEPARATOR + 389 IS_INITIAL_PREFIX + 390 "1" 391 ) 392 assertThat(logs) 393 .contains( 394 TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "2" 395 ) 396 assertThat(logs) 397 .contains( 398 TABLE_LOG_DATE_FORMAT.format(300L) + SEPARATOR + FULL_NAME + SEPARATOR + "3" 399 ) 400 assertThat(logs) 401 .contains( 402 TABLE_LOG_DATE_FORMAT.format(400L) + SEPARATOR + FULL_NAME + SEPARATOR + "4" 403 ) 404 405 job.cancel() 406 } 407 408 @Test 409 fun int_doesNotLogIfSameValue() = 410 testScope.runTest { 411 systemClock.setCurrentTimeMillis(100L) 412 val flow = flow { 413 for (bool in listOf(2, 3, 3, 3, 2, 6, 6)) { 414 systemClock.advanceTime(100L) 415 emit(bool) 416 } 417 } 418 419 val flowWithLogging = 420 flow.logDiffsForTable( 421 tableLogBuffer, 422 COLUMN_PREFIX, 423 COLUMN_NAME, 424 initialValue = 1, 425 ) 426 427 val job = launch { flowWithLogging.collect() } 428 429 val logs = dumpLog() 430 // Input flow: 1@100, 2@200, 3@300, 3@400, 3@500, 2@600, 6@700, 6@800 431 // Output log: 1@100, 2@200, 3@300, -----, -----, 2@600, 6@700, ----- 432 val expected1 = 433 TABLE_LOG_DATE_FORMAT.format(100L) + 434 SEPARATOR + 435 FULL_NAME + 436 SEPARATOR + 437 IS_INITIAL_PREFIX + 438 "1" 439 val expected2 = 440 TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "2" 441 val expected3 = 442 TABLE_LOG_DATE_FORMAT.format(300L) + SEPARATOR + FULL_NAME + SEPARATOR + "3" 443 val expected6 = 444 TABLE_LOG_DATE_FORMAT.format(600L) + SEPARATOR + FULL_NAME + SEPARATOR + "2" 445 val expected7 = 446 TABLE_LOG_DATE_FORMAT.format(700L) + SEPARATOR + FULL_NAME + SEPARATOR + "6" 447 assertThat(logs).contains(expected1) 448 assertThat(logs).contains(expected2) 449 assertThat(logs).contains(expected3) 450 assertThat(logs).contains(expected6) 451 assertThat(logs).contains(expected7) 452 453 val unexpected4 = 454 TABLE_LOG_DATE_FORMAT.format(400L) + SEPARATOR + FULL_NAME + SEPARATOR + "3" 455 val unexpected5 = 456 TABLE_LOG_DATE_FORMAT.format(500L) + SEPARATOR + FULL_NAME + SEPARATOR + "3" 457 val unexpected8 = 458 TABLE_LOG_DATE_FORMAT.format(800L) + SEPARATOR + FULL_NAME + SEPARATOR + "6" 459 assertThat(logs).doesNotContain(unexpected4) 460 assertThat(logs).doesNotContain(unexpected5) 461 assertThat(logs).doesNotContain(unexpected8) 462 job.cancel() 463 } 464 465 @Test 466 fun int_worksForStateFlows() = 467 testScope.runTest { 468 val flow = MutableStateFlow(1111) 469 470 val flowWithLogging = 471 flow.logDiffsForTable( 472 tableLogBuffer, 473 COLUMN_PREFIX, 474 COLUMN_NAME, 475 initialValue = 1111, 476 ) 477 478 systemClock.setCurrentTimeMillis(50L) 479 val job = launch { flowWithLogging.collect() } 480 assertThat(dumpLog()) 481 .contains( 482 TABLE_LOG_DATE_FORMAT.format(50L) + 483 SEPARATOR + 484 FULL_NAME + 485 SEPARATOR + 486 IS_INITIAL_PREFIX + 487 "1111" 488 ) 489 490 systemClock.setCurrentTimeMillis(100L) 491 flow.emit(2222) 492 assertThat(dumpLog()) 493 .contains( 494 TABLE_LOG_DATE_FORMAT.format(100L) + SEPARATOR + FULL_NAME + SEPARATOR + "2222" 495 ) 496 497 systemClock.setCurrentTimeMillis(200L) 498 flow.emit(3333) 499 assertThat(dumpLog()) 500 .contains( 501 TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "3333" 502 ) 503 504 // Doesn't log duplicates 505 systemClock.setCurrentTimeMillis(300L) 506 flow.emit(3333) 507 assertThat(dumpLog()) 508 .doesNotContain( 509 TABLE_LOG_DATE_FORMAT.format(300L) + SEPARATOR + FULL_NAME + SEPARATOR + "3333" 510 ) 511 512 job.cancel() 513 } 514 515 // ---- Flow<String> tests ---- 516 517 @Test 518 fun string_doesNotLogWhenNotCollected() { 519 val flow = flowOf("val5", "val6", "val7") 520 521 flow.logDiffsForTable( 522 tableLogBuffer, 523 COLUMN_PREFIX, 524 COLUMN_NAME, 525 initialValue = "val1234", 526 ) 527 528 val logs = dumpLog() 529 assertThat(logs).doesNotContain(COLUMN_PREFIX) 530 assertThat(logs).doesNotContain(COLUMN_NAME) 531 assertThat(logs).doesNotContain("val1234") 532 } 533 534 @Test 535 fun string_logsInitialWhenCollected() = 536 testScope.runTest { 537 val flow = flowOf("val5", "val6", "val7") 538 539 val flowWithLogging = 540 flow.logDiffsForTable( 541 tableLogBuffer, 542 COLUMN_PREFIX, 543 COLUMN_NAME, 544 initialValue = "val1234", 545 ) 546 547 systemClock.setCurrentTimeMillis(3000L) 548 val job = launch { flowWithLogging.collect() } 549 550 val logs = dumpLog() 551 assertThat(logs) 552 .contains( 553 TABLE_LOG_DATE_FORMAT.format(3000L) + 554 SEPARATOR + 555 FULL_NAME + 556 SEPARATOR + 557 IS_INITIAL_PREFIX + 558 "val1234" 559 ) 560 561 job.cancel() 562 } 563 564 @Test 565 fun string_logsUpdates() = 566 testScope.runTest { 567 systemClock.setCurrentTimeMillis(100L) 568 val flow = flow { 569 for (int in listOf("val2", "val3", "val4")) { 570 systemClock.advanceTime(100L) 571 emit(int) 572 } 573 } 574 575 val flowWithLogging = 576 flow.logDiffsForTable( 577 tableLogBuffer, 578 COLUMN_PREFIX, 579 COLUMN_NAME, 580 initialValue = "val1", 581 ) 582 583 val job = launch { flowWithLogging.collect() } 584 585 val logs = dumpLog() 586 assertThat(logs) 587 .contains( 588 TABLE_LOG_DATE_FORMAT.format(100L) + 589 SEPARATOR + 590 FULL_NAME + 591 SEPARATOR + 592 IS_INITIAL_PREFIX + 593 "val1" 594 ) 595 assertThat(logs) 596 .contains( 597 TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "val2" 598 ) 599 assertThat(logs) 600 .contains( 601 TABLE_LOG_DATE_FORMAT.format(300L) + SEPARATOR + FULL_NAME + SEPARATOR + "val3" 602 ) 603 assertThat(logs) 604 .contains( 605 TABLE_LOG_DATE_FORMAT.format(400L) + SEPARATOR + FULL_NAME + SEPARATOR + "val4" 606 ) 607 608 job.cancel() 609 } 610 611 @Test 612 fun string_logsNull() = 613 testScope.runTest { 614 systemClock.setCurrentTimeMillis(100L) 615 val flow = flow { 616 for (int in listOf(null, "something", null)) { 617 systemClock.advanceTime(100L) 618 emit(int) 619 } 620 } 621 622 val flowWithLogging = 623 flow.logDiffsForTable( 624 tableLogBuffer, 625 COLUMN_PREFIX, 626 COLUMN_NAME, 627 initialValue = "start", 628 ) 629 630 val job = launch { flowWithLogging.collect() } 631 632 val logs = dumpLog() 633 assertThat(logs) 634 .contains( 635 TABLE_LOG_DATE_FORMAT.format(100L) + 636 SEPARATOR + 637 FULL_NAME + 638 SEPARATOR + 639 IS_INITIAL_PREFIX + 640 "start" 641 ) 642 assertThat(logs) 643 .contains( 644 TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "null" 645 ) 646 assertThat(logs) 647 .contains( 648 TABLE_LOG_DATE_FORMAT.format(300L) + 649 SEPARATOR + 650 FULL_NAME + 651 SEPARATOR + 652 "something" 653 ) 654 assertThat(logs) 655 .contains( 656 TABLE_LOG_DATE_FORMAT.format(400L) + SEPARATOR + FULL_NAME + SEPARATOR + "null" 657 ) 658 659 job.cancel() 660 } 661 662 @Test 663 fun string_doesNotLogIfSameValue() = 664 testScope.runTest { 665 systemClock.setCurrentTimeMillis(100L) 666 val flow = flow { 667 for (bool in listOf("start", "new", "new", "newer", "newest", "newest")) { 668 systemClock.advanceTime(100L) 669 emit(bool) 670 } 671 } 672 673 val flowWithLogging = 674 flow.logDiffsForTable( 675 tableLogBuffer, 676 COLUMN_PREFIX, 677 COLUMN_NAME, 678 initialValue = "start", 679 ) 680 681 val job = launch { flowWithLogging.collect() } 682 683 val logs = dumpLog() 684 // Input flow: start@100, start@200, new@300, new@400, newer@500, newest@600, newest@700 685 // Output log: start@100, ---------, new@300, -------, newer@500, newest@600, ---------- 686 val expected1 = 687 TABLE_LOG_DATE_FORMAT.format(100L) + 688 SEPARATOR + 689 FULL_NAME + 690 SEPARATOR + 691 IS_INITIAL_PREFIX + 692 "start" 693 val expected3 = 694 TABLE_LOG_DATE_FORMAT.format(300L) + SEPARATOR + FULL_NAME + SEPARATOR + "new" 695 val expected5 = 696 TABLE_LOG_DATE_FORMAT.format(500L) + SEPARATOR + FULL_NAME + SEPARATOR + "newer" 697 val expected6 = 698 TABLE_LOG_DATE_FORMAT.format(600L) + SEPARATOR + FULL_NAME + SEPARATOR + "newest" 699 assertThat(logs).contains(expected1) 700 assertThat(logs).contains(expected3) 701 assertThat(logs).contains(expected5) 702 assertThat(logs).contains(expected6) 703 704 val unexpected2 = 705 TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "start" 706 val unexpected4 = 707 TABLE_LOG_DATE_FORMAT.format(400L) + SEPARATOR + FULL_NAME + SEPARATOR + "new" 708 val unexpected7 = 709 TABLE_LOG_DATE_FORMAT.format(700L) + SEPARATOR + FULL_NAME + SEPARATOR + "newest" 710 assertThat(logs).doesNotContain(unexpected2) 711 assertThat(logs).doesNotContain(unexpected4) 712 assertThat(logs).doesNotContain(unexpected7) 713 714 job.cancel() 715 } 716 717 @Test 718 fun string_worksForStateFlows() = 719 testScope.runTest { 720 val flow = MutableStateFlow("initial") 721 722 val flowWithLogging = 723 flow.logDiffsForTable( 724 tableLogBuffer, 725 COLUMN_PREFIX, 726 COLUMN_NAME, 727 initialValue = "initial", 728 ) 729 730 systemClock.setCurrentTimeMillis(50L) 731 val job = launch { flowWithLogging.collect() } 732 assertThat(dumpLog()) 733 .contains( 734 TABLE_LOG_DATE_FORMAT.format(50L) + 735 SEPARATOR + 736 FULL_NAME + 737 SEPARATOR + 738 IS_INITIAL_PREFIX + 739 "initial" 740 ) 741 742 systemClock.setCurrentTimeMillis(100L) 743 flow.emit("nextVal") 744 assertThat(dumpLog()) 745 .contains( 746 TABLE_LOG_DATE_FORMAT.format(100L) + 747 SEPARATOR + 748 FULL_NAME + 749 SEPARATOR + 750 "nextVal" 751 ) 752 753 systemClock.setCurrentTimeMillis(200L) 754 flow.emit("nextNextVal") 755 assertThat(dumpLog()) 756 .contains( 757 TABLE_LOG_DATE_FORMAT.format(200L) + 758 SEPARATOR + 759 FULL_NAME + 760 SEPARATOR + 761 "nextNextVal" 762 ) 763 764 // Doesn't log duplicates 765 systemClock.setCurrentTimeMillis(300L) 766 flow.emit("nextNextVal") 767 assertThat(dumpLog()) 768 .doesNotContain( 769 TABLE_LOG_DATE_FORMAT.format(300L) + 770 SEPARATOR + 771 FULL_NAME + 772 SEPARATOR + 773 "nextNextVal" 774 ) 775 776 job.cancel() 777 } 778 779 // ---- Flow<Diffable> tests ---- 780 781 @Test 782 fun diffable_doesNotLogWhenNotCollected() { 783 val flow = 784 flowOf( 785 TestDiffable(1, "1", true), 786 TestDiffable(2, "2", false), 787 ) 788 789 val initial = TestDiffable(0, "0", false) 790 flow.logDiffsForTable( 791 tableLogBuffer, 792 COLUMN_PREFIX, 793 initial, 794 ) 795 796 val logs = dumpLog() 797 assertThat(logs).doesNotContain(COLUMN_PREFIX) 798 assertThat(logs).doesNotContain(TestDiffable.COL_FULL) 799 assertThat(logs).doesNotContain(TestDiffable.COL_INT) 800 assertThat(logs).doesNotContain(TestDiffable.COL_STRING) 801 assertThat(logs).doesNotContain(TestDiffable.COL_BOOLEAN) 802 } 803 804 @Test 805 fun diffable_logsInitialWhenCollected_usingLogFull() = 806 testScope.runTest { 807 val flow = 808 flowOf( 809 TestDiffable(1, "1", true), 810 TestDiffable(2, "2", false), 811 ) 812 813 val initial = TestDiffable(1234, "string1234", false) 814 val flowWithLogging = 815 flow.logDiffsForTable( 816 tableLogBuffer, 817 COLUMN_PREFIX, 818 initial, 819 ) 820 821 systemClock.setCurrentTimeMillis(3000L) 822 val job = launch { flowWithLogging.collect() } 823 824 val logs = dumpLog() 825 assertThat(logs) 826 .contains( 827 TABLE_LOG_DATE_FORMAT.format(3000L) + 828 SEPARATOR + 829 COLUMN_PREFIX + 830 "." + 831 TestDiffable.COL_FULL + 832 SEPARATOR + 833 IS_INITIAL_PREFIX + 834 "true" 835 ) 836 assertThat(logs) 837 .contains( 838 TABLE_LOG_DATE_FORMAT.format(3000L) + 839 SEPARATOR + 840 COLUMN_PREFIX + 841 "." + 842 TestDiffable.COL_INT + 843 SEPARATOR + 844 IS_INITIAL_PREFIX + 845 "1234" 846 ) 847 assertThat(logs) 848 .contains( 849 TABLE_LOG_DATE_FORMAT.format(3000L) + 850 SEPARATOR + 851 COLUMN_PREFIX + 852 "." + 853 TestDiffable.COL_STRING + 854 SEPARATOR + 855 IS_INITIAL_PREFIX + 856 "string1234" 857 ) 858 assertThat(logs) 859 .contains( 860 TABLE_LOG_DATE_FORMAT.format(3000L) + 861 SEPARATOR + 862 COLUMN_PREFIX + 863 "." + 864 TestDiffable.COL_BOOLEAN + 865 SEPARATOR + 866 IS_INITIAL_PREFIX + 867 "false" 868 ) 869 job.cancel() 870 } 871 872 @Test 873 fun diffable_logsUpdates_usingLogDiffs() = 874 testScope.runTest { 875 val initialValue = TestDiffable(0, "string0", false) 876 val diffables = 877 listOf( 878 TestDiffable(1, "string1", true), 879 TestDiffable(2, "string1", true), 880 TestDiffable(2, "string2", false), 881 ) 882 883 systemClock.setCurrentTimeMillis(100L) 884 val flow = flow { 885 for (diffable in diffables) { 886 systemClock.advanceTime(100L) 887 emit(diffable) 888 } 889 } 890 891 val flowWithLogging = 892 flow.logDiffsForTable( 893 tableLogBuffer, 894 COLUMN_PREFIX, 895 initialValue, 896 ) 897 898 val job = launch { flowWithLogging.collect() } 899 900 val logs = dumpLog() 901 902 // Initial -> first: everything different 903 assertThat(logs) 904 .contains( 905 TABLE_LOG_DATE_FORMAT.format(200L) + 906 SEPARATOR + 907 COLUMN_PREFIX + 908 "." + 909 TestDiffable.COL_FULL + 910 SEPARATOR + 911 "false" 912 ) 913 assertThat(logs) 914 .contains( 915 TABLE_LOG_DATE_FORMAT.format(200L) + 916 SEPARATOR + 917 COLUMN_PREFIX + 918 "." + 919 TestDiffable.COL_INT + 920 SEPARATOR + 921 "1" 922 ) 923 assertThat(logs) 924 .contains( 925 TABLE_LOG_DATE_FORMAT.format(200L) + 926 SEPARATOR + 927 COLUMN_PREFIX + 928 "." + 929 TestDiffable.COL_STRING + 930 SEPARATOR + 931 "string1" 932 ) 933 assertThat(logs) 934 .contains( 935 TABLE_LOG_DATE_FORMAT.format(200L) + 936 SEPARATOR + 937 COLUMN_PREFIX + 938 "." + 939 TestDiffable.COL_BOOLEAN + 940 SEPARATOR + 941 "true" 942 ) 943 944 // First -> second: int different 945 assertThat(logs) 946 .contains( 947 TABLE_LOG_DATE_FORMAT.format(300L) + 948 SEPARATOR + 949 COLUMN_PREFIX + 950 "." + 951 TestDiffable.COL_FULL + 952 SEPARATOR + 953 "false" 954 ) 955 assertThat(logs) 956 .contains( 957 TABLE_LOG_DATE_FORMAT.format(300L) + 958 SEPARATOR + 959 COLUMN_PREFIX + 960 "." + 961 TestDiffable.COL_INT + 962 SEPARATOR + 963 "2" 964 ) 965 assertThat(logs) 966 .doesNotContain( 967 TABLE_LOG_DATE_FORMAT.format(300L) + 968 SEPARATOR + 969 COLUMN_PREFIX + 970 "." + 971 TestDiffable.COL_STRING + 972 SEPARATOR + 973 "string1" 974 ) 975 assertThat(logs) 976 .doesNotContain( 977 TABLE_LOG_DATE_FORMAT.format(300L) + 978 SEPARATOR + 979 COLUMN_PREFIX + 980 "." + 981 TestDiffable.COL_BOOLEAN + 982 SEPARATOR + 983 "true" 984 ) 985 986 // Second -> third: string & boolean different 987 assertThat(logs) 988 .contains( 989 TABLE_LOG_DATE_FORMAT.format(400L) + 990 SEPARATOR + 991 COLUMN_PREFIX + 992 "." + 993 TestDiffable.COL_FULL + 994 SEPARATOR + 995 "false" 996 ) 997 assertThat(logs) 998 .contains( 999 TABLE_LOG_DATE_FORMAT.format(400L) + 1000 SEPARATOR + 1001 COLUMN_PREFIX + 1002 "." + 1003 TestDiffable.COL_STRING + 1004 SEPARATOR + 1005 "string2" 1006 ) 1007 assertThat(logs) 1008 .contains( 1009 TABLE_LOG_DATE_FORMAT.format(400L) + 1010 SEPARATOR + 1011 COLUMN_PREFIX + 1012 "." + 1013 TestDiffable.COL_BOOLEAN + 1014 SEPARATOR + 1015 "false" 1016 ) 1017 assertThat(logs) 1018 .doesNotContain( 1019 TABLE_LOG_DATE_FORMAT.format(400L) + 1020 SEPARATOR + 1021 COLUMN_PREFIX + 1022 "." + 1023 TestDiffable.COL_INT + 1024 SEPARATOR + 1025 "2" 1026 ) 1027 1028 job.cancel() 1029 } 1030 1031 @Test 1032 fun diffable_worksForStateFlows() = 1033 testScope.runTest { 1034 val initialValue = TestDiffable(0, "string0", false) 1035 val flow = MutableStateFlow(initialValue) 1036 val flowWithLogging = 1037 flow.logDiffsForTable( 1038 tableLogBuffer, 1039 COLUMN_PREFIX, 1040 initialValue, 1041 ) 1042 1043 systemClock.setCurrentTimeMillis(50L) 1044 val job = launch { flowWithLogging.collect() } 1045 1046 var logs = dumpLog() 1047 assertThat(logs) 1048 .contains( 1049 TABLE_LOG_DATE_FORMAT.format(50L) + 1050 SEPARATOR + 1051 COLUMN_PREFIX + 1052 "." + 1053 TestDiffable.COL_INT + 1054 SEPARATOR + 1055 IS_INITIAL_PREFIX + 1056 "0" 1057 ) 1058 assertThat(logs) 1059 .contains( 1060 TABLE_LOG_DATE_FORMAT.format(50L) + 1061 SEPARATOR + 1062 COLUMN_PREFIX + 1063 "." + 1064 TestDiffable.COL_STRING + 1065 SEPARATOR + 1066 IS_INITIAL_PREFIX + 1067 "string0" 1068 ) 1069 assertThat(logs) 1070 .contains( 1071 TABLE_LOG_DATE_FORMAT.format(50L) + 1072 SEPARATOR + 1073 COLUMN_PREFIX + 1074 "." + 1075 TestDiffable.COL_BOOLEAN + 1076 SEPARATOR + 1077 IS_INITIAL_PREFIX + 1078 "false" 1079 ) 1080 1081 systemClock.setCurrentTimeMillis(100L) 1082 flow.emit(TestDiffable(1, "string1", true)) 1083 1084 logs = dumpLog() 1085 assertThat(logs) 1086 .contains( 1087 TABLE_LOG_DATE_FORMAT.format(100L) + 1088 SEPARATOR + 1089 COLUMN_PREFIX + 1090 "." + 1091 TestDiffable.COL_INT + 1092 SEPARATOR + 1093 "1" 1094 ) 1095 assertThat(logs) 1096 .contains( 1097 TABLE_LOG_DATE_FORMAT.format(100L) + 1098 SEPARATOR + 1099 COLUMN_PREFIX + 1100 "." + 1101 TestDiffable.COL_STRING + 1102 SEPARATOR + 1103 "string1" 1104 ) 1105 assertThat(logs) 1106 .contains( 1107 TABLE_LOG_DATE_FORMAT.format(100L) + 1108 SEPARATOR + 1109 COLUMN_PREFIX + 1110 "." + 1111 TestDiffable.COL_BOOLEAN + 1112 SEPARATOR + 1113 "true" 1114 ) 1115 1116 // Doesn't log duplicates 1117 systemClock.setCurrentTimeMillis(200L) 1118 flow.emit(TestDiffable(1, "newString", true)) 1119 1120 logs = dumpLog() 1121 assertThat(logs) 1122 .doesNotContain( 1123 TABLE_LOG_DATE_FORMAT.format(200L) + 1124 SEPARATOR + 1125 COLUMN_PREFIX + 1126 "." + 1127 TestDiffable.COL_INT + 1128 SEPARATOR + 1129 "1" 1130 ) 1131 assertThat(logs) 1132 .contains( 1133 TABLE_LOG_DATE_FORMAT.format(200L) + 1134 SEPARATOR + 1135 COLUMN_PREFIX + 1136 "." + 1137 TestDiffable.COL_STRING + 1138 SEPARATOR + 1139 "newString" 1140 ) 1141 assertThat(logs) 1142 .doesNotContain( 1143 TABLE_LOG_DATE_FORMAT.format(200L) + 1144 SEPARATOR + 1145 COLUMN_PREFIX + 1146 "." + 1147 TestDiffable.COL_BOOLEAN + 1148 SEPARATOR + 1149 "true" 1150 ) 1151 1152 job.cancel() 1153 } 1154 1155 // ---- Flow<List<T>> tests ---- 1156 1157 @Test 1158 fun list_doesNotLogWhenNotCollected() { 1159 val flow = flowOf(listOf(5), listOf(6), listOf(7)) 1160 1161 flow.logDiffsForTable( 1162 tableLogBuffer, 1163 COLUMN_PREFIX, 1164 COLUMN_NAME, 1165 initialValue = listOf(1234), 1166 ) 1167 1168 val logs = dumpLog() 1169 assertThat(logs).doesNotContain(COLUMN_PREFIX) 1170 assertThat(logs).doesNotContain(COLUMN_NAME) 1171 assertThat(logs).doesNotContain("1234") 1172 } 1173 1174 @Test 1175 fun list_logsInitialWhenCollected() = 1176 testScope.runTest { 1177 val flow = flowOf(listOf(5), listOf(6), listOf(7)) 1178 1179 val flowWithLogging = 1180 flow.logDiffsForTable( 1181 tableLogBuffer, 1182 COLUMN_PREFIX, 1183 COLUMN_NAME, 1184 initialValue = listOf(1234), 1185 ) 1186 1187 systemClock.setCurrentTimeMillis(3000L) 1188 val job = launch { flowWithLogging.collect() } 1189 1190 val logs = dumpLog() 1191 assertThat(logs) 1192 .contains( 1193 TABLE_LOG_DATE_FORMAT.format(3000L) + 1194 SEPARATOR + 1195 FULL_NAME + 1196 SEPARATOR + 1197 IS_INITIAL_PREFIX + 1198 listOf(1234).toString() 1199 ) 1200 1201 job.cancel() 1202 } 1203 1204 @Test 1205 fun list_logsUpdates() = 1206 testScope.runTest { 1207 systemClock.setCurrentTimeMillis(100L) 1208 1209 val listItems = 1210 listOf(listOf("val1", "val2"), listOf("val3"), listOf("val4", "val5", "val6")) 1211 val flow = flow { 1212 for (list in listItems) { 1213 systemClock.advanceTime(100L) 1214 emit(list) 1215 } 1216 } 1217 1218 val flowWithLogging = 1219 flow.logDiffsForTable( 1220 tableLogBuffer, 1221 COLUMN_PREFIX, 1222 COLUMN_NAME, 1223 initialValue = listOf("val0", "val00"), 1224 ) 1225 1226 val job = launch { flowWithLogging.collect() } 1227 1228 val logs = dumpLog() 1229 assertThat(logs) 1230 .contains( 1231 TABLE_LOG_DATE_FORMAT.format(100L) + 1232 SEPARATOR + 1233 FULL_NAME + 1234 SEPARATOR + 1235 IS_INITIAL_PREFIX + 1236 listOf("val0", "val00").toString() 1237 ) 1238 assertThat(logs) 1239 .contains( 1240 TABLE_LOG_DATE_FORMAT.format(200L) + 1241 SEPARATOR + 1242 FULL_NAME + 1243 SEPARATOR + 1244 listOf("val1", "val2").toString() 1245 ) 1246 assertThat(logs) 1247 .contains( 1248 TABLE_LOG_DATE_FORMAT.format(300L) + 1249 SEPARATOR + 1250 FULL_NAME + 1251 SEPARATOR + 1252 listOf("val3").toString() 1253 ) 1254 assertThat(logs) 1255 .contains( 1256 TABLE_LOG_DATE_FORMAT.format(400L) + 1257 SEPARATOR + 1258 FULL_NAME + 1259 SEPARATOR + 1260 listOf("val4", "val5", "val6").toString() 1261 ) 1262 1263 job.cancel() 1264 } 1265 1266 @Test 1267 fun list_doesNotLogIfSameValue() = 1268 testScope.runTest { 1269 systemClock.setCurrentTimeMillis(100L) 1270 1271 val listItems = 1272 listOf( 1273 listOf("val0", "val00"), 1274 listOf("val1"), 1275 listOf("val1"), 1276 listOf("val1", "val2"), 1277 ) 1278 val flow = flow { 1279 for (bool in listItems) { 1280 systemClock.advanceTime(100L) 1281 emit(bool) 1282 } 1283 } 1284 1285 val flowWithLogging = 1286 flow.logDiffsForTable( 1287 tableLogBuffer, 1288 COLUMN_PREFIX, 1289 COLUMN_NAME, 1290 initialValue = listOf("val0", "val00"), 1291 ) 1292 1293 val job = launch { flowWithLogging.collect() } 1294 1295 val logs = dumpLog() 1296 1297 val expected1 = 1298 TABLE_LOG_DATE_FORMAT.format(100L) + 1299 SEPARATOR + 1300 FULL_NAME + 1301 SEPARATOR + 1302 IS_INITIAL_PREFIX + 1303 listOf("val0", "val00").toString() 1304 val expected3 = 1305 TABLE_LOG_DATE_FORMAT.format(300L) + 1306 SEPARATOR + 1307 FULL_NAME + 1308 SEPARATOR + 1309 listOf("val1").toString() 1310 val expected5 = 1311 TABLE_LOG_DATE_FORMAT.format(500L) + 1312 SEPARATOR + 1313 FULL_NAME + 1314 SEPARATOR + 1315 listOf("val1", "val2").toString() 1316 assertThat(logs).contains(expected1) 1317 assertThat(logs).contains(expected3) 1318 assertThat(logs).contains(expected5) 1319 1320 val unexpected2 = 1321 TABLE_LOG_DATE_FORMAT.format(200L) + 1322 SEPARATOR + 1323 FULL_NAME + 1324 SEPARATOR + 1325 listOf("val0", "val00") 1326 val unexpected4 = 1327 TABLE_LOG_DATE_FORMAT.format(400L) + 1328 SEPARATOR + 1329 FULL_NAME + 1330 SEPARATOR + 1331 listOf("val1") 1332 assertThat(logs).doesNotContain(unexpected2) 1333 assertThat(logs).doesNotContain(unexpected4) 1334 job.cancel() 1335 } 1336 1337 @Test 1338 fun list_worksForStateFlows() = 1339 testScope.runTest { 1340 val flow = MutableStateFlow(listOf(1111)) 1341 1342 val flowWithLogging = 1343 flow.logDiffsForTable( 1344 tableLogBuffer, 1345 COLUMN_PREFIX, 1346 COLUMN_NAME, 1347 initialValue = listOf(1111), 1348 ) 1349 1350 systemClock.setCurrentTimeMillis(50L) 1351 val job = launch { flowWithLogging.collect() } 1352 assertThat(dumpLog()) 1353 .contains( 1354 TABLE_LOG_DATE_FORMAT.format(50L) + 1355 SEPARATOR + 1356 FULL_NAME + 1357 SEPARATOR + 1358 IS_INITIAL_PREFIX + 1359 listOf(1111).toString() 1360 ) 1361 1362 systemClock.setCurrentTimeMillis(100L) 1363 flow.emit(listOf(2222, 3333)) 1364 assertThat(dumpLog()) 1365 .contains( 1366 TABLE_LOG_DATE_FORMAT.format(100L) + 1367 SEPARATOR + 1368 FULL_NAME + 1369 SEPARATOR + 1370 listOf(2222, 3333).toString() 1371 ) 1372 1373 systemClock.setCurrentTimeMillis(200L) 1374 flow.emit(listOf(3333, 4444)) 1375 assertThat(dumpLog()) 1376 .contains( 1377 TABLE_LOG_DATE_FORMAT.format(200L) + 1378 SEPARATOR + 1379 FULL_NAME + 1380 SEPARATOR + 1381 listOf(3333, 4444).toString() 1382 ) 1383 1384 // Doesn't log duplicates 1385 systemClock.setCurrentTimeMillis(300L) 1386 flow.emit(listOf(3333, 4444)) 1387 assertThat(dumpLog()) 1388 .doesNotContain( 1389 TABLE_LOG_DATE_FORMAT.format(300L) + 1390 SEPARATOR + 1391 FULL_NAME + 1392 SEPARATOR + 1393 listOf(3333, 4444).toString() 1394 ) 1395 1396 job.cancel() 1397 } 1398 1399 private fun dumpLog(): String { 1400 val outputWriter = StringWriter() 1401 tableLogBuffer.dump(PrintWriter(outputWriter), arrayOf()) 1402 return outputWriter.toString() 1403 } 1404 1405 class TestDiffable( 1406 private val testInt: Int, 1407 private val testString: String, 1408 private val testBoolean: Boolean, 1409 ) : Diffable<TestDiffable> { 1410 override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { 1411 row.logChange(COL_FULL, false) 1412 1413 if (testInt != prevVal.testInt) { 1414 row.logChange(COL_INT, testInt) 1415 } 1416 if (testString != prevVal.testString) { 1417 row.logChange(COL_STRING, testString) 1418 } 1419 if (testBoolean != prevVal.testBoolean) { 1420 row.logChange(COL_BOOLEAN, testBoolean) 1421 } 1422 } 1423 1424 override fun logFull(row: TableRowLogger) { 1425 row.logChange(COL_FULL, true) 1426 row.logChange(COL_INT, testInt) 1427 row.logChange(COL_STRING, testString) 1428 row.logChange(COL_BOOLEAN, testBoolean) 1429 } 1430 1431 companion object { 1432 const val COL_INT = "intColumn" 1433 const val COL_STRING = "stringColumn" 1434 const val COL_BOOLEAN = "booleanColumn" 1435 const val COL_FULL = "loggedFullColumn" 1436 } 1437 } 1438 1439 private companion object { 1440 const val MAX_SIZE = 50 1441 const val BUFFER_NAME = "LogDiffsForTableTest" 1442 const val COLUMN_PREFIX = "columnPrefix" 1443 const val COLUMN_NAME = "columnName" 1444 const val FULL_NAME = "$COLUMN_PREFIX.$COLUMN_NAME" 1445 private const val SEPARATOR = "|" 1446 } 1447 } 1448