[Sb refactor] Add local logcat echoing to TableLogBuffer

TableLogBuffer is a sibling of LogBuffer, so it needs to reimplement the
desirable behaviors of Logbuffer. In this CL, we're adding support for
local logcat echoing on every table. The logging happens only in the
background and can fail silently so that it never blocks the main
thread.

This implementation differs from the LogBuffer implementation which was
written before the coroutines library was available, so it doesn't use
a background thread-per-table like the LogBuffer implementation does.
Rather, this implementation uses a Channel + a background
CoroutineDispatcher to execute all logging in the background.

We do leverage the existing LogcatEchoTracker class, which implements a
generic settings listener + settings format for turning logcat echoing
on at the buffer or tag level. In our case, the buffer name is the name
of the table, and the tags are the column names.

Note that all TableLogBuffer logs are considered to be `LogLevel.DEBUG`
since the API for `logChange` does not let the caller decide on a log
level. Arguably this makes perfect sense since table logs are _not_
meant to be warning or error logs. We could probably make the case that
they are `info` or `verbose`, but that would be immaterial to this
overall change.

Test: existing tests in tests/src/com/android/systemui/statusbar/pipelinestyle
Test: TableLogBufferTest
Test: manual
Bug: 255951648
Change-Id: I930e4c7730117860ab22e8849c84f1f96f786b30
This commit is contained in:
Evan Laird
2023-04-12 11:01:33 -04:00
parent 66c3f03673
commit d4c120389b
13 changed files with 430 additions and 21 deletions

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2023 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 android.util.Log
import javax.inject.Inject
/** Dagger-friendly interface so we can inject a fake [android.util.Log] in tests */
interface LogProxy {
/** verbose log */
fun v(tag: String, message: String)
/** debug log */
fun d(tag: String, message: String)
/** info log */
fun i(tag: String, message: String)
/** warning log */
fun w(tag: String, message: String)
/** error log */
fun e(tag: String, message: String)
/** wtf log */
fun wtf(tag: String, message: String)
}
class LogProxyDefault @Inject constructor() : LogProxy {
override fun v(tag: String, message: String) {
Log.v(tag, message)
}
override fun d(tag: String, message: String) {
Log.d(tag, message)
}
override fun i(tag: String, message: String) {
Log.i(tag, message)
}
override fun w(tag: String, message: String) {
Log.w(tag, message)
}
override fun e(tag: String, message: String) {
Log.e(tag, message)
}
override fun wtf(tag: String, message: String) {
Log.wtf(tag, message)
}
}

View File

