Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7d27bc5f authored by Paul Duffin's avatar Paul Duffin Committed by Gerrit Code Review
Browse files

Merge "Add error handling to test fixtures"

parents 636d152e cfd3374d
Loading
Loading
Loading
Loading
+126 −18
Original line number Diff line number Diff line
@@ -182,7 +182,13 @@ type FixtureFactory interface {
	// Create a Fixture.
	Fixture(t *testing.T, preparers ...FixturePreparer) Fixture

	// Run the test, expecting no errors, returning a TestResult instance.
	// Set the error handler that will be used to check any errors reported by the test.
	//
	// The default handlers is FixtureExpectsNoErrors which will fail the go test immediately if any
	// errors are reported.
	SetErrorHandler(errorHandler FixtureErrorHandler) FixtureFactory

	// Run the test, checking any errors reported and returning a TestResult instance.
	//
	// Shorthand for Fixture(t, preparers...).RunTest()
	RunTest(t *testing.T, preparers ...FixturePreparer) *TestResult
@@ -202,6 +208,9 @@ func NewFixtureFactory(buildDirSupplier *string, preparers ...FixturePreparer) F
	return &fixtureFactory{
		buildDirSupplier: buildDirSupplier,
		preparers:        dedupAndFlattenPreparers(nil, preparers),

		// Set the default error handler.
		errorHandler: FixtureExpectsNoErrors,
	}
}

@@ -352,9 +361,84 @@ func newSimpleFixturePreparer(preparer func(fixture *fixture)) FixturePreparer {
	return &simpleFixturePreparer{function: preparer}
}

// FixtureErrorHandler determines how to respond to errors reported by the code under test.
//
// Some possible responses:
// * Fail the test if any errors are reported, see FixtureExpectsNoErrors.
// * Fail the test if at least one error that matches a pattern is not reported see
//   FixtureExpectsAtLeastOneErrorMatchingPattern
// * Fail the test if any unexpected errors are reported.
//
// Although at the moment all the error handlers are implemented as simply a wrapper around a
// function this is defined as an interface to allow future enhancements, e.g. provide different
// ways other than patterns to match an error and to combine handlers together.
type FixtureErrorHandler interface {
	// CheckErrors checks the errors reported.
	//
	// The supplied result can be used to access the state of the code under test just as the main
	// body of the test would but if any errors other than ones expected are reported the state may
	// be indeterminate.
	CheckErrors(result *TestResult, errs []error)
}

type simpleErrorHandler struct {
	function func(result *TestResult, errs []error)
}

func (h simpleErrorHandler) CheckErrors(result *TestResult, errs []error) {
	h.function(result, errs)
}

// The default fixture error handler.
//
// Will fail the test immediately if any errors are reported.
var FixtureExpectsNoErrors = FixtureCustomErrorHandler(
	func(result *TestResult, errs []error) {
		FailIfErrored(result.T, errs)
	},
)

// FixtureExpectsAtLeastOneMatchingError returns an error handler that will cause the test to fail
// if at least one error that matches the regular expression is not found.
//
// The test will be failed if:
// * No errors are reported.
// * One or more errors are reported but none match the pattern.
//
// The test will not fail if:
// * Multiple errors are reported that do not match the pattern as long as one does match.
func FixtureExpectsAtLeastOneErrorMatchingPattern(pattern string) FixtureErrorHandler {
	return FixtureCustomErrorHandler(func(result *TestResult, errs []error) {
		FailIfNoMatchingErrors(result.T, pattern, errs)
	})
}

// FixtureExpectsOneErrorToMatchPerPattern returns an error handler that will cause the test to fail
// if there are any unexpected errors.
//
// The test will be failed if:
// * The number of errors reported does not exactly match the patterns.
// * One or more of the reported errors do not match a pattern.
// * No patterns are provided and one or more errors are reported.
//
// The test will not fail if:
// * One or more of the patterns does not match an error.
func FixtureExpectsAllErrorsToMatchAPattern(patterns []string) FixtureErrorHandler {
	return FixtureCustomErrorHandler(func(result *TestResult, errs []error) {
		CheckErrorsAgainstExpectations(result.T, errs, patterns)
	})
}

