// Copyright 2016 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.starlark;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.devtools.build.lib.analysis.OutputGroupInfo.INTERNAL_SUFFIX;
import static org.junit.Assert.assertThrows;

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.truth.Correspondence;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.FileProvider;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
import com.google.devtools.build.lib.analysis.OutputGroupInfo;
import com.google.devtools.build.lib.analysis.RunfilesProvider;
import com.google.devtools.build.lib.analysis.config.StarlarkDefinedConfigTransition;
import com.google.devtools.build.lib.analysis.configuredtargets.FileConfiguredTarget;
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget;
import com.google.devtools.build.lib.analysis.starlark.StarlarkAttributeTransitionProvider;
import com.google.devtools.build.lib.analysis.starlark.StarlarkRuleTransitionProvider;
import com.google.devtools.build.lib.analysis.test.AnalysisFailure;
import com.google.devtools.build.lib.analysis.test.AnalysisFailureInfo;
import com.google.devtools.build.lib.analysis.test.AnalysisTestResultInfo;
import com.google.devtools.build.lib.analysis.test.InstrumentedFilesInfo;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.Depset;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException;
import com.google.devtools.build.lib.packages.BuildSetting;
import com.google.devtools.build.lib.packages.FunctionSplitTransitionAllowlist;
import com.google.devtools.build.lib.packages.Provider;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.StarlarkProvider;
import com.google.devtools.build.lib.packages.StructImpl;
import com.google.devtools.build.lib.packages.Type;
import com.google.devtools.build.lib.rules.objc.ObjcProvider;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import java.io.IOException;
import java.util.List;
import net.starlark.java.eval.NoneType;
import net.starlark.java.eval.Sequence;
import net.starlark.java.eval.Starlark;
import net.starlark.java.eval.StarlarkInt;
import net.starlark.java.eval.StarlarkList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** Integration tests for Starlark. */
@RunWith(JUnit4.class)
public class StarlarkIntegrationTest extends BuildViewTestCase {
  protected boolean keepGoing() {
    return false;
  }

  @Before
  public void setupMyInfo() throws IOException {
    scratch.file("myinfo/myinfo.bzl", "MyInfo = provider()");

    scratch.file("myinfo/BUILD");
  }

  private StructImpl getMyInfoFromTarget(ConfiguredTarget configuredTarget) throws Exception {
    Provider.Key key =
        new StarlarkProvider.Key(
            Label.parseAbsolute("//myinfo:myinfo.bzl", ImmutableMap.of()), "MyInfo");
    return (StructImpl) configuredTarget.get(key);
  }

