diff options
7 files changed, 165 insertions, 3 deletions
diff --git a/ravenwood/scripts/remove-ravenizer-output.sh b/ravenwood/scripts/remove-ravenizer-output.sh new file mode 100755 index 000000000000..be15b711b980 --- /dev/null +++ b/ravenwood/scripts/remove-ravenizer-output.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Copyright (C) 2024 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. + +# Delete all the ravenizer output jar files from Soong's intermediate directory. + +# `-a -prune` is needed because otherwise find would be confused if the directory disappears. + +find "${ANDROID_BUILD_TOP:?}/out/soong/.intermediates/" \ + -type d \ + -name 'ravenizer' \ + -print \ + -exec rm -fr \{\} \; \ + -a -prune diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt index 3a7fab39e4ac..0dcd271562d1 100644 --- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt +++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt @@ -17,7 +17,14 @@ package com.android.platform.test.ravenwood.ravenizer +import com.android.hoststubgen.UserErrorException + /** * Use it for internal exception that really shouldn't happen. */ class RavenizerInternalException(message: String) : Exception(message) + +/** + * Thrown when an invalid test is detected in the target jar. (e.g. JUni3 tests) + */ +class RavenizerInvalidTestException(message: String) : Exception(message), UserErrorException diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt index e92ef7216e25..a38512ec9f2d 100644 --- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt +++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt @@ -44,6 +44,9 @@ data class RavenizerStats( /** Time took to build [ClasNodes] */ var loadStructureTime: Double = .0, + /** Time took to validate the classes */ + var validationTime: Double = .0, + /** Total real time spent for converting the jar file */ var totalProcessTime: Double = .0, @@ -67,6 +70,7 @@ data class RavenizerStats( RavenizerStats{ totalTime=$totalTime, loadStructureTime=$loadStructureTime, + validationTime=$validationTime, totalProcessTime=$totalProcessTime, totalConversionTime=$totalConversionTime, totalCopyTime=$totalCopyTime, @@ -84,16 +88,44 @@ data class RavenizerStats( class Ravenizer(val options: RavenizerOptions) { fun run() { val stats = RavenizerStats() + + val fatalValidation = options.fatalValidation.get + stats.totalTime = log.nTime { - process(options.inJar.get, options.outJar.get, stats) + process( + options.inJar.get, + options.outJar.get, + options.enableValidation.get, + fatalValidation, + stats, + ) } log.i(stats.toString()) } - private fun process(inJar: String, outJar: String, stats: RavenizerStats) { + private fun process( + inJar: String, + outJar: String, + enableValidation: Boolean, + fatalValidation: Boolean, + stats: RavenizerStats, + ) { var allClasses = ClassNodes.loadClassStructures(inJar) { time -> stats.loadStructureTime = time } + if (enableValidation) { + stats.validationTime = log.iTime("Validating classes") { + if (!validateClasses(allClasses)) { + var message = "Invalid test class(es) detected." + + " See error log for details." + if (fatalValidation) { + throw RavenizerInvalidTestException(message) + } else { + log.w("Warning: $message") + } + } + } + } stats.totalProcessTime = log.iTime("$executableName processing $inJar") { ZipFile(inJar).use { inZip -> diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt index e85e3be31b77..e8341e5ceb06 100644 --- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt +++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt @@ -27,6 +27,12 @@ class RavenizerOptions( /** Output jar file */ var outJar: SetOnce<String> = SetOnce(""), + + /** Whether to enable test validation. */ + var enableValidation: SetOnce<Boolean> = SetOnce(true), + + /** Whether the validation failure is fatal or not. */ + var fatalValidation: SetOnce<Boolean> = SetOnce(false), ) { companion object { fun parseArgs(args: Array<String>): RavenizerOptions { @@ -52,6 +58,12 @@ class RavenizerOptions( "--in-jar" -> ret.inJar.set(nextArg()).ensureFileExists() "--out-jar" -> ret.outJar.set(nextArg()) + "--enable-validation" -> ret.enableValidation.set(true) + "--disable-validation" -> ret.enableValidation.set(false) + + "--fatal-validation" -> ret.fatalValidation.set(true) + "--no-fatal-validation" -> ret.fatalValidation.set(false) + else -> throw ArgumentsException("Unknown option: $arg") } } catch (e: SetOnce.SetMoreThanOnceException) { @@ -74,6 +86,8 @@ class RavenizerOptions( RavenizerOptions{ inJar=$inJar, outJar=$outJar, + enableValidation=$enableValidation, + fatalValidation=$fatalValidation, } """.trimIndent() } diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt index e026e7ab3679..059f5a4b4d47 100644 --- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt +++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt @@ -87,9 +87,12 @@ fun String.shouldByBypassed(): Boolean { return this.startsWithAny( "java/", // just in case... "javax/", + "junit/", "org/junit/", "org/mockito/", "kotlin/", + "androidx/", + "android/support/", // TODO -- anything else? ) } diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt new file mode 100644 index 000000000000..27092d28ae5e --- /dev/null +++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 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.ravenizer + +import com.android.hoststubgen.asm.ClassNodes +import com.android.hoststubgen.asm.startsWithAny +import com.android.hoststubgen.asm.toHumanReadableClassName +import com.android.hoststubgen.log +import org.objectweb.asm.tree.ClassNode + +fun validateClasses(classes: ClassNodes): Boolean { + var allOk = true + classes.forEach { allOk = checkClass(it, classes) && allOk } + + return allOk +} + +/** + * Validate a class. + * + * - A test class shouldn't extend + * + */ +fun checkClass(cn: ClassNode, classes: ClassNodes): Boolean { + if (cn.name.shouldByBypassed()) { + // Class doesn't need to be checked. + return true + } + var allOk = true + + // See if there's any class that extends a legacy base class. + // But ignore the base classes in android.test. + if (!cn.name.startsWithAny("android/test/")) { + allOk = checkSuperClass(cn, cn, classes) && allOk + } + return allOk +} + +fun checkSuperClass(targetClass: ClassNode, currentClass: ClassNode, classes: ClassNodes): Boolean { + if (currentClass.superName == null || currentClass.superName == "java/lang/Object") { + return true // No parent class + } + if (currentClass.superName.isLegacyTestBaseClass()) { + log.e("Error: Class ${targetClass.name.toHumanReadableClassName()} extends" + + " a legacy test class ${currentClass.superName.toHumanReadableClassName()}.") + return false + } + classes.findClass(currentClass.superName)?.let { + return checkSuperClass(targetClass, it, classes) + } + // Super class not found. + // log.w("Class ${currentClass.superName} not found.") + return true +} + +/** + * Check if a class internal name is a known legacy test base class. + */ +fun String.isLegacyTestBaseClass(): Boolean { + return this.startsWithAny( + "junit/framework/TestCase", + + // In case the test doesn't statically include JUnit, we need + "android/test/AndroidTestCase", + "android/test/InstrumentationTestCase", + "android/test/InstrumentationTestSuite", + ) +} diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt index 25cad0213b72..dc934ebabbf2 100644 --- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt +++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt @@ -183,7 +183,7 @@ class RunnerRewritingAdapter private constructor( av.visit("value", ravenwoodTestRunnerType.type) av.visitEnd() } - log.d("Processed ${classInternalName.toHumanReadableClassName()}") + log.i("Update the @RunWith: ${classInternalName.toHumanReadableClassName()}") } /* |