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 package com.android.checkflaggedapis
17
18 import android.aconfig.Aconfig
19 import android.aconfig.Aconfig.flag_state.DISABLED
20 import android.aconfig.Aconfig.flag_state.ENABLED
21 import java.io.ByteArrayInputStream
22 import java.io.ByteArrayOutputStream
23 import java.io.InputStream
24 import org.junit.Assert.assertEquals
25 import org.junit.Test
26 import org.junit.runner.RunWith
27 import org.junit.runners.JUnit4
28
29 private val API_SIGNATURE =
30 """
31 // Signature format: 2.0
32 package android {
33 @FlaggedApi("android.flag.foo") public final class Clazz {
34 ctor @FlaggedApi("android.flag.foo") public Clazz();
35 field @FlaggedApi("android.flag.foo") public static final int FOO = 1; // 0x1
36 method @FlaggedApi("android.flag.foo") public int getErrorCode();
37 method @FlaggedApi("android.flag.foo") public boolean setData(int, int[][], @NonNull android.util.Utility<T, U>);
38 method @FlaggedApi("android.flag.foo") public boolean setVariableData(int, android.util.Atom...);
39 method @FlaggedApi("android.flag.foo") public boolean innerClassArg(android.Clazz.Builder);
40 }
41 @FlaggedApi("android.flag.bar") public static class Clazz.Builder {
42 }
43 }
44 """
45 .trim()
46
47 private val API_VERSIONS =
48 """
49 <?xml version="1.0" encoding="utf-8"?>
50 <api version="3">
51 <class name="android/Clazz" since="1">
52 <extends name="java/lang/Object"/>
53 <method name="<init>()V"/>
54 <field name="FOO"/>
55 <method name="getErrorCode()I"/>
56 <method name="setData(I[[ILandroid/util/Utility;)Z"/>
57 <method name="setVariableData(I[Landroid/util/Atom;)Z"/>
58 <method name="innerClassArg(Landroid/Clazz${"$"}Builder;)"/>
59 </class>
60 <class name="android/Clazz${"$"}Builder" since="2">
61 <extends name="java/lang/Object"/>
62 </class>
63 </api>
64 """
65 .trim()
66
generateFlagsProtonull67 private fun generateFlagsProto(
68 fooState: Aconfig.flag_state,
69 barState: Aconfig.flag_state
70 ): InputStream {
71 val fooFlag =
72 Aconfig.parsed_flag
73 .newBuilder()
74 .setPackage("android.flag")
75 .setName("foo")
76 .setState(fooState)
77 .setPermission(Aconfig.flag_permission.READ_ONLY)
78 .build()
79 val barFlag =
80 Aconfig.parsed_flag
81 .newBuilder()
82 .setPackage("android.flag")
83 .setName("bar")
84 .setState(barState)
85 .setPermission(Aconfig.flag_permission.READ_ONLY)
86 .build()
87 val flags =
88 Aconfig.parsed_flags.newBuilder().addParsedFlag(fooFlag).addParsedFlag(barFlag).build()
89 val binaryProto = ByteArrayOutputStream()
90 flags.writeTo(binaryProto)
91 return ByteArrayInputStream(binaryProto.toByteArray())
92 }
93
94 @RunWith(JUnit4::class)
95 class CheckFlaggedApisTest {
96 @Test
testParseApiSignaturenull97 fun testParseApiSignature() {
98 val expected =
99 setOf(
100 Pair(
101 Symbol.createClass("android/Clazz", "java/lang/Object", setOf()),
102 Flag("android.flag.foo")),
103 Pair(Symbol.createMethod("android/Clazz", "Clazz()"), Flag("android.flag.foo")),
104 Pair(Symbol.createField("android/Clazz", "FOO"), Flag("android.flag.foo")),
105 Pair(Symbol.createMethod("android/Clazz", "getErrorCode()"), Flag("android.flag.foo")),
106 Pair(
107 Symbol.createMethod("android/Clazz", "setData(I[[ILandroid/util/Utility;)"),
108 Flag("android.flag.foo")),
109 Pair(
110 Symbol.createMethod("android/Clazz", "setVariableData(I[Landroid/util/Atom;)"),
111 Flag("android.flag.foo")),
112 Pair(
113 Symbol.createMethod("android/Clazz", "innerClassArg(Landroid/Clazz/Builder;)"),
114 Flag("android.flag.foo")),
115 Pair(
116 Symbol.createClass("android/Clazz/Builder", "java/lang/Object", setOf()),
117 Flag("android.flag.bar")),
118 )
119 val actual = parseApiSignature("in-memory", API_SIGNATURE.byteInputStream())
120 assertEquals(expected, actual)
121 }
122
123 @Test
testParseApiSignatureInterfacesInheritFromJavaLangObjectnull124 fun testParseApiSignatureInterfacesInheritFromJavaLangObject() {
125 val apiSignature =
126 """
127 // Signature format: 2.0
128 package android {
129 @FlaggedApi("android.flag.foo") public interface Interface {
130 }
131 }
132 """
133 .trim()
134 val expected =
135 setOf(
136 Pair(
137 Symbol.createClass("android/Interface", "java/lang/Object", setOf()),
138 Flag("android.flag.foo")))
139 val actual = parseApiSignature("in-memory", apiSignature.byteInputStream())
140 assertEquals(expected, actual)
141 }
142
143 @Test
testParseFlagValuesnull144 fun testParseFlagValues() {
145 val expected: Map<Flag, Boolean> =
146 mapOf(Flag("android.flag.foo") to true, Flag("android.flag.bar") to true)
147 val actual = parseFlagValues(generateFlagsProto(ENABLED, ENABLED))
148 assertEquals(expected, actual)
149 }
150
151 @Test
testParseApiVersionsnull152 fun testParseApiVersions() {
153 val expected: Set<Symbol> =
154 setOf(
155 Symbol.createClass("android/Clazz", "java/lang/Object", setOf()),
156 Symbol.createMethod("android/Clazz", "Clazz()"),
157 Symbol.createField("android/Clazz", "FOO"),
158 Symbol.createMethod("android/Clazz", "getErrorCode()"),
159 Symbol.createMethod("android/Clazz", "setData(I[[ILandroid/util/Utility;)"),
160 Symbol.createMethod("android/Clazz", "setVariableData(I[Landroid/util/Atom;)"),
161 Symbol.createMethod("android/Clazz", "innerClassArg(Landroid/Clazz/Builder;)"),
162 Symbol.createClass("android/Clazz/Builder", "java/lang/Object", setOf()),
163 )
164 val actual = parseApiVersions(API_VERSIONS.byteInputStream())
165 assertEquals(expected, actual)
166 }
167
168 @Test
testParseApiVersionsNestedClassesnull169 fun testParseApiVersionsNestedClasses() {
170 val apiVersions =
171 """
172 <?xml version="1.0" encoding="utf-8"?>
173 <api version="3">
174 <class name="android/Clazz${'$'}Foo${'$'}Bar" since="1">
175 <extends name="java/lang/Object"/>
176 <method name="<init>()V"/>
177 </class>
178 </api>
179 """
180 .trim()
181 val expected: Set<Symbol> =
182 setOf(
183 Symbol.createClass("android/Clazz/Foo/Bar", "java/lang/Object", setOf()),
184 Symbol.createMethod("android/Clazz/Foo/Bar", "Bar()"),
185 )
186 val actual = parseApiVersions(apiVersions.byteInputStream())
187 assertEquals(expected, actual)
188 }
189
190 @Test
testFindErrorsNoErrorsnull191 fun testFindErrorsNoErrors() {
192 val expected = setOf<ApiError>()
193 val actual =
194 findErrors(
195 parseApiSignature("in-memory", API_SIGNATURE.byteInputStream()),
196 parseFlagValues(generateFlagsProto(ENABLED, ENABLED)),
197 parseApiVersions(API_VERSIONS.byteInputStream()))
198 assertEquals(expected, actual)
199 }
200
201 @Test
testFindErrorsVerifyImplementsnull202 fun testFindErrorsVerifyImplements() {
203 val apiSignature =
204 """
205 // Signature format: 2.0
206 package android {
207 @FlaggedApi("android.flag.foo") public final class Clazz implements android.Interface {
208 method @FlaggedApi("android.flag.foo") public boolean foo();
209 method @FlaggedApi("android.flag.foo") public boolean bar();
210 }
211 public interface Interface {
212 method public boolean bar();
213 }
214 }
215 """
216 .trim()
217
218 val apiVersions =
219 """
220 <?xml version="1.0" encoding="utf-8"?>
221 <api version="3">
222 <class name="android/Clazz" since="1">
223 <extends name="java/lang/Object"/>
224 <implements name="android/Interface"/>
225 <method name="foo()Z"/>
226 </class>
227 <class name="android/Interface" since="1">
228 <method name="bar()Z"/>
229 </class>
230 </api>
231 """
232 .trim()
233
234 val expected = setOf<ApiError>()
235 val actual =
236 findErrors(
237 parseApiSignature("in-memory", apiSignature.byteInputStream()),
238 parseFlagValues(generateFlagsProto(ENABLED, ENABLED)),
239 parseApiVersions(apiVersions.byteInputStream()))
240 assertEquals(expected, actual)
241 }
242
243 @Test
testFindErrorsVerifySuperclassnull244 fun testFindErrorsVerifySuperclass() {
245 val apiSignature =
246 """
247 // Signature format: 2.0
248 package android {
249 @FlaggedApi("android.flag.foo") public final class C extends android.B {
250 method @FlaggedApi("android.flag.foo") public boolean c();
251 method @FlaggedApi("android.flag.foo") public boolean b();
252 method @FlaggedApi("android.flag.foo") public boolean a();
253 }
254 public final class B extends android.A {
255 method public boolean b();
256 }
257 public final class A {
258 method public boolean a();
259 }
260 }
261 """
262 .trim()
263
264 val apiVersions =
265 """
266 <?xml version="1.0" encoding="utf-8"?>
267 <api version="3">
268 <class name="android/C" since="1">
269 <extends name="android/B"/>
270 <method name="c()Z"/>
271 </class>
272 <class name="android/B" since="1">
273 <extends name="android/A"/>
274 <method name="b()Z"/>
275 </class>
276 <class name="android/A" since="1">
277 <method name="a()Z"/>
278 </class>
279 </api>
280 """
281 .trim()
282
283 val expected = setOf<ApiError>()
284 val actual =
285 findErrors(
286 parseApiSignature("in-memory", apiSignature.byteInputStream()),
287 parseFlagValues(generateFlagsProto(ENABLED, ENABLED)),
288 parseApiVersions(apiVersions.byteInputStream()))
289 assertEquals(expected, actual)
290 }
291
292 @Test
testNestedFlagsOuterFlagWinsnull293 fun testNestedFlagsOuterFlagWins() {
294 val apiSignature =
295 """
296 // Signature format: 2.0
297 package android {
298 @FlaggedApi("android.flag.foo") public final class A {
299 method @FlaggedApi("android.flag.bar") public boolean method();
300 }
301 @FlaggedApi("android.flag.bar") public final class B {
302 method @FlaggedApi("android.flag.foo") public boolean method();
303 }
304 }
305 """
306 .trim()
307
308 val apiVersions =
309 """
310 <?xml version="1.0" encoding="utf-8"?>
311 <api version="3">
312 <class name="android/B" since="1">
313 <extends name="java/lang/Object"/>
314 </class>
315 </api>
316 """
317 .trim()
318
319 val expected = setOf<ApiError>()
320 val actual =
321 findErrors(
322 parseApiSignature("in-memory", apiSignature.byteInputStream()),
323 parseFlagValues(generateFlagsProto(DISABLED, ENABLED)),
324 parseApiVersions(apiVersions.byteInputStream()))
325 assertEquals(expected, actual)
326 }
327
328 @Test
testFindErrorsDisabledFlaggedApiIsPresentnull329 fun testFindErrorsDisabledFlaggedApiIsPresent() {
330 val expected =
331 setOf<ApiError>(
332 DisabledFlaggedApiIsPresentError(
333 Symbol.createClass("android/Clazz", "java/lang/Object", setOf()),
334 Flag("android.flag.foo")),
335 DisabledFlaggedApiIsPresentError(
336 Symbol.createMethod("android/Clazz", "Clazz()"), Flag("android.flag.foo")),
337 DisabledFlaggedApiIsPresentError(
338 Symbol.createField("android/Clazz", "FOO"), Flag("android.flag.foo")),
339 DisabledFlaggedApiIsPresentError(
340 Symbol.createMethod("android/Clazz", "getErrorCode()"), Flag("android.flag.foo")),
341 DisabledFlaggedApiIsPresentError(
342 Symbol.createMethod("android/Clazz", "setData(I[[ILandroid/util/Utility;)"),
343 Flag("android.flag.foo")),
344 DisabledFlaggedApiIsPresentError(
345 Symbol.createMethod("android/Clazz", "setVariableData(I[Landroid/util/Atom;)"),
346 Flag("android.flag.foo")),
347 DisabledFlaggedApiIsPresentError(
348 Symbol.createMethod("android/Clazz", "innerClassArg(Landroid/Clazz/Builder;)"),
349 Flag("android.flag.foo")),
350 DisabledFlaggedApiIsPresentError(
351 Symbol.createClass("android/Clazz/Builder", "java/lang/Object", setOf()),
352 Flag("android.flag.bar")),
353 )
354 val actual =
355 findErrors(
356 parseApiSignature("in-memory", API_SIGNATURE.byteInputStream()),
357 parseFlagValues(generateFlagsProto(DISABLED, DISABLED)),
358 parseApiVersions(API_VERSIONS.byteInputStream()))
359 assertEquals(expected, actual)
360 }
361
362 @Test
testListFlaggedApisnull363 fun testListFlaggedApis() {
364 val expected =
365 listOf(
366 "android.flag.bar DISABLED android/Clazz/Builder",
367 "android.flag.foo ENABLED android/Clazz",
368 "android.flag.foo ENABLED android/Clazz/Clazz()",
369 "android.flag.foo ENABLED android/Clazz/FOO",
370 "android.flag.foo ENABLED android/Clazz/getErrorCode()",
371 "android.flag.foo ENABLED android/Clazz/innerClassArg(Landroid/Clazz/Builder;)",
372 "android.flag.foo ENABLED android/Clazz/setData(I[[ILandroid/util/Utility;)",
373 "android.flag.foo ENABLED android/Clazz/setVariableData(I[Landroid/util/Atom;)")
374 val actual =
375 listFlaggedApis(
376 parseApiSignature("in-memory", API_SIGNATURE.byteInputStream()),
377 parseFlagValues(generateFlagsProto(ENABLED, DISABLED)))
378 assertEquals(expected, actual)
379 }
380 }
381