diff options
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 46c2c01c8ee8..d396839ec182 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 + ) + ) + } +} |