Merge "check-flagged-apis: create list of @FlaggedApi errors" into main

This commit is contained in:
Treehugger Robot 2024-04-17 15:44:39 +00:00 committed by Gerrit Code Review
commit fde34c3b65
2 changed files with 108 additions and 21 deletions

View file

@ -20,6 +20,7 @@ import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@ -36,22 +37,6 @@ private val API_SIGNATURE =
"""
.trim()
private val PARSED_FLAGS =
{
val parsed_flag =
Aconfig.parsed_flag
.newBuilder()
.setPackage("android.flag")
.setName("foo")
.setState(Aconfig.flag_state.ENABLED)
.setPermission(Aconfig.flag_permission.READ_ONLY)
.build()
val parsed_flags = Aconfig.parsed_flags.newBuilder().addParsedFlag(parsed_flag).build()
val binaryProto = ByteArrayOutputStream()
parsed_flags.writeTo(binaryProto)
ByteArrayInputStream(binaryProto.toByteArray())
}()
private val API_VERSIONS =
"""
<?xml version="1.0" encoding="utf-8"?>
@ -64,6 +49,21 @@ private val API_VERSIONS =
"""
.trim()
private fun generateFlagsProto(fooState: Aconfig.flag_state): InputStream {
val parsed_flag =
Aconfig.parsed_flag
.newBuilder()
.setPackage("android.flag")
.setName("foo")
.setState(fooState)
.setPermission(Aconfig.flag_permission.READ_ONLY)
.build()
val parsed_flags = Aconfig.parsed_flags.newBuilder().addParsedFlag(parsed_flag).build()
val binaryProto = ByteArrayOutputStream()
parsed_flags.writeTo(binaryProto)
return ByteArrayInputStream(binaryProto.toByteArray())
}
@RunWith(DeviceJUnit4ClassRunner::class)
class CheckFlaggedApisTest : BaseHostJUnit4Test() {
@Test
@ -76,7 +76,7 @@ class CheckFlaggedApisTest : BaseHostJUnit4Test() {
@Test
fun testParseFlagValues() {
val expected: Map<Flag, Boolean> = mapOf(Flag("android.flag.foo") to true)
val actual = parseFlagValues(PARSED_FLAGS)
val actual = parseFlagValues(generateFlagsProto(Aconfig.flag_state.ENABLED))
assertEquals(expected, actual)
}
@ -86,4 +86,28 @@ class CheckFlaggedApisTest : BaseHostJUnit4Test() {
val actual = parseApiVersions(API_VERSIONS.byteInputStream())
assertEquals(expected, actual)
}
@Test
fun testFindErrorsNoErrors() {
val expected = setOf<ApiError>()
val actual =
findErrors(
parseApiSignature("in-memory", API_SIGNATURE.byteInputStream()),
parseFlagValues(generateFlagsProto(Aconfig.flag_state.ENABLED)),
parseApiVersions(API_VERSIONS.byteInputStream()))
assertEquals(expected, actual)
}
@Test
fun testFindErrorsDisabledFlaggedApiIsPresent() {
val expected =
setOf<ApiError>(
DisabledFlaggedApiIsPresentError(Symbol("android.Clazz.FOO"), Flag("android.flag.foo")))
val actual =
findErrors(
parseApiSignature("in-memory", API_SIGNATURE.byteInputStream()),
parseFlagValues(generateFlagsProto(Aconfig.flag_state.DISABLED)),
parseApiVersions(API_VERSIONS.byteInputStream()))
assertEquals(expected, actual)
}
}

View file

@ -81,6 +81,36 @@ internal value class Flag(val name: String) {
override fun toString(): String = name.toString()
}
internal sealed class ApiError {
abstract val symbol: Symbol
abstract val flag: Flag
}
internal data class EnabledFlaggedApiNotPresentError(
override val symbol: Symbol,
override val flag: Flag
) : ApiError() {
override fun toString(): String {
return "error: enabled @FlaggedApi not present in built artifact: symbol=$symbol flag=$flag"
}
}
internal data class DisabledFlaggedApiIsPresentError(
override val symbol: Symbol,
override val flag: Flag
) : ApiError() {
override fun toString(): String {
return "error: disabled @FlaggedApi is present in built artifact: symbol=$symbol flag=$flag"
}
}
internal data class UnknownFlagError(override val symbol: Symbol, override val flag: Flag) :
ApiError() {
override fun toString(): String {
return "error: unknown flag: symbol=$symbol flag=$flag"
}
}
class CheckCommand :
CliktCommand(
help =
@ -122,16 +152,17 @@ The tool will exit with a non-zero exit code if any flagged APIs are found to be
.required()
override fun run() {
@Suppress("UNUSED_VARIABLE")
val flaggedSymbols =
apiSignaturePath.toFile().inputStream().use {
parseApiSignature(apiSignaturePath.toString(), it)
}
@Suppress("UNUSED_VARIABLE")
val flags = flagValuesPath.toFile().inputStream().use { parseFlagValues(it) }
@Suppress("UNUSED_VARIABLE")
val exportedSymbols = apiVersionsPath.toFile().inputStream().use { parseApiVersions(it) }
throw ProgramResult(0)
val errors = findErrors(flaggedSymbols, flags, exportedSymbols)
for (e in errors) {
println(e)
}
throw ProgramResult(errors.size)
}
}
@ -185,4 +216,36 @@ internal fun parseApiVersions(input: InputStream): Set<Symbol> {
return output
}
/**
* Find errors in the given data.
*
* @param flaggedSymbolsInSource the set of symbols that are flagged in the source code
* @param flags the set of flags and their values
* @param symbolsInOutput the set of symbols that are present in the output
* @return the set of errors found
*/
internal fun findErrors(
flaggedSymbolsInSource: Set<Pair<Symbol, Flag>>,
flags: Map<Flag, Boolean>,
symbolsInOutput: Set<Symbol>
): Set<ApiError> {
val errors = mutableSetOf<ApiError>()
for ((symbol, flag) in flaggedSymbolsInSource) {
try {
if (flags.getValue(flag)) {
if (!symbolsInOutput.contains(symbol)) {
errors.add(EnabledFlaggedApiNotPresentError(symbol, flag))
}
} else {
if (symbolsInOutput.contains(symbol)) {
errors.add(DisabledFlaggedApiIsPresentError(symbol, flag))
}
}
} catch (e: NoSuchElementException) {
errors.add(UnknownFlagError(symbol, flag))
}
}
return errors
}
fun main(args: Array<String>) = CheckCommand().main(args)