// Copyright 2019 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.devtools.build.lib.analysis;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;

import com.google.devtools.build.lib.analysis.util.AnalysisTestCase;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** Test for visibility of targets. */
@RunWith(JUnit4.class)
public class VisibilityTest extends AnalysisTestCase {

  void setupArgsScenario() throws Exception {
    scratch.file("tool/tool.sh", "#!/bin/sh", "echo Hello > $2", "cat $1 >> $2");
    scratch.file("rule/BUILD");
    scratch.file(
        "rule/rule.bzl",
        "def _impl(ctx):",
        "  output = ctx.actions.declare_file(ctx.label.name + '.out')",
        "  ctx.actions.run(",
        "    inputs = ctx.files._tool + ctx.files.data,",
        "    executable = ctx.files._tool[0].path,",
        "    arguments =  [f.path for f in ctx.files.data] + [output.path],",
        "    outputs = [output],",
        "  )",
        "",
        "greet = rule(",
        "  implementation = _impl,",
        "  attrs = {",
        "    'data' : attr.label(allow_files=True),",
        "    '_tool' : attr.label(cfg='exec', allow_files=True,",
        "                         default = Label('//tool:tool.sh')),",
        "  },",
        "  outputs = {'out' : '%{name}.out'},",
        ")");
    scratch.file("data/data.txt", "World");
    scratch.file(
        "use/BUILD",
        "load('//rule:rule.bzl', 'greet')",
        "",
        "greet(",
        "  name = 'world',",
        "  data = '//data:data.txt',",
        ")");
  }

  @Test
  public void testToolVisibilityRuleCheckAtRule() throws Exception {
    setupArgsScenario();
    scratch.file("data/BUILD", "exports_files(['data.txt'], visibility=['//visibility:public'])");
    scratch.file("tool/BUILD", "exports_files(['tool.sh'], visibility=['//rule:__pkg__'])");
    useConfiguration("--incompatible_visibility_private_attributes_at_definition");
    update("//use:world");
    assertThat(hasErrors(getConfiguredTarget("//use:world"))).isFalse();
  }

  @Test
  public void testToolVisibilityRuleCheckAtUse() throws Exception {
    setupArgsScenario();
    scratch.file("data/BUILD", "exports_files(['data.txt'], visibility=['//visibility:public'])");
    scratch.file("tool/BUILD", "exports_files(['tool.sh'], visibility=['//rule:__pkg__'])");
    useConfiguration("--noincompatible_visibility_private_attributes_at_definition");

    reporter.removeHandler(failFastHandler);
    assertThrows(ViewCreationFailedException.class, () -> update("//use:world"));
  }

  @Test
  public void testToolVisibilityUseCheckAtUse() throws Exception {
    setupArgsScenario();
    scratch.file("data/BUILD", "exports_files(['data.txt'], visibility=['//visibility:public'])");
    scratch.file("tool/BUILD", "exports_files(['tool.sh'], visibility=['//use:__pkg__'])");
    useConfiguration("--noincompatible_visibility_private_attributes_at_definition");
    update("//use:world");
    assertThat(hasErrors(getConfiguredTarget("//use:world"))).isFalse();
  }

  @Test
  public void testToolVisibilityUseCheckAtRule_fallbackToUse() throws Exception {
    setupArgsScenario();
    scratch.file("data/BUILD", "exports_files(['data.txt'], visibility=['//visibility:public'])");
    scratch.file("tool/BUILD", "exports_files(['tool.sh'], visibility=['//use:__pkg__'])");
    useConfiguration("--incompatible_visibility_private_attributes_at_definition");

    update("//use:world");

    assertThat(hasErrors(getConfiguredTarget("//use:world"))).isFalse();
  }

  @Test
  public void testToolVisibilityPrivateCheckAtUse() throws Exception {
    setupArgsScenario();
    scratch.file("data/BUILD", "exports_files(['data.txt'], visibility=['//visibility:public'])");
    scratch.file("tool/BUILD", "exports_files(['tool.sh'], visibility=['//visibility:private'])");
    useConfiguration("--noincompatible_visibility_private_attributes_at_definition");

    reporter.removeHandler(failFastHandler);
    assertThrows(ViewCreationFailedException.class, () -> update("//use:world"));
  }

