1// Copyright 2019 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 sdk
16
17import (
18	"fmt"
19	"path/filepath"
20	"strings"
21	"testing"
22
23	"android/soong/android"
24	"android/soong/apex"
25	"android/soong/cc"
26	"android/soong/genrule"
27	"android/soong/java"
28)
29
30// Prepare for running an sdk test with an apex.
31var prepareForSdkTestWithApex = android.GroupFixturePreparers(
32	apex.PrepareForTestWithApexBuildComponents,
33	android.FixtureAddTextFile("sdk/tests/Android.bp", `
34		apex_key {
35			name: "myapex.key",
36			public_key: "myapex.avbpubkey",
37			private_key: "myapex.pem",
38		}
39
40		android_app_certificate {
41			name: "myapex.cert",
42			certificate: "myapex",
43		}
44	`),
45
46	android.FixtureMergeMockFs(map[string][]byte{
47		"apex_manifest.json":                           nil,
48		"system/sepolicy/apex/myapex-file_contexts":    nil,
49		"system/sepolicy/apex/myapex2-file_contexts":   nil,
50		"system/sepolicy/apex/mysdkapex-file_contexts": nil,
51		"sdk/tests/myapex.avbpubkey":                   nil,
52		"sdk/tests/myapex.pem":                         nil,
53		"sdk/tests/myapex.x509.pem":                    nil,
54		"sdk/tests/myapex.pk8":                         nil,
55	}),
56)
57
58// Legacy preparer used for running tests within the sdk package.
59//
60// This includes everything that was needed to run any test in the sdk package prior to the
61// introduction of the test fixtures. Tests that are being converted to use fixtures directly
62// rather than through the testSdkError() and testSdkWithFs() methods should avoid using this and
63// instead should use the various preparers directly using android.GroupFixturePreparers(...) to
64// group them when necessary.
65//
66// deprecated
67var prepareForSdkTest = android.GroupFixturePreparers(
68	cc.PrepareForTestWithCcDefaultModules,
69	genrule.PrepareForTestWithGenRuleBuildComponents,
70	java.PrepareForTestWithJavaBuildComponents,
71	PrepareForTestWithSdkBuildComponents,
72
73	prepareForSdkTestWithApex,
74
75	cc.PrepareForTestOnWindows,
76	android.FixtureModifyConfig(func(config android.Config) {
77		// Add windows as a default disable OS to test behavior when some OS variants
78		// are disabled.
79		config.Targets[android.Windows] = []android.Target{
80			{android.Windows, android.Arch{ArchType: android.X86_64}, android.NativeBridgeDisabled, "", "", true},
81		}
82	}),
83
84	// Make sure that every test provides all the source files.
85	android.PrepareForTestDisallowNonExistentPaths,
86	android.MockFS{
87		"Test.java": nil,
88	}.AddToFixture(),
89)
90
91var PrepareForTestWithSdkBuildComponents = android.GroupFixturePreparers(
92	android.FixtureRegisterWithContext(registerModuleExportsBuildComponents),
93	android.FixtureRegisterWithContext(registerSdkBuildComponents),
94)
95
96func testSdkWithFs(t *testing.T, bp string, fs android.MockFS) *android.TestResult {
97	t.Helper()
98	return android.GroupFixturePreparers(
99		prepareForSdkTest,
100		fs.AddToFixture(),
101	).RunTestWithBp(t, bp)
102}
103
104func testSdkError(t *testing.T, pattern, bp string) {
105	t.Helper()
106	prepareForSdkTest.
107		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(pattern)).
108		RunTestWithBp(t, bp)
109}
110
111func ensureListContains(t *testing.T, result []string, expected string) {
112	t.Helper()
113	if !android.InList(expected, result) {
114		t.Errorf("%q is not found in %v", expected, result)
115	}
116}
117
118func pathsToStrings(paths android.Paths) []string {
119	var ret []string
120	for _, p := range paths {
121		ret = append(ret, p.String())
122	}
123	return ret
124}
125
126// Analyse the sdk build rules to extract information about what it is doing.
127//
128// e.g. find the src/dest pairs from each cp command, the various zip files
129// generated, etc.
130func getSdkSnapshotBuildInfo(t *testing.T, result *android.TestResult, sdk *sdk) *snapshotBuildInfo {
131	info := &snapshotBuildInfo{
132		t:                            t,
133		r:                            result,
134		version:                      sdk.builderForTests.version,
135		androidBpContents:            sdk.GetAndroidBpContentsForTests(),
136		androidUnversionedBpContents: sdk.GetUnversionedAndroidBpContentsForTests(),
137		androidVersionedBpContents:   sdk.GetVersionedAndroidBpContentsForTests(),
138		snapshotTestCustomizations:   map[snapshotTest]*snapshotTestCustomization{},
139	}
140
141	buildParams := sdk.BuildParamsForTests()
142	copyRules := &strings.Builder{}
143	otherCopyRules := &strings.Builder{}
144	snapshotDirPrefix := sdk.builderForTests.snapshotDir.String() + "/"
145	for _, bp := range buildParams {
146		switch bp.Rule.String() {
147		case android.Cp.String():
148			output := bp.Output
149			// Get destination relative to the snapshot root
150			dest := output.Rel()
151			src := android.NormalizePathForTesting(bp.Input)
152			// We differentiate between copy rules for the snapshot, and copy rules for the install file.
153			if strings.HasPrefix(output.String(), snapshotDirPrefix) {
154				// Get source relative to build directory.
155				_, _ = fmt.Fprintf(copyRules, "%s -> %s\n", src, dest)
156				info.snapshotContents = append(info.snapshotContents, dest)
157			} else {
158				_, _ = fmt.Fprintf(otherCopyRules, "%s -> %s\n", src, dest)
159			}
160
161		case repackageZip.String():
162			// Add the destdir to the snapshot contents as that is effectively where
163			// the content of the repackaged zip is copied.
164			dest := bp.Args["destdir"]
165			info.snapshotContents = append(info.snapshotContents, dest)
166
167		case zipFiles.String():
168			// This could be an intermediate zip file and not the actual output zip.
169			// In that case this will be overridden when the rule to merge the zips
170			// is processed.
171			info.outputZip = android.NormalizePathForTesting(bp.Output)
172
173		case mergeZips.String():
174			// Copy the current outputZip to the intermediateZip.
175			info.intermediateZip = info.outputZip
176			mergeInput := android.NormalizePathForTesting(bp.Input)
177			if info.intermediateZip != mergeInput {
178				t.Errorf("Expected intermediate zip %s to be an input to merge zips but found %s instead",
179					info.intermediateZip, mergeInput)
180			}
181
182			// Override output zip (which was actually the intermediate zip file) with the actual
183			// output zip.
184			info.outputZip = android.NormalizePathForTesting(bp.Output)
185
186			// Save the zips to be merged into the intermediate zip.
187			info.mergeZips = android.NormalizePathsForTesting(bp.Inputs)
188		}
189	}
190
191	info.copyRules = copyRules.String()
192	info.otherCopyRules = otherCopyRules.String()
193
194	return info
195}
196
197// The enum of different sdk snapshot tests performed by CheckSnapshot.
198type snapshotTest int
199
200const (
201	// The enumeration of the different test configurations.
202	// A test with the snapshot/Android.bp file but without the original Android.bp file.
203	checkSnapshotWithoutSource snapshotTest = iota
204
205	// A test with both the original source and the snapshot, with the source preferred.
206	checkSnapshotWithSourcePreferred
207
208	// A test with both the original source and the snapshot, with the snapshot preferred.
209	checkSnapshotPreferredWithSource
210
211	// The directory into which the snapshot will be 'unpacked'.
212	snapshotSubDir = "snapshot"
213)
214
215// Check the snapshot build rules.
216//
217// Takes a list of functions which check different facets of the snapshot build rules.
218// Allows each test to customize what is checked without duplicating lots of code
219// or proliferating check methods of different flavors.
220func CheckSnapshot(t *testing.T, result *android.TestResult, name string, dir string, checkers ...snapshotBuildInfoChecker) {
221	t.Helper()
222
223	// The sdk CommonOS variant is always responsible for generating the snapshot.
224	variant := android.CommonOS.Name
225
226	sdk := result.Module(name, variant).(*sdk)
227
228	snapshotBuildInfo := getSdkSnapshotBuildInfo(t, result, sdk)
229
230	// Check state of the snapshot build.
231	for _, checker := range checkers {
232		checker(snapshotBuildInfo)
233	}
234
235	// Make sure that the generated zip file is in the correct place.
236	actual := snapshotBuildInfo.outputZip
237	if dir != "" {
238		dir = filepath.Clean(dir) + "/"
239	}
240	suffix := ""
241	if snapshotBuildInfo.version != soongSdkSnapshotVersionUnversioned {
242		suffix = "-" + snapshotBuildInfo.version
243	}
244
245	expectedZipPath := fmt.Sprintf(".intermediates/%s%s/%s/%s%s.zip", dir, name, variant, name, suffix)
246	android.AssertStringEquals(t, "Snapshot zip file in wrong place", expectedZipPath, actual)
247
248	// Populate a mock filesystem with the files that would have been copied by
249	// the rules.
250	fs := android.MockFS{}
251	for _, dest := range snapshotBuildInfo.snapshotContents {
252		fs[filepath.Join(snapshotSubDir, dest)] = nil
253	}
254	fs[filepath.Join(snapshotSubDir, "Android.bp")] = []byte(snapshotBuildInfo.androidBpContents)
255
256	// The preparers from the original source fixture.
257	sourcePreparers := result.Preparer()
258
259	// Preparer to combine the snapshot and the source.
260	snapshotPreparer := android.GroupFixturePreparers(sourcePreparers, fs.AddToFixture())
261
262	var runSnapshotTestWithCheckers = func(t *testing.T, testConfig snapshotTest, extraPreparer android.FixturePreparer) {
263		t.Helper()
264		customization := snapshotBuildInfo.snapshotTestCustomization(testConfig)
265		customizedPreparers := android.GroupFixturePreparers(customization.preparers...)
266
267		// TODO(b/183184375): Set Config.TestAllowNonExistentPaths = false to verify that all the
268		//  files the snapshot needs are actually copied into the snapshot.
269
270		// Run the snapshot with the snapshot preparer and the extra preparer, which must come after as
271		// it may need to modify parts of the MockFS populated by the snapshot preparer.
272		result := android.GroupFixturePreparers(snapshotPreparer, extraPreparer, customizedPreparers).
273			ExtendWithErrorHandler(customization.errorHandler).
274			RunTest(t)
275
276		// Perform any additional checks the test need on the result of processing the snapshot.
277		for _, checker := range customization.checkers {
278			checker(t, result)
279		}
280	}
281
282	t.Run("snapshot without source", func(t *testing.T) {
283		// Remove the source Android.bp file to make sure it works without.
284		removeSourceAndroidBp := android.FixtureModifyMockFS(func(fs android.MockFS) {
285			delete(fs, "Android.bp")
286		})
287
288		runSnapshotTestWithCheckers(t, checkSnapshotWithoutSource, removeSourceAndroidBp)
289	})
290
291	t.Run("snapshot with source preferred", func(t *testing.T) {
292		runSnapshotTestWithCheckers(t, checkSnapshotWithSourcePreferred, android.NullFixturePreparer)
293	})
294
295	t.Run("snapshot preferred with source", func(t *testing.T) {
296		// Replace the snapshot/Android.bp file with one where "prefer: false," has been replaced with
297		// "prefer: true,"
298		preferPrebuilts := android.FixtureModifyMockFS(func(fs android.MockFS) {
299			snapshotBpFile := filepath.Join(snapshotSubDir, "Android.bp")
300			unpreferred := string(fs[snapshotBpFile])
301			fs[snapshotBpFile] = []byte(strings.ReplaceAll(unpreferred, "prefer: false,", "prefer: true,"))
302		})
303
304		runSnapshotTestWithCheckers(t, checkSnapshotPreferredWithSource, preferPrebuilts)
305	})
306}
307
308type snapshotBuildInfoChecker func(info *snapshotBuildInfo)
309
310// Check that the snapshot's generated Android.bp is correct.
311//
312// Both the expected and actual string are both trimmed before comparing.
313func checkAndroidBpContents(expected string) snapshotBuildInfoChecker {
314	return func(info *snapshotBuildInfo) {
315		info.t.Helper()
316		android.AssertTrimmedStringEquals(info.t, "Android.bp contents do not match", expected, info.androidBpContents)
317	}
318}
319
320// Check that the snapshot's unversioned generated Android.bp is correct.
321//
322// This func should be used to check the general snapshot generation code.
323//
324// Both the expected and actual string are both trimmed before comparing.
325func checkUnversionedAndroidBpContents(expected string) snapshotBuildInfoChecker {
326	return func(info *snapshotBuildInfo) {
327		info.t.Helper()
328		android.AssertTrimmedStringEquals(info.t, "unversioned Android.bp contents do not match", expected, info.androidUnversionedBpContents)
329	}
330}
331
332// Check that the snapshot's versioned generated Android.bp is correct.
333//
334// This func should only be used to check the version specific snapshot generation code,
335// i.e. the encoding of version into module names and the generation of the _snapshot module. The
336// general snapshot generation code should be checked using the checkUnversionedAndroidBpContents()
337// func.
338//
339// Both the expected and actual string are both trimmed before comparing.
340func checkVersionedAndroidBpContents(expected string) snapshotBuildInfoChecker {
341	return func(info *snapshotBuildInfo) {
342		info.t.Helper()
343		android.AssertTrimmedStringEquals(info.t, "versioned Android.bp contents do not match", expected, info.androidVersionedBpContents)
344	}
345}
346
347// Check that the snapshot's copy rules are correct.
348//
349// The copy rules are formatted as <src> -> <dest>, one per line and then compared
350// to the supplied expected string. Both the expected and actual string are trimmed
351// before comparing.
352func checkAllCopyRules(expected string) snapshotBuildInfoChecker {
353	return func(info *snapshotBuildInfo) {
354		info.t.Helper()
355		android.AssertTrimmedStringEquals(info.t, "Incorrect copy rules", expected, info.copyRules)
356	}
357}
358
359func checkAllOtherCopyRules(expected string) snapshotBuildInfoChecker {
360	return func(info *snapshotBuildInfo) {
361		info.t.Helper()
362		android.AssertTrimmedStringEquals(info.t, "Incorrect copy rules", expected, info.otherCopyRules)
363	}
364}
365
366// Check that the specified paths match the list of zips to merge with the intermediate zip.
367func checkMergeZips(expected ...string) snapshotBuildInfoChecker {
368	return func(info *snapshotBuildInfo) {
369		info.t.Helper()
370		if info.intermediateZip == "" {
371			info.t.Errorf("No intermediate zip file was created")
372		}
373
374		android.AssertDeepEquals(info.t, "mismatching merge zip files", expected, info.mergeZips)
375	}
376}
377
378type resultChecker func(t *testing.T, result *android.TestResult)
379
380// snapshotTestPreparer registers a preparer that will be used to customize the specified
381// snapshotTest.
382func snapshotTestPreparer(snapshotTest snapshotTest, preparer android.FixturePreparer) snapshotBuildInfoChecker {
383	return func(info *snapshotBuildInfo) {
384		customization := info.snapshotTestCustomization(snapshotTest)
385		customization.preparers = append(customization.preparers, preparer)
386	}
387}
388
389// snapshotTestChecker registers a checker that will be run against the result of processing the
390// generated snapshot for the specified snapshotTest.
391func snapshotTestChecker(snapshotTest snapshotTest, checker resultChecker) snapshotBuildInfoChecker {
392	return func(info *snapshotBuildInfo) {
393		customization := info.snapshotTestCustomization(snapshotTest)
394		customization.checkers = append(customization.checkers, checker)
395	}
396}
397
398// snapshotTestErrorHandler registers an error handler to use when processing the snapshot
399// in the specific test case.
400//
401// Generally, the snapshot should work with all the test cases but some do not and just in case
402// there are a lot of issues to resolve, or it will take a lot of time this is a
403// get-out-of-jail-free card that allows progress to be made.
404//
405// deprecated: should only be used as a temporary workaround with an attached to do and bug.
406func snapshotTestErrorHandler(snapshotTest snapshotTest, handler android.FixtureErrorHandler) snapshotBuildInfoChecker {
407	return func(info *snapshotBuildInfo) {
408		customization := info.snapshotTestCustomization(snapshotTest)
409		customization.errorHandler = handler
410	}
411}
412
413// Encapsulates information provided by each test to customize a specific snapshotTest.
414type snapshotTestCustomization struct {
415	// Preparers that are used to customize the test fixture before running the test.
416	preparers []android.FixturePreparer
417
418	// Checkers that are run on the result of processing the preferred snapshot in a specific test
419	// case.
420	checkers []resultChecker
421
422	// Specify an error handler for when processing a specific test case.
423	//
424	// In some cases the generated snapshot cannot be used in a test configuration. Those cases are
425	// invariably bugs that need to be resolved but sometimes that can take a while. This provides a
426	// mechanism to temporarily ignore that error.
427	errorHandler android.FixtureErrorHandler
428}
429
430// Encapsulates information about the snapshot build structure in order to insulate tests from
431// knowing too much about internal structures.
432//
433// All source/input paths are relative either the build directory. All dest/output paths are
434// relative to the snapshot root directory.
435type snapshotBuildInfo struct {
436	t *testing.T
437
438	// The result from RunTest()
439	r *android.TestResult
440
441	// The version of the generated snapshot.
442	//
443	// See snapshotBuilder.version for more information about this field.
444	version string
445
446	// The contents of the generated Android.bp file
447	androidBpContents string
448
449	// The contents of the unversioned Android.bp file
450	androidUnversionedBpContents string
451
452	// The contents of the versioned Android.bp file
453	androidVersionedBpContents string
454
455	// The paths, relative to the snapshot root, of all files and directories copied into the
456	// snapshot.
457	snapshotContents []string
458
459	// A formatted representation of the src/dest pairs for a snapshot, one pair per line,
460	// of the format src -> dest
461	copyRules string
462
463	// A formatted representation of the src/dest pairs for files not in a snapshot, one pair
464	// per line, of the format src -> dest
465	otherCopyRules string
466
467	// The path to the intermediate zip, which is a zip created from the source files copied
468	// into the snapshot directory and which will be merged with other zips to form the final output.
469	// Is am empty string if there is no intermediate zip because there are no zips to merge in.
470	intermediateZip string
471
472	// The paths to the zips to merge into the output zip, does not include the intermediate
473	// zip.
474	mergeZips []string
475
476	// The final output zip.
477	outputZip string
478
479	// The test specific customizations for each snapshot test.
480	snapshotTestCustomizations map[snapshotTest]*snapshotTestCustomization
481}
482
483// snapshotTestCustomization gets the test specific customization for the specified snapshotTest.
484//
485// If no customization was created previously then it creates a default customization.
486func (i *snapshotBuildInfo) snapshotTestCustomization(snapshotTest snapshotTest) *snapshotTestCustomization {
487	customization := i.snapshotTestCustomizations[snapshotTest]
488	if customization == nil {
489		customization = &snapshotTestCustomization{
490			errorHandler: android.FixtureExpectsNoErrors,
491		}
492		i.snapshotTestCustomizations[snapshotTest] = customization
493	}
494	return customization
495}
496