1 /* 2 * Copyright (C) 2022 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 com.android.tools.metalava.apilevels 18 19 import kotlin.test.Test 20 import kotlin.test.assertEquals 21 import kotlin.test.assertFailsWith 22 import kotlin.test.assertTrue 23 import org.junit.Assert 24 25 class ApiToExtensionsMapTest { 26 27 /** Get an SDK version for [level]. */ sdkVersionnull28 private fun sdkVersion(level: Int) = ApiVersion.fromLevel(level) 29 30 /** Get an extension version for [level]. */ 31 private fun extensionVersion(level: Int) = ExtVersion.fromLevel(level) 32 33 @Test 34 fun `empty input`() { 35 val xml = 36 """ 37 <?xml version="1.0" encoding="utf-8"?> 38 <!-- No rules is a valid (albeit weird). --> 39 <sdk-extensions-info> 40 <sdk shortname="R-ext" name="R Extensions" id="30" reference="android/os/Build${'$'}VERSION_CODES${'$'}R" /> 41 <sdk shortname="S-ext" name="S Extensions" id="31" reference="android/os/Build${'$'}VERSION_CODES${'$'}S" /> 42 <sdk shortname="T-ext" name="T Extensions" id="33" reference="android/os/Build${'$'}VERSION_CODES${'$'}T" /> 43 </sdk-extensions-info> 44 """ 45 .trimIndent() 46 val map = ApiToExtensionsMap.fromXml("no-module", xml) 47 48 assertTrue(map.getExtensions("com.foo.Bar").isEmpty()) 49 } 50 51 @Test wildcardnull52 fun wildcard() { 53 val xml = 54 """ 55 <?xml version="1.0" encoding="utf-8"?> 56 <!-- All APIs will default to extension SDK A. --> 57 <sdk-extensions-info> 58 <sdk shortname="A" name="A Extensions" id="1" reference="android/os/Build${'$'}VERSION_CODES${'$'}A" /> 59 <symbol jar="mod" pattern="*" sdks="A" /> 60 </sdk-extensions-info> 61 """ 62 .trimIndent() 63 val map = ApiToExtensionsMap.fromXml("mod", xml) 64 65 assertEquals(map.getExtensions("com.foo.Bar"), listOf("A")) 66 assertEquals(map.getExtensions("com.foo.SomeOtherBar"), listOf("A")) 67 } 68 69 @Test single classnull70 fun `single class`() { 71 val xml = 72 """ 73 <?xml version="1.0" encoding="utf-8"?> 74 <!-- A single class. The class, any internal classes, and any methods are allowed; 75 everything else is denied --> 76 <sdk-extensions-info> 77 <sdk shortname="A" name="A Extensions" id="1" reference="android/os/Build${'$'}VERSION_CODES${'$'}A" /> 78 <symbol jar="mod" pattern="com.foo.Bar" sdks="A" /> 79 </sdk-extensions-info> 80 """ 81 .trimIndent() 82 val map = ApiToExtensionsMap.fromXml("mod", xml) 83 84 assertEquals(map.getExtensions("com.foo.Bar"), listOf("A")) 85 assertEquals(map.getExtensions("com.foo.Bar#FIELD"), listOf("A")) 86 assertEquals(map.getExtensions("com.foo.Bar#method"), listOf("A")) 87 assertEquals(map.getExtensions("com.foo.Bar\$Inner"), listOf("A")) 88 assertEquals(map.getExtensions("com.foo.Bar\$Inner\$InnerInner"), listOf("A")) 89 90 val sdk1 = sdkVersion(1) 91 val sdk2 = sdkVersion(2) 92 93 val clazz = ApiClass("com/foo/Bar").apply { update(sdk1, false) } 94 val method = ApiElement("method(Ljava.lang.String;I)V").apply { update(sdk2, false) } 95 assertEquals(map.getExtensions(clazz), listOf("A")) 96 assertEquals(map.getExtensions(clazz, method), listOf("A")) 97 98 assertTrue(map.getExtensions("com.foo.SomeOtherClass").isEmpty()) 99 } 100 101 @Test multiple extensionsnull102 fun `multiple extensions`() { 103 val xml = 104 """ 105 <?xml version="1.0" encoding="utf-8"?> 106 <!-- Any number of white space separated extension SDKs may be listed. --> 107 <sdk-extensions-info> 108 <sdk shortname="A" name="A Extensions" id="1" reference="android/os/Build${'$'}VERSION_CODES${'$'}A" /> 109 <sdk shortname="B" name="B Extensions" id="2" reference="android/os/Build${'$'}VERSION_CODES${'$'}B" /> 110 <sdk shortname="FOO" name="FOO Extensions" id="10" reference="android/os/Build${'$'}VERSION_CODES${'$'}FOO" /> 111 <sdk shortname="BAR" name="BAR Extensions" id="11" reference="android/os/Build${'$'}VERSION_CODES${'$'}BAR" /> 112 <symbol jar="mod" pattern="*" sdks="A,B,FOO,BAR" /> 113 </sdk-extensions-info> 114 """ 115 .trimIndent() 116 val map = ApiToExtensionsMap.fromXml("mod", xml) 117 118 assertEquals(listOf("A", "B", "FOO", "BAR"), map.getExtensions("com.foo.Bar")) 119 } 120 121 @Test precedencenull122 fun precedence() { 123 val xml = 124 """ 125 <?xml version="1.0" encoding="utf-8"?> 126 <!-- Multiple classes, and multiple rules with different precedence. --> 127 <sdk-extensions-info> 128 <sdk shortname="A" name="A Extensions" id="1" reference="android/os/Build${'$'}VERSION_CODES${'$'}A" /> 129 <sdk shortname="B" name="B Extensions" id="2" reference="android/os/Build${'$'}VERSION_CODES${'$'}B" /> 130 <sdk shortname="C" name="C Extensions" id="3" reference="android/os/Build${'$'}VERSION_CODES${'$'}C" /> 131 <sdk shortname="D" name="D Extensions" id="4" reference="android/os/Build${'$'}VERSION_CODES${'$'}D" /> 132 <symbol jar="mod" pattern="*" sdks="A" /> 133 <symbol jar="mod" pattern="com.foo.Bar" sdks="B" /> 134 <symbol jar="mod" pattern="com.foo.Bar${'$'}Inner#method" sdks="C" /> 135 <symbol jar="mod" pattern="com.bar.Foo" sdks="D" /> 136 </sdk-extensions-info> 137 """ 138 .trimIndent() 139 val map = ApiToExtensionsMap.fromXml("mod", xml) 140 141 assertEquals(map.getExtensions("anything"), listOf("A")) 142 143 assertEquals(map.getExtensions("com.foo.Bar"), listOf("B")) 144 assertEquals(map.getExtensions("com.foo.Bar#FIELD"), listOf("B")) 145 assertEquals(map.getExtensions("com.foo.Bar\$Inner"), listOf("B")) 146 147 assertEquals(map.getExtensions("com.foo.Bar\$Inner#method"), listOf("C")) 148 149 assertEquals(map.getExtensions("com.bar.Foo"), listOf("D")) 150 assertEquals(map.getExtensions("com.bar.Foo#FIELD"), listOf("D")) 151 } 152 153 @Test multiple mainline modulesnull154 fun `multiple mainline modules`() { 155 val xml = 156 """ 157 <?xml version="1.0" encoding="utf-8"?> 158 <!-- The allow list will only consider patterns that are marked with the given mainline module --> 159 <sdk-extensions-info> 160 <sdk shortname="A" name="A Extensions" id="1" reference="android/os/Build${'$'}VERSION_CODES${'$'}A" /> 161 <sdk shortname="B" name="B Extensions" id="2" reference="android/os/Build${'$'}VERSION_CODES${'$'}B" /> 162 <symbol jar="foo" pattern="*" sdks="A" /> 163 <symbol jar="bar" pattern="*" sdks="B" /> 164 </sdk-extensions-info> 165 """ 166 .trimIndent() 167 val allowListA = ApiToExtensionsMap.fromXml("foo", xml) 168 val allowListB = ApiToExtensionsMap.fromXml("bar", xml) 169 val allowListC = ApiToExtensionsMap.fromXml("baz", xml) 170 171 assertEquals(allowListA.getExtensions("anything"), listOf("A")) 172 assertEquals(allowListB.getExtensions("anything"), listOf("B")) 173 assertTrue(allowListC.getExtensions("anything").isEmpty()) 174 } 175 176 @Test declarations and rules can be mixednull177 fun `declarations and rules can be mixed`() { 178 val xml = 179 """ 180 <?xml version="1.0" encoding="utf-8"?> 181 <!-- SDK declarations and rule lines can be mixed in any order --> 182 <sdk-extensions-info> 183 <sdk shortname="A" name="A Extensions" id="1" reference="android/os/Build${'$'}VERSION_CODES${'$'}A" /> 184 <symbol jar="foo" pattern="*" sdks="A,B" /> 185 <sdk shortname="B" name="B Extensions" id="2" reference="android/os/Build${'$'}VERSION_CODES${'$'}B" /> 186 </sdk-extensions-info> 187 """ 188 .trimIndent() 189 val map = ApiToExtensionsMap.fromXml("foo", xml) 190 191 assertEquals(map.getExtensions("com.foo.Bar"), listOf("A", "B")) 192 } 193 194 @Test bad inputnull195 fun `bad input`() { 196 assertFailsWith<IllegalArgumentException> { 197 ApiToExtensionsMap.fromXml( 198 "mod", 199 """ 200 <?xml version="1.0" encoding="utf-8"?> 201 <!-- Missing root element --> 202 <sdk shortname="A" name="A Extensions" id="1" reference="android/os/Build${'$'}VERSION_CODES${'$'}A" /> 203 <symbol jar="mod" pattern="com.foo.Bar" sdks="A" /> 204 """ 205 .trimIndent() 206 ) 207 } 208 209 assertFailsWith<IllegalArgumentException> { 210 ApiToExtensionsMap.fromXml( 211 "mod", 212 """ 213 <?xml version="1.0" encoding="utf-8"?> 214 <!-- <sdk> tag at unexpected depth --> 215 <sdk-extensions-info version="2"> 216 <foo> 217 <sdk shortname="A" name="A Extensions" id="1" reference="android/os/Build${'$'}VERSION_CODES${'$'}A" > 218 </foo> 219 <symbol jar="mod" pattern="com.foo.Bar" sdks="A" /> 220 </sdk-extensions-info> 221 """ 222 .trimIndent() 223 ) 224 } 225 226 assertFailsWith<IllegalArgumentException> { 227 ApiToExtensionsMap.fromXml( 228 "mod", 229 """ 230 <?xml version="1.0" encoding="utf-8"?> 231 <!-- using 0 (reserved for the Android platform SDK) as ID --> 232 <sdk-extensions-info> 233 <sdk shortname="A" name="A Extensions" id="0" reference="android/os/Build${'$'}VERSION_CODES${'$'}A" /> 234 <symbol jar="mod" pattern="com.foo.Bar" sdks="A" /> 235 </sdk-extensions-info> 236 """ 237 .trimIndent() 238 ) 239 } 240 241 assertFailsWith<IllegalArgumentException> { 242 ApiToExtensionsMap.fromXml( 243 "mod", 244 """ 245 <?xml version="1.0" encoding="utf-8"?> 246 <!-- missing module attribute --> 247 <sdk-extensions-info> 248 <sdk shortname="A" name="A Extensions" id="1" reference="android/os/Build${'$'}VERSION_CODES${'$'}A" /> 249 <symbol pattern="com.foo.Bar" sdks="A" /> 250 </sdk-extensions-info> 251 """ 252 .trimIndent() 253 ) 254 } 255 256 assertFailsWith<IllegalArgumentException> { 257 ApiToExtensionsMap.fromXml( 258 "mod", 259 """ 260 <?xml version="1.0" encoding="utf-8"?> 261 <!-- duplicate module+pattern pairs --> 262 <sdk-extensions-info> 263 <sdk shortname="A" name="A Extensions" id="1" reference="android/os/Build${'$'}VERSION_CODES${'$'}A" /> 264 <symbol jar="mod" pattern="com.foo.Bar" sdks="A" /> 265 <symbol jar="mod" pattern="com.foo.Bar" sdks="B" /> 266 </sdk-extensions-info> 267 """ 268 .trimIndent() 269 ) 270 } 271 272 assertFailsWith<IllegalArgumentException> { 273 ApiToExtensionsMap.fromXml( 274 "mod", 275 """ 276 <?xml version="1.0" encoding="utf-8"?> 277 <!-- sdks attribute refer to non-declared SDK --> 278 <sdk-extensions-info> 279 <sdk shortname="B" name="A Extensions" id="2" reference="android/os/Build${'$'}VERSION_CODES${'$'}A" /> 280 <symbol jar="mod" pattern="com.foo.Bar" sdks="A" /> 281 </sdk-extensions-info> 282 """ 283 .trimIndent() 284 ) 285 } 286 287 assertFailsWith<IllegalArgumentException> { 288 ApiToExtensionsMap.fromXml( 289 "mod", 290 """ 291 <?xml version="1.0" encoding="utf-8"?> 292 <!-- duplicate numerical ID --> 293 <sdk-extensions-info> 294 <sdk shortname="A" name="A Extensions" id="1" reference="android/os/Build${'$'}VERSION_CODES${'$'}A" /> 295 <sdk shortname="B" name="B Extensions" id="1" reference="android/os/Build${'$'}VERSION_CODES${'$'}B" /> 296 <symbol jar="mod" pattern="com.foo.Bar" sdks="A" /> 297 </sdk-extensions-info> 298 """ 299 .trimIndent() 300 ) 301 } 302 303 assertFailsWith<IllegalArgumentException> { 304 ApiToExtensionsMap.fromXml( 305 "mod", 306 """ 307 <?xml version="1.0" encoding="utf-8"?> 308 <!-- duplicate short SDK name --> 309 <sdk-extensions-info> 310 <sdk shortname="A" name="A Extensions" id="1" reference="android/os/Build${'$'}VERSION_CODES${'$'}A" /> 311 <sdk shortname="A" name="B Extensions" id="2" reference="android/os/Build${'$'}VERSION_CODES${'$'}B" /> 312 <symbol jar="mod" pattern="com.foo.Bar" sdks="A" /> 313 </sdk-extensions-info> 314 """ 315 .trimIndent() 316 ) 317 } 318 319 assertFailsWith<IllegalArgumentException> { 320 ApiToExtensionsMap.fromXml( 321 "mod", 322 """ 323 <?xml version="1.0" encoding="utf-8"?> 324 <!-- duplicate long SDK name --> 325 <sdk-extensions-info> 326 <sdk shortname="A" name="A Extensions" id="1" reference="android/os/Build${'$'}VERSION_CODES${'$'}A" /> 327 <sdk shortname="B" name="A Extensions" id="2" reference="android/os/Build${'$'}VERSION_CODES${'$'}B" /> 328 <symbol jar="mod" pattern="com.foo.Bar" sdks="A" /> 329 </sdk-extensions-info> 330 """ 331 .trimIndent() 332 ) 333 } 334 335 assertFailsWith<IllegalArgumentException> { 336 ApiToExtensionsMap.fromXml( 337 "mod", 338 """ 339 <?xml version="1.0" encoding="utf-8"?> 340 <!-- duplicate SDK reference --> 341 <sdk-extensions-info version="1"> 342 <sdk shortname="A" name="A Extensions" id="1" reference="android/os/Build${'$'}VERSION_CODES${'$'}A" /> 343 <sdk shortname="B" name="B Extensions" id="2" reference="android/os/Build${'$'}VERSION_CODES${'$'}A" /> 344 <symbol jar="mod" pattern="com.foo.Bar" sdks="A" /> 345 </sdk-extensions-info> 346 """ 347 .trimIndent() 348 ) 349 } 350 351 assertFailsWith<IllegalArgumentException> { 352 ApiToExtensionsMap.fromXml( 353 "mod", 354 """ 355 <?xml version="1.0" encoding="utf-8"?> 356 <!-- duplicate SDK for same symbol --> 357 <sdk-extensions-info> 358 <sdk shortname="A" name="A Extensions" id="1" reference="android/os/Build${'$'}VERSION_CODES${'$'}A" /> 359 <sdk shortname="B" name="B Extensions" id="1" reference="android/os/Build${'$'}VERSION_CODES${'$'}B" /> 360 <symbol jar="mod" pattern="com.foo.Bar" sdks="A,B,A" /> 361 </sdk-extensions-info> 362 """ 363 .trimIndent() 364 ) 365 } 366 } 367 368 @Test calculate sdks xml attributenull369 fun `calculate sdks xml attribute`() { 370 val xml = 371 """ 372 <?xml version="1.0" encoding="utf-8"?> 373 <!-- Verify the calculateSdksAttr method --> 374 <sdk-extensions-info> 375 <sdk shortname="R" name="R Extensions" id="30" reference="android/os/Build${'$'}VERSION_CODES${'$'}R" /> 376 <sdk shortname="S" name="S Extensions" id="31" reference="android/os/Build${'$'}VERSION_CODES${'$'}S" /> 377 <sdk shortname="T" name="T Extensions" id="33" reference="android/os/Build${'$'}VERSION_CODES${'$'}T" /> 378 <sdk shortname="FOO" name="FOO Extensions" id="1000000" reference="android/os/Build${'$'}VERSION_CODES${'$'}FOO" /> 379 <sdk shortname="BAR" name="BAR Extensions" id="1000001" reference="android/os/Build${'$'}VERSION_CODES${'$'}BAR" /> 380 </sdk-extensions-info> 381 """ 382 .trimIndent() 383 val filter = ApiToExtensionsMap.fromXml("mod", xml) 384 385 val sdk21 = sdkVersion(21) 386 val sdk30 = sdkVersion(30) 387 val sdk31 = sdkVersion(31) 388 val sdk32 = sdkVersion(32) 389 val sdk33 = sdkVersion(33) 390 val sdk34 = sdkVersion(34) 391 val ext4 = extensionVersion(4) 392 393 Assert.assertEquals("0:34", filter.calculateSdksAttr(sdk34, sdk34, listOf(), null)) 394 395 Assert.assertEquals("30:4", filter.calculateSdksAttr(sdk34, sdk34, listOf("R"), ext4)) 396 397 Assert.assertEquals( 398 "30:4,31:4", 399 filter.calculateSdksAttr(sdk34, sdk34, listOf("R", "S"), ext4) 400 ) 401 402 Assert.assertEquals( 403 "30:4,31:4,0:33", 404 filter.calculateSdksAttr(sdk33, sdk34, listOf("R", "S"), ext4) 405 ) 406 407 Assert.assertEquals( 408 "30:4,31:4,1000000:4,0:33", 409 filter.calculateSdksAttr(sdk33, sdk34, listOf("R", "S", "FOO"), ext4) 410 ) 411 412 Assert.assertEquals( 413 "30:4,31:4,1000000:4,1000001:4,0:33", 414 filter.calculateSdksAttr(sdk33, sdk34, listOf("R", "S", "FOO", "BAR"), ext4) 415 ) 416 417 // Make sure that if it was released in dessert released R (30) that it is reported as being 418 // in both the extension SDK included in R (30:4) and in R itself (0:30) but not in S or T. 419 Assert.assertEquals( 420 "30:4,0:30", 421 filter.calculateSdksAttr(sdk30, sdk34, listOf("R", "S"), ext4) 422 ) 423 424 // Make sure that if it was released in dessert released S (31) that it is reported as being 425 // in both the extension SDK included in R (30:4), S (31:4) and in S itself (0:30) but not 426 // in T. 427 Assert.assertEquals( 428 "30:4,31:4,0:31", 429 filter.calculateSdksAttr(sdk31, sdk34, listOf("R", "S", "T"), ext4) 430 ) 431 432 // Make sure that if it was released in dessert released S+ (32) that it is reported as 433 // being in both the extension SDK included in R (30:4), S (31:4) and in S itself (0:30) but 434 // not in T. 435 Assert.assertEquals( 436 "30:4,31:4,0:32", 437 filter.calculateSdksAttr(sdk32, sdk34, listOf("R", "S", "T"), ext4) 438 ) 439 440 // Make sure that if it was released in dessert released T (33) that it is reported as being 441 // in both the extension SDK included in R (30:4), S (31:4), T (33:4) and T itself. 442 Assert.assertEquals( 443 "30:4,31:4,33:4,0:33", 444 filter.calculateSdksAttr(sdk33, sdk34, listOf("R", "S", "T"), ext4) 445 ) 446 447 // Make sure that if it was released in dessert release before R (21) that it is not 448 // reported as being in any sdks; it will just have `since="21"`. 449 Assert.assertEquals("", filter.calculateSdksAttr(sdk21, sdk34, listOf("R", "S"), ext4)) 450 } 451 } 452