1// Copyright 2020 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package main 16 17import ( 18 "fmt" 19 "reflect" 20 "testing" 21 22 "github.com/golang/protobuf/proto" 23 24 bp "android/soong/cmd/extract_apks/bundle_proto" 25 "android/soong/third_party/zip" 26) 27 28type testConfigDesc struct { 29 name string 30 targetConfig TargetConfig 31 expected SelectionResult 32} 33 34type testDesc struct { 35 protoText string 36 configs []testConfigDesc 37} 38 39func TestSelectApks_ApkSet(t *testing.T) { 40 testCases := []testDesc{ 41 { 42 protoText: ` 43variant { 44 targeting { 45 sdk_version_targeting { 46 value { min { value: 29 } } } } 47 apk_set { 48 module_metadata { 49 name: "base" targeting {} delivery_type: INSTALL_TIME } 50 apk_description { 51 targeting { 52 screen_density_targeting { 53 value { density_alias: LDPI } } 54 sdk_version_targeting { 55 value { min { value: 21 } } } } 56 path: "splits/base-ldpi.apk" 57 split_apk_metadata { split_id: "config.ldpi" } } 58 apk_description { 59 targeting { 60 screen_density_targeting { 61 value { density_alias: MDPI } } 62 sdk_version_targeting { 63 value { min { value: 21 } } } } 64 path: "splits/base-mdpi.apk" 65 split_apk_metadata { split_id: "config.mdpi" } } 66 apk_description { 67 targeting { 68 sdk_version_targeting { 69 value { min { value: 21 } } } } 70 path: "splits/base-master.apk" 71 split_apk_metadata { is_master_split: true } } 72 apk_description { 73 targeting { 74 abi_targeting { 75 value { alias: ARMEABI_V7A } 76 alternatives { alias: ARM64_V8A } 77 alternatives { alias: X86 } 78 alternatives { alias: X86_64 } } 79 sdk_version_targeting { 80 value { min { value: 21 } } } } 81 path: "splits/base-armeabi_v7a.apk" 82 split_apk_metadata { split_id: "config.armeabi_v7a" } } 83 apk_description { 84 targeting { 85 abi_targeting { 86 value { alias: ARM64_V8A } 87 alternatives { alias: ARMEABI_V7A } 88 alternatives { alias: X86 } 89 alternatives { alias: X86_64 } } 90 sdk_version_targeting { 91 value { min { value: 21 } } } } 92 path: "splits/base-arm64_v8a.apk" 93 split_apk_metadata { split_id: "config.arm64_v8a" } } 94 apk_description { 95 targeting { 96 abi_targeting { 97 value { alias: X86 } 98 alternatives { alias: ARMEABI_V7A } 99 alternatives { alias: ARM64_V8A } 100 alternatives { alias: X86_64 } } 101 sdk_version_targeting { 102 value { min { value: 21 } } } } 103 path: "splits/base-x86.apk" 104 split_apk_metadata { split_id: "config.x86" } } 105 apk_description { 106 targeting { 107 abi_targeting { 108 value { alias: X86_64 } 109 alternatives { alias: ARMEABI_V7A } 110 alternatives { alias: ARM64_V8A } 111 alternatives { alias: X86 } } 112 sdk_version_targeting { 113 value { min { value: 21 } } } } 114 path: "splits/base-x86_64.apk" 115 split_apk_metadata { split_id: "config.x86_64" } } } 116} 117bundletool { 118 version: "0.10.3" } 119 120`, 121 configs: []testConfigDesc{ 122 { 123 name: "one", 124 targetConfig: TargetConfig{ 125 sdkVersion: 29, 126 screenDpi: map[bp.ScreenDensity_DensityAlias]bool{ 127 bp.ScreenDensity_DENSITY_UNSPECIFIED: true, 128 }, 129 abis: map[bp.Abi_AbiAlias]int{ 130 bp.Abi_ARMEABI_V7A: 0, 131 bp.Abi_ARM64_V8A: 1, 132 }, 133 }, 134 expected: SelectionResult{ 135 "base", 136 []string{ 137 "splits/base-ldpi.apk", 138 "splits/base-mdpi.apk", 139 "splits/base-master.apk", 140 "splits/base-armeabi_v7a.apk", 141 }, 142 }, 143 }, 144 { 145 name: "two", 146 targetConfig: TargetConfig{ 147 sdkVersion: 29, 148 screenDpi: map[bp.ScreenDensity_DensityAlias]bool{ 149 bp.ScreenDensity_LDPI: true, 150 }, 151 abis: map[bp.Abi_AbiAlias]int{}, 152 }, 153 expected: SelectionResult{ 154 "base", 155 []string{ 156 "splits/base-ldpi.apk", 157 "splits/base-master.apk", 158 }, 159 }, 160 }, 161 { 162 name: "three", 163 targetConfig: TargetConfig{ 164 sdkVersion: 20, 165 screenDpi: map[bp.ScreenDensity_DensityAlias]bool{ 166 bp.ScreenDensity_LDPI: true, 167 }, 168 abis: map[bp.Abi_AbiAlias]int{}, 169 }, 170 expected: SelectionResult{ 171 "", 172 nil, 173 }, 174 }, 175 { 176 name: "four", 177 targetConfig: TargetConfig{ 178 sdkVersion: 29, 179 screenDpi: map[bp.ScreenDensity_DensityAlias]bool{ 180 bp.ScreenDensity_MDPI: true, 181 }, 182 abis: map[bp.Abi_AbiAlias]int{ 183 bp.Abi_ARM64_V8A: 0, 184 bp.Abi_ARMEABI_V7A: 1, 185 }, 186 }, 187 expected: SelectionResult{ 188 "base", 189 []string{ 190 "splits/base-mdpi.apk", 191 "splits/base-master.apk", 192 "splits/base-arm64_v8a.apk", 193 }, 194 }, 195 }, 196 }, 197 }, 198 { 199 protoText: ` 200variant { 201 targeting { 202 sdk_version_targeting { 203 value { min { value: 10000 } } } } 204 apk_set { 205 module_metadata { 206 name: "base" targeting {} delivery_type: INSTALL_TIME } 207 apk_description { 208 targeting { 209 sdk_version_targeting { 210 value { min { value: 21 } } } } 211 path: "splits/base-master.apk" 212 split_apk_metadata { is_master_split: true } } } }`, 213 configs: []testConfigDesc{ 214 { 215 name: "Prerelease", 216 targetConfig: TargetConfig{ 217 sdkVersion: 30, 218 screenDpi: map[bp.ScreenDensity_DensityAlias]bool{}, 219 abis: map[bp.Abi_AbiAlias]int{}, 220 allowPrereleased: true, 221 }, 222 expected: SelectionResult{ 223 "base", 224 []string{"splits/base-master.apk"}, 225 }, 226 }, 227 }, 228 }, 229 { 230 protoText: ` 231variant { 232 targeting { 233 sdk_version_targeting { 234 value { min { value: 29 } } } } 235 apk_set { 236 module_metadata { 237 name: "base" targeting {} delivery_type: INSTALL_TIME } 238 apk_description { 239 targeting {} 240 path: "universal.apk" 241 standalone_apk_metadata { fused_module_name: "base" } } } }`, 242 configs: []testConfigDesc{ 243 { 244 name: "Universal", 245 targetConfig: TargetConfig{sdkVersion: 30}, 246 expected: SelectionResult{ 247 "base", 248 []string{"universal.apk"}, 249 }, 250 }, 251 }, 252 }, 253 } 254 for _, testCase := range testCases { 255 var toc bp.BuildApksResult 256 if err := proto.UnmarshalText(testCase.protoText, &toc); err != nil { 257 t.Fatal(err) 258 } 259 for _, config := range testCase.configs { 260 actual := selectApks(&toc, config.targetConfig) 261 if !reflect.DeepEqual(config.expected, actual) { 262 t.Errorf("%s: expected %v, got %v", config.name, config.expected, actual) 263 } 264 } 265 } 266} 267 268func TestSelectApks_ApexSet(t *testing.T) { 269 testCases := []testDesc{ 270 { 271 protoText: ` 272variant { 273 targeting { 274 sdk_version_targeting { 275 value { min { value: 29 } } } } 276 apk_set { 277 module_metadata { 278 name: "base" targeting {} delivery_type: INSTALL_TIME } 279 apk_description { 280 targeting { 281 multi_abi_targeting { 282 value { abi { alias: ARMEABI_V7A } } 283 alternatives { abi { alias: ARM64_V8A } } 284 alternatives { abi { alias: X86 } } 285 alternatives { abi { alias: X86_64 } } } 286 sdk_version_targeting { 287 value { min { value: 21 } } } } 288 path: "standalones/standalone-armeabi_v7a.apex" 289 apex_apk_metadata { } } 290 apk_description { 291 targeting { 292 multi_abi_targeting { 293 value { abi { alias: ARM64_V8A } } 294 alternatives { abi { alias: ARMEABI_V7A } } 295 alternatives { abi { alias: X86 } } 296 alternatives { abi { alias: X86_64 } } } 297 sdk_version_targeting { 298 value { min { value: 21 } } } } 299 path: "standalones/standalone-arm64_v8a.apex" 300 apex_apk_metadata { } } 301 apk_description { 302 targeting { 303 multi_abi_targeting { 304 value { abi { alias: X86 } } 305 alternatives { abi { alias: ARMEABI_V7A } } 306 alternatives { abi { alias: ARM64_V8A } } 307 alternatives { abi { alias: X86_64 } } } 308 sdk_version_targeting { 309 value { min { value: 21 } } } } 310 path: "standalones/standalone-x86.apex" 311 apex_apk_metadata { } } 312 apk_description { 313 targeting { 314 multi_abi_targeting { 315 value { abi { alias: X86_64 } } 316 alternatives { abi { alias: ARMEABI_V7A } } 317 alternatives { abi { alias: ARM64_V8A } } 318 alternatives { abi { alias: X86 } } } 319 sdk_version_targeting { 320 value { min { value: 21 } } } } 321 path: "standalones/standalone-x86_64.apex" 322 apex_apk_metadata { } } } 323} 324bundletool { 325 version: "0.10.3" } 326 327`, 328 configs: []testConfigDesc{ 329 { 330 name: "order matches priorities", 331 targetConfig: TargetConfig{ 332 sdkVersion: 29, 333 screenDpi: map[bp.ScreenDensity_DensityAlias]bool{ 334 bp.ScreenDensity_DENSITY_UNSPECIFIED: true, 335 }, 336 abis: map[bp.Abi_AbiAlias]int{ 337 bp.Abi_ARM64_V8A: 0, 338 bp.Abi_ARMEABI_V7A: 1, 339 }, 340 }, 341 expected: SelectionResult{ 342 "base", 343 []string{ 344 "standalones/standalone-arm64_v8a.apex", 345 }, 346 }, 347 }, 348 { 349 name: "order doesn't match priorities", 350 targetConfig: TargetConfig{ 351 sdkVersion: 29, 352 screenDpi: map[bp.ScreenDensity_DensityAlias]bool{ 353 bp.ScreenDensity_DENSITY_UNSPECIFIED: true, 354 }, 355 abis: map[bp.Abi_AbiAlias]int{ 356 bp.Abi_ARMEABI_V7A: 0, 357 bp.Abi_ARM64_V8A: 1, 358 }, 359 }, 360 expected: SelectionResult{ 361 "base", 362 []string{ 363 "standalones/standalone-arm64_v8a.apex", 364 }, 365 }, 366 }, 367 { 368 name: "single choice", 369 targetConfig: TargetConfig{ 370 sdkVersion: 29, 371 screenDpi: map[bp.ScreenDensity_DensityAlias]bool{ 372 bp.ScreenDensity_DENSITY_UNSPECIFIED: true, 373 }, 374 abis: map[bp.Abi_AbiAlias]int{ 375 bp.Abi_ARMEABI_V7A: 0, 376 }, 377 }, 378 expected: SelectionResult{ 379 "base", 380 []string{ 381 "standalones/standalone-armeabi_v7a.apex", 382 }, 383 }, 384 }, 385 { 386 name: "cross platform", 387 targetConfig: TargetConfig{ 388 sdkVersion: 29, 389 screenDpi: map[bp.ScreenDensity_DensityAlias]bool{ 390 bp.ScreenDensity_DENSITY_UNSPECIFIED: true, 391 }, 392 abis: map[bp.Abi_AbiAlias]int{ 393 bp.Abi_ARM64_V8A: 0, 394 bp.Abi_MIPS64: 1, 395 bp.Abi_X86: 2, 396 }, 397 }, 398 expected: SelectionResult{ 399 "base", 400 []string{ 401 "standalones/standalone-x86.apex", 402 }, 403 }, 404 }, 405 }, 406 }, 407 } 408 for _, testCase := range testCases { 409 var toc bp.BuildApksResult 410 if err := proto.UnmarshalText(testCase.protoText, &toc); err != nil { 411 t.Fatal(err) 412 } 413 for _, config := range testCase.configs { 414 actual := selectApks(&toc, config.targetConfig) 415 if !reflect.DeepEqual(config.expected, actual) { 416 t.Errorf("%s: expected %v, got %v", config.name, config.expected, actual) 417 } 418 } 419 } 420} 421 422type testZip2ZipWriter struct { 423 entries map[string]string 424} 425 426func (w testZip2ZipWriter) CopyFrom(file *zip.File, out string) error { 427 if x, ok := w.entries[out]; ok { 428 return fmt.Errorf("%s and %s both write to %s", x, file.Name, out) 429 } 430 w.entries[out] = file.Name 431 return nil 432} 433 434type testCaseWriteApks struct { 435 name string 436 moduleName string 437 stem string 438 partition string 439 // what we write from what 440 expectedZipEntries map[string]string 441 expectedApkcerts []string 442} 443 444func TestWriteApks(t *testing.T) { 445 testCases := []testCaseWriteApks{ 446 { 447 name: "splits", 448 moduleName: "mybase", 449 stem: "Foo", 450 partition: "system", 451 expectedZipEntries: map[string]string{ 452 "Foo.apk": "splits/mybase-master.apk", 453 "Foo-xhdpi.apk": "splits/mybase-xhdpi.apk", 454 }, 455 expectedApkcerts: []string{ 456 `name="Foo-xhdpi.apk" certificate="PRESIGNED" private_key="" partition="system"`, 457 `name="Foo.apk" certificate="PRESIGNED" private_key="" partition="system"`, 458 }, 459 }, 460 { 461 name: "universal", 462 moduleName: "base", 463 stem: "Bar", 464 partition: "product", 465 expectedZipEntries: map[string]string{ 466 "Bar.apk": "universal.apk", 467 }, 468 expectedApkcerts: []string{ 469 `name="Bar.apk" certificate="PRESIGNED" private_key="" partition="product"`, 470 }, 471 }, 472 } 473 for _, testCase := range testCases { 474 apkSet := ApkSet{entries: make(map[string]*zip.File)} 475 sel := SelectionResult{moduleName: testCase.moduleName} 476 for _, in := range testCase.expectedZipEntries { 477 apkSet.entries[in] = &zip.File{FileHeader: zip.FileHeader{Name: in}} 478 sel.entries = append(sel.entries, in) 479 } 480 writer := testZip2ZipWriter{make(map[string]string)} 481 config := TargetConfig{stem: testCase.stem} 482 apkcerts, err := apkSet.writeApks(sel, config, writer, testCase.partition) 483 if err != nil { 484 t.Error(err) 485 } 486 if !reflect.DeepEqual(testCase.expectedZipEntries, writer.entries) { 487 t.Errorf("expected zip entries %v, got %v", testCase.expectedZipEntries, writer.entries) 488 } 489 if !reflect.DeepEqual(testCase.expectedApkcerts, apkcerts) { 490 t.Errorf("expected apkcerts %v, got %v", testCase.expectedApkcerts, apkcerts) 491 } 492 } 493} 494