[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:
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -19,10 +19,17 @@ package com.android.systemui.log.table
|
||||
import android.os.Trace
|
||||
import com.android.systemui.Dumpable
|
||||
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 java.io.PrintWriter
|
||||
import java.text.SimpleDateFormat
|
||||
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.
|
||||
@@ -73,12 +80,18 @@ class TableLogBuffer(
|
||||
maxSize: Int,
|
||||
private val name: String,
|
||||
private val systemClock: SystemClock,
|
||||
private val logcatEchoTracker: LogcatEchoTracker,
|
||||
@Background private val bgDispatcher: CoroutineDispatcher,
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val localLogcat: LogProxy = LogProxyDefault(),
|
||||
) : Dumpable {
|
||||
init {
|
||||
if (maxSize <= 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() }
|
||||
|
||||
@@ -105,6 +118,16 @@ class TableLogBuffer(
|
||||
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].
|
||||
*
|
||||
@@ -189,6 +212,7 @@ class TableLogBuffer(
|
||||
Trace.beginSection("TableLogBuffer#logChange(string)")
|
||||
val change = obtain(timestamp, prefix, columnName, isInitial)
|
||||
change.set(value)
|
||||
tryAddMessage(change)
|
||||
Trace.endSection()
|
||||
}
|
||||
|
||||
@@ -202,6 +226,7 @@ class TableLogBuffer(
|
||||
Trace.beginSection("TableLogBuffer#logChange(boolean)")
|
||||
val change = obtain(timestamp, prefix, columnName, isInitial)
|
||||
change.set(value)
|
||||
tryAddMessage(change)
|
||||
Trace.endSection()
|
||||
}
|
||||
|
||||
@@ -215,9 +240,14 @@ class TableLogBuffer(
|
||||
Trace.beginSection("TableLogBuffer#logChange(int)")
|
||||
val change = obtain(timestamp, prefix, columnName, isInitial)
|
||||
change.set(value)
|
||||
tryAddMessage(change)
|
||||
Trace.endSection()
|
||||
}
|
||||
|
||||
private fun tryAddMessage(change: TableChange) {
|
||||
logMessageChannel.trySend(change)
|
||||
}
|
||||
|
||||
// TODO(b/259454430): Add additional change types here.
|
||||
|
||||
@Synchronized
|
||||
@@ -258,6 +288,17 @@ class TableLogBuffer(
|
||||
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
|
||||
override fun dump(pw: PrintWriter, args: Array<out String>) {
|
||||
pw.println(HEADER_PREFIX + name)
|
||||
@@ -284,6 +325,12 @@ class TableLogBuffer(
|
||||
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].
|
||||
*
|
||||
|
||||
@@ -17,10 +17,15 @@
|
||||
package com.android.systemui.log.table
|
||||
|
||||
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.log.LogBufferHelper.Companion.adjustMaxSize
|
||||
import com.android.systemui.plugins.log.LogcatEchoTracker
|
||||
import com.android.systemui.util.time.SystemClock
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
@SysUISingleton
|
||||
class TableLogBufferFactory
|
||||
@@ -28,6 +33,9 @@ class TableLogBufferFactory
|
||||
constructor(
|
||||
private val dumpManager: DumpManager,
|
||||
private val systemClock: SystemClock,
|
||||
private val logcatEchoTracker: LogcatEchoTracker,
|
||||
@Background private val bgDispatcher: CoroutineDispatcher,
|
||||
@Application private val coroutineScope: CoroutineScope,
|
||||
) {
|
||||
private val existingBuffers = mutableMapOf<String, TableLogBuffer>()
|
||||
|
||||
@@ -44,8 +52,17 @@ constructor(
|
||||
name: String,
|
||||
maxSize: Int,
|
||||
): TableLogBuffer {
|
||||
val tableBuffer = TableLogBuffer(adjustMaxSize(maxSize), name, systemClock)
|
||||
val tableBuffer =
|
||||
TableLogBuffer(
|
||||
adjustMaxSize(maxSize),
|
||||
name,
|
||||
systemClock,
|
||||
logcatEchoTracker,
|
||||
bgDispatcher,
|
||||
coroutineScope,
|
||||
)
|
||||
dumpManager.registerNormalDumpable(name, tableBuffer)
|
||||
tableBuffer.init()
|
||||
return tableBuffer
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController
|
||||
import com.android.systemui.user.data.repository.FakeUserRepository
|
||||
import com.android.systemui.util.mockito.KotlinArgumentCaptor
|
||||
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.time.FakeSystemClock
|
||||
import com.android.systemui.util.time.SystemClock
|
||||
@@ -191,8 +192,24 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
|
||||
bypassControllerOverride: KeyguardBypassController? = bypassController
|
||||
): DeviceEntryFaceAuthRepositoryImpl {
|
||||
val systemClock = FakeSystemClock()
|
||||
val faceAuthBuffer = TableLogBuffer(10, "face auth", systemClock)
|
||||
val faceDetectBuffer = TableLogBuffer(10, "face detect", systemClock)
|
||||
val faceAuthBuffer =
|
||||
TableLogBuffer(
|
||||
10,
|
||||
"face auth",
|
||||
systemClock,
|
||||
mock(),
|
||||
testDispatcher,
|
||||
testScope.backgroundScope
|
||||
)
|
||||
val faceDetectBuffer =
|
||||
TableLogBuffer(
|
||||
10,
|
||||
"face detect",
|
||||
systemClock,
|
||||
mock(),
|
||||
testDispatcher,
|
||||
testScope.backgroundScope
|
||||
)
|
||||
keyguardTransitionRepository = FakeKeyguardTransitionRepository()
|
||||
val keyguardTransitionInteractor =
|
||||
KeyguardTransitionInteractor(keyguardTransitionRepository)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package com.android.systemui.log.table
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
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.google.common.truth.Truth.assertThat
|
||||
import java.io.PrintWriter
|
||||
@@ -39,7 +40,8 @@ import org.junit.Test
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
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 tableLogBuffer: TableLogBuffer
|
||||
@@ -47,7 +49,15 @@ class LogDiffsForTableTest : SysuiTestCase() {
|
||||
@Before
|
||||
fun setUp() {
|
||||
systemClock = FakeSystemClock()
|
||||
tableLogBuffer = TableLogBuffer(MAX_SIZE, BUFFER_NAME, systemClock)
|
||||
tableLogBuffer =
|
||||
TableLogBuffer(
|
||||
MAX_SIZE,
|
||||
BUFFER_NAME,
|
||||
systemClock,
|
||||
mock(),
|
||||
testDispatcher,
|
||||
testScope.backgroundScope,
|
||||
)
|
||||
}
|
||||
|
||||
// ---- Flow<Boolean> tests ----
|
||||
|
||||
@@ -22,13 +22,20 @@ import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
class TableLogBufferFactoryTest : SysuiTestCase() {
|
||||
private val dumpManager: DumpManager = mock()
|
||||
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
|
||||
fun create_alwaysCreatesNewInstance() {
|
||||
|
||||
@@ -19,31 +19,66 @@ package com.android.systemui.log.table
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
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.google.common.truth.Truth.assertThat
|
||||
import java.io.PrintWriter
|
||||
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.Test
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
class TableLogBufferTest : SysuiTestCase() {
|
||||
private lateinit var underTest: TableLogBuffer
|
||||
|
||||
private lateinit var systemClock: FakeSystemClock
|
||||
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
|
||||
fun setup() {
|
||||
localLogcat = FakeLogProxy()
|
||||
logcatEchoTracker = mock()
|
||||
systemClock = FakeSystemClock()
|
||||
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)
|
||||
fun maxSizeZero_throwsException() {
|
||||
TableLogBuffer(maxSize = 0, "name", systemClock)
|
||||
TableLogBuffer(
|
||||
maxSize = 0,
|
||||
"name",
|
||||
systemClock,
|
||||
logcatEchoTracker,
|
||||
testDispatcher,
|
||||
testScope.backgroundScope,
|
||||
localLogcat = localLogcat,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -791,6 +826,112 @@ class TableLogBufferTest : SysuiTestCase() {
|
||||
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 {
|
||||
underTest.dump(PrintWriter(outputWriter), arrayOf())
|
||||
return outputWriter.toString()
|
||||
|
||||
@@ -53,6 +53,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
@@ -92,13 +93,15 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
|
||||
private val mobileMappings = FakeMobileMappingsProxy()
|
||||
private val subscriptionManagerProxy = FakeSubscriptionManagerProxy()
|
||||
|
||||
private val testDispatcher = UnconfinedTestDispatcher()
|
||||
private val scope = CoroutineScope(IMMEDIATE)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
logFactory = TableLogBufferFactory(dumpManager, FakeSystemClock())
|
||||
logFactory =
|
||||
TableLogBufferFactory(dumpManager, FakeSystemClock(), mock(), testDispatcher, scope)
|
||||
|
||||
// Never start in demo mode
|
||||
whenever(demoModeController.isInDemoMode).thenReturn(false)
|
||||
|
||||
@@ -61,11 +61,18 @@ import org.junit.runners.Parameterized.Parameters
|
||||
internal class DemoMobileConnectionParameterizedTest(private val testCase: TestCase) :
|
||||
SysuiTestCase() {
|
||||
|
||||
private val logFactory = TableLogBufferFactory(mock(), FakeSystemClock())
|
||||
|
||||
private val testDispatcher = UnconfinedTestDispatcher()
|
||||
private val testScope = TestScope(testDispatcher)
|
||||
|
||||
private val logFactory =
|
||||
TableLogBufferFactory(
|
||||
mock(),
|
||||
FakeSystemClock(),
|
||||
mock(),
|
||||
testDispatcher,
|
||||
testScope.backgroundScope,
|
||||
)
|
||||
|
||||
private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
|
||||
private val fakeWifiEventFlow = MutableStateFlow<FakeWifiEventModel?>(null)
|
||||
|
||||
|
||||
@@ -54,13 +54,20 @@ import org.junit.Test
|
||||
@SmallTest
|
||||
class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
|
||||
private val dumpManager: DumpManager = mock()
|
||||
private val logFactory = TableLogBufferFactory(dumpManager, FakeSystemClock())
|
||||
|
||||
private val testDispatcher = UnconfinedTestDispatcher()
|
||||
private val testScope = TestScope(testDispatcher)
|
||||
|
||||
private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(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 mobileDataSource: DemoModeMobileConnectionDataSource
|
||||
|
||||
@@ -66,7 +66,15 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
|
||||
private val systemClock = FakeSystemClock()
|
||||
private val testDispatcher = UnconfinedTestDispatcher()
|
||||
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 carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>()
|
||||
|
||||
@@ -294,7 +302,14 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun factory_reusesLogBuffersForSameConnection() =
|
||||
testScope.runTest {
|
||||
val realLoggerFactory = TableLogBufferFactory(mock(), FakeSystemClock())
|
||||
val realLoggerFactory =
|
||||
TableLogBufferFactory(
|
||||
mock(),
|
||||
FakeSystemClock(),
|
||||
mock(),
|
||||
testDispatcher,
|
||||
testScope.backgroundScope,
|
||||
)
|
||||
|
||||
val factory =
|
||||
FullMobileConnectionRepository.Factory(
|
||||
@@ -329,7 +344,14 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun factory_reusesLogBuffersForSameSubIDevenIfCarrierMerged() =
|
||||
testScope.runTest {
|
||||
val realLoggerFactory = TableLogBufferFactory(mock(), FakeSystemClock())
|
||||
val realLoggerFactory =
|
||||
TableLogBufferFactory(
|
||||
mock(),
|
||||
FakeSystemClock(),
|
||||
mock(),
|
||||
testDispatcher,
|
||||
testScope.backgroundScope,
|
||||
)
|
||||
|
||||
val factory =
|
||||
FullMobileConnectionRepository.Factory(
|
||||
|
||||
@@ -61,6 +61,16 @@ class MobileIconsInteractorTest : SysuiTestCase() {
|
||||
private val testDispatcher = UnconfinedTestDispatcher()
|
||||
private val testScope = TestScope(testDispatcher)
|
||||
|
||||
private val tableLogBuffer =
|
||||
TableLogBuffer(
|
||||
8,
|
||||
"MobileIconsInteractorTest",
|
||||
FakeSystemClock(),
|
||||
mock(),
|
||||
testDispatcher,
|
||||
testScope.backgroundScope,
|
||||
)
|
||||
|
||||
@Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
|
||||
|
||||
@Before
|
||||
@@ -687,16 +697,14 @@ class MobileIconsInteractorTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val tableLogBuffer =
|
||||
TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock())
|
||||
|
||||
private const val SUB_1_ID = 1
|
||||
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 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 val SUB_3_OPP =
|
||||
@@ -705,7 +713,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
|
||||
isOpportunistic = true,
|
||||
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 val SUB_4_OPP =
|
||||
@@ -714,6 +722,6 @@ class MobileIconsInteractorTest : SysuiTestCase() {
|
||||
isOpportunistic = true,
|
||||
groupUuid = ParcelUuid(UUID.randomUUID()),
|
||||
)
|
||||
private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID, tableLogBuffer)
|
||||
private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID, mock())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user