blob: 09c5b1b34650b964977c3628ec6d6d192ca3245c [file] [log] [blame]
package android
import (
"io/ioutil"
"os"
"testing"
"github.com/google/blueprint"
)
var visibilityTests = []struct {
name string
fs map[string][]byte
expectedErrors []string
}{
{
name: "invalid visibility: empty list",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
visibility: [],
}`),
},
expectedErrors: []string{`visibility: must contain at least one visibility rule`},
},
{
name: "invalid visibility: empty rule",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
visibility: [""],
}`),
},
expectedErrors: []string{`visibility: invalid visibility pattern ""`},
},
{
name: "invalid visibility: unqualified",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
visibility: ["target"],
}`),
},
expectedErrors: []string{`visibility: invalid visibility pattern "target"`},
},
{
name: "invalid visibility: empty namespace",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
visibility: ["//"],
}`),
},
expectedErrors: []string{`visibility: invalid visibility pattern "//"`},
},
{
name: "invalid visibility: empty module",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
visibility: [":"],
}`),
},
expectedErrors: []string{`visibility: invalid visibility pattern ":"`},
},
{
name: "invalid visibility: empty namespace and module",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
visibility: ["//:"],
}`),
},
expectedErrors: []string{`visibility: invalid visibility pattern "//:"`},
},
{
name: "//visibility:unknown",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
visibility: ["//visibility:unknown"],
}`),
},
expectedErrors: []string{`unrecognized visibility rule "//visibility:unknown"`},
},
{
name: "//visibility:xxx mixed",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
visibility: ["//visibility:public", "//namespace"],
}
mock_library {
name: "libother",
visibility: ["//visibility:private", "//namespace"],
}`),
},
expectedErrors: []string{
`module "libother": visibility: cannot mix "//visibility:private"` +
` with any other visibility rules`,
`module "libexample": visibility: cannot mix "//visibility:public"` +
` with any other visibility rules`,
},
},
{
name: "//visibility:legacy_public",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
visibility: ["//visibility:legacy_public"],
}`),
},
expectedErrors: []string{
`module "libexample": visibility: //visibility:legacy_public must` +
` not be used`,
},
},
{
// 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{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
visibility: ["//visibility:public"],
}
mock_library {
name: "libsamepackage",
deps: ["libexample"],
}`),
"top/nested/Blueprints": []byte(`
mock_library {
name: "libnested",
deps: ["libexample"],
}`),
"other/Blueprints": []byte(`
mock_library {
name: "libother",
deps: ["libexample"],
}`),
},
},
{
// Verify that //visibility:private allows the module to be referenced from the current
// directory only.
name: "//visibility:private",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
visibility: ["//visibility:private"],
}
mock_library {
name: "libsamepackage",
deps: ["libexample"],
}`),
"top/nested/Blueprints": []byte(`
mock_library {
name: "libnested",
deps: ["libexample"],
}`),
"other/Blueprints": []byte(`
mock_library {
name: "libother",
deps: ["libexample"],
}`),
},
expectedErrors: []string{
`module "libnested" variant "android_common": depends on //top:libexample which is not` +
` visible to this module`,
`module "libother" variant "android_common": depends on //top:libexample which is not` +
` visible to this module`,
},
},
{
// Verify that :__pkg__ allows the module to be referenced from the current directory only.
name: ":__pkg__",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
visibility: [":__pkg__"],
}
mock_library {
name: "libsamepackage",
deps: ["libexample"],
}`),
"top/nested/Blueprints": []byte(`
mock_library {
name: "libnested",
deps: ["libexample"],
}`),
"other/Blueprints": []byte(`
mock_library {
name: "libother",
deps: ["libexample"],
}`),
},
expectedErrors: []string{
`module "libnested" variant "android_common": depends on //top:libexample which is not` +
` visible to this module`,
`module "libother" variant "android_common": depends on //top:libexample which is not` +
` visible to this module`,
},
},
{
// 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{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
visibility: ["//top/nested"],
}
mock_library {
name: "libsamepackage",
deps: ["libexample"],
}`),
"top/nested/Blueprints": []byte(`
mock_library {
name: "libnested",
deps: ["libexample"],
}`),
"top/nested/again/Blueprints": []byte(`
mock_library {
name: "libnestedagain",
deps: ["libexample"],
}`),
"peak/Blueprints": []byte(`
mock_library {
name: "libother",
deps: ["libexample"],
}`),
},
expectedErrors: []string{
`module "libother" variant "android_common": depends on //top:libexample which is not` +
` visible to this module`,
`module "libnestedagain" variant "android_common": depends on //top:libexample which is not` +
` visible to this module`,
},
},
{
// 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{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
visibility: [":__subpackages__"],
}
mock_library {
name: "libsamepackage",
deps: ["libexample"],
}`),
"top/nested/Blueprints": []byte(`
mock_library {
name: "libnested",
deps: ["libexample"],
}`),
"peak/other/Blueprints": []byte(`
mock_library {
name: "libother",
deps: ["libexample"],
}`),
},
expectedErrors: []string{
`module "libother" variant "android_common": depends on //top:libexample which is not` +
` visible to this module`,
},
},
{
// 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{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
visibility: ["//top/nested:__subpackages__", "//other"],
}
mock_library {
name: "libsamepackage",
deps: ["libexample"],
}`),
"top/nested/Blueprints": []byte(`
mock_library {
name: "libnested",
deps: ["libexample"],
}`),
"top/other/Blueprints": []byte(`
mock_library {
name: "libother",
deps: ["libexample"],
}`),
},
expectedErrors: []string{
`module "libother" variant "android_common": depends on //top:libexample which is not` +
` visible to this module`,
},
},
{
// 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{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
visibility: ["//top/nested", "//peak:__subpackages__"],
}
mock_library {
name: "libsamepackage",
deps: ["libexample"],
}`),
"top/nested/Blueprints": []byte(`
mock_library {
name: "libnested",
deps: ["libexample"],
}`),
"peak/other/Blueprints": []byte(`
mock_library {
name: "libother",
deps: ["libexample"],
}`),
},
},
{
// Verify that //vendor... cannot be used outside vendor apart from //vendor:__subpackages__
name: `//vendor`,
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
visibility: ["//vendor:__subpackages__"],
}
mock_library {
name: "libsamepackage",
visibility: ["//vendor/apps/AcmeSettings"],
}`),
"vendor/Blueprints": []byte(`
mock_library {
name: "libvendorexample",
deps: ["libexample"],
visibility: ["//vendor/nested"],
}`),
"vendor/nested/Blueprints": []byte(`
mock_library {
name: "libvendornested",
deps: ["libexample", "libvendorexample"],
}`),
},
expectedErrors: []string{
`module "libsamepackage": visibility: "//vendor/apps/AcmeSettings"` +
` is not allowed. Packages outside //vendor cannot make themselves visible to specific` +
` targets within //vendor, they can only use //vendor:__subpackages__.`,
},
},
// Defaults propagation tests
{
// Check that visibility is the union of the defaults modules.
name: "defaults union, basic",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults",
visibility: ["//other"],
}
mock_library {
name: "libexample",
visibility: ["//top/nested"],
defaults: ["libexample_defaults"],
}
mock_library {
name: "libsamepackage",
deps: ["libexample"],
}`),
"top/nested/Blueprints": []byte(`
mock_library {
name: "libnested",
deps: ["libexample"],
}`),
"other/Blueprints": []byte(`
mock_library {
name: "libother",
deps: ["libexample"],
}`),
"outsider/Blueprints": []byte(`
mock_library {
name: "liboutsider",
deps: ["libexample"],
}`),
},
expectedErrors: []string{
`module "liboutsider" variant "android_common": depends on //top:libexample which is not` +
` visible to this module`,
},
},
{
name: "defaults union, multiple defaults",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults_1",
visibility: ["//other"],
}
mock_defaults {
name: "libexample_defaults_2",
visibility: ["//top/nested"],
}
mock_library {
name: "libexample",
defaults: ["libexample_defaults_1", "libexample_defaults_2"],
}
mock_library {
name: "libsamepackage",
deps: ["libexample"],
}`),
"top/nested/Blueprints": []byte(`
mock_library {
name: "libnested",
deps: ["libexample"],
}`),
"other/Blueprints": []byte(`
mock_library {
name: "libother",
deps: ["libexample"],
}`),
"outsider/Blueprints": []byte(`
mock_library {
name: "liboutsider",
deps: ["libexample"],
}`),
},
expectedErrors: []string{
`module "liboutsider" variant "android_common": depends on //top:libexample which is not` +
` visible to this module`,
},
},
{
name: "//visibility:public mixed with other in defaults",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults",
visibility: ["//visibility:public", "//namespace"],
}
mock_library {
name: "libexample",
defaults: ["libexample_defaults"],
}`),
},
expectedErrors: []string{
`module "libexample_defaults": visibility: cannot mix "//visibility:public"` +
` with any other visibility rules`,
},
},
{
name: "//visibility:public overriding defaults",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults",
visibility: ["//namespace"],
}
mock_library {
name: "libexample",
visibility: ["//visibility:public"],
defaults: ["libexample_defaults"],
}`),
"outsider/Blueprints": []byte(`
mock_library {
name: "liboutsider",
deps: ["libexample"],
}`),
},
},
{
name: "//visibility:public mixed with other from different defaults 1",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults_1",
visibility: ["//namespace"],
}
mock_defaults {
name: "libexample_defaults_2",
visibility: ["//visibility:public"],
}
mock_library {
name: "libexample",
defaults: ["libexample_defaults_1", "libexample_defaults_2"],
}`),
"outsider/Blueprints": []byte(`
mock_library {
name: "liboutsider",
deps: ["libexample"],
}`),
},
},
{
name: "//visibility:public mixed with other from different defaults 2",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults_1",
visibility: ["//visibility:public"],
}
mock_defaults {
name: "libexample_defaults_2",
visibility: ["//namespace"],
}
mock_library {
name: "libexample",
defaults: ["libexample_defaults_1", "libexample_defaults_2"],
}`),
"outsider/Blueprints": []byte(`
mock_library {
name: "liboutsider",
deps: ["libexample"],
}`),
},
},
{
name: "//visibility:private in defaults",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults",
visibility: ["//visibility:private"],
}
mock_library {
name: "libexample",
defaults: ["libexample_defaults"],
}
mock_library {
name: "libsamepackage",
deps: ["libexample"],
}`),
"top/nested/Blueprints": []byte(`
mock_library {
name: "libnested",
deps: ["libexample"],
}`),
"other/Blueprints": []byte(`
mock_library {
name: "libother",
deps: ["libexample"],
}`),
},
expectedErrors: []string{
`module "libnested" variant "android_common": depends on //top:libexample which is not` +
` visible to this module`,
`module "libother" variant "android_common": depends on //top:libexample which is not` +
` visible to this module`,
},
},
{
name: "//visibility:private mixed with other in defaults",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults",
visibility: ["//visibility:private", "//namespace"],
}
mock_library {
name: "libexample",
defaults: ["libexample_defaults"],
}`),
},
expectedErrors: []string{
`module "libexample_defaults": visibility: cannot mix "//visibility:private"` +
` with any other visibility rules`,
},
},
{
name: "//visibility:private overriding defaults",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults",
visibility: ["//namespace"],
}
mock_library {
name: "libexample",
visibility: ["//visibility:private"],
defaults: ["libexample_defaults"],
}`),
},
expectedErrors: []string{
`module "libexample": visibility: cannot mix "//visibility:private"` +
` with any other visibility rules`,
},
},
{
name: "//visibility:private in defaults overridden",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults",
visibility: ["//visibility:private"],
}
mock_library {
name: "libexample",
visibility: ["//namespace"],
defaults: ["libexample_defaults"],
}`),
},
expectedErrors: []string{
`module "libexample": visibility: cannot mix "//visibility:private"` +
` with any other visibility rules`,
},
},
{
name: "//visibility:private mixed with itself",
fs: map[string][]byte{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults_1",
visibility: ["//visibility:private"],
}
mock_defaults {
name: "libexample_defaults_2",
visibility: ["//visibility:private"],
}
mock_library {
name: "libexample",
visibility: ["//visibility:private"],
defaults: ["libexample_defaults_1", "libexample_defaults_2"],
}`),
"outsider/Blueprints": []byte(`
mock_library {
name: "liboutsider",
deps: ["libexample"],
}`),
},
expectedErrors: []string{
`module "liboutsider" variant "android_common": depends on //top:libexample which is not` +
` visible to this module`,
},
},
}
func TestVisibility(t *testing.T) {
buildDir, err := ioutil.TempDir("", "soong_neverallow_test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(buildDir)
for _, test := range visibilityTests {
t.Run(test.name, func(t *testing.T) {
_, errs := testVisibility(buildDir, test.fs)
expectedErrors := test.expectedErrors
if expectedErrors == nil {
FailIfErrored(t, errs)
} else {
for _, expectedError := range expectedErrors {
FailIfNoMatchingErrors(t, expectedError, errs)
}
if len(errs) > len(expectedErrors) {
t.Errorf("additional errors found, expected %d, found %d", len(expectedErrors), len(errs))
for i, expectedError := range expectedErrors {
t.Errorf("expectedErrors[%d] = %s", i, expectedError)
}
for i, err := range errs {
t.Errorf("errs[%d] = %s", i, err)
}
}
}
})
}
}
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)
ctx := NewTestArchContext()
ctx.RegisterModuleType("mock_library", ModuleFactoryAdaptor(newMockLibraryModule))
ctx.RegisterModuleType("mock_defaults", ModuleFactoryAdaptor(defaultsFactory))
ctx.PreArchMutators(registerVisibilityRuleChecker)
ctx.PreArchMutators(RegisterDefaultsPreArchMutators)
ctx.PreArchMutators(registerVisibilityRuleGatherer)
ctx.PostDepsMutators(registerVisibilityRuleEnforcer)
ctx.Register()
ctx.MockFileSystem(fs)
_, errs := ctx.ParseBlueprintsFiles(".")
if len(errs) > 0 {
return ctx, errs
}
_, errs = ctx.PrepareBuildActions(config)
return ctx, errs
}
type mockLibraryProperties struct {
Deps []string
}
type mockLibraryModule struct {
ModuleBase
DefaultableModuleBase
properties mockLibraryProperties
}
func newMockLibraryModule() Module {
m := &mockLibraryModule{}
m.AddProperties(&m.properties)
InitAndroidArchModule(m, HostAndDeviceSupported, MultilibCommon)
InitDefaultableModule(m)
return m
}
type dependencyTag struct {
blueprint.BaseDependencyTag
name string
}
func (j *mockLibraryModule) DepsMutator(ctx BottomUpMutatorContext) {
ctx.AddVariationDependencies(nil, dependencyTag{name: "mockdeps"}, j.properties.Deps...)
}
func (p *mockLibraryModule) GenerateAndroidBuildActions(ModuleContext) {
}
type mockDefaults struct {
ModuleBase
DefaultsModuleBase
}
func defaultsFactory() Module {
m := &mockDefaults{}
InitDefaultsModule(m)
return m
}
func (*mockDefaults) GenerateAndroidBuildActions(ctx ModuleContext) {
}