// FixtureCustomErrorHandler creates a custom error handler
func FixtureCustomErrorHandler(function func(result *TestResult, errs []error)) FixtureErrorHandler {
	return simpleErrorHandler{
		function: function,
	}
}

// Fixture defines the test environment.
type Fixture interface {
	// Run the test, expecting no errors, returning a TestResult instance.
	// Run the test, checking any errors reported and returning a TestResult instance.
	RunTest() *TestResult
}

@@ -454,14 +538,17 @@ var _ FixtureFactory = (*fixtureFactory)(nil)
type fixtureFactory struct {
	buildDirSupplier *string
	preparers        []*simpleFixturePreparer
	errorHandler     FixtureErrorHandler
}

func (f *fixtureFactory) Extend(preparers ...FixturePreparer) FixtureFactory {
	all := append(f.preparers, dedupAndFlattenPreparers(f.preparers, preparers)...)
	return &fixtureFactory{
		buildDirSupplier: f.buildDirSupplier,
		preparers:        all,
	}
	// Copy the existing factory.
	extendedFactory := &fixtureFactory{}
	*extendedFactory = *f
	// Use the extended list of preparers.
	extendedFactory.preparers = all
	return extendedFactory
}

func (f *fixtureFactory) Fixture(t *testing.T, preparers ...FixturePreparer) Fixture {
@@ -473,6 +560,7 @@ func (f *fixtureFactory) Fixture(t *testing.T, preparers ...FixturePreparer) Fix
		config:       config,
		ctx:          ctx,
		mockFS:       make(MockFS),
		errorHandler: f.errorHandler,
	}

	for _, preparer := range f.preparers {
@@ -486,6 +574,11 @@ func (f *fixtureFactory) Fixture(t *testing.T, preparers ...FixturePreparer) Fix
	return fixture
}

func (f *fixtureFactory) SetErrorHandler(errorHandler FixtureErrorHandler) FixtureFactory {
	f.errorHandler = errorHandler
	return f
}