  @Test
  public void testToolVisibilityPrivateCheckAtRule() throws Exception {
    setupArgsScenario();
    scratch.file("data/BUILD", "exports_files(['data.txt'], visibility=['//visibility:public'])");
    scratch.file("tool/BUILD", "exports_files(['tool.sh'], visibility=['//visibility:private'])");
    useConfiguration("--incompatible_visibility_private_attributes_at_definition");

    reporter.removeHandler(failFastHandler);
    assertThrows(ViewCreationFailedException.class, () -> update("//use:world"));
  }

  @Test
  public void testDataVisibilityUseCheckPrivateAtUse() throws Exception {
    setupArgsScenario();
    scratch.file("data/BUILD", "exports_files(['data.txt'], visibility=['//use:__pkg__'])");
    scratch.file("tool/BUILD", "exports_files(['tool.sh'], visibility=['//visibility:public'])");
    useConfiguration("--noincompatible_visibility_private_attributes_at_definition");
    update("//use:world");
    assertThat(hasErrors(getConfiguredTarget("//use:world"))).isFalse();
  }

  @Test
  public void testDataVisibilityUseCheckPrivateAtRule() throws Exception {
    setupArgsScenario();
    scratch.file("data/BUILD", "exports_files(['data.txt'], visibility=['//use:__pkg__'])");
    scratch.file("tool/BUILD", "exports_files(['tool.sh'], visibility=['//visibility:public'])");
    useConfiguration("--incompatible_visibility_private_attributes_at_definition");
    update("//use:world");
    assertThat(hasErrors(getConfiguredTarget("//use:world"))).isFalse();
  }

  @Test
  public void testDataVisibilityPrivateCheckPrivateAtRule() throws Exception {
    setupArgsScenario();
    scratch.file("data/BUILD", "exports_files(['data.txt'], visibility=['//visibility:private'])");
    scratch.file("tool/BUILD", "exports_files(['tool.sh'], visibility=['//visibility:public'])");
    useConfiguration("--incompatible_visibility_private_attributes_at_definition");

    reporter.removeHandler(failFastHandler);
    assertThrows(ViewCreationFailedException.class, () -> update("//use:world"));
  }

  @Test
  public void testDataVisibilityPrivateCheckPrivateAtUse() throws Exception {
    setupArgsScenario();
    scratch.file("data/BUILD", "exports_files(['data.txt'], visibility=['//visibility:private'])");
    scratch.file("tool/BUILD", "exports_files(['tool.sh'], visibility=['//visibility:public'])");
    useConfiguration("--noincompatible_visibility_private_attributes_at_definition");

    reporter.removeHandler(failFastHandler);
    assertThrows(ViewCreationFailedException.class, () -> update("//use:world"));
  }

  @Test
  public void testConfigSettingVisibilityAlwaysCheckedAtUse() throws Exception {
    scratch.file(
        "BUILD",
        "load('//build_defs:defs.bzl', 'my_rule')",
        "my_rule(",
        "    name = 'my_target',",
        "    value = select({",
        "        '//config_setting:my_setting': 'foo',",
        "        '//conditions:default': 'bar',",
        "    }),",
        ")");
    scratch.file("build_defs/BUILD");
    scratch.file(
        "build_defs/defs.bzl",
        "def _my_rule_impl(ctx):",
        "    pass",
        "my_rule = rule(",
        "    implementation = _my_rule_impl,",
        "    attrs = {",
        "        'value': attr.string(mandatory = True),",
        "    },",
        ")");
    scratch.file(
        "config_setting/BUILD",
        "config_setting(",
        "    name = 'my_setting',",
        "    values = {'cpu': 'does_not_matter'},",
        "    visibility = ['//:__pkg__'],",
        ")");
    useConfiguration("--incompatible_visibility_private_attributes_at_definition");

    update("//:my_target");
    assertThat(hasErrors(getConfiguredTarget("//:my_target"))).isFalse();
  }

