Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 16a8ea6c authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes Ia2a41432,Ib636515d

* changes:
  ProtoLog: Add end-to-end test
  ProtoLog: Fix broken hash computation
parents 39a3c604 a8685a37
Loading
Loading
Loading
Loading
+53 −25
Original line number Diff line number Diff line
@@ -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,18 +58,18 @@ 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)) {
                        transformer.processClass(text, path, code)
                        transformer.processClass(text, path, packagePath(file, code), code)
                    } else {
                        text
                    }
                } 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,18 +156,15 @@ ${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)
                        val pack = if (code.packageDeclaration.isPresent) code.packageDeclaration
                                .get().nameAsString else ""
                        val newPath = pack.replace('.', '/') + '/' + file.name
                        builder.findLogCalls(code, newPath)
                        builder.findLogCalls(code, path, packagePath(file, code))
                    } 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 {
@@ -177,11 +177,18 @@ ${updates.replaceIndent(" ")}

        executor.shutdown()

        val out = FileOutputStream(command.viewerConfigJsonArg)
        val out = injector.fileOutputStream(command.viewerConfigJsonArg)
        out.write(builder.build().toByteArray())
        out.close()
    }

    private fun packagePath(file: File, code: CompilationUnit): String {
        val pack = if (code.packageDeclaration.isPresent) code.packageDeclaration
                .get().nameAsString else ""
        val packagePath = pack.replace('.', '/') + '/' + file.name
        return packagePath
    }

    private fun read(command: CommandOptions) {
        LogParser(ViewerConfigParser())
                .parse(FileInputStream(command.logProtofileArg),
@@ -190,26 +197,47 @@ ${updates.replaceIndent(" ")}

    @JvmStatic
    fun main(args: Array<String>) {
        try {
            val command = CommandOptions(args)
            invoke(command)
        } catch (ex: InvalidCommandException) {
            println("\n${ex.message}\n")
            showHelpAndExit()
        } catch (ex: CodeProcessingException) {
            println("\n${ex.message}\n")
            exitProcess(1)
        }
    }

    fun invoke(command: CommandOptions) {
        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)
        }
        } catch (ex: InvalidCommandException) {
            println("\n${ex.message}\n")
            showHelpAndExit()
        } catch (ex: CodeProcessingException) {
    }

    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")
            exitProcess(1)
        }
    }

    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)
+10 −5
Original line number Diff line number Diff line
@@ -72,7 +72,7 @@ class SourceTransformer(
        }
        val ifStmt: IfStmt
        if (group.enabled) {
            val hash = CodeUtils.hash(fileName, messageString, level, group)
            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.
@@ -97,7 +97,7 @@ class SourceTransformer(
            if (argTypes.size != call.arguments.size - 2) {
                throw InvalidProtoLogCallException(
                        "Number of arguments (${argTypes.size} does not mach format" +
                                " string in: $call", ParsingContext(fileName, call))
                                " string in: $call", ParsingContext(path, call))
            }
            val blockStmt = BlockStmt()
            if (argTypes.isNotEmpty()) {
@@ -214,18 +214,23 @@ class SourceTransformer(
            StaticJavaParser.parseExpression<FieldAccessExpr>(protoLogCacheClassName)
    private var processedCode: MutableList<String> = mutableListOf()
    private var offsets: IntArray = IntArray(0)
    private var fileName: String = ""
    /** 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 {
        fileName = path
        this.path = path
        this.packagePath = packagePath
        processedCode = code.split('\n').toMutableList()
        offsets = IntArray(processedCode.size)
        protoLogCallProcessor.process(compilationUnit, this, fileName)
        protoLogCallProcessor.process(compilationUnit, this, path)
        return processedCode.joinToString("\n")
    }
}
+8 −4
Original line number Diff line number Diff line
@@ -46,7 +46,11 @@ class ViewerConfigBuilder(
    private val statements: MutableMap<Int, LogCall> = mutableMapOf()
    private val groups: MutableSet<LogGroup> = mutableSetOf()

    fun findLogCalls(unit: CompilationUnit, fileName: String): List<Pair<LogCall, ParsingContext>> {
    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(
@@ -55,12 +59,12 @@ class ViewerConfigBuilder(
                level: LogLevel,
                group: LogGroup
            ) {
                val logCall = LogCall(messageString, level, group, fileName)
                val context = ParsingContext(fileName, call)
                val logCall = LogCall(messageString, level, group, packagePath)
                val context = ParsingContext(path, call)
                calls.add(logCall to context)
            }
        }
        processor.process(unit, visitor, fileName)
        processor.process(unit, visitor, path)

        return calls
    }
+144 −0
Original line number Diff line number Diff line
/*
 * 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
}
+8 −8
Original line number Diff line number Diff line
@@ -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)