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