  @Test
  public void testImplicitDependency_samePackageAsDefinition_visible() throws Exception {
    scratch.file(
        "aspect_def/BUILD",
        "sh_binary(",
        "  name = 'aspect_tool',",
        "  srcs = ['a.sh'],",
        "  visibility = ['//visibility:private'])");
    scratch.file(
        "aspect_def/lib.bzl",
        "def _impl_my_aspect(ctx, target):",
        "  return []",
        "my_aspect = aspect(",
        "  _impl_my_aspect,",
        "  attrs = { '_aspect_tool': attr.label(default = '//aspect_def:aspect_tool') },",
        ")");
    scratch.file(
        "rule_def/BUILD",
        "sh_binary(",
        "  name = 'rule_tool',",
        "  srcs = ['a.sh'],",
        "  visibility = ['//visibility:private'])");
    scratch.file(
        "rule_def/lib.bzl",
        "load('//aspect_def:lib.bzl', 'my_aspect')",
        "def _impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  _impl,",
        "  attrs = {",
        "    'dep': attr.label(aspects = [my_aspect]),",
        "    '_rule_tool': attr.label(default = '//rule_def:rule_tool'),",
        "  },",
        ")",
        "simple_starlark_rule = rule(",
        "  _impl,",
        ")");
    scratch.file(
        "foo/BUILD",
        "load('//rule_def:lib.bzl','my_rule', 'simple_starlark_rule')",
        "simple_starlark_rule(name = 'simple_dep')",
        "my_rule(name = 'my_target', dep = ':simple_dep')");

    update("//foo:my_target");

    assertThat(hasErrors(getConfiguredTarget("//foo:my_target"))).isFalse();
  }

  @Test
  public void testAspectImplicitDependencyCheckedAtDefinition_visible() throws Exception {
    scratch.file("inner_aspect/BUILD");
    scratch.file(
        "inner_aspect/lib.bzl",
        "InnerAspectInfo = provider()",
        "def _impl_inner_aspect(ctx, target):",
        "  return [InnerAspectInfo()]",
        "inner_aspect = aspect(",
        "  _impl_inner_aspect,",
        "  attrs = { '_inner_aspect_tool': attr.label(default = '//tool:inner_aspect_tool') },",
        "  provides = [InnerAspectInfo],",
        ")");
    scratch.file("outer_aspect/BUILD");
    scratch.file(
        "outer_aspect/lib.bzl",
        "load('//inner_aspect:lib.bzl', 'InnerAspectInfo')",
        "def _impl_outer_aspect(ctx, target):",
        "  return []",
        "outer_aspect = aspect(",
        "  _impl_outer_aspect,",
        "  attrs = { '_outer_aspect_tool': attr.label(default = '//tool:outer_aspect_tool') },",
        "  required_aspect_providers = [InnerAspectInfo],",
        ")");
    scratch.file("rule/BUILD");
    scratch.file(
        "rule/lib.bzl",
        "load('//inner_aspect:lib.bzl', 'inner_aspect')",
        "load('//outer_aspect:lib.bzl', 'outer_aspect')",
        "def _impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  _impl,",
        "  attrs = {",
        "    'dep': attr.label(aspects = [inner_aspect, outer_aspect]),",
        "    '_rule_tool': attr.label(default = '//tool:rule_tool'),",
        "  },",
        ")",
        "simple_starlark_rule = rule(",
        "  _impl,",
        ")");
    scratch.file(
        "foo/BUILD",
        "load('//rule:lib.bzl','my_rule', 'simple_starlark_rule')",
        "simple_starlark_rule(name = 'simple_dep')",
        "my_rule(name = 'target_with_aspects', dep = ':simple_dep')");
    scratch.file(
        "tool/BUILD",
        "sh_binary(",
        "  name = 'outer_aspect_tool',",
        "  srcs = ['a.sh'],",
        "  visibility = ['//outer_aspect:__pkg__'],",
        ")",
        "sh_binary(",
        "  name = 'inner_aspect_tool',",
        "  srcs = ['a.sh'],",
        "  visibility = ['//inner_aspect:__pkg__'],",
        ")",
        "sh_binary(",
        "  name = 'rule_tool',",
        "  srcs = ['a.sh'],",
        "  visibility = ['//rule:__pkg__'],",
        ")");
    useConfiguration("--incompatible_visibility_private_attributes_at_definition");

    update("//foo:target_with_aspects");

    assertThat(hasErrors(getConfiguredTarget("//foo:target_with_aspects"))).isFalse();
  }

