blob: 427be36254d386f6e71e80fd0ff5f1984dc27595 [file] [log] [blame]
Yiming Pan823c15d2024-04-01 07:30:27 +00001/*
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
17package android.platform.coverage
18
19import com.google.common.truth.extensions.proto.ProtoTruth.assertThat
20import com.google.protobuf.TextFormat
21import java.nio.file.Files
22import java.nio.file.Path
23import java.nio.file.StandardOpenOption
24import org.junit.After
25import org.junit.Before
26import org.junit.Test
27import org.junit.runner.RunWith
28import org.junit.runners.JUnit4
29
30@RunWith(JUnit4::class)
31class 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
41 fun setup() {
42 apiTextFile = Files.createTempFile("current", ".txt")
43 flagToApiMap = Files.createTempFile("flag_api_map", ".textproto")
44 }
45
46 @After
47 fun cleanup() {
48 Files.deleteIfExists(apiTextFile)
49 Files.deleteIfExists(flagToApiMap)
50 }
51
52 @Test
53 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
84 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
115 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
Yiming Pandd35c2f2024-04-10 23:07:29 +0000144 @Test
145 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
184 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
Yiming Pan823c15d2024-04-01 07:30:27 +0000222 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
233 private fun createCommand(): Array<String> {
234 val command =
235 String.format(COMMAND, apiTextFile.toAbsolutePath(), flagToApiMap.toAbsolutePath())
236 return command.split(" ").toTypedArray()
237 }
238}