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