Update ProtoLogTool
Generate the impl classes and support writing the viewer config to a proto file
Flag: ACONFIG android.tracing.Flags.perfettoProtolog DEVELOPMENT
Bug: 276432490
Test: atest FrameworksServicesTests
Change-Id: I69956b5ef2b8b48a98860aa3b8579521480d7bc0
diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
index aa30a77..49c70b9 100644
--- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
+++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
@@ -19,6 +19,8 @@
import android.annotation.Nullable;
import android.util.Slog;
+import com.android.internal.protolog.common.ILogger;
+
import org.json.JSONException;
import org.json.JSONObject;
@@ -39,10 +41,10 @@
*/
public class ProtoLogViewerConfigReader {
private static final String TAG = "ProtoLogViewerConfigReader";
- private Map<Integer, String> mLogMessageMap = null;
+ private Map<Long, String> mLogMessageMap = null;
/** Returns message format string for its hash or null if unavailable. */
- public synchronized String getViewerString(int messageHash) {
+ public synchronized String getViewerString(long messageHash) {
if (mLogMessageMap != null) {
return mLogMessageMap.get(messageHash);
} else {
@@ -53,19 +55,19 @@
/**
* Reads the specified viewer configuration file. Does nothing if the config is already loaded.
*/
- public synchronized void loadViewerConfig(PrintWriter pw, String viewerConfigFilename) {
+ public synchronized void loadViewerConfig(ILogger logger, String viewerConfigFilename) {
try {
loadViewerConfig(new GZIPInputStream(new FileInputStream(viewerConfigFilename)));
- logAndPrintln(pw, "Loaded " + mLogMessageMap.size()
+ logger.log("Loaded " + mLogMessageMap.size()
+ " log definitions from " + viewerConfigFilename);
} catch (FileNotFoundException e) {
- logAndPrintln(pw, "Unable to load log definitions: File "
+ logger.log("Unable to load log definitions: File "
+ viewerConfigFilename + " not found." + e);
} catch (IOException e) {
- logAndPrintln(pw, "Unable to load log definitions: IOException while reading "
+ logger.log("Unable to load log definitions: IOException while reading "
+ viewerConfigFilename + ". " + e);
} catch (JSONException e) {
- logAndPrintln(pw, "Unable to load log definitions: JSON parsing exception while reading "
+ logger.log("Unable to load log definitions: JSON parsing exception while reading "
+ viewerConfigFilename + ". " + e);
}
}
@@ -95,7 +97,7 @@
while (it.hasNext()) {
String key = (String) it.next();
try {
- int hash = Integer.parseInt(key);
+ long hash = Long.parseLong(key);
JSONObject val = messages.getJSONObject(key);
String msg = val.getString("message");
mLogMessageMap.put(hash, msg);
diff --git a/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java b/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
new file mode 100644
index 0000000..ffd0d76
--- /dev/null
+++ b/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 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.internal.protolog.common;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.FIELD, ElementType.PARAMETER})
+public @interface ProtoLogToolInjected {
+ enum Value { VIEWER_CONFIG_PATH, LEGACY_OUTPUT_FILE_PATH, LEGACY_VIEWER_CONFIG_PATH }
+
+ Value value();
+}
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
index ae50216..cedf259 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
@@ -98,7 +98,7 @@
@Test
public void loadViewerConfig() {
- mConfig.loadViewerConfig(null, mTestViewerConfig.getAbsolutePath());
+ mConfig.loadViewerConfig(msg -> {}, mTestViewerConfig.getAbsolutePath());
assertEquals("Test completed successfully: %b", mConfig.getViewerString(70933285));
assertEquals("Test 2", mConfig.getViewerString(1352021864));
assertEquals("Window %s is already added", mConfig.getViewerString(409412266));
@@ -107,7 +107,7 @@
@Test
public void loadViewerConfig_invalidFile() {
- mConfig.loadViewerConfig(null, "/tmp/unknown/file/does/not/exist");
+ mConfig.loadViewerConfig(msg -> {}, "/tmp/unknown/file/does/not/exist");
// No exception is thrown.
assertNull(mConfig.getViewerString(1));
}
diff --git a/tools/protologtool/src/com/android/protolog/tool/MethodCallVisitor.kt b/tools/protologtool/src/com/android/protolog/tool/MethodCallVisitor.kt
new file mode 100644
index 0000000..fda6351
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/MethodCallVisitor.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.protolog.tool
+
+import com.github.javaparser.ast.expr.MethodCallExpr
+
+interface MethodCallVisitor {
+ fun processCall(call: MethodCallExpr)
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt
index 9a76a6f..47724b7 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -16,115 +16,13 @@
package com.android.protolog.tool
-import com.android.internal.protolog.common.LogLevel
import com.github.javaparser.ast.CompilationUnit
-import com.github.javaparser.ast.Node
-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>,
+interface ProtoLogCallProcessor {
+ fun process(
+ code: CompilationUnit,
+ logCallVisitor: ProtoLogCallVisitor?,
+ otherCallVisitor: MethodCallVisitor?,
fileName: String
- ): String {
- val context = ParsingContext(fileName, expr)
- return when (expr) {
- is NameExpr -> when {
- expr.nameAsString in staticImports -> expr.nameAsString
- else ->
- throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup: $expr",
- context)
- }
- is FieldAccessExpr -> when {
- expr.scope.toString() == protoLogGroupClassName
- || isClassImported &&
- expr.scope.toString() == protoLogGroupSimpleClassName -> expr.nameAsString
- else ->
- throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup: $expr",
- context)
- }
- else -> throw InvalidProtoLogCallException("Invalid group argument " +
- "- must be ProtoLogGroup enum member reference: $expr", context)
- }
- }
-
- 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?, fileName: String):
- CompilationUnit {
- CodeUtils.checkWildcardStaticImported(code, protoLogClassName, fileName)
- CodeUtils.checkWildcardStaticImported(code, protoLogGroupClassName, fileName)
-
- 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 ->
- val context = ParsingContext(fileName, call)
- if (call.arguments.size < 2) {
- throw InvalidProtoLogCallException("Method signature does not match " +
- "any ProtoLog method: $call", context)
- }
-
- val messageString = CodeUtils.concatMultilineString(call.getArgument(1),
- context)
- val groupNameArg = call.getArgument(0)
- val groupName =
- getLogGroupName(groupNameArg, isGroupClassImported,
- staticGroupImports, fileName)
- if (groupName !in groupMap) {
- throw InvalidProtoLogCallException("Unknown group argument " +
- "- not a ProtoLogGroup enum member: $call", context)
- }
-
- callVisitor?.processCall(call, messageString, getLevelForMethodName(
- call.name.toString(), call, context), groupMap.getValue(groupName))
- }
- return code
- }
-
- companion object {
- fun getLevelForMethodName(name: String, node: Node, context: ParsingContext): LogLevel {
- return when (name) {
- "d" -> LogLevel.DEBUG
- "v" -> LogLevel.VERBOSE
- "i" -> LogLevel.INFO
- "w" -> LogLevel.WARN
- "e" -> LogLevel.ERROR
- "wtf" -> LogLevel.WTF
- else ->
- throw InvalidProtoLogCallException("Unknown log level $name in $node", context)
- }
- }
- }
+ ): CompilationUnit
}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
new file mode 100644
index 0000000..1087ae6
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.protolog.tool
+
+import com.android.internal.protolog.common.LogLevel
+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.
+ */
+class ProtoLogCallProcessorImpl(
+ private val protoLogClassName: String,
+ private val protoLogGroupClassName: String,
+ private val groupMap: Map<String, LogGroup>
+) : ProtoLogCallProcessor {
+ private val protoLogSimpleClassName = protoLogClassName.substringAfterLast('.')
+ private val protoLogGroupSimpleClassName = protoLogGroupClassName.substringAfterLast('.')
+
+ private fun getLogGroupName(
+ expr: Expression,
+ isClassImported: Boolean,
+ staticImports: Set<String>,
+ fileName: String
+ ): String {
+ val context = ParsingContext(fileName, expr)
+ return when (expr) {
+ is NameExpr -> when {
+ expr.nameAsString in staticImports -> expr.nameAsString
+ else ->
+ throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup: $expr",
+ context)
+ }
+ is FieldAccessExpr -> when {
+ expr.scope.toString() == protoLogGroupClassName || isClassImported &&
+ expr.scope.toString() == protoLogGroupSimpleClassName -> expr.nameAsString
+ else ->
+ throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup: $expr",
+ context)
+ }
+ else -> throw InvalidProtoLogCallException("Invalid group argument " +
+ "- must be ProtoLogGroup enum member reference: $expr", context)
+ }
+ }
+
+ 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())
+ }
+
+ fun process(code: CompilationUnit, logCallVisitor: ProtoLogCallVisitor?, fileName: String):
+ CompilationUnit {
+ return process(code, logCallVisitor, null, fileName)
+ }
+
+ override fun process(
+ code: CompilationUnit,
+ logCallVisitor: ProtoLogCallVisitor?,
+ otherCallVisitor: MethodCallVisitor?,
+ fileName: String
+ ): CompilationUnit {
+ CodeUtils.checkWildcardStaticImported(code, protoLogClassName, fileName)
+ CodeUtils.checkWildcardStaticImported(code, protoLogGroupClassName, fileName)
+
+ 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 ->
+ val context = ParsingContext(fileName, call)
+
+ val logMethods = LogLevel.entries.map { it.shortCode }
+ if (logMethods.contains(call.name.id)) {
+ // Process a log call
+ if (call.arguments.size < 2) {
+ throw InvalidProtoLogCallException("Method signature does not match " +
+ "any ProtoLog method: $call", context)
+ }
+
+ val messageString = CodeUtils.concatMultilineString(call.getArgument(1),
+ context)
+ val groupNameArg = call.getArgument(0)
+ val groupName =
+ getLogGroupName(groupNameArg, isGroupClassImported,
+ staticGroupImports, fileName)
+ if (groupName !in groupMap) {
+ throw InvalidProtoLogCallException("Unknown group argument " +
+ "- not a ProtoLogGroup enum member: $call", context)
+ }
+
+ logCallVisitor?.processCall(call, messageString, getLevelForMethodName(
+ call.name.toString(), call, context), groupMap.getValue(groupName))
+ } else {
+ // Process non-log message calls
+ otherCallVisitor?.processCall(call)
+ }
+ }
+ return code
+ }
+
+ private fun getLevelForMethodName(
+ name: String,
+ node: MethodCallExpr,
+ context: ParsingContext
+ ): LogLevel = when (name) {
+ "d" -> LogLevel.DEBUG
+ "v" -> LogLevel.VERBOSE
+ "i" -> LogLevel.INFO
+ "w" -> LogLevel.WARN
+ "e" -> LogLevel.ERROR
+ "wtf" -> LogLevel.WTF
+ else ->
+ throw InvalidProtoLogCallException("Unknown log level $name in $node", context)
+ }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index ce856cd..1381847 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -16,13 +16,22 @@
package com.android.protolog.tool
+import com.android.internal.protolog.common.LogLevel
+import com.android.internal.protolog.common.ProtoLog
+import com.android.internal.protolog.common.ProtoLogToolInjected
import com.android.protolog.tool.CommandOptions.Companion.USAGE
import com.github.javaparser.ParseProblemException
import com.github.javaparser.ParserConfiguration
import com.github.javaparser.StaticJavaParser
import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
+import com.github.javaparser.ast.expr.MethodCallExpr
+import com.github.javaparser.ast.expr.NullLiteralExpr
+import com.github.javaparser.ast.expr.SimpleName
+import com.github.javaparser.ast.expr.StringLiteralExpr
import java.io.File
import java.io.FileInputStream
+import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.OutputStream
import java.time.LocalDateTime
@@ -30,9 +39,21 @@
import java.util.concurrent.Executors
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
+import kotlin.math.abs
+import kotlin.random.Random
import kotlin.system.exitProcess
object ProtoLogTool {
+ const val PROTOLOG_IMPL_SRC_PATH =
+ "frameworks/base/core/java/com/android/internal/protolog/ProtoLogImpl.java"
+
+ data class LogCall(
+ val messageString: String,
+ val logLevel: LogLevel,
+ val logGroup: LogGroup,
+ val position: String
+ )
+
private fun showHelpAndExit() {
println(USAGE)
exitProcess(-1)
@@ -51,26 +72,40 @@
}
private fun processClasses(command: CommandOptions) {
+ val generationHash = abs(Random.nextInt())
+ // Need to generate a new impl class to inject static constants into the class.
+ val generatedProtoLogImplClass =
+ "com.android.internal.protolog.ProtoLogImpl_$generationHash"
+
val groups = injector.readLogGroups(
command.protoLogGroupsJarArg,
command.protoLogGroupsClassNameArg)
val out = injector.fileOutputStream(command.outputSourceJarArg)
val outJar = JarOutputStream(out)
- val processor = ProtoLogCallProcessor(command.protoLogClassNameArg,
- command.protoLogGroupsClassNameArg, groups)
+ val processor = ProtoLogCallProcessorImpl(
+ command.protoLogClassNameArg,
+ command.protoLogGroupsClassNameArg,
+ groups)
+
+ val protologImplName = generatedProtoLogImplClass.split(".").last()
+ val protologImplPath = "gen/${generatedProtoLogImplClass.split(".")
+ .joinToString("/")}.java"
+ outJar.putNextEntry(zipEntry(protologImplPath))
+
+ outJar.write(generateProtoLogImpl(protologImplName, command.viewerConfigFilePathArg,
+ command.legacyViewerConfigFilePathArg, command.legacyOutputFilePath).toByteArray())
val executor = newThreadPool()
try {
command.javaSourceArgs.map { path ->
executor.submitCallable {
- val transformer = SourceTransformer(command.protoLogImplClassNameArg,
- command.protoLogCacheClassNameArg, processor)
+ val transformer = SourceTransformer(generatedProtoLogImplClass, processor)
val file = File(path)
val text = injector.readText(file)
val outSrc = try {
val code = tryParse(text, path)
- if (containsProtoLogText(text, command.protoLogClassNameArg)) {
+ if (containsProtoLogText(text, ProtoLog::class.java.simpleName)) {
transformer.processClass(text, path, packagePath(file, code), code)
} else {
text
@@ -93,51 +128,77 @@
executor.shutdown()
}
- val cacheSplit = command.protoLogCacheClassNameArg.split(".")
- val cacheName = cacheSplit.last()
- val cachePackage = cacheSplit.dropLast(1).joinToString(".")
- val cachePath = "gen/${cacheSplit.joinToString("/")}.java"
-
- outJar.putNextEntry(zipEntry(cachePath))
- outJar.write(generateLogGroupCache(cachePackage, cacheName, groups,
- command.protoLogImplClassNameArg, command.protoLogGroupsClassNameArg).toByteArray())
-
outJar.close()
out.close()
}
- fun generateLogGroupCache(
- cachePackage: String,
- cacheName: String,
- groups: Map<String, LogGroup>,
- protoLogImplClassName: String,
- protoLogGroupsClassName: String
+ private fun generateProtoLogImpl(
+ protoLogImplGenName: String,
+ viewerConfigFilePath: String,
+ legacyViewerConfigFilePath: String?,
+ legacyOutputFilePath: String?,
): String {
- val fields = groups.values.map {
- "public static boolean ${it.name}_enabled = false;"
- }.joinToString("\n")
+ val file = File(PROTOLOG_IMPL_SRC_PATH)
- val updates = groups.values.map {
- "${it.name}_enabled = " +
- "$protoLogImplClassName.isEnabled($protoLogGroupsClassName.${it.name});"
- }.joinToString("\n")
+ val text = try {
+ injector.readText(file)
+ } catch (e: FileNotFoundException) {
+ throw RuntimeException("Expected to find '$PROTOLOG_IMPL_SRC_PATH' but file was not " +
+ "included in source for the ProtoLog Tool to process.")
+ }
- return """
- package $cachePackage;
+ val code = tryParse(text, PROTOLOG_IMPL_SRC_PATH)
- public class $cacheName {
-${fields.replaceIndent(" ")}
+ val classDeclarations = code.findAll(ClassOrInterfaceDeclaration::class.java)
+ require(classDeclarations.size == 1) { "Expected exactly one class declaration" }
+ val classDeclaration = classDeclarations[0]
- static {
- $protoLogImplClassName.sCacheUpdater = $cacheName::update;
- update();
- }
+ val classNameNode = classDeclaration.findFirst(SimpleName::class.java).get()
+ classNameNode.setId(protoLogImplGenName)
- static void update() {
-${updates.replaceIndent(" ")}
- }
- }
- """.trimIndent()
+ injectConstants(classDeclaration,
+ viewerConfigFilePath, legacyViewerConfigFilePath, legacyOutputFilePath)
+
+ return code.toString()
+ }
+
+ private fun injectConstants(
+ classDeclaration: ClassOrInterfaceDeclaration,
+ viewerConfigFilePath: String,
+ legacyViewerConfigFilePath: String?,
+ legacyOutputFilePath: String?
+ ) {
+ classDeclaration.fields.forEach { field ->
+ field.getAnnotationByClass(ProtoLogToolInjected::class.java)
+ .ifPresent { annotationExpr ->
+ if (annotationExpr.isSingleMemberAnnotationExpr) {
+ val valueName = annotationExpr.asSingleMemberAnnotationExpr()
+ .memberValue.asNameExpr().name.asString()
+ when (valueName) {
+ ProtoLogToolInjected.Value.VIEWER_CONFIG_PATH.name -> {
+ field.setFinal(true)
+ field.variables.first()
+ .setInitializer(StringLiteralExpr(viewerConfigFilePath))
+ }
+ ProtoLogToolInjected.Value.LEGACY_OUTPUT_FILE_PATH.name -> {
+ field.setFinal(true)
+ field.variables.first()
+ .setInitializer(legacyOutputFilePath?.let {
+ StringLiteralExpr(it)
+ } ?: NullLiteralExpr())
+ }
+ ProtoLogToolInjected.Value.LEGACY_VIEWER_CONFIG_PATH.name -> {
+ field.setFinal(true)
+ field.variables.first()
+ .setInitializer(legacyViewerConfigFilePath?.let {
+ StringLiteralExpr(it)
+ } ?: NullLiteralExpr())
+ }
+ else -> error("Unhandled ProtoLogToolInjected value: $valueName.")
+ }
+ }
+ }
+ }
}
private fun tryParse(code: String, fileName: String): CompilationUnit {
@@ -145,24 +206,53 @@
return StaticJavaParser.parse(code)
} catch (ex: ParseProblemException) {
val problem = ex.problems.first()
- throw ParsingException("Java parsing erro" +
- "r: ${problem.verboseMessage}",
+ throw ParsingException("Java parsing error: ${problem.verboseMessage}",
ParsingContext(fileName, problem.location.orElse(null)
?.begin?.range?.orElse(null)?.begin?.line
?: 0))
}
}
+ class LogCallRegistry {
+ private val statements = mutableMapOf<LogCall, Long>()
+
+ fun addLogCalls(calls: List<LogCall>) {
+ calls.forEach { logCall ->
+ if (logCall.logGroup.enabled) {
+ statements.putIfAbsent(logCall,
+ CodeUtils.hash(logCall.position, logCall.messageString,
+ logCall.logLevel, logCall.logGroup))
+ }
+ }
+ }
+
+ fun getStatements(): Map<LogCall, Long> {
+ return statements
+ }
+ }
+
+ interface ProtologViewerConfigBuilder {
+ fun build(statements: Map<LogCall, Long>): ByteArray
+ }
+
private fun viewerConf(command: CommandOptions) {
val groups = injector.readLogGroups(
command.protoLogGroupsJarArg,
command.protoLogGroupsClassNameArg)
- val processor = ProtoLogCallProcessor(command.protoLogClassNameArg,
+ val processor = ProtoLogCallProcessorImpl(command.protoLogClassNameArg,
command.protoLogGroupsClassNameArg, groups)
- val builder = ViewerConfigBuilder(processor)
+ val outputType = command.viewerConfigTypeArg
+
+ val configBuilder: ProtologViewerConfigBuilder = when (outputType.lowercase()) {
+ "json" -> ViewerConfigJsonBuilder()
+ "proto" -> ViewerConfigProtoBuilder()
+ else -> error("Invalid output type provide. Provided '$outputType'.")
+ }
val executor = newThreadPool()
+ val logCallRegistry = LogCallRegistry()
+
try {
command.javaSourceArgs.map { path ->
executor.submitCallable {
@@ -171,7 +261,7 @@
if (containsProtoLogText(text, command.protoLogClassNameArg)) {
try {
val code = tryParse(text, path)
- builder.findLogCalls(code, path, packagePath(file, code))
+ findLogCalls(code, path, packagePath(file, code), processor)
} catch (ex: ParsingException) {
// If we cannot parse this file, skip it (and log why). Compilation will
// fail in a subsequent build step.
@@ -183,15 +273,38 @@
}
}
}.forEach { future ->
- builder.addLogCalls(future.get() ?: return@forEach)
+ logCallRegistry.addLogCalls(future.get() ?: return@forEach)
}
} finally {
executor.shutdown()
}
- val out = injector.fileOutputStream(command.viewerConfigJsonArg)
- out.write(builder.build().toByteArray())
- out.close()
+ val outFile = injector.fileOutputStream(command.viewerConfigFileNameArg)
+ outFile.write(configBuilder.build(logCallRegistry.getStatements()))
+ outFile.close()
+ }
+
+ private fun findLogCalls(
+ unit: CompilationUnit,
+ path: String,
+ packagePath: String,
+ processor: ProtoLogCallProcessorImpl
+ ): List<LogCall> {
+ val calls = mutableListOf<LogCall>()
+ val logCallVisitor = object : ProtoLogCallVisitor {
+ override fun processCall(
+ call: MethodCallExpr,
+ messageString: String,
+ level: LogLevel,
+ group: LogGroup
+ ) {
+ val logCall = LogCall(messageString, level, group, packagePath)
+ calls.add(logCall)
+ }
+ }
+ processor.process(unit, logCallVisitor, path)
+
+ return calls
}
private fun packagePath(file: File, code: CompilationUnit): String {
@@ -204,7 +317,7 @@
private fun read(command: CommandOptions) {
LogParser(ViewerConfigParser())
.parse(FileInputStream(command.logProtofileArg),
- FileInputStream(command.viewerConfigJsonArg), System.out)
+ FileInputStream(command.viewerConfigFileNameArg), System.out)
}
@JvmStatic
diff --git a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
index b50f357..2b71641 100644
--- a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
@@ -22,11 +22,11 @@
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.Expression
import com.github.javaparser.ast.expr.FieldAccessExpr
import com.github.javaparser.ast.expr.IntegerLiteralExpr
+import com.github.javaparser.ast.expr.LongLiteralExpr
import com.github.javaparser.ast.expr.MethodCallExpr
import com.github.javaparser.ast.expr.NameExpr
import com.github.javaparser.ast.expr.NullLiteralExpr
@@ -35,7 +35,6 @@
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.ast.type.ClassOrInterfaceType
import com.github.javaparser.ast.type.PrimitiveType
@@ -45,15 +44,59 @@
class SourceTransformer(
protoLogImplClassName: String,
- protoLogCacheClassName: String,
private val protoLogCallProcessor: ProtoLogCallProcessor
-) : ProtoLogCallVisitor {
- override fun processCall(
- call: MethodCallExpr,
- messageString: String,
- level: LogLevel,
- group: LogGroup
- ) {
+) {
+ 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)
+ }
+
+ fun processClass(
+ code: String,
+ path: String,
+ packagePath: String,
+ compilationUnit: CompilationUnit =
+ StaticJavaParser.parse(code)
+ ): String {
+ this.path = path
+ this.packagePath = packagePath
+ processedCode = code.split('\n').toMutableList()
+ offsets = IntArray(processedCode.size)
+ protoLogCallProcessor.process(compilationUnit, protoLogCallVisitor, otherCallVisitor, path)
+ return processedCode.joinToString("\n")
+ }
+
+ private val protoLogImplClassNode =
+ StaticJavaParser.parseExpression<FieldAccessExpr>(protoLogImplClassName)
+ private var processedCode: MutableList<String> = mutableListOf()
+ private var offsets: IntArray = IntArray(0)
+ /** The path of the file being processed, relative to $ANDROID_BUILD_TOP */
+ private var path: String = ""
+ /** The path of the file being processed, relative to the root package */
+ private var packagePath: String = ""
+
+ private val protoLogCallVisitor = object : ProtoLogCallVisitor {
+ override fun processCall(
+ call: MethodCallExpr,
+ messageString: String,
+ level: LogLevel,
+ group: LogGroup
+ ) {
+ validateCall(call)
+ val processedCallStatement =
+ createProcessedCallStatement(call, group, level, messageString)
+ val parentStmt = call.parentNode.get() as ExpressionStmt
+ injectProcessedCallStatementInCode(processedCallStatement, parentStmt)
+ }
+ }
+
+ private fun validateCall(call: MethodCallExpr) {
// Input format: ProtoLog.e(GROUP, "msg %d", arg)
if (!call.parentNode.isPresent) {
// Should never happen
@@ -71,89 +114,79 @@
throw RuntimeException("Unable to process log call $call " +
"- no grandparent node in AST")
}
- val ifStmt: IfStmt
- if (group.enabled) {
- val hash = CodeUtils.hash(packagePath, messageString, level, group)
- 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 = LogDataType.parseFormatString(messageString)
- val typeMask = LogDataType.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: ProtoLogImpl.e(GROUP, 1234, null, arg)
- newCall.setScope(protoLogImplClassNode)
- // Create a call to ProtoLog$Cache.GROUP_enabled
- // Out: com.android.server.protolog.ProtoLog$Cache.GROUP_enabled
- val isLogEnabled = FieldAccessExpr(protoLogCacheClassNode, "${group.name}_enabled")
- if (argTypes.size != call.arguments.size - 2) {
- throw InvalidProtoLogCallException(
- "Number of arguments (${argTypes.size} does not mach format" +
- " string in: $call", ParsingContext(path, 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(getASTTypeForDataType(type), varName,
- getConversionForType(type)(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 (ProtoLogImpl.isEnabled(GROUP)) {
- // long protoLogParam0 = arg;
- // ProtoLogImpl.e(GROUP, 1234, 0, null, protoLogParam0);
- // }
- ifStmt = IfStmt(isLogEnabled, 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 ")
+ }
+
+ private fun createProcessedCallStatement(
+ call: MethodCallExpr,
+ group: LogGroup,
+ level: LogLevel,
+ messageString: String
+ ): BlockStmt {
+ val hash = CodeUtils.hash(packagePath, messageString, level, group)
+ 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, LongLiteralExpr("" + hash + "L"))
+ val argTypes = LogDataType.parseFormatString(messageString)
+ val typeMask = LogDataType.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: ProtoLogImpl.e(GROUP, 1234, null, arg)
+ newCall.setScope(protoLogImplClassNode)
+ if (argTypes.size != call.arguments.size - 2) {
+ throw InvalidProtoLogCallException(
+ "Number of arguments (${argTypes.size} does not match format" +
+ " string in: $call", ParsingContext(path, 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(getASTTypeForDataType(type), varName,
+ getConversionForType(type)(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))
+
+ return blockStmt
+ }
+
+ private fun injectProcessedCallStatementInCode(
+ processedCallStatement: BlockStmt,
+ parentStmt: ExpressionStmt
+ ) {
// Inline the new statement.
- val printedIfStmt = inlinePrinter.print(ifStmt)
+ val printedBlockStmt = inlinePrinter.print(processedCallStatement)
// Append blank lines to preserve line numbering in file (to allow debugging)
val parentRange = parentStmt.range.get()
val newLines = parentRange.end.line - parentRange.begin.line
- val newStmt = printedIfStmt.substringBeforeLast('}') + ("\n".repeat(newLines)) + '}'
+ val newStmt = printedBlockStmt.substringBeforeLast('}') + ("\n".repeat(newLines)) + '}'
// pre-workaround code, see explanation below
- /*
- 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.")
- }
- */
+
/** Workaround for a bug in JavaParser (AST tree invalid after replacing a node when using
* LexicalPreservingPrinter (https://github.com/javaparser/javaparser/issues/2290).
* Replace the code below with the one commended-out above one the issue is resolved. */
if (!parentStmt.range.isPresent) {
// Should never happen
- throw RuntimeException("Unable to process log call $call " +
+ throw RuntimeException("Unable to process log call in $parentStmt " +
"- unable to replace the call.")
}
val range = parentStmt.range.get()
@@ -161,29 +194,38 @@
val oldLines = processedCode.subList(begin, range.end.line)
val oldCode = oldLines.joinToString("\n")
val newCode = oldCode.replaceRange(
- offsets[begin] + range.begin.column - 1,
- oldCode.length - oldLines.lastOrNull()!!.length +
- range.end.column + offsets[range.end.line - 1], newStmt)
+ offsets[begin] + range.begin.column - 1,
+ oldCode.length - oldLines.lastOrNull()!!.length +
+ range.end.column + offsets[range.end.line - 1], newStmt)
newCode.split("\n").forEachIndexed { idx, line ->
offsets[begin + idx] += line.length - processedCode[begin + idx].length
processedCode[begin + idx] = line
}
}
- private val inlinePrinter: PrettyPrinter
- private val objectType = StaticJavaParser.parseClassOrInterfaceType("Object")
+ private val otherCallVisitor = object : MethodCallVisitor {
+ override fun processCall(call: MethodCallExpr) {
+ val newCall = call.clone()
+ newCall.setScope(protoLogImplClassNode)
- init {
- val config = PrettyPrinterConfiguration()
- config.endOfLineCharacter = " "
- config.indentSize = 0
- config.tabWidth = 1
- inlinePrinter = PrettyPrinter(config)
+ val range = call.range.get()
+ val begin = range.begin.line - 1
+ val oldLines = processedCode.subList(begin, range.end.line)
+ val oldCode = oldLines.joinToString("\n")
+ val newCode = oldCode.replaceRange(
+ offsets[begin] + range.begin.column - 1,
+ oldCode.length - oldLines.lastOrNull()!!.length +
+ range.end.column + offsets[range.end.line - 1], newCall.toString())
+ newCode.split("\n").forEachIndexed { idx, line ->
+ offsets[begin + idx] += line.length - processedCode[begin + idx].length
+ processedCode[begin + idx] = line
+ }
+ }
}
companion object {
private val stringType: ClassOrInterfaceType =
- StaticJavaParser.parseClassOrInterfaceType("String")
+ StaticJavaParser.parseClassOrInterfaceType("String")
fun getASTTypeForDataType(type: Int): Type {
return when (type) {
@@ -202,36 +244,10 @@
return when (type) {
LogDataType.STRING -> { expr ->
MethodCallExpr(TypeExpr(StaticJavaParser.parseClassOrInterfaceType("String")),
- SimpleName("valueOf"), NodeList(expr))
+ SimpleName("valueOf"), NodeList(expr))
}
else -> { expr -> expr }
}
}
}
-
- private val protoLogImplClassNode =
- StaticJavaParser.parseExpression<FieldAccessExpr>(protoLogImplClassName)
- private val protoLogCacheClassNode =
- StaticJavaParser.parseExpression<FieldAccessExpr>(protoLogCacheClassName)
- private var processedCode: MutableList<String> = mutableListOf()
- private var offsets: IntArray = IntArray(0)
- /** The path of the file being processed, relative to $ANDROID_BUILD_TOP */
- private var path: String = ""
- /** The path of the file being processed, relative to the root package */
- private var packagePath: String = ""
-
- fun processClass(
- code: String,
- path: String,
- packagePath: String,
- compilationUnit: CompilationUnit =
- StaticJavaParser.parse(code)
- ): String {
- this.path = path
- this.packagePath = packagePath
- processedCode = code.split('\n').toMutableList()
- offsets = IntArray(processedCode.size)
- protoLogCallProcessor.process(compilationUnit, this, path)
- return processedCode.joinToString("\n")
- }
}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt
deleted file mode 100644
index 0d5d022..0000000
--- a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * 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.protolog.tool
-
-import com.android.internal.protolog.common.LogLevel
-import com.android.json.stream.JsonWriter
-import com.github.javaparser.ast.CompilationUnit
-import com.android.protolog.tool.Constants.VERSION
-import com.github.javaparser.ast.expr.MethodCallExpr
-import java.io.StringWriter
-
-class ViewerConfigBuilder(
- private val processor: ProtoLogCallProcessor
-) {
- private fun addLogCall(logCall: LogCall, context: ParsingContext) {
- val group = logCall.logGroup
- val messageString = logCall.messageString
- if (group.enabled) {
- val key = logCall.key()
- if (statements.containsKey(key)) {
- if (statements[key] != logCall) {
- throw HashCollisionException(
- "Please modify the log message \"$messageString\" " +
- "or \"${statements[key]}\" - their hashes are equal.", context)
- }
- } else {
- groups.add(group)
- statements[key] = logCall
- }
- }
- }
-
- private val statements: MutableMap<Int, LogCall> = mutableMapOf()
- private val groups: MutableSet<LogGroup> = mutableSetOf()
-
- fun findLogCalls(
- unit: CompilationUnit,
- path: String,
- packagePath: String
- ): List<Pair<LogCall, ParsingContext>> {
- val calls = mutableListOf<Pair<LogCall, ParsingContext>>()
- val visitor = object : ProtoLogCallVisitor {
- override fun processCall(
- call: MethodCallExpr,
- messageString: String,
- level: LogLevel,
- group: LogGroup
- ) {
- val logCall = LogCall(messageString, level, group, packagePath)
- val context = ParsingContext(path, call)
- calls.add(logCall to context)
- }
- }
- processor.process(unit, visitor, path)
-
- return calls
- }
-
- fun addLogCalls(calls: List<Pair<LogCall, ParsingContext>>) {
- calls.forEach { (logCall, context) ->
- addLogCall(logCall, context)
- }
- }
-
- 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.messageString)
- writer.name("level")
- writer.value(value.logLevel.name)
- writer.name("group")
- writer.value(value.logGroup.name)
- writer.name("at")
- writer.value(value.position)
- 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()
- }
-
- data class LogCall(
- val messageString: String,
- val logLevel: LogLevel,
- val logGroup: LogGroup,
- val position: String
- ) {
- fun key() = CodeUtils.hash(position, messageString, logLevel, logGroup)
- }
-}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigJsonBuilder.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigJsonBuilder.kt
new file mode 100644
index 0000000..7714db2
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigJsonBuilder.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 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.protolog.tool
+
+import com.android.json.stream.JsonWriter
+import com.android.protolog.tool.Constants.VERSION
+import java.io.StringWriter
+
+class ViewerConfigJsonBuilder : ProtoLogTool.ProtologViewerConfigBuilder {
+ override fun build(statements: Map<ProtoLogTool.LogCall, Long>): ByteArray {
+ val groups = statements.map { it.key.logGroup }.toSet()
+ val stringWriter = StringWriter()
+ val writer = JsonWriter(stringWriter)
+ writer.setIndent(" ")
+ writer.beginObject()
+ writer.name("version")
+ writer.value(VERSION)
+ writer.name("messages")
+ writer.beginObject()
+ statements.forEach { (log, key) ->
+ writer.name(key.toString())
+ writer.beginObject()
+ writer.name("message")
+ writer.value(log.messageString)
+ writer.name("level")
+ writer.value(log.logLevel.name)
+ writer.name("group")
+ writer.value(log.logGroup.name)
+ writer.name("at")
+ writer.value(log.position)
+ writer.endObject()
+ }
+ writer.endObject()
+ writer.name("groups")
+ writer.beginObject()
+ groups.toSortedSet { 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().toByteArray()
+ }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigParser.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigParser.kt
index 7278db0..58be3a3 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigParser.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigParser.kt
@@ -63,12 +63,12 @@
return GroupEntry(tag)
}
- fun parseMessages(jsonReader: JsonReader): Map<Int, MessageEntry> {
- val config: MutableMap<Int, MessageEntry> = mutableMapOf()
+ fun parseMessages(jsonReader: JsonReader): Map<Long, MessageEntry> {
+ val config: MutableMap<Long, MessageEntry> = mutableMapOf()
jsonReader.beginObject()
while (jsonReader.hasNext()) {
val key = jsonReader.nextName()
- val hash = key.toIntOrNull()
+ val hash = key.toLongOrNull()
?: throw InvalidViewerConfigException("Invalid key in messages viewer config")
config[hash] = parseMessage(jsonReader)
}
@@ -89,8 +89,8 @@
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
+ open fun parseConfig(jsonReader: JsonReader): Map<Long, ConfigEntry> {
+ var messages: Map<Long, MessageEntry>? = null
var groups: Map<String, GroupEntry>? = null
var version: String? = null
diff --git a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt
new file mode 100644
index 0000000..cf0876a
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.protolog.tool
+
+import perfetto.protos.PerfettoTrace.ProtoLogLevel
+import perfetto.protos.PerfettoTrace.ProtoLogViewerConfig
+
+/**
+ * A builder class to construct the viewer configuration (i.e. mappings of protolog hashes to log
+ * message information used to decode the protolog messages) encoded as a proto message.
+ */
+class ViewerConfigProtoBuilder : ProtoLogTool.ProtologViewerConfigBuilder {
+ /**
+ * @return a byte array of a ProtoLogViewerConfig proto message encoding all the viewer
+ * configurations mapping protolog hashes to message information and log group information.
+ */
+ override fun build(statements: Map<ProtoLogTool.LogCall, Long>): ByteArray {
+ val configBuilder = ProtoLogViewerConfig.newBuilder()
+
+ val groups = statements.map { it.key.logGroup }.toSet()
+ val groupIds = mutableMapOf<LogGroup, Int>()
+ groups.forEach {
+ groupIds.putIfAbsent(it, groupIds.size + 1)
+ }
+
+ groupIds.forEach { (group, id) ->
+ configBuilder.addGroups(ProtoLogViewerConfig.Group.newBuilder()
+ .setId(id)
+ .setName(group.name)
+ .setTag(group.tag)
+ .build())
+ }
+
+ statements.forEach { (log, key) ->
+ val groupId = groupIds[log.logGroup] ?: error("missing group id")
+
+ configBuilder.addMessages(
+ ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(key)
+ .setMessage(log.messageString)
+ .setLevel(
+ ProtoLogLevel.forNumber(log.logLevel.ordinal + 1))
+ .setGroupId(groupId)
+ )
+ }
+
+ return configBuilder.build().toByteArray()
+ }
+}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
index c9bcbe5..822118c 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
@@ -16,14 +16,16 @@
package com.android.protolog.tool
+import com.android.protolog.tool.ProtoLogTool.PROTOLOG_IMPL_SRC_PATH
+import com.google.common.truth.Truth
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileNotFoundException
import java.io.OutputStream
import java.util.jar.JarInputStream
+import java.util.regex.Pattern
import org.junit.Assert
-import org.junit.Assert.assertTrue
import org.junit.Test
class EndToEndTest {
@@ -31,7 +33,7 @@
@Test
fun e2e_transform() {
val output = run(
- src = "frameworks/base/org/example/Example.java" to """
+ srcs = mapOf("frameworks/base/org/example/Example.java" to """
package org.example;
import com.android.internal.protolog.common.ProtoLog;
import static com.android.internal.protolog.ProtoLogGroup.GROUP;
@@ -43,7 +45,7 @@
ProtoLog.d(GROUP, "Example: %s %d", argString, argInt);
}
}
- """.trimIndent(),
+ """.trimIndent()),
logGroup = LogGroup("GROUP", true, false, "TAG_GROUP"),
commandOptions = CommandOptions(arrayOf("transform-protolog-calls",
"--protolog-class", "com.android.internal.protolog.common.ProtoLog",
@@ -54,13 +56,18 @@
"frameworks/base/org/example/Example.java"))
)
val outSrcJar = assertLoadSrcJar(output, "out.srcjar")
- assertTrue(" 2066303299," in outSrcJar["frameworks/base/org/example/Example.java"]!!)
+ Truth.assertThat(outSrcJar["frameworks/base/org/example/Example.java"])
+ .containsMatch(Pattern.compile("\\{ String protoLogParam0 = " +
+ "String\\.valueOf\\(argString\\); long protoLogParam1 = argInt; " +
+ "com\\.android\\.internal\\.protolog.ProtoLogImpl_.*\\.d\\(" +
+ "GROUP, -6872339441335321086L, 4, null, protoLogParam0, protoLogParam1" +
+ "\\); \\}"))
}
@Test
fun e2e_viewerConfig() {
val output = run(
- src = "frameworks/base/org/example/Example.java" to """
+ srcs = mapOf("frameworks/base/org/example/Example.java" to """
package org.example;
import com.android.internal.protolog.common.ProtoLog;
import static com.android.internal.protolog.ProtoLogGroup.GROUP;
@@ -72,7 +79,7 @@
ProtoLog.d(GROUP, "Example: %s %d", argString, argInt);
}
}
- """.trimIndent(),
+ """.trimIndent()),
logGroup = LogGroup("GROUP", true, false, "TAG_GROUP"),
commandOptions = CommandOptions(arrayOf("generate-viewer-config",
"--protolog-class", "com.android.internal.protolog.common.ProtoLog",
@@ -83,7 +90,16 @@
"frameworks/base/org/example/Example.java"))
)
val viewerConfigJson = assertLoadText(output, "out.json")
- assertTrue("\"2066303299\"" in viewerConfigJson)
+ Truth.assertThat(viewerConfigJson).contains("""
+ "messages": {
+ "-6872339441335321086": {
+ "message": "Example: %s %d",
+ "level": "DEBUG",
+ "group": "GROUP",
+ "at": "org\/example\/Example.java"
+ }
+ }
+ """.trimIndent())
}
private fun assertLoadSrcJar(
@@ -111,21 +127,46 @@
}
fun run(
- src: Pair<String, String>,
+ srcs: Map<String, String>,
logGroup: LogGroup,
commandOptions: CommandOptions
): Map<String, ByteArray> {
val outputs = mutableMapOf<String, ByteArrayOutputStream>()
+ val srcs = srcs.toMutableMap()
+ srcs[PROTOLOG_IMPL_SRC_PATH] = """
+ package com.android.internal.protolog;
+
+ import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LEGACY_OUTPUT_FILE_PATH;
+ import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LEGACY_VIEWER_CONFIG_PATH;
+ import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.VIEWER_CONFIG_PATH;
+
+ import com.android.internal.protolog.common.ProtoLogToolInjected;
+
+ public class ProtoLogImpl {
+ @ProtoLogToolInjected(VIEWER_CONFIG_PATH)
+ private static String sViewerConfigPath;
+
+ @ProtoLogToolInjected(LEGACY_VIEWER_CONFIG_PATH)
+ private static String sLegacyViewerConfigPath;
+
+ @ProtoLogToolInjected(LEGACY_OUTPUT_FILE_PATH)
+ private static String sLegacyOutputFilePath;
+ }
+ """.trimIndent()
+
ProtoLogTool.injector = object : ProtoLogTool.Injector {
override fun fileOutputStream(file: String): OutputStream =
ByteArrayOutputStream().also { outputs[file] = it }
override fun readText(file: File): String {
- if (file.path == src.first) {
- return src.second
+ for (src in srcs.entries) {
+ val filePath = src.key
+ if (file.path == filePath) {
+ return src.value
+ }
}
- throw FileNotFoundException("expected: ${src.first}, but was $file")
+ throw FileNotFoundException("$file not found in [${srcs.keys.joinToString()}].")
}
override fun readLogGroups(jarPath: String, className: String) = mapOf(
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorImplTest.kt
similarity index 96%
rename from tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorTest.kt
rename to tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorImplTest.kt
index 90b8059..5e50f71 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorImplTest.kt
@@ -22,7 +22,7 @@
import org.junit.Assert.assertEquals
import org.junit.Test
-class ProtoLogCallProcessorTest {
+class ProtoLogCallProcessorImplTest {
private data class LogCall(
val call: MethodCallExpr,
val messageString: String,
@@ -32,8 +32,11 @@
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 visitor = ProtoLogCallProcessorImpl(
+ "org.example.ProtoLog",
+ "org.example.ProtoLogGroup",
+ groupMap
+ )
private val processor = object : ProtoLogCallVisitor {
override fun processCall(
call: MethodCallExpr,
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ProtoLogToolTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogToolTest.kt
deleted file mode 100644
index ea9a58d..0000000
--- a/tools/protologtool/tests/com/android/protolog/tool/ProtoLogToolTest.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.protolog.tool
-
-import org.junit.Assert.assertEquals
-import org.junit.Test
-
-class ProtoLogToolTest {
-
- @Test
- fun generateLogGroupCache() {
- val groups = mapOf(
- "GROUP1" to LogGroup("GROUP1", true, true, "TAG1"),
- "GROUP2" to LogGroup("GROUP2", true, true, "TAG2")
- )
- val code = ProtoLogTool.generateLogGroupCache("org.example", "ProtoLog\$Cache",
- groups, "org.example.ProtoLogImpl", "org.example.ProtoLogGroups")
-
- assertEquals("""
- package org.example;
-
- public class ProtoLog${'$'}Cache {
- public static boolean GROUP1_enabled = false;
- public static boolean GROUP2_enabled = false;
-
- static {
- org.example.ProtoLogImpl.sCacheUpdater = ProtoLog${'$'}Cache::update;
- update();
- }
-
- static void update() {
- GROUP1_enabled = org.example.ProtoLogImpl.isEnabled(org.example.ProtoLogGroups.GROUP1);
- GROUP2_enabled = org.example.ProtoLogImpl.isEnabled(org.example.ProtoLogGroups.GROUP2);
- }
- }
- """.trimIndent(), code)
- }
-}
\ No newline at end of file
diff --git a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
index f52bfec..de0b5ba 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
@@ -20,17 +20,14 @@
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 com.google.common.truth.Truth
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"
- /* ktlint-disable max-line-length */
private val TEST_CODE = """
package org.example;
@@ -79,7 +76,7 @@
class Test {
void test() {
- if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+ { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
}
}
""".trimIndent()
@@ -89,20 +86,20 @@
class Test {
void test() {
- if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, 1780316587, 9, "test %d %f " + "abc %s\n test", protoLogParam0, protoLogParam1, protoLogParam2);
+ { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 9, "test %d %f " + "abc %s\n test", protoLogParam0, protoLogParam1, protoLogParam2);
}
}
}
""".trimIndent()
- private val TRANSFORMED_CODE_MULTICALL_TEXT_ENABLED = """
+ private val TRANSFORMED_CODE_MULTICALL_TEXT = """
package org.example;
class Test {
void test() {
- if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); }
- if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+ { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+ { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
}
}
""".trimIndent()
@@ -112,7 +109,7 @@
class Test {
void test() {
- if (org.example.ProtoLogCache.TEST_GROUP_enabled) { org.example.ProtoLogImpl.w(TEST_GROUP, -1741986185, 0, "test", (Object[]) null); }
+ { org.example.ProtoLogImpl.w(TEST_GROUP, 3218600869538902408L, 0, "test", (Object[]) null); }
}
}
""".trimIndent()
@@ -122,7 +119,7 @@
class Test {
void test() {
- if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, null, protoLogParam0, protoLogParam1); }
+ { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, null, protoLogParam0, protoLogParam1); }
}
}
""".trimIndent()
@@ -132,43 +129,19 @@
class Test {
void test() {
- if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, 1780316587, 9, null, protoLogParam0, protoLogParam1, protoLogParam2);
+ { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 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 const val PATH = "com.example.Test.java"
}
private val processor: ProtoLogCallProcessor = Mockito.mock(ProtoLogCallProcessor::class.java)
private val implName = "org.example.ProtoLogImpl"
- private val cacheName = "org.example.ProtoLogCache"
- private val sourceJarWriter = SourceTransformer(implName, cacheName, processor)
+ private val sourceJarWriter = SourceTransformer(implName, processor)
private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
@@ -176,9 +149,12 @@
fun processClass_textEnabled() {
var code = StaticJavaParser.parse(TEST_CODE)
- Mockito.`when`(processor.process(any(CompilationUnit::class.java),
- any(ProtoLogCallVisitor::class.java), any(String::class.java)))
- .thenAnswer { invocation ->
+ Mockito.`when`(processor.process(
+ any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java),
+ any(MethodCallVisitor::class.java),
+ any(String::class.java))
+ ).thenAnswer { invocation ->
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
@@ -190,18 +166,15 @@
val out = sourceJarWriter.processClass(TEST_CODE, PATH, PATH, code)
code = StaticJavaParser.parse(out)
- val ifStmts = code.findAll(IfStmt::class.java)
- assertEquals(1, ifStmts.size)
- val ifStmt = ifStmts[0]
- assertEquals("$cacheName.TEST_GROUP_enabled", 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())
+ val protoLogCalls = code.findAll(MethodCallExpr::class.java).filter {
+ it.scope.orElse(null)?.toString() == implName
+ }
+ Truth.assertThat(protoLogCalls).hasSize(1)
+ val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
assertEquals(6, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
- assertEquals("1698911065", methodCall.arguments[1].toString())
+ assertEquals("-1473209266730422156L", 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())
@@ -213,9 +186,12 @@
fun processClass_textEnabledMulticalls() {
var code = StaticJavaParser.parse(TEST_CODE_MULTICALLS)
- Mockito.`when`(processor.process(any(CompilationUnit::class.java),
- any(ProtoLogCallVisitor::class.java), any(String::class.java)))
- .thenAnswer { invocation ->
+ Mockito.`when`(processor.process(
+ any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java),
+ any(MethodCallVisitor::class.java),
+ any(String::class.java))
+ ).thenAnswer { invocation ->
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
val calls = code.findAll(MethodCallExpr::class.java)
@@ -232,32 +208,32 @@
val out = sourceJarWriter.processClass(TEST_CODE_MULTICALLS, PATH, PATH, code)
code = StaticJavaParser.parse(out)
- val ifStmts = code.findAll(IfStmt::class.java)
- assertEquals(3, ifStmts.size)
- val ifStmt = ifStmts[1]
- assertEquals("$cacheName.TEST_GROUP_enabled", 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())
+ val protoLogCalls = code.findAll(MethodCallExpr::class.java).filter {
+ it.scope.orElse(null)?.toString() == implName
+ }
+ Truth.assertThat(protoLogCalls).hasSize(3)
+ val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
assertEquals(6, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
- assertEquals("1698911065", methodCall.arguments[1].toString())
+ assertEquals("-1473209266730422156L", 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_MULTICALL_TEXT_ENABLED, out)
+ assertEquals(TRANSFORMED_CODE_MULTICALL_TEXT, out)
}
@Test
fun processClass_textEnabledMultiline() {
var code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
- Mockito.`when`(processor.process(any(CompilationUnit::class.java),
- any(ProtoLogCallVisitor::class.java), any(String::class.java)))
- .thenAnswer { invocation ->
+ Mockito.`when`(processor.process(
+ any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java),
+ any(MethodCallVisitor::class.java),
+ any(String::class.java))
+ ).thenAnswer { invocation ->
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
@@ -270,18 +246,15 @@
val out = sourceJarWriter.processClass(TEST_CODE_MULTILINE, PATH, PATH, code)
code = StaticJavaParser.parse(out)
- val ifStmts = code.findAll(IfStmt::class.java)
- assertEquals(1, ifStmts.size)
- val ifStmt = ifStmts[0]
- assertEquals("$cacheName.TEST_GROUP_enabled", 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())
+ val protoLogCalls = code.findAll(MethodCallExpr::class.java).filter {
+ it.scope.orElse(null)?.toString() == implName
+ }
+ Truth.assertThat(protoLogCalls).hasSize(1)
+ val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
assertEquals(7, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
- assertEquals("1780316587", methodCall.arguments[1].toString())
+ assertEquals("-4447034859795564700L", methodCall.arguments[1].toString())
assertEquals(0b001001.toString(), methodCall.arguments[2].toString())
assertEquals("protoLogParam0", methodCall.arguments[4].toString())
assertEquals("protoLogParam1", methodCall.arguments[5].toString())
@@ -293,9 +266,12 @@
fun processClass_noParams() {
var code = StaticJavaParser.parse(TEST_CODE_NO_PARAMS)
- Mockito.`when`(processor.process(any(CompilationUnit::class.java),
- any(ProtoLogCallVisitor::class.java), any(String::class.java)))
- .thenAnswer { invocation ->
+ Mockito.`when`(processor.process(
+ any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java),
+ any(MethodCallVisitor::class.java),
+ any(String::class.java))
+ ).thenAnswer { invocation ->
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test",
@@ -307,18 +283,15 @@
val out = sourceJarWriter.processClass(TEST_CODE_NO_PARAMS, PATH, PATH, code)
code = StaticJavaParser.parse(out)
- val ifStmts = code.findAll(IfStmt::class.java)
- assertEquals(1, ifStmts.size)
- val ifStmt = ifStmts[0]
- assertEquals("$cacheName.TEST_GROUP_enabled", 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())
+ val protoLogCalls = code.findAll(MethodCallExpr::class.java).filter {
+ it.scope.orElse(null)?.toString() == implName
+ }
+ Truth.assertThat(protoLogCalls).hasSize(1)
+ val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
assertEquals(5, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
- assertEquals("-1741986185", methodCall.arguments[1].toString())
+ assertEquals("3218600869538902408L", methodCall.arguments[1].toString())
assertEquals(0.toString(), methodCall.arguments[2].toString())
assertEquals(TRANSFORMED_CODE_NO_PARAMS, out)
}
@@ -327,9 +300,12 @@
fun processClass_textDisabled() {
var code = StaticJavaParser.parse(TEST_CODE)
- Mockito.`when`(processor.process(any(CompilationUnit::class.java),
- any(ProtoLogCallVisitor::class.java), any(String::class.java)))
- .thenAnswer { invocation ->
+ Mockito.`when`(processor.process(
+ any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java),
+ any(MethodCallVisitor::class.java),
+ any(String::class.java))
+ ).thenAnswer { invocation ->
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
@@ -341,18 +317,15 @@
val out = sourceJarWriter.processClass(TEST_CODE, PATH, PATH, code)
code = StaticJavaParser.parse(out)
- val ifStmts = code.findAll(IfStmt::class.java)
- assertEquals(1, ifStmts.size)
- val ifStmt = ifStmts[0]
- assertEquals("$cacheName.TEST_GROUP_enabled", 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())
+ val protoLogCalls = code.findAll(MethodCallExpr::class.java).filter {
+ it.scope.orElse(null)?.toString() == implName
+ }
+ Truth.assertThat(protoLogCalls).hasSize(1)
+ val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
assertEquals(6, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
- assertEquals("1698911065", methodCall.arguments[1].toString())
+ assertEquals("-1473209266730422156L", methodCall.arguments[1].toString())
assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
assertEquals("null", methodCall.arguments[3].toString())
assertEquals("protoLogParam0", methodCall.arguments[4].toString())
@@ -364,9 +337,12 @@
fun processClass_textDisabledMultiline() {
var code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
- Mockito.`when`(processor.process(any(CompilationUnit::class.java),
- any(ProtoLogCallVisitor::class.java), any(String::class.java)))
- .thenAnswer { invocation ->
+ Mockito.`when`(processor.process(
+ any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java),
+ any(MethodCallVisitor::class.java),
+ any(String::class.java))
+ ).thenAnswer { invocation ->
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
@@ -379,18 +355,15 @@
val out = sourceJarWriter.processClass(TEST_CODE_MULTILINE, PATH, PATH, code)
code = StaticJavaParser.parse(out)
- val ifStmts = code.findAll(IfStmt::class.java)
- assertEquals(1, ifStmts.size)
- val ifStmt = ifStmts[0]
- assertEquals("$cacheName.TEST_GROUP_enabled", 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())
+ val protoLogCalls = code.findAll(MethodCallExpr::class.java).filter {
+ it.scope.orElse(null)?.toString() == implName
+ }
+ Truth.assertThat(protoLogCalls).hasSize(1)
+ val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
assertEquals(7, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
- assertEquals("1780316587", methodCall.arguments[1].toString())
+ assertEquals("-4447034859795564700L", methodCall.arguments[1].toString())
assertEquals(0b001001.toString(), methodCall.arguments[2].toString())
assertEquals("null", methodCall.arguments[3].toString())
assertEquals("protoLogParam0", methodCall.arguments[4].toString())
@@ -398,55 +371,4 @@
assertEquals("protoLogParam2", methodCall.arguments[6].toString())
assertEquals(TRANSFORMED_CODE_MULTILINE_TEXT_DISABLED, out)
}
-
- @Test
- fun processClass_disabled() {
- var code = StaticJavaParser.parse(TEST_CODE)
-
- Mockito.`when`(processor.process(any(CompilationUnit::class.java),
- any(ProtoLogCallVisitor::class.java), any(String::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(TEST_CODE, PATH, PATH, code)
- code = StaticJavaParser.parse(out)
-
- 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() {
- var code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
-
- Mockito.`when`(processor.process(any(CompilationUnit::class.java),
- any(ProtoLogCallVisitor::class.java), any(String::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(TEST_CODE_MULTILINE, PATH, PATH, code)
- code = StaticJavaParser.parse(out)
-
- 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)
- }
}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigBuilderTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigJsonBuilderTest.kt
similarity index 66%
rename from tools/protologtool/tests/com/android/protolog/tool/ViewerConfigBuilderTest.kt
rename to tools/protologtool/tests/com/android/protolog/tool/ViewerConfigJsonBuilderTest.kt
index 52dce21..d27ae88 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigBuilderTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigJsonBuilderTest.kt
@@ -18,13 +18,12 @@
import com.android.internal.protolog.common.LogLevel
import com.android.json.stream.JsonReader
-import com.android.protolog.tool.ViewerConfigBuilder.LogCall
+import com.android.protolog.tool.ProtoLogTool.LogCall
+import java.io.StringReader
import org.junit.Assert.assertEquals
import org.junit.Test
-import org.mockito.Mockito
-import java.io.StringReader
-class ViewerConfigBuilderTest {
+class ViewerConfigJsonBuilderTest {
companion object {
private val TAG1 = "WM_TEST"
private val TAG2 = "WM_DEBUG"
@@ -39,20 +38,22 @@
private const val PATH = "/tmp/test.java"
}
- private val configBuilder = ViewerConfigBuilder(Mockito.mock(ProtoLogCallProcessor::class.java))
+ private val configBuilder = ViewerConfigJsonBuilder()
- private fun parseConfig(json: String): Map<Int, ViewerConfigParser.ConfigEntry> {
+ private fun parseConfig(json: String): Map<Long, ViewerConfigParser.ConfigEntry> {
return ViewerConfigParser().parseConfig(JsonReader(StringReader(json)))
}
@Test
fun processClass() {
- configBuilder.addLogCalls(listOf(
+ val logCallRegistry = ProtoLogTool.LogCallRegistry()
+ logCallRegistry.addLogCalls(listOf(
LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH),
LogCall(TEST2.messageString, LogLevel.DEBUG, GROUP2, PATH),
- LogCall(TEST3.messageString, LogLevel.ERROR, GROUP3, PATH)).withContext())
+ LogCall(TEST3.messageString, LogLevel.ERROR, GROUP3, PATH)))
- val parsedConfig = parseConfig(configBuilder.build())
+ val parsedConfig = parseConfig(
+ configBuilder.build(logCallRegistry.getStatements()).toString(Charsets.UTF_8))
assertEquals(3, parsedConfig.size)
assertEquals(TEST1, parsedConfig[CodeUtils.hash(PATH,
TEST1.messageString, LogLevel.INFO, GROUP1)])
@@ -64,32 +65,16 @@
@Test
fun processClass_nonUnique() {
- configBuilder.addLogCalls(listOf(
+ val logCallRegistry = ProtoLogTool.LogCallRegistry()
+ logCallRegistry.addLogCalls(listOf(
LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH),
LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH),
- LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH)).withContext())
+ LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH)))
- val parsedConfig = parseConfig(configBuilder.build())
+ val parsedConfig = parseConfig(
+ configBuilder.build(logCallRegistry.getStatements()).toString(Charsets.UTF_8))
assertEquals(1, parsedConfig.size)
assertEquals(TEST1, parsedConfig[CodeUtils.hash(PATH, TEST1.messageString,
- LogLevel.INFO, GROUP1)])
+ LogLevel.INFO, GROUP1)])
}
-
- @Test
- fun processClass_disabled() {
- configBuilder.addLogCalls(listOf(
- LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH),
- LogCall(TEST2.messageString, LogLevel.DEBUG, GROUP_DISABLED, PATH),
- LogCall(TEST3.messageString, LogLevel.ERROR, GROUP_TEXT_DISABLED, PATH))
- .withContext())
-
- val parsedConfig = parseConfig(configBuilder.build())
- assertEquals(2, parsedConfig.size)
- assertEquals(TEST1, parsedConfig[CodeUtils.hash(
- PATH, TEST1.messageString, LogLevel.INFO, GROUP1)])
- assertEquals(TEST3, parsedConfig[CodeUtils.hash(
- PATH, TEST3.messageString, LogLevel.ERROR, GROUP_TEXT_DISABLED)])
- }
-
- private fun List<LogCall>.withContext() = map { it to ParsingContext() }
}