Merge "check-flagged-apis: create list of @FlaggedApi errors" into main
diff --git a/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt b/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt
index 9e6a6e3..d2b75d4 100644
--- a/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt
+++ b/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt
@@ -20,6 +20,7 @@
 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 @@
 """
         .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 @@
 """
         .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 @@
   @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 @@
     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)
+  }
 }
diff --git a/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt b/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt
index e7eff17..84564ba 100644
--- a/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt
+++ b/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt
@@ -81,6 +81,36 @@
   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 @@
           .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 @@
   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)