diff options
| author | 2022-12-22 17:44:08 +0000 | |
|---|---|---|
| committer | 2022-12-22 17:57:38 +0000 | |
| commit | 65e2735d0db765e983b1ee09a4dbef86d97f60d9 (patch) | |
| tree | a98325dab3bd6bb7eb63cc4a5efbfe274532c11d | |
| parent | d065bb8c08d9ea88c056d225c8d115798221555a (diff) | |
[Table Logging] Add tests for `logDiffsForTable`.
Bug: 259454430
Test: atest LogDiffsForTableTest
Change-Id: If4f3baee39f68d34be6a57dddc4f8e5a268927c3
| -rw-r--r-- | packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt | 1081 |
1 files changed, 1081 insertions, 0 deletions
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt new file mode 100644 index 000000000000..3b5e6b9c145c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt @@ -0,0 +1,1081 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.table + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import java.io.StringWriter +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test + +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +class LogDiffsForTableTest : SysuiTestCase() { + + private val testScope = TestScope(UnconfinedTestDispatcher()) + + private lateinit var systemClock: FakeSystemClock + private lateinit var tableLogBuffer: TableLogBuffer + + @Before + fun setUp() { + systemClock = FakeSystemClock() + tableLogBuffer = TableLogBuffer(MAX_SIZE, BUFFER_NAME, systemClock) + } + + // ---- Flow<Boolean> tests ---- + + @Test + fun boolean_doesNotLogWhenNotCollected() { + val flow = flowOf(true, true, false) + + flow.logDiffsForTable( + tableLogBuffer, + COLUMN_PREFIX, + COLUMN_NAME, + initialValue = false, + ) + + val logs = dumpLog() + assertThat(logs).doesNotContain(COLUMN_PREFIX) + assertThat(logs).doesNotContain(COLUMN_NAME) + assertThat(logs).doesNotContain("false") + } + + @Test + fun boolean_logsInitialWhenCollected() = + testScope.runTest { + val flow = flowOf(true, true, false) + + val flowWithLogging = + flow.logDiffsForTable( + tableLogBuffer, + COLUMN_PREFIX, + COLUMN_NAME, + initialValue = false, + ) + + systemClock.setCurrentTimeMillis(3000L) + val job = launch { flowWithLogging.collect() } + + val logs = dumpLog() + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(3000L) + + SEPARATOR + + FULL_NAME + + SEPARATOR + + "false" + ) + + job.cancel() + } + + @Test + fun boolean_logsUpdates() = + testScope.runTest { + systemClock.setCurrentTimeMillis(100L) + val flow = flow { + for (bool in listOf(true, false, true)) { + systemClock.advanceTime(100L) + emit(bool) + } + } + + val flowWithLogging = + flow.logDiffsForTable( + tableLogBuffer, + COLUMN_PREFIX, + COLUMN_NAME, + initialValue = false, + ) + + val job = launch { flowWithLogging.collect() } + + val logs = dumpLog() + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(100L) + SEPARATOR + FULL_NAME + SEPARATOR + "false" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "true" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(300L) + SEPARATOR + FULL_NAME + SEPARATOR + "false" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(400L) + SEPARATOR + FULL_NAME + SEPARATOR + "true" + ) + + job.cancel() + } + + @Test + fun boolean_doesNotLogIfSameValue() = + testScope.runTest { + systemClock.setCurrentTimeMillis(100L) + val flow = flow { + for (bool in listOf(true, true, false, false, true)) { + systemClock.advanceTime(100L) + emit(bool) + } + } + + val flowWithLogging = + flow.logDiffsForTable( + tableLogBuffer, + COLUMN_PREFIX, + COLUMN_NAME, + initialValue = true, + ) + + val job = launch { flowWithLogging.collect() } + + val logs = dumpLog() + // Input flow: true@100, true@200, true@300, false@400, false@500, true@600 + // Output log: true@100, --------, --------, false@400, ---------, true@600 + val expected1 = + TABLE_LOG_DATE_FORMAT.format(100L) + SEPARATOR + FULL_NAME + SEPARATOR + "true" + val expected4 = + TABLE_LOG_DATE_FORMAT.format(400L) + SEPARATOR + FULL_NAME + SEPARATOR + "false" + val expected6 = + TABLE_LOG_DATE_FORMAT.format(600L) + SEPARATOR + FULL_NAME + SEPARATOR + "true" + assertThat(logs).contains(expected1) + assertThat(logs).contains(expected4) + assertThat(logs).contains(expected6) + + val unexpected2 = + TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "true" + val unexpected3 = + TABLE_LOG_DATE_FORMAT.format(300L) + SEPARATOR + FULL_NAME + SEPARATOR + "true" + val unexpected5 = + TABLE_LOG_DATE_FORMAT.format(500L) + SEPARATOR + FULL_NAME + SEPARATOR + "false" + assertThat(logs).doesNotContain(unexpected2) + assertThat(logs).doesNotContain(unexpected3) + assertThat(logs).doesNotContain(unexpected5) + + job.cancel() + } + + @Test + fun boolean_worksForStateFlows() = + testScope.runTest { + val flow = MutableStateFlow(false) + + val flowWithLogging = + flow.logDiffsForTable( + tableLogBuffer, + COLUMN_PREFIX, + COLUMN_NAME, + initialValue = false, + ) + + systemClock.setCurrentTimeMillis(50L) + val job = launch { flowWithLogging.collect() } + assertThat(dumpLog()) + .contains( + TABLE_LOG_DATE_FORMAT.format(50L) + SEPARATOR + FULL_NAME + SEPARATOR + "false" + ) + + systemClock.setCurrentTimeMillis(100L) + flow.emit(true) + assertThat(dumpLog()) + .contains( + TABLE_LOG_DATE_FORMAT.format(100L) + SEPARATOR + FULL_NAME + SEPARATOR + "true" + ) + + systemClock.setCurrentTimeMillis(200L) + flow.emit(false) + assertThat(dumpLog()) + .contains( + TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "false" + ) + + // Doesn't log duplicates + systemClock.setCurrentTimeMillis(300L) + flow.emit(false) + assertThat(dumpLog()) + .doesNotContain( + TABLE_LOG_DATE_FORMAT.format(300L) + SEPARATOR + FULL_NAME + SEPARATOR + "false" + ) + + job.cancel() + } + + // ---- Flow<Int> tests ---- + + @Test + fun int_doesNotLogWhenNotCollected() { + val flow = flowOf(5, 6, 7) + + flow.logDiffsForTable( + tableLogBuffer, + COLUMN_PREFIX, + COLUMN_NAME, + initialValue = 1234, + ) + + val logs = dumpLog() + assertThat(logs).doesNotContain(COLUMN_PREFIX) + assertThat(logs).doesNotContain(COLUMN_NAME) + assertThat(logs).doesNotContain("1234") + } + + @Test + fun int_logsInitialWhenCollected() = + testScope.runTest { + val flow = flowOf(5, 6, 7) + + val flowWithLogging = + flow.logDiffsForTable( + tableLogBuffer, + COLUMN_PREFIX, + COLUMN_NAME, + initialValue = 1234, + ) + + systemClock.setCurrentTimeMillis(3000L) + val job = launch { flowWithLogging.collect() } + + val logs = dumpLog() + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(3000L) + SEPARATOR + FULL_NAME + SEPARATOR + "1234" + ) + + job.cancel() + } + + @Test + fun int_logsUpdates() = + testScope.runTest { + systemClock.setCurrentTimeMillis(100L) + val flow = flow { + for (int in listOf(2, 3, 4)) { + systemClock.advanceTime(100L) + emit(int) + } + } + + val flowWithLogging = + flow.logDiffsForTable( + tableLogBuffer, + COLUMN_PREFIX, + COLUMN_NAME, + initialValue = 1, + ) + + val job = launch { flowWithLogging.collect() } + + val logs = dumpLog() + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(100L) + SEPARATOR + FULL_NAME + SEPARATOR + "1" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "2" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(300L) + SEPARATOR + FULL_NAME + SEPARATOR + "3" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(400L) + SEPARATOR + FULL_NAME + SEPARATOR + "4" + ) + + job.cancel() + } + + @Test + fun int_doesNotLogIfSameValue() = + testScope.runTest { + systemClock.setCurrentTimeMillis(100L) + val flow = flow { + for (bool in listOf(2, 3, 3, 3, 2, 6, 6)) { + systemClock.advanceTime(100L) + emit(bool) + } + } + + val flowWithLogging = + flow.logDiffsForTable( + tableLogBuffer, + COLUMN_PREFIX, + COLUMN_NAME, + initialValue = 1, + ) + + val job = launch { flowWithLogging.collect() } + + val logs = dumpLog() + // Input flow: 1@100, 2@200, 3@300, 3@400, 3@500, 2@600, 6@700, 6@800 + // Output log: 1@100, 2@200, 3@300, -----, -----, 2@600, 6@700, ----- + val expected1 = + TABLE_LOG_DATE_FORMAT.format(100L) + SEPARATOR + FULL_NAME + SEPARATOR + "1" + val expected2 = + TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "2" + val expected3 = + TABLE_LOG_DATE_FORMAT.format(300L) + SEPARATOR + FULL_NAME + SEPARATOR + "3" + val expected6 = + TABLE_LOG_DATE_FORMAT.format(600L) + SEPARATOR + FULL_NAME + SEPARATOR + "2" + val expected7 = + TABLE_LOG_DATE_FORMAT.format(700L) + SEPARATOR + FULL_NAME + SEPARATOR + "6" + assertThat(logs).contains(expected1) + assertThat(logs).contains(expected2) + assertThat(logs).contains(expected3) + assertThat(logs).contains(expected6) + assertThat(logs).contains(expected7) + + val unexpected4 = + TABLE_LOG_DATE_FORMAT.format(400L) + SEPARATOR + FULL_NAME + SEPARATOR + "3" + val unexpected5 = + TABLE_LOG_DATE_FORMAT.format(500L) + SEPARATOR + FULL_NAME + SEPARATOR + "3" + val unexpected8 = + TABLE_LOG_DATE_FORMAT.format(800L) + SEPARATOR + FULL_NAME + SEPARATOR + "6" + assertThat(logs).doesNotContain(unexpected4) + assertThat(logs).doesNotContain(unexpected5) + assertThat(logs).doesNotContain(unexpected8) + job.cancel() + } + + @Test + fun int_worksForStateFlows() = + testScope.runTest { + val flow = MutableStateFlow(1111) + + val flowWithLogging = + flow.logDiffsForTable( + tableLogBuffer, + COLUMN_PREFIX, + COLUMN_NAME, + initialValue = 1111, + ) + + systemClock.setCurrentTimeMillis(50L) + val job = launch { flowWithLogging.collect() } + assertThat(dumpLog()) + .contains( + TABLE_LOG_DATE_FORMAT.format(50L) + SEPARATOR + FULL_NAME + SEPARATOR + "1111" + ) + + systemClock.setCurrentTimeMillis(100L) + flow.emit(2222) + assertThat(dumpLog()) + .contains( + TABLE_LOG_DATE_FORMAT.format(100L) + SEPARATOR + FULL_NAME + SEPARATOR + "2222" + ) + + systemClock.setCurrentTimeMillis(200L) + flow.emit(3333) + assertThat(dumpLog()) + .contains( + TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "3333" + ) + + // Doesn't log duplicates + systemClock.setCurrentTimeMillis(300L) + flow.emit(3333) + assertThat(dumpLog()) + .doesNotContain( + TABLE_LOG_DATE_FORMAT.format(300L) + SEPARATOR + FULL_NAME + SEPARATOR + "3333" + ) + + job.cancel() + } + + // ---- Flow<String> tests ---- + + @Test + fun string_doesNotLogWhenNotCollected() { + val flow = flowOf("val5", "val6", "val7") + + flow.logDiffsForTable( + tableLogBuffer, + COLUMN_PREFIX, + COLUMN_NAME, + initialValue = "val1234", + ) + + val logs = dumpLog() + assertThat(logs).doesNotContain(COLUMN_PREFIX) + assertThat(logs).doesNotContain(COLUMN_NAME) + assertThat(logs).doesNotContain("val1234") + } + + @Test + fun string_logsInitialWhenCollected() = + testScope.runTest { + val flow = flowOf("val5", "val6", "val7") + + val flowWithLogging = + flow.logDiffsForTable( + tableLogBuffer, + COLUMN_PREFIX, + COLUMN_NAME, + initialValue = "val1234", + ) + + systemClock.setCurrentTimeMillis(3000L) + val job = launch { flowWithLogging.collect() } + + val logs = dumpLog() + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(3000L) + + SEPARATOR + + FULL_NAME + + SEPARATOR + + "val1234" + ) + + job.cancel() + } + + @Test + fun string_logsUpdates() = + testScope.runTest { + systemClock.setCurrentTimeMillis(100L) + val flow = flow { + for (int in listOf("val2", "val3", "val4")) { + systemClock.advanceTime(100L) + emit(int) + } + } + + val flowWithLogging = + flow.logDiffsForTable( + tableLogBuffer, + COLUMN_PREFIX, + COLUMN_NAME, + initialValue = "val1", + ) + + val job = launch { flowWithLogging.collect() } + + val logs = dumpLog() + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(100L) + SEPARATOR + FULL_NAME + SEPARATOR + "val1" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "val2" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(300L) + SEPARATOR + FULL_NAME + SEPARATOR + "val3" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(400L) + SEPARATOR + FULL_NAME + SEPARATOR + "val4" + ) + + job.cancel() + } + + @Test + fun string_logsNull() = + testScope.runTest { + systemClock.setCurrentTimeMillis(100L) + val flow = flow { + for (int in listOf(null, "something", null)) { + systemClock.advanceTime(100L) + emit(int) + } + } + + val flowWithLogging = + flow.logDiffsForTable( + tableLogBuffer, + COLUMN_PREFIX, + COLUMN_NAME, + initialValue = "start", + ) + + val job = launch { flowWithLogging.collect() } + + val logs = dumpLog() + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(100L) + SEPARATOR + FULL_NAME + SEPARATOR + "start" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "null" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(300L) + + SEPARATOR + + FULL_NAME + + SEPARATOR + + "something" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(400L) + SEPARATOR + FULL_NAME + SEPARATOR + "null" + ) + + job.cancel() + } + + @Test + fun string_doesNotLogIfSameValue() = + testScope.runTest { + systemClock.setCurrentTimeMillis(100L) + val flow = flow { + for (bool in listOf("start", "new", "new", "newer", "newest", "newest")) { + systemClock.advanceTime(100L) + emit(bool) + } + } + + val flowWithLogging = + flow.logDiffsForTable( + tableLogBuffer, + COLUMN_PREFIX, + COLUMN_NAME, + initialValue = "start", + ) + + val job = launch { flowWithLogging.collect() } + + val logs = dumpLog() + // Input flow: start@100, start@200, new@300, new@400, newer@500, newest@600, newest@700 + // Output log: start@100, ---------, new@300, -------, newer@500, newest@600, ---------- + val expected1 = + TABLE_LOG_DATE_FORMAT.format(100L) + SEPARATOR + FULL_NAME + SEPARATOR + "start" + val expected3 = + TABLE_LOG_DATE_FORMAT.format(300L) + SEPARATOR + FULL_NAME + SEPARATOR + "new" + val expected5 = + TABLE_LOG_DATE_FORMAT.format(500L) + SEPARATOR + FULL_NAME + SEPARATOR + "newer" + val expected6 = + TABLE_LOG_DATE_FORMAT.format(600L) + SEPARATOR + FULL_NAME + SEPARATOR + "newest" + assertThat(logs).contains(expected1) + assertThat(logs).contains(expected3) + assertThat(logs).contains(expected5) + assertThat(logs).contains(expected6) + + val unexpected2 = + TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "start" + val unexpected4 = + TABLE_LOG_DATE_FORMAT.format(400L) + SEPARATOR + FULL_NAME + SEPARATOR + "new" + val unexpected7 = + TABLE_LOG_DATE_FORMAT.format(700L) + SEPARATOR + FULL_NAME + SEPARATOR + "newest" + assertThat(logs).doesNotContain(unexpected2) + assertThat(logs).doesNotContain(unexpected4) + assertThat(logs).doesNotContain(unexpected7) + + job.cancel() + } + + @Test + fun string_worksForStateFlows() = + testScope.runTest { + val flow = MutableStateFlow("initial") + + val flowWithLogging = + flow.logDiffsForTable( + tableLogBuffer, + COLUMN_PREFIX, + COLUMN_NAME, + initialValue = "initial", + ) + + systemClock.setCurrentTimeMillis(50L) + val job = launch { flowWithLogging.collect() } + assertThat(dumpLog()) + .contains( + TABLE_LOG_DATE_FORMAT.format(50L) + + SEPARATOR + + FULL_NAME + + SEPARATOR + + "initial" + ) + + systemClock.setCurrentTimeMillis(100L) + flow.emit("nextVal") + assertThat(dumpLog()) + .contains( + TABLE_LOG_DATE_FORMAT.format(100L) + + SEPARATOR + + FULL_NAME + + SEPARATOR + + "nextVal" + ) + + systemClock.setCurrentTimeMillis(200L) + flow.emit("nextNextVal") + assertThat(dumpLog()) + .contains( + TABLE_LOG_DATE_FORMAT.format(200L) + + SEPARATOR + + FULL_NAME + + SEPARATOR + + "nextNextVal" + ) + + // Doesn't log duplicates + systemClock.setCurrentTimeMillis(300L) + flow.emit("nextNextVal") + assertThat(dumpLog()) + .doesNotContain( + TABLE_LOG_DATE_FORMAT.format(300L) + + SEPARATOR + + FULL_NAME + + SEPARATOR + + "nextNextVal" + ) + + job.cancel() + } + + // ---- Flow<Diffable> tests ---- + + @Test + fun diffable_doesNotLogWhenNotCollected() { + val flow = + flowOf( + TestDiffable(1, "1", true), + TestDiffable(2, "2", false), + ) + + val initial = TestDiffable(0, "0", false) + flow.logDiffsForTable( + tableLogBuffer, + COLUMN_PREFIX, + initial, + ) + + val logs = dumpLog() + assertThat(logs).doesNotContain(COLUMN_PREFIX) + assertThat(logs).doesNotContain(TestDiffable.COL_FULL) + assertThat(logs).doesNotContain(TestDiffable.COL_INT) + assertThat(logs).doesNotContain(TestDiffable.COL_STRING) + assertThat(logs).doesNotContain(TestDiffable.COL_BOOLEAN) + } + + @Test + fun diffable_logsInitialWhenCollected_usingLogFull() = + testScope.runTest { + val flow = + flowOf( + TestDiffable(1, "1", true), + TestDiffable(2, "2", false), + ) + + val initial = TestDiffable(1234, "string1234", false) + val flowWithLogging = + flow.logDiffsForTable( + tableLogBuffer, + COLUMN_PREFIX, + initial, + ) + + systemClock.setCurrentTimeMillis(3000L) + val job = launch { flowWithLogging.collect() } + + val logs = dumpLog() + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(3000L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_FULL + + SEPARATOR + + "true" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(3000L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_INT + + SEPARATOR + + "1234" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(3000L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_STRING + + SEPARATOR + + "string1234" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(3000L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_BOOLEAN + + SEPARATOR + + "false" + ) + job.cancel() + } + + @Test + fun diffable_logsUpdates_usingLogDiffs() = + testScope.runTest { + val initialValue = TestDiffable(0, "string0", false) + val diffables = + listOf( + TestDiffable(1, "string1", true), + TestDiffable(2, "string1", true), + TestDiffable(2, "string2", false), + ) + + systemClock.setCurrentTimeMillis(100L) + val flow = flow { + for (diffable in diffables) { + systemClock.advanceTime(100L) + emit(diffable) + } + } + + val flowWithLogging = + flow.logDiffsForTable( + tableLogBuffer, + COLUMN_PREFIX, + initialValue, + ) + + val job = launch { flowWithLogging.collect() } + + val logs = dumpLog() + + // Initial -> first: everything different + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(200L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_FULL + + SEPARATOR + + "false" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(200L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_INT + + SEPARATOR + + "1" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(200L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_STRING + + SEPARATOR + + "string1" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(200L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_BOOLEAN + + SEPARATOR + + "true" + ) + + // First -> second: int different + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(300L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_FULL + + SEPARATOR + + "false" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(300L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_INT + + SEPARATOR + + "2" + ) + assertThat(logs) + .doesNotContain( + TABLE_LOG_DATE_FORMAT.format(300L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_STRING + + SEPARATOR + + "string1" + ) + assertThat(logs) + .doesNotContain( + TABLE_LOG_DATE_FORMAT.format(300L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_BOOLEAN + + SEPARATOR + + "true" + ) + + // Second -> third: string & boolean different + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(400L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_FULL + + SEPARATOR + + "false" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(400L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_STRING + + SEPARATOR + + "string2" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(400L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_BOOLEAN + + SEPARATOR + + "false" + ) + assertThat(logs) + .doesNotContain( + TABLE_LOG_DATE_FORMAT.format(400L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_INT + + SEPARATOR + + "2" + ) + + job.cancel() + } + + @Test + fun diffable_worksForStateFlows() = + testScope.runTest { + val initialValue = TestDiffable(0, "string0", false) + val flow = MutableStateFlow(initialValue) + val flowWithLogging = + flow.logDiffsForTable( + tableLogBuffer, + COLUMN_PREFIX, + initialValue, + ) + + systemClock.setCurrentTimeMillis(50L) + val job = launch { flowWithLogging.collect() } + + var logs = dumpLog() + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(50L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_INT + + SEPARATOR + + "0" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(50L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_STRING + + SEPARATOR + + "string0" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(50L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_BOOLEAN + + SEPARATOR + + "false" + ) + + systemClock.setCurrentTimeMillis(100L) + flow.emit(TestDiffable(1, "string1", true)) + + logs = dumpLog() + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(100L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_INT + + SEPARATOR + + "1" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(100L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_STRING + + SEPARATOR + + "string1" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(100L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_BOOLEAN + + SEPARATOR + + "true" + ) + + // Doesn't log duplicates + systemClock.setCurrentTimeMillis(200L) + flow.emit(TestDiffable(1, "newString", true)) + + logs = dumpLog() + assertThat(logs) + .doesNotContain( + TABLE_LOG_DATE_FORMAT.format(200L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_INT + + SEPARATOR + + "1" + ) + assertThat(logs) + .contains( + TABLE_LOG_DATE_FORMAT.format(200L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_STRING + + SEPARATOR + + "newString" + ) + assertThat(logs) + .doesNotContain( + TABLE_LOG_DATE_FORMAT.format(200L) + + SEPARATOR + + COLUMN_PREFIX + + "." + + TestDiffable.COL_BOOLEAN + + SEPARATOR + + "true" + ) + + job.cancel() + } + + private fun dumpLog(): String { + val outputWriter = StringWriter() + tableLogBuffer.dump(PrintWriter(outputWriter), arrayOf()) + return outputWriter.toString() + } + + class TestDiffable( + private val testInt: Int, + private val testString: String, + private val testBoolean: Boolean, + ) : Diffable<TestDiffable> { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange(COL_FULL, false) + + if (testInt != prevVal.testInt) { + row.logChange(COL_INT, testInt) + } + if (testString != prevVal.testString) { + row.logChange(COL_STRING, testString) + } + if (testBoolean != prevVal.testBoolean) { + row.logChange(COL_BOOLEAN, testBoolean) + } + } + + override fun logFull(row: TableRowLogger) { + row.logChange(COL_FULL, true) + row.logChange(COL_INT, testInt) + row.logChange(COL_STRING, testString) + row.logChange(COL_BOOLEAN, testBoolean) + } + + companion object { + const val COL_INT = "intColumn" + const val COL_STRING = "stringColumn" + const val COL_BOOLEAN = "booleanColumn" + const val COL_FULL = "loggedFullColumn" + } + } + + private companion object { + const val MAX_SIZE = 50 + const val BUFFER_NAME = "LogDiffsForTableTest" + const val COLUMN_PREFIX = "columnPrefix" + const val COLUMN_NAME = "columnName" + const val FULL_NAME = "$COLUMN_PREFIX.$COLUMN_NAME" + private const val SEPARATOR = "|" + } +} |