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