• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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