Implement the ProtoLogTool
Implemented the ProtoLog code processing, viewer config generation and binary log viewer tool. Design doc: http://go/windowmanager-log2proto Bug: Test: atest protologtool-tests Change-Id: Iff889944a6c381eb8a5b9b637b6bcd38ec60a245
This commit is contained in:
@@ -28,3 +28,13 @@ cc_library_static {
|
||||
"android/bluetooth/smp/enums.proto",
|
||||
],
|
||||
}
|
||||
|
||||
java_library_host {
|
||||
name: "windowmanager-log-proto",
|
||||
srcs: [
|
||||
"android/server/windowmanagerlog.proto"
|
||||
],
|
||||
proto: {
|
||||
type: "full",
|
||||
},
|
||||
}
|
||||
|
||||
61
core/proto/android/server/windowmanagerlog.proto
Normal file
61
core/proto/android/server/windowmanagerlog.proto
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.
|
||||
*/
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package com.android.server.wm;
|
||||
|
||||
option java_multiple_files = true;
|
||||
|
||||
/* represents a single log entry */
|
||||
message ProtoLogMessage {
|
||||
/* log statement identifier, created from message string and log level. */
|
||||
optional fixed32 message_hash = 1;
|
||||
/* log time, relative to the elapsed system time clock. */
|
||||
optional fixed64 elapsed_realtime_nanos = 2;
|
||||
/* string parameters passed to the log call. */
|
||||
repeated string str_params = 3;
|
||||
/* integer parameters passed to the log call. */
|
||||
repeated sint64 sint64_params = 4 [packed=true];
|
||||
/* floating point parameters passed to the log call. */
|
||||
repeated double double_params = 5 [packed=true];
|
||||
/* boolean parameters passed to the log call. */
|
||||
repeated bool boolean_params = 6 [packed=true];
|
||||
}
|
||||
|
||||
/* represents a log file containing window manager log entries.
|
||||
Encoded, it should start with 0x9 0x57 0x49 0x4e 0x44 0x4f 0x4c 0x4f 0x47 (.WINDOLOG), such
|
||||
that they can be easily identified. */
|
||||
message WindowManagerLogFileProto {
|
||||
/* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L
|
||||
(this is needed because enums have to be 32 bits and there's no nice way to put 64bit
|
||||
constants into .proto files. */
|
||||
enum MagicNumber {
|
||||
INVALID = 0;
|
||||
MAGIC_NUMBER_L = 0x444e4957; /* WIND (little-endian ASCII) */
|
||||
MAGIC_NUMBER_H = 0x474f4c4f; /* OLOG (little-endian ASCII) */
|
||||
}
|
||||
|
||||
/* the magic number header */
|
||||
optional fixed64 magic_number = 1;
|
||||
/* log proto version. */
|
||||
optional string version = 2;
|
||||
/* offset between real-time clock and elapsed system time clock in miliseconds.
|
||||
Calculated as: (System.currentTimeMillis() - (SystemClock.elapsedRealtimeNanos() / 1000000) */
|
||||
optional fixed64 realTimeToElapsedTimeOffsetMillis = 3;
|
||||
/* log entries */
|
||||
repeated ProtoLogMessage log = 4;
|
||||
}
|
||||
28
tools/protologtool/Android.bp
Normal file
28
tools/protologtool/Android.bp
Normal file
@@ -0,0 +1,28 @@
|
||||
java_binary_host {
|
||||
name: "protologtool",
|
||||
manifest: "manifest.txt",
|
||||
srcs: [
|
||||
"src/**/*.kt",
|
||||
],
|
||||
static_libs: [
|
||||
"javaparser",
|
||||
"windowmanager-log-proto",
|
||||
"jsonlib",
|
||||
],
|
||||
}
|
||||
|
||||
java_test_host {
|
||||
name: "protologtool-tests",
|
||||
test_suites: ["general-tests"],
|
||||
srcs: [
|
||||
"src/**/*.kt",
|
||||
"tests/**/*.kt",
|
||||
],
|
||||
static_libs: [
|
||||
"javaparser",
|
||||
"windowmanager-log-proto",
|
||||
"jsonlib",
|
||||
"junit",
|
||||
"mockito",
|
||||
],
|
||||
}
|
||||
106
tools/protologtool/README.md
Normal file
106
tools/protologtool/README.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# ProtoLogTool
|
||||
|
||||
Code transformation tool and viewer for ProtoLog.
|
||||
|
||||
## What does it do?
|
||||
|
||||
ProtoLogTool incorporates three different modes of operation:
|
||||
|
||||
### Code transformation
|
||||
|
||||
Command: `process <protolog class path> <protolog implementation class path>
|
||||
<protolog groups class path> <config.jar> [<input.java>] <output.srcjar>`
|
||||
|
||||
In this mode ProtoLogTool transforms every ProtoLog logging call in form of:
|
||||
```java
|
||||
ProtoLog.x(ProtoLogGroup.GROUP_NAME, "Format string %d %s", value1, value2);
|
||||
```
|
||||
into:
|
||||
```java
|
||||
if (GROUP_NAME.isLogToAny()) {
|
||||
ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 123456, "Format string %d %s or null", value1, value2);
|
||||
}
|
||||
```
|
||||
where `ProtoLog`, `ProtoLogImpl` and `ProtoLogGroup` are the classes provided as arguments
|
||||
(can be imported, static imported or full path, wildcard imports are not allowed) and, `x` is the
|
||||
logging method. The transformation is done on the source level. A hash is generated from the format
|
||||
string and log level and inserted after the `ProtoLogGroup` argument. The format string is replaced
|
||||
by `null` if `ProtoLogGroup.GROUP_NAME.isLogToLogcat()` returns false. If `ProtoLogGroup.GROUP_NAME.isEnabled()`
|
||||
returns false the log statement is removed entirely from the resultant code.
|
||||
|
||||
Input is provided as a list of java source file names. Transformed source is saved to a single
|
||||
source jar file. The ProtoLogGroup class with all dependencies should be provided as a compiled
|
||||
jar file (config.jar).
|
||||
|
||||
### Viewer config generation
|
||||
|
||||
Command: `viewerconf <protolog class path> <protolog implementation class path
|
||||
<protolog groups class path> <config.jar> [<input.java>] <output.json>`
|
||||
|
||||
This command is similar in it's syntax to the previous one, only instead of creating a processed source jar
|
||||
it writes a viewer configuration file with following schema:
|
||||
```json
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"messages": {
|
||||
"123456": {
|
||||
"message": "Format string %d %s",
|
||||
"level": "ERROR",
|
||||
"group": "GROUP_NAME"
|
||||
},
|
||||
},
|
||||
"groups": {
|
||||
"GROUP_NAME": {
|
||||
"tag": "TestLog"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Binary log viewing
|
||||
|
||||
Command: `read <viewer.json> <wm_log.pb>`
|
||||
|
||||
Reads the binary ProtoLog log file and outputs a human-readable LogCat-like text log.
|
||||
|
||||
## What is ProtoLog?
|
||||
|
||||
ProtoLog is a logging system created for the WindowManager project. It allows both binary and text logging
|
||||
and is tunable in runtime. It consists of 3 different submodules:
|
||||
* logging system built-in the Android app,
|
||||
* log viewer for reading binary logs,
|
||||
* a code processing tool.
|
||||
|
||||
ProtoLog is designed to reduce both application size (and by that memory usage) and amount of resources needed
|
||||
for logging. This is achieved by replacing log message strings with their hashes and only loading to memory/writing
|
||||
full log messages when necessary.
|
||||
|
||||
### Text logging
|
||||
|
||||
For text-based logs Android LogCat is used as a backend. Message strings are loaded from a viewer config
|
||||
located on the device when needed.
|
||||
|
||||
### Binary logging
|
||||
|
||||
Binary logs are saved as Protocol Buffers file. They can be read using the ProtoLog tool or specialised
|
||||
viewer like Winscope.
|
||||
|
||||
## How to use ProtoLog?
|
||||
|
||||
### Adding a new logging group or log statement
|
||||
|
||||
To add a new ProtoLogGroup simple create a new enum ProtoLogGroup member with desired parameters.
|
||||
|
||||
To add a new logging statement just add a new call to ProtoLog.x where x is a log level.
|
||||
|
||||
After doing any changes to logging groups or statements you should run `make update-protolog` to update
|
||||
viewer configuration saved in the code repository.
|
||||
|
||||
## How to change settings on device in runtime?
|
||||
Use the `adb shell su root cmd window logging` command. To get help just type
|
||||
`adb shell su root cmd window logging help`.
|
||||
|
||||
|
||||
|
||||
|
||||
7
tools/protologtool/TEST_MAPPING
Normal file
7
tools/protologtool/TEST_MAPPING
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"presubmit": [
|
||||
{
|
||||
"name": "protologtool-tests"
|
||||
}
|
||||
]
|
||||
}
|
||||
1
tools/protologtool/manifest.txt
Normal file
1
tools/protologtool/manifest.txt
Normal file
@@ -0,0 +1 @@
|
||||
Main-class: com.android.protologtool.ProtoLogTool
|
||||
135
tools/protologtool/src/com/android/protologtool/CodeUtils.kt
Normal file
135
tools/protologtool/src/com/android/protologtool/CodeUtils.kt
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.protologtool
|
||||
|
||||
import com.github.javaparser.StaticJavaParser
|
||||
import com.github.javaparser.ast.CompilationUnit
|
||||
import com.github.javaparser.ast.ImportDeclaration
|
||||
import com.github.javaparser.ast.NodeList
|
||||
import com.github.javaparser.ast.expr.BinaryExpr
|
||||
import com.github.javaparser.ast.expr.Expression
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr
|
||||
import com.github.javaparser.ast.expr.SimpleName
|
||||
import com.github.javaparser.ast.expr.StringLiteralExpr
|
||||
import com.github.javaparser.ast.expr.TypeExpr
|
||||
import com.github.javaparser.ast.type.PrimitiveType
|
||||
import com.github.javaparser.ast.type.Type
|
||||
|
||||
object CodeUtils {
|
||||
/**
|
||||
* Returns a stable hash of a string.
|
||||
* We reimplement String::hashCode() for readability reasons.
|
||||
*/
|
||||
fun hash(str: String, level: LogLevel): Int {
|
||||
return (level.name + str).map { c -> c.toInt() }.reduce { h, c -> h * 31 + c }
|
||||
}
|
||||
|
||||
fun isWildcardStaticImported(code: CompilationUnit, className: String): Boolean {
|
||||
return code.findAll(ImportDeclaration::class.java)
|
||||
.any { im -> im.isStatic && im.isAsterisk && im.name.toString() == className }
|
||||
}
|
||||
|
||||
fun isClassImportedOrSamePackage(code: CompilationUnit, className: String): Boolean {
|
||||
val packageName = className.substringBeforeLast('.')
|
||||
return code.packageDeclaration.isPresent &&
|
||||
code.packageDeclaration.get().nameAsString == packageName ||
|
||||
code.findAll(ImportDeclaration::class.java)
|
||||
.any { im ->
|
||||
!im.isStatic &&
|
||||
((!im.isAsterisk && im.name.toString() == className) ||
|
||||
(im.isAsterisk && im.name.toString() == packageName))
|
||||
}
|
||||
}
|
||||
|
||||
fun staticallyImportedMethods(code: CompilationUnit, className: String): Set<String> {
|
||||
return code.findAll(ImportDeclaration::class.java)
|
||||
.filter { im ->
|
||||
im.isStatic &&
|
||||
im.name.toString().substringBeforeLast('.') == className
|
||||
}
|
||||
.map { im -> im.name.toString().substringAfterLast('.') }.toSet()
|
||||
}
|
||||
|
||||
fun concatMultilineString(expr: Expression): String {
|
||||
return when (expr) {
|
||||
is StringLiteralExpr -> expr.asString()
|
||||
is BinaryExpr -> when {
|
||||
expr.operator == BinaryExpr.Operator.PLUS ->
|
||||
concatMultilineString(expr.left) + concatMultilineString(expr.right)
|
||||
else -> throw InvalidProtoLogCallException(
|
||||
"messageString must be a string literal " +
|
||||
"or concatenation of string literals.", expr)
|
||||
}
|
||||
else -> throw InvalidProtoLogCallException("messageString must be a string literal " +
|
||||
"or concatenation of string literals.", expr)
|
||||
}
|
||||
}
|
||||
|
||||
enum class LogDataTypes(
|
||||
val type: Type,
|
||||
val toType: (Expression) -> Expression = { expr -> expr }
|
||||
) {
|
||||
// When adding new LogDataType make sure to update {@code logDataTypesToBitMask} accordingly
|
||||
STRING(StaticJavaParser.parseClassOrInterfaceType("String"),
|
||||
{ expr ->
|
||||
MethodCallExpr(TypeExpr(StaticJavaParser.parseClassOrInterfaceType("String")),
|
||||
SimpleName("valueOf"), NodeList(expr))
|
||||
}),
|
||||
LONG(PrimitiveType.longType()),
|
||||
DOUBLE(PrimitiveType.doubleType()),
|
||||
BOOLEAN(PrimitiveType.booleanType());
|
||||
}
|
||||
|
||||
fun parseFormatString(messageString: String): List<LogDataTypes> {
|
||||
val types = mutableListOf<LogDataTypes>()
|
||||
var i = 0
|
||||
while (i < messageString.length) {
|
||||
if (messageString[i] == '%') {
|
||||
if (i + 1 >= messageString.length) {
|
||||
throw InvalidFormatStringException("Invalid format string in config")
|
||||
}
|
||||
when (messageString[i + 1]) {
|
||||
'b' -> types.add(CodeUtils.LogDataTypes.BOOLEAN)
|
||||
'd', 'o', 'x' -> types.add(CodeUtils.LogDataTypes.LONG)
|
||||
'f', 'e', 'g' -> types.add(CodeUtils.LogDataTypes.DOUBLE)
|
||||
's' -> types.add(CodeUtils.LogDataTypes.STRING)
|
||||
'%' -> {
|
||||
}
|
||||
else -> throw InvalidFormatStringException("Invalid format string field" +
|
||||
" %${messageString[i + 1]}")
|
||||
}
|
||||
i += 2
|
||||
} else {
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
||||
fun logDataTypesToBitMask(types: List<LogDataTypes>): Int {
|
||||
if (types.size > 16) {
|
||||
throw InvalidFormatStringException("Too many log call parameters " +
|
||||
"- max 16 parameters supported")
|
||||
}
|
||||
var mask = 0
|
||||
types.forEachIndexed { idx, type ->
|
||||
val x = LogDataTypes.values().indexOf(type)
|
||||
mask = mask or (x shl (idx * 2))
|
||||
}
|
||||
return mask
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.protologtool
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class CommandOptions(args: Array<String>) {
|
||||
companion object {
|
||||
const val TRANSFORM_CALLS_CMD = "transform-protolog-calls"
|
||||
const val GENERATE_CONFIG_CMD = "generate-viewer-config"
|
||||
const val READ_LOG_CMD = "read-log"
|
||||
private val commands = setOf(TRANSFORM_CALLS_CMD, GENERATE_CONFIG_CMD, READ_LOG_CMD)
|
||||
|
||||
private const val PROTOLOG_CLASS_PARAM = "--protolog-class"
|
||||
private const val PROTOLOGIMPL_CLASS_PARAM = "--protolog-impl-class"
|
||||
private const val PROTOLOGGROUP_CLASS_PARAM = "--loggroups-class"
|
||||
private const val PROTOLOGGROUP_JAR_PARAM = "--loggroups-jar"
|
||||
private const val VIEWER_CONFIG_JSON_PARAM = "--viewer-conf"
|
||||
private const val OUTPUT_SOURCE_JAR_PARAM = "--output-srcjar"
|
||||
private val parameters = setOf(PROTOLOG_CLASS_PARAM, PROTOLOGIMPL_CLASS_PARAM,
|
||||
PROTOLOGGROUP_CLASS_PARAM, PROTOLOGGROUP_JAR_PARAM, VIEWER_CONFIG_JSON_PARAM,
|
||||
OUTPUT_SOURCE_JAR_PARAM)
|
||||
|
||||
val USAGE = """
|
||||
Usage: ${Constants.NAME} <command> [<args>]
|
||||
Available commands:
|
||||
|
||||
$TRANSFORM_CALLS_CMD $PROTOLOG_CLASS_PARAM <class name> $PROTOLOGIMPL_CLASS_PARAM
|
||||
<class name> $PROTOLOGGROUP_CLASS_PARAM <class name> $PROTOLOGGROUP_JAR_PARAM
|
||||
<config.jar> $OUTPUT_SOURCE_JAR_PARAM <output.srcjar> [<input.java>]
|
||||
- processes java files replacing stub calls with logging code.
|
||||
|
||||
$GENERATE_CONFIG_CMD $PROTOLOG_CLASS_PARAM <class name> $PROTOLOGGROUP_CLASS_PARAM
|
||||
<class name> $PROTOLOGGROUP_JAR_PARAM <config.jar> $VIEWER_CONFIG_JSON_PARAM
|
||||
<viewer.json> [<input.java>]
|
||||
- creates viewer config file from given java files.
|
||||
|
||||
$READ_LOG_CMD $VIEWER_CONFIG_JSON_PARAM <viewer.json> <wm_log.pb>
|
||||
- translates a binary log to a readable format.
|
||||
""".trimIndent()
|
||||
|
||||
private fun validateClassName(name: String): String {
|
||||
if (!Pattern.matches("^([a-z]+[A-Za-z0-9]*\\.)+([A-Za-z0-9]+)$", name)) {
|
||||
throw InvalidCommandException("Invalid class name $name")
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
private fun getParam(paramName: String, params: Map<String, String>): String {
|
||||
if (!params.containsKey(paramName)) {
|
||||
throw InvalidCommandException("Param $paramName required")
|
||||
}
|
||||
return params.getValue(paramName)
|
||||
}
|
||||
|
||||
private fun validateNotSpecified(paramName: String, params: Map<String, String>): String {
|
||||
if (params.containsKey(paramName)) {
|
||||
throw InvalidCommandException("Unsupported param $paramName")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
private fun validateJarName(name: String): String {
|
||||
if (!name.endsWith(".jar")) {
|
||||
throw InvalidCommandException("Jar file required, got $name instead")
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
private fun validateSrcJarName(name: String): String {
|
||||
if (!name.endsWith(".srcjar")) {
|
||||
throw InvalidCommandException("Source jar file required, got $name instead")
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
private fun validateJSONName(name: String): String {
|
||||
if (!name.endsWith(".json")) {
|
||||
throw InvalidCommandException("Json file required, got $name instead")
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
private fun validateJavaInputList(list: List<String>): List<String> {
|
||||
if (list.isEmpty()) {
|
||||
throw InvalidCommandException("No java source input files")
|
||||
}
|
||||
list.forEach { name ->
|
||||
if (!name.endsWith(".java")) {
|
||||
throw InvalidCommandException("Not a java source file $name")
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
private fun validateLogInputList(list: List<String>): String {
|
||||
if (list.isEmpty()) {
|
||||
throw InvalidCommandException("No log input file")
|
||||
}
|
||||
if (list.size > 1) {
|
||||
throw InvalidCommandException("Only one log input file allowed")
|
||||
}
|
||||
return list[0]
|
||||
}
|
||||
}
|
||||
|
||||
val protoLogClassNameArg: String
|
||||
val protoLogGroupsClassNameArg: String
|
||||
val protoLogImplClassNameArg: String
|
||||
val protoLogGroupsJarArg: String
|
||||
val viewerConfigJsonArg: String
|
||||
val outputSourceJarArg: String
|
||||
val logProtofileArg: String
|
||||
val javaSourceArgs: List<String>
|
||||
val command: String
|
||||
|
||||
init {
|
||||
if (args.isEmpty()) {
|
||||
throw InvalidCommandException("No command specified.")
|
||||
}
|
||||
command = args[0]
|
||||
if (command !in commands) {
|
||||
throw InvalidCommandException("Unknown command.")
|
||||
}
|
||||
|
||||
val params: MutableMap<String, String> = mutableMapOf()
|
||||
val inputFiles: MutableList<String> = mutableListOf()
|
||||
|
||||
var idx = 1
|
||||
while (idx < args.size) {
|
||||
if (args[idx].startsWith("--")) {
|
||||
if (idx + 1 >= args.size) {
|
||||
throw InvalidCommandException("No value for ${args[idx]}")
|
||||
}
|
||||
if (args[idx] !in parameters) {
|
||||
throw InvalidCommandException("Unknown parameter ${args[idx]}")
|
||||
}
|
||||
if (args[idx + 1].startsWith("--")) {
|
||||
throw InvalidCommandException("No value for ${args[idx]}")
|
||||
}
|
||||
if (params.containsKey(args[idx])) {
|
||||
throw InvalidCommandException("Duplicated parameter ${args[idx]}")
|
||||
}
|
||||
params[args[idx]] = args[idx + 1]
|
||||
idx += 2
|
||||
} else {
|
||||
inputFiles.add(args[idx])
|
||||
idx += 1
|
||||
}
|
||||
}
|
||||
|
||||
when (command) {
|
||||
TRANSFORM_CALLS_CMD -> {
|
||||
protoLogClassNameArg = validateClassName(getParam(PROTOLOG_CLASS_PARAM, params))
|
||||
protoLogGroupsClassNameArg = validateClassName(getParam(PROTOLOGGROUP_CLASS_PARAM,
|
||||
params))
|
||||
protoLogImplClassNameArg = validateClassName(getParam(PROTOLOGIMPL_CLASS_PARAM,
|
||||
params))
|
||||
protoLogGroupsJarArg = validateJarName(getParam(PROTOLOGGROUP_JAR_PARAM, params))
|
||||
viewerConfigJsonArg = validateNotSpecified(VIEWER_CONFIG_JSON_PARAM, params)
|
||||
outputSourceJarArg = validateSrcJarName(getParam(OUTPUT_SOURCE_JAR_PARAM, params))
|
||||
javaSourceArgs = validateJavaInputList(inputFiles)
|
||||
logProtofileArg = ""
|
||||
}
|
||||
GENERATE_CONFIG_CMD -> {
|
||||
protoLogClassNameArg = validateClassName(getParam(PROTOLOG_CLASS_PARAM, params))
|
||||
protoLogGroupsClassNameArg = validateClassName(getParam(PROTOLOGGROUP_CLASS_PARAM,
|
||||
params))
|
||||
protoLogImplClassNameArg = validateNotSpecified(PROTOLOGIMPL_CLASS_PARAM, params)
|
||||
protoLogGroupsJarArg = validateJarName(getParam(PROTOLOGGROUP_JAR_PARAM, params))
|
||||
viewerConfigJsonArg = validateJSONName(getParam(VIEWER_CONFIG_JSON_PARAM, params))
|
||||
outputSourceJarArg = validateNotSpecified(OUTPUT_SOURCE_JAR_PARAM, params)
|
||||
javaSourceArgs = validateJavaInputList(inputFiles)
|
||||
logProtofileArg = ""
|
||||
}
|
||||
READ_LOG_CMD -> {
|
||||
protoLogClassNameArg = validateNotSpecified(PROTOLOG_CLASS_PARAM, params)
|
||||
protoLogGroupsClassNameArg = validateNotSpecified(PROTOLOGGROUP_CLASS_PARAM, params)
|
||||
protoLogImplClassNameArg = validateNotSpecified(PROTOLOGIMPL_CLASS_PARAM, params)
|
||||
protoLogGroupsJarArg = validateNotSpecified(PROTOLOGGROUP_JAR_PARAM, params)
|
||||
viewerConfigJsonArg = validateJSONName(getParam(VIEWER_CONFIG_JSON_PARAM, params))
|
||||
outputSourceJarArg = validateNotSpecified(OUTPUT_SOURCE_JAR_PARAM, params)
|
||||
javaSourceArgs = listOf()
|
||||
logProtofileArg = validateLogInputList(inputFiles)
|
||||
}
|
||||
else -> {
|
||||
throw InvalidCommandException("Unknown command.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
tools/protologtool/src/com/android/protologtool/Constants.kt
Normal file
27
tools/protologtool/src/com/android/protologtool/Constants.kt
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.protologtool
|
||||
|
||||
object Constants {
|
||||
const val NAME = "protologtool"
|
||||
const val VERSION = "1.0.0"
|
||||
const val IS_ENABLED_METHOD = "isEnabled"
|
||||
const val IS_LOG_TO_LOGCAT_METHOD = "isLogToLogcat"
|
||||
const val IS_LOG_TO_ANY_METHOD = "isLogToAny"
|
||||
const val GET_TAG_METHOD = "getTag"
|
||||
const val ENUM_VALUES_METHOD = "values"
|
||||
}
|
||||
24
tools/protologtool/src/com/android/protologtool/LogGroup.kt
Normal file
24
tools/protologtool/src/com/android/protologtool/LogGroup.kt
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.protologtool
|
||||
|
||||
data class LogGroup(
|
||||
val name: String,
|
||||
val enabled: Boolean,
|
||||
val textEnabled: Boolean,
|
||||
val tag: String
|
||||
)
|
||||
37
tools/protologtool/src/com/android/protologtool/LogLevel.kt
Normal file
37
tools/protologtool/src/com/android/protologtool/LogLevel.kt
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.protologtool
|
||||
|
||||
import com.github.javaparser.ast.Node
|
||||
|
||||
enum class LogLevel {
|
||||
DEBUG, VERBOSE, INFO, WARN, ERROR, WTF;
|
||||
|
||||
companion object {
|
||||
fun getLevelForMethodName(name: String, node: Node): LogLevel {
|
||||
return when (name) {
|
||||
"d" -> DEBUG
|
||||
"v" -> VERBOSE
|
||||
"i" -> INFO
|
||||
"w" -> WARN
|
||||
"e" -> ERROR
|
||||
"wtf" -> WTF
|
||||
else -> throw InvalidProtoLogCallException("Unknown log level $name", node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
112
tools/protologtool/src/com/android/protologtool/LogParser.kt
Normal file
112
tools/protologtool/src/com/android/protologtool/LogParser.kt
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.protologtool
|
||||
|
||||
import com.android.json.stream.JsonReader
|
||||
import com.android.server.wm.ProtoLogMessage
|
||||
import com.android.server.wm.WindowManagerLogFileProto
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.io.PrintStream
|
||||
import java.lang.Exception
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Implements a simple parser/viewer for binary ProtoLog logs.
|
||||
* A binary log is translated into Android "LogCat"-like text log.
|
||||
*/
|
||||
class LogParser(private val configParser: ViewerConfigParser) {
|
||||
companion object {
|
||||
private val dateFormat = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
|
||||
private val magicNumber =
|
||||
WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or
|
||||
WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong()
|
||||
}
|
||||
|
||||
private fun printTime(time: Long, offset: Long, ps: PrintStream) {
|
||||
ps.print(dateFormat.format(Date(time / 1000000 + offset)) + " ")
|
||||
}
|
||||
|
||||
private fun printFormatted(
|
||||
protoLogMessage: ProtoLogMessage,
|
||||
configEntry: ViewerConfigParser.ConfigEntry,
|
||||
ps: PrintStream
|
||||
) {
|
||||
val strParmIt = protoLogMessage.strParamsList.iterator()
|
||||
val longParamsIt = protoLogMessage.sint64ParamsList.iterator()
|
||||
val doubleParamsIt = protoLogMessage.doubleParamsList.iterator()
|
||||
val boolParamsIt = protoLogMessage.booleanParamsList.iterator()
|
||||
val args = mutableListOf<Any>()
|
||||
val format = configEntry.messageString
|
||||
val argTypes = CodeUtils.parseFormatString(format)
|
||||
try {
|
||||
argTypes.forEach {
|
||||
when (it) {
|
||||
CodeUtils.LogDataTypes.BOOLEAN -> args.add(boolParamsIt.next())
|
||||
CodeUtils.LogDataTypes.LONG -> args.add(longParamsIt.next())
|
||||
CodeUtils.LogDataTypes.DOUBLE -> args.add(doubleParamsIt.next())
|
||||
CodeUtils.LogDataTypes.STRING -> args.add(strParmIt.next())
|
||||
}
|
||||
}
|
||||
} catch (ex: NoSuchElementException) {
|
||||
throw InvalidFormatStringException("Invalid format string in config", ex)
|
||||
}
|
||||
if (strParmIt.hasNext() || longParamsIt.hasNext() ||
|
||||
doubleParamsIt.hasNext() || boolParamsIt.hasNext()) {
|
||||
throw RuntimeException("Invalid format string in config - no enough matchers")
|
||||
}
|
||||
val formatted = format.format(*(args.toTypedArray()))
|
||||
ps.print("${configEntry.level} ${configEntry.tag}: $formatted\n")
|
||||
}
|
||||
|
||||
private fun printUnformatted(protoLogMessage: ProtoLogMessage, ps: PrintStream, tag: String) {
|
||||
ps.println("$tag: ${protoLogMessage.messageHash} - ${protoLogMessage.strParamsList}" +
|
||||
" ${protoLogMessage.sint64ParamsList} ${protoLogMessage.doubleParamsList}" +
|
||||
" ${protoLogMessage.booleanParamsList}")
|
||||
}
|
||||
|
||||
fun parse(protoLogInput: InputStream, jsonConfigInput: InputStream, ps: PrintStream) {
|
||||
val jsonReader = JsonReader(BufferedReader(InputStreamReader(jsonConfigInput)))
|
||||
val config = configParser.parseConfig(jsonReader)
|
||||
val protoLog = WindowManagerLogFileProto.parseFrom(protoLogInput)
|
||||
|
||||
if (protoLog.magicNumber != magicNumber) {
|
||||
throw InvalidInputException("ProtoLog file magic number is invalid.")
|
||||
}
|
||||
if (protoLog.version != Constants.VERSION) {
|
||||
throw InvalidInputException("ProtoLog file version not supported by this tool," +
|
||||
" log version ${protoLog.version}, viewer version ${Constants.VERSION}")
|
||||
}
|
||||
|
||||
protoLog.logList.forEach { log ->
|
||||
printTime(log.elapsedRealtimeNanos, protoLog.realTimeToElapsedTimeOffsetMillis, ps)
|
||||
if (log.messageHash !in config) {
|
||||
printUnformatted(log, ps, "UNKNOWN")
|
||||
} else {
|
||||
val conf = config.getValue(log.messageHash)
|
||||
try {
|
||||
printFormatted(log, conf, ps)
|
||||
} catch (ex: Exception) {
|
||||
printUnformatted(log, ps, "INVALID")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.protologtool
|
||||
|
||||
import com.github.javaparser.ast.CompilationUnit
|
||||
import com.github.javaparser.ast.expr.Expression
|
||||
import com.github.javaparser.ast.expr.FieldAccessExpr
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr
|
||||
import com.github.javaparser.ast.expr.NameExpr
|
||||
|
||||
/**
|
||||
* Helper class for visiting all ProtoLog calls.
|
||||
* For every valid call in the given {@code CompilationUnit} a {@code ProtoLogCallVisitor} callback
|
||||
* is executed.
|
||||
*/
|
||||
open class ProtoLogCallProcessor(
|
||||
private val protoLogClassName: String,
|
||||
private val protoLogGroupClassName: String,
|
||||
private val groupMap: Map<String, LogGroup>
|
||||
) {
|
||||
private val protoLogSimpleClassName = protoLogClassName.substringAfterLast('.')
|
||||
private val protoLogGroupSimpleClassName = protoLogGroupClassName.substringAfterLast('.')
|
||||
|
||||
private fun getLogGroupName(
|
||||
expr: Expression,
|
||||
isClassImported: Boolean,
|
||||
staticImports: Set<String>
|
||||
): String {
|
||||
return when (expr) {
|
||||
is NameExpr -> when {
|
||||
expr.nameAsString in staticImports -> expr.nameAsString
|
||||
else ->
|
||||
throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup", expr)
|
||||
}
|
||||
is FieldAccessExpr -> when {
|
||||
expr.scope.toString() == protoLogGroupClassName
|
||||
|| isClassImported &&
|
||||
expr.scope.toString() == protoLogGroupSimpleClassName -> expr.nameAsString
|
||||
else ->
|
||||
throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup", expr)
|
||||
}
|
||||
else -> throw InvalidProtoLogCallException("Invalid group argument " +
|
||||
"- must be ProtoLogGroup enum member reference", expr)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isProtoCall(
|
||||
call: MethodCallExpr,
|
||||
isLogClassImported: Boolean,
|
||||
staticLogImports: Collection<String>
|
||||
): Boolean {
|
||||
return call.scope.isPresent && call.scope.get().toString() == protoLogClassName ||
|
||||
isLogClassImported && call.scope.isPresent &&
|
||||
call.scope.get().toString() == protoLogSimpleClassName ||
|
||||
!call.scope.isPresent && staticLogImports.contains(call.name.toString())
|
||||
}
|
||||
|
||||
open fun process(code: CompilationUnit, callVisitor: ProtoLogCallVisitor?): CompilationUnit {
|
||||
if (CodeUtils.isWildcardStaticImported(code, protoLogClassName) ||
|
||||
CodeUtils.isWildcardStaticImported(code, protoLogGroupClassName)) {
|
||||
throw IllegalImportException("Wildcard static imports of $protoLogClassName " +
|
||||
"and $protoLogGroupClassName methods are not supported.")
|
||||
}
|
||||
|
||||
val isLogClassImported = CodeUtils.isClassImportedOrSamePackage(code, protoLogClassName)
|
||||
val staticLogImports = CodeUtils.staticallyImportedMethods(code, protoLogClassName)
|
||||
val isGroupClassImported = CodeUtils.isClassImportedOrSamePackage(code,
|
||||
protoLogGroupClassName)
|
||||
val staticGroupImports = CodeUtils.staticallyImportedMethods(code, protoLogGroupClassName)
|
||||
|
||||
code.findAll(MethodCallExpr::class.java)
|
||||
.filter { call ->
|
||||
isProtoCall(call, isLogClassImported, staticLogImports)
|
||||
}.forEach { call ->
|
||||
if (call.arguments.size < 2) {
|
||||
throw InvalidProtoLogCallException("Method signature does not match " +
|
||||
"any ProtoLog method.", call)
|
||||
}
|
||||
|
||||
val messageString = CodeUtils.concatMultilineString(call.getArgument(1))
|
||||
val groupNameArg = call.getArgument(0)
|
||||
val groupName =
|
||||
getLogGroupName(groupNameArg, isGroupClassImported, staticGroupImports)
|
||||
if (groupName !in groupMap) {
|
||||
throw InvalidProtoLogCallException("Unknown group argument " +
|
||||
"- not a ProtoLogGroup enum member", call)
|
||||
}
|
||||
|
||||
callVisitor?.processCall(call, messageString, LogLevel.getLevelForMethodName(
|
||||
call.name.toString(), call), groupMap.getValue(groupName))
|
||||
}
|
||||
return code
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.protologtool
|
||||
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr
|
||||
|
||||
interface ProtoLogCallVisitor {
|
||||
fun processCall(call: MethodCallExpr, messageString: String, level: LogLevel, group: LogGroup)
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.protologtool
|
||||
|
||||
import com.android.protologtool.Constants.ENUM_VALUES_METHOD
|
||||
import com.android.protologtool.Constants.GET_TAG_METHOD
|
||||
import com.android.protologtool.Constants.IS_ENABLED_METHOD
|
||||
import com.android.protologtool.Constants.IS_LOG_TO_LOGCAT_METHOD
|
||||
import java.io.File
|
||||
import java.lang.RuntimeException
|
||||
import java.net.URLClassLoader
|
||||
|
||||
class ProtoLogGroupReader {
|
||||
private fun getClassloaderForJar(jarPath: String): ClassLoader {
|
||||
val jarFile = File(jarPath)
|
||||
val url = jarFile.toURI().toURL()
|
||||
return URLClassLoader(arrayOf(url), ProtoLogGroupReader::class.java.classLoader)
|
||||
}
|
||||
|
||||
private fun getEnumValues(clazz: Class<*>): List<Enum<*>> {
|
||||
val valuesMethod = clazz.getMethod(ENUM_VALUES_METHOD)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return (valuesMethod.invoke(null) as Array<Enum<*>>).toList()
|
||||
}
|
||||
|
||||
private fun getLogGroupFromEnumValue(group: Any, clazz: Class<*>): LogGroup {
|
||||
val enabled = clazz.getMethod(IS_ENABLED_METHOD).invoke(group) as Boolean
|
||||
val textEnabled = clazz.getMethod(IS_LOG_TO_LOGCAT_METHOD).invoke(group) as Boolean
|
||||
val tag = clazz.getMethod(GET_TAG_METHOD).invoke(group) as String
|
||||
val name = (group as Enum<*>).name
|
||||
return LogGroup(name, enabled, textEnabled, tag)
|
||||
}
|
||||
|
||||
fun loadFromJar(jarPath: String, className: String): Map<String, LogGroup> {
|
||||
try {
|
||||
val classLoader = getClassloaderForJar(jarPath)
|
||||
val clazz = classLoader.loadClass(className)
|
||||
val values = getEnumValues(clazz)
|
||||
return values.map { group ->
|
||||
group.name to getLogGroupFromEnumValue(group, clazz)
|
||||
}.toMap()
|
||||
} catch (ex: ReflectiveOperationException) {
|
||||
throw RuntimeException("Unable to load ProtoLogGroup enum class", ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.protologtool
|
||||
|
||||
import com.android.protologtool.CommandOptions.Companion.USAGE
|
||||
import com.github.javaparser.StaticJavaParser
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.util.jar.JarOutputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
object ProtoLogTool {
|
||||
private fun showHelpAndExit() {
|
||||
println(USAGE)
|
||||
exitProcess(-1)
|
||||
}
|
||||
|
||||
private fun processClasses(command: CommandOptions) {
|
||||
val groups = ProtoLogGroupReader()
|
||||
.loadFromJar(command.protoLogGroupsJarArg, command.protoLogGroupsClassNameArg)
|
||||
val out = FileOutputStream(command.outputSourceJarArg)
|
||||
val outJar = JarOutputStream(out)
|
||||
val processor = ProtoLogCallProcessor(command.protoLogClassNameArg,
|
||||
command.protoLogGroupsClassNameArg, groups)
|
||||
val transformer = SourceTransformer(command.protoLogImplClassNameArg, processor)
|
||||
|
||||
command.javaSourceArgs.forEach { path ->
|
||||
val file = File(path)
|
||||
val code = StaticJavaParser.parse(file)
|
||||
val outSrc = transformer.processClass(code)
|
||||
val pack = if (code.packageDeclaration.isPresent) code.packageDeclaration
|
||||
.get().nameAsString else ""
|
||||
val newPath = pack.replace('.', '/') + '/' + file.name
|
||||
outJar.putNextEntry(ZipEntry(newPath))
|
||||
outJar.write(outSrc.toByteArray())
|
||||
outJar.closeEntry()
|
||||
}
|
||||
|
||||
outJar.close()
|
||||
out.close()
|
||||
}
|
||||
|
||||
private fun viewerConf(command: CommandOptions) {
|
||||
val groups = ProtoLogGroupReader()
|
||||
.loadFromJar(command.protoLogGroupsJarArg, command.protoLogGroupsClassNameArg)
|
||||
val processor = ProtoLogCallProcessor(command.protoLogClassNameArg,
|
||||
command.protoLogGroupsClassNameArg, groups)
|
||||
val builder = ViewerConfigBuilder(processor)
|
||||
command.javaSourceArgs.forEach { path ->
|
||||
val file = File(path)
|
||||
builder.processClass(StaticJavaParser.parse(file))
|
||||
}
|
||||
val out = FileOutputStream(command.viewerConfigJsonArg)
|
||||
out.write(builder.build().toByteArray())
|
||||
out.close()
|
||||
}
|
||||
|
||||
fun read(command: CommandOptions) {
|
||||
LogParser(ViewerConfigParser())
|
||||
.parse(FileInputStream(command.logProtofileArg),
|
||||
FileInputStream(command.viewerConfigJsonArg), System.out)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
try {
|
||||
val command = CommandOptions(args)
|
||||
when (command.command) {
|
||||
CommandOptions.TRANSFORM_CALLS_CMD -> processClasses(command)
|
||||
CommandOptions.GENERATE_CONFIG_CMD -> viewerConf(command)
|
||||
CommandOptions.READ_LOG_CMD -> read(command)
|
||||
}
|
||||
} catch (ex: InvalidCommandException) {
|
||||
println(ex.message)
|
||||
showHelpAndExit()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.protologtool
|
||||
|
||||
import com.android.protologtool.Constants.IS_LOG_TO_ANY_METHOD
|
||||
import com.github.javaparser.StaticJavaParser
|
||||
import com.github.javaparser.ast.CompilationUnit
|
||||
import com.github.javaparser.ast.NodeList
|
||||
import com.github.javaparser.ast.body.VariableDeclarator
|
||||
import com.github.javaparser.ast.expr.BooleanLiteralExpr
|
||||
import com.github.javaparser.ast.expr.CastExpr
|
||||
import com.github.javaparser.ast.expr.FieldAccessExpr
|
||||
import com.github.javaparser.ast.expr.IntegerLiteralExpr
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr
|
||||
import com.github.javaparser.ast.expr.NameExpr
|
||||
import com.github.javaparser.ast.expr.NullLiteralExpr
|
||||
import com.github.javaparser.ast.expr.SimpleName
|
||||
import com.github.javaparser.ast.expr.VariableDeclarationExpr
|
||||
import com.github.javaparser.ast.stmt.BlockStmt
|
||||
import com.github.javaparser.ast.stmt.ExpressionStmt
|
||||
import com.github.javaparser.ast.stmt.IfStmt
|
||||
import com.github.javaparser.ast.type.ArrayType
|
||||
import com.github.javaparser.printer.PrettyPrinter
|
||||
import com.github.javaparser.printer.PrettyPrinterConfiguration
|
||||
import com.github.javaparser.printer.lexicalpreservation.LexicalPreservingPrinter
|
||||
|
||||
class SourceTransformer(
|
||||
protoLogImplClassName: String,
|
||||
private val protoLogCallProcessor: ProtoLogCallProcessor
|
||||
) : ProtoLogCallVisitor {
|
||||
override fun processCall(
|
||||
call: MethodCallExpr,
|
||||
messageString: String,
|
||||
level: LogLevel,
|
||||
group: LogGroup
|
||||
) {
|
||||
// Input format: ProtoLog.e(GROUP, "msg %d", arg)
|
||||
if (!call.parentNode.isPresent) {
|
||||
// Should never happen
|
||||
throw RuntimeException("Unable to process log call $call " +
|
||||
"- no parent node in AST")
|
||||
}
|
||||
if (call.parentNode.get() !is ExpressionStmt) {
|
||||
// Should never happen
|
||||
throw RuntimeException("Unable to process log call $call " +
|
||||
"- parent node in AST is not an ExpressionStmt")
|
||||
}
|
||||
val parentStmt = call.parentNode.get() as ExpressionStmt
|
||||
if (!parentStmt.parentNode.isPresent) {
|
||||
// Should never happen
|
||||
throw RuntimeException("Unable to process log call $call " +
|
||||
"- no grandparent node in AST")
|
||||
}
|
||||
val ifStmt: IfStmt
|
||||
if (group.enabled) {
|
||||
val hash = CodeUtils.hash(messageString, level)
|
||||
val newCall = call.clone()
|
||||
if (!group.textEnabled) {
|
||||
// Remove message string if text logging is not enabled by default.
|
||||
// Out: ProtoLog.e(GROUP, null, arg)
|
||||
newCall.arguments[1].replace(NameExpr("null"))
|
||||
}
|
||||
// Insert message string hash as a second argument.
|
||||
// Out: ProtoLog.e(GROUP, 1234, null, arg)
|
||||
newCall.arguments.add(1, IntegerLiteralExpr(hash))
|
||||
val argTypes = CodeUtils.parseFormatString(messageString)
|
||||
val typeMask = CodeUtils.logDataTypesToBitMask(argTypes)
|
||||
// Insert bitmap representing which Number parameters are to be considered as
|
||||
// floating point numbers.
|
||||
// Out: ProtoLog.e(GROUP, 1234, 0, null, arg)
|
||||
newCall.arguments.add(2, IntegerLiteralExpr(typeMask))
|
||||
// Replace call to a stub method with an actual implementation.
|
||||
// Out: com.android.server.wm.ProtoLogImpl.e(GROUP, 1234, null, arg)
|
||||
newCall.setScope(protoLogImplClassNode)
|
||||
// Create a call to GROUP.isLogAny()
|
||||
// Out: GROUP.isLogAny()
|
||||
val isLogAnyExpr = MethodCallExpr(newCall.arguments[0].clone(),
|
||||
SimpleName(IS_LOG_TO_ANY_METHOD))
|
||||
if (argTypes.size != call.arguments.size - 2) {
|
||||
throw InvalidProtoLogCallException(
|
||||
"Number of arguments does not mach format string", call)
|
||||
}
|
||||
val blockStmt = BlockStmt()
|
||||
if (argTypes.isNotEmpty()) {
|
||||
// Assign every argument to a variable to check its type in compile time
|
||||
// (this is assignment is optimized-out by dex tool, there is no runtime impact)/
|
||||
// Out: long protoLogParam0 = arg
|
||||
argTypes.forEachIndexed { idx, type ->
|
||||
val varName = "protoLogParam$idx"
|
||||
val declaration = VariableDeclarator(type.type, varName,
|
||||
type.toType(newCall.arguments[idx + 4].clone()))
|
||||
blockStmt.addStatement(ExpressionStmt(VariableDeclarationExpr(declaration)))
|
||||
newCall.setArgument(idx + 4, NameExpr(SimpleName(varName)))
|
||||
}
|
||||
} else {
|
||||
// Assign (Object[])null as the vararg parameter to prevent allocating an empty
|
||||
// object array.
|
||||
val nullArray = CastExpr(ArrayType(objectType), NullLiteralExpr())
|
||||
newCall.addArgument(nullArray)
|
||||
}
|
||||
blockStmt.addStatement(ExpressionStmt(newCall))
|
||||
// Create an IF-statement with the previously created condition.
|
||||
// Out: if (GROUP.isLogAny()) {
|
||||
// long protoLogParam0 = arg;
|
||||
// com.android.server.wm.ProtoLogImpl.e(GROUP, 1234, 0, null, protoLogParam0);
|
||||
// }
|
||||
ifStmt = IfStmt(isLogAnyExpr, blockStmt, null)
|
||||
} else {
|
||||
// Surround with if (false).
|
||||
val newCall = parentStmt.clone()
|
||||
ifStmt = IfStmt(BooleanLiteralExpr(false), BlockStmt(NodeList(newCall)), null)
|
||||
newCall.setBlockComment(" ${group.name} is disabled ")
|
||||
}
|
||||
// Inline the new statement.
|
||||
val printedIfStmt = inlinePrinter.print(ifStmt)
|
||||
// Append blank lines to preserve line numbering in file (to allow debugging)
|
||||
val newLines = LexicalPreservingPrinter.print(parentStmt).count { c -> c == '\n' }
|
||||
val newStmt = printedIfStmt.substringBeforeLast('}') + ("\n".repeat(newLines)) + '}'
|
||||
val inlinedIfStmt = StaticJavaParser.parseStatement(newStmt)
|
||||
LexicalPreservingPrinter.setup(inlinedIfStmt)
|
||||
// Replace the original call.
|
||||
if (!parentStmt.replace(inlinedIfStmt)) {
|
||||
// Should never happen
|
||||
throw RuntimeException("Unable to process log call $call " +
|
||||
"- unable to replace the call.")
|
||||
}
|
||||
}
|
||||
|
||||
private val inlinePrinter: PrettyPrinter
|
||||
private val objectType = StaticJavaParser.parseClassOrInterfaceType("Object")
|
||||
|
||||
init {
|
||||
val config = PrettyPrinterConfiguration()
|
||||
config.endOfLineCharacter = " "
|
||||
config.indentSize = 0
|
||||
config.tabWidth = 1
|
||||
inlinePrinter = PrettyPrinter(config)
|
||||
}
|
||||
|
||||
private val protoLogImplClassNode =
|
||||
StaticJavaParser.parseExpression<FieldAccessExpr>(protoLogImplClassName)
|
||||
|
||||
fun processClass(compilationUnit: CompilationUnit): String {
|
||||
LexicalPreservingPrinter.setup(compilationUnit)
|
||||
protoLogCallProcessor.process(compilationUnit, this)
|
||||
return LexicalPreservingPrinter.print(compilationUnit)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.protologtool
|
||||
|
||||
import com.android.json.stream.JsonWriter
|
||||
import com.github.javaparser.ast.CompilationUnit
|
||||
import com.android.protologtool.Constants.VERSION
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr
|
||||
import java.io.StringWriter
|
||||
|
||||
class ViewerConfigBuilder(
|
||||
private val protoLogCallVisitor: ProtoLogCallProcessor
|
||||
) : ProtoLogCallVisitor {
|
||||
override fun processCall(
|
||||
call: MethodCallExpr,
|
||||
messageString: String,
|
||||
level: LogLevel,
|
||||
group: LogGroup
|
||||
) {
|
||||
if (group.enabled) {
|
||||
val key = CodeUtils.hash(messageString, level)
|
||||
if (statements.containsKey(key)) {
|
||||
if (statements[key] != Triple(messageString, level, group)) {
|
||||
throw HashCollisionException(
|
||||
"Please modify the log message \"$messageString\" " +
|
||||
"or \"${statements[key]}\" - their hashes are equal.")
|
||||
}
|
||||
} else {
|
||||
groups.add(group)
|
||||
statements[key] = Triple(messageString, level, group)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val statements: MutableMap<Int, Triple<String, LogLevel, LogGroup>> = mutableMapOf()
|
||||
private val groups: MutableSet<LogGroup> = mutableSetOf()
|
||||
|
||||
fun processClass(unit: CompilationUnit) {
|
||||
protoLogCallVisitor.process(unit, this)
|
||||
}
|
||||
|
||||
fun build(): String {
|
||||
val stringWriter = StringWriter()
|
||||
val writer = JsonWriter(stringWriter)
|
||||
writer.setIndent(" ")
|
||||
writer.beginObject()
|
||||
writer.name("version")
|
||||
writer.value(VERSION)
|
||||
writer.name("messages")
|
||||
writer.beginObject()
|
||||
statements.toSortedMap().forEach { (key, value) ->
|
||||
writer.name(key.toString())
|
||||
writer.beginObject()
|
||||
writer.name("message")
|
||||
writer.value(value.first)
|
||||
writer.name("level")
|
||||
writer.value(value.second.name)
|
||||
writer.name("group")
|
||||
writer.value(value.third.name)
|
||||
writer.endObject()
|
||||
}
|
||||
writer.endObject()
|
||||
writer.name("groups")
|
||||
writer.beginObject()
|
||||
groups.toSortedSet(Comparator { o1, o2 -> o1.name.compareTo(o2.name) }).forEach { group ->
|
||||
writer.name(group.name)
|
||||
writer.beginObject()
|
||||
writer.name("tag")
|
||||
writer.value(group.tag)
|
||||
writer.endObject()
|
||||
}
|
||||
writer.endObject()
|
||||
writer.endObject()
|
||||
stringWriter.buffer.append('\n')
|
||||
return stringWriter.toString()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.protologtool
|
||||
|
||||
import com.android.json.stream.JsonReader
|
||||
|
||||
open class ViewerConfigParser {
|
||||
data class MessageEntry(
|
||||
val messageString: String,
|
||||
val level: String,
|
||||
val groupName: String
|
||||
)
|
||||
|
||||
fun parseMessage(jsonReader: JsonReader): MessageEntry {
|
||||
jsonReader.beginObject()
|
||||
var message: String? = null
|
||||
var level: String? = null
|
||||
var groupName: String? = null
|
||||
while (jsonReader.hasNext()) {
|
||||
val key = jsonReader.nextName()
|
||||
when (key) {
|
||||
"message" -> message = jsonReader.nextString()
|
||||
"level" -> level = jsonReader.nextString()
|
||||
"group" -> groupName = jsonReader.nextString()
|
||||
else -> jsonReader.skipValue()
|
||||
}
|
||||
}
|
||||
jsonReader.endObject()
|
||||
if (message.isNullOrBlank() || level.isNullOrBlank() || groupName.isNullOrBlank()) {
|
||||
throw InvalidViewerConfigException("Invalid message entry in viewer config")
|
||||
}
|
||||
return MessageEntry(message, level, groupName)
|
||||
}
|
||||
|
||||
data class GroupEntry(val tag: String)
|
||||
|
||||
fun parseGroup(jsonReader: JsonReader): GroupEntry {
|
||||
jsonReader.beginObject()
|
||||
var tag: String? = null
|
||||
while (jsonReader.hasNext()) {
|
||||
val key = jsonReader.nextName()
|
||||
when (key) {
|
||||
"tag" -> tag = jsonReader.nextString()
|
||||
else -> jsonReader.skipValue()
|
||||
}
|
||||
}
|
||||
jsonReader.endObject()
|
||||
if (tag.isNullOrBlank()) {
|
||||
throw InvalidViewerConfigException("Invalid group entry in viewer config")
|
||||
}
|
||||
return GroupEntry(tag)
|
||||
}
|
||||
|
||||
fun parseMessages(jsonReader: JsonReader): Map<Int, MessageEntry> {
|
||||
val config: MutableMap<Int, MessageEntry> = mutableMapOf()
|
||||
jsonReader.beginObject()
|
||||
while (jsonReader.hasNext()) {
|
||||
val key = jsonReader.nextName()
|
||||
val hash = key.toIntOrNull()
|
||||
?: throw InvalidViewerConfigException("Invalid key in messages viewer config")
|
||||
config[hash] = parseMessage(jsonReader)
|
||||
}
|
||||
jsonReader.endObject()
|
||||
return config
|
||||
}
|
||||
|
||||
fun parseGroups(jsonReader: JsonReader): Map<String, GroupEntry> {
|
||||
val config: MutableMap<String, GroupEntry> = mutableMapOf()
|
||||
jsonReader.beginObject()
|
||||
while (jsonReader.hasNext()) {
|
||||
val key = jsonReader.nextName()
|
||||
config[key] = parseGroup(jsonReader)
|
||||
}
|
||||
jsonReader.endObject()
|
||||
return config
|
||||
}
|
||||
|
||||
data class ConfigEntry(val messageString: String, val level: String, val tag: String)
|
||||
|
||||
open fun parseConfig(jsonReader: JsonReader): Map<Int, ConfigEntry> {
|
||||
var messages: Map<Int, MessageEntry>? = null
|
||||
var groups: Map<String, GroupEntry>? = null
|
||||
var version: String? = null
|
||||
|
||||
jsonReader.beginObject()
|
||||
while (jsonReader.hasNext()) {
|
||||
val key = jsonReader.nextName()
|
||||
when (key) {
|
||||
"messages" -> messages = parseMessages(jsonReader)
|
||||
"groups" -> groups = parseGroups(jsonReader)
|
||||
"version" -> version = jsonReader.nextString()
|
||||
|
||||
else -> jsonReader.skipValue()
|
||||
}
|
||||
}
|
||||
jsonReader.endObject()
|
||||
if (messages == null || groups == null || version == null) {
|
||||
throw InvalidViewerConfigException("Invalid config - definitions missing")
|
||||
}
|
||||
if (version != Constants.VERSION) {
|
||||
throw InvalidViewerConfigException("Viewer config version not supported by this tool," +
|
||||
" config version $version, viewer version ${Constants.VERSION}")
|
||||
}
|
||||
return messages.map { msg ->
|
||||
msg.key to ConfigEntry(
|
||||
msg.value.messageString, msg.value.level, groups[msg.value.groupName]?.tag
|
||||
?: throw InvalidViewerConfigException(
|
||||
"Group definition missing for ${msg.value.groupName}"))
|
||||
}.toMap()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.protologtool
|
||||
|
||||
import com.github.javaparser.ast.Node
|
||||
import java.lang.Exception
|
||||
import java.lang.RuntimeException
|
||||
|
||||
class HashCollisionException(message: String) : RuntimeException(message)
|
||||
|
||||
class IllegalImportException(message: String) : Exception(message)
|
||||
|
||||
class InvalidProtoLogCallException(message: String, node: Node)
|
||||
: RuntimeException("$message\nAt: $node")
|
||||
|
||||
class InvalidViewerConfigException : Exception {
|
||||
constructor(message: String) : super(message)
|
||||
|
||||
constructor(message: String, ex: Exception) : super(message, ex)
|
||||
}
|
||||
|
||||
class InvalidFormatStringException : Exception {
|
||||
constructor(message: String) : super(message)
|
||||
|
||||
constructor(message: String, ex: Exception) : super(message, ex)
|
||||
}
|
||||
|
||||
class InvalidInputException(message: String) : Exception(message)
|
||||
|
||||
class InvalidCommandException(message: String) : Exception(message)
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.protologtool
|
||||
|
||||
import com.github.javaparser.StaticJavaParser
|
||||
import com.github.javaparser.ast.expr.BinaryExpr
|
||||
import com.github.javaparser.ast.expr.StringLiteralExpr
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class CodeUtilsTest {
|
||||
@Test
|
||||
fun hash() {
|
||||
assertEquals(-1704685243, CodeUtils.hash("test", LogLevel.DEBUG))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hash_changeLevel() {
|
||||
assertEquals(-1176900998, CodeUtils.hash("test", LogLevel.ERROR))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hash_changeMessage() {
|
||||
assertEquals(-1305634931, CodeUtils.hash("test2", LogLevel.DEBUG))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isWildcardStaticImported_true() {
|
||||
val code = """package org.example.test;
|
||||
import static org.example.Test.*;
|
||||
"""
|
||||
assertTrue(CodeUtils.isWildcardStaticImported(
|
||||
StaticJavaParser.parse(code), "org.example.Test"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isWildcardStaticImported_notStatic() {
|
||||
val code = """package org.example.test;
|
||||
import org.example.Test.*;
|
||||
"""
|
||||
assertFalse(CodeUtils.isWildcardStaticImported(
|
||||
StaticJavaParser.parse(code), "org.example.Test"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isWildcardStaticImported_differentClass() {
|
||||
val code = """package org.example.test;
|
||||
import static org.example.Test2.*;
|
||||
"""
|
||||
assertFalse(CodeUtils.isWildcardStaticImported(
|
||||
StaticJavaParser.parse(code), "org.example.Test"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isWildcardStaticImported_notWildcard() {
|
||||
val code = """package org.example.test;
|
||||
import org.example.Test.test;
|
||||
"""
|
||||
assertFalse(CodeUtils.isWildcardStaticImported(
|
||||
StaticJavaParser.parse(code), "org.example.Test"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isClassImportedOrSamePackage_imported() {
|
||||
val code = """package org.example.test;
|
||||
import org.example.Test;
|
||||
"""
|
||||
assertTrue(CodeUtils.isClassImportedOrSamePackage(
|
||||
StaticJavaParser.parse(code), "org.example.Test"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isClassImportedOrSamePackage_samePackage() {
|
||||
val code = """package org.example.test;
|
||||
"""
|
||||
assertTrue(CodeUtils.isClassImportedOrSamePackage(
|
||||
StaticJavaParser.parse(code), "org.example.test.Test"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isClassImportedOrSamePackage_false() {
|
||||
val code = """package org.example.test;
|
||||
import org.example.Test;
|
||||
"""
|
||||
assertFalse(CodeUtils.isClassImportedOrSamePackage(
|
||||
StaticJavaParser.parse(code), "org.example.Test2"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun staticallyImportedMethods_ab() {
|
||||
val code = """
|
||||
import static org.example.Test.a;
|
||||
import static org.example.Test.b;
|
||||
"""
|
||||
val imported = CodeUtils.staticallyImportedMethods(StaticJavaParser.parse(code),
|
||||
"org.example.Test")
|
||||
assertTrue(imported.containsAll(listOf("a", "b")))
|
||||
assertEquals(2, imported.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun staticallyImportedMethods_differentClass() {
|
||||
val code = """
|
||||
import static org.example.Test.a;
|
||||
import static org.example.Test2.b;
|
||||
"""
|
||||
val imported = CodeUtils.staticallyImportedMethods(StaticJavaParser.parse(code),
|
||||
"org.example.Test")
|
||||
assertTrue(imported.containsAll(listOf("a")))
|
||||
assertEquals(1, imported.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun staticallyImportedMethods_notStatic() {
|
||||
val code = """
|
||||
import static org.example.Test.a;
|
||||
import org.example.Test.b;
|
||||
"""
|
||||
val imported = CodeUtils.staticallyImportedMethods(StaticJavaParser.parse(code),
|
||||
"org.example.Test")
|
||||
assertTrue(imported.containsAll(listOf("a")))
|
||||
assertEquals(1, imported.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun concatMultilineString_single() {
|
||||
val str = StringLiteralExpr("test")
|
||||
val out = CodeUtils.concatMultilineString(str)
|
||||
assertEquals("test", out)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun concatMultilineString_double() {
|
||||
val str = """
|
||||
"test" + "abc"
|
||||
"""
|
||||
val code = StaticJavaParser.parseExpression<BinaryExpr>(str)
|
||||
val out = CodeUtils.concatMultilineString(code)
|
||||
assertEquals("testabc", out)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun concatMultilineString_multiple() {
|
||||
val str = """
|
||||
"test" + "abc" + "1234" + "test"
|
||||
"""
|
||||
val code = StaticJavaParser.parseExpression<BinaryExpr>(str)
|
||||
val out = CodeUtils.concatMultilineString(code)
|
||||
assertEquals("testabc1234test", out)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseFormatString() {
|
||||
val str = "%b %d %o %x %f %e %g %s %%"
|
||||
val out = CodeUtils.parseFormatString(str)
|
||||
assertEquals(listOf(
|
||||
CodeUtils.LogDataTypes.BOOLEAN,
|
||||
CodeUtils.LogDataTypes.LONG,
|
||||
CodeUtils.LogDataTypes.LONG,
|
||||
CodeUtils.LogDataTypes.LONG,
|
||||
CodeUtils.LogDataTypes.DOUBLE,
|
||||
CodeUtils.LogDataTypes.DOUBLE,
|
||||
CodeUtils.LogDataTypes.DOUBLE,
|
||||
CodeUtils.LogDataTypes.STRING
|
||||
), out)
|
||||
}
|
||||
|
||||
@Test(expected = InvalidFormatStringException::class)
|
||||
fun parseFormatString_invalid() {
|
||||
val str = "%q"
|
||||
CodeUtils.parseFormatString(str)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun logDataTypesToBitMask() {
|
||||
val types = listOf(CodeUtils.LogDataTypes.STRING, CodeUtils.LogDataTypes.DOUBLE,
|
||||
CodeUtils.LogDataTypes.LONG, CodeUtils.LogDataTypes.BOOLEAN)
|
||||
val mask = CodeUtils.logDataTypesToBitMask(types)
|
||||
assertEquals(0b11011000, mask)
|
||||
}
|
||||
|
||||
@Test(expected = InvalidFormatStringException::class)
|
||||
fun logDataTypesToBitMask_toManyParams() {
|
||||
val types = mutableListOf<CodeUtils.LogDataTypes>()
|
||||
for (i in 0..16) {
|
||||
types.add(CodeUtils.LogDataTypes.STRING)
|
||||
}
|
||||
CodeUtils.logDataTypesToBitMask(types)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.protologtool
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class CommandOptionsTest {
|
||||
companion object {
|
||||
val TEST_JAVA_SRC = listOf(
|
||||
"frameworks/base/services/core/java/com/android/server/wm/" +
|
||||
"AccessibilityController.java",
|
||||
"frameworks/base/services/core/java/com/android/server/wm/ActivityDisplay.java",
|
||||
"frameworks/base/services/core/java/com/android/server/wm/" +
|
||||
"ActivityMetricsLaunchObserver.java"
|
||||
)
|
||||
private const val TEST_PROTOLOG_CLASS = "com.android.server.wm.ProtoLog"
|
||||
private const val TEST_PROTOLOGIMPL_CLASS = "com.android.server.wm.ProtoLogImpl"
|
||||
private const val TEST_PROTOLOGGROUP_CLASS = "com.android.server.wm.ProtoLogGroup"
|
||||
private const val TEST_PROTOLOGGROUP_JAR = "out/soong/.intermediates/frameworks/base/" +
|
||||
"services/core/services.core.wm.protologgroups/android_common/javac/" +
|
||||
"services.core.wm.protologgroups.jar"
|
||||
private const val TEST_SRC_JAR = "out/soong/.temp/sbox175955373/" +
|
||||
"services.core.wm.protolog.srcjar"
|
||||
private const val TEST_VIEWER_JSON = "out/soong/.temp/sbox175955373/" +
|
||||
"services.core.wm.protolog.json"
|
||||
private const val TEST_LOG = "./test_log.pb"
|
||||
}
|
||||
|
||||
@Test(expected = InvalidCommandException::class)
|
||||
fun noCommand() {
|
||||
CommandOptions(arrayOf())
|
||||
}
|
||||
|
||||
@Test(expected = InvalidCommandException::class)
|
||||
fun invalidCommand() {
|
||||
val testLine = "invalid"
|
||||
CommandOptions(testLine.split(' ').toTypedArray())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun transformClasses() {
|
||||
val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
|
||||
"--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
|
||||
"--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
|
||||
"--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
|
||||
"--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
|
||||
val cmd = CommandOptions(testLine.split(' ').toTypedArray())
|
||||
assertEquals(CommandOptions.TRANSFORM_CALLS_CMD, cmd.command)
|
||||
assertEquals(TEST_PROTOLOG_CLASS, cmd.protoLogClassNameArg)
|
||||
assertEquals(TEST_PROTOLOGIMPL_CLASS, cmd.protoLogImplClassNameArg)
|
||||
assertEquals(TEST_PROTOLOGGROUP_CLASS, cmd.protoLogGroupsClassNameArg)
|
||||
assertEquals(TEST_PROTOLOGGROUP_JAR, cmd.protoLogGroupsJarArg)
|
||||
assertEquals(TEST_SRC_JAR, cmd.outputSourceJarArg)
|
||||
assertEquals(TEST_JAVA_SRC, cmd.javaSourceArgs)
|
||||
}
|
||||
|
||||
@Test(expected = InvalidCommandException::class)
|
||||
fun transformClasses_noProtoLogClass() {
|
||||
val testLine = "transform-protolog-calls " +
|
||||
"--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
|
||||
"--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
|
||||
"--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
|
||||
"--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
|
||||
CommandOptions(testLine.split(' ').toTypedArray())
|
||||
}
|
||||
|
||||
@Test(expected = InvalidCommandException::class)
|
||||
fun transformClasses_noProtoLogImplClass() {
|
||||
val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
|
||||
"--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
|
||||
"--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
|
||||
"--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
|
||||
CommandOptions(testLine.split(' ').toTypedArray())
|
||||
}
|
||||
|
||||
@Test(expected = InvalidCommandException::class)
|
||||
fun transformClasses_noProtoLogGroupClass() {
|
||||
val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
|
||||
"--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
|
||||
"--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
|
||||
"--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
|
||||
CommandOptions(testLine.split(' ').toTypedArray())
|
||||
}
|
||||
|
||||
@Test(expected = InvalidCommandException::class)
|
||||
fun transformClasses_noProtoLogGroupJar() {
|
||||
val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
|
||||
"--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
|
||||
"--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
|
||||
"--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
|
||||
CommandOptions(testLine.split(' ').toTypedArray())
|
||||
}
|
||||
|
||||
@Test(expected = InvalidCommandException::class)
|
||||
fun transformClasses_noOutJar() {
|
||||
val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
|
||||
"--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
|
||||
"--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
|
||||
"--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
|
||||
TEST_JAVA_SRC.joinToString(" ")
|
||||
CommandOptions(testLine.split(' ').toTypedArray())
|
||||
}
|
||||
|
||||
@Test(expected = InvalidCommandException::class)
|
||||
fun transformClasses_noJavaInput() {
|
||||
val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
|
||||
"--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
|
||||
"--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
|
||||
"--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
|
||||
"--output-srcjar $TEST_SRC_JAR"
|
||||
CommandOptions(testLine.split(' ').toTypedArray())
|
||||
}
|
||||
|
||||
@Test(expected = InvalidCommandException::class)
|
||||
fun transformClasses_invalidProtoLogClass() {
|
||||
val testLine = "transform-protolog-calls --protolog-class invalid " +
|
||||
"--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
|
||||
"--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
|
||||
"--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
|
||||
"--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
|
||||
CommandOptions(testLine.split(' ').toTypedArray())
|
||||
}
|
||||
|
||||
@Test(expected = InvalidCommandException::class)
|
||||
fun transformClasses_invalidProtoLogImplClass() {
|
||||
val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
|
||||
"--protolog-impl-class invalid " +
|
||||
"--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
|
||||
"--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
|
||||
"--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
|
||||
CommandOptions(testLine.split(' ').toTypedArray())
|
||||
}
|
||||
|
||||
@Test(expected = InvalidCommandException::class)
|
||||
fun transformClasses_invalidProtoLogGroupClass() {
|
||||
val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
|
||||
"--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
|
||||
"--loggroups-class invalid " +
|
||||
"--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
|
||||
"--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
|
||||
CommandOptions(testLine.split(' ').toTypedArray())
|
||||
}
|
||||
|
||||
@Test(expected = InvalidCommandException::class)
|
||||
fun transformClasses_invalidProtoLogGroupJar() {
|
||||
val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
|
||||
"--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
|
||||
"--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
|
||||
"--loggroups-jar invalid.txt " +
|
||||
"--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
|
||||
CommandOptions(testLine.split(' ').toTypedArray())
|
||||
}
|
||||
|
||||
@Test(expected = InvalidCommandException::class)
|
||||
fun transformClasses_invalidOutJar() {
|
||||
val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
|
||||
"--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
|
||||
"--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
|
||||
"--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
|
||||
"--output-srcjar invalid.db ${TEST_JAVA_SRC.joinToString(" ")}"
|
||||
CommandOptions(testLine.split(' ').toTypedArray())
|
||||
}
|
||||
|
||||
@Test(expected = InvalidCommandException::class)
|
||||
fun transformClasses_invalidJavaInput() {
|
||||
val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
|
||||
"--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
|
||||
"--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
|
||||
"--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
|
||||
"--output-srcjar $TEST_SRC_JAR invalid.py"
|
||||
CommandOptions(testLine.split(' ').toTypedArray())
|
||||
}
|
||||
|
||||
@Test(expected = InvalidCommandException::class)
|
||||
fun transformClasses_unknownParam() {
|
||||
val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
|
||||
"--unknown test --protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
|
||||
"--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
|
||||
"--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
|
||||
"--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
|
||||
CommandOptions(testLine.split(' ').toTypedArray())
|
||||
}
|
||||
|
||||
@Test(expected = InvalidCommandException::class)
|
||||
fun transformClasses_noValue() {
|
||||
val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
|
||||
"--protolog-impl-class " +
|
||||
"--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
|
||||
"--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
|
||||
"--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
|
||||
CommandOptions(testLine.split(' ').toTypedArray())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun generateConfig() {
|
||||
val testLine = "generate-viewer-config --protolog-class $TEST_PROTOLOG_CLASS " +
|
||||
"--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
|
||||
"--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
|
||||
"--viewer-conf $TEST_VIEWER_JSON ${TEST_JAVA_SRC.joinToString(" ")}"
|
||||
val cmd = CommandOptions(testLine.split(' ').toTypedArray())
|
||||
assertEquals(CommandOptions.GENERATE_CONFIG_CMD, cmd.command)
|
||||
assertEquals(TEST_PROTOLOG_CLASS, cmd.protoLogClassNameArg)
|
||||
assertEquals(TEST_PROTOLOGGROUP_CLASS, cmd.protoLogGroupsClassNameArg)
|
||||
assertEquals(TEST_PROTOLOGGROUP_JAR, cmd.protoLogGroupsJarArg)
|
||||
assertEquals(TEST_VIEWER_JSON, cmd.viewerConfigJsonArg)
|
||||
assertEquals(TEST_JAVA_SRC, cmd.javaSourceArgs)
|
||||
}
|
||||
|
||||
@Test(expected = InvalidCommandException::class)
|
||||
fun generateConfig_noViewerConfig() {
|
||||
val testLine = "generate-viewer-config --protolog-class $TEST_PROTOLOG_CLASS " +
|
||||
"--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
|
||||
"--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
|
||||
TEST_JAVA_SRC.joinToString(" ")
|
||||
CommandOptions(testLine.split(' ').toTypedArray())
|
||||
}
|
||||
|
||||
@Test(expected = InvalidCommandException::class)
|
||||
fun generateConfig_invalidViewerConfig() {
|
||||
val testLine = "generate-viewer-config --protolog-class $TEST_PROTOLOG_CLASS " +
|
||||
"--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
|
||||
"--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
|
||||
"--viewer-conf invalid.yaml ${TEST_JAVA_SRC.joinToString(" ")}"
|
||||
CommandOptions(testLine.split(' ').toTypedArray())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun readLog() {
|
||||
val testLine = "read-log --viewer-conf $TEST_VIEWER_JSON $TEST_LOG"
|
||||
val cmd = CommandOptions(testLine.split(' ').toTypedArray())
|
||||
assertEquals(CommandOptions.READ_LOG_CMD, cmd.command)
|
||||
assertEquals(TEST_VIEWER_JSON, cmd.viewerConfigJsonArg)
|
||||
assertEquals(TEST_LOG, cmd.logProtofileArg)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.protologtool
|
||||
|
||||
import com.android.json.stream.JsonReader
|
||||
import com.android.server.wm.ProtoLogMessage
|
||||
import com.android.server.wm.WindowManagerLogFileProto
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.mock
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.io.PrintStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
class LogParserTest {
|
||||
private val configParser: ViewerConfigParser = mock(ViewerConfigParser::class.java)
|
||||
private val parser = LogParser(configParser)
|
||||
private var config: MutableMap<Int, ViewerConfigParser.ConfigEntry> = mutableMapOf()
|
||||
private var outStream: OutputStream = ByteArrayOutputStream()
|
||||
private var printStream: PrintStream = PrintStream(outStream)
|
||||
private val dateFormat = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
|
||||
|
||||
@Before
|
||||
fun init() {
|
||||
Mockito.`when`(configParser.parseConfig(any(JsonReader::class.java))).thenReturn(config)
|
||||
}
|
||||
|
||||
private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
|
||||
|
||||
private fun getConfigDummyStream(): InputStream {
|
||||
return "".byteInputStream()
|
||||
}
|
||||
|
||||
private fun buildProtoInput(logBuilder: WindowManagerLogFileProto.Builder): InputStream {
|
||||
logBuilder.setVersion(Constants.VERSION)
|
||||
logBuilder.magicNumber =
|
||||
WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or
|
||||
WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong()
|
||||
return logBuilder.build().toByteArray().inputStream()
|
||||
}
|
||||
|
||||
private fun testDate(timeMS: Long): String {
|
||||
return dateFormat.format(Date(timeMS))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parse() {
|
||||
config[70933285] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b",
|
||||
"ERROR", "WindowManager")
|
||||
|
||||
val logBuilder = WindowManagerLogFileProto.newBuilder()
|
||||
val logMessageBuilder = ProtoLogMessage.newBuilder()
|
||||
logMessageBuilder
|
||||
.setMessageHash(70933285)
|
||||
.setElapsedRealtimeNanos(0)
|
||||
.addBooleanParams(true)
|
||||
logBuilder.addLog(logMessageBuilder.build())
|
||||
|
||||
parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
|
||||
|
||||
assertEquals("${testDate(0)} ERROR WindowManager: Test completed successfully: true\n",
|
||||
outStream.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parse_formatting() {
|
||||
config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o" +
|
||||
" %x %e %g %s %f", "ERROR", "WindowManager")
|
||||
|
||||
val logBuilder = WindowManagerLogFileProto.newBuilder()
|
||||
val logMessageBuilder = ProtoLogMessage.newBuilder()
|
||||
logMessageBuilder
|
||||
.setMessageHash(123)
|
||||
.setElapsedRealtimeNanos(0)
|
||||
.addBooleanParams(true)
|
||||
.addAllSint64Params(listOf(1000, 20000, 300000))
|
||||
.addAllDoubleParams(listOf(0.1, 0.00001, 1000.1))
|
||||
.addStrParams("test")
|
||||
logBuilder.addLog(logMessageBuilder.build())
|
||||
|
||||
parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
|
||||
|
||||
assertEquals("${testDate(0)} ERROR WindowManager: Test completed successfully: " +
|
||||
"true 1000 % 47040 493e0 1.000000e-01 1.00000e-05 test 1000.100000\n",
|
||||
outStream.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parse_invalidParamsTooMany() {
|
||||
config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o",
|
||||
"ERROR", "WindowManager")
|
||||
|
||||
val logBuilder = WindowManagerLogFileProto.newBuilder()
|
||||
val logMessageBuilder = ProtoLogMessage.newBuilder()
|
||||
logMessageBuilder
|
||||
.setMessageHash(123)
|
||||
.setElapsedRealtimeNanos(0)
|
||||
.addBooleanParams(true)
|
||||
.addAllSint64Params(listOf(1000, 20000, 300000))
|
||||
.addAllDoubleParams(listOf(0.1, 0.00001, 1000.1))
|
||||
.addStrParams("test")
|
||||
logBuilder.addLog(logMessageBuilder.build())
|
||||
|
||||
parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
|
||||
|
||||
assertEquals("${testDate(0)} INVALID: 123 - [test] [1000, 20000, 300000] " +
|
||||
"[0.1, 1.0E-5, 1000.1] [true]\n", outStream.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parse_invalidParamsNotEnough() {
|
||||
config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o" +
|
||||
" %x %e %g %s %f", "ERROR", "WindowManager")
|
||||
|
||||
val logBuilder = WindowManagerLogFileProto.newBuilder()
|
||||
val logMessageBuilder = ProtoLogMessage.newBuilder()
|
||||
logMessageBuilder
|
||||
.setMessageHash(123)
|
||||
.setElapsedRealtimeNanos(0)
|
||||
.addBooleanParams(true)
|
||||
.addStrParams("test")
|
||||
logBuilder.addLog(logMessageBuilder.build())
|
||||
|
||||
parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
|
||||
|
||||
assertEquals("${testDate(0)} INVALID: 123 - [test] [] [] [true]\n",
|
||||
outStream.toString())
|
||||
}
|
||||
|
||||
@Test(expected = InvalidInputException::class)
|
||||
fun parse_invalidMagicNumber() {
|
||||
val logBuilder = WindowManagerLogFileProto.newBuilder()
|
||||
logBuilder.setVersion(Constants.VERSION)
|
||||
logBuilder.magicNumber = 0
|
||||
val stream = logBuilder.build().toByteArray().inputStream()
|
||||
|
||||
parser.parse(stream, getConfigDummyStream(), printStream)
|
||||
}
|
||||
|
||||
@Test(expected = InvalidInputException::class)
|
||||
fun parse_invalidVersion() {
|
||||
val logBuilder = WindowManagerLogFileProto.newBuilder()
|
||||
logBuilder.setVersion("invalid")
|
||||
logBuilder.magicNumber =
|
||||
WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or
|
||||
WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong()
|
||||
val stream = logBuilder.build().toByteArray().inputStream()
|
||||
|
||||
parser.parse(stream, getConfigDummyStream(), printStream)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parse_noConfig() {
|
||||
val logBuilder = WindowManagerLogFileProto.newBuilder()
|
||||
val logMessageBuilder = ProtoLogMessage.newBuilder()
|
||||
logMessageBuilder
|
||||
.setMessageHash(70933285)
|
||||
.setElapsedRealtimeNanos(0)
|
||||
.addBooleanParams(true)
|
||||
logBuilder.addLog(logMessageBuilder.build())
|
||||
|
||||
parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
|
||||
|
||||
assertEquals("${testDate(0)} UNKNOWN: 70933285 - [] [] [] [true]\n",
|
||||
outStream.toString())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.protologtool
|
||||
|
||||
import com.github.javaparser.StaticJavaParser
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class ProtoLogCallProcessorTest {
|
||||
private data class LogCall(
|
||||
val call: MethodCallExpr,
|
||||
val messageString: String,
|
||||
val level: LogLevel,
|
||||
val group: LogGroup
|
||||
)
|
||||
|
||||
private val groupMap: MutableMap<String, LogGroup> = mutableMapOf()
|
||||
private val calls: MutableList<LogCall> = mutableListOf()
|
||||
private val visitor = ProtoLogCallProcessor("org.example.ProtoLog", "org.example.ProtoLogGroup",
|
||||
groupMap)
|
||||
private val processor = object : ProtoLogCallVisitor {
|
||||
override fun processCall(
|
||||
call: MethodCallExpr,
|
||||
messageString: String,
|
||||
level: LogLevel,
|
||||
group: LogGroup
|
||||
) {
|
||||
calls.add(LogCall(call, messageString, level, group))
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkCalls() {
|
||||
assertEquals(1, calls.size)
|
||||
val c = calls[0]
|
||||
assertEquals("test %b", c.messageString)
|
||||
assertEquals(groupMap["TEST"], c.group)
|
||||
assertEquals(LogLevel.DEBUG, c.level)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun process_samePackage() {
|
||||
val code = """
|
||||
package org.example;
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
|
||||
ProtoLog.e(ProtoLogGroup.ERROR, "error %d", 1);
|
||||
}
|
||||
}
|
||||
"""
|
||||
groupMap["TEST"] = LogGroup("TEST", true, false, "WindowManager")
|
||||
groupMap["ERROR"] = LogGroup("ERROR", true, true, "WindowManagerERROR")
|
||||
visitor.process(StaticJavaParser.parse(code), processor)
|
||||
assertEquals(2, calls.size)
|
||||
var c = calls[0]
|
||||
assertEquals("test %b", c.messageString)
|
||||
assertEquals(groupMap["TEST"], c.group)
|
||||
assertEquals(LogLevel.DEBUG, c.level)
|
||||
c = calls[1]
|
||||
assertEquals("error %d", c.messageString)
|
||||
assertEquals(groupMap["ERROR"], c.group)
|
||||
assertEquals(LogLevel.ERROR, c.level)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun process_imported() {
|
||||
val code = """
|
||||
package org.example2;
|
||||
|
||||
import org.example.ProtoLog;
|
||||
import org.example.ProtoLogGroup;
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
|
||||
}
|
||||
}
|
||||
"""
|
||||
groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
|
||||
visitor.process(StaticJavaParser.parse(code), processor)
|
||||
checkCalls()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun process_importedStatic() {
|
||||
val code = """
|
||||
package org.example2;
|
||||
|
||||
import static org.example.ProtoLog.d;
|
||||
import static org.example.ProtoLogGroup.TEST;
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
d(TEST, "test %b", true);
|
||||
}
|
||||
}
|
||||
"""
|
||||
groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
|
||||
visitor.process(StaticJavaParser.parse(code), processor)
|
||||
checkCalls()
|
||||
}
|
||||
|
||||
@Test(expected = InvalidProtoLogCallException::class)
|
||||
fun process_groupNotImported() {
|
||||
val code = """
|
||||
package org.example2;
|
||||
|
||||
import org.example.ProtoLog;
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
|
||||
}
|
||||
}
|
||||
"""
|
||||
groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
|
||||
visitor.process(StaticJavaParser.parse(code), processor)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun process_protoLogNotImported() {
|
||||
val code = """
|
||||
package org.example2;
|
||||
|
||||
import org.example.ProtoLogGroup;
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
|
||||
}
|
||||
}
|
||||
"""
|
||||
groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
|
||||
visitor.process(StaticJavaParser.parse(code), processor)
|
||||
assertEquals(0, calls.size)
|
||||
}
|
||||
|
||||
@Test(expected = InvalidProtoLogCallException::class)
|
||||
fun process_unknownGroup() {
|
||||
val code = """
|
||||
package org.example;
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
|
||||
}
|
||||
}
|
||||
"""
|
||||
visitor.process(StaticJavaParser.parse(code), processor)
|
||||
}
|
||||
|
||||
@Test(expected = InvalidProtoLogCallException::class)
|
||||
fun process_staticGroup() {
|
||||
val code = """
|
||||
package org.example;
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
ProtoLog.d(TEST, "test %b", true);
|
||||
}
|
||||
}
|
||||
"""
|
||||
visitor.process(StaticJavaParser.parse(code), processor)
|
||||
}
|
||||
|
||||
@Test(expected = InvalidProtoLogCallException::class)
|
||||
fun process_badGroup() {
|
||||
val code = """
|
||||
package org.example;
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
ProtoLog.d(0, "test %b", true);
|
||||
}
|
||||
}
|
||||
"""
|
||||
visitor.process(StaticJavaParser.parse(code), processor)
|
||||
}
|
||||
|
||||
@Test(expected = InvalidProtoLogCallException::class)
|
||||
fun process_invalidSignature() {
|
||||
val code = """
|
||||
package org.example;
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
ProtoLog.d("test");
|
||||
}
|
||||
}
|
||||
"""
|
||||
visitor.process(StaticJavaParser.parse(code), processor)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun process_disabled() {
|
||||
// Disabled groups are also processed.
|
||||
val code = """
|
||||
package org.example;
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
|
||||
}
|
||||
}
|
||||
"""
|
||||
groupMap["TEST"] = LogGroup("TEST", false, true, "WindowManager")
|
||||
visitor.process(StaticJavaParser.parse(code), processor)
|
||||
checkCalls()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.protologtool
|
||||
|
||||
import com.github.javaparser.StaticJavaParser
|
||||
import com.github.javaparser.ast.CompilationUnit
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr
|
||||
import com.github.javaparser.ast.stmt.IfStmt
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito
|
||||
|
||||
class SourceTransformerTest {
|
||||
companion object {
|
||||
private const val PROTO_LOG_IMPL_PATH = "org.example.ProtoLogImpl"
|
||||
private val TEST_CODE = """
|
||||
package org.example;
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1);
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
private val TEST_CODE_MULTILINE = """
|
||||
package org.example;
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
ProtoLog.w(TEST_GROUP, "test %d %f " +
|
||||
"abc %s\n test", 100,
|
||||
0.1, "test");
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
private val TEST_CODE_NO_PARAMS = """
|
||||
package org.example;
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
ProtoLog.w(TEST_GROUP, "test");
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
/* ktlint-disable max-line-length */
|
||||
private val TRANSFORMED_CODE_TEXT_ENABLED = """
|
||||
package org.example;
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 835524026, 9, "test %d %f", protoLogParam0, protoLogParam1); }
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
private val TRANSFORMED_CODE_MULTILINE_TEXT_ENABLED = """
|
||||
package org.example;
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -986393606, 9, "test %d %f " + "abc %s\n test", protoLogParam0, protoLogParam1, protoLogParam2);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
private val TRANSFORMED_CODE_NO_PARAMS = """
|
||||
package org.example;
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
if (TEST_GROUP.isLogToAny()) { org.example.ProtoLogImpl.w(TEST_GROUP, 1282022424, 0, "test", (Object[]) null); }
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
private val TRANSFORMED_CODE_TEXT_DISABLED = """
|
||||
package org.example;
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 835524026, 9, null, protoLogParam0, protoLogParam1); }
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
private val TRANSFORMED_CODE_MULTILINE_TEXT_DISABLED = """
|
||||
package org.example;
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -986393606, 9, null, protoLogParam0, protoLogParam1, protoLogParam2);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
private val TRANSFORMED_CODE_DISABLED = """
|
||||
package org.example;
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
if (false) { /* TEST_GROUP is disabled */ ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); }
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
private val TRANSFORMED_CODE_MULTILINE_DISABLED = """
|
||||
package org.example;
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
if (false) { /* TEST_GROUP is disabled */ ProtoLog.w(TEST_GROUP, "test %d %f " + "abc %s\n test", 100, 0.1, "test");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
/* ktlint-enable max-line-length */
|
||||
}
|
||||
|
||||
private val processor: ProtoLogCallProcessor = Mockito.mock(ProtoLogCallProcessor::class.java)
|
||||
private val sourceJarWriter = SourceTransformer("org.example.ProtoLogImpl", processor)
|
||||
|
||||
private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
|
||||
|
||||
@Test
|
||||
fun processClass_textEnabled() {
|
||||
val code = StaticJavaParser.parse(TEST_CODE)
|
||||
|
||||
Mockito.`when`(processor.process(any(CompilationUnit::class.java),
|
||||
any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
|
||||
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
|
||||
|
||||
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
|
||||
LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
|
||||
|
||||
invocation.arguments[0] as CompilationUnit
|
||||
}
|
||||
|
||||
val out = sourceJarWriter.processClass(code)
|
||||
|
||||
val ifStmts = code.findAll(IfStmt::class.java)
|
||||
assertEquals(1, ifStmts.size)
|
||||
val ifStmt = ifStmts[0]
|
||||
assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
|
||||
ifStmt.condition.toString())
|
||||
assertFalse(ifStmt.elseStmt.isPresent)
|
||||
assertEquals(3, ifStmt.thenStmt.childNodes.size)
|
||||
val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
|
||||
assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
|
||||
assertEquals("w", methodCall.name.asString())
|
||||
assertEquals(6, methodCall.arguments.size)
|
||||
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
|
||||
assertEquals("835524026", methodCall.arguments[1].toString())
|
||||
assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
|
||||
assertEquals("\"test %d %f\"", methodCall.arguments[3].toString())
|
||||
assertEquals("protoLogParam0", methodCall.arguments[4].toString())
|
||||
assertEquals("protoLogParam1", methodCall.arguments[5].toString())
|
||||
assertEquals(TRANSFORMED_CODE_TEXT_ENABLED, out)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun processClass_textEnabledMultiline() {
|
||||
val code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
|
||||
|
||||
Mockito.`when`(processor.process(any(CompilationUnit::class.java),
|
||||
any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
|
||||
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
|
||||
|
||||
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
|
||||
"test %d %f abc %s\n test", LogLevel.WARN, LogGroup("TEST_GROUP",
|
||||
true, true, "WM_TEST"))
|
||||
|
||||
invocation.arguments[0] as CompilationUnit
|
||||
}
|
||||
|
||||
val out = sourceJarWriter.processClass(code)
|
||||
|
||||
val ifStmts = code.findAll(IfStmt::class.java)
|
||||
assertEquals(1, ifStmts.size)
|
||||
val ifStmt = ifStmts[0]
|
||||
assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
|
||||
ifStmt.condition.toString())
|
||||
assertFalse(ifStmt.elseStmt.isPresent)
|
||||
assertEquals(4, ifStmt.thenStmt.childNodes.size)
|
||||
val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[1] as MethodCallExpr
|
||||
assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
|
||||
assertEquals("w", methodCall.name.asString())
|
||||
assertEquals(7, methodCall.arguments.size)
|
||||
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
|
||||
assertEquals("-986393606", methodCall.arguments[1].toString())
|
||||
assertEquals(0b001001.toString(), methodCall.arguments[2].toString())
|
||||
assertEquals("protoLogParam0", methodCall.arguments[4].toString())
|
||||
assertEquals("protoLogParam1", methodCall.arguments[5].toString())
|
||||
assertEquals("protoLogParam2", methodCall.arguments[6].toString())
|
||||
assertEquals(TRANSFORMED_CODE_MULTILINE_TEXT_ENABLED, out)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun processClass_noParams() {
|
||||
val code = StaticJavaParser.parse(TEST_CODE_NO_PARAMS)
|
||||
|
||||
Mockito.`when`(processor.process(any(CompilationUnit::class.java),
|
||||
any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
|
||||
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
|
||||
|
||||
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test",
|
||||
LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
|
||||
|
||||
invocation.arguments[0] as CompilationUnit
|
||||
}
|
||||
|
||||
val out = sourceJarWriter.processClass(code)
|
||||
|
||||
val ifStmts = code.findAll(IfStmt::class.java)
|
||||
assertEquals(1, ifStmts.size)
|
||||
val ifStmt = ifStmts[0]
|
||||
assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
|
||||
ifStmt.condition.toString())
|
||||
assertFalse(ifStmt.elseStmt.isPresent)
|
||||
assertEquals(1, ifStmt.thenStmt.childNodes.size)
|
||||
val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
|
||||
assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
|
||||
assertEquals("w", methodCall.name.asString())
|
||||
assertEquals(5, methodCall.arguments.size)
|
||||
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
|
||||
assertEquals("1282022424", methodCall.arguments[1].toString())
|
||||
assertEquals(0.toString(), methodCall.arguments[2].toString())
|
||||
assertEquals(TRANSFORMED_CODE_NO_PARAMS, out)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun processClass_textDisabled() {
|
||||
val code = StaticJavaParser.parse(TEST_CODE)
|
||||
|
||||
Mockito.`when`(processor.process(any(CompilationUnit::class.java),
|
||||
any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
|
||||
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
|
||||
|
||||
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
|
||||
LogLevel.WARN, LogGroup("TEST_GROUP", true, false, "WM_TEST"))
|
||||
|
||||
invocation.arguments[0] as CompilationUnit
|
||||
}
|
||||
|
||||
val out = sourceJarWriter.processClass(code)
|
||||
|
||||
val ifStmts = code.findAll(IfStmt::class.java)
|
||||
assertEquals(1, ifStmts.size)
|
||||
val ifStmt = ifStmts[0]
|
||||
assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
|
||||
ifStmt.condition.toString())
|
||||
assertFalse(ifStmt.elseStmt.isPresent)
|
||||
assertEquals(3, ifStmt.thenStmt.childNodes.size)
|
||||
val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
|
||||
assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
|
||||
assertEquals("w", methodCall.name.asString())
|
||||
assertEquals(6, methodCall.arguments.size)
|
||||
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
|
||||
assertEquals("835524026", methodCall.arguments[1].toString())
|
||||
assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
|
||||
assertEquals("null", methodCall.arguments[3].toString())
|
||||
assertEquals("protoLogParam0", methodCall.arguments[4].toString())
|
||||
assertEquals("protoLogParam1", methodCall.arguments[5].toString())
|
||||
assertEquals(TRANSFORMED_CODE_TEXT_DISABLED, out)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun processClass_textDisabledMultiline() {
|
||||
val code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
|
||||
|
||||
Mockito.`when`(processor.process(any(CompilationUnit::class.java),
|
||||
any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
|
||||
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
|
||||
|
||||
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
|
||||
"test %d %f abc %s\n test", LogLevel.WARN, LogGroup("TEST_GROUP",
|
||||
true, false, "WM_TEST"))
|
||||
|
||||
invocation.arguments[0] as CompilationUnit
|
||||
}
|
||||
|
||||
val out = sourceJarWriter.processClass(code)
|
||||
|
||||
val ifStmts = code.findAll(IfStmt::class.java)
|
||||
assertEquals(1, ifStmts.size)
|
||||
val ifStmt = ifStmts[0]
|
||||
assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
|
||||
ifStmt.condition.toString())
|
||||
assertFalse(ifStmt.elseStmt.isPresent)
|
||||
assertEquals(4, ifStmt.thenStmt.childNodes.size)
|
||||
val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[1] as MethodCallExpr
|
||||
assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
|
||||
assertEquals("w", methodCall.name.asString())
|
||||
assertEquals(7, methodCall.arguments.size)
|
||||
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
|
||||
assertEquals("-986393606", methodCall.arguments[1].toString())
|
||||
assertEquals(0b001001.toString(), methodCall.arguments[2].toString())
|
||||
assertEquals("null", methodCall.arguments[3].toString())
|
||||
assertEquals("protoLogParam0", methodCall.arguments[4].toString())
|
||||
assertEquals("protoLogParam1", methodCall.arguments[5].toString())
|
||||
assertEquals("protoLogParam2", methodCall.arguments[6].toString())
|
||||
assertEquals(TRANSFORMED_CODE_MULTILINE_TEXT_DISABLED, out)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun processClass_disabled() {
|
||||
val code = StaticJavaParser.parse(TEST_CODE)
|
||||
|
||||
Mockito.`when`(processor.process(any(CompilationUnit::class.java),
|
||||
any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
|
||||
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
|
||||
|
||||
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
|
||||
LogLevel.WARN, LogGroup("TEST_GROUP", false, true, "WM_TEST"))
|
||||
|
||||
invocation.arguments[0] as CompilationUnit
|
||||
}
|
||||
|
||||
val out = sourceJarWriter.processClass(code)
|
||||
|
||||
val ifStmts = code.findAll(IfStmt::class.java)
|
||||
assertEquals(1, ifStmts.size)
|
||||
val ifStmt = ifStmts[0]
|
||||
assertEquals("false", ifStmt.condition.toString())
|
||||
assertEquals(TRANSFORMED_CODE_DISABLED, out)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun processClass_disabledMultiline() {
|
||||
val code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
|
||||
|
||||
Mockito.`when`(processor.process(any(CompilationUnit::class.java),
|
||||
any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
|
||||
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
|
||||
|
||||
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
|
||||
"test %d %f abc %s\n test", LogLevel.WARN, LogGroup("TEST_GROUP",
|
||||
false, true, "WM_TEST"))
|
||||
|
||||
invocation.arguments[0] as CompilationUnit
|
||||
}
|
||||
|
||||
val out = sourceJarWriter.processClass(code)
|
||||
|
||||
val ifStmts = code.findAll(IfStmt::class.java)
|
||||
assertEquals(1, ifStmts.size)
|
||||
val ifStmt = ifStmts[0]
|
||||
assertEquals("false", ifStmt.condition.toString())
|
||||
assertEquals(TRANSFORMED_CODE_MULTILINE_DISABLED, out)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.protologtool
|
||||
|
||||
import com.android.json.stream.JsonReader
|
||||
import com.github.javaparser.ast.CompilationUnit
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito
|
||||
import java.io.StringReader
|
||||
|
||||
class ViewerConfigBuilderTest {
|
||||
companion object {
|
||||
private val TAG1 = "WM_TEST"
|
||||
private val TAG2 = "WM_DEBUG"
|
||||
private val TEST1 = ViewerConfigParser.ConfigEntry("test1", LogLevel.INFO.name, TAG1)
|
||||
private val TEST2 = ViewerConfigParser.ConfigEntry("test2", LogLevel.DEBUG.name, TAG2)
|
||||
private val TEST3 = ViewerConfigParser.ConfigEntry("test3", LogLevel.ERROR.name, TAG2)
|
||||
}
|
||||
|
||||
private val processor: ProtoLogCallProcessor = Mockito.mock(ProtoLogCallProcessor::class.java)
|
||||
private val configBuilder = ViewerConfigBuilder(processor)
|
||||
private val dummyCompilationUnit = CompilationUnit()
|
||||
|
||||
private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
|
||||
|
||||
private fun parseConfig(json: String): Map<Int, ViewerConfigParser.ConfigEntry> {
|
||||
return ViewerConfigParser().parseConfig(JsonReader(StringReader(json)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun processClass() {
|
||||
Mockito.`when`(processor.process(any(CompilationUnit::class.java),
|
||||
any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
|
||||
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
|
||||
|
||||
visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,
|
||||
LogGroup("TEST_GROUP", true, true, TAG1))
|
||||
visitor.processCall(MethodCallExpr(), TEST2.messageString, LogLevel.DEBUG,
|
||||
LogGroup("DEBUG_GROUP", true, true, TAG2))
|
||||
visitor.processCall(MethodCallExpr(), TEST3.messageString, LogLevel.ERROR,
|
||||
LogGroup("DEBUG_GROUP", true, true, TAG2))
|
||||
|
||||
invocation.arguments[0] as CompilationUnit
|
||||
}
|
||||
|
||||
configBuilder.processClass(dummyCompilationUnit)
|
||||
|
||||
val parsedConfig = parseConfig(configBuilder.build())
|
||||
assertEquals(3, parsedConfig.size)
|
||||
assertEquals(TEST1, parsedConfig[CodeUtils.hash(TEST1.messageString,
|
||||
LogLevel.INFO)])
|
||||
assertEquals(TEST2, parsedConfig[CodeUtils.hash(TEST2.messageString,
|
||||
LogLevel.DEBUG)])
|
||||
assertEquals(TEST3, parsedConfig[CodeUtils.hash(TEST3.messageString,
|
||||
LogLevel.ERROR)])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun processClass_nonUnique() {
|
||||
Mockito.`when`(processor.process(any(CompilationUnit::class.java),
|
||||
any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
|
||||
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
|
||||
|
||||
visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,
|
||||
LogGroup("TEST_GROUP", true, true, TAG1))
|
||||
visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,
|
||||
LogGroup("TEST_GROUP", true, true, TAG1))
|
||||
visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,
|
||||
LogGroup("TEST_GROUP", true, true, TAG1))
|
||||
|
||||
invocation.arguments[0] as CompilationUnit
|
||||
}
|
||||
|
||||
configBuilder.processClass(dummyCompilationUnit)
|
||||
|
||||
val parsedConfig = parseConfig(configBuilder.build())
|
||||
assertEquals(1, parsedConfig.size)
|
||||
assertEquals(TEST1, parsedConfig[CodeUtils.hash(TEST1.messageString, LogLevel.INFO)])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun processClass_disabled() {
|
||||
Mockito.`when`(processor.process(any(CompilationUnit::class.java),
|
||||
any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
|
||||
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
|
||||
|
||||
visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,
|
||||
LogGroup("TEST_GROUP", true, true, TAG1))
|
||||
visitor.processCall(MethodCallExpr(), TEST2.messageString, LogLevel.DEBUG,
|
||||
LogGroup("DEBUG_GROUP", false, true, TAG2))
|
||||
visitor.processCall(MethodCallExpr(), TEST3.messageString, LogLevel.ERROR,
|
||||
LogGroup("DEBUG_GROUP", true, false, TAG2))
|
||||
|
||||
invocation.arguments[0] as CompilationUnit
|
||||
}
|
||||
|
||||
configBuilder.processClass(dummyCompilationUnit)
|
||||
|
||||
val parsedConfig = parseConfig(configBuilder.build())
|
||||
assertEquals(2, parsedConfig.size)
|
||||
assertEquals(TEST1, parsedConfig[CodeUtils.hash(TEST1.messageString, LogLevel.INFO)])
|
||||
assertEquals(TEST3, parsedConfig[CodeUtils.hash(TEST3.messageString, LogLevel.ERROR)])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.protologtool
|
||||
|
||||
import com.android.json.stream.JsonReader
|
||||
import org.junit.Test
|
||||
import java.io.StringReader
|
||||
import org.junit.Assert.assertEquals
|
||||
|
||||
class ViewerConfigParserTest {
|
||||
private val parser = ViewerConfigParser()
|
||||
|
||||
private fun getJSONReader(str: String): JsonReader {
|
||||
return JsonReader(StringReader(str))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseMessage() {
|
||||
val json = """
|
||||
{
|
||||
"message": "Test completed successfully: %b",
|
||||
"level": "ERROR",
|
||||
"group": "GENERIC_WM"
|
||||
}
|
||||
"""
|
||||
val msg = parser.parseMessage(getJSONReader(json))
|
||||
assertEquals("Test completed successfully: %b", msg.messageString)
|
||||
assertEquals("ERROR", msg.level)
|
||||
assertEquals("GENERIC_WM", msg.groupName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseMessage_reorder() {
|
||||
val json = """
|
||||
{
|
||||
"group": "GENERIC_WM",
|
||||
"level": "ERROR",
|
||||
"message": "Test completed successfully: %b"
|
||||
}
|
||||
"""
|
||||
val msg = parser.parseMessage(getJSONReader(json))
|
||||
assertEquals("Test completed successfully: %b", msg.messageString)
|
||||
assertEquals("ERROR", msg.level)
|
||||
assertEquals("GENERIC_WM", msg.groupName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseMessage_unknownEntry() {
|
||||
val json = """
|
||||
{
|
||||
"unknown": "unknown entries should not block parsing",
|
||||
"message": "Test completed successfully: %b",
|
||||
"level": "ERROR",
|
||||
"group": "GENERIC_WM"
|
||||
}
|
||||
"""
|
||||
val msg = parser.parseMessage(getJSONReader(json))
|
||||
assertEquals("Test completed successfully: %b", msg.messageString)
|
||||
assertEquals("ERROR", msg.level)
|
||||
assertEquals("GENERIC_WM", msg.groupName)
|
||||
}
|
||||
|
||||
@Test(expected = InvalidViewerConfigException::class)
|
||||
fun parseMessage_noMessage() {
|
||||
val json = """
|
||||
{
|
||||
"level": "ERROR",
|
||||
"group": "GENERIC_WM"
|
||||
}
|
||||
"""
|
||||
parser.parseMessage(getJSONReader(json))
|
||||
}
|
||||
|
||||
@Test(expected = InvalidViewerConfigException::class)
|
||||
fun parseMessage_noLevel() {
|
||||
val json = """
|
||||
{
|
||||
"message": "Test completed successfully: %b",
|
||||
"group": "GENERIC_WM"
|
||||
}
|
||||
"""
|
||||
parser.parseMessage(getJSONReader(json))
|
||||
}
|
||||
|
||||
@Test(expected = InvalidViewerConfigException::class)
|
||||
fun parseMessage_noGroup() {
|
||||
val json = """
|
||||
{
|
||||
"message": "Test completed successfully: %b",
|
||||
"level": "ERROR"
|
||||
}
|
||||
"""
|
||||
parser.parseMessage(getJSONReader(json))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseGroup() {
|
||||
val json = """
|
||||
{
|
||||
"tag": "WindowManager"
|
||||
}
|
||||
"""
|
||||
val group = parser.parseGroup(getJSONReader(json))
|
||||
assertEquals("WindowManager", group.tag)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseGroup_unknownEntry() {
|
||||
val json = """
|
||||
{
|
||||
"unknown": "unknown entries should not block parsing",
|
||||
"tag": "WindowManager"
|
||||
}
|
||||
"""
|
||||
val group = parser.parseGroup(getJSONReader(json))
|
||||
assertEquals("WindowManager", group.tag)
|
||||
}
|
||||
|
||||
@Test(expected = InvalidViewerConfigException::class)
|
||||
fun parseGroup_noTag() {
|
||||
val json = """
|
||||
{
|
||||
}
|
||||
"""
|
||||
parser.parseGroup(getJSONReader(json))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseMessages() {
|
||||
val json = """
|
||||
{
|
||||
"70933285": {
|
||||
"message": "Test completed successfully: %b",
|
||||
"level": "ERROR",
|
||||
"group": "GENERIC_WM"
|
||||
},
|
||||
"1792430067": {
|
||||
"message": "Attempted to add window to a display that does not exist: %d. Aborting.",
|
||||
"level": "WARN",
|
||||
"group": "ERROR_WM"
|
||||
}
|
||||
}
|
||||
"""
|
||||
val messages = parser.parseMessages(getJSONReader(json))
|
||||
assertEquals(2, messages.size)
|
||||
val msg1 =
|
||||
ViewerConfigParser.MessageEntry("Test completed successfully: %b",
|
||||
"ERROR", "GENERIC_WM")
|
||||
val msg2 =
|
||||
ViewerConfigParser.MessageEntry("Attempted to add window to a display that " +
|
||||
"does not exist: %d. Aborting.", "WARN", "ERROR_WM")
|
||||
|
||||
assertEquals(msg1, messages[70933285])
|
||||
assertEquals(msg2, messages[1792430067])
|
||||
}
|
||||
|
||||
@Test(expected = InvalidViewerConfigException::class)
|
||||
fun parseMessages_invalidHash() {
|
||||
val json = """
|
||||
{
|
||||
"invalid": {
|
||||
"message": "Test completed successfully: %b",
|
||||
"level": "ERROR",
|
||||
"group": "GENERIC_WM"
|
||||
}
|
||||
}
|
||||
"""
|
||||
parser.parseMessages(getJSONReader(json))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseGroups() {
|
||||
val json = """
|
||||
{
|
||||
"GENERIC_WM": {
|
||||
"tag": "WindowManager"
|
||||
},
|
||||
"ERROR_WM": {
|
||||
"tag": "WindowManagerError"
|
||||
}
|
||||
}
|
||||
"""
|
||||
val groups = parser.parseGroups(getJSONReader(json))
|
||||
assertEquals(2, groups.size)
|
||||
val grp1 = ViewerConfigParser.GroupEntry("WindowManager")
|
||||
val grp2 = ViewerConfigParser.GroupEntry("WindowManagerError")
|
||||
assertEquals(grp1, groups["GENERIC_WM"])
|
||||
assertEquals(grp2, groups["ERROR_WM"])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseConfig() {
|
||||
val json = """
|
||||
{
|
||||
"version": "${Constants.VERSION}",
|
||||
"messages": {
|
||||
"70933285": {
|
||||
"message": "Test completed successfully: %b",
|
||||
"level": "ERROR",
|
||||
"group": "GENERIC_WM"
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"GENERIC_WM": {
|
||||
"tag": "WindowManager"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
val config = parser.parseConfig(getJSONReader(json))
|
||||
assertEquals(1, config.size)
|
||||
val cfg1 = ViewerConfigParser.ConfigEntry("Test completed successfully: %b",
|
||||
"ERROR", "WindowManager")
|
||||
assertEquals(cfg1, config[70933285])
|
||||
}
|
||||
|
||||
@Test(expected = InvalidViewerConfigException::class)
|
||||
fun parseConfig_invalidVersion() {
|
||||
val json = """
|
||||
{
|
||||
"version": "invalid",
|
||||
"messages": {
|
||||
"70933285": {
|
||||
"message": "Test completed successfully: %b",
|
||||
"level": "ERROR",
|
||||
"group": "GENERIC_WM"
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"GENERIC_WM": {
|
||||
"tag": "WindowManager"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
parser.parseConfig(getJSONReader(json))
|
||||
}
|
||||
|
||||
@Test(expected = InvalidViewerConfigException::class)
|
||||
fun parseConfig_noVersion() {
|
||||
val json = """
|
||||
{
|
||||
"messages": {
|
||||
"70933285": {
|
||||
"message": "Test completed successfully: %b",
|
||||
"level": "ERROR",
|
||||
"group": "GENERIC_WM"
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"GENERIC_WM": {
|
||||
"tag": "WindowManager"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
parser.parseConfig(getJSONReader(json))
|
||||
}
|
||||
|
||||
@Test(expected = InvalidViewerConfigException::class)
|
||||
fun parseConfig_noMessages() {
|
||||
val json = """
|
||||
{
|
||||
"version": "${Constants.VERSION}",
|
||||
"groups": {
|
||||
"GENERIC_WM": {
|
||||
"tag": "WindowManager"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
parser.parseConfig(getJSONReader(json))
|
||||
}
|
||||
|
||||
@Test(expected = InvalidViewerConfigException::class)
|
||||
fun parseConfig_noGroups() {
|
||||
val json = """
|
||||
{
|
||||
"version": "${Constants.VERSION}",
|
||||
"messages": {
|
||||
"70933285": {
|
||||
"message": "Test completed successfully: %b",
|
||||
"level": "ERROR",
|
||||
"group": "GENERIC_WM"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
parser.parseConfig(getJSONReader(json))
|
||||
}
|
||||
|
||||
@Test(expected = InvalidViewerConfigException::class)
|
||||
fun parseConfig_missingGroup() {
|
||||
val json = """
|
||||
{
|
||||
"version": "${Constants.VERSION}",
|
||||
"messages": {
|
||||
"70933285": {
|
||||
"message": "Test completed successfully: %b",
|
||||
"level": "ERROR",
|
||||
"group": "GENERIC_WM"
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"ERROR_WM": {
|
||||
"tag": "WindowManager"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
val config = parser.parseConfig(getJSONReader(json))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user