Merge changes from topic "cherrypicker-L19100000960963656:N27300001373608395" into udc-qpr-dev
* changes: Move TableLogBuffer into its own DumpManager collection Rearchitect DumpManager to be registry-only
This commit is contained in:
@@ -30,8 +30,10 @@ import com.android.internal.os.BinderInternal;
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.dump.DumpHandler;
|
||||
import com.android.systemui.dump.LogBufferEulogizer;
|
||||
import com.android.systemui.dump.LogBufferFreezer;
|
||||
import com.android.systemui.dump.SystemUIAuxiliaryDumpService;
|
||||
import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager;
|
||||
import com.android.systemui.statusbar.policy.BatteryStateNotifier;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
@@ -44,22 +46,29 @@ public class SystemUIService extends Service {
|
||||
private final Handler mMainHandler;
|
||||
private final DumpHandler mDumpHandler;
|
||||
private final BroadcastDispatcher mBroadcastDispatcher;
|
||||
private final LogBufferEulogizer mLogBufferEulogizer;
|
||||
private final LogBufferFreezer mLogBufferFreezer;
|
||||
private final BatteryStateNotifier mBatteryStateNotifier;
|
||||
|
||||
private final UncaughtExceptionPreHandlerManager mUncaughtExceptionPreHandlerManager;
|
||||
|
||||
@Inject
|
||||
public SystemUIService(
|
||||
@Main Handler mainHandler,
|
||||
DumpHandler dumpHandler,
|
||||
BroadcastDispatcher broadcastDispatcher,
|
||||
LogBufferEulogizer logBufferEulogizer,
|
||||
LogBufferFreezer logBufferFreezer,
|
||||
BatteryStateNotifier batteryStateNotifier) {
|
||||
BatteryStateNotifier batteryStateNotifier,
|
||||
UncaughtExceptionPreHandlerManager uncaughtExceptionPreHandlerManager) {
|
||||
super();
|
||||
mMainHandler = mainHandler;
|
||||
mDumpHandler = dumpHandler;
|
||||
mBroadcastDispatcher = broadcastDispatcher;
|
||||
mLogBufferEulogizer = logBufferEulogizer;
|
||||
mLogBufferFreezer = logBufferFreezer;
|
||||
mBatteryStateNotifier = batteryStateNotifier;
|
||||
mUncaughtExceptionPreHandlerManager = uncaughtExceptionPreHandlerManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -71,7 +80,13 @@ public class SystemUIService extends Service {
|
||||
|
||||
// Finish initializing dump logic
|
||||
mLogBufferFreezer.attach(mBroadcastDispatcher);
|
||||
mDumpHandler.init();
|
||||
|
||||
// Attempt to dump all LogBuffers for any uncaught exception
|
||||
mUncaughtExceptionPreHandlerManager.registerHandler((thread, throwable) -> {
|
||||
if (throwable instanceof Exception) {
|
||||
mLogBufferEulogizer.record(((Exception) throwable));
|
||||
}
|
||||
});
|
||||
|
||||
// If configured, set up a battery notification
|
||||
if (getResources().getBoolean(R.bool.config_showNotificationForUnknownBatteryState)) {
|
||||
|
||||
@@ -20,12 +20,16 @@ import android.content.Context
|
||||
import android.os.SystemClock
|
||||
import android.os.Trace
|
||||
import com.android.systemui.CoreStartable
|
||||
import com.android.systemui.ProtoDumpable
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL
|
||||
import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_NORMAL
|
||||
import com.android.systemui.dump.DumpsysEntry.DumpableEntry
|
||||
import com.android.systemui.dump.DumpsysEntry.LogBufferEntry
|
||||
import com.android.systemui.dump.DumpsysEntry.TableLogBufferEntry
|
||||
import com.android.systemui.dump.nano.SystemUIProtoDump
|
||||
import com.android.systemui.log.LogBuffer
|
||||
import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager
|
||||
import com.android.systemui.log.table.TableLogBuffer
|
||||
import com.google.protobuf.nano.MessageNano
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.FileDescriptor
|
||||
@@ -39,11 +43,11 @@ import javax.inject.Provider
|
||||
*
|
||||
* Dump output is split into two sections, CRITICAL and NORMAL. In general, the CRITICAL section
|
||||
* contains all dumpables that were registered to the [DumpManager], while the NORMAL sections
|
||||
* contains all [LogBuffer]s (due to their length).
|
||||
* contains all [LogBuffer]s and [TableLogBuffer]s (due to their length).
|
||||
*
|
||||
* The CRITICAL and NORMAL sections can be found within a bug report by searching for
|
||||
* "SERVICE com.android.systemui/.SystemUIService" and
|
||||
* "SERVICE com.android.systemui/.dump.SystemUIAuxiliaryDumpService", respectively.
|
||||
* The CRITICAL and NORMAL sections can be found within a bug report by searching for "SERVICE
|
||||
* com.android.systemui/.SystemUIService" and "SERVICE
|
||||
* com.android.systemui/.dump.SystemUIAuxiliaryDumpService", respectively.
|
||||
*
|
||||
* Finally, some or all of the dump can be triggered on-demand via adb (see below).
|
||||
*
|
||||
@@ -72,6 +76,7 @@ import javax.inject.Provider
|
||||
* # To dump all dumpables or all buffers:
|
||||
* $ <invocation> dumpables
|
||||
* $ <invocation> buffers
|
||||
* $ <invocation> tables
|
||||
*
|
||||
* # Finally, the following will simulate what we dump during the CRITICAL and NORMAL sections of a
|
||||
* # bug report:
|
||||
@@ -83,37 +88,26 @@ import javax.inject.Provider
|
||||
* $ <invocation> --help
|
||||
* ```
|
||||
*/
|
||||
class DumpHandler @Inject constructor(
|
||||
class DumpHandler
|
||||
@Inject
|
||||
constructor(
|
||||
private val context: Context,
|
||||
private val dumpManager: DumpManager,
|
||||
private val logBufferEulogizer: LogBufferEulogizer,
|
||||
private val startables: MutableMap<Class<*>, Provider<CoreStartable>>,
|
||||
private val uncaughtExceptionPreHandlerManager: UncaughtExceptionPreHandlerManager
|
||||
) {
|
||||
/**
|
||||
* Registers an uncaught exception handler
|
||||
*/
|
||||
fun init() {
|
||||
uncaughtExceptionPreHandlerManager.registerHandler { _, e ->
|
||||
if (e is Exception) {
|
||||
logBufferEulogizer.record(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the diagnostics! Behavior can be controlled via [args].
|
||||
*/
|
||||
/** Dump the diagnostics! Behavior can be controlled via [args]. */
|
||||
fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
|
||||
Trace.beginSection("DumpManager#dump()")
|
||||
val start = SystemClock.uptimeMillis()
|
||||
|
||||
val parsedArgs = try {
|
||||
parseArgs(args)
|
||||
} catch (e: ArgParseException) {
|
||||
pw.println(e.message)
|
||||
return
|
||||
}
|
||||
val parsedArgs =
|
||||
try {
|
||||
parseArgs(args)
|
||||
} catch (e: ArgParseException) {
|
||||
pw.println(e.message)
|
||||
return
|
||||
}
|
||||
|
||||
when {
|
||||
parsedArgs.dumpPriority == PRIORITY_ARG_CRITICAL -> dumpCritical(pw, parsedArgs)
|
||||
@@ -134,6 +128,7 @@ class DumpHandler @Inject constructor(
|
||||
"bugreport-normal" -> dumpNormal(pw, args)
|
||||
"dumpables" -> dumpDumpables(pw, args)
|
||||
"buffers" -> dumpBuffers(pw, args)
|
||||
"tables" -> dumpTables(pw, args)
|
||||
"config" -> dumpConfig(pw)
|
||||
"help" -> dumpHelp(pw)
|
||||
else -> {
|
||||
@@ -147,44 +142,65 @@ class DumpHandler @Inject constructor(
|
||||
}
|
||||
|
||||
private fun dumpCritical(pw: PrintWriter, args: ParsedArgs) {
|
||||
dumpManager.dumpCritical(pw, args.rawArgs)
|
||||
val targets = dumpManager.getDumpables()
|
||||
for (target in targets) {
|
||||
if (target.priority == DumpPriority.CRITICAL) {
|
||||
dumpDumpable(target, pw, args.rawArgs)
|
||||
}
|
||||
}
|
||||
dumpConfig(pw)
|
||||
}
|
||||
|
||||
private fun dumpNormal(pw: PrintWriter, args: ParsedArgs) {
|
||||
dumpManager.dumpNormal(pw, args.rawArgs, args.tailLength)
|
||||
val targets = dumpManager.getDumpables()
|
||||
for (target in targets) {
|
||||
if (target.priority == DumpPriority.NORMAL) {
|
||||
dumpDumpable(target, pw, args.rawArgs)
|
||||
}
|
||||
}
|
||||
|
||||
val buffers = dumpManager.getLogBuffers()
|
||||
for (buffer in buffers) {
|
||||
dumpBuffer(buffer, pw, args.tailLength)
|
||||
}
|
||||
|
||||
val tableBuffers = dumpManager.getTableLogBuffers()
|
||||
for (table in tableBuffers) {
|
||||
dumpTableBuffer(table, pw, args.rawArgs)
|
||||
}
|
||||
|
||||
logBufferEulogizer.readEulogyIfPresent(pw)
|
||||
}
|
||||
|
||||
private fun dumpDumpables(pw: PrintWriter, args: ParsedArgs) {
|
||||
if (args.listOnly) {
|
||||
dumpManager.listDumpables(pw)
|
||||
} else {
|
||||
dumpManager.dumpDumpables(pw, args.rawArgs)
|
||||
private fun dumpDumpables(pw: PrintWriter, args: ParsedArgs) =
|
||||
dumpManager.getDumpables().listOrDumpEntries(pw, args)
|
||||
|
||||
private fun dumpBuffers(pw: PrintWriter, args: ParsedArgs) =
|
||||
dumpManager.getLogBuffers().listOrDumpEntries(pw, args)
|
||||
|
||||
private fun dumpTables(pw: PrintWriter, args: ParsedArgs) =
|
||||
dumpManager.getTableLogBuffers().listOrDumpEntries(pw, args)
|
||||
|
||||
private fun listTargetNames(targets: Collection<DumpsysEntry>, pw: PrintWriter) {
|
||||
for (target in targets) {
|
||||
pw.println(target.name)
|
||||
}
|
||||
}
|
||||
|
||||
private fun dumpBuffers(pw: PrintWriter, args: ParsedArgs) {
|
||||
if (args.listOnly) {
|
||||
dumpManager.listBuffers(pw)
|
||||
} else {
|
||||
dumpManager.dumpBuffers(pw, args.tailLength)
|
||||
}
|
||||
}
|
||||
|
||||
private fun dumpProtoTargets(
|
||||
targets: List<String>,
|
||||
fd: FileDescriptor,
|
||||
args: ParsedArgs
|
||||
) {
|
||||
private fun dumpProtoTargets(targets: List<String>, fd: FileDescriptor, args: ParsedArgs) {
|
||||
val systemUIProto = SystemUIProtoDump()
|
||||
val dumpables = dumpManager.getDumpables()
|
||||
if (targets.isNotEmpty()) {
|
||||
for (target in targets) {
|
||||
dumpManager.dumpProtoTarget(target, systemUIProto, args.rawArgs)
|
||||
findBestProtoTargetMatch(dumpables, target)?.dumpProto(systemUIProto, args.rawArgs)
|
||||
}
|
||||
} else {
|
||||
dumpManager.dumpProtoDumpables(systemUIProto, args.rawArgs)
|
||||
// Dump all protos
|
||||
for (dumpable in dumpables) {
|
||||
(dumpable.dumpable as? ProtoDumpable)?.dumpProto(systemUIProto, args.rawArgs)
|
||||
}
|
||||
}
|
||||
|
||||
val buffer = BufferedOutputStream(FileOutputStream(fd))
|
||||
buffer.use {
|
||||
it.write(MessageNano.toByteArray(systemUIProto))
|
||||
@@ -192,36 +208,70 @@ class DumpHandler @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun dumpTargets(
|
||||
targets: List<String>,
|
||||
pw: PrintWriter,
|
||||
args: ParsedArgs
|
||||
) {
|
||||
// Attempts to dump the target list to the given PrintWriter. Since the arguments come in as
|
||||
// a list of strings, we use the [findBestTargetMatch] method to determine the most-correct
|
||||
// target with the given search string.
|
||||
private fun dumpTargets(targets: List<String>, pw: PrintWriter, args: ParsedArgs) {
|
||||
if (targets.isNotEmpty()) {
|
||||
for (target in targets) {
|
||||
dumpManager.dumpTarget(target, pw, args.rawArgs, args.tailLength)
|
||||
val dumpables = dumpManager.getDumpables()
|
||||
val buffers = dumpManager.getLogBuffers()
|
||||
val tableBuffers = dumpManager.getTableLogBuffers()
|
||||
|
||||
targets.forEach { target ->
|
||||
findTargetInCollection(target, dumpables, buffers, tableBuffers)?.dump(pw, args)
|
||||
}
|
||||
} else {
|
||||
if (args.listOnly) {
|
||||
val dumpables = dumpManager.getDumpables()
|
||||
val buffers = dumpManager.getLogBuffers()
|
||||
|
||||
pw.println("Dumpables:")
|
||||
dumpManager.listDumpables(pw)
|
||||
listTargetNames(dumpables, pw)
|
||||
pw.println()
|
||||
|
||||
pw.println("Buffers:")
|
||||
dumpManager.listBuffers(pw)
|
||||
listTargetNames(buffers, pw)
|
||||
} else {
|
||||
pw.println("Nothing to dump :(")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun findTargetInCollection(
|
||||
target: String,
|
||||
dumpables: Collection<DumpableEntry>,
|
||||
logBuffers: Collection<LogBufferEntry>,
|
||||
tableBuffers: Collection<TableLogBufferEntry>,
|
||||
) =
|
||||
sequence {
|
||||
findBestTargetMatch(dumpables, target)?.let { yield(it) }
|
||||
findBestTargetMatch(logBuffers, target)?.let { yield(it) }
|
||||
findBestTargetMatch(tableBuffers, target)?.let { yield(it) }
|
||||
}
|
||||
.sortedBy { it.name }
|
||||
.minByOrNull { it.name.length }
|
||||
|
||||
private fun dumpDumpable(entry: DumpableEntry, pw: PrintWriter, args: Array<String>) {
|
||||
pw.preamble(entry)
|
||||
entry.dumpable.dump(pw, args)
|
||||
}
|
||||
|
||||
private fun dumpBuffer(entry: LogBufferEntry, pw: PrintWriter, tailLength: Int) {
|
||||
pw.preamble(entry)
|
||||
entry.buffer.dump(pw, tailLength)
|
||||
}
|
||||
|
||||
private fun dumpTableBuffer(buffer: TableLogBufferEntry, pw: PrintWriter, args: Array<String>) {
|
||||
pw.preamble(buffer)
|
||||
buffer.table.dump(pw, args)
|
||||
}
|
||||
|
||||
private fun dumpConfig(pw: PrintWriter) {
|
||||
pw.println("SystemUiServiceComponents configuration:")
|
||||
pw.print("vendor component: ")
|
||||
pw.println(context.resources.getString(R.string.config_systemUIVendorServiceComponent))
|
||||
val services: MutableList<String> = startables.keys
|
||||
.map({ cls: Class<*> -> cls.simpleName })
|
||||
.toMutableList()
|
||||
val services: MutableList<String> =
|
||||
startables.keys.map({ cls: Class<*> -> cls.simpleName }).toMutableList()
|
||||
|
||||
services.add(context.resources.getString(R.string.config_systemUIVendorServiceComponent))
|
||||
dumpServiceList(pw, "global", services.toTypedArray())
|
||||
@@ -265,6 +315,7 @@ class DumpHandler @Inject constructor(
|
||||
pw.println("Special commands:")
|
||||
pw.println("$ <invocation> dumpables")
|
||||
pw.println("$ <invocation> buffers")
|
||||
pw.println("$ <invocation> tables")
|
||||
pw.println("$ <invocation> bugreport-critical")
|
||||
pw.println("$ <invocation> bugreport-normal")
|
||||
pw.println("$ <invocation> config")
|
||||
@@ -274,6 +325,7 @@ class DumpHandler @Inject constructor(
|
||||
pw.println("$ <invocation> --list")
|
||||
pw.println("$ <invocation> dumpables --list")
|
||||
pw.println("$ <invocation> buffers --list")
|
||||
pw.println("$ <invocation> tables --list")
|
||||
pw.println()
|
||||
|
||||
pw.println("Show only the most recent N lines of buffers")
|
||||
@@ -291,24 +343,26 @@ class DumpHandler @Inject constructor(
|
||||
iterator.remove()
|
||||
when (arg) {
|
||||
PRIORITY_ARG -> {
|
||||
pArgs.dumpPriority = readArgument(iterator, PRIORITY_ARG) {
|
||||
if (PRIORITY_OPTIONS.contains(it)) {
|
||||
it
|
||||
} else {
|
||||
throw IllegalArgumentException()
|
||||
pArgs.dumpPriority =
|
||||
readArgument(iterator, PRIORITY_ARG) {
|
||||
if (PRIORITY_OPTIONS.contains(it)) {
|
||||
it
|
||||
} else {
|
||||
throw IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PROTO -> pArgs.proto = true
|
||||
"-t", "--tail" -> {
|
||||
pArgs.tailLength = readArgument(iterator, arg) {
|
||||
it.toInt()
|
||||
}
|
||||
"-t",
|
||||
"--tail" -> {
|
||||
pArgs.tailLength = readArgument(iterator, arg) { it.toInt() }
|
||||
}
|
||||
"-l", "--list" -> {
|
||||
"-l",
|
||||
"--list" -> {
|
||||
pArgs.listOnly = true
|
||||
}
|
||||
"-h", "--help" -> {
|
||||
"-h",
|
||||
"--help" -> {
|
||||
pArgs.command = "help"
|
||||
}
|
||||
// This flag is passed as part of the proto dump in Bug reports, we can ignore
|
||||
@@ -345,29 +399,131 @@ class DumpHandler @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun DumpsysEntry.dump(pw: PrintWriter, args: ParsedArgs) =
|
||||
when (this) {
|
||||
is DumpableEntry -> dumpDumpable(this, pw, args.rawArgs)
|
||||
is LogBufferEntry -> dumpBuffer(this, pw, args.tailLength)
|
||||
is TableLogBufferEntry -> dumpTableBuffer(this, pw, args.rawArgs)
|
||||
}
|
||||
|
||||
private fun Collection<DumpsysEntry>.listOrDumpEntries(pw: PrintWriter, args: ParsedArgs) =
|
||||
if (args.listOnly) {
|
||||
listTargetNames(this, pw)
|
||||
} else {
|
||||
forEach { it.dump(pw, args) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PRIORITY_ARG = "--dump-priority"
|
||||
const val PRIORITY_ARG_CRITICAL = "CRITICAL"
|
||||
const val PRIORITY_ARG_NORMAL = "NORMAL"
|
||||
const val PROTO = "--proto"
|
||||
|
||||
/**
|
||||
* Important: do not change this divider without updating any bug report processing tools
|
||||
* (e.g. ABT), since this divider is used to determine boundaries for bug report views
|
||||
*/
|
||||
const val DUMPSYS_DUMPABLE_DIVIDER =
|
||||
"----------------------------------------------------------------------------"
|
||||
|
||||
/**
|
||||
* Important: do not change this divider without updating any bug report processing tools
|
||||
* (e.g. ABT), since this divider is used to determine boundaries for bug report views
|
||||
*/
|
||||
const val DUMPSYS_BUFFER_DIVIDER =
|
||||
"============================================================================"
|
||||
|
||||
private fun findBestTargetMatch(c: Collection<DumpsysEntry>, target: String) =
|
||||
c.asSequence().filter { it.name.endsWith(target) }.minByOrNull { it.name.length }
|
||||
|
||||
private fun findBestProtoTargetMatch(
|
||||
c: Collection<DumpableEntry>,
|
||||
target: String
|
||||
): ProtoDumpable? =
|
||||
c.asSequence()
|
||||
.filter { it.name.endsWith(target) }
|
||||
.filter { it.dumpable is ProtoDumpable }
|
||||
.minByOrNull { it.name.length }
|
||||
?.dumpable as? ProtoDumpable
|
||||
|
||||
private fun PrintWriter.preamble(entry: DumpsysEntry) =
|
||||
when (entry) {
|
||||
// Historically TableLogBuffer was not separate from dumpables, so they have the
|
||||
// same header
|
||||
is DumpableEntry,
|
||||
is TableLogBufferEntry -> {
|
||||
println()
|
||||
println(entry.name)
|
||||
println(DUMPSYS_DUMPABLE_DIVIDER)
|
||||
}
|
||||
is LogBufferEntry -> {
|
||||
println()
|
||||
println()
|
||||
println("BUFFER ${entry.name}:")
|
||||
println(DUMPSYS_BUFFER_DIVIDER)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zero-arg utility to write a [DumpableEntry] to the given [PrintWriter] in a
|
||||
* dumpsys-appropriate format.
|
||||
*/
|
||||
private fun dumpDumpable(entry: DumpableEntry, pw: PrintWriter) {
|
||||
pw.preamble(entry)
|
||||
entry.dumpable.dump(pw, arrayOf())
|
||||
}
|
||||
|
||||
/**
|
||||
* Zero-arg utility to write a [LogBufferEntry] to the given [PrintWriter] in a
|
||||
* dumpsys-appropriate format.
|
||||
*/
|
||||
private fun dumpBuffer(entry: LogBufferEntry, pw: PrintWriter) {
|
||||
pw.preamble(entry)
|
||||
entry.buffer.dump(pw, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Zero-arg utility to write a [TableLogBufferEntry] to the given [PrintWriter] in a
|
||||
* dumpsys-appropriate format.
|
||||
*/
|
||||
private fun dumpTableBuffer(entry: TableLogBufferEntry, pw: PrintWriter) {
|
||||
pw.preamble(entry)
|
||||
entry.table.dump(pw, arrayOf())
|
||||
}
|
||||
|
||||
/**
|
||||
* Zero-arg utility to write a [DumpsysEntry] to the given [PrintWriter] in a
|
||||
* dumpsys-appropriate format.
|
||||
*/
|
||||
fun DumpsysEntry.dump(pw: PrintWriter) {
|
||||
when (this) {
|
||||
is DumpableEntry -> dumpDumpable(this, pw)
|
||||
is LogBufferEntry -> dumpBuffer(this, pw)
|
||||
is TableLogBufferEntry -> dumpTableBuffer(this, pw)
|
||||
}
|
||||
}
|
||||
|
||||
/** Format [entries] in a dumpsys-appropriate way, using [pw] */
|
||||
fun dumpEntries(entries: Collection<DumpsysEntry>, pw: PrintWriter) {
|
||||
entries.forEach { it.dump(pw) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val PRIORITY_OPTIONS = arrayOf(PRIORITY_ARG_CRITICAL, PRIORITY_ARG_NORMAL)
|
||||
|
||||
private val COMMANDS = arrayOf(
|
||||
private val COMMANDS =
|
||||
arrayOf(
|
||||
"bugreport-critical",
|
||||
"bugreport-normal",
|
||||
"buffers",
|
||||
"dumpables",
|
||||
"tables",
|
||||
"config",
|
||||
"help"
|
||||
)
|
||||
)
|
||||
|
||||
private class ParsedArgs(
|
||||
val rawArgs: Array<String>,
|
||||
val nonFlagArgs: List<String>
|
||||
) {
|
||||
private class ParsedArgs(val rawArgs: Array<String>, val nonFlagArgs: List<String>) {
|
||||
var dumpPriority: String? = null
|
||||
var tailLength: Int = 0
|
||||
var command: String? = null
|
||||
|
||||
@@ -18,9 +18,11 @@ package com.android.systemui.dump
|
||||
|
||||
import com.android.systemui.Dumpable
|
||||
import com.android.systemui.ProtoDumpable
|
||||
import com.android.systemui.dump.nano.SystemUIProtoDump
|
||||
import com.android.systemui.dump.DumpsysEntry.DumpableEntry
|
||||
import com.android.systemui.dump.DumpsysEntry.LogBufferEntry
|
||||
import com.android.systemui.dump.DumpsysEntry.TableLogBufferEntry
|
||||
import com.android.systemui.log.LogBuffer
|
||||
import java.io.PrintWriter
|
||||
import com.android.systemui.log.table.TableLogBuffer
|
||||
import java.util.TreeMap
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@@ -37,8 +39,9 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
open class DumpManager @Inject constructor() {
|
||||
// NOTE: Using TreeMap ensures that iteration is in a predictable & alphabetical order.
|
||||
private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = TreeMap()
|
||||
private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = TreeMap()
|
||||
private val dumpables: MutableMap<String, DumpableEntry> = TreeMap()
|
||||
private val buffers: MutableMap<String, LogBufferEntry> = TreeMap()
|
||||
private val tableLogBuffers: MutableMap<String, TableLogBufferEntry> = TreeMap()
|
||||
|
||||
/** See [registerCriticalDumpable]. */
|
||||
fun registerCriticalDumpable(module: Dumpable) {
|
||||
@@ -77,14 +80,14 @@ open class DumpManager @Inject constructor() {
|
||||
* Register a dumpable to be called during a bug report.
|
||||
*
|
||||
* @param name The name to register the dumpable under. This is typically the qualified class
|
||||
* name of the thing being dumped (getClass().getName()), but can be anything as long as it
|
||||
* doesn't clash with an existing registration.
|
||||
* name of the thing being dumped (getClass().getName()), but can be anything as long as it
|
||||
* doesn't clash with an existing registration.
|
||||
* @param priority the priority level of this dumpable, which affects at what point in the bug
|
||||
* report this gets dump. By default, the dumpable will be called during the CRITICAL section of
|
||||
* the bug report, so don't dump an excessive amount of stuff here.
|
||||
* report this gets dump. By default, the dumpable will be called during the CRITICAL section
|
||||
* of the bug report, so don't dump an excessive amount of stuff here.
|
||||
*
|
||||
* TODO(b/259973758): Replace all calls to this method with calls to [registerCriticalDumpable]
|
||||
* or [registerNormalDumpable] instead.
|
||||
* or [registerNormalDumpable] instead.
|
||||
*/
|
||||
@Synchronized
|
||||
@JvmOverloads
|
||||
@@ -98,7 +101,7 @@ open class DumpManager @Inject constructor() {
|
||||
throw IllegalArgumentException("'$name' is already registered")
|
||||
}
|
||||
|
||||
dumpables[name] = RegisteredDumpable(name, module, priority)
|
||||
dumpables[name] = DumpableEntry(module, name, priority)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,217 +113,62 @@ open class DumpManager @Inject constructor() {
|
||||
registerDumpable(module::class.java.simpleName, module)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a previously-registered dumpable.
|
||||
*/
|
||||
/** Unregisters a previously-registered dumpable. */
|
||||
@Synchronized
|
||||
fun unregisterDumpable(name: String) {
|
||||
dumpables.remove(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a [LogBuffer] to be dumped during a bug report.
|
||||
*/
|
||||
/** Register a [LogBuffer] to be dumped during a bug report. */
|
||||
@Synchronized
|
||||
fun registerBuffer(name: String, buffer: LogBuffer) {
|
||||
if (!canAssignToNameLocked(name, buffer)) {
|
||||
throw IllegalArgumentException("'$name' is already registered")
|
||||
}
|
||||
|
||||
buffers[name] = LogBufferEntry(buffer, name)
|
||||
}
|
||||
|
||||
/** Register a [TableLogBuffer] to be dumped during a bugreport */
|
||||
@Synchronized
|
||||
fun registerTableLogBuffer(name: String, buffer: TableLogBuffer) {
|
||||
if (!canAssignToNameLocked(name, buffer)) {
|
||||
throw IllegalArgumentException("'$name' is already registered")
|
||||
}
|
||||
|
||||
// All buffers must be priority NORMAL, not CRITICAL, because they often contain a lot of
|
||||
// data.
|
||||
buffers[name] = RegisteredDumpable(name, buffer, DumpPriority.NORMAL)
|
||||
tableLogBuffers[name] = TableLogBufferEntry(buffer, name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the alphabetically first, shortest-named dumpable or buffer whose registered name ends
|
||||
* with [target].
|
||||
*/
|
||||
@Synchronized
|
||||
fun dumpTarget(
|
||||
target: String,
|
||||
pw: PrintWriter,
|
||||
args: Array<String>,
|
||||
tailLength: Int,
|
||||
) {
|
||||
sequence {
|
||||
findBestTargetMatch(dumpables, target)?.let {
|
||||
yield(it.name to { dumpDumpable(it, pw, args) })
|
||||
}
|
||||
findBestTargetMatch(buffers, target)?.let {
|
||||
yield(it.name to { dumpBuffer(it, pw, tailLength) })
|
||||
}
|
||||
}.sortedBy { it.first }.minByOrNull { it.first.length }?.second?.invoke()
|
||||
}
|
||||
@Synchronized fun getDumpables(): Collection<DumpableEntry> = dumpables.values.toList()
|
||||
|
||||
@Synchronized fun getLogBuffers(): Collection<LogBufferEntry> = buffers.values.toList()
|
||||
|
||||
@Synchronized
|
||||
fun dumpProtoTarget(
|
||||
target: String,
|
||||
protoDump: SystemUIProtoDump,
|
||||
args: Array<String>
|
||||
) {
|
||||
findBestProtoTargetMatch(dumpables, target)?.let {
|
||||
dumpProtoDumpable(it, protoDump, args)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun dumpProtoDumpables(
|
||||
systemUIProtoDump: SystemUIProtoDump,
|
||||
args: Array<String>
|
||||
) {
|
||||
for (dumpable in dumpables.values) {
|
||||
if (dumpable.dumpable is ProtoDumpable) {
|
||||
dumpProtoDumpable(
|
||||
dumpable.dumpable,
|
||||
systemUIProtoDump,
|
||||
args
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps all registered dumpables with critical priority to [pw]
|
||||
*/
|
||||
@Synchronized
|
||||
fun dumpCritical(pw: PrintWriter, args: Array<String>) {
|
||||
for (dumpable in dumpables.values) {
|
||||
if (dumpable.priority == DumpPriority.CRITICAL) {
|
||||
dumpDumpable(dumpable, pw, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To [pw], dumps (1) all registered dumpables with normal priority; and (2) all [LogBuffer]s.
|
||||
*/
|
||||
@Synchronized
|
||||
fun dumpNormal(pw: PrintWriter, args: Array<String>, tailLength: Int = 0) {
|
||||
for (dumpable in dumpables.values) {
|
||||
if (dumpable.priority == DumpPriority.NORMAL) {
|
||||
dumpDumpable(dumpable, pw, args)
|
||||
}
|
||||
}
|
||||
|
||||
for (buffer in buffers.values) {
|
||||
dumpBuffer(buffer, pw, tailLength)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump all the instances of [Dumpable].
|
||||
*/
|
||||
@Synchronized
|
||||
fun dumpDumpables(pw: PrintWriter, args: Array<String>) {
|
||||
for (module in dumpables.values) {
|
||||
dumpDumpable(module, pw, args)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the names of all registered dumpables (one per line)
|
||||
*/
|
||||
@Synchronized
|
||||
fun listDumpables(pw: PrintWriter) {
|
||||
for (module in dumpables.values) {
|
||||
pw.println(module.name)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps all registered [LogBuffer]s to [pw]
|
||||
*/
|
||||
@Synchronized
|
||||
fun dumpBuffers(pw: PrintWriter, tailLength: Int) {
|
||||
for (buffer in buffers.values) {
|
||||
dumpBuffer(buffer, pw, tailLength)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the names of all registered buffers (one per line)
|
||||
*/
|
||||
@Synchronized
|
||||
fun listBuffers(pw: PrintWriter) {
|
||||
for (buffer in buffers.values) {
|
||||
pw.println(buffer.name)
|
||||
}
|
||||
}
|
||||
fun getTableLogBuffers(): Collection<TableLogBufferEntry> = tableLogBuffers.values.toList()
|
||||
|
||||
@Synchronized
|
||||
fun freezeBuffers() {
|
||||
for (buffer in buffers.values) {
|
||||
buffer.dumpable.freeze()
|
||||
buffer.buffer.freeze()
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun unfreezeBuffers() {
|
||||
for (buffer in buffers.values) {
|
||||
buffer.dumpable.unfreeze()
|
||||
buffer.buffer.unfreeze()
|
||||
}
|
||||
}
|
||||
|
||||
private fun dumpDumpable(
|
||||
dumpable: RegisteredDumpable<Dumpable>,
|
||||
pw: PrintWriter,
|
||||
args: Array<String>
|
||||
) {
|
||||
pw.println()
|
||||
pw.println("${dumpable.name}:")
|
||||
pw.println("----------------------------------------------------------------------------")
|
||||
dumpable.dumpable.dump(pw, args)
|
||||
}
|
||||
|
||||
private fun dumpBuffer(
|
||||
buffer: RegisteredDumpable<LogBuffer>,
|
||||
pw: PrintWriter,
|
||||
tailLength: Int
|
||||
) {
|
||||
pw.println()
|
||||
pw.println()
|
||||
pw.println("BUFFER ${buffer.name}:")
|
||||
pw.println("============================================================================")
|
||||
buffer.dumpable.dump(pw, tailLength)
|
||||
}
|
||||
|
||||
private fun dumpProtoDumpable(
|
||||
protoDumpable: ProtoDumpable,
|
||||
systemUIProtoDump: SystemUIProtoDump,
|
||||
args: Array<String>
|
||||
) {
|
||||
protoDumpable.dumpProto(systemUIProtoDump, args)
|
||||
}
|
||||
|
||||
private fun canAssignToNameLocked(name: String, newDumpable: Any): Boolean {
|
||||
val existingDumpable = dumpables[name]?.dumpable ?: buffers[name]?.dumpable
|
||||
val existingDumpable =
|
||||
dumpables[name]?.dumpable ?: buffers[name]?.buffer ?: tableLogBuffers[name]?.table
|
||||
return existingDumpable == null || newDumpable == existingDumpable
|
||||
}
|
||||
|
||||
private fun <V : Any> findBestTargetMatch(map: Map<String, V>, target: String): V? = map
|
||||
.asSequence()
|
||||
.filter { it.key.endsWith(target) }
|
||||
.minByOrNull { it.key.length }
|
||||
?.value
|
||||
|
||||
private fun findBestProtoTargetMatch(
|
||||
map: Map<String, RegisteredDumpable<Dumpable>>,
|
||||
target: String
|
||||
): ProtoDumpable? = map
|
||||
.asSequence()
|
||||
.filter { it.key.endsWith(target) }
|
||||
.filter { it.value.dumpable is ProtoDumpable }
|
||||
.minByOrNull { it.key.length }
|
||||
?.value?.dumpable as? ProtoDumpable
|
||||
}
|
||||
|
||||
private data class RegisteredDumpable<T>(
|
||||
val name: String,
|
||||
val dumpable: T,
|
||||
val priority: DumpPriority,
|
||||
)
|
||||
|
||||
/**
|
||||
* The priority level for a given dumpable, which affects at what point in the bug report this gets
|
||||
* dumped.
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.dump
|
||||
|
||||
import com.android.systemui.Dumpable
|
||||
import com.android.systemui.log.LogBuffer
|
||||
import com.android.systemui.log.table.TableLogBuffer
|
||||
|
||||
/**
|
||||
* A DumpsysEntry is a named, registered entry tracked by [DumpManager] which can be addressed and
|
||||
* used both in a bugreport / dumpsys invocation or in an individual CLI implementation.
|
||||
*
|
||||
* The idea here is that we define every type that [DumpManager] knows about and defines the minimum
|
||||
* shared interface between each type. So far, just [name] and [priority]. This way, [DumpManager]
|
||||
* can just store them in separate maps and do the minimal amount of work to discriminate between
|
||||
* them.
|
||||
*
|
||||
* Individual consumers can request these participants in a list via the relevant get* methods on
|
||||
* [DumpManager]
|
||||
*/
|
||||
sealed interface DumpsysEntry {
|
||||
val name: String
|
||||
val priority: DumpPriority
|
||||
|
||||
data class DumpableEntry(
|
||||
val dumpable: Dumpable,
|
||||
override val name: String,
|
||||
override val priority: DumpPriority,
|
||||
) : DumpsysEntry
|
||||
|
||||
data class LogBufferEntry(
|
||||
val buffer: LogBuffer,
|
||||
override val name: String,
|
||||
) : DumpsysEntry {
|
||||
// All buffers must be priority NORMAL, not CRITICAL, because they often contain a lot of
|
||||
// data.
|
||||
override val priority: DumpPriority = DumpPriority.NORMAL
|
||||
}
|
||||
|
||||
data class TableLogBufferEntry(
|
||||
val table: TableLogBuffer,
|
||||
override val name: String,
|
||||
) : DumpsysEntry {
|
||||
// All buffers must be priority NORMAL, not CRITICAL, because they often contain a lot of
|
||||
// data.
|
||||
override val priority: DumpPriority = DumpPriority.NORMAL
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import android.content.Context
|
||||
import android.icu.text.SimpleDateFormat
|
||||
import android.util.Log
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dump.DumpHandler.Companion.dumpEntries
|
||||
import com.android.systemui.log.LogBuffer
|
||||
import com.android.systemui.util.io.Files
|
||||
import com.android.systemui.util.time.SystemClock
|
||||
@@ -48,20 +49,21 @@ class LogBufferEulogizer(
|
||||
private val files: Files,
|
||||
private val logPath: Path,
|
||||
private val minWriteGap: Long,
|
||||
private val maxLogAgeToDump: Long
|
||||
private val maxLogAgeToDump: Long,
|
||||
) {
|
||||
@Inject constructor(
|
||||
@Inject
|
||||
constructor(
|
||||
context: Context,
|
||||
dumpManager: DumpManager,
|
||||
systemClock: SystemClock,
|
||||
files: Files
|
||||
files: Files,
|
||||
) : this(
|
||||
dumpManager,
|
||||
systemClock,
|
||||
files,
|
||||
Paths.get(context.filesDir.toPath().toString(), "log_buffers.txt"),
|
||||
MIN_WRITE_GAP,
|
||||
MAX_AGE_TO_DUMP
|
||||
MAX_AGE_TO_DUMP,
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -91,7 +93,8 @@ class LogBufferEulogizer(
|
||||
pw.println()
|
||||
pw.println("Dump triggered by exception:")
|
||||
reason.printStackTrace(pw)
|
||||
dumpManager.dumpBuffers(pw, 0)
|
||||
val buffers = dumpManager.getLogBuffers()
|
||||
dumpEntries(buffers, pw)
|
||||
duration = systemClock.uptimeMillis() - start
|
||||
pw.println()
|
||||
pw.println("Buffer eulogy took ${duration}ms")
|
||||
@@ -105,16 +108,17 @@ class LogBufferEulogizer(
|
||||
return reason
|
||||
}
|
||||
|
||||
/**
|
||||
* If a eulogy file is present, writes its contents to [pw].
|
||||
*/
|
||||
/** If a eulogy file is present, writes its contents to [pw]. */
|
||||
fun readEulogyIfPresent(pw: PrintWriter) {
|
||||
try {
|
||||
val millisSinceLastWrite = getMillisSinceLastWrite(logPath)
|
||||
if (millisSinceLastWrite > maxLogAgeToDump) {
|
||||
Log.i(TAG, "Not eulogizing buffers; they are " +
|
||||
Log.i(
|
||||
TAG,
|
||||
"Not eulogizing buffers; they are " +
|
||||
TimeUnit.HOURS.convert(millisSinceLastWrite, TimeUnit.MILLISECONDS) +
|
||||
" hours old")
|
||||
" hours old"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -122,9 +126,7 @@ class LogBufferEulogizer(
|
||||
pw.println()
|
||||
pw.println()
|
||||
pw.println("=============== BUFFERS FROM MOST RECENT CRASH ===============")
|
||||
s.forEach { line ->
|
||||
pw.println(line)
|
||||
}
|
||||
s.forEach { line -> pw.println(line) }
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
// File doesn't exist, okay
|
||||
@@ -134,12 +136,13 @@ class LogBufferEulogizer(
|
||||
}
|
||||
|
||||
private fun getMillisSinceLastWrite(path: Path): Long {
|
||||
val stats = try {
|
||||
files.readAttributes(path, BasicFileAttributes::class.java)
|
||||
} catch (e: IOException) {
|
||||
// File doesn't exist
|
||||
null
|
||||
}
|
||||
val stats =
|
||||
try {
|
||||
files.readAttributes(path, BasicFileAttributes::class.java)
|
||||
} catch (e: IOException) {
|
||||
// File doesn't exist
|
||||
null
|
||||
}
|
||||
return systemClock.currentTimeMillis() - (stats?.lastModifiedTime()?.toMillis() ?: 0)
|
||||
}
|
||||
}
|
||||
@@ -147,4 +150,4 @@ class LogBufferEulogizer(
|
||||
private const val TAG = "BufferEulogizer"
|
||||
private val MIN_WRITE_GAP = TimeUnit.MINUTES.toMillis(5)
|
||||
private val MAX_AGE_TO_DUMP = TimeUnit.HOURS.toMillis(48)
|
||||
private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
|
||||
private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
|
||||
|
||||
@@ -61,7 +61,7 @@ constructor(
|
||||
bgDispatcher,
|
||||
coroutineScope,
|
||||
)
|
||||
dumpManager.registerNormalDumpable(name, tableBuffer)
|
||||
dumpManager.registerTableLogBuffer(name, tableBuffer)
|
||||
tableBuffer.init()
|
||||
return tableBuffer
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import com.android.systemui.Dumpable
|
||||
import com.android.systemui.ProtoDumpable
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.log.LogBuffer
|
||||
import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager
|
||||
import com.android.systemui.log.table.TableLogBuffer
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.eq
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
@@ -45,8 +45,6 @@ class DumpHandlerTest : SysuiTestCase() {
|
||||
|
||||
@Mock
|
||||
private lateinit var logBufferEulogizer: LogBufferEulogizer
|
||||
@Mock
|
||||
private lateinit var exceptionHandlerManager: UncaughtExceptionPreHandlerManager
|
||||
|
||||
@Mock
|
||||
private lateinit var pw: PrintWriter
|
||||
@@ -70,6 +68,11 @@ class DumpHandlerTest : SysuiTestCase() {
|
||||
@Mock
|
||||
private lateinit var buffer2: LogBuffer
|
||||
|
||||
@Mock
|
||||
private lateinit var table1: TableLogBuffer
|
||||
@Mock
|
||||
private lateinit var table2: TableLogBuffer
|
||||
|
||||
private val dumpManager = DumpManager()
|
||||
|
||||
@Before
|
||||
@@ -83,21 +86,22 @@ class DumpHandlerTest : SysuiTestCase() {
|
||||
mutableMapOf(
|
||||
EmptyCoreStartable::class.java to Provider { EmptyCoreStartable() }
|
||||
),
|
||||
exceptionHandlerManager
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpablesCanBeDumpedSelectively() {
|
||||
// GIVEN a variety of registered dumpables and buffers
|
||||
dumpManager.registerDumpable("dumpable1", dumpable1)
|
||||
dumpManager.registerDumpable("dumpable2", dumpable2)
|
||||
dumpManager.registerDumpable("dumpable3", dumpable3)
|
||||
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
|
||||
dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
|
||||
dumpManager.registerBuffer("buffer1", buffer1)
|
||||
dumpManager.registerBuffer("buffer2", buffer2)
|
||||
dumpManager.registerTableLogBuffer("table1", table1)
|
||||
dumpManager.registerTableLogBuffer("table2", table2)
|
||||
|
||||
// WHEN some of them are dumped explicitly
|
||||
val args = arrayOf("dumpable1", "dumpable3", "buffer2")
|
||||
val args = arrayOf("dumpable1", "dumpable3", "buffer2", "table2")
|
||||
dumpHandler.dump(fd, pw, args)
|
||||
|
||||
// THEN only the requested ones have their dump() method called
|
||||
@@ -108,12 +112,14 @@ class DumpHandlerTest : SysuiTestCase() {
|
||||
verify(dumpable3).dump(pw, args)
|
||||
verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
|
||||
verify(buffer2).dump(pw, 0)
|
||||
verify(table1, never()).dump(any(), any())
|
||||
verify(table2).dump(pw, args)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpableMatchingIsBasedOnEndOfTag() {
|
||||
// GIVEN a dumpable registered to the manager
|
||||
dumpManager.registerDumpable("com.android.foo.bar.dumpable1", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("com.android.foo.bar.dumpable1", dumpable1)
|
||||
|
||||
// WHEN that module is dumped
|
||||
val args = arrayOf("dumpable1")
|
||||
@@ -131,6 +137,8 @@ class DumpHandlerTest : SysuiTestCase() {
|
||||
dumpManager.registerNormalDumpable("dumpable3", dumpable3)
|
||||
dumpManager.registerBuffer("buffer1", buffer1)
|
||||
dumpManager.registerBuffer("buffer2", buffer2)
|
||||
dumpManager.registerTableLogBuffer("table1", table1)
|
||||
dumpManager.registerTableLogBuffer("table2", table2)
|
||||
|
||||
// WHEN a critical dump is requested
|
||||
val args = arrayOf("--dump-priority", "CRITICAL")
|
||||
@@ -144,6 +152,8 @@ class DumpHandlerTest : SysuiTestCase() {
|
||||
any(Array<String>::class.java))
|
||||
verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
|
||||
verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
|
||||
verify(table1, never()).dump(any(), any())
|
||||
verify(table2, never()).dump(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -154,6 +164,8 @@ class DumpHandlerTest : SysuiTestCase() {
|
||||
dumpManager.registerNormalDumpable("dumpable3", dumpable3)
|
||||
dumpManager.registerBuffer("buffer1", buffer1)
|
||||
dumpManager.registerBuffer("buffer2", buffer2)
|
||||
dumpManager.registerTableLogBuffer("table1", table1)
|
||||
dumpManager.registerTableLogBuffer("table2", table2)
|
||||
|
||||
// WHEN a normal dump is requested
|
||||
val args = arrayOf("--dump-priority", "NORMAL")
|
||||
@@ -169,6 +181,8 @@ class DumpHandlerTest : SysuiTestCase() {
|
||||
verify(dumpable3).dump(pw, args)
|
||||
verify(buffer1).dump(pw, 0)
|
||||
verify(buffer2).dump(pw, 0)
|
||||
verify(table1).dump(pw, args)
|
||||
verify(table2).dump(pw, args)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -183,6 +197,81 @@ class DumpHandlerTest : SysuiTestCase() {
|
||||
assertThat(stringWriter.toString()).contains(EmptyCoreStartable::class.java.simpleName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpBuffers() {
|
||||
// GIVEN a variety of registered dumpables and buffers and tables
|
||||
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
|
||||
dumpManager.registerNormalDumpable("dumpable3", dumpable3)
|
||||
dumpManager.registerBuffer("buffer1", buffer1)
|
||||
dumpManager.registerBuffer("buffer2", buffer2)
|
||||
dumpManager.registerTableLogBuffer("table1", table1)
|
||||
dumpManager.registerTableLogBuffer("table2", table2)
|
||||
|
||||
// WHEN a buffer dump is requested
|
||||
val args = arrayOf("buffers", "--tail", "1")
|
||||
dumpHandler.dump(fd, pw, args)
|
||||
|
||||
// THEN all buffers are dumped (and no dumpables or tables)
|
||||
verify(dumpable1, never()).dump(any(), any())
|
||||
verify(dumpable2, never()).dump(any(), any())
|
||||
verify(dumpable3, never()).dump(any(), any())
|
||||
verify(buffer1).dump(pw, tailLength = 1)
|
||||
verify(buffer2).dump(pw, tailLength = 1)
|
||||
verify(table1, never()).dump(any(), any())
|
||||
verify(table2, never()).dump(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpDumpables() {
|
||||
// GIVEN a variety of registered dumpables and buffers and tables
|
||||
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
|
||||
dumpManager.registerNormalDumpable("dumpable3", dumpable3)
|
||||
dumpManager.registerBuffer("buffer1", buffer1)
|
||||
dumpManager.registerBuffer("buffer2", buffer2)
|
||||
dumpManager.registerTableLogBuffer("table1", table1)
|
||||
dumpManager.registerTableLogBuffer("table2", table2)
|
||||
|
||||
// WHEN a dumpable dump is requested
|
||||
val args = arrayOf("dumpables")
|
||||
dumpHandler.dump(fd, pw, args)
|
||||
|
||||
// THEN all dumpables are dumped (both critical and normal) (and no dumpables)
|
||||
verify(dumpable1).dump(pw, args)
|
||||
verify(dumpable2).dump(pw, args)
|
||||
verify(dumpable3).dump(pw, args)
|
||||
verify(buffer1, never()).dump(any(), anyInt())
|
||||
verify(buffer2, never()).dump(any(), anyInt())
|
||||
verify(table1, never()).dump(any(), any())
|
||||
verify(table2, never()).dump(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpTables() {
|
||||
// GIVEN a variety of registered dumpables and buffers and tables
|
||||
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
|
||||
dumpManager.registerNormalDumpable("dumpable3", dumpable3)
|
||||
dumpManager.registerBuffer("buffer1", buffer1)
|
||||
dumpManager.registerBuffer("buffer2", buffer2)
|
||||
dumpManager.registerTableLogBuffer("table1", table1)
|
||||
dumpManager.registerTableLogBuffer("table2", table2)
|
||||
|
||||
// WHEN a dumpable dump is requested
|
||||
val args = arrayOf("tables")
|
||||
dumpHandler.dump(fd, pw, args)
|
||||
|
||||
// THEN all dumpables are dumped (both critical and normal) (and no dumpables)
|
||||
verify(dumpable1, never()).dump(any(), any())
|
||||
verify(dumpable2, never()).dump(any(), any())
|
||||
verify(dumpable3, never()).dump(any(), any())
|
||||
verify(buffer1, never()).dump(any(), anyInt())
|
||||
verify(buffer2, never()).dump(any(), anyInt())
|
||||
verify(table1).dump(pw, args)
|
||||
verify(table2).dump(pw, args)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpAllProtoDumpables() {
|
||||
dumpManager.registerDumpable("protoDumpable1", protoDumpable1)
|
||||
@@ -207,6 +296,123 @@ class DumpHandlerTest : SysuiTestCase() {
|
||||
verify(protoDumpable2, never()).dumpProto(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpTarget_selectsShortestNamedDumpable() {
|
||||
// GIVEN a variety of registered dumpables and buffers
|
||||
dumpManager.registerCriticalDumpable("first-dumpable", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("scnd-dumpable", dumpable2)
|
||||
dumpManager.registerCriticalDumpable("third-dumpable", dumpable3)
|
||||
|
||||
// WHEN a dumpable is dumped by a suffix that matches multiple options
|
||||
val args = arrayOf("dumpable")
|
||||
dumpHandler.dump(fd, pw, args)
|
||||
|
||||
// THEN the matching dumpable with the shorter name is dumped
|
||||
verify(dumpable1, never()).dump(any(), any())
|
||||
verify(dumpable2).dump(pw, args)
|
||||
verify(dumpable3, never()).dump(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpTarget_selectsShortestNamedBuffer() {
|
||||
// GIVEN a variety of registered dumpables and buffers
|
||||
dumpManager.registerBuffer("first-buffer", buffer1)
|
||||
dumpManager.registerBuffer("scnd-buffer", buffer2)
|
||||
|
||||
// WHEN a dumpable is dumped by a suffix that matches multiple options
|
||||
val args = arrayOf("buffer", "--tail", "14")
|
||||
dumpHandler.dump(fd, pw, args)
|
||||
|
||||
// THEN the matching buffer with the shorter name is dumped
|
||||
verify(buffer1, never()).dump(any(), anyInt())
|
||||
verify(buffer2).dump(pw, tailLength = 14)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpTarget_selectsShortestNamedMatch_dumpable() {
|
||||
// GIVEN a variety of registered dumpables and buffers
|
||||
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
|
||||
dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
|
||||
dumpManager.registerBuffer("big-buffer1", buffer1)
|
||||
dumpManager.registerBuffer("big-buffer2", buffer2)
|
||||
|
||||
// WHEN a dumpable is dumped by a suffix that matches multiple options
|
||||
val args = arrayOf("2")
|
||||
dumpHandler.dump(fd, pw, args)
|
||||
|
||||
// THEN the matching dumpable with the shorter name is dumped
|
||||
verify(dumpable1, never()).dump(any(), any())
|
||||
verify(dumpable2).dump(pw, args)
|
||||
verify(dumpable3, never()).dump(any(), any())
|
||||
verify(buffer1, never()).dump(any(), anyInt())
|
||||
verify(buffer2, never()).dump(any(), anyInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpTarget_selectsShortestNamedMatch_buffer() {
|
||||
// GIVEN a variety of registered dumpables and buffers
|
||||
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
|
||||
dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
|
||||
dumpManager.registerBuffer("buffer1", buffer1)
|
||||
dumpManager.registerBuffer("buffer2", buffer2)
|
||||
|
||||
// WHEN a dumpable is dumped by a suffix that matches multiple options
|
||||
val args = arrayOf("2", "--tail", "14")
|
||||
dumpHandler.dump(fd, pw, args)
|
||||
|
||||
// THEN the matching buffer with the shorter name is dumped
|
||||
verify(dumpable1, never()).dump(any(), any())
|
||||
verify(dumpable2, never()).dump(any(), any())
|
||||
verify(dumpable3, never()).dump(any(), any())
|
||||
verify(buffer1, never()).dump(any(), anyInt())
|
||||
verify(buffer2).dump(pw, tailLength = 14)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_dumpable() {
|
||||
// GIVEN a variety of registered dumpables and buffers
|
||||
dumpManager.registerCriticalDumpable("d1x", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("d2x", dumpable2)
|
||||
dumpManager.registerCriticalDumpable("a3x", dumpable3)
|
||||
dumpManager.registerBuffer("ab1x", buffer1)
|
||||
dumpManager.registerBuffer("b2x", buffer2)
|
||||
|
||||
// WHEN a dumpable is dumped by a suffix that matches multiple options
|
||||
val args = arrayOf("x")
|
||||
dumpHandler.dump(fd, pw, args)
|
||||
|
||||
// THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
|
||||
verify(dumpable1, never()).dump(any(), any())
|
||||
verify(dumpable2, never()).dump(any(), any())
|
||||
verify(dumpable3).dump(pw, args)
|
||||
verify(buffer1, never()).dump(any(), anyInt())
|
||||
verify(buffer2, never()).dump(any(), anyInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_buffer() {
|
||||
// GIVEN a variety of registered dumpables and buffers
|
||||
dumpManager.registerCriticalDumpable("d1x", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("d2x", dumpable2)
|
||||
dumpManager.registerCriticalDumpable("az1x", dumpable3)
|
||||
dumpManager.registerBuffer("b1x", buffer1)
|
||||
dumpManager.registerBuffer("b2x", buffer2)
|
||||
|
||||
// WHEN a dumpable is dumped by a suffix that matches multiple options
|
||||
val args = arrayOf("x", "--tail", "14")
|
||||
dumpHandler.dump(fd, pw, args)
|
||||
|
||||
// THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
|
||||
verify(dumpable1, never()).dump(any(), any())
|
||||
verify(dumpable2, never()).dump(any(), any())
|
||||
verify(dumpable3, never()).dump(any(), any())
|
||||
verify(buffer1).dump(pw, tailLength = 14)
|
||||
verify(buffer2, never()).dump(any(), anyInt())
|
||||
}
|
||||
|
||||
|
||||
private class EmptyCoreStartable : CoreStartable {
|
||||
override fun start() {}
|
||||
}
|
||||
|
||||
@@ -20,21 +20,17 @@ import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.Dumpable
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.log.LogBuffer
|
||||
import com.android.systemui.util.mockito.any
|
||||
import java.io.PrintWriter
|
||||
import com.android.systemui.log.table.TableLogBuffer
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.anyInt
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
class DumpManagerTest : SysuiTestCase() {
|
||||
|
||||
@Mock private lateinit var pw: PrintWriter
|
||||
|
||||
@Mock private lateinit var dumpable1: Dumpable
|
||||
@Mock private lateinit var dumpable2: Dumpable
|
||||
@Mock private lateinit var dumpable3: Dumpable
|
||||
@@ -42,6 +38,9 @@ class DumpManagerTest : SysuiTestCase() {
|
||||
@Mock private lateinit var buffer1: LogBuffer
|
||||
@Mock private lateinit var buffer2: LogBuffer
|
||||
|
||||
@Mock private lateinit var table1: TableLogBuffer
|
||||
@Mock private lateinit var table2: TableLogBuffer
|
||||
|
||||
private val dumpManager = DumpManager()
|
||||
|
||||
@Before
|
||||
@@ -50,276 +49,144 @@ class DumpManagerTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpTarget_dumpable() {
|
||||
// GIVEN a variety of registered dumpables and buffers
|
||||
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
|
||||
dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
|
||||
dumpManager.registerBuffer("buffer1", buffer1)
|
||||
dumpManager.registerBuffer("buffer2", buffer2)
|
||||
|
||||
// WHEN a dumpable is dumped explicitly
|
||||
val args = arrayOf<String>()
|
||||
dumpManager.dumpTarget("dumpable2", pw, args, tailLength = 0)
|
||||
|
||||
// THEN only the requested one has their dump() method called
|
||||
verify(dumpable1, never()).dump(any(), any())
|
||||
verify(dumpable2).dump(pw, args)
|
||||
verify(dumpable3, never()).dump(any(), any())
|
||||
verify(buffer1, never()).dump(any(), anyInt())
|
||||
verify(buffer2, never()).dump(any(), anyInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpTarget_buffer() {
|
||||
// GIVEN a variety of registered dumpables and buffers
|
||||
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
|
||||
dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
|
||||
dumpManager.registerBuffer("buffer1", buffer1)
|
||||
dumpManager.registerBuffer("buffer2", buffer2)
|
||||
|
||||
// WHEN a buffer is dumped explicitly
|
||||
val args = arrayOf<String>()
|
||||
dumpManager.dumpTarget("buffer1", pw, args, tailLength = 14)
|
||||
|
||||
// THEN only the requested one has their dump() method called
|
||||
verify(dumpable1, never()).dump(any(), any())
|
||||
verify(dumpable2, never()).dump(any(), any())
|
||||
verify(dumpable3, never()).dump(any(), any())
|
||||
verify(buffer1).dump(pw, tailLength = 14)
|
||||
verify(buffer2, never()).dump(any(), anyInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpableMatchingIsBasedOnEndOfTag() {
|
||||
// GIVEN a dumpable registered to the manager
|
||||
dumpManager.registerCriticalDumpable("com.android.foo.bar.dumpable1", dumpable1)
|
||||
|
||||
// WHEN that module is dumped
|
||||
val args = arrayOf<String>()
|
||||
dumpManager.dumpTarget("dumpable1", pw, arrayOf(), tailLength = 14)
|
||||
|
||||
// THEN its dump() method is called
|
||||
verify(dumpable1).dump(pw, args)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpTarget_selectsShortestNamedDumpable() {
|
||||
// GIVEN a variety of registered dumpables and buffers
|
||||
dumpManager.registerCriticalDumpable("first-dumpable", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("scnd-dumpable", dumpable2)
|
||||
dumpManager.registerCriticalDumpable("third-dumpable", dumpable3)
|
||||
|
||||
// WHEN a dumpable is dumped by a suffix that matches multiple options
|
||||
val args = arrayOf<String>()
|
||||
dumpManager.dumpTarget("dumpable", pw, args, tailLength = 0)
|
||||
|
||||
// THEN the matching dumpable with the shorter name is dumped
|
||||
verify(dumpable1, never()).dump(any(), any())
|
||||
verify(dumpable2).dump(pw, args)
|
||||
verify(dumpable3, never()).dump(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpTarget_selectsShortestNamedBuffer() {
|
||||
// GIVEN a variety of registered dumpables and buffers
|
||||
dumpManager.registerBuffer("first-buffer", buffer1)
|
||||
dumpManager.registerBuffer("scnd-buffer", buffer2)
|
||||
|
||||
// WHEN a dumpable is dumped by a suffix that matches multiple options
|
||||
val args = arrayOf<String>()
|
||||
dumpManager.dumpTarget("buffer", pw, args, tailLength = 14)
|
||||
|
||||
// THEN the matching buffer with the shorter name is dumped
|
||||
verify(buffer1, never()).dump(any(), anyInt())
|
||||
verify(buffer2).dump(pw, tailLength = 14)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpTarget_selectsShortestNamedMatch_dumpable() {
|
||||
// GIVEN a variety of registered dumpables and buffers
|
||||
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
|
||||
dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
|
||||
dumpManager.registerBuffer("big-buffer1", buffer1)
|
||||
dumpManager.registerBuffer("big-buffer2", buffer2)
|
||||
|
||||
// WHEN a dumpable is dumped by a suffix that matches multiple options
|
||||
val args = arrayOf<String>()
|
||||
dumpManager.dumpTarget("2", pw, args, tailLength = 14)
|
||||
|
||||
// THEN the matching dumpable with the shorter name is dumped
|
||||
verify(dumpable1, never()).dump(any(), any())
|
||||
verify(dumpable2).dump(pw, args)
|
||||
verify(dumpable3, never()).dump(any(), any())
|
||||
verify(buffer1, never()).dump(any(), anyInt())
|
||||
verify(buffer2, never()).dump(any(), anyInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpTarget_selectsShortestNamedMatch_buffer() {
|
||||
// GIVEN a variety of registered dumpables and buffers
|
||||
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
|
||||
dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
|
||||
dumpManager.registerBuffer("buffer1", buffer1)
|
||||
dumpManager.registerBuffer("buffer2", buffer2)
|
||||
|
||||
// WHEN a dumpable is dumped by a suffix that matches multiple options
|
||||
val args = arrayOf<String>()
|
||||
dumpManager.dumpTarget("2", pw, args, tailLength = 14)
|
||||
|
||||
// THEN the matching buffer with the shorter name is dumped
|
||||
verify(dumpable1, never()).dump(any(), any())
|
||||
verify(dumpable2, never()).dump(any(), any())
|
||||
verify(dumpable3, never()).dump(any(), any())
|
||||
verify(buffer1, never()).dump(any(), anyInt())
|
||||
verify(buffer2).dump(pw, tailLength = 14)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_dumpable() {
|
||||
// GIVEN a variety of registered dumpables and buffers
|
||||
dumpManager.registerCriticalDumpable("d1x", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("d2x", dumpable2)
|
||||
dumpManager.registerCriticalDumpable("a3x", dumpable3)
|
||||
dumpManager.registerBuffer("ab1x", buffer1)
|
||||
dumpManager.registerBuffer("b2x", buffer2)
|
||||
|
||||
// WHEN a dumpable is dumped by a suffix that matches multiple options
|
||||
val args = arrayOf<String>()
|
||||
dumpManager.dumpTarget("x", pw, args, tailLength = 14)
|
||||
|
||||
// THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
|
||||
verify(dumpable1, never()).dump(any(), any())
|
||||
verify(dumpable2, never()).dump(any(), any())
|
||||
verify(dumpable3).dump(pw, args)
|
||||
verify(buffer1, never()).dump(any(), anyInt())
|
||||
verify(buffer2, never()).dump(any(), anyInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_buffer() {
|
||||
// GIVEN a variety of registered dumpables and buffers
|
||||
dumpManager.registerCriticalDumpable("d1x", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("d2x", dumpable2)
|
||||
dumpManager.registerCriticalDumpable("az1x", dumpable3)
|
||||
dumpManager.registerBuffer("b1x", buffer1)
|
||||
dumpManager.registerBuffer("b2x", buffer2)
|
||||
|
||||
// WHEN a dumpable is dumped by a suffix that matches multiple options
|
||||
val args = arrayOf<String>()
|
||||
dumpManager.dumpTarget("x", pw, args, tailLength = 14)
|
||||
|
||||
// THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
|
||||
verify(dumpable1, never()).dump(any(), any())
|
||||
verify(dumpable2, never()).dump(any(), any())
|
||||
verify(dumpable3, never()).dump(any(), any())
|
||||
verify(buffer1).dump(pw, tailLength = 14)
|
||||
verify(buffer2, never()).dump(any(), anyInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpDumpables() {
|
||||
// GIVEN a variety of registered dumpables and buffers
|
||||
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
|
||||
dumpManager.registerNormalDumpable("dumpable3", dumpable3)
|
||||
dumpManager.registerBuffer("buffer1", buffer1)
|
||||
dumpManager.registerBuffer("buffer2", buffer2)
|
||||
|
||||
// WHEN a dumpable dump is requested
|
||||
val args = arrayOf<String>()
|
||||
dumpManager.dumpDumpables(pw, args)
|
||||
|
||||
// THEN all dumpables are dumped (both critical and normal) (and no dumpables)
|
||||
verify(dumpable1).dump(pw, args)
|
||||
verify(dumpable2).dump(pw, args)
|
||||
verify(dumpable3).dump(pw, args)
|
||||
verify(buffer1, never()).dump(any(), anyInt())
|
||||
verify(buffer2, never()).dump(any(), anyInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDumpBuffers() {
|
||||
// GIVEN a variety of registered dumpables and buffers
|
||||
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
|
||||
dumpManager.registerNormalDumpable("dumpable3", dumpable3)
|
||||
dumpManager.registerBuffer("buffer1", buffer1)
|
||||
dumpManager.registerBuffer("buffer2", buffer2)
|
||||
|
||||
// WHEN a buffer dump is requested
|
||||
dumpManager.dumpBuffers(pw, tailLength = 1)
|
||||
|
||||
// THEN all buffers are dumped (and no dumpables)
|
||||
verify(dumpable1, never()).dump(any(), any())
|
||||
verify(dumpable2, never()).dump(any(), any())
|
||||
verify(dumpable3, never()).dump(any(), any())
|
||||
verify(buffer1).dump(pw, tailLength = 1)
|
||||
verify(buffer2).dump(pw, tailLength = 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCriticalDump() {
|
||||
// GIVEN a variety of registered dumpables and buffers
|
||||
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
|
||||
dumpManager.registerNormalDumpable("dumpable3", dumpable3)
|
||||
dumpManager.registerBuffer("buffer1", buffer1)
|
||||
dumpManager.registerBuffer("buffer2", buffer2)
|
||||
|
||||
// WHEN a critical dump is requested
|
||||
val args = arrayOf<String>()
|
||||
dumpManager.dumpCritical(pw, args)
|
||||
|
||||
// THEN only critical modules are dumped (and no buffers)
|
||||
verify(dumpable1).dump(pw, args)
|
||||
verify(dumpable2).dump(pw, args)
|
||||
verify(dumpable3, never()).dump(any(), any())
|
||||
verify(buffer1, never()).dump(any(), anyInt())
|
||||
verify(buffer2, never()).dump(any(), anyInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNormalDump() {
|
||||
// GIVEN a variety of registered dumpables and buffers
|
||||
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
|
||||
dumpManager.registerNormalDumpable("dumpable3", dumpable3)
|
||||
dumpManager.registerBuffer("buffer1", buffer1)
|
||||
dumpManager.registerBuffer("buffer2", buffer2)
|
||||
|
||||
// WHEN a normal dump is requested
|
||||
val args = arrayOf<String>()
|
||||
dumpManager.dumpNormal(pw, args, tailLength = 2)
|
||||
|
||||
// THEN the normal module and all buffers are dumped
|
||||
verify(dumpable1, never()).dump(any(), any())
|
||||
verify(dumpable2, never()).dump(any(), any())
|
||||
verify(dumpable3).dump(pw, args)
|
||||
verify(buffer1).dump(pw, tailLength = 2)
|
||||
verify(buffer2).dump(pw, tailLength = 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUnregister() {
|
||||
// GIVEN a variety of registered dumpables and buffers
|
||||
fun testRegisterUnregister_dumpables() {
|
||||
// GIVEN a variety of registered dumpables
|
||||
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
|
||||
dumpManager.registerNormalDumpable("dumpable3", dumpable3)
|
||||
|
||||
// WHEN the collection is requested
|
||||
var dumpables = dumpManager.getDumpables().map { it.dumpable }
|
||||
|
||||
// THEN it contains the registered entries
|
||||
assertThat(dumpables).containsExactly(dumpable1, dumpable2, dumpable3)
|
||||
|
||||
// WHEN the dumpables are unregistered
|
||||
dumpManager.unregisterDumpable("dumpable2")
|
||||
dumpManager.unregisterDumpable("dumpable3")
|
||||
|
||||
// WHEN a dumpables dump is requested
|
||||
val args = arrayOf<String>()
|
||||
dumpManager.dumpDumpables(pw, args)
|
||||
// WHEN the dumpable collection is requests
|
||||
dumpables = dumpManager.getDumpables().map { it.dumpable }
|
||||
|
||||
// THEN the unregistered dumpables (both normal and critical) are not dumped
|
||||
verify(dumpable1).dump(pw, args)
|
||||
verify(dumpable2, never()).dump(any(), any())
|
||||
verify(dumpable3, never()).dump(any(), any())
|
||||
// THEN it contains only the currently-registered entry
|
||||
assertThat(dumpables).containsExactly(dumpable1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRegister_buffers() {
|
||||
// GIVEN a set of registered buffers
|
||||
dumpManager.registerBuffer("buffer1", buffer1)
|
||||
dumpManager.registerBuffer("buffer2", buffer2)
|
||||
|
||||
// WHEN the collection is requested
|
||||
val dumpables = dumpManager.getLogBuffers().map { it.buffer }
|
||||
|
||||
// THEN it contains the registered entries
|
||||
assertThat(dumpables).containsExactly(buffer1, buffer2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRegister_tableLogBuffers() {
|
||||
// GIVEN a set of registered buffers
|
||||
dumpManager.registerTableLogBuffer("table1", table1)
|
||||
dumpManager.registerTableLogBuffer("table2", table2)
|
||||
|
||||
// WHEN the collection is requested
|
||||
val tables = dumpManager.getTableLogBuffers().map { it.table }
|
||||
|
||||
// THEN it contains the registered entries
|
||||
assertThat(tables).containsExactly(table1, table2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun registerDumpable_throwsWhenNameCannotBeAssigned() {
|
||||
// GIVEN dumpable1 and buffer1 and table1 are registered
|
||||
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
|
||||
dumpManager.registerBuffer("buffer1", buffer1)
|
||||
dumpManager.registerTableLogBuffer("table1", table1)
|
||||
|
||||
// THEN an exception is thrown when trying to re-register a new dumpable under the same key
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
dumpManager.registerCriticalDumpable("dumpable1", dumpable2)
|
||||
}
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
dumpManager.registerBuffer("buffer1", buffer2)
|
||||
}
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
dumpManager.registerTableLogBuffer("table1", table2)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun registerDumpable_doesNotThrowWhenReRegistering() {
|
||||
// GIVEN dumpable1 and buffer1 are registered
|
||||
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
|
||||
dumpManager.registerBuffer("buffer1", buffer1)
|
||||
|
||||
// THEN no exception is thrown when trying to re-register a new dumpable under the same key
|
||||
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
|
||||
dumpManager.registerBuffer("buffer1", buffer1)
|
||||
|
||||
// No exception thrown
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getDumpables_returnsSafeCollection() {
|
||||
// GIVEN a variety of registered dumpables
|
||||
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
|
||||
dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
|
||||
dumpManager.registerNormalDumpable("dumpable3", dumpable3)
|
||||
|
||||
// WHEN the collection is retrieved
|
||||
val dumpables = dumpManager.getDumpables()
|
||||
|
||||
// WHEN the collection changes from underneath
|
||||
dumpManager.unregisterDumpable("dumpable1")
|
||||
dumpManager.unregisterDumpable("dumpable2")
|
||||
dumpManager.unregisterDumpable("dumpable3")
|
||||
|
||||
// THEN new collections are empty
|
||||
assertThat(dumpManager.getDumpables()).isEmpty()
|
||||
|
||||
// AND the collection is still safe to use
|
||||
assertThat(dumpables).hasSize(3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getBuffers_returnsSafeCollection() {
|
||||
// GIVEN a set of registered buffers
|
||||
dumpManager.registerBuffer("buffer1", buffer1)
|
||||
dumpManager.registerBuffer("buffer2", buffer2)
|
||||
|
||||
// WHEN the collection is requested
|
||||
val buffers = dumpManager.getLogBuffers()
|
||||
|
||||
// WHEN the collection changes
|
||||
dumpManager.registerBuffer("buffer3", buffer1)
|
||||
|
||||
// THEN the new entry is represented
|
||||
assertThat(dumpManager.getLogBuffers()).hasSize(3)
|
||||
|
||||
// AND the previous collection is unchanged
|
||||
assertThat(buffers).hasSize(2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getTableBuffers_returnsSafeCollection() {
|
||||
// GIVEN a set of registered buffers
|
||||
dumpManager.registerTableLogBuffer("table1", table1)
|
||||
dumpManager.registerTableLogBuffer("table2", table2)
|
||||
|
||||
// WHEN the collection is requested
|
||||
val tables = dumpManager.getTableLogBuffers()
|
||||
|
||||
// WHEN the collection changes
|
||||
dumpManager.registerTableLogBuffer("table3", table1)
|
||||
|
||||
// THEN the new entry is represented
|
||||
assertThat(dumpManager.getTableLogBuffers()).hasSize(3)
|
||||
|
||||
// AND the previous collection is unchanged
|
||||
assertThat(tables).hasSize(2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,20 +18,14 @@ package com.android.systemui.dump
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.dump.DumpHandler.Companion.dump
|
||||
import com.android.systemui.log.LogBuffer
|
||||
import com.android.systemui.util.io.FakeBasicFileAttributes
|
||||
import com.android.systemui.util.io.Files
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.eq
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
import java.io.BufferedWriter
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
@@ -42,17 +36,29 @@ import java.nio.file.OpenOption
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.attribute.BasicFileAttributes
|
||||
import java.util.Arrays
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
class LogEulogizerTest : SysuiTestCase() {
|
||||
|
||||
lateinit var eulogizer: LogBufferEulogizer
|
||||
|
||||
@Mock
|
||||
lateinit var dumpManager: DumpManager
|
||||
@Mock lateinit var dumpManager: DumpManager
|
||||
@Mock lateinit var logBuffer1: LogBuffer
|
||||
lateinit var logBufferEntry1: DumpsysEntry.LogBufferEntry
|
||||
@Mock lateinit var logBuffer2: LogBuffer
|
||||
lateinit var logBufferEntry2: DumpsysEntry.LogBufferEntry
|
||||
|
||||
@Mock
|
||||
lateinit var files: Files
|
||||
@Mock lateinit var files: Files
|
||||
|
||||
private val clock = FakeSystemClock()
|
||||
|
||||
@@ -67,37 +73,47 @@ class LogEulogizerTest : SysuiTestCase() {
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
logBufferEntry1 = DumpsysEntry.LogBufferEntry(logBuffer1, "logbuffer1")
|
||||
logBufferEntry2 = DumpsysEntry.LogBufferEntry(logBuffer2, "logbuffer2")
|
||||
|
||||
eulogizer =
|
||||
LogBufferEulogizer(dumpManager, clock, files, path, MIN_WRITE_GAP, MAX_READ_AGE)
|
||||
eulogizer = LogBufferEulogizer(dumpManager, clock, files, path, MIN_WRITE_GAP, MAX_READ_AGE)
|
||||
|
||||
Mockito.`when`(files.newBufferedWriter(eq(path), any(OpenOption::class.java)))
|
||||
.thenReturn(fileWriter)
|
||||
.thenReturn(fileWriter)
|
||||
|
||||
Mockito.`when`(
|
||||
files.readAttributes(eq(path),
|
||||
eq(BasicFileAttributes::class.java),
|
||||
any(LinkOption::class.java))
|
||||
).thenReturn(fileAttrs)
|
||||
files.readAttributes(
|
||||
eq(path),
|
||||
eq(BasicFileAttributes::class.java),
|
||||
any(LinkOption::class.java)
|
||||
)
|
||||
)
|
||||
.thenReturn(fileAttrs)
|
||||
|
||||
Mockito.`when`(files.lines(eq(path))).thenReturn(Arrays.stream(FAKE_LINES))
|
||||
|
||||
whenever(dumpManager.getLogBuffers()).thenReturn(listOf(logBufferEntry1, logBufferEntry2))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFileIsCreated() {
|
||||
// GIVEN that the log file doesn't already exist
|
||||
Mockito.`when`(
|
||||
files.readAttributes(eq(path),
|
||||
eq(BasicFileAttributes::class.java),
|
||||
any(LinkOption::class.java))
|
||||
).thenThrow(IOException("File not found"))
|
||||
files.readAttributes(
|
||||
eq(path),
|
||||
eq(BasicFileAttributes::class.java),
|
||||
any(LinkOption::class.java)
|
||||
)
|
||||
)
|
||||
.thenThrow(IOException("File not found"))
|
||||
|
||||
// WHEN .record() is called
|
||||
val exception = RuntimeException("Something bad happened")
|
||||
assertEquals(exception, eulogizer.record(exception))
|
||||
|
||||
// THEN the buffers are dumped to the file
|
||||
verify(dumpManager).dumpBuffers(any(PrintWriter::class.java), Mockito.anyInt())
|
||||
verify(logBuffer1).dump(any(PrintWriter::class.java), anyInt())
|
||||
verify(logBuffer2).dump(any(PrintWriter::class.java), anyInt())
|
||||
assertTrue(fileStream.toString().isNotEmpty())
|
||||
}
|
||||
|
||||
@@ -111,7 +127,8 @@ class LogEulogizerTest : SysuiTestCase() {
|
||||
assertEquals(exception, eulogizer.record(exception))
|
||||
|
||||
// THEN the buffers are dumped to the file
|
||||
verify(dumpManager).dumpBuffers(any(PrintWriter::class.java), Mockito.anyInt())
|
||||
verify(logBuffer1).dump(any(PrintWriter::class.java), anyInt())
|
||||
verify(logBuffer2).dump(any(PrintWriter::class.java), anyInt())
|
||||
assertTrue(fileStream.toString().isNotEmpty())
|
||||
}
|
||||
|
||||
@@ -125,7 +142,8 @@ class LogEulogizerTest : SysuiTestCase() {
|
||||
assertEquals(exception, eulogizer.record(exception))
|
||||
|
||||
// THEN the file isn't written to
|
||||
verify(dumpManager, never()).dumpBuffers(any(PrintWriter::class.java), Mockito.anyInt())
|
||||
verify(logBuffer1, never()).dump(any(PrintWriter::class.java), anyInt())
|
||||
verify(logBuffer2, never()).dump(any(PrintWriter::class.java), anyInt())
|
||||
assertTrue(fileStream.toString().isEmpty())
|
||||
}
|
||||
|
||||
@@ -161,9 +179,4 @@ class LogEulogizerTest : SysuiTestCase() {
|
||||
private const val MIN_WRITE_GAP = 10L
|
||||
private const val MAX_READ_AGE = 100L
|
||||
|
||||
private val FAKE_LINES =
|
||||
arrayOf(
|
||||
"First line",
|
||||
"Second line",
|
||||
"Third line"
|
||||
)
|
||||
private val FAKE_LINES = arrayOf("First line", "Second line", "Third line")
|
||||
|
||||
Reference in New Issue
Block a user