1 /* <lambda>null2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.protolog.tool 18 19 import com.android.protolog.tool.ProtoLogTool.PROTOLOG_IMPL_SRC_PATH 20 import com.android.protolog.tool.ProtoLogTool.injector 21 import com.google.common.truth.Truth 22 import java.io.ByteArrayInputStream 23 import java.io.ByteArrayOutputStream 24 import java.io.File 25 import java.io.FileNotFoundException 26 import java.io.OutputStream 27 import java.util.jar.JarInputStream 28 import java.util.regex.Pattern 29 import org.junit.Assert 30 import org.junit.Test 31 32 class EndToEndTest { 33 34 @Test 35 fun e2e_transform() { 36 val output = run( 37 srcs = mapOf("frameworks/base/org/example/Example.java" to """ 38 package org.example; 39 import com.android.internal.protolog.ProtoLog; 40 import static com.android.internal.protolog.ProtoLogGroup.GROUP; 41 42 class Example { 43 void method() { 44 String argString = "hello"; 45 int argInt = 123; 46 ProtoLog.d(GROUP, "Example: %s %d", argString, argInt); 47 } 48 } 49 """.trimIndent()), 50 logGroup = LogGroup("GROUP", true, false, "TAG_GROUP"), 51 commandOptions = CommandOptions(arrayOf("transform-protolog-calls", 52 "--protolog-class", "com.android.internal.protolog.ProtoLog", 53 "--loggroups-class", "com.android.internal.protolog.ProtoLogGroup", 54 "--loggroups-jar", "not_required.jar", 55 "--viewer-config-file-path", "not_required.pb", 56 "--output-srcjar", "out.srcjar", 57 "frameworks/base/org/example/Example.java")) 58 ) 59 val outSrcJar = assertLoadSrcJar(output, "out.srcjar") 60 Truth.assertThat(outSrcJar["frameworks/base/org/example/Example.java"]) 61 .containsMatch(Pattern.compile("\\{ String protoLogParam0 = " + 62 "String\\.valueOf\\(argString\\); long protoLogParam1 = argInt; " + 63 "com\\.android\\.internal\\.protolog.ProtoLogImpl_.*\\.d\\(" + 64 "GROUP, -6872339441335321086L, 4, protoLogParam0, protoLogParam1" + 65 "\\); \\}")) 66 } 67 68 @Test 69 fun e2e_viewerConfig() { 70 val output = run( 71 srcs = mapOf("frameworks/base/org/example/Example.java" to """ 72 package org.example; 73 import com.android.internal.protolog.ProtoLog; 74 import static com.android.internal.protolog.ProtoLogGroup.GROUP; 75 76 class Example { 77 void method() { 78 String argString = "hello"; 79 int argInt = 123; 80 ProtoLog.d(GROUP, "Example: %s %d", argString, argInt); 81 } 82 } 83 """.trimIndent()), 84 logGroup = LogGroup("GROUP", true, false, "TAG_GROUP"), 85 commandOptions = CommandOptions(arrayOf("generate-viewer-config", 86 "--protolog-class", "com.android.internal.protolog.ProtoLog", 87 "--loggroups-class", "com.android.internal.protolog.ProtoLogGroup", 88 "--loggroups-jar", "not_required.jar", 89 "--viewer-config-type", "json", 90 "--viewer-config", "out.json", 91 "frameworks/base/org/example/Example.java")) 92 ) 93 val viewerConfigJson = assertLoadText(output, "out.json") 94 Truth.assertThat(viewerConfigJson).contains(""" 95 "messages": { 96 "-6872339441335321086": { 97 "message": "Example: %s %d", 98 "level": "DEBUG", 99 "group": "GROUP", 100 "at": "org\/example\/Example.java" 101 } 102 } 103 """.trimIndent()) 104 } 105 106 @Test 107 fun e2e_transform_withErrors() { 108 val srcs = mapOf( 109 "frameworks/base/org/example/Example.java" to """ 110 package org.example; 111 import com.android.internal.protolog.ProtoLog; 112 import static com.android.internal.protolog.ProtoLogGroup.GROUP; 113 114 class Example { 115 void method() { 116 String argString = "hello"; 117 int argInt = 123; 118 ProtoLog.d(GROUP, "Invalid format: %s %d %9 %", argString, argInt); 119 } 120 } 121 """.trimIndent()) 122 val output = run( 123 srcs = srcs, 124 logGroup = LogGroup("GROUP", true, false, "TAG_GROUP"), 125 commandOptions = CommandOptions(arrayOf("transform-protolog-calls", 126 "--protolog-class", "com.android.internal.protolog.ProtoLog", 127 "--loggroups-class", "com.android.internal.protolog.ProtoLogGroup", 128 "--loggroups-jar", "not_required.jar", 129 "--viewer-config-file-path", "not_required.pb", 130 "--output-srcjar", "out.srcjar", 131 "frameworks/base/org/example/Example.java")) 132 ) 133 val outSrcJar = assertLoadSrcJar(output, "out.srcjar") 134 // No change to source code on failure to process 135 Truth.assertThat(outSrcJar["frameworks/base/org/example/Example.java"]) 136 .contains(srcs["frameworks/base/org/example/Example.java"]) 137 138 Truth.assertThat(injector.processingErrors).hasSize(1) 139 Truth.assertThat(injector.processingErrors.first().message).contains("Invalid format") 140 } 141 142 private fun assertLoadSrcJar( 143 outputs: Map<String, ByteArray>, 144 path: String 145 ): Map<String, String> { 146 val out = outputs[path] ?: fail("$path not in outputs (${outputs.keys})") 147 148 val sources = mutableMapOf<String, String>() 149 JarInputStream(ByteArrayInputStream(out)).use { jarStream -> 150 var entry = jarStream.nextJarEntry 151 while (entry != null) { 152 if (entry.name.endsWith(".java")) { 153 sources[entry.name] = jarStream.reader().readText() 154 } 155 entry = jarStream.nextJarEntry 156 } 157 } 158 return sources 159 } 160 161 private fun assertLoadText(outputs: Map<String, ByteArray>, path: String): String { 162 val out = outputs[path] ?: fail("$path not in outputs (${outputs.keys})") 163 return out.toString(Charsets.UTF_8) 164 } 165 166 fun run( 167 srcs: Map<String, String>, 168 logGroup: LogGroup, 169 commandOptions: CommandOptions 170 ): Map<String, ByteArray> { 171 val outputs = mutableMapOf<String, ByteArrayOutputStream>() 172 173 val srcs = srcs.toMutableMap() 174 srcs[PROTOLOG_IMPL_SRC_PATH] = """ 175 package com.android.internal.protolog; 176 177 import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LEGACY_OUTPUT_FILE_PATH; 178 import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LEGACY_VIEWER_CONFIG_PATH; 179 import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.VIEWER_CONFIG_PATH; 180 181 import com.android.internal.protolog.common.ProtoLogToolInjected; 182 183 public class ProtoLogImpl { 184 @ProtoLogToolInjected(VIEWER_CONFIG_PATH) 185 private static String sViewerConfigPath; 186 187 @ProtoLogToolInjected(LEGACY_VIEWER_CONFIG_PATH) 188 private static String sLegacyViewerConfigPath; 189 190 @ProtoLogToolInjected(LEGACY_OUTPUT_FILE_PATH) 191 private static String sLegacyOutputFilePath; 192 } 193 """.trimIndent() 194 195 ProtoLogTool.injector = object : ProtoLogTool.Injector { 196 override fun fileOutputStream(file: String): OutputStream = 197 ByteArrayOutputStream().also { outputs[file] = it } 198 199 override fun readText(file: File): String { 200 for (src in srcs.entries) { 201 val filePath = src.key 202 if (file.path == filePath) { 203 return src.value 204 } 205 } 206 throw FileNotFoundException("$file not found in [${srcs.keys.joinToString()}].") 207 } 208 209 override fun readLogGroups(jarPath: String, className: String) = mapOf( 210 logGroup.name to logGroup) 211 212 override fun reportProcessingError(ex: CodeProcessingException) { 213 processingErrors.add(ex) 214 } 215 216 override val processingErrors: MutableList<CodeProcessingException> = mutableListOf() 217 } 218 219 ProtoLogTool.invoke(commandOptions) 220 221 return outputs.mapValues { it.value.toByteArray() } 222 } 223 224 fun fail(message: String): Nothing = Assert.fail(message) as Nothing 225 } 226