func (f *fixtureFactory) RunTest(t *testing.T, preparers ...FixturePreparer) *TestResult {
	t.Helper()
	fixture := f.Fixture(t, preparers...)
@@ -498,11 +591,23 @@ func (f *fixtureFactory) RunTestWithBp(t *testing.T, bp string) *TestResult {
}

type fixture struct {
	// The factory used to create this fixture.
	factory *fixtureFactory

	// The gotest state of the go test within which this was created.
	t *testing.T

	// The configuration prepared for this fixture.
	config Config

	// The test context prepared for this fixture.
	ctx *TestContext

	// The mock filesystem prepared for this fixture.
	mockFS MockFS

	// The error handler used to check the errors, if any, that are reported.
	errorHandler FixtureErrorHandler
}

func (f *fixture) RunTest() *TestResult {
@@ -525,9 +630,9 @@ func (f *fixture) RunTest() *TestResult {

	ctx.Register()
	_, errs := ctx.ParseBlueprintsFiles("ignored")
	FailIfErrored(f.t, errs)
	if len(errs) == 0 {
		_, errs = ctx.PrepareBuildActions(f.config)
	FailIfErrored(f.t, errs)
	}

	result := &TestResult{
		TestHelper:  TestHelper{T: f.t},
@@ -535,6 +640,9 @@ func (f *fixture) RunTest() *TestResult {
		fixture:     f,
		Config:      f.config,
	}

	f.errorHandler.CheckErrors(result, errs)

	return result
}

+2 −0
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ func init() {
	RegisterPackageBuildComponents(InitRegistrationContext)
}

var PrepareForTestWithPackageModule = FixtureRegisterWithContext(RegisterPackageBuildComponents)

// Register the package module type.
func RegisterPackageBuildComponents(ctx RegistrationContext) {
	ctx.RegisterModuleType("package", PackageFactory)
+2 −0
Original line number Diff line number Diff line
@@ -388,6 +388,8 @@ func registerTestPrebuiltBuildComponents(ctx RegistrationContext) {
	ctx.PostDepsMutators(RegisterOverridePostDepsMutators)
}

var prepareForTestWithFakePrebuiltModules = FixtureRegisterWithContext(registerTestPrebuiltModules)

func registerTestPrebuiltModules(ctx RegistrationContext) {
	ctx.RegisterModuleType("prebuilt", newPrebuiltModule)
	ctx.RegisterModuleType("source", newSourceModule)
+12 −0
Original line number Diff line number Diff line
@@ -202,6 +202,18 @@ type ExcludeFromVisibilityEnforcementTag interface {
	ExcludeFromVisibilityEnforcement()
}

var PrepareForTestWithVisibilityRuleChecker = FixtureRegisterWithContext(func(ctx RegistrationContext) {
	ctx.PreArchMutators(RegisterVisibilityRuleChecker)
})

var PrepareForTestWithVisibilityRuleGatherer = FixtureRegisterWithContext(func(ctx RegistrationContext) {
	ctx.PreArchMutators(RegisterVisibilityRuleGatherer)
})

var PrepareForTestWithVisibilityRuleEnforcer = FixtureRegisterWithContext(func(ctx RegistrationContext) {
	ctx.PostDepsMutators(RegisterVisibilityRuleEnforcer)
})

// The rule checker needs to be registered before defaults expansion to correctly check that
// //visibility:xxx isn't combined with other packages in the same list in any one module.
func RegisterVisibilityRuleChecker(ctx RegisterMutatorsContext) {
+76 −88
Original line number Diff line number Diff line
@@ -9,13 +9,13 @@ import (

var visibilityTests = []struct {
	name                string
	fs                  map[string][]byte
	fs                  MockFS
	expectedErrors      []string
	effectiveVisibility map[qualifiedModuleName][]string
}{
	{
		name: "invalid visibility: empty list",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_library {
					name: "libexample",
@@ -26,7 +26,7 @@ var visibilityTests = []struct {
	},
	{
		name: "invalid visibility: empty rule",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_library {
					name: "libexample",
@@ -37,7 +37,7 @@ var visibilityTests = []struct {
	},
	{
		name: "invalid visibility: unqualified",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_library {
					name: "libexample",
@@ -48,7 +48,7 @@ var visibilityTests = []struct {
	},
	{
		name: "invalid visibility: empty namespace",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_library {
					name: "libexample",
@@ -59,7 +59,7 @@ var visibilityTests = []struct {
	},
	{
		name: "invalid visibility: empty module",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_library {
					name: "libexample",
@@ -70,7 +70,7 @@ var visibilityTests = []struct {
	},
	{
		name: "invalid visibility: empty namespace and module",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_library {
					name: "libexample",
@@ -81,7 +81,7 @@ var visibilityTests = []struct {
	},
	{
		name: "//visibility:unknown",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_library {
					name: "libexample",
@@ -92,7 +92,7 @@ var visibilityTests = []struct {
	},
	{
		name: "//visibility:xxx mixed",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_library {
					name: "libexample",
@@ -113,7 +113,7 @@ var visibilityTests = []struct {
	},
	{
		name: "//visibility:legacy_public",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_library {
					name: "libexample",
@@ -129,7 +129,7 @@ var visibilityTests = []struct {
		// Verify that //visibility:public will allow the module to be referenced from anywhere, e.g.
		// the current directory, a nested directory and a directory in a separate tree.
		name: "//visibility:public",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_library {
					name: "libexample",
@@ -156,7 +156,7 @@ var visibilityTests = []struct {
		// Verify that //visibility:private allows the module to be referenced from the current
		// directory only.
		name: "//visibility:private",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_library {
					name: "libexample",
@@ -188,7 +188,7 @@ var visibilityTests = []struct {
	{
		// Verify that :__pkg__ allows the module to be referenced from the current directory only.
		name: ":__pkg__",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_library {
					name: "libexample",
@@ -221,7 +221,7 @@ var visibilityTests = []struct {
		// Verify that //top/nested allows the module to be referenced from the current directory and
		// the top/nested directory only, not a subdirectory of top/nested and not peak directory.
		name: "//top/nested",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_library {
					name: "libexample",
@@ -259,7 +259,7 @@ var visibilityTests = []struct {
		// Verify that :__subpackages__ allows the module to be referenced from the current directory
		// and sub directories but nowhere else.
		name: ":__subpackages__",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_library {
					name: "libexample",
@@ -290,7 +290,7 @@ var visibilityTests = []struct {
		// Verify that //top/nested:__subpackages__ allows the module to be referenced from the current
		// directory and sub directories but nowhere else.
		name: "//top/nested:__subpackages__",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_library {
					name: "libexample",
@@ -321,7 +321,7 @@ var visibilityTests = []struct {
		// Verify that ["//top/nested", "//peak:__subpackages"] allows the module to be referenced from
		// the current directory, top/nested and peak and all its subpackages.
		name: `["//top/nested", "//peak:__subpackages__"]`,
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_library {
					name: "libexample",
@@ -347,7 +347,7 @@ var visibilityTests = []struct {
	{
		// Verify that //vendor... cannot be used outside vendor apart from //vendor:__subpackages__
		name: `//vendor`,
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_library {
					name: "libexample",
@@ -381,7 +381,7 @@ var visibilityTests = []struct {
	{
		// Check that visibility is the union of the defaults modules.
		name: "defaults union, basic",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_defaults {
					name: "libexample_defaults",
@@ -419,7 +419,7 @@ var visibilityTests = []struct {
	},
	{
		name: "defaults union, multiple defaults",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_defaults {
					name: "libexample_defaults_1",
@@ -460,7 +460,7 @@ var visibilityTests = []struct {
	},
	{
		name: "//visibility:public mixed with other in defaults",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_defaults {
					name: "libexample_defaults",
@@ -478,7 +478,7 @@ var visibilityTests = []struct {
	},
	{
		name: "//visibility:public overriding defaults",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_defaults {
					name: "libexample_defaults",
@@ -501,7 +501,7 @@ var visibilityTests = []struct {
	},
	{
		name: "//visibility:public mixed with other from different defaults 1",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_defaults {
					name: "libexample_defaults_1",
@@ -524,7 +524,7 @@ var visibilityTests = []struct {
	},
	{
		name: "//visibility:public mixed with other from different defaults 2",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_defaults {
					name: "libexample_defaults_1",
@@ -547,7 +547,7 @@ var visibilityTests = []struct {
	},
	{
		name: "//visibility:private in defaults",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_defaults {
					name: "libexample_defaults",
@@ -581,7 +581,7 @@ var visibilityTests = []struct {
	},
	{
		name: "//visibility:private mixed with other in defaults",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_defaults {
					name: "libexample_defaults",
@@ -599,7 +599,7 @@ var visibilityTests = []struct {
	},
	{
		name: "//visibility:private overriding defaults",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_defaults {
					name: "libexample_defaults",
@@ -618,7 +618,7 @@ var visibilityTests = []struct {
	},
	{
		name: "//visibility:private in defaults overridden",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_defaults {
					name: "libexample_defaults",
@@ -637,7 +637,7 @@ var visibilityTests = []struct {
	},
	{
		name: "//visibility:private override //visibility:public",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_defaults {
					name: "libexample_defaults",
@@ -655,7 +655,7 @@ var visibilityTests = []struct {
	},
	{
		name: "//visibility:public override //visibility:private",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_defaults {
					name: "libexample_defaults",
@@ -673,7 +673,7 @@ var visibilityTests = []struct {
	},
	{
		name: "//visibility:override must be first in the list",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_library {
					name: "libexample",
@@ -686,7 +686,7 @@ var visibilityTests = []struct {
	},
	{
		name: "//visibility:override discards //visibility:private",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_defaults {
					name: "libexample_defaults",
@@ -707,7 +707,7 @@ var visibilityTests = []struct {
	},
	{
		name: "//visibility:override discards //visibility:public",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_defaults {
					name: "libexample_defaults",
@@ -736,7 +736,7 @@ var visibilityTests = []struct {
	},
	{
		name: "//visibility:override discards defaults supplied rules",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_defaults {
					name: "libexample_defaults",
@@ -765,7 +765,7 @@ var visibilityTests = []struct {
	},
	{
		name: "//visibility:override can override //visibility:public with //visibility:private",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_defaults {
					name: "libexample_defaults",
@@ -788,7 +788,7 @@ var visibilityTests = []struct {
	},
	{
		name: "//visibility:override can override //visibility:private with //visibility:public",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_defaults {
					name: "libexample_defaults",
@@ -808,7 +808,7 @@ var visibilityTests = []struct {
	},
	{
		name: "//visibility:private mixed with itself",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_defaults {
					name: "libexample_defaults_1",
@@ -838,7 +838,7 @@ var visibilityTests = []struct {
	// Defaults module's defaults_visibility tests
	{
		name: "defaults_visibility invalid",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_defaults {
					name: "top_defaults",
@@ -851,7 +851,7 @@ var visibilityTests = []struct {
	},
	{
		name: "defaults_visibility overrides package default",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				package {
					default_visibility: ["//visibility:private"],
@@ -871,7 +871,7 @@ var visibilityTests = []struct {
	// Package default_visibility tests
	{
		name: "package default_visibility property is checked",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				package {
					default_visibility: ["//visibility:invalid"],
@@ -882,7 +882,7 @@ var visibilityTests = []struct {
	{
		// This test relies on the default visibility being legacy_public.
		name: "package default_visibility property used when no visibility specified",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				package {
					default_visibility: ["//visibility:private"],
@@ -904,7 +904,7 @@ var visibilityTests = []struct {
	},
	{
		name: "package default_visibility public does not override visibility private",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				package {
					default_visibility: ["//visibility:public"],
@@ -927,7 +927,7 @@ var visibilityTests = []struct {
	},
	{
		name: "package default_visibility private does not override visibility public",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				package {
					default_visibility: ["//visibility:private"],
@@ -946,7 +946,7 @@ var visibilityTests = []struct {
	},
	{
		name: "package default_visibility :__subpackages__",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				package {
					default_visibility: [":__subpackages__"],
@@ -973,7 +973,7 @@ var visibilityTests = []struct {
	},
	{
		name: "package default_visibility inherited to subpackages",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				package {
					default_visibility: ["//outsider"],
@@ -1001,7 +1001,7 @@ var visibilityTests = []struct {
	},
	{
		name: "package default_visibility inherited to subpackages",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				package {
					default_visibility: ["//visibility:private"],
@@ -1031,7 +1031,7 @@ var visibilityTests = []struct {
	},
	{
		name: "verify that prebuilt dependencies are ignored for visibility reasons (not preferred)",
		fs: map[string][]byte{
		fs: MockFS{
			"prebuilts/Blueprints": []byte(`
				prebuilt {
					name: "module",
@@ -1053,7 +1053,7 @@ var visibilityTests = []struct {
	},
	{
		name: "verify that prebuilt dependencies are ignored for visibility reasons (preferred)",
		fs: map[string][]byte{
		fs: MockFS{
			"prebuilts/Blueprints": []byte(`
				prebuilt {
					name: "module",
@@ -1076,7 +1076,7 @@ var visibilityTests = []struct {
	},
	{
		name: "ensure visibility properties are checked for correctness",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_parent {
					name: "parent",
@@ -1093,7 +1093,7 @@ var visibilityTests = []struct {
	},
	{
		name: "invalid visibility added to child detected during gather phase",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_parent {
					name: "parent",
@@ -1115,7 +1115,7 @@ var visibilityTests = []struct {
	},
	{
		name: "automatic visibility inheritance enabled",
		fs: map[string][]byte{
		fs: MockFS{
			"top/Blueprints": []byte(`
				mock_parent {
					name: "parent",
@@ -1142,53 +1142,41 @@ var visibilityTests = []struct {
func TestVisibility(t *testing.T) {
	for _, test := range visibilityTests {
		t.Run(test.name, func(t *testing.T) {
			ctx, errs := testVisibility(buildDir, test.fs)

			CheckErrorsAgainstExpectations(t, errs, test.expectedErrors)
			result := emptyTestFixtureFactory.Extend(
				FixtureRegisterWithContext(func(ctx RegistrationContext) {
					ctx.RegisterModuleType("mock_library", newMockLibraryModule)
					ctx.RegisterModuleType("mock_parent", newMockParentFactory)
					ctx.RegisterModuleType("mock_defaults", defaultsFactory)
				}),
				prepareForTestWithFakePrebuiltModules,
				PrepareForTestWithPackageModule,
				// Order of the following method calls is significant as they register mutators.
				PrepareForTestWithArchMutator,
				PrepareForTestWithPrebuilts,
				PrepareForTestWithOverrides,
				PrepareForTestWithVisibilityRuleChecker,
				PrepareForTestWithDefaults,
				PrepareForTestWithVisibilityRuleGatherer,
				PrepareForTestWithVisibilityRuleEnforcer,
				// Add additional files to the mock filesystem
				test.fs.AddToFixture(),
			).
				SetErrorHandler(FixtureExpectsAllErrorsToMatchAPattern(test.expectedErrors)).
				RunTest(t)

			if test.effectiveVisibility != nil {
				checkEffectiveVisibility(t, ctx, test.effectiveVisibility)
				checkEffectiveVisibility(result, test.effectiveVisibility)
			}
		})
	}
}

func checkEffectiveVisibility(t *testing.T, ctx *TestContext, effectiveVisibility map[qualifiedModuleName][]string) {
func checkEffectiveVisibility(result *TestResult, effectiveVisibility map[qualifiedModuleName][]string) {
	for moduleName, expectedRules := range effectiveVisibility {
		rule := effectiveVisibilityRules(ctx.config, moduleName)
		rule := effectiveVisibilityRules(result.Config, moduleName)
		stringRules := rule.Strings()
		if !reflect.DeepEqual(expectedRules, stringRules) {
			t.Errorf("effective rules mismatch: expected %q, found %q", expectedRules, stringRules)
		}
	}
}

func testVisibility(buildDir string, fs map[string][]byte) (*TestContext, []error) {

	// Create a new config per test as visibility information is stored in the config.
	config := TestArchConfig(buildDir, nil, "", fs)

	ctx := NewTestArchContext(config)
	ctx.RegisterModuleType("mock_library", newMockLibraryModule)
	ctx.RegisterModuleType("mock_parent", newMockParentFactory)
	ctx.RegisterModuleType("mock_defaults", defaultsFactory)

	// Order of the following method calls is significant.
	RegisterPackageBuildComponents(ctx)
	registerTestPrebuiltBuildComponents(ctx)
	ctx.PreArchMutators(RegisterVisibilityRuleChecker)
	ctx.PreArchMutators(RegisterDefaultsPreArchMutators)
	ctx.PreArchMutators(RegisterVisibilityRuleGatherer)
	ctx.PostDepsMutators(RegisterVisibilityRuleEnforcer)
	ctx.Register()

	_, errs := ctx.ParseBlueprintsFiles(".")
	if len(errs) > 0 {
		return ctx, errs
		result.AssertDeepEquals("effective rules mismatch", expectedRules, stringRules)
	}

	_, errs = ctx.PrepareBuildActions(config)
	return ctx, errs
}

type mockLibraryProperties struct {