• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2021 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 bp2build
16
17/*
18For shareable/common bp2build testing functionality and dumping ground for
19specific-but-shared functionality among tests in package
20*/
21
22import (
23	"fmt"
24	"sort"
25	"strings"
26	"testing"
27
28	"github.com/google/blueprint/proptools"
29
30	"android/soong/android"
31	"android/soong/android/allowlists"
32	"android/soong/bazel"
33)
34
35var (
36	buildDir string
37)
38
39func checkError(t *testing.T, errs []error, expectedErr error) bool {
40	t.Helper()
41
42	if len(errs) != 1 {
43		return false
44	}
45	if strings.Contains(errs[0].Error(), expectedErr.Error()) {
46		return true
47	}
48
49	return false
50}
51
52func errored(t *testing.T, tc Bp2buildTestCase, errs []error) bool {
53	t.Helper()
54	if tc.ExpectedErr != nil {
55		// Rely on checkErrors, as this test case is expected to have an error.
56		return false
57	}
58
59	if len(errs) > 0 {
60		for _, err := range errs {
61			t.Errorf("%s: %s", tc.Description, err)
62		}
63		return true
64	}
65
66	// All good, continue execution.
67	return false
68}
69
70func RunBp2BuildTestCaseSimple(t *testing.T, tc Bp2buildTestCase) {
71	t.Helper()
72	RunBp2BuildTestCase(t, func(ctx android.RegistrationContext) {}, tc)
73}
74
75type Bp2buildTestCase struct {
76	Description                string
77	ModuleTypeUnderTest        string
78	ModuleTypeUnderTestFactory android.ModuleFactory
79	Blueprint                  string
80	ExpectedBazelTargets       []string
81	Filesystem                 map[string]string
82	Dir                        string
83	// An error with a string contained within the string of the expected error
84	ExpectedErr         error
85	UnconvertedDepsMode unconvertedDepsMode
86
87	// For every directory listed here, the BUILD file for that directory will
88	// be merged with the generated BUILD file. This allows custom BUILD targets
89	// to be used in tests, or use BUILD files to draw package boundaries.
90	KeepBuildFileForDirs []string
91}
92
93func RunBp2BuildTestCase(t *testing.T, registerModuleTypes func(ctx android.RegistrationContext), tc Bp2buildTestCase) {
94	t.Helper()
95	bp2buildSetup := android.GroupFixturePreparers(
96		android.FixtureRegisterWithContext(registerModuleTypes),
97		SetBp2BuildTestRunner,
98	)
99	runBp2BuildTestCaseWithSetup(t, bp2buildSetup, tc)
100}
101
102func RunApiBp2BuildTestCase(t *testing.T, registerModuleTypes func(ctx android.RegistrationContext), tc Bp2buildTestCase) {
103	t.Helper()
104	apiBp2BuildSetup := android.GroupFixturePreparers(
105		android.FixtureRegisterWithContext(registerModuleTypes),
106		SetApiBp2BuildTestRunner,
107	)
108	runBp2BuildTestCaseWithSetup(t, apiBp2BuildSetup, tc)
109}
110
111func runBp2BuildTestCaseWithSetup(t *testing.T, extraPreparer android.FixturePreparer, tc Bp2buildTestCase) {
112	t.Helper()
113	dir := "."
114	filesystem := make(map[string][]byte)
115	for f, content := range tc.Filesystem {
116		filesystem[f] = []byte(content)
117	}
118
119	preparers := []android.FixturePreparer{
120		extraPreparer,
121		android.FixtureMergeMockFs(filesystem),
122		android.FixtureWithRootAndroidBp(tc.Blueprint),
123		android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
124			ctx.RegisterModuleType(tc.ModuleTypeUnderTest, tc.ModuleTypeUnderTestFactory)
125		}),
126		android.FixtureModifyContext(func(ctx *android.TestContext) {
127			// A default configuration for tests to not have to specify bp2build_available on top level
128			// targets.
129			bp2buildConfig := android.NewBp2BuildAllowlist().SetDefaultConfig(
130				allowlists.Bp2BuildConfig{
131					android.Bp2BuildTopLevel: allowlists.Bp2BuildDefaultTrueRecursively,
132				},
133			)
134			for _, f := range tc.KeepBuildFileForDirs {
135				bp2buildConfig.SetKeepExistingBuildFile(map[string]bool{
136					f: /*recursive=*/ false,
137				})
138			}
139			ctx.RegisterBp2BuildConfig(bp2buildConfig)
140		}),
141		android.FixtureModifyEnv(func(env map[string]string) {
142			if tc.UnconvertedDepsMode == errorModulesUnconvertedDeps {
143				env["BP2BUILD_ERROR_UNCONVERTED"] = "true"
144			}
145		}),
146	}
147
148	preparer := android.GroupFixturePreparers(preparers...)
149	if tc.ExpectedErr != nil {
150		pattern := "\\Q" + tc.ExpectedErr.Error() + "\\E"
151		preparer = preparer.ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(pattern))
152	}
153	result := preparer.RunTestWithCustomResult(t).(*BazelTestResult)
154	if len(result.Errs) > 0 {
155		return
156	}
157
158	checkDir := dir
159	if tc.Dir != "" {
160		checkDir = tc.Dir
161	}
162	expectedTargets := map[string][]string{
163		checkDir: tc.ExpectedBazelTargets,
164	}
165
166	result.CompareAllBazelTargets(t, tc.Description, expectedTargets, true)
167}
168
169// SetBp2BuildTestRunner customizes the test fixture mechanism to run tests in Bp2Build mode.
170var SetBp2BuildTestRunner = android.FixtureSetTestRunner(&bazelTestRunner{Bp2Build})
171
172// SetApiBp2BuildTestRunner customizes the test fixture mechanism to run tests in ApiBp2build mode.
173var SetApiBp2BuildTestRunner = android.FixtureSetTestRunner(&bazelTestRunner{ApiBp2build})
174
175// bazelTestRunner customizes the test fixture mechanism to run tests of the bp2build and
176// apiBp2build build modes.
177type bazelTestRunner struct {
178	mode CodegenMode
179}
180
181func (b *bazelTestRunner) FinalPreparer(result *android.TestResult) android.CustomTestResult {
182	ctx := result.TestContext
183	switch b.mode {
184	case Bp2Build:
185		ctx.RegisterForBazelConversion()
186	case ApiBp2build:
187		ctx.RegisterForApiBazelConversion()
188	default:
189		panic(fmt.Errorf("unknown build mode: %d", b.mode))
190	}
191
192	return &BazelTestResult{TestResult: result}
193}
194
195func (b *bazelTestRunner) PostParseProcessor(result android.CustomTestResult) {
196	bazelResult := result.(*BazelTestResult)
197	ctx := bazelResult.TestContext
198	config := bazelResult.Config
199	_, errs := ctx.ResolveDependencies(config)
200	if bazelResult.CollateErrs(errs) {
201		return
202	}
203
204	codegenCtx := NewCodegenContext(config, ctx.Context, Bp2Build, "")
205	res, errs := GenerateBazelTargets(codegenCtx, false)
206	if bazelResult.CollateErrs(errs) {
207		return
208	}
209
210	// Store additional data for access by tests.
211	bazelResult.conversionResults = res
212}
213
214// BazelTestResult is a wrapper around android.TestResult to provide type safe access to the bazel
215// specific data stored by the bazelTestRunner.
216type BazelTestResult struct {
217	*android.TestResult
218
219	// The result returned by the GenerateBazelTargets function.
220	conversionResults
221}
222
223// CompareAllBazelTargets compares the BazelTargets produced by the test for all the directories
224// with the supplied set of expected targets.
225//
226// If ignoreUnexpected=false then this enforces an exact match where every BazelTarget produced must
227// have a corresponding expected BazelTarget.
228//
229// If ignoreUnexpected=true then it will ignore directories for which there are no expected targets.
230func (b BazelTestResult) CompareAllBazelTargets(t *testing.T, description string, expectedTargets map[string][]string, ignoreUnexpected bool) {
231	t.Helper()
232	actualTargets := b.buildFileToTargets
233
234	// Generate the sorted set of directories to check.
235	dirsToCheck := android.SortedKeys(expectedTargets)
236	if !ignoreUnexpected {
237		// This needs to perform an exact match so add the directories in which targets were
238		// produced to the list of directories to check.
239		dirsToCheck = append(dirsToCheck, android.SortedKeys(actualTargets)...)
240		dirsToCheck = android.SortedUniqueStrings(dirsToCheck)
241	}
242
243	for _, dir := range dirsToCheck {
244		expected := expectedTargets[dir]
245		actual := actualTargets[dir]
246
247		if expected == nil {
248			if actual != nil {
249				t.Errorf("did not expect any bazel modules in %q but found %d", dir, len(actual))
250			}
251		} else if actual == nil {
252			expectedCount := len(expected)
253			if expectedCount > 0 {
254				t.Errorf("expected %d bazel modules in %q but did not find any", expectedCount, dir)
255			}
256		} else {
257			b.CompareBazelTargets(t, description, expected, actual)
258		}
259	}
260}
261
262func (b BazelTestResult) CompareBazelTargets(t *testing.T, description string, expectedContents []string, actualTargets BazelTargets) {
263	t.Helper()
264	if actualCount, expectedCount := len(actualTargets), len(expectedContents); actualCount != expectedCount {
265		t.Errorf("%s: Expected %d bazel target (%s), got %d (%s)",
266			description, expectedCount, expectedContents, actualCount, actualTargets)
267	} else {
268		sort.SliceStable(actualTargets, func(i, j int) bool {
269			return actualTargets[i].name < actualTargets[j].name
270		})
271		sort.SliceStable(expectedContents, func(i, j int) bool {
272			return getTargetName(expectedContents[i]) < getTargetName(expectedContents[j])
273		})
274		for i, actualTarget := range actualTargets {
275			if w, g := expectedContents[i], actualTarget.content; w != g {
276				t.Errorf(
277					"%s[%d]: Expected generated Bazel target to be `%s`, got `%s`",
278					description, i, w, g)
279			}
280		}
281	}
282}
283
284type nestedProps struct {
285	Nested_prop *string
286}
287
288type EmbeddedProps struct {
289	Embedded_prop *string
290}
291
292type OtherEmbeddedProps struct {
293	Other_embedded_prop *string
294}
295
296type customProps struct {
297	EmbeddedProps
298	*OtherEmbeddedProps
299
300	Bool_prop     bool
301	Bool_ptr_prop *bool
302	// Ensure that properties tagged `blueprint:mutated` are omitted
303	Int_prop            int `blueprint:"mutated"`
304	Int64_ptr_prop      *int64
305	String_prop         string
306	String_literal_prop *string `android:"arch_variant"`
307	String_ptr_prop     *string
308	String_list_prop    []string
309
310	Nested_props     nestedProps
311	Nested_props_ptr *nestedProps
312
313	Arch_paths         []string `android:"path,arch_variant"`
314	Arch_paths_exclude []string `android:"path,arch_variant"`
315
316	// Prop used to indicate this conversion should be 1 module -> multiple targets
317	One_to_many_prop *bool
318
319	Api *string // File describing the APIs of this module
320}
321
322type customModule struct {
323	android.ModuleBase
324	android.BazelModuleBase
325
326	props customProps
327}
328
329// OutputFiles is needed because some instances of this module use dist with a
330// tag property which requires the module implements OutputFileProducer.
331func (m *customModule) OutputFiles(tag string) (android.Paths, error) {
332	return android.PathsForTesting("path" + tag), nil
333}
334
335func (m *customModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
336	// nothing for now.
337}
338
339func customModuleFactoryBase() android.Module {
340	module := &customModule{}
341	module.AddProperties(&module.props)
342	android.InitBazelModule(module)
343	return module
344}
345
346func customModuleFactoryHostAndDevice() android.Module {
347	m := customModuleFactoryBase()
348	android.InitAndroidArchModule(m, android.HostAndDeviceSupported, android.MultilibBoth)
349	return m
350}
351
352func customModuleFactoryDeviceSupported() android.Module {
353	m := customModuleFactoryBase()
354	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibBoth)
355	return m
356}
357
358func customModuleFactoryHostSupported() android.Module {
359	m := customModuleFactoryBase()
360	android.InitAndroidArchModule(m, android.HostSupported, android.MultilibBoth)
361	return m
362}
363
364func customModuleFactoryHostAndDeviceDefault() android.Module {
365	m := customModuleFactoryBase()
366	android.InitAndroidArchModule(m, android.HostAndDeviceDefault, android.MultilibBoth)
367	return m
368}
369
370func customModuleFactoryNeitherHostNorDeviceSupported() android.Module {
371	m := customModuleFactoryBase()
372	android.InitAndroidArchModule(m, android.NeitherHostNorDeviceSupported, android.MultilibBoth)
373	return m
374}
375
376type testProps struct {
377	Test_prop struct {
378		Test_string_prop string
379	}
380}
381
382type customTestModule struct {
383	android.ModuleBase
384
385	props      customProps
386	test_props testProps
387}
388
389func (m *customTestModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
390	// nothing for now.
391}
392
393func customTestModuleFactoryBase() android.Module {
394	m := &customTestModule{}
395	m.AddProperties(&m.props)
396	m.AddProperties(&m.test_props)
397	return m
398}
399
400func customTestModuleFactory() android.Module {
401	m := customTestModuleFactoryBase()
402	android.InitAndroidModule(m)
403	return m
404}
405
406type customDefaultsModule struct {
407	android.ModuleBase
408	android.DefaultsModuleBase
409}
410
411func customDefaultsModuleFactoryBase() android.DefaultsModule {
412	module := &customDefaultsModule{}
413	module.AddProperties(&customProps{})
414	return module
415}
416
417func customDefaultsModuleFactoryBasic() android.Module {
418	return customDefaultsModuleFactoryBase()
419}
420
421func customDefaultsModuleFactory() android.Module {
422	m := customDefaultsModuleFactoryBase()
423	android.InitDefaultsModule(m)
424	return m
425}
426
427type EmbeddedAttr struct {
428	Embedded_attr *string
429}
430
431type OtherEmbeddedAttr struct {
432	Other_embedded_attr *string
433}
434
435type customBazelModuleAttributes struct {
436	EmbeddedAttr
437	*OtherEmbeddedAttr
438	String_literal_prop bazel.StringAttribute
439	String_ptr_prop     *string
440	String_list_prop    []string
441	Arch_paths          bazel.LabelListAttribute
442	Api                 bazel.LabelAttribute
443}
444
445func (m *customModule) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
446	if p := m.props.One_to_many_prop; p != nil && *p {
447		customBp2buildOneToMany(ctx, m)
448		return
449	}
450
451	paths := bazel.LabelListAttribute{}
452	strAttr := bazel.StringAttribute{}
453	for axis, configToProps := range m.GetArchVariantProperties(ctx, &customProps{}) {
454		for config, props := range configToProps {
455			if custProps, ok := props.(*customProps); ok {
456				if custProps.Arch_paths != nil {
457					paths.SetSelectValue(axis, config, android.BazelLabelForModuleSrcExcludes(ctx, custProps.Arch_paths, custProps.Arch_paths_exclude))
458				}
459				if custProps.String_literal_prop != nil {
460					strAttr.SetSelectValue(axis, config, custProps.String_literal_prop)
461				}
462			}
463		}
464	}
465	productVariableProps := android.ProductVariableProperties(ctx, ctx.Module())
466	if props, ok := productVariableProps["String_literal_prop"]; ok {
467		for c, p := range props {
468			if val, ok := p.(*string); ok {
469				strAttr.SetSelectValue(c.ConfigurationAxis(), c.SelectKey(), val)
470			}
471		}
472	}
473
474	paths.ResolveExcludes()
475
476	attrs := &customBazelModuleAttributes{
477		String_literal_prop: strAttr,
478		String_ptr_prop:     m.props.String_ptr_prop,
479		String_list_prop:    m.props.String_list_prop,
480		Arch_paths:          paths,
481	}
482
483	attrs.Embedded_attr = m.props.Embedded_prop
484	if m.props.OtherEmbeddedProps != nil {
485		attrs.OtherEmbeddedAttr = &OtherEmbeddedAttr{Other_embedded_attr: m.props.OtherEmbeddedProps.Other_embedded_prop}
486	}
487
488	props := bazel.BazelTargetModuleProperties{
489		Rule_class: "custom",
490	}
491
492	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
493}
494
495var _ android.ApiProvider = (*customModule)(nil)
496
497func (c *customModule) ConvertWithApiBp2build(ctx android.TopDownMutatorContext) {
498	props := bazel.BazelTargetModuleProperties{
499		Rule_class: "custom_api_contribution",
500	}
501	apiAttribute := bazel.MakeLabelAttribute(
502		android.BazelLabelForModuleSrcSingle(ctx, proptools.String(c.props.Api)).Label,
503	)
504	attrs := &customBazelModuleAttributes{
505		Api: *apiAttribute,
506	}
507	ctx.CreateBazelTargetModule(props,
508		android.CommonAttributes{Name: c.Name()},
509		attrs)
510}
511
512// A bp2build mutator that uses load statements and creates a 1:M mapping from
513// module to target.
514func customBp2buildOneToMany(ctx android.TopDownMutatorContext, m *customModule) {
515
516	baseName := m.Name()
517	attrs := &customBazelModuleAttributes{}
518
519	myLibraryProps := bazel.BazelTargetModuleProperties{
520		Rule_class:        "my_library",
521		Bzl_load_location: "//build/bazel/rules:rules.bzl",
522	}
523	ctx.CreateBazelTargetModule(myLibraryProps, android.CommonAttributes{Name: baseName}, attrs)
524
525	protoLibraryProps := bazel.BazelTargetModuleProperties{
526		Rule_class:        "proto_library",
527		Bzl_load_location: "//build/bazel/rules:proto.bzl",
528	}
529	ctx.CreateBazelTargetModule(protoLibraryProps, android.CommonAttributes{Name: baseName + "_proto_library_deps"}, attrs)
530
531	myProtoLibraryProps := bazel.BazelTargetModuleProperties{
532		Rule_class:        "my_proto_library",
533		Bzl_load_location: "//build/bazel/rules:proto.bzl",
534	}
535	ctx.CreateBazelTargetModule(myProtoLibraryProps, android.CommonAttributes{Name: baseName + "_my_proto_library_deps"}, attrs)
536}
537
538// Helper method for tests to easily access the targets in a dir.
539func generateBazelTargetsForDir(codegenCtx *CodegenContext, dir string) (BazelTargets, []error) {
540	// TODO: Set generateFilegroups to true and/or remove the generateFilegroups argument completely
541	res, err := GenerateBazelTargets(codegenCtx, false)
542	if err != nil {
543		return BazelTargets{}, err
544	}
545	return res.buildFileToTargets[dir], err
546}
547
548func registerCustomModuleForBp2buildConversion(ctx *android.TestContext) {
549	ctx.RegisterModuleType("custom", customModuleFactoryHostAndDevice)
550	ctx.RegisterForBazelConversion()
551}
552
553func simpleModuleDoNotConvertBp2build(typ, name string) string {
554	return fmt.Sprintf(`
555%s {
556		name: "%s",
557		bazel_module: { bp2build_available: false },
558}`, typ, name)
559}
560
561type AttrNameToString map[string]string
562
563func (a AttrNameToString) clone() AttrNameToString {
564	newAttrs := make(AttrNameToString, len(a))
565	for k, v := range a {
566		newAttrs[k] = v
567	}
568	return newAttrs
569}
570
571// makeBazelTargetNoRestrictions returns bazel target build file definition that can be host or
572// device specific, or independent of host/device.
573func makeBazelTargetHostOrDevice(typ, name string, attrs AttrNameToString, hod android.HostOrDeviceSupported) string {
574	if _, ok := attrs["target_compatible_with"]; !ok {
575		switch hod {
576		case android.HostSupported:
577			attrs["target_compatible_with"] = `select({
578        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
579        "//conditions:default": [],
580    })`
581		case android.DeviceSupported:
582			attrs["target_compatible_with"] = `["//build/bazel/platforms/os:android"]`
583		}
584	}
585
586	attrStrings := make([]string, 0, len(attrs)+1)
587	if name != "" {
588		attrStrings = append(attrStrings, fmt.Sprintf(`    name = "%s",`, name))
589	}
590	for _, k := range android.SortedKeys(attrs) {
591		attrStrings = append(attrStrings, fmt.Sprintf("    %s = %s,", k, attrs[k]))
592	}
593	return fmt.Sprintf(`%s(
594%s
595)`, typ, strings.Join(attrStrings, "\n"))
596}
597
598// MakeBazelTargetNoRestrictions returns bazel target build file definition that does not add a
599// target_compatible_with.  This is useful for module types like filegroup and genrule that arch not
600// arch variant
601func MakeBazelTargetNoRestrictions(typ, name string, attrs AttrNameToString) string {
602	return makeBazelTargetHostOrDevice(typ, name, attrs, android.HostAndDeviceDefault)
603}
604
605// makeBazelTargetNoRestrictions returns bazel target build file definition that is device specific
606// as this is the most common default in Soong.
607func MakeBazelTarget(typ, name string, attrs AttrNameToString) string {
608	return makeBazelTargetHostOrDevice(typ, name, attrs, android.DeviceSupported)
609}
610
611type ExpectedRuleTarget struct {
612	Rule  string
613	Name  string
614	Attrs AttrNameToString
615	Hod   android.HostOrDeviceSupported
616}
617
618func (ebr ExpectedRuleTarget) String() string {
619	return makeBazelTargetHostOrDevice(ebr.Rule, ebr.Name, ebr.Attrs, ebr.Hod)
620}
621
622func makeCcStubSuiteTargets(name string, attrs AttrNameToString) string {
623	if _, hasStubs := attrs["stubs_symbol_file"]; !hasStubs {
624		return ""
625	}
626	STUB_SUITE_ATTRS := map[string]string{
627		"stubs_symbol_file":    "symbol_file",
628		"stubs_versions":       "versions",
629		"soname":               "soname",
630		"source_library_label": "source_library_label",
631	}
632
633	stubSuiteAttrs := AttrNameToString{}
634	for key, _ := range attrs {
635		if _, stubSuiteAttr := STUB_SUITE_ATTRS[key]; stubSuiteAttr {
636			stubSuiteAttrs[STUB_SUITE_ATTRS[key]] = attrs[key]
637		} else {
638			panic(fmt.Sprintf("unused cc_stub_suite attr %q\n", key))
639		}
640	}
641	return MakeBazelTarget("cc_stub_suite", name+"_stub_libs", stubSuiteAttrs)
642}
643
644func MakeNeverlinkDuplicateTarget(moduleType string, name string) string {
645	return MakeNeverlinkDuplicateTargetWithAttrs(moduleType, name, AttrNameToString{})
646}
647
648func MakeNeverlinkDuplicateTargetWithAttrs(moduleType string, name string, extraAttrs AttrNameToString) string {
649	attrs := extraAttrs
650	attrs["neverlink"] = `True`
651	attrs["exports"] = `[":` + name + `"]`
652	return MakeBazelTarget(moduleType, name+"-neverlink", attrs)
653}
654
655func getTargetName(targetContent string) string {
656	data := strings.Split(targetContent, "name = \"")
657	if len(data) < 2 {
658		return ""
659	} else {
660		endIndex := strings.Index(data[1], "\"")
661		return data[1][:endIndex]
662	}
663}
664