package android import ( "reflect" "testing" "github.com/google/blueprint" ) var visibilityTests = []struct { name string fs MockFS expectedErrors []string effectiveVisibility map[qualifiedModuleName][]string }{ { name: "invalid visibility: empty list", fs: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: [], }`), }, expectedErrors: []string{`visibility: must contain at least one visibility rule`}, }, { name: "invalid visibility: empty rule", fs: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: [""], }`), }, expectedErrors: []string{`visibility: invalid visibility pattern ""`}, }, { name: "invalid visibility: unqualified", fs: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: ["target"], }`), }, expectedErrors: []string{`visibility: invalid visibility pattern "target"`}, }, { name: "invalid visibility: empty namespace", fs: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: ["//"], }`), }, expectedErrors: []string{`visibility: invalid visibility pattern "//"`}, }, { name: "invalid visibility: empty module", fs: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: [":"], }`), }, expectedErrors: []string{`visibility: invalid visibility pattern ":"`}, }, { name: "invalid visibility: empty namespace and module", fs: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: ["//:"], }`), }, expectedErrors: []string{`visibility: invalid visibility pattern "//:"`}, }, { name: "//visibility:unknown", fs: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: ["//visibility:unknown"], }`), }, expectedErrors: []string{`unrecognized visibility rule "//visibility:unknown"`}, }, { name: "//visibility:xxx mixed", fs: MockFS{ "top/Android.bp": []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: MockFS{ "top/Android.bp": []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: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: ["//visibility:public"], } mock_library { name: "libsamepackage", deps: ["libexample"], } gen_notice { name: "libexample-notice", for: ["libexample"], }`), "top/nested/Android.bp": []byte(` mock_library { name: "libnested", deps: ["libexample"], } gen_notice { name: "nested-notice", for: ["libexample"], }`), "other/Android.bp": []byte(` mock_library { name: "libother", deps: ["libexample"], } gen_notice { name: "other-notice", for: ["libexample"], }`), }, }, { // Verify that //visibility:private allows the module to be referenced from the current // directory only. name: "//visibility:private", fs: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: ["//visibility:private"], } mock_library { name: "libsamepackage", deps: ["libexample"], }`), "top/nested/Android.bp": []byte(` mock_library { name: "libnested", deps: ["libexample"], }`), "other/Android.bp": []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 //visibility:private allows the module to be referenced from the current // directory only. name: "//visibility:private (notices)", fs: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: ["//visibility:private"], } mock_library { name: "libsamepackage", deps: ["libexample"], } gen_notice { name: "libexample-notice", for: ["libexample"], }`), "top/nested/Android.bp": []byte(` gen_notice { name: "nested-notice", for: ["libexample"], }`), "other/Android.bp": []byte(` gen_notice { name: "other-notice", for: ["libexample"], }`), }, expectedErrors: []string{ `module "nested-notice" references "//top:libexample" which is not visible to this` + ` module\nYou may need to add "//top/nested" to its visibility`, `module "other-notice" references "//top:libexample" which is not visible to this module\n` + `You may need to add "//other" to its visibility`, }, }, { // Verify that :__pkg__ allows the module to be referenced from the current directory only. name: ":__pkg__", fs: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: [":__pkg__"], } mock_library { name: "libsamepackage", deps: ["libexample"], }`), "top/nested/Android.bp": []byte(` mock_library { name: "libnested", deps: ["libexample"], }`), "other/Android.bp": []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__ (notices)", fs: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: [":__pkg__"], } gen_notice { name: "libexample-notice", for: ["libexample"], }`), "top/nested/Android.bp": []byte(` gen_notice { name: "nested-notice", for: ["libexample"], }`), "other/Android.bp": []byte(` gen_notice { name: "other-notice", for: ["libexample"], }`), }, expectedErrors: []string{ `module "nested-notice" references "//top:libexample" which is not visible to this module`, `module "other-notice" references "//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: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: ["//top/nested"], } mock_library { name: "libsamepackage", deps: ["libexample"], }`), "top/nested/Android.bp": []byte(` mock_library { name: "libnested", deps: ["libexample"], }`), "top/nested/again/Android.bp": []byte(` mock_library { name: "libnestedagain", deps: ["libexample"], }`), "peak/Android.bp": []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 //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 (notices)", fs: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: ["//top/nested"], } gen_notice { name: "libexample-notice", for: ["libexample"], }`), "top/nested/Android.bp": []byte(` gen_notice { name: "nested-notice", for: ["libexample"], }`), "top/nested/again/Android.bp": []byte(` gen_notice { name: "nestedagain-notice", for: ["libexample"], }`), "peak/Android.bp": []byte(` gen_notice { name: "other-notice", for: ["libexample"], }`), }, expectedErrors: []string{ `module "other-notice" references "//top:libexample" which is not visible to this module`, `module "nestedagain-notice" references "//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: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: [":__subpackages__"], } mock_library { name: "libsamepackage", deps: ["libexample"], }`), "top/nested/Android.bp": []byte(` mock_library { name: "libnested", deps: ["libexample"], }`), "peak/other/Android.bp": []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 :__subpackages__ allows the module to be referenced from the current directory // and sub directories but nowhere else. name: ":__subpackages__ (notices)", fs: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: [":__subpackages__"], } gen_notice { name: "libexample-notice", for: ["libexample"], }`), "top/nested/Android.bp": []byte(` gen_notice { name: "nested-notice", for: ["libexample"], }`), "peak/other/Android.bp": []byte(` gen_notice { name: "other-notice", for: ["libexample"], }`), }, expectedErrors: []string{ `module "other-notice" references "//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: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: ["//top/nested:__subpackages__", "//other"], } mock_library { name: "libsamepackage", deps: ["libexample"], }`), "top/nested/Android.bp": []byte(` mock_library { name: "libnested", deps: ["libexample"], }`), "top/other/Android.bp": []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__ (notices)", fs: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: ["//top/nested:__subpackages__", "//other"], } gen_notice { name: "libexample-notice", for: ["libexample"], }`), "top/nested/Android.bp": []byte(` gen_notice { name: "nested-notice", for: ["libexample"], }`), "top/other/Android.bp": []byte(` gen_notice { name: "other-notice", for: ["libexample"], }`), }, expectedErrors: []string{ `module "other-notice" references "//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: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: ["//top/nested", "//peak:__subpackages__"], } mock_library { name: "libsamepackage", deps: ["libexample"], }`), "top/nested/Android.bp": []byte(` mock_library { name: "libnested", deps: ["libexample"], }`), "peak/other/Android.bp": []byte(` mock_library { name: "libother", deps: ["libexample"], }`), }, }, { // 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__ (notices)"]`, fs: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: ["//top/nested", "//peak:__subpackages__"], } gen_notice { name: "libexample-notice", for: ["libexample"], }`), "top/nested/Android.bp": []byte(` gen_notice { name: "nested-notice", for: ["libexample"], }`), "peak/other/Android.bp": []byte(` gen_notice { name: "other-notice", for: ["libexample"], }`), }, }, { // Verify that //vendor... cannot be used outside vendor apart from //vendor:__subpackages__ name: `//vendor`, fs: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: ["//vendor:__subpackages__"], } mock_library { name: "libsamepackage", visibility: ["//vendor/apps/AcmeSettings"], }`), "vendor/Android.bp": []byte(` mock_library { name: "libvendorexample", deps: ["libexample"], visibility: ["//vendor/nested"], }`), "vendor/nested/Android.bp": []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: MockFS{ "top/Android.bp": []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/Android.bp": []byte(` mock_library { name: "libnested", deps: ["libexample"], }`), "other/Android.bp": []byte(` mock_library { name: "libother", deps: ["libexample"], }`), "outsider/Android.bp": []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`, }, }, { // Check that visibility is the union of the defaults modules. name: "defaults union, basic (notices)", fs: MockFS{ "top/Android.bp": []byte(` mock_defaults { name: "libexample_defaults", visibility: ["//other"], } mock_library { name: "libexample", visibility: ["//top/nested"], defaults: ["libexample_defaults"], } gen_notice { name: "libexample-notice", for: ["libexample"], }`), "top/nested/Android.bp": []byte(` gen_notice { name: "nested-notice", for: ["libexample"], }`), "other/Android.bp": []byte(` gen_notice { name: "other-notice", for: ["libexample"], }`), "outsider/Android.bp": []byte(` gen_notice { name: "outsider-notice", for: ["libexample"], }`), }, expectedErrors: []string{ `module "outsider-notice" references "//top:libexample" which is not visible to this module`, }, }, { name: "defaults union, multiple defaults", fs: MockFS{ "top/Android.bp": []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/Android.bp": []byte(` mock_library { name: "libnested", deps: ["libexample"], }`), "other/Android.bp": []byte(` mock_library { name: "libother", deps: ["libexample"], }`), "outsider/Android.bp": []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 (notices)", fs: MockFS{ "top/Android.bp": []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"], } gen_notice { name: "libexample-notice", for: ["libexample"], }`), "top/nested/Android.bp": []byte(` gen_notice { name: "nested-notice", for: ["libexample"], }`), "other/Android.bp": []byte(` gen_notice { name: "other-notice", for: ["libexample"], }`), "outsider/Android.bp": []byte(` gen_notice { name: "outsider-notice", for: ["libexample"], }`), }, expectedErrors: []string{ `module "outsider-notice" references "//top:libexample" which is not visible to this module`, }, }, { name: "//visibility:public mixed with other in defaults", fs: MockFS{ "top/Android.bp": []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: MockFS{ "top/Android.bp": []byte(` mock_defaults { name: "libexample_defaults", visibility: ["//namespace"], } mock_library { name: "libexample", visibility: ["//visibility:public"], defaults: ["libexample_defaults"], }`), "outsider/Android.bp": []byte(` mock_library { name: "liboutsider", deps: ["libexample"], }`), }, effectiveVisibility: map[qualifiedModuleName][]string{ qualifiedModuleName{pkg: "top", name: "libexample"}: {"//visibility:public"}, }, }, { name: "//visibility:public overriding defaults (notices)", fs: MockFS{ "top/Android.bp": []byte(` mock_defaults { name: "libexample_defaults", visibility: ["//namespace"], } mock_library { name: "libexample", visibility: ["//visibility:public"], defaults: ["libexample_defaults"], }`), "outsider/Android.bp": []byte(` gen_notice { name: "outsider-notice", for: ["libexample"], }`), }, effectiveVisibility: map[qualifiedModuleName][]string{ qualifiedModuleName{pkg: "top", name: "libexample"}: {"//visibility:public"}, }, }, { name: "//visibility:public mixed with other from different defaults 1", fs: MockFS{ "top/Android.bp": []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/Android.bp": []byte(` mock_library { name: "liboutsider", deps: ["libexample"], }`), }, }, { name: "//visibility:public mixed with other from different defaults 1", fs: MockFS{ "top/Android.bp": []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"], } gen_notice { name: "libexample-notice", for: ["libexample"], }`), "outsider/Android.bp": []byte(` gen_notice { name: "outsider-notice", for: ["libexample"], }`), }, }, { name: "//visibility:public mixed with other from different defaults 2", fs: MockFS{ "top/Android.bp": []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/Android.bp": []byte(` mock_library { name: "liboutsider", deps: ["libexample"], }`), }, }, { name: "//visibility:public mixed with other from different defaults 2 (notices)", fs: MockFS{ "top/Android.bp": []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/Android.bp": []byte(` gen_notice { name: "outsider-notice", for: ["libexample"], }`), }, }, { name: "//visibility:private in defaults", fs: MockFS{ "top/Android.bp": []byte(` mock_defaults { name: "libexample_defaults", visibility: ["//visibility:private"], } mock_library { name: "libexample", defaults: ["libexample_defaults"], } mock_library { name: "libsamepackage", deps: ["libexample"], }`), "top/nested/Android.bp": []byte(` mock_library { name: "libnested", deps: ["libexample"], }`), "other/Android.bp": []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 in defaults (notices)", fs: MockFS{ "top/Android.bp": []byte(` mock_defaults { name: "libexample_defaults", visibility: ["//visibility:private"], } mock_library { name: "libexample", defaults: ["libexample_defaults"], } gen_notice { name: "libexample-notice", for: ["libexample"], }`), "top/nested/Android.bp": []byte(` gen_notice { name: "nested-notice", for: ["libexample"], }`), "other/Android.bp": []byte(` gen_notice { name: "other-notice", for: ["libexample"], }`), }, expectedErrors: []string{ `module "nested-notice" references "//top:libexample" which is not visible to this module`, `module "other-notice" references "//top:libexample" which is not visible to this module`, }, }, { name: "//visibility:private mixed with other in defaults", fs: MockFS{ "top/Android.bp": []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: MockFS{ "top/Android.bp": []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: MockFS{ "top/Android.bp": []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 override //visibility:public", fs: MockFS{ "top/Android.bp": []byte(` mock_defaults { name: "libexample_defaults", visibility: ["//visibility:public"], } 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:public override //visibility:private", fs: MockFS{ "top/Android.bp": []byte(` mock_defaults { name: "libexample_defaults", visibility: ["//visibility:private"], } mock_library { name: "libexample", visibility: ["//visibility:public"], defaults: ["libexample_defaults"], }`), }, expectedErrors: []string{ `module "libexample": visibility: cannot mix "//visibility:private" with any other visibility rules`, }, }, { name: "//visibility:override must be first in the list", fs: MockFS{ "top/Android.bp": []byte(` mock_library { name: "libexample", visibility: ["//other", "//visibility:override", "//namespace"], }`), }, expectedErrors: []string{ `module "libexample": visibility: "//visibility:override" may only be used at the start of the visibility rules`, }, }, { name: "//visibility:override discards //visibility:private", fs: MockFS{ "top/Android.bp": []byte(` mock_defaults { name: "libexample_defaults", visibility: ["//visibility:private"], } mock_library { name: "libexample", // Make this visibility to //other but not //visibility:private visibility: ["//visibility:override", "//other"], defaults: ["libexample_defaults"], }`), "other/Android.bp": []byte(` mock_library { name: "libother", deps: ["libexample"], }`), }, }, { name: "//visibility:override discards //visibility:private (notices)", fs: MockFS{ "top/Android.bp": []byte(` mock_defaults { name: "libexample_defaults", visibility: ["//visibility:private"], } mock_library { name: "libexample", // Make this visibility to //other but not //visibility:private visibility: ["//visibility:override", "//other"], defaults: ["libexample_defaults"], }`), "other/Android.bp": []byte(` gen_notice { name: "other-notice", for: ["libexample"], }`), }, }, { name: "//visibility:override discards //visibility:public", fs: MockFS{ "top/Android.bp": []byte(` mock_defaults { name: "libexample_defaults", visibility: ["//visibility:public"], } mock_library { name: "libexample", // Make this visibility to //other but not //visibility:public visibility: ["//visibility:override", "//other"], defaults: ["libexample_defaults"], }`), "other/Android.bp": []byte(` mock_library { name: "libother", deps: ["libexample"], }`), "namespace/Android.bp": []byte(` mock_library { name: "libnamespace", deps: ["libexample"], }`), }, expectedErrors: []string{ `module "libnamespace" variant "android_common": depends on //top:libexample which is not visible to this module\nYou may need to add "//namespace" to its visibility`, }, }, { name: "//visibility:override discards //visibility:public (notices)", fs: MockFS{ "top/Android.bp": []byte(` mock_defaults { name: "libexample_defaults", visibility: ["//visibility:public"], } mock_library { name: "libexample", // Make this visibility to //other but not //visibility:public visibility: ["//visibility:override", "//other"], defaults: ["libexample_defaults"], }`), "other/Android.bp": []byte(` gen_notice { name: "other-notice", for: ["libexample"], }`), "namespace/Android.bp": []byte(` gen_notice { name: "namespace-notice", for: ["libexample"], }`), }, expectedErrors: []string{ `module "namespace-notice" references "//top:libexample" which is not visible to this module\nYou may need to add "//namespace" to its visibility`, }, }, { name: "//visibility:override discards defaults supplied rules", fs: MockFS{ "top/Android.bp": []byte(` mock_defaults { name: "libexample_defaults", visibility: ["//namespace"], } mock_library { name: "libexample", // Make this visibility to //other but not //namespace visibility: ["//visibility:override", "//other"], defaults: ["libexample_defaults"], }`), "other/Android.bp": []byte(` mock_library { name: "libother", deps: ["libexample"], }`), "namespace/Android.bp": []byte(` mock_library { name: "libnamespace", deps: ["libexample"], }`), }, expectedErrors: []string{ `module "libnamespace" variant "android_common": depends on //top:libexample which is not visible to this module\nYou may need to add "//namespace" to its visibility`, }, }, { name: "//visibility:override discards defaults supplied rules (notices)", fs: MockFS{ "top/Android.bp": []byte(` mock_defaults { name: "libexample_defaults", visibility: ["//namespace"], } mock_library { name: "libexample", // Make this visibility to //other but not //namespace visibility: ["//visibility:override", "//other"], defaults: ["libexample_defaults"], }`), "other/Android.bp": []byte(` gen_notice { name: "other-notice", for: ["libexample"], }`), "namespace/Android.bp": []byte(` gen_notice { name: "namespace-notice", for: ["libexample"], }`), }, expectedErrors: []string{ `module "namespace-notice" references "//top:libexample" which is not visible to this module\nYou may need to add "//namespace" to its visibility`, }, }, { name: "//visibility:override can override //visibility:public with //visibility:private", fs: MockFS{ "top/Android.bp": []byte(` mock_defaults { name: "libexample_defaults", visibility: ["//visibility:public"], } mock_library { name: "libexample", visibility: ["//visibility:override", "//visibility:private"], defaults: ["libexample_defaults"], }`), "namespace/Android.bp": []byte(` mock_library { name: "libnamespace", deps: ["libexample"], }`), }, expectedErrors: []string{ `module "libnamespace" variant "android_common": depends on //top:libexample which is not visible to this module`, }, }, { name: "//visibility:override can override //visibility:public with //visibility:private (notices)", fs: MockFS{ "top/Android.bp": []byte(` mock_defaults { name: "libexample_defaults", visibility: ["//visibility:public"], } mock_library { name: "libexample", visibility: ["//visibility:override", "//visibility:private"], defaults: ["libexample_defaults"], }`), "namespace/Android.bp": []byte(` gen_notice { name: "namespace-notice", for: ["libexample"], }`), }, expectedErrors: []string{ `module "namespace-notice" references "//top:libexample" which is not visible to this module`, }, }, { name: "//visibility:override can override //visibility:private with //visibility:public", fs: MockFS{ "top/Android.bp": []byte(` mock_defaults { name: "libexample_defaults", visibility: ["//visibility:private"], } mock_library { name: "libexample", visibility: ["//visibility:override", "//visibility:public"], defaults: ["libexample_defaults"], }`), "namespace/Android.bp": []byte(` mock_library { name: "libnamespace", deps: ["libexample"], }`), }, }, { name: "//visibility:override can override //visibility:private with //visibility:public (notices)", fs: MockFS{ "top/Android.bp": []byte(` mock_defaults { name: "libexample_defaults", visibility: ["//visibility:private"], } mock_library { name: "libexample", visibility: ["//visibility:override", "//visibility:public"], defaults: ["libexample_defaults"], }`), "namespace/Android.bp": []byte(` gen_notice { name: "namespace-notice", for: ["libexample"], }`), }, }, { name: "//visibility:private mixed with itself", fs: MockFS{ "top/Android.bp": []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/Android.bp": []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:private mixed with itself (notices)", fs: MockFS{ "top/Android.bp": []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/Android.bp": []byte(` gen_notice { name: "outsider-notice", for: ["libexample"], }`), }, expectedErrors: []string{ `module "outsider-notice" references "//top:libexample" which is not visible to this module`, }, }, // Defaults module's defaults_visibility tests { name: "defaults_visibility invalid", fs: MockFS{ "top/Android.bp": []byte(` mock_defaults { name: "top_defaults", defaults_visibility: ["//visibility:invalid"], }`), }, expectedErrors: []string{ `defaults_visibility: unrecognized visibility rule "//visibility:invalid"`, }, }, { name: "defaults_visibility overrides package default", fs: MockFS{ "top/Android.bp": []byte(` package { default_visibility: ["//visibility:private"], } mock_defaults { name: "top_defaults", defaults_visibility: ["//visibility:public"], }`), "outsider/Android.bp": []byte(` mock_library { name: "liboutsider", defaults: ["top_defaults"], }`), }, }, // Package default_visibility tests { name: "package default_visibility property is checked", fs: MockFS{ "top/Android.bp": []byte(` package { default_visibility: ["//visibility:invalid"], }`), }, expectedErrors: []string{`default_visibility: unrecognized visibility rule "//visibility:invalid"`}, }, { // This test relies on the default visibility being legacy_public. name: "package default_visibility property used when no visibility specified", fs: MockFS{ "top/Android.bp": []byte(` package { default_visibility: ["//visibility:private"], } mock_library { name: "libexample", }`), "outsider/Android.bp": []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`, }, }, { // This test relies on the default visibility being legacy_public. name: "package default_visibility property used when no visibility specified (notices)", fs: MockFS{ "top/Android.bp": []byte(` package { default_visibility: ["//visibility:private"], } mock_library { name: "libexample", }`), "outsider/Android.bp": []byte(` gen_notice { name: "outsider-notice", for: ["libexample"], }`), }, expectedErrors: []string{ `module "outsider-notice" references "//top:libexample" which is not visible to this module`, }, }, { name: "package default_visibility public does not override visibility private", fs: MockFS{ "top/Android.bp": []byte(` package { default_visibility: ["//visibility:public"], } mock_library { name: "libexample", visibility: ["//visibility:private"], }`), "outsider/Android.bp": []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: "package default_visibility public does not override visibility private (notices)", fs: MockFS{ "top/Android.bp": []byte(` package { default_visibility: ["//visibility:public"], } mock_library { name: "libexample", visibility: ["//visibility:private"], }`), "outsider/Android.bp": []byte(` gen_notice { name: "outsider-notice", for: ["libexample"], }`), }, expectedErrors: []string{ `module "outsider-notice" references "//top:libexample" which is not visible to this module`, }, }, { name: "package default_visibility private does not override visibility public", fs: MockFS{ "top/Android.bp": []byte(` package { default_visibility: ["//visibility:private"], } mock_library { name: "libexample", visibility: ["//visibility:public"], }`), "outsider/Android.bp": []byte(` mock_library { name: "liboutsider", deps: ["libexample"], }`), }, }, { name: "package default_visibility private does not override visibility public (notices)", fs: MockFS{ "top/Android.bp": []byte(` package { default_visibility: ["//visibility:private"], } mock_library { name: "libexample", visibility: ["//visibility:public"], }`), "outsider/Android.bp": []byte(` gen_notice { name: "outsider-notice", for: ["libexample"], }`), }, }, { name: "package default_visibility :__subpackages__", fs: MockFS{ "top/Android.bp": []byte(` package { default_visibility: [":__subpackages__"], } mock_library { name: "libexample", }`), "top/nested/Android.bp": []byte(` mock_library { name: "libnested", deps: ["libexample"], }`), "outsider/Android.bp": []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: "package default_visibility :__subpackages__ (notices)", fs: MockFS{ "top/Android.bp": []byte(` package { default_visibility: [":__subpackages__"], } mock_library { name: "libexample", }`), "top/nested/Android.bp": []byte(` gen_notice { name: "nested-notice", for: ["libexample"], }`), "outsider/Android.bp": []byte(` gen_notice { name: "outsider-notice", for: ["libexample"], }`), }, expectedErrors: []string{ `module "outsider-notice" references "//top:libexample" which is not visible to this module`, }, }, { name: "package default_visibility inherited to subpackages", fs: MockFS{ "top/Android.bp": []byte(` package { default_visibility: ["//outsider"], } mock_library { name: "libexample", visibility: [":__subpackages__"], }`), "top/nested/Android.bp": []byte(` mock_library { name: "libnested", deps: ["libexample"], }`), "outsider/Android.bp": []byte(` mock_library { name: "liboutsider", deps: ["libexample", "libnested"], }`), }, expectedErrors: []string{ `module "liboutsider" variant "android_common": depends on //top:libexample which is not` + ` visible to this module`, }, }, { name: "package default_visibility inherited to subpackages (notices)", fs: MockFS{ "top/Android.bp": []byte(` package { default_visibility: ["//outsider"], } mock_library { name: "libexample", visibility: [":__subpackages__"], }`), "top/nested/Android.bp": []byte(` mock_library { name: "libnested", deps: ["libexample"], } gen_notice { name: "nested-notice", for: ["libexample"], }`), "outsider/Android.bp": []byte(` gen_notice { name: "outsider-notice", for: ["libexample", "libnested"], }`), }, expectedErrors: []string{ `module "outsider-notice" references "//top:libexample" which is not visible to this module`, }, }, { name: "package default_visibility inherited to subpackages", fs: MockFS{ "top/Android.bp": []byte(` package { default_visibility: ["//visibility:private"], }`), "top/nested/Android.bp": []byte(` package { default_visibility: ["//outsider"], } mock_library { name: "libnested", }`), "top/other/Android.bp": []byte(` mock_library { name: "libother", }`), "outsider/Android.bp": []byte(` mock_library { name: "liboutsider", deps: ["libother", "libnested"], }`), }, expectedErrors: []string{ `module "liboutsider" variant "android_common": depends on //top/other:libother which is` + ` not visible to this module`, }, }, { name: "package default_visibility inherited to subpackages (notices)", fs: MockFS{ "top/Android.bp": []byte(` package { default_visibility: ["//visibility:private"], }`), "top/nested/Android.bp": []byte(` package { default_visibility: ["//outsider"], } mock_library { name: "libnested", }`), "top/other/Android.bp": []byte(` mock_library { name: "libother", } gen_notice { name: "other-notice", for: ["libother"], }`), "outsider/Android.bp": []byte(` gen_notice { name: "outsider-notice", for: ["libother", "libnested"], }`), }, expectedErrors: []string{ `module "outsider-notice" references "//top/other:libother" which is not visible to this` + ` module\nYou may need to add "//outsider" to its visibility`, }, }, { name: "verify that prebuilt dependencies are ignored for visibility reasons (not preferred)", fs: MockFS{ "prebuilts/Android.bp": []byte(` prebuilt { name: "module", visibility: ["//top/other"], }`), "top/sources/source_file": nil, "top/sources/Android.bp": []byte(` source { name: "module", visibility: ["//top/other"], }`), "top/other/source_file": nil, "top/other/Android.bp": []byte(` source { name: "other", deps: [":module"], }`), }, }, { name: "verify that prebuilt dependencies are ignored for visibility reasons (not preferred) (notices)", fs: MockFS{ "prebuilts/Android.bp": []byte(` prebuilt { name: "module", visibility: ["//top/other"], }`), "top/sources/source_file": nil, "top/sources/Android.bp": []byte(` source { name: "module", visibility: ["//top/other"], }`), "top/other/source_file": nil, "top/other/Android.bp": []byte(` gen_notice { name: "other-notice", for: ["module"], }`), }, }, { name: "verify that prebuilt dependencies are ignored for visibility reasons (preferred)", fs: MockFS{ "prebuilts/Android.bp": []byte(` prebuilt { name: "module", visibility: ["//top/other"], prefer: true, }`), "top/sources/source_file": nil, "top/sources/Android.bp": []byte(` source { name: "module", visibility: ["//top/other"], }`), "top/other/source_file": nil, "top/other/Android.bp": []byte(` source { name: "other", deps: [":module"], }`), }, }, { name: "verify that prebuilt dependencies are ignored for visibility reasons (preferred) (notices)", fs: MockFS{ "prebuilts/Android.bp": []byte(` prebuilt { name: "module", visibility: ["//top/other"], prefer: true, }`), "top/sources/source_file": nil, "top/sources/Android.bp": []byte(` source { name: "module", visibility: ["//top/other"], }`), "top/other/source_file": nil, "top/other/Android.bp": []byte(` gen_notice { name: "other-notice", for: ["module"], }`), }, }, { name: "ensure visibility properties are checked for correctness", fs: MockFS{ "top/Android.bp": []byte(` mock_parent { name: "parent", visibility: ["//top/nested"], child: { name: "libchild", visibility: ["top/other"], }, }`), }, expectedErrors: []string{ `module "parent": child.visibility: invalid visibility pattern "top/other"`, }, }, { name: "invalid visibility added to child detected during gather phase", fs: MockFS{ "top/Android.bp": []byte(` mock_parent { name: "parent", visibility: ["//top/nested"], child: { name: "libchild", invalid_visibility: ["top/other"], }, }`), }, expectedErrors: []string{ // That this error is reported against the child not the parent shows it was // not being detected in the parent which is correct as invalid_visibility is // purposely not added to the list of visibility properties to check, and was // in fact detected in the child in the gather phase. Contrast this error message // with the preceding one. `module "libchild" \(created by module "parent"\): visibility: invalid visibility pattern "top/other"`, }, }, { name: "automatic visibility inheritance enabled", fs: MockFS{ "top/Android.bp": []byte(` mock_parent { name: "parent", visibility: ["//top/nested"], child: { name: "libchild", visibility: ["//top/other"], }, }`), "top/nested/Android.bp": []byte(` mock_library { name: "libnested", deps: ["libchild"], }`), "top/other/Android.bp": []byte(` mock_library { name: "libother", deps: ["libchild"], }`), }, }, { name: "automatic visibility inheritance enabled (notices)", fs: MockFS{ "top/Android.bp": []byte(` mock_parent { name: "parent", visibility: ["//top/nested"], child: { name: "libchild", visibility: ["//top/other"], }, }`), "top/nested/Android.bp": []byte(` gen_notice { name: "nested-notice", for: ["libchild"], }`), "top/other/Android.bp": []byte(` gen_notice { name: "other-notice", for: ["libchild"], }`), }, }, { name: "any_system_partition visibility works", fs: MockFS{ "top/Android.bp": []byte(` android_filesystem { name: "foo", deps: ["bar"], }`), "top/nested/Android.bp": []byte(` package(default_visibility=["//visibility:private"]) mock_library { name: "bar", visibility: ["//visibility:any_system_partition"], }`), }, }, { name: "any_system_partition visibility works with the other visibility", fs: MockFS{ "top/Android.bp": []byte(` android_filesystem { name: "foo", deps: ["bar"], }`), "top2/Android.bp": []byte(``), "top/nested/Android.bp": []byte(` package(default_visibility=["//visibility:private"]) mock_library { name: "bar", visibility: [ "//top2", "//visibility:any_system_partition" ], }`), }, }, { name: "any_system_partition visibility doesn't work for non-partitions", fs: MockFS{ "top/Android.bp": []byte(` mock_library { name: "foo", deps: ["bar"], }`), "top/nested/Android.bp": []byte(` mock_library { name: "bar", visibility: ["//visibility:any_system_partition"], }`), }, expectedErrors: []string{`module "foo" variant "android_common": depends on //top/nested:bar which is not visible to this module`}, }, { name: "any_system_partition visibility doesn't work for vendor partitions", fs: MockFS{ "top/Android.bp": []byte(` android_filesystem { name: "foo", partition_type: "vendor", deps: ["bar"], }`), "top/nested/Android.bp": []byte(` package(default_visibility=["//visibility:private"]) mock_library { name: "bar", visibility: ["//visibility:any_system_partition"], }`), }, expectedErrors: []string{`module "foo" variant "android_common": depends on //top/nested:bar which is not visible to this module`}, }, { name: "Vendor modules are visible to any vendor partition by default", fs: MockFS{ "top/Android.bp": []byte(` android_filesystem { name: "foo", partition_type: "vendor", deps: ["bar"], }`), "top/nested/Android.bp": []byte(` package(default_visibility=["//visibility:private"]) mock_library { name: "bar", vendor: true, }`), }, }, { name: "Not visible to vendor partitions when using any_system_partiton, even if vendor: true", fs: MockFS{ "top/Android.bp": []byte(` android_filesystem { name: "foo", partition_type: "vendor", deps: ["bar"], }`), "top/nested/Android.bp": []byte(` package(default_visibility=["//visibility:private"]) mock_library { name: "bar", vendor: true, visibility: ["//visibility:any_system_partition"], }`), }, expectedErrors: []string{`module "foo" variant "android_common": depends on //top/nested:bar which is not visible to this module`}, }, { name: "unknown any_partition specs throw errors", fs: MockFS{ "top/nested/Android.bp": []byte(` package(default_visibility=["//visibility:private"]) mock_library { name: "bar", visibility: ["//visibility:any_unknown_partition"], }`), }, expectedErrors: []string{`unrecognized visibility rule "//visibility:any_unknown_partition"`}, }, } func TestVisibility(t *testing.T) { for _, test := range visibilityTests { t.Run(test.name, func(t *testing.T) { result := GroupFixturePreparers( // General preparers in alphabetical order as test infrastructure will enforce correct // registration order. PrepareForTestWithArchMutator, PrepareForTestWithDefaults, PrepareForTestWithGenNotice, PrepareForTestWithOverrides, PrepareForTestWithPackageModule, PrepareForTestWithPrebuilts, PrepareForTestWithVisibility, // Additional test specific preparers. FixtureRegisterWithContext(func(ctx RegistrationContext) { ctx.RegisterModuleType("mock_library", newMockLibraryModule) ctx.RegisterModuleType("mock_parent", newMockParentFactory) ctx.RegisterModuleType("mock_defaults", defaultsFactory) ctx.RegisterModuleType("android_filesystem", newMockFilesystemModule) }), prepareForTestWithFakePrebuiltModules, // Add additional files to the mock filesystem test.fs.AddToFixture(), ). ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern(test.expectedErrors)). RunTest(t) if test.effectiveVisibility != nil { checkEffectiveVisibility(t, result, test.effectiveVisibility) } }) } } func checkEffectiveVisibility(t *testing.T, result *TestResult, effectiveVisibility map[qualifiedModuleName][]string) { for moduleName, expectedRules := range effectiveVisibility { rule := effectiveVisibilityRules(result.Config, moduleName) stringRules := rule.Strings() AssertDeepEquals(t, "effective rules mismatch", expectedRules, stringRules) } } 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 mockFilesystemModuleProperties struct { Partition_type *string Deps []string } type mockFilesystemModule struct { ModuleBase properties mockFilesystemModuleProperties } func (j *mockFilesystemModule) DepsMutator(ctx BottomUpMutatorContext) { ctx.AddVariationDependencies(nil, dependencyTag{name: "mockdeps"}, j.properties.Deps...) } func (p *mockFilesystemModule) GenerateAndroidBuildActions(ModuleContext) { } func (p *mockFilesystemModule) PartitionType() string { if p.properties.Partition_type == nil { return "system" } return *p.properties.Partition_type } func newMockFilesystemModule() Module { m := &mockFilesystemModule{} m.AddProperties(&m.properties) InitAndroidArchModule(m, DeviceSupported, MultilibCommon) return m } type mockDefaults struct { ModuleBase DefaultsModuleBase } func defaultsFactory() Module { m := &mockDefaults{} InitDefaultsModule(m) return m } type mockParentProperties struct { Child struct { Name *string // Visibility to pass to the child module. Visibility []string // Purposely not validated visibility to pass to the child. Invalid_visibility []string } } type mockParent struct { ModuleBase DefaultableModuleBase properties mockParentProperties } func (p *mockParent) GenerateAndroidBuildActions(ModuleContext) { } func newMockParentFactory() Module { m := &mockParent{} m.AddProperties(&m.properties) InitAndroidArchModule(m, HostAndDeviceSupported, MultilibCommon) InitDefaultableModule(m) AddVisibilityProperty(m, "child.visibility", &m.properties.Child.Visibility) m.SetDefaultableHook(func(ctx DefaultableHookContext) { visibility := m.properties.Child.Visibility visibility = append(visibility, m.properties.Child.Invalid_visibility...) ctx.CreateModule(newMockLibraryModule, &struct { Name *string Visibility []string }{m.properties.Child.Name, visibility}) }) return m } func testVisibilityRuleSet(t *testing.T, rules, extra, expected []string) { t.Helper() set := &visibilityRuleSet{rules} err := set.Widen(extra) if err != nil { t.Error(err) return } actual := set.Strings() if !reflect.DeepEqual(actual, expected) { t.Errorf("mismatching rules after extend: expected %#v, actual %#v", expected, actual) } } func TestVisibilityRuleSet(t *testing.T) { t.Run("extend empty", func(t *testing.T) { testVisibilityRuleSet(t, nil, []string{"//foo"}, []string{"//foo"}) }) t.Run("extend", func(t *testing.T) { testVisibilityRuleSet(t, []string{"//foo"}, []string{"//bar"}, []string{"//bar", "//foo"}) }) t.Run("extend duplicate", func(t *testing.T) { testVisibilityRuleSet(t, []string{"//foo"}, []string{"//bar", "//foo"}, []string{"//bar", "//foo"}) }) t.Run("extend public", func(t *testing.T) { testVisibilityRuleSet(t, []string{"//visibility:public"}, []string{"//foo"}, []string{"//visibility:public"}) }) t.Run("extend private", func(t *testing.T) { testVisibilityRuleSet(t, []string{"//visibility:private"}, []string{"//foo"}, []string{"//foo"}) }) t.Run("extend with public", func(t *testing.T) { testVisibilityRuleSet(t, []string{"//foo"}, []string{"//visibility:public"}, []string{"//visibility:public"}) }) t.Run("extend with private", func(t *testing.T) { t.Helper() set := &visibilityRuleSet{[]string{"//foo"}} err := set.Widen([]string{"//visibility:private"}) expectedError := `"//visibility:private" does not widen the visibility` if err == nil { t.Errorf("missing error") } else if err.Error() != expectedError { t.Errorf("expected error %q found error %q", expectedError, err) } }) }