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