  @Ignore(
      "TODO(b/206127051): The aspects path implicit dependencies with same name are incorrectly"
          + " merged, enable this test when this is fixed.")
  @Test
  public void testAspectImplicitDependencyCheckedAtDefinition_visibleWithNameCollision()
      throws Exception {
    scratch.file("inner_aspect/BUILD");
    scratch.file(
        "inner_aspect/lib.bzl",
        "InnerAspectInfo = provider()",
        "def _impl_inner_aspect(ctx, target):",
        "  return [InnerAspectInfo()]",
        "inner_aspect = aspect(",
        "  _impl_inner_aspect,",
        "  attrs = { '_tool': attr.label(default = '//tool:inner_aspect_tool') },",
        "  provides = [InnerAspectInfo],",
        ")");
    scratch.file("outer_aspect/BUILD");
    scratch.file(
        "outer_aspect/lib.bzl",
        "load('//inner_aspect:lib.bzl', 'InnerAspectInfo')",
        "def _impl_outer_aspect(ctx, target):",
        "  return []",
        "outer_aspect = aspect(",
        "  _impl_outer_aspect,",
        "  attrs = { '_tool': attr.label(default = '//tool:outer_aspect_tool') },",
        "  required_aspect_providers = [InnerAspectInfo],",
        ")");
    scratch.file("rule/BUILD");
    scratch.file(
        "rule/lib.bzl",
        "load('//inner_aspect:lib.bzl', 'inner_aspect')",
        "load('//outer_aspect:lib.bzl', 'outer_aspect')",
        "def _impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  _impl,",
        "  attrs = {",
        "    'dep': attr.label(aspects = [inner_aspect, outer_aspect]),",
        "    '_tool': attr.label(default = '//tool:rule_tool'),",
        "  },",
        ")",
        "simple_starlark_rule = rule(",
        "  _impl,",
        ")");
    scratch.file(
        "foo/BUILD",
        "load('//rule:lib.bzl','my_rule', 'simple_starlark_rule')",
        "simple_starlark_rule(name = 'simple_dep')",
        "my_rule(name = 'target_with_aspects', dep = ':simple_dep')");
    scratch.file(
        "tool/BUILD",
        "sh_binary(",
        "  name = 'outer_aspect_tool',",
        "  srcs = ['a.sh'],",
        "  visibility = ['//outer_aspect:__pkg__'],",
        ")",
        "sh_binary(",
        "  name = 'inner_aspect_tool',",
        "  srcs = ['a.sh'],",
        "  visibility = ['//inner_aspect:__pkg__'],",
        ")",
        "sh_binary(",
        "  name = 'rule_tool',",
        "  srcs = ['a.sh'],",
        "  visibility = ['//rule:__pkg__'],",
        ")");
    useConfiguration("--incompatible_visibility_private_attributes_at_definition");

    update("//foo:target_with_aspects");

    assertThat(hasErrors(getConfiguredTarget("//foo:target_with_aspects"))).isFalse();
  }

  @Test
  public void testAspectImplicitDependencyCheckedAtDefinition_outerAspectToolNotVisible()
      throws Exception {
    scratch.file("inner_aspect/BUILD");
    scratch.file(
        "inner_aspect/lib.bzl",
        "InnerAspectInfo = provider()",
        "def _impl_inner_aspect(ctx, target):",
        "  return [InnerAspectInfo()]",
        "inner_aspect = aspect(",
        "  _impl_inner_aspect,",
        "  attrs = { '_inner_aspect_tool': attr.label(default = '//tool:inner_aspect_tool') },",
        "  provides = [InnerAspectInfo],",
        ")");
    scratch.file("outer_aspect/BUILD");
    scratch.file(
        "outer_aspect/lib.bzl",
        "load('//inner_aspect:lib.bzl', 'InnerAspectInfo')",
        "def _impl_outer_aspect(ctx, target):",
        "  return []",
        "outer_aspect = aspect(",
        "  _impl_outer_aspect,",
        "  attrs = { '_outer_aspect_tool': attr.label(default = '//tool:outer_aspect_tool') },",
        "  required_aspect_providers = [InnerAspectInfo],",
        ")");
    scratch.file("rule/BUILD");
    scratch.file(
        "rule/lib.bzl",
        "load('//inner_aspect:lib.bzl', 'inner_aspect')",
        "load('//outer_aspect:lib.bzl', 'outer_aspect')",
        "def _impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  _impl,",
        "  attrs = {",
        "    'dep': attr.label(aspects = [inner_aspect, outer_aspect]),",
        "    '_rule_tool': attr.label(default = '//tool:rule_tool'),",
        "  },",
        ")",
        "simple_starlark_rule = rule(",
        "  _impl,",
        ")");
    scratch.file(
        "foo/BUILD",
        "load('//rule:lib.bzl','my_rule', 'simple_starlark_rule')",
        "simple_starlark_rule(name = 'simple_dep')",
        "my_rule(name = 'target_with_aspects', dep = ':simple_dep')");
    scratch.file(
        "tool/BUILD",
        "sh_binary(",
        "  name = 'outer_aspect_tool',",
        "  srcs = ['a.sh'],",
        "  visibility = [",
        "    '//inner_aspect:__pkg__',",
        "    '//rule:__pkg__',",
        "  ],",
        ")",
        "sh_binary(",
        "  name = 'inner_aspect_tool',",
        "  srcs = ['a.sh'],",
        "  visibility = ['//inner_aspect:__pkg__'],",
        ")",
        "sh_binary(",
        "  name = 'rule_tool',",
        "  srcs = ['a.sh'],",
        "  visibility = ['//rule:__pkg__'],",
        ")");
    useConfiguration("--incompatible_visibility_private_attributes_at_definition");
    reporter.removeHandler(failFastHandler);

    assertThrows(ViewCreationFailedException.class, () -> update("//foo:target_with_aspects"));
    assertContainsEvent(
        "in //inner_aspect:lib.bzl%inner_aspect,//outer_aspect:lib.bzl%outer_aspect "
            + "aspect on simple_starlark_rule rule //foo:simple_dep: target "
            + "'//tool:outer_aspect_tool' is not visible from target '//outer_aspect:lib.bzl'");
  }