  @Test
  public void testRemoteLabelAsDefaultAttributeValue() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "def _impl(ctx):",
        "  pass",
        "my_rule = rule(implementation = _impl,",
        "    attrs = { 'dep' : attr.label_list(default=[\"@r//:t\"]) })");

    // We are only interested in whether the label string in the default value can be converted
    // to a proper Label without an exception (see GitHub issue #1442).
    // Consequently, we expect getTarget() to fail later since the repository does not exist.
    checkError(
        "test/starlark",
        "the_rule",
        "no such package '@r//': The repository '@r' could not be resolved",
        "load('//test/starlark:extension.bzl', 'my_rule')",
        "",
        "my_rule(name='the_rule')");
  }

  @Test
  public void testMainRepoLabelWorkspaceRoot() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "load('//myinfo:myinfo.bzl', 'MyInfo')",
        "def _impl(ctx):",
        "  return [MyInfo(result = ctx.label.workspace_root)]",
        "my_rule = rule(implementation = _impl, attrs = { })");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'my_rule')",
        "my_rule(name='t')");

    ConfiguredTarget myTarget = getConfiguredTarget("//test/starlark:t");
    String result = (String) getMyInfoFromTarget(myTarget).getValue("result");
    assertThat(result).isEmpty();
  }

  @Test
  public void testExternalRepoLabelWorkspaceRoot_subdirRepoLayout() throws Exception {
    scratch.overwriteFile(
        "WORKSPACE",
        new ImmutableList.Builder<String>()
            .addAll(analysisMock.getWorkspaceContents(mockToolsConfig))
            .add("local_repository(name='r', path='/r')")
            .build());

    scratch.file("/r/WORKSPACE");
    scratch.file(
        "/r/test/starlark/extension.bzl",
        "load('@//myinfo:myinfo.bzl', 'MyInfo')",
        "def _impl(ctx):",
        "  return [MyInfo(result = ctx.label.workspace_root)]",
        "my_rule = rule(implementation = _impl, attrs = { })");
    scratch.file(
        "/r/BUILD", "load('//:test/starlark/extension.bzl', 'my_rule')", "my_rule(name='t')");

    // Required since we have a new WORKSPACE file.
    invalidatePackages(true);

    ConfiguredTarget myTarget = getConfiguredTarget("@r//:t");
    String result = (String) getMyInfoFromTarget(myTarget).getValue("result");
    assertThat(result).isEqualTo("external/r");
  }

  @Test
  public void testExternalRepoLabelWorkspaceRoot_siblingRepoLayout() throws Exception {
    scratch.overwriteFile(
        "WORKSPACE",
        new ImmutableList.Builder<String>()
            .addAll(analysisMock.getWorkspaceContents(mockToolsConfig))
            .add("local_repository(name='r', path='/r')")
            .build());

    scratch.file("/r/WORKSPACE");
    scratch.file(
        "/r/test/starlark/extension.bzl",
        "load('@//myinfo:myinfo.bzl', 'MyInfo')",
        "def _impl(ctx):",
        "  return [MyInfo(result = ctx.label.workspace_root)]",
        "my_rule = rule(implementation = _impl, attrs = { })");
    scratch.file(
        "/r/BUILD", "load('//:test/starlark/extension.bzl', 'my_rule')", "my_rule(name='t')");

    // Required since we have a new WORKSPACE file.
    invalidatePackages(true);

    setBuildLanguageOptions("--experimental_sibling_repository_layout");

    ConfiguredTarget myTarget = getConfiguredTarget("@r//:t");
    String result = (String) getMyInfoFromTarget(myTarget).getValue("result");
    assertThat(result).isEqualTo("../r");
  }

  @Test
  public void testSameMethodNames() throws Exception {
    // The alias feature of load() may hide the fact that two methods in the stack trace have the
    // same name. This is perfectly legal as long as these two methods are actually distinct.
    // Consequently, no "Recursion was detected" error must be thrown.
    scratch.file(
        "test/starlark/extension.bzl",
        "load('//test/starlark:other.bzl', other_impl = 'impl')",
        "def impl(ctx):",
        "  other_impl(ctx)",
        "empty = rule(implementation = impl)");
    scratch.file("test/starlark/other.bzl", "def impl(ctx):", "  print('This rule does nothing')");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'empty')",
        "empty(name = 'test_target')");

    getConfiguredTarget("//test/starlark:test_target");
  }

  private Rule getRuleForTarget(String targetName) throws Exception {
    ConfiguredTargetAndData target = getConfiguredTargetAndData("//test/starlark:" + targetName);
    return target.getTarget().getAssociatedRule();
  }

  @Test
  public void testMacroHasGeneratorAttributes() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "def _impl(ctx):",
        "  print('This rule does nothing')",
        "",
        "empty = rule(implementation = _impl)",
        "no_macro = rule(implementation = _impl)",
        "",
        "def macro(name, visibility=None):",
        "  empty(name = name, visibility=visibility)",
        "def native_macro(name):",
        "  native.cc_library(name = name + '_suffix')");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', macro_rule = 'macro', no_macro_rule = 'no_macro',",
        "  native_macro_rule = 'native_macro')",
        "macro_rule(name = 'macro_target')",
        "no_macro_rule(name = 'no_macro_target')",
        "native_macro_rule(name = 'native_macro_target')",
        "cc_binary(name = 'cc_target', deps = ['cc_dep'])",
        "cc_library(name = 'cc_dep')");

    Rule withMacro = getRuleForTarget("macro_target");
    assertThat(withMacro.getAttr("generator_name")).isEqualTo("macro_target");
    assertThat(withMacro.getAttr("generator_function")).isEqualTo("macro");
    assertThat(withMacro.getAttr("generator_location")).isEqualTo("test/starlark/BUILD:3:11");

    // Attributes are only set when the rule was created by a macro
    Rule noMacro = getRuleForTarget("no_macro_target");
    assertThat(noMacro.getAttr("generator_name")).isEqualTo("");
    assertThat(noMacro.getAttr("generator_function")).isEqualTo("");
    assertThat(noMacro.getAttr("generator_location")).isEqualTo("");

    Rule nativeMacro = getRuleForTarget("native_macro_target_suffix");
    assertThat(nativeMacro.getAttr("generator_name")).isEqualTo("native_macro_target");
    assertThat(nativeMacro.getAttr("generator_function")).isEqualTo("native_macro");
    assertThat(nativeMacro.getAttr("generator_location")).isEqualTo("test/starlark/BUILD:5:18");

    Rule ccTarget = getRuleForTarget("cc_target");
    assertThat(ccTarget.getAttr("generator_name")).isEqualTo("");
    assertThat(ccTarget.getAttr("generator_function")).isEqualTo("");
    assertThat(ccTarget.getAttr("generator_location")).isEqualTo("");
  }

  @Test
  public void sanityCheckUserDefinedTestRule() throws Exception {
    scratch.file(
        "test/starlark/test_rule.bzl",
        "def _impl(ctx):",
        "  output = ctx.outputs.out",
        "  ctx.actions.write(output = output, content = 'hello', is_executable=True)",
        "  return [DefaultInfo(executable = output)]",
        "",
        "fake_test = rule(",
        "  implementation = _impl,",
        "  test=True,",
        "  attrs = {'_xcode_config': attr.label(default = configuration_field(",
        "  fragment = 'apple', name = \"xcode_config_label\"))},",
        "  outputs = {\"out\": \"%{name}.txt\"})");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:test_rule.bzl', 'fake_test')",
        "fake_test(name = 'test_name')");
    getConfiguredTarget("//test/starlark:test_name");
  }

  @Test
  public void testOutputGroupsDeclaredProvider() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "load('//myinfo:myinfo.bzl', 'MyInfo')",
        "def _impl(ctx):",
        "  f = ctx.attr.dep[OutputGroupInfo]._hidden_top_level" + INTERNAL_SUFFIX,
        "  return [MyInfo(result = f),",
        "      OutputGroupInfo(my_group = f)]",
        "my_rule = rule(implementation = _impl,",
        "    attrs = { 'dep' : attr.label() })");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl',  'my_rule')",
        "cc_binary(name = 'lib', data = ['a.txt'])",
        "my_rule(name='my', dep = ':lib')");
    NestedSet<Artifact> hiddenTopLevelArtifacts =
        OutputGroupInfo.get(getConfiguredTarget("//test/starlark:lib"))
            .getOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL);
    ConfiguredTarget myTarget = getConfiguredTarget("//test/starlark:my");
    Depset result = (Depset) getMyInfoFromTarget(myTarget).getValue("result");
    assertThat(result.getSet(Artifact.class).toList())
        .containsExactlyElementsIn(hiddenTopLevelArtifacts.toList());
    assertThat(OutputGroupInfo.get(myTarget).getOutputGroup("my_group").toList())
        .containsExactlyElementsIn(hiddenTopLevelArtifacts.toList());
  }


  @Test
  public void testOutputGroupsAsDictionary() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "load('//myinfo:myinfo.bzl', 'MyInfo')",
        "def _impl(ctx):",
        "  f = ctx.attr.dep.output_groups['_hidden_top_level" + INTERNAL_SUFFIX + "']",
        "  has_key1 = '_hidden_top_level" + INTERNAL_SUFFIX + "' in ctx.attr.dep.output_groups",
        "  has_key2 = 'foobar' in ctx.attr.dep.output_groups",
        "  all_keys = [k for k in ctx.attr.dep.output_groups]",
        "  return [MyInfo(result = f, ",
        "                has_key1 = has_key1,",
        "                has_key2 = has_key2,",
        "                all_keys = all_keys),",
        "      OutputGroupInfo(my_group = f)]",
        "my_rule = rule(implementation = _impl,",
        "    attrs = { 'dep' : attr.label() })");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl',  'my_rule')",
        "cc_binary(name = 'lib', data = ['a.txt'])",
        "my_rule(name='my', dep = ':lib')");
    NestedSet<Artifact> hiddenTopLevelArtifacts =
        OutputGroupInfo.get(getConfiguredTarget("//test/starlark:lib"))
            .getOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL);
    ConfiguredTarget myTarget = getConfiguredTarget("//test/starlark:my");
    StructImpl myInfo = getMyInfoFromTarget(myTarget);
    Depset result = (Depset) myInfo.getValue("result");
    assertThat(result.getSet(Artifact.class).toList())
        .containsExactlyElementsIn(hiddenTopLevelArtifacts.toList());
    assertThat(OutputGroupInfo.get(myTarget).getOutputGroup("my_group").toList())
        .containsExactlyElementsIn(hiddenTopLevelArtifacts.toList());
    assertThat(myInfo.getValue("has_key1")).isEqualTo(Boolean.TRUE);
    assertThat(myInfo.getValue("has_key2")).isEqualTo(Boolean.FALSE);
    assertThat((Sequence) myInfo.getValue("all_keys"))
        .containsExactly(
            OutputGroupInfo.HIDDEN_TOP_LEVEL,
            OutputGroupInfo.COMPILATION_PREREQUISITES,
            OutputGroupInfo.FILES_TO_COMPILE,
            OutputGroupInfo.TEMP_FILES,
            OutputGroupInfo.VALIDATION);
  }

  @Test
  public void testOutputGroupsAsDictionaryPipe() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "load('//myinfo:myinfo.bzl', 'MyInfo')",
        "def _impl(ctx):",
        "  g = depset(ctx.attr.dep.output_groups['_hidden_top_level" + INTERNAL_SUFFIX + "'])",
        "  return [MyInfo(result = g),",
        "      OutputGroupInfo(my_group = g)]",
        "my_rule = rule(implementation = _impl,",
        "    attrs = { 'dep' : attr.label() })");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl',  'my_rule')",
        "cc_binary(name = 'lib', data = ['a.txt'])",
        "my_rule(name='my', dep = ':lib')");
    NestedSet<Artifact> hiddenTopLevelArtifacts =
        OutputGroupInfo.get(getConfiguredTarget("//test/starlark:lib"))
            .getOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL);
    ConfiguredTarget myTarget = getConfiguredTarget("//test/starlark:my");
    Depset result = (Depset) getMyInfoFromTarget(myTarget).getValue("result");
    assertThat(result.getSet(Artifact.class).toList())
        .containsExactlyElementsIn(hiddenTopLevelArtifacts.toList());
    assertThat(OutputGroupInfo.get(myTarget).getOutputGroup("my_group").toList())
        .containsExactlyElementsIn(hiddenTopLevelArtifacts.toList());
  }

  @Test
  public void testOutputGroupsDeclaredProviderWithList() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "load('//myinfo:myinfo.bzl', 'MyInfo')",
        "def _impl(ctx):",
        "  f = ctx.attr.dep[OutputGroupInfo]._hidden_top_level" + INTERNAL_SUFFIX,
        "  g = f.to_list()",
        "  return [MyInfo(result = f),",
        "      OutputGroupInfo(my_group = g, my_empty_group = [])]",
        "my_rule = rule(implementation = _impl,",
        "    attrs = { 'dep' : attr.label() })");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl',  'my_rule')",
        "cc_binary(name = 'lib', data = ['a.txt'])",
        "my_rule(name='my', dep = ':lib')");
    NestedSet<Artifact> hiddenTopLevelArtifacts =
        OutputGroupInfo.get(getConfiguredTarget("//test/starlark:lib"))
            .getOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL);
    ConfiguredTarget myTarget = getConfiguredTarget("//test/starlark:my");
    Depset result = (Depset) getMyInfoFromTarget(myTarget).getValue("result");
    assertThat(result.getSet(Artifact.class).toList())
        .containsExactlyElementsIn(hiddenTopLevelArtifacts.toList());
    assertThat(OutputGroupInfo.get(myTarget).getOutputGroup("my_group").toList())
        .containsExactlyElementsIn(hiddenTopLevelArtifacts.toList());
    assertThat(OutputGroupInfo.get(myTarget).getOutputGroup("my_empty_group").toList()).isEmpty();
  }

  @Test
  public void testStackTraceErrorInFunction() throws Exception {
    runStackTraceTest(
        "  str.index(1)",
        // The error occurs in a built-in, so the backtrace omits the
        // final frame and instead prints "Error in index:".
        "Error in index: in call to index(), parameter 'sub' got value of type 'int', want"
            + " 'string'");
  }

  @Test
  public void testStackTraceMissingMethod() throws Exception {
    // The error occurs in a Starlark operator, so the backtrace includes
    // the final frame, with a source location. We report "Error: ...".
    runStackTraceTest(
        "  (None   ).index(1)", "Error: 'NoneType' value has no field or method 'index'");
  }

  // Precondition: 'expr' must have a 2-space indent and an error at column 12. Ugh.
  // TODO(adonovan): rewrite this and similar tests as assertions over the error data
  // structure, not its formatting.
  protected void runStackTraceTest(String expr, String errorMessage) throws Exception {
    reporter.removeHandler(failFastHandler);
    // The stack doesn't include source lines because we haven't told the relevant
    // call to EvalException.getMessageWithStack how to read from scratch.
    String expectedTrace =
        Joiner.on("\n")
            .join(
                "ERROR /workspace/test/starlark/BUILD:3:12: in custom_rule rule"
                    + " //test/starlark:cr: ",
                "Traceback (most recent call last):",
                "\tFile \"/workspace/test/starlark/extension.bzl\", line 6, column 6, in"
                    + " custom_rule_impl",
                // "\t\tfoo()",
                "\tFile \"/workspace/test/starlark/extension.bzl\", line 9, column 6, in foo",
                // "\t\tbar(2, 4)",
                "\tFile \"/workspace/test/starlark/extension.bzl\", line 11, column 8, in bar",
                // "\t\tfirst(x, y, z)",
                "\tFile \"/workspace/test/starlark/functions.bzl\", line 2, column 9, in first",
                // "\t\tsecond(a, b)",
                "\tFile \"/workspace/test/starlark/functions.bzl\", line 5, column 8, in second",
                // "\t\tthird(\"legal\")",
                "\tFile \"/workspace/test/starlark/functions.bzl\", line 7, column 12, in third",
                // ...
                errorMessage);
    scratch.file(
        "test/starlark/extension.bzl",
        "load('//test/starlark:functions.bzl', 'first')",
        "load('//myinfo:myinfo.bzl', 'MyInfo')",
        "def custom_rule_impl(ctx):",
        "  attr1 = ctx.files.attr1",
        "  ftb = depset(attr1)",
        "  foo()",
        "  return [MyInfo(provider_key = ftb)]",
        "def foo():",
        "  bar(2,4)",
        "def bar(x,y,z=1):",
        "  first(x,y, z)",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {'attr1': attr.label_list(mandatory=True, allow_files=True)})");
    scratch.file(
        "test/starlark/functions.bzl",
        "def first(a, b, c):",
        "  second(a, b)",
        "  third(b)",
        "def second(a, b):",
        "  third('legal')",
        "def third(str):",
        expr); // 2-space indent, error at column 12
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'cr', attr1 = [':a.txt'])");

    getConfiguredTarget("//test/starlark:cr");
    assertContainsEvent(expectedTrace);
  }

  @Test
  public void testFilesToBuild() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  attr1 = ctx.files.attr1",
        "  ftb = depset(attr1)",
        "  return [DefaultInfo(runfiles = ctx.runfiles(), files = ftb)]",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {'attr1': attr.label_list(mandatory=True, allow_files=True)})");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'cr', attr1 = [':a.txt'])");

    ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");

    assertThat(target.getLabel().toString()).isEqualTo("//test/starlark:cr");
    assertThat(
        ActionsTestUtil.baseArtifactNames(
            target.getProvider(FileProvider.class).getFilesToBuild()))
        .containsExactly("a.txt");
  }

  @Test
  public void testRunfiles() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  attr1 = ctx.files.attr1",
        "  rf = ctx.runfiles(files = attr1)",
        "  return [DefaultInfo(runfiles = rf)]",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {'attr1': attr.label_list(mandatory=True, allow_files=True)})");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'cr', attr1 = [':a.txt'])");

    ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");

    assertThat(target.getLabel().toString()).isEqualTo("//test/starlark:cr");
    assertThat(
        ActionsTestUtil.baseArtifactNames(
            target.getProvider(RunfilesProvider.class).getDefaultRunfiles().getAllArtifacts()))
        .containsExactly("a.txt");
    assertThat(
        ActionsTestUtil.baseArtifactNames(
            target.getProvider(RunfilesProvider.class).getDataRunfiles().getAllArtifacts()))
        .containsExactly("a.txt");
  }

  @Test
  public void testAccessRunfiles() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  runfiles = ctx.attr.x.default_runfiles.files",
        "  return [DefaultInfo(files = runfiles)]",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {'x': attr.label(allow_files=True)})");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "cc_library(name = 'lib', data = ['a.txt'])",
        "custom_rule(name = 'cr1', x = ':lib')",
        "custom_rule(name = 'cr2', x = 'b.txt')");

    scratch.file("test/starlark/a.txt");
    scratch.file("test/starlark/b.txt");

    ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr1");
    List<String> baseArtifactNames =
        ActionsTestUtil.baseArtifactNames(target.getProvider(FileProvider.class).getFilesToBuild());
    assertThat(baseArtifactNames).containsExactly("a.txt");

    target = getConfiguredTarget("//test/starlark:cr2");
    baseArtifactNames =
        ActionsTestUtil.baseArtifactNames(target.getProvider(FileProvider.class).getFilesToBuild());
    assertThat(baseArtifactNames).isEmpty();
  }

  @Test
  public void testStatefulRunfiles() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  attr1 = ctx.files.attr1",
        "  rf1 = ctx.runfiles(files = attr1)",
        "  rf2 = ctx.runfiles()",
        "  return [DefaultInfo(data_runfiles = rf1, default_runfiles = rf2)]",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {'attr1': attr.label_list(mandatory = True, allow_files=True)})");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'cr', attr1 = [':a.txt'])");

    ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");

    assertThat(target.getLabel().toString()).isEqualTo("//test/starlark:cr");
    assertThat(target.getProvider(RunfilesProvider.class).getDefaultRunfiles().isEmpty()).isTrue();
    assertThat(
        ActionsTestUtil.baseArtifactNames(
            target.getProvider(RunfilesProvider.class).getDataRunfiles().getAllArtifacts()))
        .containsExactly("a.txt");
  }

  @Test
  public void testExecutableGetsInRunfilesAndFilesToBuild() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  ctx.actions.write(output = ctx.outputs.executable, content = 'echo hello')",
        "  rf = ctx.runfiles(ctx.files.data)",
        "  return [DefaultInfo(runfiles = rf)]",
        "",
        "custom_rule = rule(implementation = custom_rule_impl, executable = True,",
        "  attrs = {'data': attr.label_list(allow_files=True)})");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'cr', data = [':a.txt'])");

    ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");

    assertThat(target.getLabel().toString()).isEqualTo("//test/starlark:cr");
    assertThat(
        ActionsTestUtil.baseArtifactNames(
            target.getProvider(RunfilesProvider.class).getDefaultRunfiles().getAllArtifacts()))
        .containsExactly("a.txt", "cr")
        .inOrder();
    assertThat(
        ActionsTestUtil.baseArtifactNames(
            target.getProvider(FileProvider.class).getFilesToBuild()))
        .containsExactly("cr");
  }

  @Test
  public void testCannotSpecifyRunfilesWithDataOrDefaultRunfiles_struct() throws Exception {
    setBuildLanguageOptions("--incompatible_disallow_struct_provider_syntax=false");
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  rf = ctx.runfiles()",
        "  return struct(runfiles = rf, default_runfiles = rf)",
        "",
        "custom_rule = rule(implementation = custom_rule_impl)");

    checkError(
        "test/starlark",
        "cr",
        "Cannot specify the provider 'runfiles' together with "
            + "'data_runfiles' or 'default_runfiles'",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'cr')");
  }

  @Test
  public void testCannotSpecifyRunfilesWithDataOrDefaultRunfiles_defaultInfo() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  rf = ctx.runfiles()",
        "  return [DefaultInfo(runfiles = rf, default_runfiles = rf)]",
        "",
        "custom_rule = rule(implementation = custom_rule_impl)");

    checkError(
        "test/starlark",
        "cr",
        "Cannot specify the provider 'runfiles' together with "
            + "'data_runfiles' or 'default_runfiles'",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'cr')");
  }

  @Test
  public void testDefaultInfoWithRunfilesConstructor() throws Exception {
    scratch.file(
        "pkg/BUILD",
        "sh_binary(name = 'tryme',",
        "          srcs = [':tryme.sh'],",
        "          visibility = ['//visibility:public'],",
        ")");

    scratch.file(
        "src/rulez.bzl",
        "def  _impl(ctx):",
        "   info = DefaultInfo(runfiles = ctx.runfiles(files=[ctx.executable.dep]))",
        "   if info.default_runfiles.files.to_list()[0] != ctx.executable.dep:",
        "       fail('expected runfile to be in info.default_runfiles')",
        "   return [info]",
        "r = rule(_impl,",
        "         attrs = {",
        "            'dep' : attr.label(executable = True, mandatory = True, cfg = 'host'),",
        "         }",
        ")");

    scratch.file(
        "src/BUILD", "load(':rulez.bzl', 'r')", "r(name = 'r_tools', dep = '//pkg:tryme')");

    assertThat(getConfiguredTarget("//src:r_tools")).isNotNull();
  }

  @Test
  public void testInstrumentedFilesProviderWithCodeCoverageDisabled() throws Exception {
    setBuildLanguageOptions("--incompatible_disallow_struct_provider_syntax=false");
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  return struct(instrumented_files=struct(",
        "      extensions = ['txt'],",
        "      source_attributes = ['attr1'],",
        "      dependency_attributes = ['attr2']))",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {",
        "      'attr1': attr.label_list(mandatory = True, allow_files=True),",
        "      'attr2': attr.label_list(mandatory = True)})");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "java_library(name='jl', srcs = [':A.java'])",
        "custom_rule(name = 'cr', attr1 = [':a.txt', ':a.random'], attr2 = [':jl'])");

    useConfiguration("--nocollect_code_coverage");

    ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");

    assertThat(target.getLabel().toString()).isEqualTo("//test/starlark:cr");
    InstrumentedFilesInfo provider = target.get(InstrumentedFilesInfo.STARLARK_CONSTRUCTOR);
    assertWithMessage("InstrumentedFilesInfo should be set.").that(provider).isNotNull();
    assertThat(ActionsTestUtil.baseArtifactNames(provider.getInstrumentedFiles())).isEmpty();
  }

  @Test
  public void testInstrumentedFilesProviderWithCodeCoverageEnabled() throws Exception {
    setBuildLanguageOptions("--incompatible_disallow_struct_provider_syntax=false");
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  return struct(instrumented_files=struct(",
        "      extensions = ['txt'],",
        "      source_attributes = ['attr1'],",
        "      dependency_attributes = ['attr2']))",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {",
        "      'attr1': attr.label_list(mandatory = True, allow_files=True),",
        "      'attr2': attr.label_list(mandatory = True)})");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "java_library(name='jl', srcs = [':A.java'])",
        "custom_rule(name = 'cr', attr1 = [':a.txt', ':a.random'], attr2 = [':jl'])");

    useConfiguration("--collect_code_coverage");

    ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");

    assertThat(target.getLabel().toString()).isEqualTo("//test/starlark:cr");
    InstrumentedFilesInfo provider = target.get(InstrumentedFilesInfo.STARLARK_CONSTRUCTOR);
    assertWithMessage("InstrumentedFilesInfo should be set.").that(provider).isNotNull();
    assertThat(ActionsTestUtil.baseArtifactNames(provider.getInstrumentedFiles()))
        .containsExactly("a.txt", "A.java");
  }

  @Test
  public void testInstrumentedFilesInfo_coverageDisabled() throws Exception {
    setBuildLanguageOptions("--incompatible_disallow_struct_provider_syntax=false");
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  return struct(instrumented_files=struct(",
        "      extensions = ['txt'],",
        "      source_attributes = ['attr1'],",
        "      dependency_attributes = ['attr2']))",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {",
        "      'attr1': attr.label_list(mandatory = True, allow_files=True),",
        "      'attr2': attr.label_list(mandatory = True)})");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "java_library(name='jl', srcs = [':A.java'])",
        "custom_rule(name = 'cr', attr1 = [':a.txt', ':a.random'], attr2 = [':jl'])");

    useConfiguration("--nocollect_code_coverage");

    ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");

    InstrumentedFilesInfo provider = target.get(InstrumentedFilesInfo.STARLARK_CONSTRUCTOR);
    assertWithMessage("InstrumentedFilesInfo should be set.").that(provider).isNotNull();
    assertThat(ActionsTestUtil.baseArtifactNames(provider.getInstrumentedFiles())).isEmpty();
  }

  @Test
  public void testInstrumentedFilesInfo_coverageEnabled() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "load('//myinfo:myinfo.bzl', 'MyInfo')",
        "",
        "def custom_rule_impl(ctx):",
        "  return [coverage_common.instrumented_files_info(ctx,",
        "      extensions = ['txt'],",
        "      source_attributes = ['attr1'],",
        "      dependency_attributes = ['attr2'])]",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {",
        "      'attr1': attr.label_list(mandatory = True, allow_files=True),",
        "      'attr2': attr.label_list(mandatory = True)})",
        "",
        "def test_rule_impl(ctx):",
        "  return [MyInfo(",
        // The point of this is to assert that these fields can be read in analysistest.
        // Normally, this information wouldn't be forwarded via a different provider.
        "    instrumented_files = ctx.attr.target[InstrumentedFilesInfo].instrumented_files,",
        "    metadata_files = ctx.attr.target[InstrumentedFilesInfo].metadata_files)]",
        "",
        "test_rule = rule(implementation = test_rule_impl,",
        "  attrs = {'target': attr.label(mandatory = True)})");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule', 'test_rule')",
        "",
        "cc_library(name='cl', srcs = [':A.cc'])",
        "custom_rule(name = 'cr', attr1 = [':a.txt', ':a.random'], attr2 = [':cl'])",
        "test_rule(name = 'test', target = ':cr')");

    useConfiguration("--collect_code_coverage");

    ConfiguredTarget target = getConfiguredTarget("//test/starlark:test");
    StructImpl myInfo = getMyInfoFromTarget(target);
    assertThat(
            ActionsTestUtil.baseArtifactNames(
                ((Depset) myInfo.getValue("instrumented_files")).getSet(Artifact.class)))
        .containsExactly("a.txt", "A.cc");
    assertThat(
            ActionsTestUtil.baseArtifactNames(
                ((Depset) myInfo.getValue("metadata_files")).getSet(Artifact.class)))
        .containsExactly("A.gcno");
    ConfiguredTarget customRule = getConfiguredTarget("//test/starlark:cr");
    assertThat(
            ActionsTestUtil.baseArtifactNames(
                customRule
                    .get(InstrumentedFilesInfo.STARLARK_CONSTRUCTOR)
                    .getBaselineCoverageInstrumentedFiles()))
        .containsExactly("a.txt", "A.cc");
  }

  @Test
  public void testTransitiveInfoProviders() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "load('//myinfo:myinfo.bzl', 'MyInfo')",
        "def custom_rule_impl(ctx):",
        "  attr1 = ctx.files.attr1",
        "  ftb = depset(attr1)",
        "  return [MyInfo(provider_key = ftb)]",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {'attr1': attr.label_list(mandatory=True, allow_files=True)})");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'cr', attr1 = [':a.txt'])");

    RuleConfiguredTarget target = (RuleConfiguredTarget) getConfiguredTarget("//test/starlark:cr");

    assertThat(
            ActionsTestUtil.baseArtifactNames(
                ((Depset) getMyInfoFromTarget(target).getValue("provider_key"))
                    .getSet(Artifact.class)))
        .containsExactly("a.txt");
  }

  @Test
  public void testInstrumentedFilesForwardedFromDepsByDefaultExperimentFlag() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "def wrapper_impl(ctx):",
        // This wrapper doesn't configure InstrumentedFilesInfo.
        "    return []",
        "",
        "wrapper = rule(implementation = wrapper_impl,",
        "    attrs = {",
        "        'srcs': attr.label_list(allow_files = True),",
        "        'wrapped': attr.label(mandatory = True),",
        "        'wrapped_list': attr.label_list(),",
        // Host deps aren't forwarded by default, since they don't provide code/binaries executed
        // at runtime.
        "        'tool': attr.label(cfg = 'host', executable = True, mandatory = True),",
        "    })");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'wrapper')",
        "",
        "cc_binary(name = 'tool', srcs = [':tool.cc'])",
        "cc_binary(name = 'wrapped', srcs = [':wrapped.cc'])",
        "cc_binary(name = 'wrapped_list', srcs = [':wrapped_list.cc'])",
        "wrapper(",
        "    name = 'wrapper',",
        "    srcs = ['ignored.cc'],",
        "    wrapped = ':wrapped',",
        "    wrapped_list = [':wrapped_list'],",
        "    tool = ':tool',",
        ")",
        "cc_binary(name = 'outer', data = [':wrapper'])");

    // Current behavior is that nothing gets forwarded if IntstrumentedFilesInfo is not configured.
    // That means that source files are not collected for the coverage manifest unless the entire
    // dependency chain between the test and the source file explicitly configures coverage.
    // New behavior is protected by --experimental_forward_instrumented_files_info_by_default.
    useConfiguration("--collect_code_coverage");
    ConfiguredTarget target = getConfiguredTarget("//test/starlark:outer");
    InstrumentedFilesInfo provider = target.get(InstrumentedFilesInfo.STARLARK_CONSTRUCTOR);
    assertWithMessage("InstrumentedFilesInfo should be set.").that(provider).isNotNull();
    assertThat(ActionsTestUtil.baseArtifactNames(provider.getInstrumentedFiles())).isEmpty();

    // Instead, the default behavior could be to forward InstrumentedFilesInfo from all
    // dependencies. Coverage still needs to be configured for rules that handle source files for
    // languages which support coverage instrumentation, but not every wrapper rule in the
    // dependency chain needs to configure that for instrumentation to be correct.
    useConfiguration(
        "--collect_code_coverage", "--experimental_forward_instrumented_files_info_by_default");
    target = getConfiguredTarget("//test/starlark:outer");
    provider = target.get(InstrumentedFilesInfo.STARLARK_CONSTRUCTOR);
    assertWithMessage("InstrumentedFilesInfo should be set.").that(provider).isNotNull();
    assertThat(ActionsTestUtil.baseArtifactNames(provider.getInstrumentedFiles()))
        .containsExactly("wrapped.cc", "wrapped_list.cc");
  }

  @Test
  public void testMandatoryProviderMissing() throws Exception {
    scratch.file("test/starlark/BUILD");
    scratch.file(
        "test/starlark/extension.bzl",
        "def rule_impl(ctx):",
        "  return []",
        "",
        "dependent_rule = rule(implementation = rule_impl)",
        "",
        "main_rule = rule(implementation = rule_impl,",
        "    attrs = {'dependencies': attr.label_list(providers = ['some_provider'],",
        "        allow_files=True)})");

    checkError(
        "test",
        "b",
        "in dependencies attribute of main_rule rule //test:b: "
            + "'//test:a' does not have mandatory providers: 'some_provider'",
        "load('//test/starlark:extension.bzl', 'dependent_rule')",
        "load('//test/starlark:extension.bzl', 'main_rule')",
        "",
        "dependent_rule(name = 'a')",
        "main_rule(name = 'b', dependencies = [':a'])");
  }

  @Test
  public void testSpecialMandatoryProviderMissing() throws Exception {
    // Test that rules satisfy `providers = [...]` condition if a special provider that always
    // exists for all rules is requested. Also check external rules.

    FileSystemUtils.appendIsoLatin1(scratch.resolve("WORKSPACE"),
        "bind(name = 'bar', actual = '//test/ext:bar')");
    scratch.file(
        "test/ext/BUILD",
        "load('//test/starlark:extension.bzl', 'foobar')",
        "",
        "foobar(name = 'bar', visibility = ['//visibility:public'],)");
    scratch.file(
        "test/starlark/extension.bzl",
        "def rule_impl(ctx):",
        "  pass",
        "",
        "foobar = rule(implementation = rule_impl)",
        "main_rule = rule(implementation = rule_impl, attrs = {",
        "    'deps': attr.label_list(providers = [",
        "        'files', 'data_runfiles', 'default_runfiles',",
        "        'files_to_run', 'output_groups',",
        "    ])",
        "})");
    scratch.file(
        "test/starlark/BUILD",
        "load(':extension.bzl', 'foobar', 'main_rule')",
        "",
        "foobar(name = 'foo')",
        "main_rule(name = 'main', deps = [':foo', '//external:bar'])");

    invalidatePackages();
    getConfiguredTarget("//test/starlark:main");
  }

  @Test
  public void testActions() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  attr1 = ctx.files.attr1",
        "  output = ctx.outputs.o",
        "  ctx.actions.run_shell(",
        "    inputs = attr1,",
        "    outputs = [output],",
        "    command = 'echo')",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {'attr1': attr.label_list(mandatory=True, allow_files=True)},",
        "  outputs = {'o': 'o.txt'})");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'cr', attr1 = [':a.txt'])");

    getConfiguredTarget("//test/starlark:cr");

    FileConfiguredTarget target = getFileConfiguredTarget("//test/starlark:o.txt");
    assertThat(
        ActionsTestUtil.baseArtifactNames(
            getGeneratingAction(target.getArtifact()).getInputs()))
        .containsExactly("a.txt");
  }

  @Test
  public void testRuleClassImplicitOutputFunction() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  files = [ctx.outputs.o]",
        "  ctx.actions.run_shell(",
        "    outputs = files,",
        "    command = 'echo')",
        "  ftb = depset(files)",
        "  return [DefaultInfo(runfiles = ctx.runfiles(), files = ftb)]",
        "",
        "def output_func(name, public_attr, _private_attr):",
        "  if _private_attr != None: return {}",
        "  return {'o': name + '-' + public_attr + '.txt'}",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {'public_attr': attr.string(),",
        "           '_private_attr': attr.label()},",
        "  outputs = output_func)");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'cr', public_attr = 'bar')");

    ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");

    assertThat(
            ActionsTestUtil.baseArtifactNames(
                target.getProvider(FileProvider.class).getFilesToBuild()))
        .containsExactly("cr-bar.txt");
  }

  @Test
  public void testRuleClassImplicitOutputFunctionDependingOnComputedAttribute() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  files = [ctx.outputs.o]",
        "  ctx.actions.run_shell(",
        "    outputs = files,",
        "    command = 'echo')",
        "  ftb = depset(files)",
        "  return [DefaultInfo(runfiles = ctx.runfiles(), files = ftb)]",
        "",
        "def attr_func(public_attr):",
        "  return public_attr",
        "",
        "def output_func(_private_attr):",
        "  return {'o': _private_attr.name + '.txt'}",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {'public_attr': attr.label(),",
        "           '_private_attr': attr.label(default = attr_func)},",
        "  outputs = output_func)",
        "",
        "def empty_rule_impl(ctx):",
        "  pass",
        "",
        "empty_rule = rule(implementation = empty_rule_impl)");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule', 'empty_rule')",
        "",
        "empty_rule(name = 'foo')",
        "custom_rule(name = 'cr', public_attr = '//test/starlark:foo')");

    ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");

    assertThat(
        ActionsTestUtil.baseArtifactNames(
            target.getProvider(FileProvider.class).getFilesToBuild()))
        .containsExactly("foo.txt");
  }

  @Test
  public void testRuleClassImplicitOutputs() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  files = [ctx.outputs.lbl, ctx.outputs.list, ctx.outputs.str]",
        "  print('==!=!=!=')",
        "  print(files)",
        "  ctx.actions.run_shell(",
        "    outputs = files,",
        "    command = 'echo')",
        "  return [DefaultInfo(files = depset(files))]",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {",
        "    'attr1': attr.label(allow_files=True),",
        "    'attr2': attr.label_list(allow_files=True),",
        "    'attr3': attr.string(),",
        "  },",
        "  outputs = {",
        "    'lbl': '%{attr1}.a',",
        "    'list': '%{attr2}.b',",
        "    'str': '%{attr3}.c',",
        "})");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(",
        "  name='cr',",
        "  attr1='f1.txt',",
        "  attr2=['f2.txt'],",
        "  attr3='f3.txt',",
        ")");

    scratch.file("test/starlark/f1.txt");
    scratch.file("test/starlark/f2.txt");
    scratch.file("test/starlark/f3.txt");

    ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");
    assertThat(
        ActionsTestUtil.baseArtifactNames(
            target.getProvider(FileProvider.class).getFilesToBuild()))
        .containsExactly("f1.a", "f2.b", "f3.txt.c");
  }

  @Test
  public void testRuleClassImplicitOutputFunctionAndDefaultValue() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  ctx.actions.run_shell(",
        "    outputs = [ctx.outputs.o],",
        "    command = 'echo')",
        "  return [DefaultInfo(runfiles = ctx.runfiles())]",
        "",
        "def output_func(attr1):",
        "  return {'o': attr1 + '.txt'}",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {'attr1': attr.string(default='bar')},",
        "  outputs = output_func)");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'cr', attr1 = None)");

    ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");

    assertThat(
        ActionsTestUtil.baseArtifactNames(
            target.getProvider(FileProvider.class).getFilesToBuild()))
        .containsExactly("bar.txt");
  }

  @Test
  public void testPrintProviderCollection() throws Exception {
    scratch.file(
        "test/starlark/rules.bzl",
        "",
        "FooInfo = provider()",
        "BarInfo = provider()",
        "",
        "def _top_level_rule_impl(ctx):",
        "  print('My Dep Providers:', ctx.attr.my_dep)",
        "",
        "def _dep_rule_impl(name):",
        "  providers = [",
        "      FooInfo(),",
        "      BarInfo(),",
        "  ]",
        "  return providers",
        "",
        "top_level_rule = rule(",
        "    implementation=_top_level_rule_impl,",
        "    attrs={'my_dep':attr.label()}",
        ")",
        "",
        "dep_rule = rule(",
        "    implementation=_dep_rule_impl,",
        ")");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:rules.bzl', 'top_level_rule', 'dep_rule')",
        "",
        "top_level_rule(name = 'tl', my_dep=':d')",
        "",
        "dep_rule(name = 'd')");

    getConfiguredTarget("//test/starlark:tl");
    assertContainsEvent(
        "My Dep Providers: <target //test/starlark:d, keys:[FooInfo, BarInfo, OutputGroupInfo]>");
  }

  @Test
  public void testRuleClassImplicitOutputFunctionPrints() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  print('implementation', ctx.label)",
        "  files = [ctx.outputs.o]",
        "  ctx.actions.run_shell(",
        "    outputs = files,",
        "    command = 'echo')",
        "",
        "def output_func(name):",
        "  print('output function', name)",
        "  return {'o': name + '.txt'}",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  outputs = output_func)");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'cr')");

    getConfiguredTarget("//test/starlark:cr");
    assertContainsEvent("output function cr");
    assertContainsEvent("implementation //test/starlark:cr");
  }

  @Test
  public void testNoOutputAttrDefault() throws Exception {
    scratch.file(
        "test/extension.bzl",
        "load('//myinfo:myinfo.bzl', 'MyInfo')",
        "def custom_rule_impl(ctx):",
        "  out_file = ctx.actions.declare_file(ctx.attr._o1.name)",
        "  ctx.actions.write(output=out_file, content='hi')",
        "  return [MyInfo(o1=ctx.attr._o1)]",
        "",
        "def output_fn():",
        "  return Label('//test/starlark:foo.txt')",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {'_o1': attr.output(default = output_fn)})");

    scratch.file(
        "test/BUILD",
        "load('//test:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'r')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:r");
    assertContainsEvent("got unexpected keyword argument 'default'");
  }

  @Test
  public void testNoOutputListAttrDefault() throws Exception {
    scratch.file(
        "test/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  return []",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {'outs': attr.output_list(default = [])})");

    scratch.file(
        "test/BUILD",
        "load('//test:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'r')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:r");
    assertContainsEvent("got unexpected keyword argument 'default'");
  }

  @Test
  public void testRuleClassNonMandatoryEmptyOutputs() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "load('//myinfo:myinfo.bzl', 'MyInfo')",
        "def custom_rule_impl(ctx):",
        "  return [MyInfo(",
        "      o1=ctx.outputs.o1,",
        "      o2=ctx.outputs.o2)]",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {'o1': attr.output(), 'o2': attr.output_list()})");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'cr')");

    ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");
    StructImpl myInfo = getMyInfoFromTarget(target);
    assertThat(myInfo.getValue("o1")).isEqualTo(Starlark.NONE);
    assertThat(myInfo.getValue("o2")).isEqualTo(StarlarkList.empty());
  }

  @Test
  public void testRuleClassImplicitAndExplicitOutputNamesCollide() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  return []",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {'o': attr.output_list()},",
        "  outputs = {'o': '%{name}.txt'})");

    checkError(
        "test/starlark",
        "cr",
        "Multiple outputs with the same key: o",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'cr', o = [':bar.txt'])");
  }

  @Test
  public void testRuleClassDefaultFilesToBuild() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  files = [ctx.outputs.o]",
        "  ctx.actions.run_shell(",
        "    outputs = files,",
        "    command = 'echo')",
        "  ftb = depset(files)",
        "  for i in ctx.outputs.out:",
        "    ctx.actions.write(output=i, content='hi there')",
        "",
        "def output_func(attr1):",
        "  return {'o': attr1 + '.txt'}",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {",
        "    'attr1': attr.string(),",
        "    'out': attr.output_list()",
        "  },",
        "  outputs = output_func)");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'cr', attr1 = 'bar', out=['other'])");

    ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");

    assertThat(
        ActionsTestUtil.baseArtifactNames(
            target.getProvider(FileProvider.class).getFilesToBuild()))
        .containsExactly("bar.txt", "other")
        .inOrder();
  }

  @Test
  public void rulesReturningDeclaredProviders() throws Exception {
    scratch.file(
        "test/extension.bzl",
        "my_provider = provider()",
        "def _impl(ctx):",
        "   return [my_provider(x = 1)]",
        "my_rule = rule(_impl)"
    );
    scratch.file(
        "test/BUILD",
        "load(':extension.bzl', 'my_rule')",
        "my_rule(name = 'r')"
    );

    ConfiguredTarget configuredTarget = getConfiguredTarget("//test:r");
    Provider.Key key =
        new StarlarkProvider.Key(
            Label.create(configuredTarget.getLabel().getPackageIdentifier(), "extension.bzl"),
            "my_provider");
    StructImpl declaredProvider = (StructImpl) configuredTarget.get(key);
    assertThat(declaredProvider).isNotNull();
    assertThat(declaredProvider.getProvider().getKey()).isEqualTo(key);
    assertThat(declaredProvider.getValue("x")).isEqualTo(StarlarkInt.of(1));
  }

  @Test
  public void rulesReturningDeclaredProvidersCompatMode() throws Exception {
    scratch.file(
        "test/extension.bzl",
        "my_provider = provider()",
        "def _impl(ctx):",
        "   return [my_provider(x = 1)]",
        "my_rule = rule(_impl)");
    scratch.file(
        "test/BUILD",
        "load(':extension.bzl', 'my_rule')",
        "my_rule(name = 'r')"
    );

    ConfiguredTarget configuredTarget  = getConfiguredTarget("//test:r");
    Provider.Key key =
        new StarlarkProvider.Key(
            Label.create(configuredTarget.getLabel().getPackageIdentifier(), "extension.bzl"),
            "my_provider");
    StructImpl declaredProvider = (StructImpl) configuredTarget.get(key);
    assertThat(declaredProvider).isNotNull();
    assertThat(declaredProvider.getProvider().getKey()).isEqualTo(key);
    assertThat(declaredProvider.getValue("x")).isEqualTo(StarlarkInt.of(1));
  }

  @Test
  public void testRuleReturningUnwrappedDeclaredProvider() throws Exception {
    scratch.file(
        "test/extension.bzl",
        "my_provider = provider()",
        "def _impl(ctx):",
        "   return my_provider(x = 1)",
        "my_rule = rule(_impl)"
    );
    scratch.file(
        "test/BUILD",
        "load(':extension.bzl', 'my_rule')",
        "my_rule(name = 'r')"
    );

    ConfiguredTarget configuredTarget  = getConfiguredTarget("//test:r");
    Provider.Key key =
        new StarlarkProvider.Key(
            Label.create(configuredTarget.getLabel().getPackageIdentifier(), "extension.bzl"),
            "my_provider");
    StructImpl declaredProvider = (StructImpl) configuredTarget.get(key);
    assertThat(declaredProvider).isNotNull();
    assertThat(declaredProvider.getProvider().getKey()).isEqualTo(key);
    assertThat(declaredProvider.getValue("x")).isEqualTo(StarlarkInt.of(1));
  }

  @Test
  public void testConflictingProviderKeys_fromStruct_disallowed() throws Exception {
    scratch.file(
        "test/extension.bzl",
        "my_provider = provider()",
        "other_provider = provider()",
        "def _impl(ctx):",
        "   return [my_provider(x = 1), other_provider(), my_provider(x = 2)]",
        "my_rule = rule(_impl)");

    checkError(
        "test",
        "r",
        "Multiple conflicting returned providers with key my_provider",
        "load(':extension.bzl', 'my_rule')",
        "my_rule(name = 'r')");
  }

  @Test
  public void testConflictingProviderKeys_fromIterable_disallowed() throws Exception {
    scratch.file(
        "test/extension.bzl",
        "my_provider = provider()",
        "other_provider = provider()",
        "def _impl(ctx):",
        "   return [my_provider(x = 1), other_provider(), my_provider(x = 2)]",
        "my_rule = rule(_impl)"
    );

    checkError(
        "test",
        "r",
        "Multiple conflicting returned providers with key my_provider",
        "load(':extension.bzl', 'my_rule')",
        "my_rule(name = 'r')");
  }

  @Test
  public void testRecursionDetection() throws Exception {
    reporter.removeHandler(failFastHandler);
    scratch.file(
        "test/starlark/extension.bzl",
        "def _impl(ctx):",
        "  _impl(ctx)",
        "empty = rule(implementation = _impl)");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'empty')",
        "empty(name = 'test_target')");

    getConfiguredTarget("//test/starlark:test_target");
    assertContainsEvent("function '_impl' called recursively");
  }

  @Test
  public void testBadCallbackFunction() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl", "def impl(): return 0", "", "custom_rule = rule(impl)");

    checkError(
        "test/starlark",
        "cr",
        "impl() does not accept positional arguments",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'cr')");
  }

  @Test
  public void testRuleClassImplicitOutputFunctionBadAttr() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  return None",
        "",
        "def output_func(bad_attr):",
        "  return {'a': bad_attr}",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {'attr1': attr.string()},",
        "  outputs = output_func)");

    checkError(
        "test/starlark",
        "cr",
        "Attribute 'bad_attr' either doesn't exist or uses a select()",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'cr', attr1 = 'bar')");
  }

  @Test
  public void testHelperFunctionInRuleImplementation() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "def helper_func(attr1):",
        "  return depset(attr1)",
        "",
        "def custom_rule_impl(ctx):",
        "  attr1 = ctx.files.attr1",
        "  ftb = helper_func(attr1)",
        "  return [DefaultInfo(runfiles = ctx.runfiles(), files = ftb)]",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "  attrs = {'attr1': attr.label_list(mandatory=True, allow_files=True)})");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'cr', attr1 = [':a.txt'])");

    ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");

    assertThat(target.getLabel().toString()).isEqualTo("//test/starlark:cr");
    assertThat(
        ActionsTestUtil.baseArtifactNames(
            target.getProvider(FileProvider.class).getFilesToBuild()))
        .containsExactly("a.txt");
  }

  @Test
  public void testMultipleLoadsOfSameRule() throws Exception {
    scratch.file("test/starlark/BUILD");
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  return None",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "     attrs = {'dep': attr.label_list(allow_files=True)})");

    scratch.file(
        "test/starlark1/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "custom_rule(name = 'cr1')");

    scratch.file(
        "test/starlark2/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "custom_rule(name = 'cr2', dep = ['//test/starlark1:cr1'])");

    getConfiguredTarget("//test/starlark2:cr2");
  }

  @Test
  public void testFunctionGeneratingRules() throws Exception {
    scratch.file(
        "test/starlark/extension.bzl",
        "def impl(ctx): return None",
        "def gen(): return rule(impl)",
        "r = gen()",
        "s = gen()");

    scratch.file(
        "test/starlark/BUILD", "load(':extension.bzl', 'r', 's')",
        "r(name = 'r')", "s(name = 's')");

    getConfiguredTarget("//test/starlark:r");
    getConfiguredTarget("//test/starlark:s");
  }

  @Test
  public void testLoadInStarlark() throws Exception {
    scratch.file("test/starlark/implementation.bzl", "def custom_rule_impl(ctx):", "  return None");

    scratch.file(
        "test/starlark/extension.bzl",
        "load('//test/starlark:implementation.bzl', 'custom_rule_impl')",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "     attrs = {'dep': attr.label_list(allow_files=True)})");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "custom_rule(name = 'cr')");

    getConfiguredTarget("//test/starlark:cr");
  }

  @Test
  public void testRuleAliasing() throws Exception {
    scratch.file(
        "test/starlark/implementation.bzl",
        "def impl(ctx): return []",
        "custom_rule = rule(implementation = impl)");

    scratch.file(
        "test/starlark/ext.bzl",
        "load('//test/starlark:implementation.bzl', 'custom_rule')",
        "def impl(ctx): return []",
        "custom_rule1 = rule(implementation = impl)",
        "custom_rule2 = custom_rule1",
        "custom_rule3 = custom_rule");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:ext.bzl', 'custom_rule1', 'custom_rule2', 'custom_rule3')",
        "custom_rule4 = custom_rule3",
        "custom_rule1(name = 'cr1')",
        "custom_rule2(name = 'cr2')",
        "custom_rule3(name = 'cr3')",
        "custom_rule4(name = 'cr4')");

    getConfiguredTarget("//test/starlark:cr1");
    getConfiguredTarget("//test/starlark:cr2");
    getConfiguredTarget("//test/starlark:cr3");
    getConfiguredTarget("//test/starlark:cr4");
  }

  @Test
  public void testRecursiveLoad() throws Exception {
    scratch.file("test/starlark/ext2.bzl", "load('//test/starlark:ext1.bzl', 'symbol2')");

    scratch.file("test/starlark/ext1.bzl", "load('//test/starlark:ext2.bzl', 'symbol1')");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:ext1.bzl', 'custom_rule')",
        "genrule(name = 'rule')");

    reporter.removeHandler(failFastHandler);
    assertThrows(BuildFileContainsErrorsException.class, () -> getTarget("//test/starlark:rule"));
    assertContainsEvent(
        "cycle detected in extension files: \n"
            + "    test/starlark/BUILD\n"
            + ".-> //test/starlark:ext1.bzl\n"
            + "|   //test/starlark:ext2.bzl\n"
            + "`-- //test/starlark:ext1.bzl");
  }

  @Test
  public void testRecursiveLoad2() throws Exception {
    scratch.file("test/starlark/ext1.bzl", "load('//test/starlark:ext2.bzl', 'symbol2')");
    scratch.file("test/starlark/ext2.bzl", "load('//test/starlark:ext3.bzl', 'symbol3')");
    scratch.file("test/starlark/ext3.bzl", "load('//test/starlark:ext4.bzl', 'symbol4')");
    scratch.file("test/starlark/ext4.bzl", "load('//test/starlark:ext2.bzl', 'symbol2')");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:ext1.bzl', 'custom_rule')",
        "genrule(name = 'rule')");

    reporter.removeHandler(failFastHandler);
    assertThrows(BuildFileContainsErrorsException.class, () -> getTarget("//test/starlark:rule"));
    assertContainsEvent(
        "cycle detected in extension files: \n"
            + "    test/starlark/BUILD\n"
            + "    //test/starlark:ext1.bzl\n"
            + ".-> //test/starlark:ext2.bzl\n"
            + "|   //test/starlark:ext3.bzl\n"
            + "|   //test/starlark:ext4.bzl\n"
            + "`-- //test/starlark:ext2.bzl");
  }

  @Test
  public void testLoadSymbolTypo() throws Exception {
    scratch.file("test/starlark/ext1.bzl", "myvariable = 2");

    scratch.file("test/starlark/BUILD", "load('//test/starlark:ext1.bzl', 'myvariables')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test/starlark:test_target");
    assertContainsEvent(
        "file '//test/starlark:ext1.bzl' does not contain symbol 'myvariables' "
            + "(did you mean 'myvariable'?)");
  }

  @Test
  public void testOutputsObjectOrphanExecutableReportError() throws Exception {
    scratch.file(
        "test/rule.bzl",
        "def _impl(ctx):",
        "   o = ctx.outputs.executable",
        "   return [DefaultInfo(executable = o)]",
        "my_rule = rule(_impl, executable = True)"
    );

    scratch.file(
        "test/BUILD",
        "load(':rule.bzl', 'my_rule')",
        "my_rule(name = 'xxx')"
    );

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:xxx");
    assertContainsEvent("ERROR /workspace/test/BUILD:2:8: in my_rule rule //test:xxx: ");
    assertContainsEvent("The following files have no generating action:");
    assertContainsEvent("test/xxx");
  }

  @Test
  public void testCustomExecutableUsed() throws Exception {
    scratch.file(
        "test/rule.bzl",
        "def _impl(ctx):",
        "   o = ctx.actions.declare_file('x.sh')",
        "   ctx.actions.write(o, 'echo Stuff', is_executable = True)",
        "   return [DefaultInfo(executable = o)]",
        "my_rule = rule(_impl, executable = True)"
    );

    scratch.file(
        "test/BUILD",
        "load(':rule.bzl', 'my_rule')",
        "my_rule(name = 'xxx')"
    );

    ConfiguredTarget configuredTarget = getConfiguredTarget("//test:xxx");
    Artifact executable = configuredTarget.getProvider(FilesToRunProvider.class).getExecutable();
    assertThat(executable.getRootRelativePathString()).isEqualTo("test/x.sh");
  }

  @Test
  public void testCustomAndDefaultExecutableReportsError() throws Exception {
    scratch.file(
        "test/rule.bzl",
        "def _impl(ctx):",
        "   e = ctx.outputs.executable",
        "   o = ctx.actions.declare_file('x.sh')",
        "   ctx.actions.write(o, 'echo Stuff', is_executable = True)",
        "   return [DefaultInfo(executable = o)]",
        "my_rule = rule(_impl, executable = True)"
    );

    scratch.file(
        "test/BUILD",
        "load(':rule.bzl', 'my_rule')",
        "my_rule(name = 'xxx')"
    );
    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:xxx");
    assertContainsEvent("ERROR /workspace/test/BUILD:2:8: in my_rule rule //test:xxx: ");
    assertContainsEvent(
        "/workspace/test/rule.bzl:5:23: The rule 'my_rule' both accesses "
            + "'ctx.outputs.executable' and provides a different executable 'test/x.sh'. "
            + "Do not use 'ctx.output.executable'.");
  }


  @Test
  public void testCustomExecutableStrNoEffect() throws Exception {
    scratch.file(
        "test/rule.bzl",
        "def _impl(ctx):",
        "   o = ctx.actions.declare_file('x.sh')",
        "   ctx.actions.write(o, 'echo Stuff', is_executable = True)",
        "   print(str(ctx.outputs))",
        "   return [DefaultInfo(executable = o)]",
        "my_rule = rule(_impl, executable = True)"
    );

    scratch.file(
        "test/BUILD",
        "load(':rule.bzl', 'my_rule')",
        "my_rule(name = 'xxx')"
    );

    ConfiguredTarget configuredTarget = getConfiguredTarget("//test:xxx");
    Artifact executable = configuredTarget.getProvider(FilesToRunProvider.class).getExecutable();
    assertThat(executable.getRootRelativePathString()).isEqualTo("test/x.sh");
  }

  @Test
  public void testCustomExecutableDirNoEffect() throws Exception {
    scratch.file(
        "test/rule.bzl",
        "def _impl(ctx):",
        "   o = ctx.actions.declare_file('x.sh')",
        "   ctx.actions.write(o, 'echo Stuff', is_executable = True)",
        "   print(dir(ctx.outputs))",
        "   return [DefaultInfo(executable = o)]",
        "my_rule = rule(_impl, executable = True)"
    );

    scratch.file(
        "test/BUILD",
        "load(':rule.bzl', 'my_rule')",
        "my_rule(name = 'xxx')"
    );

    ConfiguredTarget configuredTarget = getConfiguredTarget("//test:xxx");
    Artifact executable = configuredTarget.getProvider(FilesToRunProvider.class).getExecutable();
    assertThat(executable.getRootRelativePathString()).isEqualTo("test/x.sh");
  }

  @Test
  public void testOutputsObjectInDifferentRuleInaccessible() throws Exception {
    scratch.file(
        "test/rule.bzl",
        "PInfo = provider(fields = ['outputs'])",
        "def _impl(ctx):",
        "   o = ctx.actions.declare_file('x.sh')",
        "   ctx.actions.write(o, 'echo Stuff', is_executable = True)",
        "   return [PInfo(outputs = ctx.outputs), DefaultInfo(executable = o)]",
        "my_rule = rule(_impl, executable = True)",
        "def _dep_impl(ctx):",
        "   o = ctx.attr.dep[PInfo].outputs.executable", // this is line 8
        "   pass",
        "my_dep_rule = rule(_dep_impl, attrs = { 'dep' : attr.label() })");

    scratch.file(
        "test/BUILD",
        "load(':rule.bzl', 'my_rule', 'my_dep_rule')",
        "my_rule(name = 'xxx')",
        "my_dep_rule(name = 'yyy', dep = ':xxx')"
    );

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:yyy");
    assertContainsEvent("ERROR /workspace/test/BUILD:3:12: in my_dep_rule rule //test:yyy: ");
    assertContainsEvent("File \"/workspace/test/rule.bzl\", line 8, column 35, in _dep_impl");
    assertContainsEvent("cannot access outputs of rule '//test:xxx' outside "
        + "of its own rule implementation function");
  }

  @Test
  public void testOutputsObjectStringRepresentation() throws Exception {
    scratch.file(
        "test/rule.bzl",
        "PInfo = provider(fields = ['outputs', 's'])",
        "def _impl(ctx):",
        "   ctx.actions.write(ctx.outputs.executable, 'echo Stuff', is_executable = True)",
        "   ctx.actions.write(ctx.outputs.other, 'Other')",
        "   return [PInfo(outputs = ctx.outputs, s = str(ctx.outputs))]",
        "my_rule = rule(_impl, executable = True, outputs = { 'other' : '%{name}.other' })",
        "def _dep_impl(ctx):",
        "   return [PInfo(s = str(ctx.attr.dep[PInfo].outputs))]",
        "my_dep_rule = rule(_dep_impl, attrs = { 'dep' : attr.label() })"
    );

    scratch.file(
        "test/BUILD",
        "load(':rule.bzl', 'my_rule', 'my_dep_rule')",
        "my_rule(name = 'xxx')",
        "my_dep_rule(name = 'yyy', dep = ':xxx')"
    );

    StarlarkProvider.Key pInfoKey =
        new StarlarkProvider.Key(
            Label.parseAbsolute("//test:rule.bzl", ImmutableMap.of()), "PInfo");

    ConfiguredTarget targetXXX = getConfiguredTarget("//test:xxx");
    StructImpl structXXX = (StructImpl) targetXXX.get(pInfoKey);

    assertThat(structXXX.getValue("s"))
        .isEqualTo(
            "ctx.outputs(executable = <generated file test/xxx>, "
                + "other = <generated file test/xxx.other>)");

    ConfiguredTarget targetYYY = getConfiguredTarget("//test:yyy");
    StructImpl structYYY = (StructImpl) targetYYY.get(pInfoKey);
    assertThat(structYYY.getValue("s"))
        .isEqualTo("ctx.outputs(for //test:xxx)");
  }

  @Test
  public void testExecutableRuleWithNoExecutableReportsError() throws Exception {
    scratch.file(
        "test/rule.bzl",
        "def _impl(ctx):",
        "   pass",
        "my_rule = rule(_impl, executable = True)"
    );

    scratch.file(
        "test/BUILD",
        "load(':rule.bzl', 'my_rule')",
        "my_rule(name = 'xxx')"
    );

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:xxx");
    assertContainsEvent("ERROR /workspace/test/BUILD:2:8: in my_rule rule //test:xxx: ");
    assertContainsEvent("/rule.bzl:1:5: The rule 'my_rule' is executable. "
        + "It needs to create an executable File and pass it as the 'executable' "
        + "parameter to the DefaultInfo it returns.");
  }

  @Test
  public void testExecutableFromDifferentRuleIsForbidden() throws Exception {
    scratch.file(
        "pkg/BUILD",
        "sh_binary(name = 'tryme',",
        "          srcs = [':tryme.sh'],",
        "          visibility = ['//visibility:public'],",
        ")");

    scratch.file(
        "src/rulez.bzl",
        "def  _impl(ctx):",
        "   return [DefaultInfo(executable = ctx.executable.runme,",
        "                       files = depset([ctx.executable.runme]),",
        "          )]",
        "r = rule(_impl,",
        "         executable = True,",
        "         attrs = {",
        "            'runme' : attr.label(executable = True, mandatory = True, cfg = 'host'),",
        "         }",
        ")");

    scratch.file(
        "src/BUILD", "load(':rulez.bzl', 'r')", "r(name = 'r_tools', runme = '//pkg:tryme')");
    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//src:r_tools");
    assertContainsEvent(
        "/workspace/src/rulez.bzl:2:23: 'executable' provided by an executable"
            + " rule 'r' should be created by the same rule.");
  }

  @Test
  public void testFileAndDirectory() throws Exception {
    scratch.file(
        "ext.bzl",
        "def _extrule(ctx):",
        "  dir = ctx.actions.declare_directory('foo/bar/baz')",
        "  ctx.actions.run_shell(",
        "      outputs = [dir],",
        "      command = 'mkdir -p ' + dir.path + ' && echo wtf > ' + dir.path + '/wtf.txt')",
        "",
        "extrule = rule(",
        "    _extrule,",
        "    outputs = {",
        "      'out': 'foo/bar/baz',",
        "    },",
        ")");
    scratch.file(
        "BUILD", //
        "load(':ext.bzl', 'extrule')",
        "",
        "extrule(",
        "    name = 'test'",
        ")");
    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//:test");
    assertContainsEvent("ERROR /workspace/BUILD:3:8: in extrule rule //:test:");
    assertContainsEvent("he following directories were also declared as files:");
    assertContainsEvent("foo/bar/baz");
  }

  @Test
  public void testEnvironmentConstraintsFromStarlarkRule() throws Exception {
    scratch.file(
        "buildenv/foo/BUILD",
        "environment_group(name = 'env_group',",
        "    defaults = [':default'],",
        "    environments = ['default', 'other'])",
        "environment(name = 'default')",
        "environment(name = 'other')");
    // The example Starlark rule explicitly provides the MyProvider provider as a regression test
    // for a bug where a Starlark rule with unsatisfied constraints but explicit providers would
    // result in Bazel throwing a null pointer exception.
    scratch.file(
        "test/starlark/extension.bzl",
        "MyProvider = provider()",
        "",
        "def _impl(ctx):",
        "  return [MyProvider(foo = 'bar')]",
        "my_rule = rule(implementation = _impl,",
        "    attrs = { 'deps' : attr.label_list() },",
        "    provides = [MyProvider])");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl',  'my_rule')",
        "java_library(name = 'dep', srcs = ['a.java'], restricted_to = ['//buildenv/foo:other'])",
        "my_rule(name='my', deps = [':dep'])");

    reporter.removeHandler(failFastHandler);
    assertThat(getConfiguredTarget("//test/starlark:my")).isNull();
    assertContainsEvent(
        "//test/starlark:dep doesn't support expected environment: //buildenv/foo:default");
  }

  @Test
  public void testAnalysisFailureInfo() throws Exception {
    scratch.file(
        "test/extension.bzl",
        "def custom_rule_impl(ctx):",
        "   fail('This Is My Failure Message')",
        "",
        "custom_rule = rule(implementation = custom_rule_impl)");

    scratch.file(
        "test/BUILD",
        "load('//test:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'r')");

    useConfiguration("--allow_analysis_failures=true");

    ConfiguredTarget target = getConfiguredTarget("//test:r");
    AnalysisFailureInfo info =
        (AnalysisFailureInfo) target.get(AnalysisFailureInfo.STARLARK_CONSTRUCTOR.getKey());
    AnalysisFailure failure = info.getCauses().getSet(AnalysisFailure.class).toList().get(0);
    assertThat(failure.getMessage()).contains("This Is My Failure Message");
    assertThat(failure.getLabel()).isEqualTo(Label.parseAbsoluteUnchecked("//test:r"));
  }

  @Test
  public void testAnalysisFailureInfo_forTest() throws Exception {
    scratch.file(
        "test/extension.bzl",
        "def custom_rule_impl(ctx):",
        "   fail('This Is My Failure Message')",
        "",
        "custom_test = rule(implementation = custom_rule_impl,",
        "    test = True)");

    scratch.file(
        "test/BUILD", "load('//test:extension.bzl', 'custom_test')", "", "custom_test(name = 'r')");

    useConfiguration("--allow_analysis_failures=true");

    ConfiguredTarget target = getConfiguredTarget("//test:r");
    AnalysisFailureInfo info =
        (AnalysisFailureInfo) target.get(AnalysisFailureInfo.STARLARK_CONSTRUCTOR.getKey());
    AnalysisFailure failure = info.getCauses().getSet(AnalysisFailure.class).toList().get(0);
    assertThat(failure.getMessage()).contains("This Is My Failure Message");
    assertThat(failure.getLabel()).isEqualTo(Label.parseAbsoluteUnchecked("//test:r"));
  }

  @Test
  public void testAnalysisFailureInfoWithOutput() throws Exception {
    scratch.file(
        "test/extension.bzl",
        "def custom_rule_impl(ctx):",
        "   fail('This Is My Failure Message')",
        "",
        "custom_rule = rule(implementation = custom_rule_impl,",
        "    outputs = {'my_output': '%{name}.txt'})");

    scratch.file(
        "test/BUILD", "load('//test:extension.bzl', 'custom_rule')", "", "custom_rule(name = 'r')");

    useConfiguration("--allow_analysis_failures=true");

    ConfiguredTarget target = getConfiguredTarget("//test:r");
    AnalysisFailureInfo info =
        (AnalysisFailureInfo) target.get(AnalysisFailureInfo.STARLARK_CONSTRUCTOR.getKey());
    AnalysisFailure failure = info.getCauses().getSet(AnalysisFailure.class).toList().get(0);
    assertThat(failure.getMessage()).contains("This Is My Failure Message");
    assertThat(failure.getLabel()).isEqualTo(Label.parseAbsoluteUnchecked("//test:r"));
  }

  @Test
  public void testTransitiveAnalysisFailureInfo() throws Exception {
    scratch.file(
        "test/extension.bzl",
        "def custom_rule_impl(ctx):",
        "   fail('This Is My Failure Message')",
        "",
        "custom_rule = rule(implementation = custom_rule_impl)",
        "",
        "def depending_rule_impl(ctx):",
        "   return []",
        "",
        "depending_rule = rule(implementation = depending_rule_impl,",
        "     attrs = {'deps' : attr.label_list()})");

    scratch.file(
        "test/BUILD",
        "load('//test:extension.bzl', 'custom_rule', 'depending_rule')",
        "",
        "custom_rule(name = 'one')",
        "custom_rule(name = 'two')",
        "depending_rule(name = 'failures_are_direct_deps',",
        "    deps = [':one', ':two'])",
        "depending_rule(name = 'failures_are_indirect_deps',",
        "    deps = [':failures_are_direct_deps'])");

    useConfiguration("--allow_analysis_failures=true");

    ConfiguredTarget target = getConfiguredTarget("//test:failures_are_indirect_deps");
    AnalysisFailureInfo info =
        (AnalysisFailureInfo) target.get(AnalysisFailureInfo.STARLARK_CONSTRUCTOR.getKey());

    Correspondence<AnalysisFailure, AnalysisFailure> correspondence =
        Correspondence.from(
            (actual, expected) ->
                actual.getLabel().equals(expected.getLabel())
                    && actual.getMessage().contains(expected.getMessage()),
            "is equivalent to");

    AnalysisFailure expectedOne =
        new AnalysisFailure(
            Label.parseAbsoluteUnchecked("//test:one"), "This Is My Failure Message");
    AnalysisFailure expectedTwo =
        new AnalysisFailure(
            Label.parseAbsoluteUnchecked("//test:two"), "This Is My Failure Message");

    assertThat(info.getCauses().getSet(AnalysisFailure.class).toList())
        .comparingElementsUsing(correspondence)
        .containsExactly(expectedOne, expectedTwo);
  }

  @Test
  public void testTestResultInfo() throws Exception {
    scratch.file(
        "test/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  return [AnalysisTestResultInfo(success = True, message = 'message contents')]",
        "",
        "custom_rule = rule(implementation = custom_rule_impl)");

    scratch.file(
        "test/BUILD",
        "load('//test:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'r')");

    ConfiguredTarget target = getConfiguredTarget("//test:r");
    AnalysisTestResultInfo info =
        (AnalysisTestResultInfo) target.get(AnalysisTestResultInfo.STARLARK_CONSTRUCTOR.getKey());
    assertThat(info.getSuccess()).isTrue();
    assertThat(info.getMessage()).isEqualTo("message contents");
  }

  @Test
  public void testAnalysisTestRuleWithActionRegistration() throws Exception {
    scratch.file(
        "test/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  out_file = ctx.actions.declare_file('file.txt')",
        "  ctx.actions.write(output=out_file, content='hi')",
        "",
        "custom_test = rule(implementation = custom_rule_impl, analysis_test = True)");

    scratch.file(
        "test/BUILD", "load('//test:extension.bzl', 'custom_test')", "", "custom_test(name = 'r')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:r");
    assertContainsEvent(
        "implementation function of a rule with analysis_test=true may not register actions");
  }

  @Test
  public void testAnalysisTestRuleWithFlag() throws Exception {
    scratch.file(
        "test/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  return [AnalysisTestResultInfo(success = True, message = 'message contents')]",
        "",
        "custom_test = rule(implementation = custom_rule_impl, analysis_test = True)");

    scratch.file(
        "test/BUILD", "load('//test:extension.bzl', 'custom_test')", "", "custom_test(name = 'r')");

    ConfiguredTarget target = getConfiguredTarget("//test:r");
    AnalysisTestResultInfo info =
        (AnalysisTestResultInfo) target.get(AnalysisTestResultInfo.STARLARK_CONSTRUCTOR.getKey());
    assertThat(info.getSuccess()).isTrue();
    assertThat(info.getMessage()).isEqualTo("message contents");

    // TODO(cparsons): Verify implicit action registration via AnalysisTestResultInfo.
  }

  @Test
  public void testAnalysisTestTransitionOnAnalysisTest() throws Exception {
    useConfiguration("--copt=yeehaw");

    scratch.file(
        "test/extension.bzl",
        "MyInfo = provider()",
        "MyDep = provider()",
        "",
        "def outer_rule_impl(ctx):",
        "  return [MyInfo(copts = ctx.fragments.cpp.copts),",
        "          MyDep(info = ctx.attr.dep[0][MyInfo]),",
        "          AnalysisTestResultInfo(success = True, message = 'message contents')]",
        "def inner_rule_impl(ctx):",
        "  return [MyInfo(copts = ctx.fragments.cpp.copts)]",
        "",
        "my_transition = analysis_test_transition(",
        "    settings = {",
        "        '//command_line_option:copt' : ['cowabunga'] }",
        ")",
        "inner_rule = rule(implementation = inner_rule_impl,",
        "                  fragments = ['cpp'])",
        "outer_rule_test = rule(",
        "  implementation = outer_rule_impl,",
        "  fragments = ['cpp'],",
        "  analysis_test = True,",
        "  attrs = {",
        "    'dep':  attr.label(cfg = my_transition),",
        "  })");

    scratch.file(
        "test/BUILD",
        "load('//test:extension.bzl', 'inner_rule', 'outer_rule_test')",
        "",
        "inner_rule(name = 'inner')",
        "outer_rule_test(name = 'r', dep = ':inner')");

    StarlarkProvider.Key myInfoKey =
        new StarlarkProvider.Key(
            Label.parseAbsolute("//test:extension.bzl", ImmutableMap.of()), "MyInfo");
    StarlarkProvider.Key myDepKey =
        new StarlarkProvider.Key(
            Label.parseAbsolute("//test:extension.bzl", ImmutableMap.of()), "MyDep");

    ConfiguredTarget outerTarget = getConfiguredTarget("//test:r");
    StructImpl outerInfo = (StructImpl) outerTarget.get(myInfoKey);
    StructImpl outerDepInfo = (StructImpl) outerTarget.get(myDepKey);
    StructImpl innerInfo = (StructImpl) outerDepInfo.getValue("info");

    assertThat((Sequence) outerInfo.getValue("copts")).containsExactly("yeehaw");
    assertThat((Sequence) innerInfo.getValue("copts")).containsExactly("cowabunga");
  }

  // Regression test for b/168715549 which exposed a bug when an analysistest transition
  // set an option to the same value it already had in the configuration, depended on a c++ rule,
  // and was built at the same time as the same cc rule not under transition. Basically it's
  // ensuring that analysistests are never treated as no-op transitions (which don't update the
  // output directory).
  @Test
  public void testAnalysisTestTransitionOnAndWithCcRuleHasNoActionConflicts() throws Exception {
    scratch.file(
        "test/extension.bzl",
        "test_transition = analysis_test_transition(",
        "  settings = {'//command_line_option:compilation_mode': 'fastbuild'}",
        ")",
        "def _test_impl(ctx):",
        "  return [AnalysisTestResultInfo(success = True, message = 'message contents')]",
        "my_analysis_test = rule(",
        "  implementation = _test_impl,",
        "  attrs = {",
        "    'target_under_test': attr.label(cfg = test_transition),",
        "  },",
        "  test = True,",
        "  analysis_test = True",
        ")",
        "def _impl(ctx):",
        "  pass",
        "",
        "parent = rule(",
        "  implementation = _impl,",
        "  attrs = {",
        "    'one': attr.label(),",
        "    'two': attr.label(),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:extension.bzl', 'my_analysis_test', 'parent')",
        "cc_library(name = 'dep')",
        "my_analysis_test(",
        "  name = 'test',",
        "  target_under_test = ':dep',",
        ")",
        "parent(",
        "  name = 'parent',",
        // Needs to be testonly to depend on a test rule.
        "  testonly = True,",
        "  one = ':dep',",
        "  two = ':test',",
        ")");
    useConfiguration("--compilation_mode=fastbuild");
    getConfiguredTarget("//test:parent");
  }

  @Test
  public void testAnalysisTestTransitionOnNonAnalysisTest() throws Exception {
    scratch.file(
        "test/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  return []",
        "my_transition = analysis_test_transition(",
        "    settings = {",
        "        '//command_line_option:test_arg' : ['yeehaw'] }",
        ")",
        "",
        "custom_rule = rule(",
        "  implementation = custom_rule_impl,",
        "  attrs = {",
        "    'dep':  attr.label(cfg = my_transition),",
        "  })");

    scratch.file(
        "test/BUILD",
        "load('//test:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'r')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:r");
    assertContainsEvent(
        "Only rule definitions with analysis_test=True may have attributes "
            + "with analysis_test_transition transitions");
  }

  @Test
  public void testBuildSettingRule_flag() throws Exception {
    scratch.file("test/rules.bzl",
        "def _impl(ctx): return None",
        "build_setting_rule = rule(_impl, build_setting = config.string(flag=True))");
    scratch.file("test/BUILD",
    "load('//test:rules.bzl', 'build_setting_rule')",
    "build_setting_rule(name = 'my_build_setting', build_setting_default = 'default')");

    BuildSetting buildSetting =
        getTarget("//test:my_build_setting")
            .getAssociatedRule()
            .getRuleClassObject()
            .getBuildSetting();

    assertThat(buildSetting.getType()).isEqualTo(Type.STRING);
    assertThat(buildSetting.isFlag()).isTrue();
  }

  @Test
  public void testBuildSettingRule_settingByDefault() throws Exception {
    scratch.file("test/rules.bzl",
        "def _impl(ctx): return None",
        "build_setting_rule = rule(_impl, build_setting = config.string())");
    scratch.file("test/BUILD",
        "load('//test:rules.bzl', 'build_setting_rule')",
        "build_setting_rule(name = 'my_build_setting', build_setting_default = 'default')");

    BuildSetting buildSetting =
        getTarget("//test:my_build_setting")
            .getAssociatedRule()
            .getRuleClassObject()
            .getBuildSetting();

    assertThat(buildSetting.getType()).isEqualTo(Type.STRING);
    assertThat(buildSetting.isFlag()).isFalse();
  }

  @Test
  public void testBuildSettingRule_settingByFlagParameter() throws Exception {
    scratch.file("test/rules.bzl",
        "def _impl(ctx): return None",
        "build_setting_rule = rule(_impl, build_setting = config.string(flag=False))");
    scratch.file("test/BUILD",
        "load('//test:rules.bzl', 'build_setting_rule')",
        "build_setting_rule(name = 'my_build_setting', build_setting_default = 'default')");

    BuildSetting buildSetting =
        getTarget("//test:my_build_setting")
            .getAssociatedRule()
            .getRuleClassObject()
            .getBuildSetting();

    assertThat(buildSetting.getType()).isEqualTo(Type.STRING);
    assertThat(buildSetting.isFlag()).isFalse();
  }


  @Test
  public void testBuildSettingRule_noDefault() throws Exception {
    scratch.file("test/rules.bzl",
        "def _impl(ctx): return None",
        "build_setting_rule = rule(_impl, build_setting = config.string())");
    scratch.file("test/BUILD",
        "load('//test:rules.bzl', 'build_setting_rule')",
        "build_setting_rule(name = 'my_build_setting')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:my_build_setting");
    assertContainsEvent("missing value for mandatory attribute "
        + "'build_setting_default' in 'build_setting_rule' rule");

  }

  @Test
  public void testAnalysisTestCannotDependOnAnalysisTest() throws Exception {
    scratch.file(
        "test/extension.bzl",
        "",
        "def analysis_test_rule_impl(ctx):",
        "  return [AnalysisTestResultInfo(success = True, message = 'message contents')]",
        "def middle_rule_impl(ctx):",
        "  return []",
        "def inner_rule_impl(ctx):",
        "  return [AnalysisTestResultInfo(success = True, message = 'message contents')]",
        "",
        "my_transition = analysis_test_transition(",
        "    settings = {",
        "        '//command_line_option:test_arg' : ['yeehaw'] }",
        ")",
        "",
        "inner_rule_test = rule(",
        "  implementation = analysis_test_rule_impl,",
        "  analysis_test = True,",
        ")",
        "middle_rule = rule(",
        "  implementation = middle_rule_impl,",
        "  attrs = {'dep':  attr.label()}",
        ")",
        "outer_rule_test = rule(",
        "  implementation = analysis_test_rule_impl,",
        "  analysis_test = True,",
        "  attrs = {",
        "    'dep':  attr.label(cfg = my_transition),",
        "  })");

    scratch.file(
        "test/BUILD",
        "load('//test:extension.bzl', 'outer_rule_test', 'middle_rule', 'inner_rule_test')",
        "",
        "outer_rule_test(name = 'outer', dep = ':middle')",
        "middle_rule(name = 'middle', dep = ':inner')",
        "inner_rule_test(name = 'inner')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:outer");
    assertContainsEvent(
        "analysis_test rule '//test:inner' cannot be transitively "
            + "depended on by another analysis test rule");
  }

  @Test
  public void testAnalysisTestOverDepsLimit() throws Exception {
    setupAnalysisTestDepsLimitTest(10, 12, true);

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:r");
    assertContainsEvent(
        "analysis test rule excedeed maximum dependency edge count. " + "Count: 14. Limit is 10.");
  }

  @Test
  public void testAnalysisTestUnderDepsLimit() throws Exception {
    setupAnalysisTestDepsLimitTest(10, 8, true);

    assertThat(getConfiguredTarget("//test:r")).isNotNull();
  }

  @Test
  public void testAnalysisDepsLimitOnlyForTransition() throws Exception {
    setupAnalysisTestDepsLimitTest(3, 10, false);

    assertThat(getConfiguredTarget("//test:r")).isNotNull();
  }

  private void setupAnalysisTestDepsLimitTest(
      int limit, int dependencyChainSize, boolean useTransition) throws Exception {
    Preconditions.checkArgument(dependencyChainSize > 2);
    useConfiguration("--analysis_testing_deps_limit=" + limit);

    String transitionDefinition;
    if (useTransition) {
      transitionDefinition =
          "my_transition = analysis_test_transition("
              + "settings = {'//command_line_option:test_arg' : ['yeehaw'] })";
    } else {
      transitionDefinition = "my_transition = None";
    }

    scratch.file(
        "test/extension.bzl",
        "",
        "def outer_rule_impl(ctx):",
        "  return [AnalysisTestResultInfo(success = True, message = 'message contents')]",
        "def dep_rule_impl(ctx):",
        "  return []",
        "",
        transitionDefinition,
        "",
        "dep_rule = rule(",
        "  implementation = dep_rule_impl,",
        "  attrs = {'deps': attr.label_list()}",
        ")",
        "outer_rule_test = rule(",
        "  implementation = outer_rule_impl,",
        "  fragments = ['java'],",
        "  analysis_test = True,",
        "  attrs = {",
        "    'dep':  attr.label(cfg = my_transition),",
        "  })");

    // Create a chain of targets where 'innerN' depends on 'inner{N+1}' until the max length.
    StringBuilder dependingRulesChain = new StringBuilder();
    for (int i = 0; i < dependencyChainSize - 1; i++) {
      // Each dep_rule target also depends on the leaf.
      // The leaf should not be counted multiple times.
      dependingRulesChain.append(
          String.format(
              "dep_rule(name = 'inner%s', deps = [':inner%s', ':inner%s'])\n",
              i, (i + 1), dependencyChainSize));
    }
    dependingRulesChain.append(
        String.format(
            "dep_rule(name = 'inner%s', deps = [':inner%s'])\n",
            dependencyChainSize - 1, dependencyChainSize));
    dependingRulesChain.append(String.format("dep_rule(name = 'inner%s')", dependencyChainSize));

    scratch.file(
        "test/BUILD",
        "load('//test:extension.bzl', 'dep_rule', 'outer_rule_test')",
        "",
        "outer_rule_test(name = 'r', dep = ':inner0')",
        dependingRulesChain.toString());
  }

  @Test
  public void testBadAllowlistTransition_onNonLabelAttr() throws Exception {
    String allowlistAttributeName =
        FunctionSplitTransitionAllowlist.ATTRIBUTE_NAME.replace("$", "_");
    scratch.file(
        "test/rules.bzl",
        "def _impl(ctx):",
        "    return []",
        "",
        "my_rule = rule(_impl, attrs = {'"
            + allowlistAttributeName
            + "':attr.string(default = 'blah')})");
    scratch.file("test/BUILD", "load('//test:rules.bzl', 'my_rule')", "my_rule(name = 'my_rule')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:my_rule");
    assertContainsEvent("_allowlist_function_transition attribute must be a label type");
  }

  @Test
  public void testBadAllowlistTransition_noDefaultValue() throws Exception {
    String allowlistAttributeName =
        FunctionSplitTransitionAllowlist.ATTRIBUTE_NAME.replace("$", "_");
    scratch.file(
        "test/rules.bzl",
        "def _impl(ctx):",
        "    return []",
        "",
        "my_rule = rule(_impl, attrs = {'" + allowlistAttributeName + "':attr.label()})");
    scratch.file("test/BUILD", "load('//test:rules.bzl', 'my_rule')", "my_rule(name = 'my_rule')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:my_rule");
    assertContainsEvent("_allowlist_function_transition attribute must have a default value");
  }

  @Test
  public void testBadAllowlistTransition_wrongDefaultValue() throws Exception {
    String allowlistAttributeName =
        FunctionSplitTransitionAllowlist.ATTRIBUTE_NAME.replace("$", "_");
    scratch.file(
        "test/rules.bzl",
        "def _impl(ctx):",
        "    return []",
        "",
        "my_rule = rule(_impl, attrs = {'"
            + allowlistAttributeName
            + "':attr.label(default = Label('//test:my_other_rule'))})");
    scratch.file(
        "test/BUILD",
        "load('//test:rules.bzl', 'my_rule')",
        "my_rule(name = 'my_rule')",
        "my_rule(name = 'my_other_rule')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:my_rule");
    assertContainsEvent(
        " _allowlist_function_transition attribute (//test:my_other_rule) does not have the"
            + " expected value");
  }

  @Test
  public void testBadAnalysisTestRule_notAnalysisTest() throws Exception {
    scratch.file(
        "test/extension.bzl",
        "",
        "def outer_rule_impl(ctx):",
        "  return [AnalysisTestResultInfo(success = True, message = 'message contents')]",
        "def dep_rule_impl(ctx):",
        "  return []",
        "",
        "my_transition = analysis_test_transition(",
        "    settings = {",
        "        '//command_line_option:test_arg' : ['yeehaw'] }",
        ")",
        "dep_rule = rule(",
        "  implementation = dep_rule_impl,",
        "  attrs = {'dep':  attr.label()}",
        ")",
        "outer_rule = rule(",
        "  implementation = outer_rule_impl,",
        "# analysis_test = True,",
        "  fragments = ['java'],",
        "  attrs = {",
        "    'dep':  attr.label(cfg = my_transition),",
        "  })");

    scratch.file(
        "test/BUILD",
        "load('//test:extension.bzl', 'dep_rule', 'outer_rule_test')",
        "",
        "outer_rule(name = 'r', dep = ':inner')",
        "dep_rule(name = 'inner')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:outer_rule");
    assertContainsEvent(
        "Only rule definitions with analysis_test=True may have attributes with "
            + "analysis_test_transition transitions");
  }

  @Test
  public void testBadAllowlistTransition_noAllowlist() throws Exception {
    scratch.file(
        "tools/allowlists/function_transition_allowlist/BUILD",
        "package_group(",
        "    name = 'function_transition_allowlist',",
        "    packages = [",
        "        '//test/...',",
        "    ],",
        ")");
    scratch.file(
        "test/rules.bzl",
        "def transition_func(settings):",
        "  return {'t0': {'//command_line_option:cpu': 'k8'}}",
        "my_transition = transition(implementation = transition_func, inputs = [],",
        "  outputs = ['//command_line_option:cpu'])",
        "def _my_rule_impl(ctx): ",
        "  return []",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "  attrs = {",
        "    'dep':  attr.label(cfg = my_transition),",
        "#   '_allowlist_function_transition': attr.label(",
        "#       default = '//tools/allowlists/function_transition_allowlist',",
        "#   ),",
        "  })",
        "def _simple_rule_impl(ctx):",
        "  return []",
        "simple_rule = rule(_simple_rule_impl)");

    scratch.file(
        "test/BUILD",
        "load('//test:rules.bzl', 'my_rule', 'simple_rule')",
        "my_rule(name = 'my_rule', dep = ':dep')",
        "simple_rule(name = 'dep')");
    setBuildLanguageOptions("--experimental_starlark_config_transitions");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:my_rule");
    assertContainsEvent("Use of Starlark transition without allowlist");
  }

  @Test
  public void testPrintFromTransitionImpl() throws Exception {
    setBuildLanguageOptions("--experimental_starlark_config_transitions");
    scratch.file(
        "tools/allowlists/function_transition_allowlist/BUILD",
        "package_group(",
        "    name = 'function_transition_allowlist',",
        "    packages = [",
        "        '//test/...',",
        "    ],",
        ")");
    scratch.file(
        "test/rules.bzl",
        "def _transition_impl(settings, attr):",
        "  print('printing from transition impl', settings['//command_line_option:test_arg'])",
        "  return {'//command_line_option:test_arg': "
            + "settings['//command_line_option:test_arg']+['meow']}",
        "my_transition = transition(",
        "  implementation = _transition_impl,",
        "  inputs = ['//command_line_option:test_arg'],",
        "  outputs = ['//command_line_option:test_arg'],",
        ")",
        "def _rule_impl(ctx):",
        "  return []",
        "my_rule = rule(",
        "  implementation = _rule_impl,",
        "  cfg = my_transition,",
        "  attrs = {",
        "    'dep': attr.label(cfg = my_transition),",
        "    '_allowlist_function_transition': attr.label(",
        "        default = '//tools/allowlists/function_transition_allowlist',",
        "    ),",
        "  }",
        ")");

    scratch.file(
        "test/BUILD",
        "load('//test:rules.bzl', 'my_rule')",
        "my_rule(name = 'test', dep = ':dep')",
        "my_rule(name = 'dep')");

    useConfiguration("--test_arg=meow");

    getConfiguredTarget("//test");
    // Test print from top level transition
    assertContainsEvent("printing from transition impl [\"meow\"]");
    // Test print from dep transition
    assertContainsEvent("printing from transition impl [\"meow\", \"meow\"]");
    // Test print from (non-top level) rule class transition
    assertContainsEvent("printing from transition impl [\"meow\", \"meow\", \"meow\"]");
  }

  @Test
  public void testTransitionEquality() throws Exception {
    setBuildLanguageOptions("--experimental_starlark_config_transitions");
    scratch.file(
        "tools/allowlists/function_transition_allowlist/BUILD",
        "package_group(",
        "    name = 'function_transition_allowlist',",
        "    packages = [",
        "        '//test/...',",
        "    ],",
        ")");
    scratch.file(
        "test/rules.bzl",
        "def _transition_impl(settings, attr):",
        "  return {'//command_line_option:test_arg': ['meow']}",
        "my_transition = transition(",
        "  implementation = _transition_impl,",
        "  inputs = [],",
        "  outputs = ['//command_line_option:test_arg'],",
        ")",
        "def _rule_impl(ctx):",
        "  return []",
        "my_rule = rule(",
        "  implementation = _rule_impl,",
        "  cfg = my_transition,",
        "  attrs = {",
        "    'dep': attr.label(cfg = my_transition),",
        "    '_allowlist_function_transition': attr.label(",
        "        default = '//tools/allowlists/function_transition_allowlist',",
        "    ),",
        "  }",
        ")");

    scratch.file(
        "test/BUILD",
        "load('//test:rules.bzl', 'my_rule')",
        "my_rule(name = 'test', dep = ':dep')",
        "my_rule(name = 'dep')");

    useConfiguration("--test_arg=meow");

    StarlarkDefinedConfigTransition ruleTransition =
        ((StarlarkAttributeTransitionProvider)
                getTarget("//test")
                    .getAssociatedRule()
                    .getRuleClassObject()
                    .getAttributeByName("dep")
                    .getTransitionFactory())
            .getStarlarkDefinedConfigTransitionForTesting();

    StarlarkDefinedConfigTransition attrTransition =
        ((StarlarkRuleTransitionProvider)
                getTarget("//test").getAssociatedRule().getRuleClassObject().getTransitionFactory())
            .getStarlarkDefinedConfigTransitionForTesting();

    assertThat(ruleTransition).isEqualTo(attrTransition);
    assertThat(attrTransition).isEqualTo(ruleTransition);
    assertThat(ruleTransition.hashCode()).isEqualTo(attrTransition.hashCode());
  }

  @Test
  public void testBadAllowlistTransition_allowlistNoCfg() throws Exception {
    scratch.file(
        "tools/allowlists/function_transition_allowlist/BUILD",
        "package_group(",
        "    name = 'function_transition_allowlist',",
        "    packages = [",
        "        '//test/...',",
        "    ],",
        ")");
    scratch.file(
        "test/rules.bzl",
        "def _my_rule_impl(ctx): ",
        "  return []",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "  attrs = {",
        "#   'dep':  attr.label(cfg = my_transition),",
        "    '_allowlist_function_transition': attr.label(",
        "        default = '//tools/allowlists/function_transition_allowlist',",
        "    ),",
        "  })",
        "def _simple_rule_impl(ctx):",
        "  return []",
        "simple_rule = rule(_simple_rule_impl)");

    scratch.file(
        "test/BUILD",
        "load('//test:rules.bzl', 'my_rule', 'simple_rule')",
        "my_rule(name = 'my_rule', dep = ':dep')",
        "simple_rule(name = 'dep')");
    setBuildLanguageOptions("--experimental_starlark_config_transitions");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:my_rule");
    assertContainsEvent("Unused function-based split transition allowlist");
  }

  @Test
  public void testLicenseType() throws Exception {
    // Note that attr.license is deprecated, and thus this test is subject to imminent removal.
    // (See --incompatible_no_attr_license). However, this verifies that until the attribute
    // is removed, values of the attribute are a valid Starlark type.
    setBuildLanguageOptions("--incompatible_no_attr_license=false");
    scratch.file(
        "test/rule.bzl",
        "def _my_rule_impl(ctx): ",
        "  print(ctx.attr.my_license)",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "  attrs = {",
        "    'my_license':  attr.license(),",
        "  })");
    scratch.file("test/BUILD", "load(':rule.bzl', 'my_rule')", "my_rule(name = 'test')");

    getConfiguredTarget("//test:test");

    assertContainsEvent("[none]");
  }

  @Test
  public void testNativeModuleFields() throws Exception {
    // Check that
    scratch.file(
        "test/file.bzl",
        "def valid(s):",
        "    if not s[0].isalpha(): return False",
        "    for c in s.elems():",
        "        if not (c.isalpha() or c == '_' or c.isdigit()): return False",
        "    return True",
        "",
        "bad_names = [name for name in dir(native) if not valid(name)]",
        "print('bad_names =', bad_names)");
    scratch.file("test/BUILD", "load('//test:file.bzl', 'bad_names')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:anything");
    assertContainsEvent("bad_names = []");
  }

  @Test
  public void testDisallowStructProviderSyntax() throws Exception {
    setBuildLanguageOptions("--incompatible_disallow_struct_provider_syntax=true");
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  return struct()",
        "",
        "custom_rule = rule(implementation = custom_rule_impl)");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'cr')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test/starlark:cr");
    assertContainsEvent(
        "Returning a struct from a rule implementation function is deprecated and will be "
            + "removed soon. It may be temporarily re-enabled by setting "
            + "--incompatible_disallow_struct_provider_syntax=false");
  }

  @Test
  public void testDisableTargetProviderFields() throws Exception {
    setBuildLanguageOptions("--incompatible_disable_target_provider_fields=true");
    scratch.file(
        "test/starlark/rule.bzl",
        "MyProvider = provider()",
        "",
        "def _my_rule_impl(ctx): ",
        "  print(ctx.attr.dep.my_info)",
        "def _dep_rule_impl(ctx): ",
        "  my_info = MyProvider(foo = 'bar')",
        "  return struct(my_info = my_info, providers = [my_info])",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "  attrs = {",
        "    'dep':  attr.label(),",
        "  })",
        "dep_rule = rule(implementation = _dep_rule_impl)");
    scratch.file(
        "test/starlark/BUILD",
        "load(':rule.bzl', 'my_rule', 'dep_rule')",
        "",
        "my_rule(name = 'r', dep = ':d')",
        "dep_rule(name = 'd')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test/starlark:r");
    assertContainsEvent(
        "Accessing providers via the field syntax on structs is deprecated and will be removed "
            + "soon. It may be temporarily re-enabled by setting "
            + "--incompatible_disable_target_provider_fields=false. "
            + "See https://github.com/bazelbuild/bazel/issues/9014 for details.");
  }

  // Verifies that non-provider fields on the 'target' type are still available even with
  // --incompatible_disable_target_provider_fields.
  @Test
  public void testDisableTargetProviderFields_actionsField() throws Exception {
    setBuildLanguageOptions("--incompatible_disable_target_provider_fields=true");
    scratch.file(
        "test/starlark/rule.bzl",
        "MyProvider = provider()",
        "",
        "def _my_rule_impl(ctx): ",
        "  print(ctx.attr.dep.actions)",
        "def _dep_rule_impl(ctx): ",
        "  my_info = MyProvider(foo = 'bar')",
        "  return struct(my_info = my_info, providers = [my_info])",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "  attrs = {",
        "    'dep':  attr.label(),",
        "  })",
        "dep_rule = rule(implementation = _dep_rule_impl)");
    scratch.file(
        "test/starlark/BUILD",
        "load(':rule.bzl', 'my_rule', 'dep_rule')",
        "",
        "my_rule(name = 'r', dep = ':d')",
        "dep_rule(name = 'd')");

    assertThat(getConfiguredTarget("//test/starlark:r")).isNotNull();
  }

  @Test
  public void testDisableTargetProviderFields_disabled() throws Exception {
    setBuildLanguageOptions("--incompatible_disable_target_provider_fields=false");
    scratch.file(
        "test/starlark/rule.bzl",
        "MyProvider = provider()",
        "",
        "def _my_rule_impl(ctx): ",
        "  print(ctx.attr.dep.my_info)",
        "def _dep_rule_impl(ctx): ",
        "  my_info = MyProvider(foo = 'bar')",
        "  return struct(my_info = my_info, providers = [my_info])",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "  attrs = {",
        "    'dep':  attr.label(),",
        "  })",
        "dep_rule = rule(implementation = _dep_rule_impl)");
    scratch.file(
        "test/starlark/BUILD",
        "load(':rule.bzl', 'my_rule', 'dep_rule')",
        "",
        "my_rule(name = 'r', dep = ':d')",
        "dep_rule(name = 'd')");

    assertThat(getConfiguredTarget("//test/starlark:r")).isNotNull();
  }

  @Test
  public void testNoRuleOutputsParam() throws Exception {
    setBuildLanguageOptions("--incompatible_no_rule_outputs_param=true");
    scratch.file(
        "test/starlark/test_rule.bzl",
        "def _impl(ctx):",
        "  output = ctx.outputs.out",
        "  ctx.actions.write(output = output, content = 'hello')",
        "",
        "my_rule = rule(",
        "  implementation = _impl,",
        "  outputs = {\"out\": \"%{name}.txt\"})");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:test_rule.bzl', 'my_rule')",
        "my_rule(name = 'target')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test/starlark:target");
    assertContainsEvent(
        "parameter 'outputs' is deprecated and will be removed soon. It may be temporarily "
            + "re-enabled by setting --incompatible_no_rule_outputs_param=false");
  }

  @Test
  public void testExecutableNotInRunfiles() throws Exception {
    setBuildLanguageOptions("--incompatible_disallow_struct_provider_syntax=false");
    scratch.file(
        "test/starlark/test_rule.bzl",
        "def _my_rule_impl(ctx):",
        "  exe = ctx.actions.declare_file('exe')",
        "  ctx.actions.run_shell(outputs=[exe], command='touch exe')",
        "  runfile = ctx.actions.declare_file('rrr')",
        "  ctx.actions.run_shell(outputs=[runfile], command='touch rrr')",
        "  return struct(executable = exe, default_runfiles = ctx.runfiles(files = [runfile]))",
        "my_rule = rule(implementation = _my_rule_impl, executable = True)");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:test_rule.bzl', 'my_rule')",
        "my_rule(name = 'target')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test/starlark:target");
    assertContainsEvent("exe not included in runfiles");
  }

  @Test
  public void testCommandStringList() throws Exception {
    setBuildLanguageOptions("--incompatible_run_shell_command_string");
    scratch.file(
        "test/starlark/test_rule.bzl",
        "def _my_rule_impl(ctx):",
        "  exe = ctx.actions.declare_file('exe')",
        "  ctx.actions.run_shell(outputs=[exe], command=['touch', 'exe'])",
        "  return []",
        "my_rule = rule(implementation = _my_rule_impl)");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:test_rule.bzl', 'my_rule')",
        "my_rule(name = 'target')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test/starlark:target");
    assertContainsEvent("'command' must be of type string");
  }

  /** Starlark integration test that forces inlining. */
  @RunWith(JUnit4.class)
  public static class StarlarkIntegrationTestsWithInlineCalls extends StarlarkIntegrationTest {

    @Override
    protected boolean usesInliningBzlLoadFunction() {
      return true;
    }

    @Override
    @Test
    public void testRecursiveLoad() throws Exception {
      scratch.file("test/starlark/ext2.bzl", "load('//test/starlark:ext1.bzl', 'symbol2')");

      scratch.file("test/starlark/ext1.bzl", "load('//test/starlark:ext2.bzl', 'symbol1')");

      scratch.file(
          "test/starlark/BUILD",
          "load('//test/starlark:ext1.bzl', 'custom_rule')",
          "genrule(name = 'rule')");

      reporter.removeHandler(failFastHandler);
      BuildFileContainsErrorsException e =
          assertThrows(
              BuildFileContainsErrorsException.class, () -> getTarget("//test/starlark:rule"));
      assertThat(e)
          .hasMessageThat()
          .contains("Starlark load cycle: [//test/starlark:ext1.bzl, //test/starlark:ext2.bzl]");
    }

    @Override
    @Test
    public void testRecursiveLoad2() throws Exception {
      scratch.file("test/starlark/ext1.bzl", "load('//test/starlark:ext2.bzl', 'symbol2')");
      scratch.file("test/starlark/ext2.bzl", "load('//test/starlark:ext3.bzl', 'symbol3')");
      scratch.file("test/starlark/ext3.bzl", "load('//test/starlark:ext4.bzl', 'symbol4')");
      scratch.file("test/starlark/ext4.bzl", "load('//test/starlark:ext2.bzl', 'symbol2')");

      scratch.file(
          "test/starlark/BUILD",
          "load('//test/starlark:ext1.bzl', 'custom_rule')",
          "genrule(name = 'rule')");

      reporter.removeHandler(failFastHandler);
      BuildFileContainsErrorsException e =
          assertThrows(
              BuildFileContainsErrorsException.class, () -> getTarget("//test/starlark:rule"));
      assertThat(e)
          .hasMessageThat()
          .contains(
              "Starlark load cycle: [//test/starlark:ext2.bzl, "
                  + "//test/starlark:ext3.bzl, //test/starlark:ext4.bzl]");
    }
  }

  @Test
  public void testUnhashableInDictForbidden() throws Exception {
    scratch.file("test/extension.bzl", "y = [] in {}");

    scratch.file("test/BUILD", "load('//test:extension.bzl', 'y')", "cc_library(name = 'r')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:r");
    assertContainsEvent("unhashable type: 'list'");
  }

  @Test
  public void testDictGetUnhashableForbidden() throws Exception {
    scratch.file("test/extension.bzl", "y = {}.get({})");

    scratch.file("test/BUILD", "load('//test:extension.bzl', 'y')", "cc_library(name = 'r')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:r");
    assertContainsEvent("unhashable type: 'dict'");
  }

  @Test
  public void testUnknownStringEscapesForbidden() throws Exception {
    setBuildLanguageOptions("--incompatible_restrict_string_escapes=true");

    scratch.file("test/extension.bzl", "y = \"\\z\"");

    scratch.file("test/BUILD", "load('//test:extension.bzl', 'y')", "cc_library(name = 'r')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:r");
    assertContainsEvent("invalid escape sequence: \\z");
  }

  @Test
  public void testUnknownStringEscapes() throws Exception {
    setBuildLanguageOptions("--incompatible_restrict_string_escapes=false");

    scratch.file("test/extension.bzl", "y = \"\\z\"");

    scratch.file("test/BUILD", "load('//test:extension.bzl', 'y')", "cc_library(name = 'r')");

    getConfiguredTarget("//test:r");
  }

  @Test
  public void testSplitEmptySeparatorForbidden() throws Exception {
    scratch.file("test/extension.bzl", "y = 'abc'.split('')");

    scratch.file("test/BUILD", "load('//test:extension.bzl', 'y')", "cc_library(name = 'r')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:r");
    assertContainsEvent("Empty separator");
  }

  @Test
  public void testIdentifierAssignmentFromOuterScope2() throws Exception {
    scratch.file(
        "test/extension.bzl",
        "a = [1, 2, 3]",
        "def f(): a[0] = 9",
        "y = f()",
        "fail() if a[0] != 9 else None");

    scratch.file("test/BUILD", "load('//test:extension.bzl', 'y')", "cc_library(name = 'r')");

    getConfiguredTarget("//test:r");
  }

  @Test
  public void testIdentifierAssignmentFromOuterScopeForbidden() throws Exception {
    scratch.file("test/extension.bzl", "a = []", "def f(): a += [1]", "y = f()");

    scratch.file("test/BUILD", "load('//test:extension.bzl', 'y')", "cc_library(name = 'r')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:r");
    assertContainsEvent("local variable 'a' is referenced before assignment");
  }

  @Test
  public void testHashFrozenListForbidden() throws Exception {
    scratch.file("test/extension.bzl", "y = []");

    scratch.file(
        "test/BUILD", "load('//test:extension.bzl', 'y')", "{y: 1}", "cc_library(name = 'r')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:r");
    assertContainsEvent("unhashable type: 'list'");
  }

  @Test
  public void testHashFrozenDeepMutableForbidden() throws Exception {
    scratch.file("test/extension.bzl", "y = {}");

    scratch.file(
        "test/BUILD",
        "load('//test:extension.bzl', 'y')",
        "{('a', (y,), True): None}",
        "cc_library(name = 'r')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test:r");
    assertContainsEvent("unhashable type: 'dict'");
  }

  @Test
  public void testNoOutputsError() throws Exception {
    scratch.file(
        "test/starlark/test_rule.bzl",
        "def _my_rule_impl(ctx):",
        "  ctx.actions.run_shell(outputs=[], command='foo')",
        "my_rule = rule(implementation = _my_rule_impl, executable = True)");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:test_rule.bzl', 'my_rule')",
        "my_rule(name = 'target')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test/starlark:target");
    assertContainsEvent("param 'outputs' may not be empty");
  }

  @Test
  public void testDeclareFileInvalidDirectory_withSibling() throws Exception {
    scratch.file("test/dep/test_file.txt", "Test file");

    scratch.file("test/dep/BUILD", "exports_files(['test_file.txt'])");

    scratch.file(
        "test/starlark/test_rule.bzl",
        "def _my_rule_impl(ctx):",
        "  exe = ctx.actions.declare_file('exe', sibling = ctx.file.dep)",
        "  ctx.actions.run_shell(outputs=[exe], command=['touch', 'exe'])",
        "  return []",
        "my_rule = rule(implementation = _my_rule_impl,",
        "    attrs = {'dep': attr.label(allow_single_file = True)})");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:test_rule.bzl', 'my_rule')",
        "my_rule(name = 'target', dep = '//test/dep:test_file.txt')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//test/starlark:target");
    assertContainsEvent(
        "the output artifact 'test/dep/exe' is not under package directory "
            + "'test/starlark' for target '//test/starlark:target'");
  }

  @Test
  public void testDeclareFileInvalidDirectory_noSibling() throws Exception {
    scratch.file("test/dep/test_file.txt", "Test file");

    scratch.file("test/dep/BUILD", "exports_files(['test_file.txt'])");

    scratch.file(
        "test/starlark/test_rule.bzl",
        "def _my_rule_impl(ctx):",
        "  exe = ctx.actions.declare_file('/foo/exe')",
        "  ctx.actions.run_shell(outputs=[exe], command=['touch', 'exe'])",
        "  return []",
        "my_rule = rule(implementation = _my_rule_impl,",
        "    attrs = {'dep': attr.label(allow_single_file = True)})");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:test_rule.bzl', 'my_rule')",
        "my_rule(name = 'target', dep = '//test/dep:test_file.txt')");

    reporter.removeHandler(failFastHandler);
    assertThat(getConfiguredTarget("//test/starlark:target")).isNull();
    assertContainsEvent(
        "the output artifact '/foo/exe' is not under package directory "
            + "'test/starlark' for target '//test/starlark:target'");
  }

  @Test
  public void testDeclareDirectoryInvalidParent_withSibling() throws Exception {
    scratch.file("test/dep/test_file.txt", "Test file");

    scratch.file("test/dep/BUILD", "exports_files(['test_file.txt'])");

    scratch.file(
        "test/starlark/test_rule.bzl",
        "def _my_rule_impl(ctx):",
        "  exe = ctx.actions.declare_directory('/foo/exe', sibling = ctx.file.dep)",
        "  ctx.actions.run_shell(outputs=[exe], command=['touch', 'exe'])",
        "  return []",
        "my_rule = rule(implementation = _my_rule_impl,",
        "    attrs = {'dep': attr.label(allow_single_file = True)})");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:test_rule.bzl', 'my_rule')",
        "my_rule(name = 'target', dep = '//test/dep:test_file.txt')");

    reporter.removeHandler(failFastHandler);
    assertThat(getConfiguredTarget("//test/starlark:target")).isNull();
    assertContainsEvent(
        "the output directory '/foo/exe' is not under package directory "
            + "'test/starlark' for target '//test/starlark:target'");
  }

  @Test
  public void testDeclareDirectoryInvalidParent_noSibling() throws Exception {
    scratch.file("test/dep/test_file.txt", "Test file");

    scratch.file("test/dep/BUILD", "exports_files(['test_file.txt'])");

    scratch.file(
        "test/starlark/test_rule.bzl",
        "def _my_rule_impl(ctx):",
        "  exe = ctx.actions.declare_directory('/foo/exe')",
        "  ctx.actions.run_shell(outputs=[exe], command=['touch', 'exe'])",
        "  return []",
        "my_rule = rule(implementation = _my_rule_impl,",
        "    attrs = {'dep': attr.label(allow_single_file = True)})");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:test_rule.bzl', 'my_rule')",
        "my_rule(name = 'target', dep = '//test/dep:test_file.txt')");

    reporter.removeHandler(failFastHandler);
    assertThat(getConfiguredTarget("//test/starlark:target")).isNull();
    assertContainsEvent(
        "the output directory '/foo/exe' is not under package directory "
            + "'test/starlark' for target '//test/starlark:target'");
  }

  @Test
  public void testLegacyProvider_addCanonicalLegacyKeyAndModernKey() throws Exception {
    setBuildLanguageOptions("--incompatible_disallow_struct_provider_syntax=false");
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  return struct(foo = apple_common.new_objc_provider(linkopt=depset(['foo'])))",
        "",
        "custom_rule = rule(implementation = custom_rule_impl)");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'test')");

    ConfiguredTarget target = getConfiguredTarget("//test/starlark:test");

    ObjcProvider providerFromModernKey = target.get(ObjcProvider.STARLARK_CONSTRUCTOR);
    ObjcProvider providerFromObjc = (ObjcProvider) target.get("objc");
    ObjcProvider providerFromFoo = (ObjcProvider) target.get("foo");

    // The modern key and the canonical legacy key "objc" are set to the one available ObjcProvider.
    assertThat(providerFromModernKey.get(ObjcProvider.LINKOPT).toList()).containsExactly("foo");
    assertThat(providerFromObjc.get(ObjcProvider.LINKOPT).toList()).containsExactly("foo");
    assertThat(providerFromFoo.get(ObjcProvider.LINKOPT).toList()).containsExactly("foo");
  }

  @Test
  public void testLegacyProvider_dontAutomaticallyAddKeysAlreadyPresent() throws Exception {
    setBuildLanguageOptions("--incompatible_disallow_struct_provider_syntax=false");
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  return struct(providers = [apple_common.new_objc_provider(linkopt=depset(['prov']))],",
        "       bah = apple_common.new_objc_provider(linkopt=depset(['bah'])),",
        "       objc = apple_common.new_objc_provider(linkopt=depset(['objc'])))",
        "",
        "custom_rule = rule(implementation = custom_rule_impl)");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'test')");

    ConfiguredTarget target = getConfiguredTarget("//test/starlark:test");

    ObjcProvider providerFromModernKey = target.get(ObjcProvider.STARLARK_CONSTRUCTOR);
    ObjcProvider providerFromObjc = (ObjcProvider) target.get("objc");
    ObjcProvider providerFromBah = (ObjcProvider) target.get("bah");

    assertThat(providerFromModernKey.get(ObjcProvider.LINKOPT).toList()).containsExactly("prov");
    assertThat(providerFromObjc.get(ObjcProvider.LINKOPT).toList()).containsExactly("objc");
    assertThat(providerFromBah.get(ObjcProvider.LINKOPT).toList()).containsExactly("bah");
  }

  @Test
  public void testLegacyProvider_firstNoncanonicalKeyBecomesCanonical() throws Exception {
    setBuildLanguageOptions("--incompatible_disallow_struct_provider_syntax=false");
    scratch.file(
        "test/starlark/extension.bzl",
        "def custom_rule_impl(ctx):",
        "  return struct(providers = [apple_common.new_objc_provider(linkopt=depset(['prov']))],",
        "       foo = apple_common.new_objc_provider(linkopt=depset(['foo'])),",
        "       bar = apple_common.new_objc_provider(linkopt=depset(['bar'])))",
        "",
        "custom_rule = rule(implementation = custom_rule_impl)");

    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'custom_rule')",
        "",
        "custom_rule(name = 'test')");

    ConfiguredTarget target = getConfiguredTarget("//test/starlark:test");

    ObjcProvider providerFromModernKey = target.get(ObjcProvider.STARLARK_CONSTRUCTOR);
    ObjcProvider providerFromObjc = (ObjcProvider) target.get("objc");
    ObjcProvider providerFromFoo = (ObjcProvider) target.get("foo");
    ObjcProvider providerFromBar = (ObjcProvider) target.get("bar");

    assertThat(providerFromModernKey.get(ObjcProvider.LINKOPT).toList()).containsExactly("prov");
    // The first defined provider is set to the legacy "objc" key.
    assertThat(providerFromObjc.get(ObjcProvider.LINKOPT).toList()).containsExactly("foo");
    assertThat(providerFromFoo.get(ObjcProvider.LINKOPT).toList()).containsExactly("foo");
    assertThat(providerFromBar.get(ObjcProvider.LINKOPT).toList()).containsExactly("bar");
  }

  @Test
  public void testCustomMallocUnset() throws Exception {
    setUpCustomMallocRule();
    ConfiguredTarget target = getConfiguredTarget("//test/starlark:malloc");
    StructImpl provider = getMyInfoFromTarget(target);
    Object customMalloc = provider.getValue("malloc");
    assertThat(customMalloc).isInstanceOf(NoneType.class);
  }

  @Test
  public void testCustomMallocSet() throws Exception {
    setUpCustomMallocRule();
    useConfiguration("--custom_malloc=//base:system_malloc");
    ConfiguredTarget target = getConfiguredTarget("//test/starlark:malloc");
    StructImpl provider = getMyInfoFromTarget(target);
    RuleConfiguredTarget customMalloc = provider.getValue("malloc", RuleConfiguredTarget.class);
    assertThat(customMalloc.getLabel().getCanonicalForm()).isEqualTo("//base:system_malloc");
  }

  private void setUpCustomMallocRule() throws IOException {
    scratch.overwriteFile("base/BUILD", "cc_library(name = 'system_malloc')");
    scratch.file(
        "test/starlark/extension.bzl",
        "load('//myinfo:myinfo.bzl', 'MyInfo')",
        "",
        "def _malloc_rule_impl(ctx):",
        "  return [MyInfo(malloc = ctx.attr._custom_malloc)]",
        "",
        "malloc_rule = rule(",
        "    implementation = _malloc_rule_impl,",
        "    attrs = {",
        "        '_custom_malloc': attr.label(",
        "            default = configuration_field(",
        "                fragment = 'cpp',",
        "                name = 'custom_malloc',",
        "            ),",
        "            providers = [CcInfo],",
        "        ),",
        "    }",
        ")");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:extension.bzl', 'malloc_rule')",
        "",
        "malloc_rule(name = 'malloc')");
  }

  // Test for an interesting situation for the inlining implementation's attempt to process
  // subsequent load statements even when an earlier one has a missing Skyframe dep.
  @Test
  public void bzlFileWithErrorsLoadedThroughMultipleLoadPathsWithTheLatterOneHavingMissingDeps()
      throws Exception {
    scratch.file("test/starlark/error.bzl", "nope");
    scratch.file("test/starlark/ok.bzl", "ok = 42");
    scratch.file(
        "test/starlark/loads-error-and-has-missing-deps.bzl",
        "load('//test/starlark:error.bzl', 'doesntmatter')",
        "load('//test/starlark:ok.bzl', 'ok')");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:error.bzl', 'doesntmatter')",
        "load('//test/starlark:loads-error-and-has-missing-deps.bzl', 'doesntmatter')");

    reporter.removeHandler(failFastHandler);
    BuildFileContainsErrorsException e =
        assertThrows(
            BuildFileContainsErrorsException.class, () -> getTarget("//test/starlark:BUILD"));
    assertThat(e).hasMessageThat().contains("Extension 'test/starlark/error.bzl' has errors");
  }

  // Test for an interesting situation for the inlining implementation's attempt to process
  // subsequent load statements even when an earlier one has a missing Skyframe dep.
  @Test
  public void bzlFileWithErrorsLoadedThroughMultipleLoadPathsWithTheLatterOneNotHavingMissingDeps()
      throws Exception {
    scratch.file("test/starlark/error.bzl", "nope");
    scratch.file("test/starlark/ok.bzl", "ok = 42");
    scratch.file(
        "test/starlark/loads-error-and-has-missing-deps.bzl",
        "load('//test/starlark:error.bzl', 'doesntmatter')",
        "load('//test/starlark:ok.bzl', 'ok')");
    scratch.file(
        "test/starlark/BUILD",
        "load('//test/starlark:ok.bzl', 'ok')",
        "load('//test/starlark:error.bzl', 'doesntmatter')",
        "load('//test/starlark:loads-error-and-has-missing-deps.bzl', 'doesntmatter')");

    reporter.removeHandler(failFastHandler);
    BuildFileContainsErrorsException e =
        assertThrows(
            BuildFileContainsErrorsException.class, () -> getTarget("//test/starlark:BUILD"));
    assertThat(e).hasMessageThat().contains("Extension 'test/starlark/error.bzl' has errors");
  }
}
