1 /* <lambda>null2 * Copyright (C) 2025 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 package com.android.platform.test.ravenwood.ravenhelper.sourcemap 17 18 import com.android.hoststubgen.GeneralUserErrorException 19 import com.android.hoststubgen.log 20 import com.android.platform.test.ravenwood.ravenhelper.SubcommandHandler 21 import com.android.platform.test.ravenwood.ravenhelper.policytoannot.SourceOperation 22 import com.android.platform.test.ravenwood.ravenhelper.policytoannot.SourceOperationType 23 import com.android.platform.test.ravenwood.ravenhelper.policytoannot.SourceOperations 24 import com.android.platform.test.ravenwood.ravenhelper.policytoannot.createShellScript 25 import com.android.platform.test.ravenwood.ravenhelper.psi.createUastEnvironment 26 import java.io.BufferedReader 27 import java.io.FileReader 28 29 /** 30 * This is the main routine of the "mm" subcommands, which marks specified methods with 31 * a given line, which defaults to "@DisabledOnRavenwood". This can be used to add bulk-annotate 32 * tests methods that are failing. 33 * 34 * See the javadoc of [MarkMethodProcessor] for more details. 35 */ 36 class MarkMethodHandler : SubcommandHandler { 37 override fun handle(args: List<String>) { 38 val options = MapOptions().apply { parseArgs(args) } 39 40 log.i("Options: $options") 41 42 // Files from which we load a list of methods. 43 val inputFiles = if (options.targetMethodFiles.isEmpty()) { 44 log.w("[Reading method list from STDIN...]") 45 log.flush() 46 listOf("/dev/stdin") 47 } else { 48 options.targetMethodFiles 49 } 50 51 // A string to inject before each method. 52 val text = if (options.text.isSet) { 53 options.text.get!! 54 } else { 55 "@android.platform.test.annotations.DisabledOnRavenwood" 56 } 57 58 // Process. 59 val processor = MarkMethodProcessor( 60 options.sourceFilesOrDirectories, 61 inputFiles, 62 text, 63 ) 64 processor.process() 65 66 // Create the output script. 67 createShellScript(processor.resultOperations, options.outputScriptFile.get) 68 } 69 } 70 71 /** 72 * Load a list of methods / classes from [targetMethodFiles], and inject [textToInsert] to 73 * each of them, to the source files under [sourceFilesOrDirectories] 74 * 75 * An example input files look like this -- this can be generated from atest output. 76 * <pre> 77 78 # We add @DisabledOnRavenwood to the following methods. 79 com.android.ravenwoodtest.tests.Test1#testA 80 com.android.ravenwoodtest.tests.Test1#testB 81 com.android.ravenwoodtest.tests.Test1#testC 82 83 # We add @DisabledOnRavenwood to the following class. 84 com.android.ravenwoodtest.tests.Test2 85 86 # Special case: we add the annotation to the class too. 87 com.android.ravenwoodtest.tests.Test3#initializationError 88 </pre> 89 90 */ 91 private class MarkMethodProcessor( 92 private val sourceFilesOrDirectories: List<String>, 93 private val targetMethodFiles: List<String>, 94 private val textToInsert: String, 95 ) { 96 private val classes = AllClassInfo() 97 val resultOperations = SourceOperations() 98 99 /** 100 * Entry point. 101 */ processnull102 fun process() { 103 val env = createUastEnvironment() 104 try { 105 loadSources() 106 107 processInputFiles() 108 } finally { 109 env.dispose() 110 } 111 } 112 loadSourcesnull113 private fun loadSources() { 114 val env = createUastEnvironment() 115 try { 116 val loader = SourceLoader(env) 117 loader.load(sourceFilesOrDirectories, classes) 118 } finally { 119 env.dispose() 120 } 121 } 122 123 /** 124 * Process liput files. Input files looks like this: 125 * <pre> 126 * # We add @DisabledOnRavenwood to the following methods. 127 * com.android.ravenwoodtest.tests.Test1#testA 128 * com.android.ravenwoodtest.tests.Test1#testB 129 * com.android.ravenwoodtest.tests.Test1#testC 130 * 131 * # We add @DisabledOnRavenwood to the following class. 132 * com.android.ravenwoodtest.tests.Test2 133 * 134 * # Special case: we add the annotation to the class too. 135 * com.android.ravenwoodtest.tests.Test3#initializationError 136 * </pre> 137 */ processInputFilesnull138 private fun processInputFiles() { 139 targetMethodFiles.forEach { filename -> 140 BufferedReader(FileReader(filename)).use { reader -> 141 reader.readLines().forEach { line -> 142 if (line.isBlank() || line.startsWith('#')) { 143 return@forEach 144 } 145 processSingleLine(line) 146 } 147 } 148 } 149 } 150 processSingleLinenull151 private fun processSingleLine(line: String) { 152 val cm = line.split("#") // Class and method 153 if (cm.size > 2) { 154 throw GeneralUserErrorException("Input line \"$line\" contains too many #'s") 155 } 156 val className = cm[0] 157 val methodName = if (cm.size == 2 && cm[1] != "initializationError") { 158 cm[1] 159 } else { 160 "" 161 } 162 163 // Find class info 164 val ci = classes.findClass(className) 165 ?: throw GeneralUserErrorException("Class \"$className\" not found\"") 166 167 if (methodName == "") { 168 processClass(ci) 169 } else { 170 processMethod(ci, methodName) 171 } 172 } 173 processClassnull174 private fun processClass(ci: ClassInfo) { 175 addOperation(ci.location, "Class ${ci.fullName}") 176 } 177 processMethodnull178 private fun processMethod(ci: ClassInfo, methodName: String) { 179 var methods = ci.methods[methodName] 180 ?: throw GeneralUserErrorException("method \"$methodName\" not found\"") 181 methods.forEach { mi -> 182 addOperation(mi.location, "Method ${mi.name}") 183 } 184 } 185 addOperationnull186 private fun addOperation(loc: Location, description: String) { 187 resultOperations.add( 188 SourceOperation( 189 loc.file, 190 loc.line, 191 SourceOperationType.Insert, 192 loc.getIndent() + textToInsert, 193 description 194 ) 195 ) 196 } 197 } 198