  @Test
  public void testAspectImplicitDependencyCheckedAtDefinition_innerAspectToolNotVisible()
      throws Exception {
    scratch.file("inner_aspect/BUILD");
    scratch.file(
        "inner_aspect/lib.bzl",
        "InnerAspectInfo = provider()",
        "def _impl_inner_aspect(ctx, target):",
        "  return [InnerAspectInfo()]",
        "inner_aspect = aspect(",
        "  _impl_inner_aspect,",
        "  attrs = { '_inner_aspect_tool': attr.label(default = '//tool:inner_aspect_tool') },",
        "  provides = [InnerAspectInfo],",
        ")");
    scratch.file("outer_aspect/BUILD");
    scratch.file(
        "outer_aspect/lib.bzl",
        "load('//inner_aspect:lib.bzl', 'InnerAspectInfo')",
        "def _impl_outer_aspect(ctx, target):",
        "  return []",
        "outer_aspect = aspect(",
        "  _impl_outer_aspect,",
        "  attrs = { '_outer_aspect_tool': attr.label(default = '//tool:outer_aspect_tool') },",
        "  required_aspect_providers = [InnerAspectInfo],",
        ")");
    scratch.file("rule/BUILD");
    scratch.file(
        "rule/lib.bzl",
        "load('//inner_aspect:lib.bzl', 'inner_aspect')",
        "load('//outer_aspect:lib.bzl', 'outer_aspect')",
        "def _impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  _impl,",
        "  attrs = {",
        "    'dep': attr.label(aspects = [inner_aspect, outer_aspect]),",
        "    '_rule_tool': attr.label(default = '//tool:rule_tool'),",
        "  },",
        ")",
        "simple_starlark_rule = rule(",
        "  _impl,",
        ")");
    scratch.file(
        "foo/BUILD",
        "load('//rule:lib.bzl','my_rule', 'simple_starlark_rule')",
        "simple_starlark_rule(name = 'simple_dep')",
        "my_rule(name = 'target_with_aspects', dep = ':simple_dep')");
    scratch.file(
        "tool/BUILD",
        "sh_binary(",
        "  name = 'outer_aspect_tool',",
        "  srcs = ['a.sh'],",
        "  visibility = ['//outer_aspect:__pkg__'],",
        ")",
        "sh_binary(",
        "  name = 'inner_aspect_tool',",
        "  srcs = ['a.sh'],",
        "  visibility = [",
        "    '//outer_aspect:__pkg__',",
        "    '//rule:__pkg__',",
        "  ],",
        ")",
        "sh_binary(",
        "  name = 'rule_tool',",
        "  srcs = ['a.sh'],",
        "  visibility = ['//rule:__pkg__'],",
        ")");
    useConfiguration("--incompatible_visibility_private_attributes_at_definition");
    reporter.removeHandler(failFastHandler);

    assertThrows(ViewCreationFailedException.class, () -> update("//foo:target_with_aspects"));
    assertContainsEvent(
        "in //inner_aspect:lib.bzl%inner_aspect aspect on simple_starlark_rule "
            + "rule //foo:simple_dep: target '//tool:inner_aspect_tool' is not visible from "
            + "target '//inner_aspect:lib.bzl'");
  }

