/* * 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.checkflaggedapis import android.aconfig.Aconfig import android.aconfig.Aconfig.flag_state.DISABLED import android.aconfig.Aconfig.flag_state.ENABLED 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 import org.junit.runners.JUnit4 private val API_SIGNATURE = """ // Signature format: 2.0 package android { @FlaggedApi("android.flag.foo") public final class Clazz { ctor @FlaggedApi("android.flag.foo") public Clazz(); field @FlaggedApi("android.flag.foo") public static final int FOO = 1; // 0x1 method @FlaggedApi("android.flag.foo") public int getErrorCode(); method @FlaggedApi("android.flag.foo") public boolean setData(int, int[][], @NonNull android.util.Utility); method @FlaggedApi("android.flag.foo") public boolean setVariableData(int, android.util.Atom...); method @FlaggedApi("android.flag.foo") public boolean innerClassArg(android.Clazz.Builder); } @FlaggedApi("android.flag.bar") public static class Clazz.Builder { } } """ .trim() private val API_VERSIONS = """ """ .trim() private fun generateFlagsProto( fooState: Aconfig.flag_state, barState: Aconfig.flag_state ): InputStream { val fooFlag = Aconfig.parsed_flag .newBuilder() .setPackage("android.flag") .setName("foo") .setState(fooState) .setPermission(Aconfig.flag_permission.READ_ONLY) .build() val barFlag = Aconfig.parsed_flag .newBuilder() .setPackage("android.flag") .setName("bar") .setState(barState) .setPermission(Aconfig.flag_permission.READ_ONLY) .build() val flags = Aconfig.parsed_flags.newBuilder().addParsedFlag(fooFlag).addParsedFlag(barFlag).build() val binaryProto = ByteArrayOutputStream() flags.writeTo(binaryProto) return ByteArrayInputStream(binaryProto.toByteArray()) } @RunWith(JUnit4::class) class CheckFlaggedApisTest { @Test fun testParseApiSignature() { val expected = setOf( Pair( Symbol.createClass("android/Clazz", "java/lang/Object", setOf()), Flag("android.flag.foo")), Pair(Symbol.createMethod("android/Clazz", "Clazz()"), Flag("android.flag.foo")), Pair(Symbol.createField("android/Clazz", "FOO"), Flag("android.flag.foo")), Pair(Symbol.createMethod("android/Clazz", "getErrorCode()"), Flag("android.flag.foo")), Pair( Symbol.createMethod("android/Clazz", "setData(I[[ILandroid/util/Utility;)"), Flag("android.flag.foo")), Pair( Symbol.createMethod("android/Clazz", "setVariableData(I[Landroid/util/Atom;)"), Flag("android.flag.foo")), Pair( Symbol.createMethod("android/Clazz", "innerClassArg(Landroid/Clazz/Builder;)"), Flag("android.flag.foo")), Pair( Symbol.createClass("android/Clazz/Builder", "java/lang/Object", setOf()), Flag("android.flag.bar")), ) val actual = parseApiSignature("in-memory", API_SIGNATURE.byteInputStream()) assertEquals(expected, actual) } @Test fun testParseApiSignatureInterfacesInheritFromJavaLangObject() { val apiSignature = """ // Signature format: 2.0 package android { @FlaggedApi("android.flag.foo") public interface Interface { } } """ .trim() val expected = setOf( Pair( Symbol.createClass("android/Interface", "java/lang/Object", setOf()), Flag("android.flag.foo"))) val actual = parseApiSignature("in-memory", apiSignature.byteInputStream()) assertEquals(expected, actual) } @Test fun testParseFlagValues() { val expected: Map = mapOf(Flag("android.flag.foo") to true, Flag("android.flag.bar") to true) val actual = parseFlagValues(generateFlagsProto(ENABLED, ENABLED)) assertEquals(expected, actual) } @Test fun testParseApiVersions() { val expected: Set = setOf( Symbol.createClass("android/Clazz", "java/lang/Object", setOf()), Symbol.createMethod("android/Clazz", "Clazz()"), Symbol.createField("android/Clazz", "FOO"), Symbol.createMethod("android/Clazz", "getErrorCode()"), Symbol.createMethod("android/Clazz", "setData(I[[ILandroid/util/Utility;)"), Symbol.createMethod("android/Clazz", "setVariableData(I[Landroid/util/Atom;)"), Symbol.createMethod("android/Clazz", "innerClassArg(Landroid/Clazz/Builder;)"), Symbol.createClass("android/Clazz/Builder", "java/lang/Object", setOf()), ) val actual = parseApiVersions(API_VERSIONS.byteInputStream()) assertEquals(expected, actual) } @Test fun testParseApiVersionsNestedClasses() { val apiVersions = """ """ .trim() val expected: Set = setOf( Symbol.createClass("android/Clazz/Foo/Bar", "java/lang/Object", setOf()), Symbol.createMethod("android/Clazz/Foo/Bar", "Bar()"), ) val actual = parseApiVersions(apiVersions.byteInputStream()) assertEquals(expected, actual) } @Test fun testFindErrorsNoErrors() { val expected = setOf() val actual = findErrors( parseApiSignature("in-memory", API_SIGNATURE.byteInputStream()), parseFlagValues(generateFlagsProto(ENABLED, ENABLED)), parseApiVersions(API_VERSIONS.byteInputStream())) assertEquals(expected, actual) } @Test fun testFindErrorsVerifyImplements() { val apiSignature = """ // Signature format: 2.0 package android { @FlaggedApi("android.flag.foo") public final class Clazz implements android.Interface { method @FlaggedApi("android.flag.foo") public boolean foo(); method @FlaggedApi("android.flag.foo") public boolean bar(); } public interface Interface { method public boolean bar(); } } """ .trim() val apiVersions = """ """ .trim() val expected = setOf() val actual = findErrors( parseApiSignature("in-memory", apiSignature.byteInputStream()), parseFlagValues(generateFlagsProto(ENABLED, ENABLED)), parseApiVersions(apiVersions.byteInputStream())) assertEquals(expected, actual) } @Test fun testFindErrorsVerifySuperclass() { val apiSignature = """ // Signature format: 2.0 package android { @FlaggedApi("android.flag.foo") public final class C extends android.B { method @FlaggedApi("android.flag.foo") public boolean c(); method @FlaggedApi("android.flag.foo") public boolean b(); method @FlaggedApi("android.flag.foo") public boolean a(); } public final class B extends android.A { method public boolean b(); } public final class A { method public boolean a(); } } """ .trim() val apiVersions = """ """ .trim() val expected = setOf() val actual = findErrors( parseApiSignature("in-memory", apiSignature.byteInputStream()), parseFlagValues(generateFlagsProto(ENABLED, ENABLED)), parseApiVersions(apiVersions.byteInputStream())) assertEquals(expected, actual) } @Test fun testNestedFlagsOuterFlagWins() { val apiSignature = """ // Signature format: 2.0 package android { @FlaggedApi("android.flag.foo") public final class A { method @FlaggedApi("android.flag.bar") public boolean method(); } @FlaggedApi("android.flag.bar") public final class B { method @FlaggedApi("android.flag.foo") public boolean method(); } } """ .trim() val apiVersions = """ """ .trim() val expected = setOf() val actual = findErrors( parseApiSignature("in-memory", apiSignature.byteInputStream()), parseFlagValues(generateFlagsProto(DISABLED, ENABLED)), parseApiVersions(apiVersions.byteInputStream())) assertEquals(expected, actual) } @Test fun testFindErrorsDisabledFlaggedApiIsPresent() { val expected = setOf( DisabledFlaggedApiIsPresentError( Symbol.createClass("android/Clazz", "java/lang/Object", setOf()), Flag("android.flag.foo")), DisabledFlaggedApiIsPresentError( Symbol.createMethod("android/Clazz", "Clazz()"), Flag("android.flag.foo")), DisabledFlaggedApiIsPresentError( Symbol.createField("android/Clazz", "FOO"), Flag("android.flag.foo")), DisabledFlaggedApiIsPresentError( Symbol.createMethod("android/Clazz", "getErrorCode()"), Flag("android.flag.foo")), DisabledFlaggedApiIsPresentError( Symbol.createMethod("android/Clazz", "setData(I[[ILandroid/util/Utility;)"), Flag("android.flag.foo")), DisabledFlaggedApiIsPresentError( Symbol.createMethod("android/Clazz", "setVariableData(I[Landroid/util/Atom;)"), Flag("android.flag.foo")), DisabledFlaggedApiIsPresentError( Symbol.createMethod("android/Clazz", "innerClassArg(Landroid/Clazz/Builder;)"), Flag("android.flag.foo")), DisabledFlaggedApiIsPresentError( Symbol.createClass("android/Clazz/Builder", "java/lang/Object", setOf()), Flag("android.flag.bar")), ) val actual = findErrors( parseApiSignature("in-memory", API_SIGNATURE.byteInputStream()), parseFlagValues(generateFlagsProto(DISABLED, DISABLED)), parseApiVersions(API_VERSIONS.byteInputStream())) assertEquals(expected, actual) } @Test fun testListFlaggedApis() { val expected = listOf( "android.flag.bar DISABLED android/Clazz/Builder", "android.flag.foo ENABLED android/Clazz", "android.flag.foo ENABLED android/Clazz/Clazz()", "android.flag.foo ENABLED android/Clazz/FOO", "android.flag.foo ENABLED android/Clazz/getErrorCode()", "android.flag.foo ENABLED android/Clazz/innerClassArg(Landroid/Clazz/Builder;)", "android.flag.foo ENABLED android/Clazz/setData(I[[ILandroid/util/Utility;)", "android.flag.foo ENABLED android/Clazz/setVariableData(I[Landroid/util/Atom;)") val actual = listFlaggedApis( parseApiSignature("in-memory", API_SIGNATURE.byteInputStream()), parseFlagValues(generateFlagsProto(ENABLED, DISABLED))) assertEquals(expected, actual) } }