ProtoLog: Add end-to-end test

Adds a test that verifies we compute the correct hash end-to-end, i.e. including
the actual path logic, which previously caused a divergence between the viewer config
and the generated code.

Test: atest protologtool-tests
Change-Id: Ia2a414322aa6ccdaedda7f9754d903ec984902d6
This commit is contained in:
Adrian Roos
2019-10-31 13:33:53 +01:00
parent 6599f2dea4
commit a8685a3772
3 changed files with 196 additions and 28 deletions

View File

@@ -24,6 +24,7 @@ import com.github.javaparser.ast.CompilationUnit
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.OutputStream
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.jar.JarOutputStream
@@ -42,9 +43,10 @@ object ProtoLogTool {
}
private fun processClasses(command: CommandOptions) {
val groups = ProtoLogGroupReader()
.loadFromJar(command.protoLogGroupsJarArg, command.protoLogGroupsClassNameArg)
val out = FileOutputStream(command.outputSourceJarArg)
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)
@@ -56,7 +58,7 @@ object ProtoLogTool {
val transformer = SourceTransformer(command.protoLogImplClassNameArg,
command.protoLogCacheClassNameArg, processor)
val file = File(path)
val text = file.readText()
val text = injector.readText(file)
val outSrc = try {
val code = tryParse(text, path)
if (containsProtoLogText(text, command.protoLogClassNameArg)) {
@@ -67,7 +69,7 @@ object ProtoLogTool {
} catch (ex: ParsingException) {
// If we cannot parse this file, skip it (and log why). Compilation will fail
// in a subsequent build step.
println("\n${ex.message}\n")
injector.reportParseError(ex)
text
}
path to outSrc
@@ -142,8 +144,9 @@ ${updates.replaceIndent(" ")}
}
private fun viewerConf(command: CommandOptions) {
val groups = ProtoLogGroupReader()
.loadFromJar(command.protoLogGroupsJarArg, command.protoLogGroupsClassNameArg)
val groups = injector.readLogGroups(
command.protoLogGroupsJarArg,
command.protoLogGroupsClassNameArg)
val processor = ProtoLogCallProcessor(command.protoLogClassNameArg,
command.protoLogGroupsClassNameArg, groups)
val builder = ViewerConfigBuilder(processor)
@@ -153,7 +156,7 @@ ${updates.replaceIndent(" ")}
command.javaSourceArgs.map { path ->
executor.submitCallable {
val file = File(path)
val text = file.readText()
val text = injector.readText(file)
if (containsProtoLogText(text, command.protoLogClassNameArg)) {
try {
val code = tryParse(text, path)
@@ -161,7 +164,7 @@ ${updates.replaceIndent(" ")}
} catch (ex: ParsingException) {
// If we cannot parse this file, skip it (and log why). Compilation will fail
// in a subsequent build step.
println("\n${ex.message}\n")
injector.reportParseError(ex)
null
}
} else {
@@ -174,7 +177,7 @@ ${updates.replaceIndent(" ")}
executor.shutdown()
val out = FileOutputStream(command.viewerConfigJsonArg)
val out = injector.fileOutputStream(command.viewerConfigJsonArg)
out.write(builder.build().toByteArray())
out.close()
}
@@ -194,18 +197,9 @@ ${updates.replaceIndent(" ")}
@JvmStatic
fun main(args: Array<String>) {
StaticJavaParser.setConfiguration(ParserConfiguration().apply {
setLanguageLevel(ParserConfiguration.LanguageLevel.RAW)
setAttributeComments(false)
})
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)
}
invoke(command)
} catch (ex: InvalidCommandException) {
println("\n${ex.message}\n")
showHelpAndExit()
@@ -214,6 +208,36 @@ ${updates.replaceIndent(" ")}
exitProcess(1)
}
}
fun invoke(command: CommandOptions) {
StaticJavaParser.setConfiguration(ParserConfiguration().apply {
setLanguageLevel(ParserConfiguration.LanguageLevel.RAW)
setAttributeComments(false)
})
when (command.command) {
CommandOptions.TRANSFORM_CALLS_CMD -> processClasses(command)
CommandOptions.GENERATE_CONFIG_CMD -> viewerConf(command)
CommandOptions.READ_LOG_CMD -> read(command)
}
}
var injector = object : Injector {
override fun fileOutputStream(file: String) = FileOutputStream(file)
override fun readText(file: File) = file.readText()
override fun readLogGroups(jarPath: String, className: String) =
ProtoLogGroupReader().loadFromJar(jarPath, className)
override fun reportParseError(ex: ParsingException) {
println("\n${ex.message}\n")
}
}
interface Injector {
fun fileOutputStream(file: String): OutputStream
fun readText(file: File): String
fun readLogGroups(jarPath: String, className: String): Map<String, LogGroup>
fun reportParseError(ex: ParsingException)
}
}
private fun <T> ExecutorService.submitCallable(f: () -> T) = submit(f)

View File

@@ -0,0 +1,144 @@
/*
* 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
import org.junit.Assert.assertTrue
import org.junit.Test
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
class EndToEndTest {
@Test
fun e2e_transform() {
val output = run(
src = "frameworks/base/org/example/Example.java" to """
package org.example;
import com.android.server.protolog.common.ProtoLog;
import static com.android.server.wm.ProtoLogGroup.GROUP;
class Example {
void method() {
String argString = "hello";
int argInt = 123;
ProtoLog.d(GROUP, "Example: %s %d", argString, argInt);
}
}
""".trimIndent(),
logGroup = LogGroup("GROUP", true, false, "TAG_GROUP"),
commandOptions = CommandOptions(arrayOf("transform-protolog-calls",
"--protolog-class", "com.android.server.protolog.common.ProtoLog",
"--protolog-impl-class", "com.android.server.protolog.ProtoLogImpl",
"--protolog-cache-class",
"com.android.server.protolog.ProtoLog${"\$\$"}Cache",
"--loggroups-class", "com.android.server.wm.ProtoLogGroup",
"--loggroups-jar", "not_required.jar",
"--output-srcjar", "out.srcjar",
"frameworks/base/org/example/Example.java"))
)
val outSrcJar = assertLoadSrcJar(output, "out.srcjar")
assertTrue(" 2066303299," in outSrcJar["frameworks/base/org/example/Example.java"]!!)
}
@Test
fun e2e_viewerConfig() {
val output = run(
src = "frameworks/base/org/example/Example.java" to """
package org.example;
import com.android.server.protolog.common.ProtoLog;
import static com.android.server.wm.ProtoLogGroup.GROUP;
class Example {
void method() {
String argString = "hello";
int argInt = 123;
ProtoLog.d(GROUP, "Example: %s %d", argString, argInt);
}
}
""".trimIndent(),
logGroup = LogGroup("GROUP", true, false, "TAG_GROUP"),
commandOptions = CommandOptions(arrayOf("generate-viewer-config",
"--protolog-class", "com.android.server.protolog.common.ProtoLog",
"--loggroups-class", "com.android.server.wm.ProtoLogGroup",
"--loggroups-jar", "not_required.jar",
"--viewer-conf", "out.json",
"frameworks/base/org/example/Example.java"))
)
val viewerConfigJson = assertLoadText(output, "out.json")
assertTrue("\"2066303299\"" in viewerConfigJson)
}
private fun assertLoadSrcJar(
outputs: Map<String, ByteArray>,
path: String
): Map<String, String> {
val out = outputs[path] ?: fail("$path not in outputs (${outputs.keys})")
val sources = mutableMapOf<String, String>()
JarInputStream(ByteArrayInputStream(out)).use { jarStream ->
var entry = jarStream.nextJarEntry
while (entry != null) {
if (entry.name.endsWith(".java")) {
sources[entry.name] = jarStream.reader().readText()
}
entry = jarStream.nextJarEntry
}
}
return sources
}
private fun assertLoadText(outputs: Map<String, ByteArray>, path: String): String {
val out = outputs[path] ?: fail("$path not in outputs (${outputs.keys})")
return out.toString(Charsets.UTF_8)
}
fun run(
src: Pair<String, String>,
logGroup: LogGroup,
commandOptions: CommandOptions
): Map<String, ByteArray> {
val outputs = mutableMapOf<String, ByteArrayOutputStream>()
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
}
throw FileNotFoundException("expected: ${src.first}, but was $file")
}
override fun readLogGroups(jarPath: String, className: String) = mapOf(
logGroup.name to logGroup)
override fun reportParseError(ex: ParsingException) = throw AssertionError(ex)
}
ProtoLogTool.invoke(commandOptions)
return outputs.mapValues { it.value.toByteArray() }
}
fun fail(message: String): Nothing = Assert.fail(message) as Nothing
}

View File

@@ -186,7 +186,7 @@ class SourceTransformerTest {
invocation.arguments[0] as CompilationUnit
}
val out = sourceJarWriter.processClass(TEST_CODE, PATH, code)
val out = sourceJarWriter.processClass(TEST_CODE, PATH, PATH, code)
code = StaticJavaParser.parse(out)
val ifStmts = code.findAll(IfStmt::class.java)
@@ -228,7 +228,7 @@ class SourceTransformerTest {
invocation.arguments[0] as CompilationUnit
}
val out = sourceJarWriter.processClass(TEST_CODE_MULTICALLS, PATH, code)
val out = sourceJarWriter.processClass(TEST_CODE_MULTICALLS, PATH, PATH, code)
code = StaticJavaParser.parse(out)
val ifStmts = code.findAll(IfStmt::class.java)
@@ -266,7 +266,7 @@ class SourceTransformerTest {
invocation.arguments[0] as CompilationUnit
}
val out = sourceJarWriter.processClass(TEST_CODE_MULTILINE, PATH, code)
val out = sourceJarWriter.processClass(TEST_CODE_MULTILINE, PATH, PATH, code)
code = StaticJavaParser.parse(out)
val ifStmts = code.findAll(IfStmt::class.java)
@@ -303,7 +303,7 @@ class SourceTransformerTest {
invocation.arguments[0] as CompilationUnit
}
val out = sourceJarWriter.processClass(TEST_CODE_NO_PARAMS, PATH, code)
val out = sourceJarWriter.processClass(TEST_CODE_NO_PARAMS, PATH, PATH, code)
code = StaticJavaParser.parse(out)
val ifStmts = code.findAll(IfStmt::class.java)
@@ -337,7 +337,7 @@ class SourceTransformerTest {
invocation.arguments[0] as CompilationUnit
}
val out = sourceJarWriter.processClass(TEST_CODE, PATH, code)
val out = sourceJarWriter.processClass(TEST_CODE, PATH, PATH, code)
code = StaticJavaParser.parse(out)
val ifStmts = code.findAll(IfStmt::class.java)
@@ -375,7 +375,7 @@ class SourceTransformerTest {
invocation.arguments[0] as CompilationUnit
}
val out = sourceJarWriter.processClass(TEST_CODE_MULTILINE, PATH, code)
val out = sourceJarWriter.processClass(TEST_CODE_MULTILINE, PATH, PATH, code)
code = StaticJavaParser.parse(out)
val ifStmts = code.findAll(IfStmt::class.java)
@@ -413,7 +413,7 @@ class SourceTransformerTest {
invocation.arguments[0] as CompilationUnit
}
val out = sourceJarWriter.processClass(TEST_CODE, PATH, code)
val out = sourceJarWriter.processClass(TEST_CODE, PATH, PATH, code)
code = StaticJavaParser.parse(out)
val ifStmts = code.findAll(IfStmt::class.java)
@@ -439,7 +439,7 @@ class SourceTransformerTest {
invocation.arguments[0] as CompilationUnit
}
val out = sourceJarWriter.processClass(TEST_CODE_MULTILINE, PATH, code)
val out = sourceJarWriter.processClass(TEST_CODE_MULTILINE, PATH, PATH, code)
code = StaticJavaParser.parse(out)
val ifStmts = code.findAll(IfStmt::class.java)