summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Makoto Onuki <omakoto@google.com> 2025-01-27 15:44:13 -0800
committer Makoto Onuki <omakoto@google.com> 2025-01-27 16:22:30 -0800
commit799a348923efed985030168e08fdf3e650baea67 (patch)
tree6077e8288ed767bcc454bea1db4ed69cb2b997b9
parent9b828add5e3fddcce23b92b51a568923428773ff (diff)
Add ravenhelper command to bulk-disable tests
- Add "ravenhelper mm", which stands for "mark-method", which reads from STDIN a list of methods in a form of: com.android.ravenwoodtest.tests.Test1#testA com.android.ravenwoodtest.tests.Test1#testB com.android.ravenwoodtest.tests.Test1#testC (which is basically a failed test list from atest) and inject arbitrary lines on each of these methods. - This is similar to "ravenizer pta" -- it creates a shell script that updates the source files, rather than directly updating them. - The primary purpose is for bulk-adding arbitrary annotations, such as "@Ignore" or "@DisabledOnRavenwood" to broken test methods. - Add a wrapper script "add-annotations.sh". Flag: EXEMPT host test change only Bug: 292141694 Test: Manual test: ./scripts/add-annotations.sh -t '@@Ignore' /android/main-without-vendor/frameworks/base/ravenwood/tests/ <method-list.txt Run the generated script and make sure '@Ignore' is added to the target methods. Change-Id: If61cc968cae26a1a8dbff7b68a9f42a7459ad0dc
-rwxr-xr-xravenwood/scripts/add-annotations.sh84
-rwxr-xr-xravenwood/scripts/pta-framework.sh2
-rw-r--r--ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt7
-rw-r--r--ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/RavenHelperMain.kt7
-rw-r--r--ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Operations.kt24
-rw-r--r--ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaProcessor.kt27
-rw-r--r--ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MapOptions.kt95
-rw-r--r--ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MarkMethodHandler.kt197
8 files changed, 416 insertions, 27 deletions
diff --git a/ravenwood/scripts/add-annotations.sh b/ravenwood/scripts/add-annotations.sh
new file mode 100755
index 000000000000..3e86037d7c7b
--- /dev/null
+++ b/ravenwood/scripts/add-annotations.sh
@@ -0,0 +1,84 @@
+#!/bin/bash
+# Copyright (C) 2025 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.
+
+#
+# Use "ravehleper mm" to create a shell script which:
+# - Reads read a list of methods from STDIN
+# Which basically looks like a list of 'com.android.ravenwoodtest.tests.Test1#testA'
+# - Add @DisabledOnRavenwood to them
+#
+# Example usage:
+#
+# ./add-annotations.sh $ANDROID_BUILD_TOP/frameworks/base/ravenwood/tests <METHOD-LIST.txt
+#
+# Use a different annotation instead. (Note, in order to use an at, you need to use a double-at.)
+# ./add-annotations.sh -t '@@Ignore' $ANDROID_BUILD_TOP/frameworks/base/ravenwood/tests <METHOD-LIST.txt
+#
+
+set -e
+
+# Uncomment it to always build ravenhelper (slow)
+# ${BUILD_CMD:-m} ravenhelper
+
+# We add this line to each methods found.
+# Note, if we used a single @, that'd be handled as an at file. Use
+# the double-at instead.
+annotation="@@android.platform.test.annotations.DisabledOnRavenwood"
+while getopts "t:" opt; do
+case "$opt" in
+ t)
+ annotation="$OPTARG"
+ ;;
+ '?')
+ exit 1
+ ;;
+esac
+done
+shift $(($OPTIND - 1))
+
+source_dirs="$@"
+
+OUT_SCRIPT="${OUT_SCRIPT:-/tmp/add-annotations.sh}"
+
+rm -f "$OUT_SCRIPT"
+
+
+with_flag() {
+ local flag="$1"
+ shift
+
+ for arg in "$@"; do
+ echo "$flag $arg"
+ done
+}
+
+run() {
+ echo "Running: $*"
+ "$@"
+}
+
+run ${RAVENHELPER_CMD:-ravenhelper mm} \
+ --output-script $OUT_SCRIPT \
+ --text "$annotation" \
+ $(with_flag --src $source_dirs)
+
+
+if ! [[ -f $OUT_SCRIPT ]] ; then
+ # no operations generated.
+ exit 0
+fi
+
+echo
+echo "Created script at $OUT_SCRIPT. Run it with: sh $OUT_SCRIPT"
diff --git a/ravenwood/scripts/pta-framework.sh b/ravenwood/scripts/pta-framework.sh
index 224ab59e2e09..e7fe9e19846b 100755
--- a/ravenwood/scripts/pta-framework.sh
+++ b/ravenwood/scripts/pta-framework.sh
@@ -1,5 +1,5 @@
#!/bin/bash
-# Copyright (C) 2024 The Android Open Source Project
+# Copyright (C) 2025 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.
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index ae9276f711e1..297420d08ac1 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -354,6 +354,8 @@ class ArgIterator(
* Scan the arguments, and if any of them starts with an `@`, then load from the file
* and use its content as arguments.
*
+ * In order to pass an argument that starts with an '@', use '@@' instead.
+ *
* In this file, each line is treated as a single argument.
*
* The file can contain '#' as comments.
@@ -362,7 +364,10 @@ private fun expandAtFiles(args: Array<String>): List<String> {
val ret = mutableListOf<String>()
args.forEach { arg ->
- if (!arg.startsWith('@')) {
+ if (arg.startsWith("@@")) {
+ ret += arg.substring(1)
+ return@forEach
+ } else if (!arg.startsWith('@')) {
ret += arg
return@forEach
}
diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/RavenHelperMain.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/RavenHelperMain.kt
index e6efbf6c5223..0be0c96c33d4 100644
--- a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/RavenHelperMain.kt
+++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/RavenHelperMain.kt
@@ -27,6 +27,7 @@ import com.android.hoststubgen.executableName
import com.android.hoststubgen.log
import com.android.hoststubgen.runMainWithBoilerplate
import com.android.platform.test.ravenwood.ravenhelper.policytoannot.PtaProcessor
+import com.android.platform.test.ravenwood.ravenhelper.sourcemap.MarkMethodHandler
interface SubcommandHandler {
fun handle(args: List<String>)
@@ -39,7 +40,10 @@ fun usage() {
Subcommands:
pta: "policy-to-annotations" Convert policy file to annotations.
- (See the pta-framework.sh script for usage.) 1
+ (See the pta-framework.sh script for usage.)
+
+ mm: "mark methods" Used to add annotations (such as @DisabledOnRavenwood)
+ to methods.
""".trimIndent())
}
@@ -60,6 +64,7 @@ fun main(args: Array<String>) {
val subcommand = args[0]
val handler: SubcommandHandler = when (subcommand) {
"pta" -> PtaProcessor()
+ "mm" -> MarkMethodHandler()
else -> {
usage()
throw GeneralUserErrorException("Unknown subcommand '$subcommand'")
diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Operations.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Operations.kt
index 3531ba951b1c..256d1234e1e6 100644
--- a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Operations.kt
+++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Operations.kt
@@ -24,6 +24,8 @@ package com.android.platform.test.ravenwood.ravenhelper.policytoannot
import com.android.hoststubgen.log
import java.io.BufferedWriter
import java.io.File
+import java.io.FileOutputStream
+import java.io.OutputStreamWriter
enum class SourceOperationType {
/** Insert a line */
@@ -198,4 +200,26 @@ private fun toSedScript(ops: List<SourceOperation>, writer: BufferedWriter) {
}
}
}
+}
+
+fun createShellScript(ops: SourceOperations, scriptFile: String?): Boolean {
+ if (ops.size == 0) {
+ log.i("No files need to be updated.")
+ return false
+ }
+
+ val scriptWriter = BufferedWriter(
+ OutputStreamWriter(
+ scriptFile?.let { file ->
+ FileOutputStream(file)
+ } ?: System.out
+ ))
+
+ scriptWriter.use { writer ->
+ scriptFile?.let {
+ log.i("Creating script file at $it ...")
+ }
+ createShellScript(ops, writer)
+ }
+ return true
} \ No newline at end of file
diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaProcessor.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaProcessor.kt
index 5984e4fc8f9f..3657a9077577 100644
--- a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaProcessor.kt
+++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaProcessor.kt
@@ -31,10 +31,7 @@ import com.android.platform.test.ravenwood.ravenhelper.sourcemap.AllClassInfo
import com.android.platform.test.ravenwood.ravenhelper.sourcemap.ClassInfo
import com.android.platform.test.ravenwood.ravenhelper.sourcemap.MethodInfo
import com.android.platform.test.ravenwood.ravenhelper.sourcemap.SourceLoader
-import java.io.BufferedWriter
-import java.io.FileOutputStream
import java.io.FileReader
-import java.io.OutputStreamWriter
import java.util.regex.Pattern
/**
@@ -55,25 +52,7 @@ class PtaProcessor : SubcommandHandler {
)
converter.process()
- val ops = converter.resultOperations
-
- if (ops.size == 0) {
- log.i("No files need to be updated.")
- return
- }
-
- val scriptWriter = BufferedWriter(OutputStreamWriter(
- options.outputScriptFile.get?.let { file ->
- FileOutputStream(file)
- } ?: System.out
- ))
-
- scriptWriter.use { writer ->
- options.outputScriptFile.get?.let {
- log.i("Creating script file at $it ...")
- }
- createShellScript(ops, writer)
- }
+ createShellScript(converter.resultOperations, options.outputScriptFile.get)
}
}
@@ -424,7 +403,7 @@ private class TextPolicyToAnnotationConverter(
if (methodsAndAnnot == null) {
classHasMember = true
- return // This policy can't converted.
+ return // This policy can't be converted.
}
val methods = methodsAndAnnot.first
val annot = methodsAndAnnot.second
@@ -476,4 +455,4 @@ private class TextPolicyToAnnotationConverter(
classHasMember = true
}
}
-} \ No newline at end of file
+}
diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MapOptions.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MapOptions.kt
new file mode 100644
index 000000000000..ee200bb39df2
--- /dev/null
+++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MapOptions.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2025 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.platform.test.ravenwood.ravenhelper.sourcemap
+
+import com.android.hoststubgen.ArgIterator
+import com.android.hoststubgen.ArgumentsException
+import com.android.hoststubgen.SetOnce
+import com.android.hoststubgen.ensureFileExists
+import com.android.hoststubgen.log
+
+/**
+ * Options for the "ravenhelper map" subcommand.
+ */
+class MapOptions(
+ /** Source files or directories. */
+ var sourceFilesOrDirectories: MutableList<String> = mutableListOf(),
+
+ /** Files containing target methods */
+ var targetMethodFiles: MutableList<String> = mutableListOf(),
+
+ /** Output script file. */
+ var outputScriptFile: SetOnce<String?> = SetOnce(null),
+
+ /** Text to insert. */
+ var text: SetOnce<String?> = SetOnce(null),
+) {
+ companion object {
+ fun parseArgs(args: List<String>): MapOptions {
+ val ret = MapOptions()
+ val ai = ArgIterator.withAtFiles(args.toTypedArray())
+
+ while (true) {
+ val arg = ai.nextArgOptional() ?: break
+
+ fun nextArg(): String = ai.nextArgRequired(arg)
+
+ if (log.maybeHandleCommandLineArg(arg) { nextArg() }) {
+ continue
+ }
+ try {
+ when (arg) {
+ // TODO: Write help
+ "-h", "--help" -> TODO("Help is not implemented yet")
+
+ "-s", "--src" ->
+ ret.sourceFilesOrDirectories.add(nextArg().ensureFileExists())
+
+ "-i", "--input" ->
+ ret.targetMethodFiles.add(nextArg().ensureFileExists())
+
+ "-o", "--output-script" ->
+ ret.outputScriptFile.set(nextArg())
+
+ "-t", "--text" ->
+ ret.text.set(nextArg())
+
+ else -> throw ArgumentsException("Unknown option: $arg")
+ }
+ } catch (e: SetOnce.SetMoreThanOnceException) {
+ throw ArgumentsException("Duplicate or conflicting argument found: $arg")
+ }
+ }
+
+ if (ret.sourceFilesOrDirectories.size == 0) {
+ throw ArgumentsException("Must specify at least one source path")
+ }
+
+ return ret
+ }
+ }
+
+ override fun toString(): String {
+ return """
+ PtaOptions{
+ sourceFilesOrDirectories=$sourceFilesOrDirectories
+ targetMethods=$targetMethodFiles
+ outputScriptFile=$outputScriptFile
+ text=$text
+ }
+ """.trimIndent()
+ }
+} \ No newline at end of file
diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MarkMethodHandler.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MarkMethodHandler.kt
new file mode 100644
index 000000000000..8085253895f9
--- /dev/null
+++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MarkMethodHandler.kt
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2025 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.platform.test.ravenwood.ravenhelper.sourcemap
+
+import com.android.hoststubgen.GeneralUserErrorException
+import com.android.hoststubgen.log
+import com.android.platform.test.ravenwood.ravenhelper.SubcommandHandler
+import com.android.platform.test.ravenwood.ravenhelper.policytoannot.SourceOperation
+import com.android.platform.test.ravenwood.ravenhelper.policytoannot.SourceOperationType
+import com.android.platform.test.ravenwood.ravenhelper.policytoannot.SourceOperations
+import com.android.platform.test.ravenwood.ravenhelper.policytoannot.createShellScript
+import com.android.platform.test.ravenwood.ravenhelper.psi.createUastEnvironment
+import java.io.BufferedReader
+import java.io.FileReader
+
+/**
+ * This is the main routine of the "mm" subcommands, which marks specified methods with
+ * a given line, which defaults to "@DisabledOnRavenwood". This can be used to add bulk-annotate
+ * tests methods that are failing.
+ *
+ * See the javadoc of [MarkMethodProcessor] for more details.
+ */
+class MarkMethodHandler : SubcommandHandler {
+ override fun handle(args: List<String>) {
+ val options = MapOptions.parseArgs(args)
+
+ log.i("Options: $options")
+
+ // Files from which we load a list of methods.
+ val inputFiles = if (options.targetMethodFiles.isEmpty()) {
+ log.w("[Reading method list from STDIN...]")
+ log.flush()
+ listOf("/dev/stdin")
+ } else {
+ options.targetMethodFiles
+ }
+
+ // A string to inject before each method.
+ val text = if (options.text.isSet) {
+ options.text.get!!
+ } else {
+ "@android.platform.test.annotations.DisabledOnRavenwood"
+ }
+
+ // Process.
+ val processor = MarkMethodProcessor(
+ options.sourceFilesOrDirectories,
+ inputFiles,
+ text,
+ )
+ processor.process()
+
+ // Create the output script.
+ createShellScript(processor.resultOperations, options.outputScriptFile.get)
+ }
+}
+
+/**
+ * Load a list of methods / classes from [targetMethodFiles], and inject [textToInsert] to
+ * each of them, to the source files under [sourceFilesOrDirectories]
+ *
+ * An example input files look like this -- this can be generated from atest output.
+ * <pre>
+
+ # We add @DisabledOnRavenwood to the following methods.
+ com.android.ravenwoodtest.tests.Test1#testA
+ com.android.ravenwoodtest.tests.Test1#testB
+ com.android.ravenwoodtest.tests.Test1#testC
+
+ # We add @DisabledOnRavenwood to the following class.
+ com.android.ravenwoodtest.tests.Test2
+
+ # Special case: we add the annotation to the class too.
+ com.android.ravenwoodtest.tests.Test3#initializationError
+ </pre>
+
+ */
+private class MarkMethodProcessor(
+ private val sourceFilesOrDirectories: List<String>,
+ private val targetMethodFiles: List<String>,
+ private val textToInsert: String,
+) {
+ private val classes = AllClassInfo()
+ val resultOperations = SourceOperations()
+
+ /**
+ * Entry point.
+ */
+ fun process() {
+ val env = createUastEnvironment()
+ try {
+ loadSources()
+
+ processInputFiles()
+ } finally {
+ env.dispose()
+ }
+ }
+
+ private fun loadSources() {
+ val env = createUastEnvironment()
+ try {
+ val loader = SourceLoader(env)
+ loader.load(sourceFilesOrDirectories, classes)
+ } finally {
+ env.dispose()
+ }
+ }
+
+ /**
+ * Process liput files. Input files looks like this:
+ * <pre>
+ * # We add @DisabledOnRavenwood to the following methods.
+ * com.android.ravenwoodtest.tests.Test1#testA
+ * com.android.ravenwoodtest.tests.Test1#testB
+ * com.android.ravenwoodtest.tests.Test1#testC
+ *
+ * # We add @DisabledOnRavenwood to the following class.
+ * com.android.ravenwoodtest.tests.Test2
+ *
+ * # Special case: we add the annotation to the class too.
+ * com.android.ravenwoodtest.tests.Test3#initializationError
+ * </pre>
+ */
+ private fun processInputFiles() {
+ targetMethodFiles.forEach { filename ->
+ BufferedReader(FileReader(filename)).use { reader ->
+ reader.readLines().forEach { line ->
+ if (line.isBlank() || line.startsWith('#')) {
+ return@forEach
+ }
+ processSingleLine(line)
+ }
+ }
+ }
+ }
+
+ private fun processSingleLine(line: String) {
+ val cm = line.split("#") // Class and method
+ if (cm.size > 2) {
+ throw GeneralUserErrorException("Input line \"$line\" contains too many #'s")
+ }
+ val className = cm[0]
+ val methodName = if (cm.size == 2 && cm[1] != "initializationError") {
+ cm[1]
+ } else {
+ ""
+ }
+
+ // Find class info
+ val ci = classes.findClass(className)
+ ?: throw GeneralUserErrorException("Class \"$className\" not found\"")
+
+ if (methodName == "") {
+ processClass(ci)
+ } else {
+ processMethod(ci, methodName)
+ }
+ }
+
+ private fun processClass(ci: ClassInfo) {
+ addOperation(ci.location, "Class ${ci.fullName}")
+ }
+
+ private fun processMethod(ci: ClassInfo, methodName: String) {
+ var methods = ci.methods[methodName]
+ ?: throw GeneralUserErrorException("method \"$methodName\" not found\"")
+ methods.forEach { mi ->
+ addOperation(mi.location, "Method ${mi.name}")
+ }
+ }
+
+ private fun addOperation(loc: Location, description: String) {
+ resultOperations.add(
+ SourceOperation(
+ loc.file,
+ loc.line,
+ SourceOperationType.Insert,
+ loc.getIndent() + textToInsert,
+ description
+ )
+ )
+ }
+}