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