  @Test
  public void testAspectImplicitDependencyCheckedAtDefinition_ruleToolNotVisible()
      throws Exception {
    scratch.file("inner_aspect/BUILD");
    scratch.file(
        "inner_aspect/lib.bzl",
        "InnerAspectInfo = provider()",
        "def _impl_inner_aspect(ctx, target):",
        "  return [InnerAspectInfo()]",
        "inner_aspect = aspect(",
        "  _impl_inner_aspect,",
        "  attrs = { '_inner_aspect_tool': attr.label(default = '//tool:inner_aspect_tool') },",
        "  provides = [InnerAspectInfo],",
        ")");
    scratch.file("outer_aspect/BUILD");
    scratch.file(
        "outer_aspect/lib.bzl",
        "load('//inner_aspect:lib.bzl', 'InnerAspectInfo')",
        "def _impl_outer_aspect(ctx, target):",
        "  return []",
        "outer_aspect = aspect(",
        "  _impl_outer_aspect,",
        "  attrs = { '_outer_aspect_tool': attr.label(default = '//tool:outer_aspect_tool') },",
        "  required_aspect_providers = [InnerAspectInfo],",
        ")");
    scratch.file("rule/BUILD");
    scratch.file(
        "rule/lib.bzl",
        "load('//inner_aspect:lib.bzl', 'inner_aspect')",
        "load('//outer_aspect:lib.bzl', 'outer_aspect')",
        "def _impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  _impl,",
        "  attrs = {",
        "    'dep': attr.label(aspects = [inner_aspect, outer_aspect]),",
        "    '_rule_tool': attr.label(default = '//tool:rule_tool'),",
        "  },",
        ")",
        "simple_starlark_rule = rule(",
        "  _impl,",
        ")");
    scratch.file(
        "foo/BUILD",
        "load('//rule:lib.bzl','my_rule', 'simple_starlark_rule')",
        "simple_starlark_rule(name = 'simple_dep')",
        "my_rule(name = 'target_with_aspects', dep = ':simple_dep')");
    scratch.file(
        "tool/BUILD",
        "sh_binary(",
        "  name = 'outer_aspect_tool',",
        "  srcs = ['a.sh'],",
        "  visibility = ['//outer_aspect:__pkg__'],",
        ")",
        "sh_binary(",
        "  name = 'inner_aspect_tool',",
        "  srcs = ['a.sh'],",
        "  visibility = ['//inner_aspect:__pkg__'],",
        ")",
        "sh_binary(",
        "  name = 'rule_tool',",
        "  srcs = ['a.sh'],",
        "  visibility = [",
        "    '//outer_aspect:__pkg__',",
        "    '//inner_aspect:__pkg__',",
        "  ],",
        ")");
    useConfiguration("--incompatible_visibility_private_attributes_at_definition");
    reporter.removeHandler(failFastHandler);

    assertThrows(ViewCreationFailedException.class, () -> update("//foo:target_with_aspects"));
    assertContainsEvent(
        "in my_rule rule //foo:target_with_aspects: target '//tool:rule_tool' is "
            + "not visible from target '//rule:lib.bzl'");
  }

  void setupFilesScenario(String wantRead) throws Exception {
    scratch.file("src/source.txt", "source");
    scratch.file("src/BUILD", "exports_files(['source.txt'], visibility=['//pkg:__pkg__'])");
    scratch.file("pkg/foo.txt", "foo");
    scratch.file("pkg/bar.txt", "bar");
    scratch.file("pkg/groupfile.txt", "groupfile");
    scratch.file("pkg/unused.txt", "unused");
    scratch.file("pkg/exported.txt", "exported");
    scratch.file(
        "pkg/BUILD",
        "package(default_visibility=['//visibility:public'])",
        "exports_files(['exported.txt'])",
        "",
        "genrule(",
        "  name = 'foobar',",
        "  outs = ['foobar.txt'],",
        "  srcs = ['foo.txt', 'bar.txt'],",
        "  cmd = 'cat $(SRCS) > $@',",
        ")",
        "",
        "filegroup(",
        "  name = 'remotegroup',",
        "  srcs = ['//src:source.txt'],",
        ")",
        "",
        "filegroup(",
        "  name = 'localgroup',",
        "  srcs = [':groupfile.txt'],",
        ")");
    scratch.file(
        "otherpkg/BUILD",
        "genrule(",
        "  name = 'it',",
        "  srcs = ['//pkg:" + wantRead + "'],",
        "  outs = ['it.xt'],",
        "  cmd = 'cp $< $@',",
        ")");
  }

