xref: /aosp_15_r20/frameworks/base/api/coverage/tools/ExtractFlaggedApisTest.kt (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1*d57664e9SAndroid Build Coastguard Worker /*
2*d57664e9SAndroid Build Coastguard Worker  * Copyright (C) 2024 The Android Open Source Project
3*d57664e9SAndroid Build Coastguard Worker  *
4*d57664e9SAndroid Build Coastguard Worker  * Licensed under the Apache License, Version 2.0 (the "License");
5*d57664e9SAndroid Build Coastguard Worker  * you may not use this file except in compliance with the License.
6*d57664e9SAndroid Build Coastguard Worker  * You may obtain a copy of the License at
7*d57664e9SAndroid Build Coastguard Worker  *
8*d57664e9SAndroid Build Coastguard Worker  *      http://www.apache.org/licenses/LICENSE-2.0
9*d57664e9SAndroid Build Coastguard Worker  *
10*d57664e9SAndroid Build Coastguard Worker  * Unless required by applicable law or agreed to in writing, software
11*d57664e9SAndroid Build Coastguard Worker  * distributed under the License is distributed on an "AS IS" BASIS,
12*d57664e9SAndroid Build Coastguard Worker  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*d57664e9SAndroid Build Coastguard Worker  * See the License for the specific language governing permissions and
14*d57664e9SAndroid Build Coastguard Worker  * limitations under the License.
15*d57664e9SAndroid Build Coastguard Worker  */
16*d57664e9SAndroid Build Coastguard Worker 
17*d57664e9SAndroid Build Coastguard Worker package android.platform.coverage
18*d57664e9SAndroid Build Coastguard Worker 
19*d57664e9SAndroid Build Coastguard Worker import com.google.common.truth.extensions.proto.ProtoTruth.assertThat
20*d57664e9SAndroid Build Coastguard Worker import com.google.protobuf.TextFormat
21*d57664e9SAndroid Build Coastguard Worker import java.nio.file.Files
22*d57664e9SAndroid Build Coastguard Worker import java.nio.file.Path
23*d57664e9SAndroid Build Coastguard Worker import java.nio.file.StandardOpenOption
24*d57664e9SAndroid Build Coastguard Worker import org.junit.After
25*d57664e9SAndroid Build Coastguard Worker import org.junit.Before
26*d57664e9SAndroid Build Coastguard Worker import org.junit.Test
27*d57664e9SAndroid Build Coastguard Worker import org.junit.runner.RunWith
28*d57664e9SAndroid Build Coastguard Worker import org.junit.runners.JUnit4
29*d57664e9SAndroid Build Coastguard Worker 
30*d57664e9SAndroid Build Coastguard Worker @RunWith(JUnit4::class)
31*d57664e9SAndroid Build Coastguard Worker class ExtractFlaggedApisTest {
32*d57664e9SAndroid Build Coastguard Worker 
33*d57664e9SAndroid Build Coastguard Worker     companion object {
34*d57664e9SAndroid Build Coastguard Worker         const val COMMAND = "java -jar extract-flagged-apis.jar %s %s"
35*d57664e9SAndroid Build Coastguard Worker     }
36*d57664e9SAndroid Build Coastguard Worker 
37*d57664e9SAndroid Build Coastguard Worker     private var apiTextFile: Path = Files.createTempFile("current", ".txt")
38*d57664e9SAndroid Build Coastguard Worker     private var flagToApiMap: Path = Files.createTempFile("flag_api_map", ".textproto")
39*d57664e9SAndroid Build Coastguard Worker 
40*d57664e9SAndroid Build Coastguard Worker     @Before
setupnull41*d57664e9SAndroid Build Coastguard Worker     fun setup() {
42*d57664e9SAndroid Build Coastguard Worker         apiTextFile = Files.createTempFile("current", ".txt")
43*d57664e9SAndroid Build Coastguard Worker         flagToApiMap = Files.createTempFile("flag_api_map", ".textproto")
44*d57664e9SAndroid Build Coastguard Worker     }
45*d57664e9SAndroid Build Coastguard Worker 
46*d57664e9SAndroid Build Coastguard Worker     @After
cleanupnull47*d57664e9SAndroid Build Coastguard Worker     fun cleanup() {
48*d57664e9SAndroid Build Coastguard Worker         Files.deleteIfExists(apiTextFile)
49*d57664e9SAndroid Build Coastguard Worker         Files.deleteIfExists(flagToApiMap)
50*d57664e9SAndroid Build Coastguard Worker     }
51*d57664e9SAndroid Build Coastguard Worker 
52*d57664e9SAndroid Build Coastguard Worker     @Test
extractFlaggedApis_onlyMethodFlag_useMethodFlagnull53*d57664e9SAndroid Build Coastguard Worker     fun extractFlaggedApis_onlyMethodFlag_useMethodFlag() {
54*d57664e9SAndroid Build Coastguard Worker         val apiText =
55*d57664e9SAndroid Build Coastguard Worker             """
56*d57664e9SAndroid Build Coastguard Worker             // Signature format: 2.0
57*d57664e9SAndroid Build Coastguard Worker             package android.net.ipsec.ike {
58*d57664e9SAndroid Build Coastguard Worker               public final class IkeSession implements java.lang.AutoCloseable {
59*d57664e9SAndroid Build Coastguard Worker                 method @FlaggedApi("com.android.ipsec.flags.dumpsys_api") public void dump(@NonNull java.io.PrintWriter);
60*d57664e9SAndroid Build Coastguard Worker               }
61*d57664e9SAndroid Build Coastguard Worker             }
62*d57664e9SAndroid Build Coastguard Worker         """
63*d57664e9SAndroid Build Coastguard Worker                 .trimIndent()
64*d57664e9SAndroid Build Coastguard Worker         Files.write(apiTextFile, apiText.toByteArray(Charsets.UTF_8), StandardOpenOption.APPEND)
65*d57664e9SAndroid Build Coastguard Worker 
66*d57664e9SAndroid Build Coastguard Worker         val process = Runtime.getRuntime().exec(createCommand())
67*d57664e9SAndroid Build Coastguard Worker         process.waitFor()
68*d57664e9SAndroid Build Coastguard Worker 
69*d57664e9SAndroid Build Coastguard Worker         val content = Files.readAllBytes(flagToApiMap).toString(Charsets.UTF_8)
70*d57664e9SAndroid Build Coastguard Worker         val result = TextFormat.parse(content, FlagApiMap::class.java)
71*d57664e9SAndroid Build Coastguard Worker 
72*d57664e9SAndroid Build Coastguard Worker         val expected = FlagApiMap.newBuilder()
73*d57664e9SAndroid Build Coastguard Worker         val api =
74*d57664e9SAndroid Build Coastguard Worker             JavaMethod.newBuilder()
75*d57664e9SAndroid Build Coastguard Worker                 .setPackageName("android.net.ipsec.ike")
76*d57664e9SAndroid Build Coastguard Worker                 .setClassName("IkeSession")
77*d57664e9SAndroid Build Coastguard Worker                 .setMethodName("dump")
78*d57664e9SAndroid Build Coastguard Worker         api.addParameters("java.io.PrintWriter")
79*d57664e9SAndroid Build Coastguard Worker         addFlaggedApi(expected, api, "com.android.ipsec.flags.dumpsys_api")
80*d57664e9SAndroid Build Coastguard Worker         assertThat(result).isEqualTo(expected.build())
81*d57664e9SAndroid Build Coastguard Worker     }
82*d57664e9SAndroid Build Coastguard Worker 
83*d57664e9SAndroid Build Coastguard Worker     @Test
extractFlaggedApis_onlyClassFlag_useClassFlagnull84*d57664e9SAndroid Build Coastguard Worker     fun extractFlaggedApis_onlyClassFlag_useClassFlag() {
85*d57664e9SAndroid Build Coastguard Worker         val apiText =
86*d57664e9SAndroid Build Coastguard Worker             """
87*d57664e9SAndroid Build Coastguard Worker             // Signature format: 2.0
88*d57664e9SAndroid Build Coastguard Worker             package android.net.ipsec.ike {
89*d57664e9SAndroid Build Coastguard Worker               @FlaggedApi("com.android.ipsec.flags.dumpsys_api") public final class IkeSession implements java.lang.AutoCloseable {
90*d57664e9SAndroid Build Coastguard Worker                 method public void dump(@NonNull java.io.PrintWriter);
91*d57664e9SAndroid Build Coastguard Worker               }
92*d57664e9SAndroid Build Coastguard Worker             }
93*d57664e9SAndroid Build Coastguard Worker         """
94*d57664e9SAndroid Build Coastguard Worker                 .trimIndent()
95*d57664e9SAndroid Build Coastguard Worker         Files.write(apiTextFile, apiText.toByteArray(Charsets.UTF_8), StandardOpenOption.APPEND)
96*d57664e9SAndroid Build Coastguard Worker 
97*d57664e9SAndroid Build Coastguard Worker         val process = Runtime.getRuntime().exec(createCommand())
98*d57664e9SAndroid Build Coastguard Worker         process.waitFor()
99*d57664e9SAndroid Build Coastguard Worker 
100*d57664e9SAndroid Build Coastguard Worker         val content = Files.readAllBytes(flagToApiMap).toString(Charsets.UTF_8)
101*d57664e9SAndroid Build Coastguard Worker         val result = TextFormat.parse(content, FlagApiMap::class.java)
102*d57664e9SAndroid Build Coastguard Worker 
103*d57664e9SAndroid Build Coastguard Worker         val expected = FlagApiMap.newBuilder()
104*d57664e9SAndroid Build Coastguard Worker         val api =
105*d57664e9SAndroid Build Coastguard Worker             JavaMethod.newBuilder()
106*d57664e9SAndroid Build Coastguard Worker                 .setPackageName("android.net.ipsec.ike")
107*d57664e9SAndroid Build Coastguard Worker                 .setClassName("IkeSession")
108*d57664e9SAndroid Build Coastguard Worker                 .setMethodName("dump")
109*d57664e9SAndroid Build Coastguard Worker         api.addParameters("java.io.PrintWriter")
110*d57664e9SAndroid Build Coastguard Worker         addFlaggedApi(expected, api, "com.android.ipsec.flags.dumpsys_api")
111*d57664e9SAndroid Build Coastguard Worker         assertThat(result).isEqualTo(expected.build())
112*d57664e9SAndroid Build Coastguard Worker     }
113*d57664e9SAndroid Build Coastguard Worker 
114*d57664e9SAndroid Build Coastguard Worker     @Test
extractFlaggedApis_flaggedConstructorsAreFlaggedApisnull115*d57664e9SAndroid Build Coastguard Worker     fun extractFlaggedApis_flaggedConstructorsAreFlaggedApis() {
116*d57664e9SAndroid Build Coastguard Worker         val apiText =
117*d57664e9SAndroid Build Coastguard Worker             """
118*d57664e9SAndroid Build Coastguard Worker             // Signature format: 2.0
119*d57664e9SAndroid Build Coastguard Worker             package android.app.pinner {
120*d57664e9SAndroid Build Coastguard Worker               @FlaggedApi("android.app.pinner_service_client_api") public class PinnerServiceClient {
121*d57664e9SAndroid Build Coastguard Worker                 ctor @FlaggedApi("android.app.pinner_service_client_api") public PinnerServiceClient();
122*d57664e9SAndroid Build Coastguard Worker               }
123*d57664e9SAndroid Build Coastguard Worker             }
124*d57664e9SAndroid Build Coastguard Worker         """
125*d57664e9SAndroid Build Coastguard Worker                 .trimIndent()
126*d57664e9SAndroid Build Coastguard Worker         Files.write(apiTextFile, apiText.toByteArray(Charsets.UTF_8), StandardOpenOption.APPEND)
127*d57664e9SAndroid Build Coastguard Worker 
128*d57664e9SAndroid Build Coastguard Worker         val process = Runtime.getRuntime().exec(createCommand())
129*d57664e9SAndroid Build Coastguard Worker         process.waitFor()
130*d57664e9SAndroid Build Coastguard Worker 
131*d57664e9SAndroid Build Coastguard Worker         val content = Files.readAllBytes(flagToApiMap).toString(Charsets.UTF_8)
132*d57664e9SAndroid Build Coastguard Worker         val result = TextFormat.parse(content, FlagApiMap::class.java)
133*d57664e9SAndroid Build Coastguard Worker 
134*d57664e9SAndroid Build Coastguard Worker         val expected = FlagApiMap.newBuilder()
135*d57664e9SAndroid Build Coastguard Worker         val api =
136*d57664e9SAndroid Build Coastguard Worker             JavaMethod.newBuilder()
137*d57664e9SAndroid Build Coastguard Worker                 .setPackageName("android.app.pinner")
138*d57664e9SAndroid Build Coastguard Worker                 .setClassName("PinnerServiceClient")
139*d57664e9SAndroid Build Coastguard Worker                 .setMethodName("PinnerServiceClient")
140*d57664e9SAndroid Build Coastguard Worker         addFlaggedApi(expected, api, "android.app.pinner_service_client_api")
141*d57664e9SAndroid Build Coastguard Worker         assertThat(result).isEqualTo(expected.build())
142*d57664e9SAndroid Build Coastguard Worker     }
143*d57664e9SAndroid Build Coastguard Worker 
144*d57664e9SAndroid Build Coastguard Worker     @Test
extractFlaggedApis_unflaggedNestedClassShouldUseOuterClassFlagnull145*d57664e9SAndroid Build Coastguard Worker     fun extractFlaggedApis_unflaggedNestedClassShouldUseOuterClassFlag() {
146*d57664e9SAndroid Build Coastguard Worker         val apiText =
147*d57664e9SAndroid Build Coastguard Worker             """
148*d57664e9SAndroid Build Coastguard Worker             // Signature format: 2.0
149*d57664e9SAndroid Build Coastguard Worker             package android.location.provider {
150*d57664e9SAndroid Build Coastguard Worker               @FlaggedApi(Flags.FLAG_NEW_GEOCODER) public final class ForwardGeocodeRequest implements android.os.Parcelable {
151*d57664e9SAndroid Build Coastguard Worker                 method public int describeContents();
152*d57664e9SAndroid Build Coastguard Worker               }
153*d57664e9SAndroid Build Coastguard Worker               public static final class ForwardGeocodeRequest.Builder {
154*d57664e9SAndroid Build Coastguard Worker                 method @NonNull public android.location.provider.ForwardGeocodeRequest build();
155*d57664e9SAndroid Build Coastguard Worker               }
156*d57664e9SAndroid Build Coastguard Worker             }
157*d57664e9SAndroid Build Coastguard Worker         """
158*d57664e9SAndroid Build Coastguard Worker                 .trimIndent()
159*d57664e9SAndroid Build Coastguard Worker         Files.write(apiTextFile, apiText.toByteArray(Charsets.UTF_8), StandardOpenOption.APPEND)
160*d57664e9SAndroid Build Coastguard Worker 
161*d57664e9SAndroid Build Coastguard Worker         val process = Runtime.getRuntime().exec(createCommand())
162*d57664e9SAndroid Build Coastguard Worker         process.waitFor()
163*d57664e9SAndroid Build Coastguard Worker 
164*d57664e9SAndroid Build Coastguard Worker         val content = Files.readAllBytes(flagToApiMap).toString(Charsets.UTF_8)
165*d57664e9SAndroid Build Coastguard Worker         val result = TextFormat.parse(content, FlagApiMap::class.java)
166*d57664e9SAndroid Build Coastguard Worker 
167*d57664e9SAndroid Build Coastguard Worker         val expected = FlagApiMap.newBuilder()
168*d57664e9SAndroid Build Coastguard Worker         val api1 =
169*d57664e9SAndroid Build Coastguard Worker             JavaMethod.newBuilder()
170*d57664e9SAndroid Build Coastguard Worker                 .setPackageName("android.location.provider")
171*d57664e9SAndroid Build Coastguard Worker                 .setClassName("ForwardGeocodeRequest")
172*d57664e9SAndroid Build Coastguard Worker                 .setMethodName("describeContents")
173*d57664e9SAndroid Build Coastguard Worker         addFlaggedApi(expected, api1, "Flags.FLAG_NEW_GEOCODER")
174*d57664e9SAndroid Build Coastguard Worker         val api2 =
175*d57664e9SAndroid Build Coastguard Worker             JavaMethod.newBuilder()
176*d57664e9SAndroid Build Coastguard Worker                 .setPackageName("android.location.provider")
177*d57664e9SAndroid Build Coastguard Worker                 .setClassName("ForwardGeocodeRequest.Builder")
178*d57664e9SAndroid Build Coastguard Worker                 .setMethodName("build")
179*d57664e9SAndroid Build Coastguard Worker         addFlaggedApi(expected, api2, "Flags.FLAG_NEW_GEOCODER")
180*d57664e9SAndroid Build Coastguard Worker         assertThat(result).ignoringRepeatedFieldOrder().isEqualTo(expected.build())
181*d57664e9SAndroid Build Coastguard Worker     }
182*d57664e9SAndroid Build Coastguard Worker 
183*d57664e9SAndroid Build Coastguard Worker     @Test
extractFlaggedApis_unflaggedNestedClassShouldUseOuterClassFlag_deeplyNestednull184*d57664e9SAndroid Build Coastguard Worker     fun extractFlaggedApis_unflaggedNestedClassShouldUseOuterClassFlag_deeplyNested() {
185*d57664e9SAndroid Build Coastguard Worker         val apiText =
186*d57664e9SAndroid Build Coastguard Worker             """
187*d57664e9SAndroid Build Coastguard Worker             // Signature format: 2.0
188*d57664e9SAndroid Build Coastguard Worker             package android.package.xyz {
189*d57664e9SAndroid Build Coastguard Worker               @FlaggedApi(outer_class_flag) public final class OuterClass {
190*d57664e9SAndroid Build Coastguard Worker                 method public int apiInOuterClass();
191*d57664e9SAndroid Build Coastguard Worker               }
192*d57664e9SAndroid Build Coastguard Worker               public final class OuterClass.Deeply.NestedClass {
193*d57664e9SAndroid Build Coastguard Worker                 method public void apiInNestedClass();
194*d57664e9SAndroid Build Coastguard Worker               }
195*d57664e9SAndroid Build Coastguard Worker             }
196*d57664e9SAndroid Build Coastguard Worker         """
197*d57664e9SAndroid Build Coastguard Worker                 .trimIndent()
198*d57664e9SAndroid Build Coastguard Worker         Files.write(apiTextFile, apiText.toByteArray(Charsets.UTF_8), StandardOpenOption.APPEND)
199*d57664e9SAndroid Build Coastguard Worker 
200*d57664e9SAndroid Build Coastguard Worker         val process = Runtime.getRuntime().exec(createCommand())
201*d57664e9SAndroid Build Coastguard Worker         process.waitFor()
202*d57664e9SAndroid Build Coastguard Worker 
203*d57664e9SAndroid Build Coastguard Worker         val content = Files.readAllBytes(flagToApiMap).toString(Charsets.UTF_8)
204*d57664e9SAndroid Build Coastguard Worker         val result = TextFormat.parse(content, FlagApiMap::class.java)
205*d57664e9SAndroid Build Coastguard Worker 
206*d57664e9SAndroid Build Coastguard Worker         val expected = FlagApiMap.newBuilder()
207*d57664e9SAndroid Build Coastguard Worker         val api1 =
208*d57664e9SAndroid Build Coastguard Worker             JavaMethod.newBuilder()
209*d57664e9SAndroid Build Coastguard Worker                 .setPackageName("android.package.xyz")
210*d57664e9SAndroid Build Coastguard Worker                 .setClassName("OuterClass")
211*d57664e9SAndroid Build Coastguard Worker                 .setMethodName("apiInOuterClass")
212*d57664e9SAndroid Build Coastguard Worker         addFlaggedApi(expected, api1, "outer_class_flag")
213*d57664e9SAndroid Build Coastguard Worker         val api2 =
214*d57664e9SAndroid Build Coastguard Worker             JavaMethod.newBuilder()
215*d57664e9SAndroid Build Coastguard Worker                 .setPackageName("android.package.xyz")
216*d57664e9SAndroid Build Coastguard Worker                 .setClassName("OuterClass.Deeply.NestedClass")
217*d57664e9SAndroid Build Coastguard Worker                 .setMethodName("apiInNestedClass")
218*d57664e9SAndroid Build Coastguard Worker         addFlaggedApi(expected, api2, "outer_class_flag")
219*d57664e9SAndroid Build Coastguard Worker         assertThat(result).ignoringRepeatedFieldOrder().isEqualTo(expected.build())
220*d57664e9SAndroid Build Coastguard Worker     }
221*d57664e9SAndroid Build Coastguard Worker 
addFlaggedApinull222*d57664e9SAndroid Build Coastguard Worker     private fun addFlaggedApi(builder: FlagApiMap.Builder, api: JavaMethod.Builder, flag: String) {
223*d57664e9SAndroid Build Coastguard Worker         if (builder.containsFlagToApi(flag)) {
224*d57664e9SAndroid Build Coastguard Worker             val updatedApis =
225*d57664e9SAndroid Build Coastguard Worker                 builder.getFlagToApiOrThrow(flag).toBuilder().addJavaMethods(api).build()
226*d57664e9SAndroid Build Coastguard Worker             builder.putFlagToApi(flag, updatedApis)
227*d57664e9SAndroid Build Coastguard Worker         } else {
228*d57664e9SAndroid Build Coastguard Worker             val apis = FlaggedApis.newBuilder().addJavaMethods(api).build()
229*d57664e9SAndroid Build Coastguard Worker             builder.putFlagToApi(flag, apis)
230*d57664e9SAndroid Build Coastguard Worker         }
231*d57664e9SAndroid Build Coastguard Worker     }
232*d57664e9SAndroid Build Coastguard Worker 
createCommandnull233*d57664e9SAndroid Build Coastguard Worker     private fun createCommand(): Array<String> {
234*d57664e9SAndroid Build Coastguard Worker         val command =
235*d57664e9SAndroid Build Coastguard Worker             String.format(COMMAND, apiTextFile.toAbsolutePath(), flagToApiMap.toAbsolutePath())
236*d57664e9SAndroid Build Coastguard Worker         return command.split(" ").toTypedArray()
237*d57664e9SAndroid Build Coastguard Worker     }
238*d57664e9SAndroid Build Coastguard Worker }
239