@@ -19,10 +19,17 @@ package com.android.systemui.log.table
import android.os.Trace import android.os.Trace
import com.android.systemui.Dumpable import com.android.systemui.Dumpable
import com.android.systemui.common.buffer.RingBuffer import com.android.systemui.common.buffer.RingBuffer
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.plugins.log.LogcatEchoTracker
import com.android.systemui.util.time.SystemClock import com.android.systemui.util.time.SystemClock
import java.io.PrintWriter import java.io.PrintWriter
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
/** /**
* A logger that logs changes in table format. * A logger that logs changes in table format.
@@ -73,12 +80,18 @@ class TableLogBuffer(
maxSize: Int, maxSize: Int,
private val name: String, private val name: String,
private val systemClock: SystemClock, private val systemClock: SystemClock,
private val logcatEchoTracker: LogcatEchoTracker,
@Background private val bgDispatcher: CoroutineDispatcher,
private val coroutineScope: CoroutineScope,
private val localLogcat: LogProxy = LogProxyDefault(),
) : Dumpable { ) : Dumpable {
init { init {
if (maxSize <= 0) { if (maxSize <= 0) {
throw IllegalArgumentException("maxSize must be > 0") throw IllegalArgumentException("maxSize must be > 0")
} }
} }
// For local logcat, send messages across this channel so the background job can process them
private val logMessageChannel = Channel<TableChange>(capacity = 10)
private val buffer = RingBuffer(maxSize) { TableChange() } private val buffer = RingBuffer(maxSize) { TableChange() }
@@ -105,6 +118,16 @@ class TableLogBuffer(
tableLogBuffer = this, tableLogBuffer = this,
) )
/** Start this log buffer logging in the background */
internal fun init() {
coroutineScope.launch(bgDispatcher) {
while (!logMessageChannel.isClosedForReceive) {
val log = logMessageChannel.receive()
echoToDesiredEndpoints(log)
}
}
}
/** /**
* Log the differences between [prevVal] and [newVal]. * Log the differences between [prevVal] and [newVal].
* *
@@ -189,6 +212,7 @@ class TableLogBuffer(
Trace.beginSection("TableLogBuffer#logChange(string)") Trace.beginSection("TableLogBuffer#logChange(string)")
val change = obtain(timestamp, prefix, columnName, isInitial) val change = obtain(timestamp, prefix, columnName, isInitial)
change.set(value) change.set(value)
tryAddMessage(change)
Trace.endSection() Trace.endSection()
} }
@@ -202,6 +226,7 @@ class TableLogBuffer(
Trace.beginSection("TableLogBuffer#logChange(boolean)") Trace.beginSection("TableLogBuffer#logChange(boolean)")
val change = obtain(timestamp, prefix, columnName, isInitial) val change = obtain(timestamp, prefix, columnName, isInitial)
change.set(value) change.set(value)
tryAddMessage(change)
Trace.endSection() Trace.endSection()
} }
@@ -215,9 +240,14 @@ class TableLogBuffer(
Trace.beginSection("TableLogBuffer#logChange(int)") Trace.beginSection("TableLogBuffer#logChange(int)")
val change = obtain(timestamp, prefix, columnName, isInitial) val change = obtain(timestamp, prefix, columnName, isInitial)
change.set(value) change.set(value)
tryAddMessage(change)
Trace.endSection() Trace.endSection()
} }
private fun tryAddMessage(change: TableChange) {
logMessageChannel.trySend(change)
}
// TODO(b/259454430): Add additional change types here. // TODO(b/259454430): Add additional change types here.
@Synchronized @Synchronized
@@ -258,6 +288,17 @@ class TableLogBuffer(
Trace.endSection() Trace.endSection()
} }
private fun echoToDesiredEndpoints(change: TableChange) {
if (
logcatEchoTracker.isBufferLoggable(bufferName = name, LogLevel.DEBUG) ||
logcatEchoTracker.isTagLoggable(change.columnName, LogLevel.DEBUG)
) {
if (change.hasData()) {
localLogcat.d(name, change.logcatRepresentation())
}
}
}
@Synchronized @Synchronized
override fun dump(pw: PrintWriter, args: Array<out String>) { override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.println(HEADER_PREFIX + name) pw.println(HEADER_PREFIX + name)
@@ -284,6 +325,12 @@ class TableLogBuffer(
pw.println() pw.println()
} }
/** Transforms an individual [TableChange] into a String for logcat */
private fun TableChange.logcatRepresentation(): String {
val formattedTimestamp = TABLE_LOG_DATE_FORMAT.format(timestamp)
return "$formattedTimestamp$SEPARATOR${getName()}$SEPARATOR${getVal()}"
}
/** /**
* A private implementation of [TableRowLogger]. * A private implementation of [TableRowLogger].
* *

View File

@@ -17,10 +17,15 @@
package com.android.systemui.log.table package com.android.systemui.log.table
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager import com.android.systemui.dump.DumpManager
import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
import com.android.systemui.plugins.log.LogcatEchoTracker
import com.android.systemui.util.time.SystemClock import com.android.systemui.util.time.SystemClock
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@SysUISingleton @SysUISingleton
class TableLogBufferFactory class TableLogBufferFactory
@@ -28,6 +33,9 @@ class TableLogBufferFactory
constructor( constructor(
private val dumpManager: DumpManager, private val dumpManager: DumpManager,
private val systemClock: SystemClock, private val systemClock: SystemClock,
private val logcatEchoTracker: LogcatEchoTracker,
@Background private val bgDispatcher: CoroutineDispatcher,
@Application private val coroutineScope: CoroutineScope,
) { ) {
private val existingBuffers = mutableMapOf<String, TableLogBuffer>() private val existingBuffers = mutableMapOf<String, TableLogBuffer>()
@@ -44,8 +52,17 @@ constructor(
name: String, name: String,
maxSize: Int, maxSize: Int,
): TableLogBuffer { ): TableLogBuffer {
val tableBuffer = TableLogBuffer(adjustMaxSize(maxSize), name, systemClock) val tableBuffer =
TableLogBuffer(
adjustMaxSize(maxSize),
name,
systemClock,
logcatEchoTracker,
bgDispatcher,
coroutineScope,
)
dumpManager.registerNormalDumpable(name, tableBuffer) dumpManager.registerNormalDumpable(name, tableBuffer)
tableBuffer.init()
return tableBuffer return tableBuffer
} }

View File

@@ -65,6 +65,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.KotlinArgumentCaptor import com.android.systemui.util.mockito.KotlinArgumentCaptor
import com.android.systemui.util.mockito.captureMany import com.android.systemui.util.mockito.captureMany
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.time.SystemClock import com.android.systemui.util.time.SystemClock
@@ -191,8 +192,24 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
bypassControllerOverride: KeyguardBypassController? = bypassController bypassControllerOverride: KeyguardBypassController? = bypassController
): DeviceEntryFaceAuthRepositoryImpl { ): DeviceEntryFaceAuthRepositoryImpl {
val systemClock = FakeSystemClock() val systemClock = FakeSystemClock()
val faceAuthBuffer = TableLogBuffer(10, "face auth", systemClock) val faceAuthBuffer =
val faceDetectBuffer = TableLogBuffer(10, "face detect", systemClock) TableLogBuffer(
10,
"face auth",
systemClock,
mock(),
testDispatcher,
testScope.backgroundScope
)
val faceDetectBuffer =
TableLogBuffer(
10,
"face detect",
systemClock,
mock(),
testDispatcher,
testScope.backgroundScope
)
keyguardTransitionRepository = FakeKeyguardTransitionRepository() keyguardTransitionRepository = FakeKeyguardTransitionRepository()
val keyguardTransitionInteractor = val keyguardTransitionInteractor =
KeyguardTransitionInteractor(keyguardTransitionRepository) KeyguardTransitionInteractor(keyguardTransitionRepository)

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2023 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
/**
* Fake [LogProxy] that collects all lines sent to it. Mimics the ADB logcat format without the
* timestamp. [FakeLogProxy.d] will write a log like so:
* ```
* logger.d("TAG", "message here")
* // writes this to the [logs] field
* "D TAG: message here"
* ```
*
* Logs sent to this class are collected as a list of strings for simple test assertions.
*/
class FakeLogProxy : LogProxy {
val logs: MutableList<String> = mutableListOf()
override fun v(tag: String, message: String) {
logs.add("V $tag: $message")
}
override fun d(tag: String, message: String) {
logs.add("D $tag: $message")
}
override fun i(tag: String, message: String) {
logs.add("I $tag: $message")
}
override fun w(tag: String, message: String) {
logs.add("W $tag: $message")
}
override fun e(tag: String, message: String) {
logs.add("E $tag: $message")
}
override fun wtf(tag: String, message: String) {
logs.add("WTF $tag: $message")
}
}