  @Test
  public void testTargetImplicitExport() throws Exception {
    setupFilesScenario("foobar");
    useConfiguration("--noincompatible_no_implicit_file_export");
    update("//otherpkg:it");
    assertThat(hasErrors(getConfiguredTarget("//otherpkg:it"))).isFalse();
  }

  @Test
  public void testTargetNoImplicitExport() throws Exception {
    setupFilesScenario("foobar");
    useConfiguration("--incompatible_no_implicit_file_export");
    update("//otherpkg:it");
    assertThat(hasErrors(getConfiguredTarget("//otherpkg:it"))).isFalse();
  }

  @Test
  public void testLocalFilegroupImplicitExport() throws Exception {
    setupFilesScenario("localgroup");
    useConfiguration("--noincompatible_no_implicit_file_export");
    update("//otherpkg:it");
    assertThat(hasErrors(getConfiguredTarget("//otherpkg:it"))).isFalse();
  }

  @Test
  public void testLocalFilegroupNoImplicitExport() throws Exception {
    setupFilesScenario("localgroup");
    useConfiguration("--incompatible_no_implicit_file_export");
    update("//otherpkg:it");
    assertThat(hasErrors(getConfiguredTarget("//otherpkg:it"))).isFalse();
  }

  @Test
  public void testRemoteFilegroupImplicitExport() throws Exception {
    setupFilesScenario("remotegroup");
    useConfiguration("--noincompatible_no_implicit_file_export");
    update("//otherpkg:it");
    assertThat(hasErrors(getConfiguredTarget("//otherpkg:it"))).isFalse();
  }

  @Test
  public void testRemoteFilegroupNoImplicitExport() throws Exception {
    setupFilesScenario("remotegroup");
    useConfiguration("--incompatible_no_implicit_file_export");
    update("//otherpkg:it");
    assertThat(hasErrors(getConfiguredTarget("//otherpkg:it"))).isFalse();
  }

  @Test
  public void testExportedImplicitExport() throws Exception {
    setupFilesScenario("exported.txt");
    useConfiguration("--noincompatible_no_implicit_file_export");
    update("//otherpkg:it");
    assertThat(hasErrors(getConfiguredTarget("//otherpkg:it"))).isFalse();
  }

  @Test
  public void testExportedNoImplicitExport() throws Exception {
    setupFilesScenario("exported.txt");
    useConfiguration("--incompatible_no_implicit_file_export");
    update("//otherpkg:it");
    assertThat(hasErrors(getConfiguredTarget("//otherpkg:it"))).isFalse();
  }

  @Test
  public void testUnusedImplicitExport() throws Exception {
    setupFilesScenario("unused.txt");
    useConfiguration("--noincompatible_no_implicit_file_export");

    reporter.removeHandler(failFastHandler);
    assertThrows(ViewCreationFailedException.class, () -> update("//otherpkg:it"));
  }

  @Test
  public void testUnusedNoImplicitExport() throws Exception {
    setupFilesScenario("unused.txt");
    useConfiguration("--incompatible_no_implicit_file_export");

    reporter.removeHandler(failFastHandler);
    assertThrows(ViewCreationFailedException.class, () -> update("//otherpkg:it"));
  }

  @Test
  public void testSourcefileImplicitExport() throws Exception {
    setupFilesScenario("foo.txt");
    useConfiguration("--noincompatible_no_implicit_file_export");
    update("//otherpkg:it");
    assertThat(hasErrors(getConfiguredTarget("//otherpkg:it"))).isFalse();
  }

  @Test
  public void testSourcefileNoImplicitExport() throws Exception {
    setupFilesScenario("foo.txt");
    useConfiguration("--incompatible_no_implicit_file_export");

    reporter.removeHandler(failFastHandler);
    assertThrows(ViewCreationFailedException.class, () -> update("//otherpkg:it"));
  }
}