View File

@@ -19,6 +19,7 @@ package com.android.systemui.log.table
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter import java.io.PrintWriter
@@ -39,7 +40,8 @@ import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class LogDiffsForTableTest : SysuiTestCase() { class LogDiffsForTableTest : SysuiTestCase() {
private val testScope = TestScope(UnconfinedTestDispatcher()) private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
private lateinit var systemClock: FakeSystemClock private lateinit var systemClock: FakeSystemClock
private lateinit var tableLogBuffer: TableLogBuffer private lateinit var tableLogBuffer: TableLogBuffer
@@ -47,7 +49,15 @@ class LogDiffsForTableTest : SysuiTestCase() {
@Before @Before
fun setUp() { fun setUp() {
systemClock = FakeSystemClock() systemClock = FakeSystemClock()
tableLogBuffer = TableLogBuffer(MAX_SIZE, BUFFER_NAME, systemClock) tableLogBuffer =
TableLogBuffer(
MAX_SIZE,
BUFFER_NAME,
systemClock,
mock(),
testDispatcher,
testScope.backgroundScope,
)
} }
// ---- Flow<Boolean> tests ---- // ---- Flow<Boolean> tests ----

View File

@@ -22,13 +22,20 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.mock
import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import org.junit.Test import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest @SmallTest
class TableLogBufferFactoryTest : SysuiTestCase() { class TableLogBufferFactoryTest : SysuiTestCase() {
private val dumpManager: DumpManager = mock() private val dumpManager: DumpManager = mock()
private val systemClock = FakeSystemClock() private val systemClock = FakeSystemClock()
private val underTest = TableLogBufferFactory(dumpManager, systemClock) private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
private val underTest =
TableLogBufferFactory(dumpManager, systemClock, mock(), testDispatcher, testScope)
@Test @Test
fun create_alwaysCreatesNewInstance() { fun create_alwaysCreatesNewInstance() {

View File

@@ -19,31 +19,66 @@ package com.android.systemui.log.table
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX
import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.plugins.log.LogcatEchoTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter import java.io.PrintWriter
import java.io.StringWriter import java.io.StringWriter
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest @SmallTest
class TableLogBufferTest : SysuiTestCase() { class TableLogBufferTest : SysuiTestCase() {
private lateinit var underTest: TableLogBuffer private lateinit var underTest: TableLogBuffer
private lateinit var systemClock: FakeSystemClock private lateinit var systemClock: FakeSystemClock
private lateinit var outputWriter: StringWriter private lateinit var outputWriter: StringWriter
private lateinit var logcatEchoTracker: LogcatEchoTracker
private lateinit var localLogcat: FakeLogProxy
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@Before @Before
fun setup() { fun setup() {
localLogcat = FakeLogProxy()
logcatEchoTracker = mock()
systemClock = FakeSystemClock() systemClock = FakeSystemClock()
outputWriter = StringWriter() outputWriter = StringWriter()
underTest = TableLogBuffer(MAX_SIZE, NAME, systemClock) underTest =
TableLogBuffer(
MAX_SIZE,
NAME,
systemClock,
logcatEchoTracker,
testDispatcher,
testScope.backgroundScope,
localLogcat = localLogcat,
)
underTest.init()
} }
@Test(expected = IllegalArgumentException::class) @Test(expected = IllegalArgumentException::class)
fun maxSizeZero_throwsException() { fun maxSizeZero_throwsException() {
TableLogBuffer(maxSize = 0, "name", systemClock) TableLogBuffer(
maxSize = 0,
"name",
systemClock,
logcatEchoTracker,
testDispatcher,
testScope.backgroundScope,
localLogcat = localLogcat,
)
} }
@Test @Test
@@ -791,6 +826,112 @@ class TableLogBufferTest : SysuiTestCase() {
assertThat(dumpedString).contains(evictedColumnLog3) assertThat(dumpedString).contains(evictedColumnLog3)
} }
@Test
fun logcat_bufferNotLoggable_tagNotLoggable_noEcho() {
whenever(logcatEchoTracker.isBufferLoggable(eq(NAME), any())).thenReturn(false)
whenever(logcatEchoTracker.isTagLoggable(eq("columnName"), any())).thenReturn(false)
underTest.logChange("prefix", "columnName", true)
assertThat(localLogcat.logs).isEmpty()
}
@Test
fun logcat_bufferIsLoggable_tagNotLoggable_echoes() {
whenever(logcatEchoTracker.isBufferLoggable(eq(NAME), any())).thenReturn(true)
whenever(logcatEchoTracker.isTagLoggable(eq("columnName"), any())).thenReturn(false)
underTest.logChange("prefix", "columnName", true)
assertThat(localLogcat.logs).hasSize(1)
}
@Test
fun logcat_bufferNotLoggable_tagIsLoggable_echoes() {
whenever(logcatEchoTracker.isBufferLoggable(eq(NAME), any())).thenReturn(false)
whenever(logcatEchoTracker.isTagLoggable(eq("columnName"), any())).thenReturn(true)
underTest.logChange("prefix", "columnName", true)
assertThat(localLogcat.logs).hasSize(1)
}
@Test
fun logcat_echoesDebugLogs_debugDisabled_noEcho() {
// Allow any log other than debug
whenever(logcatEchoTracker.isBufferLoggable(eq(NAME), any())).thenAnswer { invocation ->
(invocation.getArgument(1) as LogLevel) != LogLevel.DEBUG
}
underTest.logChange("prefix", "columnName", true)
assertThat(localLogcat.logs).isEmpty()
}
@Test
fun logcat_echoesDebugLogs_debugEnabled_echoes() {
// Only allow debug logs
whenever(logcatEchoTracker.isBufferLoggable(eq(NAME), eq(LogLevel.DEBUG))).thenReturn(true)
underTest.logChange("prefix", "columnName", true)
assertThat(localLogcat.logs).hasSize(1)
}
@Test
fun logcat_bufferNotLoggable_tagIsLoggable_usesColNameForTagCheck() {
systemClock.setCurrentTimeMillis(1000L)
val nonLoggingTag = "nonLoggingColName"
val loggingTag = "loggingColName"
whenever(logcatEchoTracker.isBufferLoggable(eq(NAME), any())).thenReturn(false)
whenever(logcatEchoTracker.isTagLoggable(eq(loggingTag), eq(LogLevel.DEBUG)))
.thenReturn(true)
whenever(logcatEchoTracker.isTagLoggable(eq(nonLoggingTag), eq(LogLevel.DEBUG)))
.thenReturn(false)
underTest.logChange("", nonLoggingTag, true)
underTest.logChange("", loggingTag, true)
assertThat(localLogcat.logs).hasSize(1)
val timestamp = TABLE_LOG_DATE_FORMAT.format(1000L)
val expectedMessage = "${timestamp}${SEPARATOR}${loggingTag}${SEPARATOR}true"
val expectedLine = "D $NAME: $expectedMessage"
assertThat(localLogcat.logs[0]).isEqualTo(expectedLine)
}
@Test
fun logcat_bufferLoggable_multipleMessagesAreEchoed() {
systemClock.setCurrentTimeMillis(1000L)
whenever(logcatEchoTracker.isBufferLoggable(eq(NAME), any())).thenReturn(true)
val col1 = "column1"
val col2 = "column2"
// Log a couple of columns that flip bits
underTest.logChange("", col1, true)
underTest.logChange("", col2, false)
underTest.logChange("", col1, false)
underTest.logChange("", col2, true)
assertThat(localLogcat.logs).hasSize(4)
val timestamp = TABLE_LOG_DATE_FORMAT.format(1000L)
val msg1 = "${timestamp}${SEPARATOR}${col1}${SEPARATOR}true"
val msg2 = "${timestamp}${SEPARATOR}${col2}${SEPARATOR}false"
val msg3 = "${timestamp}${SEPARATOR}${col1}${SEPARATOR}false"
val msg4 = "${timestamp}${SEPARATOR}${col2}${SEPARATOR}true"
val expected = listOf(msg1, msg2, msg3, msg4).map { "D $NAME: $it" }
// Logs use the same bg dispatcher for writing to logcat, they should be in order
for ((msg, logLine) in expected zip localLogcat.logs) {
assertThat(logLine).isEqualTo(msg)
}
}
private fun dumpChanges(): String { private fun dumpChanges(): String {
underTest.dump(PrintWriter(outputWriter), arrayOf()) underTest.dump(PrintWriter(outputWriter), arrayOf())
return outputWriter.toString() return outputWriter.toString()

View File

@@ -53,6 +53,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@@ -92,13 +93,15 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
private val mobileMappings = FakeMobileMappingsProxy() private val mobileMappings = FakeMobileMappingsProxy()
private val subscriptionManagerProxy = FakeSubscriptionManagerProxy() private val subscriptionManagerProxy = FakeSubscriptionManagerProxy()
private val testDispatcher = UnconfinedTestDispatcher()
private val scope = CoroutineScope(IMMEDIATE) private val scope = CoroutineScope(IMMEDIATE)
@Before @Before
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
logFactory = TableLogBufferFactory(dumpManager, FakeSystemClock()) logFactory =
TableLogBufferFactory(dumpManager, FakeSystemClock(), mock(), testDispatcher, scope)
// Never start in demo mode // Never start in demo mode
whenever(demoModeController.isInDemoMode).thenReturn(false) whenever(demoModeController.isInDemoMode).thenReturn(false)

View File

@@ -61,11 +61,18 @@ import org.junit.runners.Parameterized.Parameters
internal class DemoMobileConnectionParameterizedTest(private val testCase: TestCase) : internal class DemoMobileConnectionParameterizedTest(private val testCase: TestCase) :
SysuiTestCase() { SysuiTestCase() {
private val logFactory = TableLogBufferFactory(mock(), FakeSystemClock())
private val testDispatcher = UnconfinedTestDispatcher() private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher) private val testScope = TestScope(testDispatcher)
private val logFactory =
TableLogBufferFactory(
mock(),
FakeSystemClock(),
mock(),
testDispatcher,
testScope.backgroundScope,
)
private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null) private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
private val fakeWifiEventFlow = MutableStateFlow<FakeWifiEventModel?>(null) private val fakeWifiEventFlow = MutableStateFlow<FakeWifiEventModel?>(null)

View File

@@ -54,13 +54,20 @@ import org.junit.Test
@SmallTest @SmallTest
class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
private val dumpManager: DumpManager = mock() private val dumpManager: DumpManager = mock()
private val logFactory = TableLogBufferFactory(dumpManager, FakeSystemClock())
private val testDispatcher = UnconfinedTestDispatcher() private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher) private val testScope = TestScope(testDispatcher)
private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null) private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
private val fakeWifiEventFlow = MutableStateFlow<FakeWifiEventModel?>(null) private val fakeWifiEventFlow = MutableStateFlow<FakeWifiEventModel?>(null)
private val logFactory =
TableLogBufferFactory(
dumpManager,
FakeSystemClock(),
mock(),
testDispatcher,
testScope.backgroundScope,
)
private lateinit var underTest: DemoMobileConnectionsRepository private lateinit var underTest: DemoMobileConnectionsRepository
private lateinit var mobileDataSource: DemoModeMobileConnectionDataSource private lateinit var mobileDataSource: DemoModeMobileConnectionDataSource

View File

@@ -66,7 +66,15 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
private val systemClock = FakeSystemClock() private val systemClock = FakeSystemClock()
private val testDispatcher = UnconfinedTestDispatcher() private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher) private val testScope = TestScope(testDispatcher)
private val tableLogBuffer = TableLogBuffer(maxSize = 100, name = "TestName", systemClock) private val tableLogBuffer =
TableLogBuffer(
maxSize = 100,
name = "TestName",
systemClock,
mock(),
testDispatcher,
testScope.backgroundScope,
)
private val mobileFactory = mock<MobileConnectionRepositoryImpl.Factory>() private val mobileFactory = mock<MobileConnectionRepositoryImpl.Factory>()
private val carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>() private val carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>()
@@ -294,7 +302,14 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun factory_reusesLogBuffersForSameConnection() = fun factory_reusesLogBuffersForSameConnection() =
testScope.runTest { testScope.runTest {
val realLoggerFactory = TableLogBufferFactory(mock(), FakeSystemClock()) val realLoggerFactory =
TableLogBufferFactory(
mock(),
FakeSystemClock(),
mock(),
testDispatcher,
testScope.backgroundScope,
)
val factory = val factory =
FullMobileConnectionRepository.Factory( FullMobileConnectionRepository.Factory(
@@ -329,7 +344,14 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun factory_reusesLogBuffersForSameSubIDevenIfCarrierMerged() = fun factory_reusesLogBuffersForSameSubIDevenIfCarrierMerged() =
testScope.runTest { testScope.runTest {
val realLoggerFactory = TableLogBufferFactory(mock(), FakeSystemClock()) val realLoggerFactory =
TableLogBufferFactory(
mock(),
FakeSystemClock(),
mock(),
testDispatcher,
testScope.backgroundScope,
)
val factory = val factory =
FullMobileConnectionRepository.Factory( FullMobileConnectionRepository.Factory(

View File

@@ -61,6 +61,16 @@ class MobileIconsInteractorTest : SysuiTestCase() {
private val testDispatcher = UnconfinedTestDispatcher() private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher) private val testScope = TestScope(testDispatcher)
private val tableLogBuffer =
TableLogBuffer(
8,
"MobileIconsInteractorTest",
FakeSystemClock(),
mock(),
testDispatcher,
testScope.backgroundScope,
)
@Mock private lateinit var carrierConfigTracker: CarrierConfigTracker @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
@Before @Before
@@ -687,16 +697,14 @@ class MobileIconsInteractorTest : SysuiTestCase() {
} }
companion object { companion object {
private val tableLogBuffer =
TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock())
private const val SUB_1_ID = 1 private const val SUB_1_ID = 1
private val SUB_1 = SubscriptionModel(subscriptionId = SUB_1_ID) private val SUB_1 = SubscriptionModel(subscriptionId = SUB_1_ID)
private val CONNECTION_1 = FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer) private val CONNECTION_1 = FakeMobileConnectionRepository(SUB_1_ID, mock())
private const val SUB_2_ID = 2 private const val SUB_2_ID = 2
private val SUB_2 = SubscriptionModel(subscriptionId = SUB_2_ID) private val SUB_2 = SubscriptionModel(subscriptionId = SUB_2_ID)
private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID, tableLogBuffer) private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID, mock())
private const val SUB_3_ID = 3 private const val SUB_3_ID = 3
private val SUB_3_OPP = private val SUB_3_OPP =
@@ -705,7 +713,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
isOpportunistic = true, isOpportunistic = true,
groupUuid = ParcelUuid(UUID.randomUUID()), groupUuid = ParcelUuid(UUID.randomUUID()),
) )
private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID, tableLogBuffer) private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID, mock())
private const val SUB_4_ID = 4 private const val SUB_4_ID = 4
private val SUB_4_OPP = private val SUB_4_OPP =
@@ -714,6 +722,6 @@ class MobileIconsInteractorTest : SysuiTestCase() {
isOpportunistic = true, isOpportunistic = true,
groupUuid = ParcelUuid(UUID.randomUUID()), groupUuid = ParcelUuid(UUID.randomUUID()),
) )
private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID, tableLogBuffer) private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID, mock())
} }
} }