// Copyright 2015 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.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.Streams.stream;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.devtools.build.lib.analysis.OutputGroupInfo.INTERNAL_SUFFIX;
import static java.util.stream.Collectors.toList;
import static org.junit.Assert.assertThrows;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.Action;
import com.google.devtools.build.lib.actions.ActionOwner;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.AnalysisResult;
import com.google.devtools.build.lib.analysis.AspectValue;
import com.google.devtools.build.lib.analysis.ConfiguredAspect;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.OutputGroupInfo;
import com.google.devtools.build.lib.analysis.ViewCreationFailedException;
import com.google.devtools.build.lib.analysis.util.AnalysisTestCase;
import com.google.devtools.build.lib.analysis.util.TestAspects;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.TargetParsingException;
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.AspectClass;
import com.google.devtools.build.lib.packages.AspectDefinition;
import com.google.devtools.build.lib.packages.StarlarkAspectClass;
import com.google.devtools.build.lib.packages.StarlarkProvider;
import com.google.devtools.build.lib.packages.StructImpl;
import com.google.devtools.build.lib.rules.cpp.CppConfiguration;
import com.google.devtools.build.lib.rules.java.JavaConfiguration;
import com.google.devtools.build.lib.server.FailureDetails.Analysis;
import com.google.devtools.build.lib.server.FailureDetails.Analysis.Code;
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
import com.google.devtools.build.lib.skyframe.AspectKeyCreator.AspectKey;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey;
import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
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.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** Tests for Starlark aspects */
@RunWith(JUnit4.class)
public class StarlarkDefinedAspectsTest extends AnalysisTestCase {
  protected boolean keepGoing() {
    return false;
  }

  @Test
  public void simpleAspect() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   print('This aspect does nothing')",
        "   return struct()",
        "MyAspect = aspect(implementation=_impl)");
    scratch.file("test/BUILD", "java_library(name = 'xxx',)");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
    assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
    assertThat(getAspectDescriptions(analysisResult))
        .containsExactly("//test:aspect.bzl%MyAspect(//test:xxx)");
  }

  @Test
  public void aspectWithSingleDeclaredProvider() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "foo = provider()",
        "def _impl(target, ctx):",
        "   return foo()",
        "MyAspect = aspect(implementation=_impl)");
    scratch.file("test/BUILD", "java_library(name = 'xxx',)");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
    assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
    assertThat(getAspectDescriptions(analysisResult))
        .containsExactly("//test:aspect.bzl%MyAspect(//test:xxx)");
    ConfiguredAspect configuredAspect =
        Iterables.getOnlyElement(analysisResult.getAspectsMap().values());

    StarlarkProvider.Key fooKey =
        new StarlarkProvider.Key(Label.parseCanonical("//test:aspect.bzl"), "foo");

    assertThat(configuredAspect.get(fooKey).getProvider().getKey()).isEqualTo(fooKey);
  }

  @Test
  public void aspectWithDeclaredProviders() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "foo = provider()",
        "bar = provider()",
        "def _impl(target, ctx):",
        "   return [foo(), bar()]",
        "MyAspect = aspect(implementation=_impl)");
    scratch.file("test/BUILD", "java_library(name = 'xxx',)");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
    assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
    assertThat(getAspectDescriptions(analysisResult))
        .containsExactly("//test:aspect.bzl%MyAspect(//test:xxx)");
    ConfiguredAspect configuredAspect =
        Iterables.getOnlyElement(analysisResult.getAspectsMap().values());

    StarlarkProvider.Key fooKey =
        new StarlarkProvider.Key(Label.parseCanonical("//test:aspect.bzl"), "foo");
    StarlarkProvider.Key barKey =
        new StarlarkProvider.Key(Label.parseCanonical("//test:aspect.bzl"), "bar");

    assertThat(configuredAspect.get(fooKey).getProvider().getKey()).isEqualTo(fooKey);
    assertThat(configuredAspect.get(barKey).getProvider().getKey()).isEqualTo(barKey);
  }

  @Test
  public void aspectWithDeclaredProvidersInAStruct() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "foo = provider()",
        "bar = provider()",
        "def _impl(target, ctx):",
        "   return struct(foobar='foobar', providers=[foo(), bar()])",
        "MyAspect = aspect(implementation=_impl)");
    scratch.file("test/BUILD", "java_library(name = 'xxx',)");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
    assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
    assertThat(getAspectDescriptions(analysisResult))
        .containsExactly("//test:aspect.bzl%MyAspect(//test:xxx)");

    ConfiguredAspect configuredAspect =
        Iterables.getOnlyElement(analysisResult.getAspectsMap().values());

    StarlarkProvider.Key fooKey =
        new StarlarkProvider.Key(Label.parseCanonical("//test:aspect.bzl"), "foo");
    StarlarkProvider.Key barKey =
        new StarlarkProvider.Key(Label.parseCanonical("//test:aspect.bzl"), "bar");

    assertThat(configuredAspect.get(fooKey).getProvider().getKey()).isEqualTo(fooKey);
    assertThat(configuredAspect.get(barKey).getProvider().getKey()).isEqualTo(barKey);
  }

  private static Iterable<String> getAspectDescriptions(AnalysisResult analysisResult) {
    return Iterables.transform(
        analysisResult.getAspectsMap().keySet(),
        aspectKey ->
            String.format("%s(%s)", aspectKey.getAspectClass().getName(), aspectKey.getLabel()));
  }

  @Test
  public void aspectCommandLineLabel() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   print('This aspect does nothing')",
        "   return struct()",
        "MyAspect = aspect(implementation=_impl)");
    scratch.file("test/BUILD", "java_library(name = 'xxx',)");

    AnalysisResult analysisResult =
        update(ImmutableList.of("//test:aspect.bzl%MyAspect"), "//test:xxx");
    assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
    assertThat(getAspectDescriptions(analysisResult))
        .containsExactly("//test:aspect.bzl%MyAspect(//test:xxx)");
  }

  @Test
  public void aspectCommandLineRepoLabel() throws Exception {
    scratch.overwriteFile(
        "WORKSPACE",
        scratch.readFile("WORKSPACE"),
        "local_repository(name='local', path='local/repo')");
    scratch.file("local/repo/WORKSPACE");
    scratch.file(
        "local/repo/aspect.bzl",
        "def _impl(target, ctx):",
        "   print('This aspect does nothing')",
        "   return struct()",
        "MyAspect = aspect(implementation=_impl)");
    scratch.file("local/repo/BUILD");

    scratch.file("test/BUILD", "java_library(name = 'xxx',)");

    AnalysisResult analysisResult =
        update(ImmutableList.of("@local//:aspect.bzl%MyAspect"), "//test:xxx");
    assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
    assertThat(getAspectDescriptions(analysisResult))
        .containsExactly("@@local//:aspect.bzl%MyAspect(//test:xxx)");
  }

  private static Iterable<String> getLabelsToBuild(AnalysisResult analysisResult) {
    return Iterables.transform(
        analysisResult.getTargetsToBuild(),
        configuredTarget -> configuredTarget.getLabel().toString());
  }

  @Test
  public void aspectAllowsFragmentsToBeSpecified() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   print('This aspect does nothing')",
        "   return struct()",
        "MyAspect = aspect(implementation=_impl, fragments=['java'])");
    scratch.file("test/BUILD", "java_library(name = 'xxx',)");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");

    AspectKey key = Iterables.getOnlyElement(analysisResult.getAspectsMap().keySet());
    AspectValue aspectValue = (AspectValue) skyframeExecutor.getEvaluator().getExistingValue(key);
    AspectDefinition aspectDefinition = aspectValue.getAspect().getDefinition();
    assertThat(
            aspectDefinition
                .getConfigurationFragmentPolicy()
                .isLegalConfigurationFragment(JavaConfiguration.class))
        .isTrue();
    assertThat(
            aspectDefinition
                .getConfigurationFragmentPolicy()
                .isLegalConfigurationFragment(CppConfiguration.class))
        .isFalse();
  }

  @Test
  public void aspectPropagating() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   s = depset([target.label], transitive = [i.target_labels for i in ctx.rule.attr.deps])",
        "   c = depset([ctx.rule.kind], transitive = [i.rule_kinds for i in ctx.rule.attr.deps])",
        "   return struct(target_labels = s, rule_kinds = c)",
        "",
        "MyAspect = aspect(",
        "   implementation=_impl,",
        "   attr_aspects=['deps'],",
        ")");
    scratch.file(
        "test/BUILD",
        "java_library(",
        "     name = 'yyy',",
        ")",
        "java_library(",
        "     name = 'xxx',",
        "     srcs = ['A.java'],",
        "     deps = [':yyy'],",
        ")");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
    assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
    ConfiguredAspect configuredAspect = analysisResult.getAspectsMap().values().iterator().next();
    assertThat(configuredAspect).isNotNull();
    Object names = configuredAspect.get("target_labels");
    assertThat(names).isInstanceOf(Depset.class);
    assertThat(
            Iterables.transform(
                ((Depset) names).toList(),
                o -> {
                  assertThat(o).isInstanceOf(Label.class);
                  return o.toString();
                }))
        .containsExactly("//test:xxx", "//test:yyy");
    Object ruleKinds = configuredAspect.get("rule_kinds");
    assertThat(ruleKinds).isInstanceOf(Depset.class);
    assertThat(((Depset) ruleKinds).toList()).containsExactly("java_library");
  }

  @Test
  public void aspectsPropagatingForDefaultAndImplicit() throws Exception {
    useConfiguration(
        "--experimental_builtins_injection_override=+cc_library",
        "--incompatible_enable_cc_toolchain_resolution");
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   s = []",
        "   c = []",
        "   a = ctx.rule.attr",
        "   if getattr(a, '_defaultattr', None):",
        "       s += [a._defaultattr.target_labels]",
        "       c += [a._defaultattr.rule_kinds]",
        "   if getattr(a, '_cc_toolchain', None):",
        "       s += [a._cc_toolchain.target_labels]",
        "       c += [a._cc_toolchain.rule_kinds]",
        "   return struct(",
        "       target_labels = depset([target.label], transitive = s),",
        "       rule_kinds = depset([ctx.rule.kind], transitive = c))",
        "",
        "def _rule_impl(ctx):",
        "   pass",
        "",
        "my_rule = rule(implementation = _rule_impl,",
        "   attrs = { '_defaultattr' : attr.label(default = Label('//test:xxx')) },",
        ")",
        "MyAspect = aspect(",
        "   implementation=_impl,",
        "   attr_aspects=['_defaultattr', '_cc_toolchain'],",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:aspect.bzl', 'my_rule')",
        "cc_library(",
        "     name = 'xxx',",
        ")",
        "my_rule(",
        "     name = 'yyy',",
        ")");
    AnalysisResult analysisResult =
        update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:yyy");
    ConfiguredAspect configuredAspect = analysisResult.getAspectsMap().values().iterator().next();
    assertThat(configuredAspect).isNotNull();
    Object nameSet = configuredAspect.get("target_labels");
    ImmutableList<String> names =
        ImmutableList.copyOf(
            Iterables.transform(
                ((Depset) nameSet).toList(),
                o -> {
                  assertThat(o).isInstanceOf(Label.class);
                  return ((Label) o).getName();
                }));

    assertThat(names).containsAtLeast("xxx", "yyy");
    // 3rd is the C++ toolchain; its name changes between Blaze and Bazel.
    assertThat(names).hasSize(3);
  }

  @Test
  public void aspectsDirOnMergedTargets() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct(aspect_provider = 'data')",
        "",
        "p = provider()",
        "MyAspect = aspect(implementation=_impl)",
        "def _rule_impl(ctx):",
        "   if ctx.attr.dep:",
        "      return [p(dir = dir(ctx.attr.dep))]",
        "   return [p()]",
        "",
        "my_rule = rule(implementation = _rule_impl,",
        "   attrs = { 'dep' : attr.label(aspects = [MyAspect]) },",
        ")");
    StarlarkProvider.Key providerKey =
        new StarlarkProvider.Key(Label.parseCanonicalUnchecked("//test:aspect.bzl"), "p");
    scratch.file(
        "test/BUILD",
        "load('//test:aspect.bzl', 'my_rule')",
        "my_rule(name = 'xxx',)",
        "my_rule(name = 'yyy', dep = ':xxx')");
    AnalysisResult analysisResult = update("//test:yyy");
    ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());

    StructImpl names = (StructImpl) target.get(providerKey);
    assertThat((Iterable<?>) names.getValue("dir"))
        .containsExactly(
            "actions",
            "aspect_provider",
            "data_runfiles",
            "default_runfiles",
            "files",
            "files_to_run",
            "label",
            "output_groups");
  }

  @Test
  public void aspectWithOutputGroupsDeclaredProvider() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   f = target[OutputGroupInfo]._hidden_top_level" + INTERNAL_SUFFIX,
        "   return [OutputGroupInfo(my_result = f)]",
        "",
        "MyAspect = aspect(",
        "   implementation=_impl,",
        ")");
    scratch.file(
        "test/BUILD", "java_library(", "     name = 'xxx',", "     srcs = ['A.java'],", ")");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
    assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
    ConfiguredAspect configuredAspect = analysisResult.getAspectsMap().values().iterator().next();
    OutputGroupInfo outputGroupInfo = OutputGroupInfo.get(configuredAspect);

    assertThat(outputGroupInfo).isNotNull();
    NestedSet<Artifact> names = outputGroupInfo.getOutputGroup("my_result");
    assertThat(names.toList()).isNotEmpty();

    // Configuration of the true Artifact may diverge slightly (e.g. be trimmed) causing owners to
    // also diverge so just compare paths instead of the whole Artifact.
    ImmutableList<Path> paths =
        names.toList().stream().map(Artifact::getPath).collect(toImmutableList());
    ImmutableList<Path> expectedPaths =
        OutputGroupInfo.get(getConfiguredTarget("//test:xxx"))
            .getOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL)
            .toList()
            .stream()
            .map(Artifact::getPath)
            .collect(toImmutableList());
    assertThat(paths).containsExactlyElementsIn(expectedPaths);
  }

  @Test
  public void aspectWithOutputGroupsAsListDeclaredProvider() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   g = target[OutputGroupInfo]._hidden_top_level" + INTERNAL_SUFFIX,
        "   return [OutputGroupInfo(my_result=g.to_list())]",
        "",
        "MyAspect = aspect(",
        "   implementation=_impl,",
        ")");
    scratch.file(
        "test/BUILD", "java_library(", "     name = 'xxx',", "     srcs = ['A.java'],", ")");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
    assertThat(
            Iterables.transform(
                analysisResult.getTargetsToBuild(),
                configuredTarget -> configuredTarget.getLabel().toString()))
        .containsExactly("//test:xxx");
    ConfiguredAspect configuredAspect = analysisResult.getAspectsMap().values().iterator().next();
    OutputGroupInfo outputGroupInfo = OutputGroupInfo.get(configuredAspect);
    assertThat(outputGroupInfo).isNotNull();
    NestedSet<Artifact> names = outputGroupInfo.getOutputGroup("my_result");
    assertThat(names.toList()).isNotEmpty();

    // Configuration of the true Artifact may diverge slightly (e.g. be trimmed) causing owners to
    // also diverge so just compare paths instead of the whole Artifact.
    ImmutableList<Path> paths =
        names.toList().stream().map(Artifact::getPath).collect(toImmutableList());
    ImmutableList<Path> expectedPaths =
        OutputGroupInfo.get(getConfiguredTarget("//test:xxx"))
            .getOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL)
            .toList()
            .stream()
            .map(Artifact::getPath)
            .collect(toImmutableList());
    assertThat(paths).containsExactlyElementsIn(expectedPaths);
  }

  @Test
  public void aspectsFromStarlarkRules() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _aspect_impl(target, ctx):",
        "   s = depset([target.label], transitive = [i.target_labels for i in ctx.rule.attr.deps])",
        "   return struct(target_labels = s)",
        "",
        "def _rule_impl(ctx):",
        "   s = depset(transitive = [i.target_labels for i in ctx.attr.attr])",
        "   return struct(rule_deps = s)",
        "",
        "MyAspect = aspect(",
        "   implementation=_aspect_impl,",
        "   attr_aspects=['deps'],",
        ")",
        "my_rule = rule(",
        "   implementation=_rule_impl,",
        "   attrs = { 'attr' : ",
        "             attr.label_list(mandatory=True, allow_files=True, aspects = [MyAspect]) },",
        ")");

    scratch.file(
        "test/BUILD",
        "load('//test:aspect.bzl', 'my_rule')",
        "java_library(",
        "     name = 'yyy',",
        ")",
        "my_rule(",
        "     name = 'xxx',",
        "     attr = [':yyy'],",
        ")");

    AnalysisResult analysisResult = update("//test:xxx");
    assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
    ConfiguredTarget target = analysisResult.getTargetsToBuild().iterator().next();
    Object names = target.get("rule_deps");
    assertThat(names).isInstanceOf(Depset.class);
    assertThat(
            Iterables.transform(
                ((Depset) names).toList(),
                o -> {
                  assertThat(o).isInstanceOf(Label.class);
                  return o.toString();
                }))
        .containsExactly("//test:yyy");
  }

  @Test
  public void aspectsNonExported() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _aspect_impl(target, ctx):",
        "   return []",
        "",
        "def _rule_impl(ctx):",
        "   pass",
        "",
        "def mk_aspect():",
        "   return aspect(implementation=_aspect_impl)",
        "my_rule = rule(",
        "   implementation=_rule_impl,",
        "   attrs = { 'attr' : attr.label_list(aspects = [mk_aspect()]) },", // line 11
        ")");

    scratch.file(
        "test/BUILD",
        "load('//test:aspect.bzl', 'my_rule')",
        "java_library(",
        "     name = 'yyy',",
        ")",
        "my_rule(",
        "     name = 'xxx',",
        "     attr = [':yyy'],",
        ")");

    reporter.removeHandler(failFastHandler);
    try {
      AnalysisResult analysisResult = update("//test:xxx");
      assertThat(keepGoing()).isTrue();
      assertThat(analysisResult.hasError()).isTrue();
    } catch (ViewCreationFailedException | TargetParsingException e) {
      // expected
    }

    // attr.label_list() fails, stack=[<toplevel>@rules.bzl:11:38, label_list:<builtin>]
    assertContainsEvent("File \"/workspace/test/aspect.bzl\", line 11, column 38, in <toplevel>");
    assertContainsEvent(
        "Error in label_list: Aspects should be top-level values in extension files that define"
            + " them.");
  }

  @Test
  @SuppressWarnings("EmptyCatchBlock")
  public void aspectReturnsNonExportedProvider() throws Exception {
    scratch.file(
        "test/inc.bzl",
        "a = aspect(implementation = lambda target, ctx: [provider()()])",
        "r = rule(",
        "  implementation = lambda ctx: [],",
        "  attrs = {'a': attr.label_list(aspects = [a])})");
    scratch.file(
        "test/BUILD",
        "load('//test:inc.bzl', 'r')",
        "java_library(name = 'j')",
        "r(name = 'test', a = [':j'])");

    reporter.removeHandler(failFastHandler);
    try {
      update("//test");
      /* reached if --keep_going=true */
    } catch (ViewCreationFailedException unused) {
      /* reached if --keep_going=false */
    }
    assertContainsEvent(
        "aspect function returned an instance of a provider "
            + "(defined at /workspace/test/inc.bzl:1:58) that is not a global");
  }

  @Test
  public void providerNonExported() throws Exception {
    scratch.file(
        "test/rule.bzl",
        "def mk_provider():",
        "   return provider()",
        "def _rule_impl(ctx):",
        "   pass",
        "my_rule = rule(",
        "   implementation=_rule_impl,",
        "   attrs = { 'attr' : attr.label_list(providers = [mk_provider()]) },", // line 7
        ")");

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

    reporter.removeHandler(failFastHandler);
    try {
      AnalysisResult analysisResult = update("//test:xxx");
      assertThat(keepGoing()).isTrue();
      assertThat(analysisResult.hasError()).isTrue();
    } catch (ViewCreationFailedException | TargetParsingException e) {
      // expected
    }

    // attr.label_list() fails, stack=[<toplevel>@rules.bzl:7:38, label_list:<builtin>]
    assertContainsEvent("File \"/workspace/test/rule.bzl\", line 7, column 38, in <toplevel>");
    assertContainsEvent(
        "Error in label_list: Providers should be top-level values in extension files that define"
            + " them.");
  }

  @Test
  public void aspectOnLabelAttr() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _aspect_impl(target, ctx):",
        "   return struct(aspect_data='foo')",
        "",
        "def _rule_impl(ctx):",
        "   return struct(data=ctx.attr.attr.aspect_data)",
        "",
        "MyAspect = aspect(",
        "   implementation=_aspect_impl,",
        ")",
        "my_rule = rule(",
        "   implementation=_rule_impl,",
        "   attrs = { 'attr' : ",
        "             attr.label(aspects = [MyAspect]) },",
        ")");

    scratch.file(
        "test/BUILD",
        "load('//test:aspect.bzl', 'my_rule')",
        "java_library(",
        "     name = 'yyy',",
        ")",
        "my_rule(",
        "     name = 'xxx',",
        "     attr = ':yyy',",
        ")");

    AnalysisResult analysisResult = update("//test:xxx");
    ConfiguredTarget target = analysisResult.getTargetsToBuild().iterator().next();
    Object value = target.get("data");
    assertThat(value).isEqualTo("foo");
  }

  @Test
  public void labelKeyedStringDictAllowsAspects() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _aspect_impl(target, ctx):",
        "   return struct(aspect_data=target.label.name)",
        "",
        "def _rule_impl(ctx):",
        "   return struct(",
        "       data=','.join(['{}:{}'.format(dep.aspect_data, val)",
        "                      for dep, val in ctx.attr.attr.items()]))",
        "",
        "MyAspect = aspect(",
        "   implementation=_aspect_impl,",
        ")",
        "my_rule = rule(",
        "   implementation=_rule_impl,",
        "   attrs = { 'attr' : ",
        "             attr.label_keyed_string_dict(aspects = [MyAspect]) },",
        ")");

    scratch.file(
        "test/BUILD",
        "load('//test:aspect.bzl', 'my_rule')",
        "java_library(",
        "     name = 'yyy',",
        ")",
        "my_rule(",
        "     name = 'xxx',",
        "     attr = {':yyy': 'zzz'},",
        ")");

    AnalysisResult analysisResult = update("//test:xxx");
    ConfiguredTarget target = analysisResult.getTargetsToBuild().iterator().next();
    Object value = target.get("data");
    assertThat(value).isEqualTo("yyy:zzz");
  }

  @Test
  public void aspectsDoNotAttachToFiles() throws Exception {
    FileSystemUtils.appendIsoLatin1(
        scratch.resolve("WORKSPACE"), "bind(name = 'yyy', actual = '//test:zzz.jar')");
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "",
        "MyAspect = aspect(",
        "   implementation=_impl,",
        "   attr_aspects=['deps'],",
        ")");
    scratch.file("test/zzz.jar");
    scratch.file(
        "test/BUILD",
        "exports_files(['zzz.jar'])",
        "java_library(",
        "     name = 'xxx',",
        "     srcs = ['A.java'],",
        "     deps = ['//external:yyy'],",
        ")");

    AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
    assertThat(result.hasError()).isFalse();
  }

  @Test
  public void aspectsDoNotAttachToTopLevelFiles() throws Exception {
    FileSystemUtils.appendIsoLatin1(
        scratch.resolve("WORKSPACE"), "bind(name = 'yyy', actual = '//test:zzz.jar')");
    scratch.file(
        "test/aspect.bzl",
        "p = provider()",
        "def _impl(target, ctx):",
        "   return [p()]",
        "",
        "MyAspect = aspect(",
        "   implementation=_impl,",
        "   attr_aspects=['deps'],",
        ")");
    scratch.file("test/zzz.jar");
    scratch.file(
        "test/BUILD",
        "exports_files(['zzz.jar'])",
        "java_library(",
        "     name = 'xxx',",
        "     srcs = ['A.java'],",
        "     deps = ['//external:yyy'],",
        ")");

    AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:zzz.jar");
    assertThat(result.hasError()).isFalse();
    assertThat(
            Iterables.getOnlyElement(result.getAspectsMap().values())
                .getProviders()
                .getProviderCount())
        .isEqualTo(0);
  }

  @Test
  public void aspectFailingExecution() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return 1 // 0",
        "",
        "MyAspect = aspect(implementation=_impl)");
    scratch.file("test/BUILD", "java_library(name = 'xxx',)");

    reporter.removeHandler(failFastHandler);
    try {
      AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
      assertThat(keepGoing()).isTrue();
      assertThat(result.hasError()).isTrue();
    } catch (ViewCreationFailedException e) {
      // expect to fail.
    }
    // Stack doesn't include source lines because we haven't told EvalException
    // how to read from scratch.
    assertContainsEvent(
        "ERROR /workspace/test/BUILD:1:13: in "
            + "//test:aspect.bzl%MyAspect aspect on java_library rule //test:xxx: \n"
            + "Traceback (most recent call last):\n"
            + "\tFile \"/workspace/test/aspect.bzl\", line 2, column 13, in _impl\n"
            + "\t\treturn 1 // 0\n"
            + "Error: integer division by zero");
  }

  @Test
  public void aspectFailingReturnsNotAStruct() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return 0",
        "",
        "MyAspect = aspect(implementation=_impl)");
    scratch.file("test/BUILD", "java_library(name = 'xxx',)");

    reporter.removeHandler(failFastHandler);
    try {
      AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
      assertThat(keepGoing()).isTrue();
      assertThat(result.hasError()).isTrue();
    } catch (ViewCreationFailedException e) {
      // expect to fail.
    }
    assertContainsEvent(
        "Aspect implementation should return a struct, a list, or a provider "
            + "instance, but got int");
  }

  @Test
  public void aspectFailingOrphanArtifacts() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "  ctx.actions.declare_file('missing_in_action.txt')",
        "  return struct()",
        "",
        "MyAspect = aspect(implementation=_impl)");
    scratch.file("test/BUILD", "java_library(name = 'xxx',)");

    reporter.removeHandler(failFastHandler);
    try {
      AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
      assertThat(keepGoing()).isTrue();
      assertThat(result.hasError()).isTrue();
    } catch (ViewCreationFailedException e) {
      // expect to fail.
    }
    assertContainsEvent(
        "ERROR /workspace/test/BUILD:1:13: in "
            + "//test:aspect.bzl%MyAspect aspect on java_library rule //test:xxx: \n"
            + "The following files have no generating action:\n"
            + "test/missing_in_action.txt");
  }

  @Test
  public void aspectSkippingOrphanArtifactsWithLocation() throws Exception {
    scratch.file(
        "simple/print.bzl",
        "def _print_expanded_location_impl(target, ctx):",
        "    return struct(result=ctx.expand_location(ctx.rule.attr.cmd, []))",
        "",
        "print_expanded_location = aspect(",
        "    implementation = _print_expanded_location_impl,",
        ")");
    scratch.file(
        "simple/BUILD",
        "filegroup(",
        "    name = \"files\",",
        "    srcs = [\"afile\"],",
        ")",
        "",
        "genrule(",
        "    name = \"concat_all_files\",",
        "    srcs = [\":files\"],",
        "    outs = [\"concatenated.txt\"],",
        "    cmd = \"$(location :files)\"",
        ")");

    reporter.removeHandler(failFastHandler);
    AnalysisResult analysisResult =
        update(
            ImmutableList.of("//simple:print.bzl%print_expanded_location"),
            "//simple:concat_all_files");
    assertThat(analysisResult.hasError()).isFalse();
    ConfiguredAspect configuredAspect = analysisResult.getAspectsMap().values().iterator().next();
    String result = (String) configuredAspect.get("result");

    assertThat(result).isEqualTo("simple/afile");
  }

  @Test
  public void topLevelAspectIsNotAnAspect() throws Exception {
    scratch.file("test/aspect.bzl", "MyAspect = 4");
    scratch.file("test/BUILD", "java_library(name = 'xxx')");

    reporter.removeHandler(failFastHandler);
    try {
      AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
      assertThat(keepGoing()).isTrue();
      assertThat(result.hasError()).isTrue();
    } catch (ViewCreationFailedException e) {
      // expect to fail.
    }
    assertContainsEvent("MyAspect from //test:aspect.bzl is not an aspect");
  }

  @Test
  public void duplicateOutputGroups() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "  f = ctx.actions.declare_file('f.txt')",
        "  ctx.actions.write(f, 'f')",
        "  return struct(output_groups = { 'duplicate' : depset([f]) })",
        "",
        "MyAspect = aspect(implementation=_impl)",
        "def _rule_impl(ctx):",
        "  g = ctx.actions.declare_file('g.txt')",
        "  ctx.actions.write(g, 'g')",
        "  return struct(output_groups = { 'duplicate' : depset([g]) })",
        "my_rule = rule(_rule_impl)",
        "def _noop(ctx):",
        "  pass",
        "rbase = rule(_noop, attrs = { 'dep' : attr.label(aspects = [MyAspect]) })");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'my_rule', 'rbase')",
        "my_rule(name = 'xxx')",
        "rbase(name = 'yyy', dep = ':xxx')");

    reporter.removeHandler(failFastHandler);
    try {
      AnalysisResult result = update("//test:yyy");
      assertThat(keepGoing()).isTrue();
      assertThat(result.hasError()).isTrue();
    } catch (ViewCreationFailedException e) {
      // expect to fail.
    }
    assertContainsEvent("ERROR /workspace/test/BUILD:3:6: Output group duplicate provided twice");
  }

  @Test
  public void outputGroupsFromOneAspect() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _a1_impl(target, ctx):",
        "  f = ctx.actions.declare_file(target.label.name + '_a1.txt')",
        "  ctx.actions.write(f, 'f')",
        "  return struct(output_groups = { 'a1_group' : depset([f]) })",
        "",
        "a1 = aspect(implementation=_a1_impl, attr_aspects = ['dep'])",
        "def _rule_impl(ctx):",
        "  if not ctx.attr.dep:",
        "     return struct()",
        "  og = {k:ctx.attr.dep.output_groups[k] for k in ctx.attr.dep.output_groups}",
        "  return struct(output_groups = og)",
        "my_rule1 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a1]) })");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'my_rule1')",
        "my_rule1(name = 'base')",
        "my_rule1(name = 'xxx', dep = ':base')");

    AnalysisResult analysisResult = update("//test:xxx");
    OutputGroupInfo outputGroupInfo =
        OutputGroupInfo.get(Iterables.getOnlyElement(analysisResult.getTargetsToBuild()));
    assertThat(getOutputGroupContents(outputGroupInfo, "a1_group"))
        .containsExactly("test/base_a1.txt");
  }

  @Test
  public void outputGroupsDeclaredProviderFromOneAspect() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _a1_impl(target, ctx):",
        "  f = ctx.actions.declare_file(target.label.name + '_a1.txt')",
        "  ctx.actions.write(f, 'f')",
        "  return [OutputGroupInfo(a1_group = depset([f]))]",
        "",
        "a1 = aspect(implementation=_a1_impl, attr_aspects = ['dep'])",
        "def _rule_impl(ctx):",
        "  if not ctx.attr.dep:",
        "     return struct()",
        "  return [OutputGroupInfo(a1_group = ctx.attr.dep[OutputGroupInfo].a1_group)]",
        "my_rule1 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a1]) })");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'my_rule1')",
        "my_rule1(name = 'base')",
        "my_rule1(name = 'xxx', dep = ':base')");

    AnalysisResult analysisResult = update("//test:xxx");
    OutputGroupInfo outputGroupInfo =
        OutputGroupInfo.get(Iterables.getOnlyElement(analysisResult.getTargetsToBuild()));
    assertThat(getOutputGroupContents(outputGroupInfo, "a1_group"))
        .containsExactly("test/base_a1.txt");
  }

  @Test
  public void outputGroupsFromTwoAspects() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _a1_impl(target, ctx):",
        "  f = ctx.actions.declare_file(target.label.name + '_a1.txt')",
        "  ctx.actions.write(f, 'f')",
        "  return struct(output_groups = { 'a1_group' : depset([f]) })",
        "",
        "a1 = aspect(implementation=_a1_impl, attr_aspects = ['dep'])",
        "def _rule_impl(ctx):",
        "  if not ctx.attr.dep:",
        "     return struct()",
        "  og = {k:ctx.attr.dep.output_groups[k] for k in ctx.attr.dep.output_groups}",
        "  return struct(output_groups = og)",
        "my_rule1 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a1]) })",
        "def _a2_impl(target, ctx):",
        "  g = ctx.actions.declare_file(target.label.name + '_a2.txt')",
        "  ctx.actions.write(g, 'f')",
        "  return struct(output_groups = { 'a2_group' : depset([g]) })",
        "",
        "a2 = aspect(implementation=_a2_impl, attr_aspects = ['dep'])",
        "my_rule2 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a2]) })");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'my_rule1', 'my_rule2')",
        "my_rule1(name = 'base')",
        "my_rule1(name = 'xxx', dep = ':base')",
        "my_rule2(name = 'yyy', dep = ':xxx')");

    AnalysisResult analysisResult = update("//test:yyy");
    OutputGroupInfo outputGroupInfo =
        OutputGroupInfo.get(Iterables.getOnlyElement(analysisResult.getTargetsToBuild()));
    assertThat(getOutputGroupContents(outputGroupInfo, "a1_group"))
        .containsExactly("test/base_a1.txt");
    assertThat(getOutputGroupContents(outputGroupInfo, "a2_group"))
        .containsExactly("test/xxx_a2.txt");
  }

  @Test
  public void outputGroupsDeclaredProvidersFromTwoAspects() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _a1_impl(target, ctx):",
        "  f = ctx.actions.declare_file(target.label.name + '_a1.txt')",
        "  ctx.actions.write(f, 'f')",
        "  return [OutputGroupInfo(a1_group = depset([f]))]",
        "",
        "a1 = aspect(implementation=_a1_impl, attr_aspects = ['dep'])",
        "def _rule_impl(ctx):",
        "  if not ctx.attr.dep:",
        "     return struct()",
        "  og = dict()",
        "  dep_og = ctx.attr.dep[OutputGroupInfo]",
        "  if hasattr(dep_og, 'a1_group'):",
        "     og['a1_group'] = dep_og.a1_group",
        "  if hasattr(dep_og, 'a2_group'):",
        "     og['a2_group'] = dep_og.a2_group",
        "  return [OutputGroupInfo(**og)]",
        "my_rule1 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a1]) })",
        "def _a2_impl(target, ctx):",
        "  g = ctx.actions.declare_file(target.label.name + '_a2.txt')",
        "  ctx.actions.write(g, 'f')",
        "  return [OutputGroupInfo(a2_group = depset([g]))]",
        "",
        "a2 = aspect(implementation=_a2_impl, attr_aspects = ['dep'])",
        "my_rule2 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a2]) })");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'my_rule1', 'my_rule2')",
        "my_rule1(name = 'base')",
        "my_rule1(name = 'xxx', dep = ':base')",
        "my_rule2(name = 'yyy', dep = ':xxx')");

    AnalysisResult analysisResult = update("//test:yyy");
    OutputGroupInfo outputGroupInfo =
        OutputGroupInfo.get(Iterables.getOnlyElement(analysisResult.getTargetsToBuild()));
    assertThat(getOutputGroupContents(outputGroupInfo, "a1_group"))
        .containsExactly("test/base_a1.txt");
    assertThat(getOutputGroupContents(outputGroupInfo, "a2_group"))
        .containsExactly("test/xxx_a2.txt");
  }

  @Test
  public void duplicateOutputGroupsFromTwoAspects() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _a1_impl(target, ctx):",
        "  f = ctx.actions.declare_file(target.label.name + '_a1.txt')",
        "  ctx.actions.write(f, 'f')",
        "  return struct(output_groups = { 'a1_group' : depset([f]) })",
        "",
        "a1 = aspect(implementation=_a1_impl, attr_aspects = ['dep'])",
        "def _rule_impl(ctx):",
        "  if not ctx.attr.dep:",
        "     return struct()",
        "  og = {k:ctx.attr.dep.output_groups[k] for k in ctx.attr.dep.output_groups}",
        "  return struct(output_groups = og)",
        "my_rule1 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a1]) })",
        "def _a2_impl(target, ctx):",
        "  g = ctx.actions.declare_file(target.label.name + '_a2.txt')",
        "  ctx.actions.write(g, 'f')",
        "  return struct(output_groups = { 'a1_group' : depset([g]) })",
        "",
        "a2 = aspect(implementation=_a2_impl, attr_aspects = ['dep'])",
        "my_rule2 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a2]) })");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'my_rule1', 'my_rule2')",
        "my_rule1(name = 'base')",
        "my_rule1(name = 'xxx', dep = ':base')",
        "my_rule2(name = 'yyy', dep = ':xxx')");

    reporter.removeHandler(failFastHandler);
    try {
      AnalysisResult analysisResult = update("//test:yyy");
      assertThat(analysisResult.hasError()).isTrue();
      assertThat(keepGoing()).isTrue();
    } catch (ViewCreationFailedException e) {
      // expected.
    }
    assertContainsEvent("ERROR /workspace/test/BUILD:3:9: Output group a1_group provided twice");
  }

  private static Iterable<String> getOutputGroupContents(
      OutputGroupInfo outputGroupInfo, String groupName) {
    return Iterables.transform(
        outputGroupInfo.getOutputGroup(groupName).toList(), Artifact::getRootRelativePathString);
  }

  @Test
  public void duplicateStarlarkProviders() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "  return struct(duplicate = 'x')",
        "",
        "MyAspect = aspect(implementation=_impl)",
        "def _rule_impl(ctx):",
        "  return struct(duplicate = 'y')",
        "my_rule = rule(_rule_impl)",
        "def _noop(ctx):",
        "  pass",
        "rbase = rule(_noop, attrs = { 'dep' : attr.label(aspects = [MyAspect]) })");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'my_rule', 'rbase')",
        "my_rule(name = 'xxx')",
        "rbase(name = 'yyy', dep = ':xxx')");

    reporter.removeHandler(failFastHandler);
    try {
      AnalysisResult result = update("//test:yyy");
      assertThat(keepGoing()).isTrue();
      assertThat(result.hasError()).isTrue();
    } catch (ViewCreationFailedException e) {
      // expect to fail.
    }
    assertContainsEvent("ERROR /workspace/test/BUILD:3:6: Provider duplicate provided twice");
  }

  @Test
  public void topLevelAspectDoesNotExist() throws Exception {
    scratch.file("test/aspect.bzl", "");
    scratch.file("test/BUILD", "java_library(name = 'xxx')");

    reporter.removeHandler(failFastHandler);
    try {
      AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
      assertThat(keepGoing()).isTrue();
      assertThat(result.hasError()).isTrue();
    } catch (ViewCreationFailedException e) {
      // expect to fail.
    }
    assertContainsEvent("MyAspect is not exported from //test:aspect.bzl");
  }

  @Test
  public void topLevelAspectDoesNotExist2() throws Exception {
    scratch.file("test/BUILD", "java_library(name = 'xxx')");

    reporter.removeHandler(failFastHandler);
    try {
      AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
      assertThat(keepGoing()).isTrue();
      assertThat(result.hasError()).isTrue();
    } catch (ViewCreationFailedException e) {
      // expect to fail.
    }
    assertContainsEvent("cannot load '//test:aspect.bzl': no such file");
  }

  @Test
  public void topLevelAspectDoesNotExistNoBuildFile() throws Exception {
    scratch.file("test/BUILD", "java_library(name = 'xxx')");

    reporter.removeHandler(failFastHandler);
    try {
      AnalysisResult result = update(ImmutableList.of("foo/aspect.bzl%MyAspect"), "//test:xxx");
      assertThat(keepGoing()).isTrue();
      assertThat(result.hasError()).isTrue();
    } catch (ViewCreationFailedException e) {
      // expect to fail.
    }
    assertContainsEvent("Every .bzl file must have a corresponding package");
  }

  /**
   * Tests that a loading-level error (missing bzl file) is properly transformed by the configured
   * target that requested the relevant package, and doesn't bubble up to a higher configured
   * target/aspect that wasn't expecting a loading-level error. The complication is that the
   * configured target that depends directly on the error tries to do configuration resolution after
   * noticing the error, and configuration resolution is interruptible, so it is interrupted. It
   * needs to then throw the error, rather than the interruption.
   *
   * <p>This test covers error propagation up to both the configured target that depends on the one
   * in error, as well as the aspect on that configured target, since the error goes through both.
   */
  @Test
  public void aspectBaseConfiguredTargetTransitivelyDependingOnPackageInError() throws Exception {
    setRulesAndAspectsAvailableInTests(ImmutableList.of(), ImmutableList.of());
    scratch.overwriteFile(
        "tools/allowlists/function_transition_allowlist/BUILD",
        "package_group(name = 'function_transition_allowlist', packages = ['//aspect/...'])");
    scratch.file(
        "aspect/aspect.bzl",
        "def _setting_impl(ctx):",
        "  return []",
        "",
        "string_flag = rule(",
        "  implementation = _setting_impl,",
        "  build_setting = config.string(flag=True),",
        ")",
        "",
        "def _rule_impl(ctx):",
        "  pass",
        "",
        "def _transition_impl(settings, attr):",
        "  return {'//aspect:formation': 'mesa'}",
        "",
        "formation_transition = transition(",
        "  implementation = _transition_impl,",
        "  inputs = ['//aspect:formation'],",
        "  outputs = ['//aspect:formation'],",
        ")",
        "",
        "def _aspect_impl(target, ctx):",
        "  pass",
        "",
        "myaspect = aspect(implementation = _aspect_impl)",
        "",
        "cfgrule = rule(",
        "  implementation = _rule_impl,",
        "  attrs = {",
        "    '_allowlist_function_transition': attr.label(",
        "      default = '//tools/allowlists/function_transition_allowlist',",
        "    ),",
        "    'to': attr.label(),",
        "    'innocent': attr.label(cfg = formation_transition),",
        "  }",
        ")");
    scratch.file(
        "aspect/BUILD",
        "load('aspect.bzl', 'cfgrule', 'string_flag')",
        "string_flag(name = 'formation', build_setting_default = 'canyon')",
        "sh_library(name = 'innocent')",
        "cfgrule(name = 'top', to = '//baz:baz', innocent = ':innocent')");
    scratch.file("bar/BUILD", "sh_library(name = 'bar', deps = ['//baz:baz'])");
    scratch.file(
        "baz/BUILD", "load('//baz/subdir:missing.bzl', 'sym')", "sh_library(name = 'baz')");
    scratch.file("baz/subdir/missing.bzl");
    reporter.removeHandler(failFastHandler);
    try {
      AnalysisResult result =
          update(ImmutableList.of("//aspect:aspect.bzl%myaspect"), "//aspect:top");
      assertThat(keepGoing()).isTrue();
      assertThat(result.hasError()).isTrue();
    } catch (ViewCreationFailedException e) {
      // expect to fail.
      assertThat(keepGoing()).isFalse();
    }
    assertContainsEvent("Label '//baz/subdir:missing.bzl' is invalid");
  }

  @Test
  public void aspectParametersUncovered() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "def _rule_impl(ctx):",
        "   return struct()",
        "MyAspectUncovered = aspect(",
        "    implementation=_impl,",
        "    attrs = { 'my_attr' : attr.string(values=['aaa']) },",
        ")",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'deps' : attr.label_list(aspects=[MyAspectUncovered]) },",
        ")");
    scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name = 'xxx')");

    reporter.removeHandler(failFastHandler);
    try {
      AnalysisResult result = update(ImmutableList.of(), "//test:xxx");
      assertThat(keepGoing()).isTrue();
      assertThat(result.hasError()).isTrue();
    } catch (Exception e) {
      // expect to fail.
    }
    assertContainsEvent( // "ERROR /workspace/test/aspect.bzl:9:11: "
        "Aspect //test:aspect.bzl%MyAspectUncovered requires rule my_rule to specify attribute "
            + "'my_attr' with type string.");
  }

  @Test
  public void aspectParametersTypeMismatch() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "def _rule_impl(ctx):",
        "   return struct()",
        "MyAspectMismatch = aspect(",
        "    implementation=_impl,",
        "    attrs = { 'my_attr' : attr.string(values=['aaa']) },",
        ")",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'deps' : attr.label_list(aspects=[MyAspectMismatch]),",
        "              'my_attr' : attr.int() },",
        ")");
    scratch.file(
        "test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name = 'xxx', my_attr = 4)");

    reporter.removeHandler(failFastHandler);
    try {
      AnalysisResult result = update(ImmutableList.of(), "//test:xxx");
      assertThat(keepGoing()).isTrue();
      assertThat(result.hasError()).isTrue();
    } catch (Exception e) {
      // expect to fail.
    }
    assertContainsEvent(
        "Aspect //test:aspect.bzl%MyAspectMismatch requires rule my_rule to specify attribute "
            + "'my_attr' with type string.");
  }

  @Test
  public void aspectParametersDontSupportSelect() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "def _rule_impl(ctx):",
        "   return struct()",
        "MyAspectMismatch = aspect(",
        "    implementation=_impl,",
        "    attrs = { 'my_attr' : attr.string(values=['aaa']) },",
        ")",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'deps' : attr.label_list(aspects=[MyAspectMismatch]),",
        "              'my_attr' : attr.string() },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:aspect.bzl', 'my_rule')",
        "my_rule(name = 'xxx', my_attr = select({'//conditions:default': 'foo'}))");

    reporter.removeHandler(failFastHandler);
    try {
      AnalysisResult result = update(ImmutableList.of(), "//test:xxx");
      assertThat(keepGoing()).isTrue();
      assertThat(result.hasError()).isTrue();
    } catch (Exception e) {
      // expect to fail.
    }
    assertContainsEvent(
        "//test:xxx: attribute 'my_attr' has a select() and aspect "
            + "//test:aspect.bzl%MyAspectMismatch also declares '//test:xxx'. Aspect attributes "
            + "don't currently support select().");
  }

  @Test
  public void aspectParametersBadDefault() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "def _rule_impl(ctx):",
        "   return struct()",
        "MyAspectBadDefault = aspect(",
        "    implementation=_impl,",
        "    attrs = { 'my_attr' : attr.string(values=['a'], default='b') },",
        ")",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'deps' : attr.label_list(aspects=[MyAspectBadDefault]) },", // line 11
        ")");
    scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name = 'xxx')");

    reporter.removeHandler(failFastHandler);
    try {
      AnalysisResult result = update(ImmutableList.of(), "//test:xxx");
      assertThat(keepGoing()).isTrue();
      assertThat(result.hasError()).isTrue();
    } catch (Exception e) {
      // expect to fail.
    }

    // aspect fails, stack = [<toplevel>@:5:28, aspect@<builtin>]
    assertContainsEvent("File \"/workspace/test/aspect.bzl\", line 5, column 28, in <toplevel>");
    assertContainsEvent(
        "Error in aspect: Aspect parameter attribute 'my_attr' has a bad default value: has to be"
            + " one of 'a' instead of 'b'");
  }

  @Test
  public void aspectParametersBadValue() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "def _rule_impl(ctx):",
        "   return struct()",
        "MyAspectBadValue = aspect(",
        "    implementation=_impl,",
        "    attrs = { 'my_attr' : attr.string(values=['a']) },",
        ")",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'deps' : attr.label_list(aspects=[MyAspectBadValue]),",
        "              'my_attr' : attr.string() },",
        ")");
    scratch.file(
        "test/BUILD", //
        "load('//test:aspect.bzl', 'my_rule')",
        "my_rule(name = 'xxx', my_attr='b')");

    reporter.removeHandler(failFastHandler);
    try {
      AnalysisResult result = update(ImmutableList.of(), "//test:xxx");
      assertThat(keepGoing()).isTrue();
      assertThat(result.hasError()).isTrue();
    } catch (Exception e) {
      // expect to fail.
    }
    assertContainsEvent(
        "ERROR /workspace/test/BUILD:2:8: //test:xxx: invalid value in 'my_attr' "
            + "attribute: has to be one of 'a' instead of 'b'");
  }

  @Test
  public void aspectParameters() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "def _rule_impl(ctx):",
        "   return struct()",
        "MyAspect = aspect(",
        "    implementation=_impl,",
        "    attrs = { 'my_attr' : attr.string(values=['aaa']) },",
        ")",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'deps' : attr.label_list(aspects=[MyAspect]),",
        "              'my_attr' : attr.string() },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:aspect.bzl', 'my_rule')",
        "my_rule(name = 'xxx', my_attr = 'aaa')");

    AnalysisResult result = update(ImmutableList.of(), "//test:xxx");
    assertThat(result.hasError()).isFalse();
  }

  @Test
  public void aspectParametersConfigurationField() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "def _rule_impl(ctx):",
        "   return struct()",
        "MyAspect = aspect(",
        "    implementation=_impl,",
        "    attrs = { '_my_attr' : attr.label(default=",
        "             configuration_field(fragment='cpp', name = 'cc_toolchain')) },",
        ")",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'deps' : attr.label_list(aspects=[MyAspect]) },",
        ")");
    scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name = 'xxx')");

    AnalysisResult result = update(ImmutableList.of(), "//test:xxx");
    assertThat(result.hasError()).isFalse();
  }

  @Test
  public void aspectParameterComputedDefault() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "def _rule_impl(ctx):",
        "   return struct()",
        "def _defattr():",
        "   return Label('//foo/bar:baz')",
        "MyAspect = aspect(",
        "    implementation=_impl,",
        "    attrs = { '_extra' : attr.label(default = _defattr) }",
        ")",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'deps' : attr.label_list(aspects=[MyAspect]) },",
        ")");
    scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name = 'xxx')");
    reporter.removeHandler(failFastHandler);

    if (keepGoing()) {
      AnalysisResult result = update("//test:xxx");
      assertThat(result.hasError()).isTrue();
    } else {
      assertThrows(TargetParsingException.class, () -> update("//test:xxx"));
    }
    assertContainsEvent(
        "Aspect attribute '_extra' (label) with computed default value is unsupported.");
  }

  @Test
  public void aspectParametersOptional() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "def _rule_impl(ctx):",
        "   return struct()",
        "MyAspectOptParam = aspect(",
        "    implementation=_impl,",
        "    attrs = { 'my_attr' : attr.string(values=['aaa'], default='aaa') },",
        ")",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'deps' : attr.label_list(aspects=[MyAspectOptParam]) },",
        ")");
    scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name = 'xxx')");

    AnalysisResult result = update(ImmutableList.of(), "//test:xxx");
    assertThat(result.hasError()).isFalse();
  }

  @Test
  public void aspectParametersOptionalOverride() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   if (ctx.attr.my_attr == 'a'):",
        "       fail('Rule is not overriding default, still has value ' + ctx.attr.my_attr)",
        "   return struct()",
        "def _rule_impl(ctx):",
        "   return struct()",
        "MyAspectOptOverride = aspect(",
        "    implementation=_impl,",
        "    attrs = { 'my_attr' : attr.string(values=['a', 'b'], default='a') },",
        ")",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'deps' : attr.label_list(aspects=[MyAspectOptOverride]),",
        "              'my_attr' : attr.string() },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:aspect.bzl', 'my_rule')",
        "my_rule(name = 'xxx', my_attr = 'b')");

    AnalysisResult result = update(ImmutableList.of(), "//test:xxx");
    assertThat(result.hasError()).isFalse();
  }

  @Test
  public void testMultipleExecutablesInTarget() throws Exception {
    scratch.file(
        "foo/extension.bzl",
        "def _aspect_impl(target, ctx):",
        "   return struct()",
        "my_aspect = aspect(_aspect_impl)",
        "def _main_rule_impl(ctx):",
        "   pass",
        "my_rule = rule(_main_rule_impl,",
        "   attrs = { ",
        "      'exe1' : attr.label(executable = True, allow_files = True, cfg = 'exec'),",
        "      'exe2' : attr.label(executable = True, allow_files = True, cfg = 'exec'),",
        "   },",
        ")");

    scratch.file("foo/tool.sh", "#!/bin/bash");
    scratch.file(
        "foo/BUILD",
        "load(':extension.bzl',  'my_rule')",
        "my_rule(name = 'main', exe1 = ':tool.sh', exe2 = ':tool.sh')");
    AnalysisResult analysisResultOfRule = update(ImmutableList.of(), "//foo:main");
    assertThat(analysisResultOfRule.hasError()).isFalse();

    AnalysisResult analysisResultOfAspect =
        update(ImmutableList.of("/foo/extension.bzl%my_aspect"), "//foo:main");
    assertThat(analysisResultOfAspect.hasError()).isFalse();
  }

  @Test
  public void aspectFragmentAccessSuccess() throws Exception {
    analyzeConfiguredTargetForAspectFragment("ctx.fragments.java.strict_java_deps", "'java'", "");
    assertNoEvents();
  }

  @Test
  public void aspectFragmentAccessError() {
    reporter.removeHandler(failFastHandler);
    assertThrows(
        ViewCreationFailedException.class,
        () ->
            analyzeConfiguredTargetForAspectFragment(
                "ctx.fragments.java.strict_java_deps", "'cpp'", "'cpp'"));
    assertContainsEvent(
        "//test:aspect.bzl%MyAspect aspect on my_rule has to declare 'java' as a "
            + "required fragment in order to access it. Please update the 'fragments' argument of "
            + "the rule definition (for example: fragments = [\"java\"])");
  }

  private void analyzeConfiguredTargetForAspectFragment(
      String fullFieldName, String fragments, String ruleFragments) throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _aspect_impl(target, ctx):",
        "   return struct(result = str(" + fullFieldName + "))",
        "",
        "def _rule_impl(ctx):",
        "   return struct(stuff = '...')",
        "",
        "MyAspect = aspect(",
        "   implementation=_aspect_impl,",
        "   attr_aspects=['deps'],",
        "   fragments=[" + fragments + "],",
        ")",
        "my_rule = rule(",
        "   implementation=_rule_impl,",
        "   attrs = { 'attr' : ",
        "             attr.label_list(mandatory=True, allow_files=True, aspects = [MyAspect]) },",
        "   fragments=[" + ruleFragments + "],",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:aspect.bzl', 'my_rule')",
        "exports_files(['zzz'])",
        "my_rule(",
        "     name = 'yyy',",
        "     attr = ['zzz'],",
        ")",
        "my_rule(",
        "     name = 'xxx',",
        "     attr = ['yyy'],",
        ")");

    AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
    if (result.hasError()) {
      assertThat(keepGoing()).isTrue();
      String errorMessage = "Analysis failed";
      throw new ViewCreationFailedException(
          errorMessage,
          FailureDetail.newBuilder()
              .setMessage(errorMessage)
              .setAnalysis(Analysis.newBuilder().setCode(Code.ANALYSIS_UNKNOWN))
              .build());
    }

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

  @Test
  public void invalidateAspectOnBzlFileChange() throws Exception {
    scratch.file("test/build_defs.bzl", aspectBzlFile("'deps'"));
    scratch.file(
        "test/BUILD",
        "load(':build_defs.bzl', 'repro', 'repro_no_aspect')",
        "repro_no_aspect(name = 'r0')",
        "repro_no_aspect(name = 'r1', deps = [':r0'])",
        "repro(name = 'r2', deps = [':r1'])");
    buildTargetAndCheckRuleInfo("@//test:r0", "@//test:r1");

    // Make aspect propagation list empty.
    scratch.overwriteFile("test/build_defs.bzl", aspectBzlFile(""));

    // The aspect should not propagate to //test:r0 anymore.
    buildTargetAndCheckRuleInfo("@//test:r1");
  }

  private void buildTargetAndCheckRuleInfo(String... expectedLabels) throws Exception {
    AnalysisResult result = update(ImmutableList.of(), "//test:r2");
    ConfiguredTarget configuredTarget = result.getTargetsToBuild().iterator().next();
    Depset ruleInfoValue = (Depset) configuredTarget.get("rule_info");
    assertThat(ruleInfoValue.getSet(String.class).toList())
        .containsExactlyElementsIn(expectedLabels);
  }

  private static String[] aspectBzlFile(String attrAspects) {
    return new String[] {
      "def _repro_aspect_impl(target, ctx):",
      "    s = depset([str(target.label)], transitive =",
      "      [d.aspect_info for d in ctx.rule.attr.deps if hasattr(d, 'aspect_info')])",
      "    return struct(aspect_info = s)",
      "",
      "_repro_aspect = aspect(",
      "    _repro_aspect_impl,",
      "    attr_aspects = [" + attrAspects + "],",
      ")",
      "",
      "def repro_impl(ctx):",
      "    s = depset(transitive = ",
      "      [d.aspect_info for d in ctx.attr.deps if hasattr(d, 'aspect_info')])",
      "    return struct(rule_info = s)",
      "",
      "def repro_no_aspect_impl(ctx):",
      "    pass",
      "",
      "repro_no_aspect = rule(implementation = repro_no_aspect_impl,",
      "             attrs = {",
      "                       'deps': attr.label_list(",
      "                             allow_files = True,",
      "                       )",
      "                      },",
      ")",
      "",
      "repro = rule(implementation = repro_impl,",
      "             attrs = {",
      "                       'deps': attr.label_list(",
      "                             allow_files = True,",
      "                             aspects = [_repro_aspect],",
      "                       )",
      "                      },",
      ")"
    };
  }

  @Test
  public void aspectOutputsToBinDirectory() throws Exception {
    scratch.file(
        "foo/extension.bzl",
        "def _aspect_impl(target, ctx):",
        "   file = ctx.actions.declare_file('aspect-output-' + target.label.name)",
        "   ctx.actions.write(file, 'data')",
        "   return struct(aspect_file = file)",
        "my_aspect = aspect(_aspect_impl)",
        "def _rule_impl(ctx):",
        "   pass",
        "rule_bin_out = rule(_rule_impl, output_to_genfiles=False)",
        "rule_gen_out = rule(_rule_impl, output_to_genfiles=True)",
        "def _main_rule_impl(ctx):",
        "   s = depset([d.aspect_file for d in ctx.attr.deps])",
        "   return struct(aspect_files = s)",
        "main_rule = rule(_main_rule_impl,",
        "   attrs = { 'deps' : attr.label_list(aspects = [my_aspect]) },",
        ")");

    scratch.file(
        "foo/BUILD",
        "load(':extension.bzl', 'rule_bin_out', 'rule_gen_out', 'main_rule')",
        "rule_bin_out(name = 'rbin')",
        "rule_gen_out(name = 'rgen')",
        "main_rule(name = 'main', deps = [':rbin', ':rgen'])");
    AnalysisResult analysisResult = update(ImmutableList.of(), "//foo:main");
    ConfiguredTarget target = analysisResult.getTargetsToBuild().iterator().next();
    NestedSet<Artifact> aspectFiles = ((Depset) target.get("aspect_files")).getSet(Artifact.class);
    assertThat(Iterables.transform(aspectFiles.toList(), Artifact::getFilename))
        .containsExactly("aspect-output-rbin", "aspect-output-rgen");
    for (Artifact aspectFile : aspectFiles.toList()) {
      String rootPath = aspectFile.getRoot().getExecPath().toString();
      assertWithMessage("Artifact %s should not be in genfiles", aspectFile)
          .that(rootPath)
          .doesNotContain("genfiles");
      assertWithMessage("Artifact %s should be in bin", aspectFile).that(rootPath).endsWith("bin");
    }
  }

  @Test
  public void toplevelAspectOnFile() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   print('This aspect does nothing')",
        "   return struct()",
        "MyAspect = aspect(implementation=_impl)");
    scratch.file("test/BUILD", "exports_files(['file.txt'])");
    scratch.file("test/file.txt", "");
    AnalysisResult analysisResult =
        update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:file.txt");
    assertThat(analysisResult.hasError()).isFalse();
    assertThat(
            Iterables.getOnlyElement(analysisResult.getAspectsMap().values())
                .getProviders()
                .getProviderCount())
        .isEqualTo(0);
  }

  @Test
  public void sharedAttributeDefinitionWithAspects() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _aspect_impl(target,ctx):",
        "  return struct()",
        "my_aspect = aspect(implementation = _aspect_impl)",
        "_ATTR = { 'deps' : attr.label_list(aspects = [my_aspect]) }",
        "def _dummy_impl(ctx):",
        "  pass",
        "r1 = rule(_dummy_impl, attrs =  _ATTR)",
        "r2 = rule(_dummy_impl, attrs =  _ATTR)");

    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'r1', 'r2')",
        "r1(name = 't1')",
        "r2(name = 't2', deps = [':t1'])");
    AnalysisResult analysisResult = update("//test:t2");
    assertThat(analysisResult.hasError()).isFalse();
  }

  @Test
  public void multipleAspects() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _aspect_impl(target,ctx):",
        "  return struct()",
        "my_aspect = aspect(implementation = _aspect_impl)",
        "def _dummy_impl(ctx):",
        "  pass",
        "r1 = rule(_dummy_impl, ",
        "          attrs = { 'deps' : attr.label_list(aspects = [my_aspect, my_aspect]) })");

    scratch.file("test/BUILD", "load(':aspect.bzl', 'r1')", "r1(name = 't1')");
    reporter.removeHandler(failFastHandler);
    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a TargetParsingException.
    if (keepGoing()) {
      AnalysisResult result = update("//test:r1");
      assertThat(result.hasError()).isTrue();
    } else {
      assertThrows(TargetParsingException.class, () -> update("//test:r1"));
    }
    assertContainsEvent("aspect //test:aspect.bzl%my_aspect added more than once");
  }

  @Test
  public void topLevelAspectsAndExtraActions() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _aspect_impl(target,ctx):",
        "  f = ctx.actions.declare_file('dummy.txt')",
        "  ctx.actions.run_shell(outputs = [f], command='echo xxx > $(location f)',",
        "                        mnemonic='AspectAction')",
        "  return struct()",
        "my_aspect = aspect(implementation = _aspect_impl)");
    scratch.file(
        "test/BUILD",
        "extra_action(",
        "    name = 'xa',",
        "    cmd = 'echo $(EXTRA_ACTION_FILE) > $(output file.xa)',",
        "    out_templates = ['file.xa'],",
        ")",
        "action_listener(",
        "    name = 'al',",
        "    mnemonics = [ 'AspectAction' ],",
        "    extra_actions = [ ':xa' ])",
        "java_library(name = 'xxx')");
    useConfiguration("--experimental_action_listener=//test:al");
    AnalysisResult analysisResult =
        update(ImmutableList.of("test/aspect.bzl%my_aspect"), "//test:xxx");
    assertThat(Iterables.transform(analysisResult.getArtifactsToBuild(), Artifact::getFilename))
        .contains("file.xa");
  }

  /** Regression test for b/137960630. */
  @Test
  public void topLevelAspectsAndExtraActionsWithConflict() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _aspect_impl(target, ctx):",
        "  f = ctx.actions.declare_file('dummy.txt')",
        "  ctx.actions.run_shell(outputs = [f], command='echo xxx > $(location f)',",
        "                        mnemonic='AspectAction')",
        "  return struct()",
        "my_aspect = aspect(implementation = _aspect_impl)");
    scratch.file(
        "test/BUILD",
        "extra_action(",
        "    name = 'xa',",
        "    cmd = 'echo $(EXTRA_ACTION_FILE) > $(output file.xa)',",
        "    out_templates = ['file.xa'],",
        ")",
        "action_listener(",
        "    name = 'al',",
        "    mnemonics = ['AspectAction'],",
        "    extra_actions = [':xa'],",
        ")",
        "java_library(name = 'xxx')",
        "java_library(name = 'yyy')");
    useConfiguration("--experimental_action_listener=//test:al");
    reporter.removeHandler(failFastHandler); // We expect an error.

    if (keepGoing()) {
      AnalysisResult result =
          update(ImmutableList.of("test/aspect.bzl%my_aspect"), "//test:xxx", "//test:yyy");
      assertThat(result.hasError()).isTrue();
    } else {
      assertThrows(
          ViewCreationFailedException.class,
          () -> update(ImmutableList.of("test/aspect.bzl%my_aspect"), "//test:xxx", "//test:yyy"));
    }
    assertContainsEvent(
        "file 'extra_actions/test/xa/test/file.xa' is generated by these conflicting actions");
  }

  @Test
  public void aspectsPropagatingToAllAttributes() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   s = depset([target.label], transitive =",
        "     [i.target_labels for i in ctx.rule.attr.runtime_deps]",
        "     if hasattr(ctx.rule.attr, 'runtime_deps') else [])",
        "   return struct(target_labels = s)",
        "",
        "MyAspect = aspect(",
        "    implementation=_impl,",
        "    attrs = { '_tool' : attr.label(default = Label('//test:tool')) },",
        "    attr_aspects=['*'],",
        ")");
    scratch.file(
        "test/BUILD",
        "java_library(",
        "    name = 'tool',",
        ")",
        "java_library(",
        "     name = 'bar',",
        "     runtime_deps = [':tool'],",
        ")",
        "java_library(",
        "     name = 'foo',",
        "     runtime_deps = [':bar'],",
        ")");
    AnalysisResult analysisResult =
        update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:foo");
    ConfiguredAspect configuredAspect = analysisResult.getAspectsMap().values().iterator().next();
    assertThat(configuredAspect).isNotNull();
    Object names = configuredAspect.get("target_labels");
    assertThat(names).isInstanceOf(Depset.class);
    assertThat(
            Iterables.transform(
                ((Depset) names).toList(),
                o -> {
                  assertThat(o).isInstanceOf(Label.class);
                  return ((Label) o).getName();
                }))
        .containsExactly("foo", "bar", "tool");
  }

  /** Simple straightforward linear aspects-on-aspects. */
  @Test
  public void aspectOnAspectLinear() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "a1p = provider()",
        "def _a1_impl(target,ctx):",
        "  return struct(a1p = a1p(text = 'random'))",
        "a1 = aspect(_a1_impl, attr_aspects = ['dep'], provides = ['a1p'])",
        "a2p = provider()",
        "def _a2_impl(target,ctx):",
        "  value = []",
        "  if hasattr(ctx.rule.attr.dep, 'a2p'):",
        "     value += ctx.rule.attr.dep.a2p.value",
        "  if hasattr(target, 'a1p'):",
        "     value.append(str(target.label) + str(ctx.aspect_ids) + '=yes')",
        "  else:",
        "     value.append(str(target.label) + str(ctx.aspect_ids) + '=no')",
        "  return struct(a2p = a2p(value = value))",
        "a2 = aspect(_a2_impl, attr_aspects = ['dep'], required_aspect_providers = ['a1p'])",
        "def _r1_impl(ctx):",
        "  pass",
        "def _r2_impl(ctx):",
        "  return struct(result = ctx.attr.dep.a2p.value)",
        "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
        "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'r1', 'r2')",
        "r1(name = 'r0')",
        "r1(name = 'r1', dep = ':r0')",
        "r2(name = 'r2', dep = ':r1')");
    AnalysisResult analysisResult = update("//test:r2");
    ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    Sequence<?> result = (Sequence<?>) target.get("result");

    // "yes" means that aspect a2 sees a1's providers.
    assertThat(result)
        .containsExactly(
            "@//test:r0[\"//test:aspect.bzl%a1\", \"//test:aspect.bzl%a2\"]=yes",
            "@//test:r1[\"//test:aspect.bzl%a2\"]=no");
  }

  /**
   * Diamond case. rule r1 depends or r0 with aspect a1. rule r2 depends or r0 with aspect a2. rule
   * rcollect depends on r1, r2 with aspect a3.
   *
   * <p>Aspect a3 should be applied twice to target r0: once in [a1, a3] sequence and once in [a2,
   * a3] sequence.
   */
  @Test
  public void aspectOnAspectDiamond() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _a1_impl(target,ctx):",
        "  return struct(a1p = 'text from a1')",
        "a1 = aspect(_a1_impl, attr_aspects = ['deps'], provides = ['a1p'])",
        "",
        "def _a2_impl(target,ctx):",
        "  return struct(a2p = 'text from a2')",
        "a2 = aspect(_a2_impl, attr_aspects = ['deps'], provides = ['a2p'])",
        "",
        "def _a3_impl(target,ctx):",
        "  value = []",
        "  f = ctx.actions.declare_file('a3.out')",
        "  ctx.actions.write(f, 'text')",
        "  for dep in ctx.rule.attr.deps:",
        "     if hasattr(dep, 'a3p'):",
        "         value += dep.a3p",
        "  s = str(target.label) + str(ctx.aspect_ids) + '='",
        "  if hasattr(target, 'a1p'):",
        "     s += 'a1p'",
        "  if hasattr(target, 'a2p'):",
        "     s += 'a2p'",
        "  value.append(s)",
        "  return struct(a3p = value)",
        "a3 = aspect(_a3_impl, attr_aspects = ['deps'],",
        "            required_aspect_providers = [['a1p'], ['a2p']])",
        "def _r1_impl(ctx):",
        "  pass",
        "def _rcollect_impl(ctx):",
        "  value = []",
        "  for dep in ctx.attr.deps:",
        "     if hasattr(dep, 'a3p'):",
        "         value += dep.a3p",
        "  return struct(result = value)",
        "r1 = rule(_r1_impl, attrs = { 'deps' : attr.label_list(aspects = [a1])})",
        "r2 = rule(_r1_impl, attrs = { 'deps' : attr.label_list(aspects = [a2])})",
        "rcollect = rule(_rcollect_impl, attrs = { 'deps' : attr.label_list(aspects = [a3])})");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'r1', 'r2', 'rcollect')",
        "r1(name = 'r0')",
        "r1(name = 'r1', deps = [':r0'])",
        "r2(name = 'r2', deps = [':r0'])",
        "rcollect(name = 'rcollect', deps = [':r1', ':r2'])");
    AnalysisResult analysisResult = update("//test:rcollect");
    ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    Sequence<?> result = (Sequence<?>) target.get("result");
    assertThat(result)
        .containsExactly(
            "@//test:r0[\"//test:aspect.bzl%a1\", \"//test:aspect.bzl%a3\"]=a1p",
            "@//test:r1[\"//test:aspect.bzl%a3\"]=",
            "@//test:r0[\"//test:aspect.bzl%a2\", \"//test:aspect.bzl%a3\"]=a2p",
            "@//test:r2[\"//test:aspect.bzl%a3\"]=");
  }

  /**
   * Linear with duplicates. r2_1 depends on r0 with aspect a2. r1 depends on r2_1 with aspect a1.
   * r2 depends on r1 with aspect a2.
   *
   * <p>a2 is not interested in a1. There should be just one instance of aspect a2 on r0, and is
   * should *not* see a1.
   */
  @Test
  public void aspectOnAspectLinearDuplicates() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "a1p = provider()",
        "def _a1_impl(target,ctx):",
        "  return struct(a1p = 'a1p')",
        "a1 = aspect(_a1_impl, attr_aspects = ['dep'], provides = ['a1p'])",
        "a2p = provider()",
        "def _a2_impl(target,ctx):",
        "  value = []",
        "  if hasattr(ctx.rule.attr.dep, 'a2p'):",
        "     value += ctx.rule.attr.dep.a2p.value",
        "  if hasattr(target, 'a1p'):",
        "     value.append(str(target.label) + str(ctx.aspect_ids) + '=yes')",
        "  else:",
        "     value.append(str(target.label) + str(ctx.aspect_ids) + '=no')",
        "  return struct(a2p = a2p(value = value))",
        "a2 = aspect(_a2_impl, attr_aspects = ['dep'], required_aspect_providers = [])",
        "def _r1_impl(ctx):",
        "  pass",
        "def _r2_impl(ctx):",
        "  return struct(result = ctx.attr.dep.a2p.value)",
        "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
        "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'r1', 'r2')",
        "r1(name = 'r0')",
        "r2(name = 'r2_1', dep = ':r0')",
        "r1(name = 'r1', dep = ':r2_1')",
        "r2(name = 'r2', dep = ':r1')");
    AnalysisResult analysisResult = update("//test:r2");
    ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    Sequence<?> result = (Sequence<?>) target.get("result");
    // "yes" means that aspect a2 sees a1's providers.
    assertThat(result)
        .containsExactly(
            "@//test:r0[\"//test:aspect.bzl%a2\"]=no",
            "@//test:r1[\"//test:aspect.bzl%a2\"]=no", "@//test:r2_1[\"//test:aspect.bzl%a2\"]=no");
  }

  /** Linear aspects-on-aspects with alias rule. */
  @Test
  public void aspectOnAspectLinearAlias() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "a1p = provider()",
        "def _a1_impl(target,ctx):",
        "  return struct(a1p = a1p(text = 'random'))",
        "a1 = aspect(_a1_impl, attr_aspects = ['dep'], provides = ['a1p'])",
        "a2p = provider()",
        "def _a2_impl(target,ctx):",
        "  value = []",
        "  if hasattr(ctx.rule.attr.dep, 'a2p'):",
        "     value += ctx.rule.attr.dep.a2p.value",
        "  if hasattr(target, 'a1p'):",
        "     value.append(str(target.label) + str(ctx.aspect_ids) + '=yes')",
        "  else:",
        "     value.append(str(target.label) + str(ctx.aspect_ids) + '=no')",
        "  return struct(a2p = a2p(value = value))",
        "a2 = aspect(_a2_impl, attr_aspects = ['dep'], required_aspect_providers = ['a1p'])",
        "def _r1_impl(ctx):",
        "  pass",
        "def _r2_impl(ctx):",
        "  return struct(result = ctx.attr.dep.a2p.value)",
        "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
        "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'r1', 'r2')",
        "r1(name = 'r0')",
        "alias(name = 'a0', actual = ':r0')",
        "r1(name = 'r1', dep = ':a0')",
        "r2(name = 'r2', dep = ':r1')");
    AnalysisResult analysisResult = update("//test:r2");
    ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    Sequence<?> result = (Sequence<?>) target.get("result");

    // "yes" means that aspect a2 sees a1's providers.
    assertThat(result)
        .containsExactly(
            "@//test:r0[\"//test:aspect.bzl%a1\", \"//test:aspect.bzl%a2\"]=yes",
            "@//test:r1[\"//test:aspect.bzl%a2\"]=no");
  }

  @Test
  public void aspectDescriptions() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _a_impl(target,ctx):",
        "  s = str(target.label) + str(ctx.aspect_ids) + '='",
        "  value = []",
        "  if ctx.rule.attr.dep:",
        "     d = ctx.rule.attr.dep",
        "     this_id = ctx.aspect_ids[len(ctx.aspect_ids) - 1]",
        "     s += str(d.label) + str(d.my_ids) + ',' + str(this_id in d.my_ids)",
        "     value += ctx.rule.attr.dep.ap",
        "  else:",
        "     s += 'None'",
        "  value.append(s)",
        "  return struct(ap = value, my_ids = ctx.aspect_ids)",
        "a = aspect(_a_impl, attr_aspects = ['dep'])",
        "def _r_impl(ctx):",
        "  if not ctx.attr.dep:",
        "     return struct(result = [])",
        "  return struct(result = ctx.attr.dep.ap)",
        "r = rule(_r_impl, attrs = { 'dep' : attr.label(aspects = [a])})");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'r')",
        "r(name = 'r0')",
        "r(name = 'r1', dep = ':r0')",
        "r(name = 'r2', dep = ':r1')");
    AnalysisResult analysisResult = update("//test:r2");
    ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    Sequence<?> result = (Sequence<?>) target.get("result");

    assertThat(result)
        .containsExactly(
            "@//test:r0[\"//test:aspect.bzl%a\"]=None",
            "@//test:r1[\"//test:aspect.bzl%a\"]=@//test:r0[\"//test:aspect.bzl%a\"],True");
  }

  @Test
  public void attributesWithAspectsReused() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "my_aspect = aspect(_impl)",
        "a_dict = { 'foo' : attr.label_list(aspects = [my_aspect]) }");

    scratch.file(
        "test/r1.bzl",
        "load(':aspect.bzl', 'my_aspect', 'a_dict')",
        "def _rule_impl(ctx):",
        "   pass",
        "r1 = rule(_rule_impl, attrs = a_dict)");

    scratch.file(
        "test/r2.bzl",
        "load(':aspect.bzl', 'my_aspect', 'a_dict')",
        "def _rule_impl(ctx):",
        "   pass",
        "r2 = rule(_rule_impl, attrs = a_dict)");

    scratch.file(
        "test/BUILD",
        "load(':r1.bzl', 'r1')",
        "load(':r2.bzl', 'r2')",
        "r1(name = 'x1')",
        "r2(name = 'x2', foo = [':x1'])");
    AnalysisResult analysisResult = update("//test:x2");
    assertThat(analysisResult.hasError()).isFalse();
  }

  @Test
  public void aspectAdvertisingProviders() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "my_aspect = aspect(_impl, provides = ['foo'])",
        "a_dict = { 'foo' : attr.label_list(aspects = [my_aspect]) }");
    scratch.file("test/BUILD", "java_library(name = 'xxx',)");

    reporter.removeHandler(failFastHandler);
    try {
      AnalysisResult analysisResult =
          update(ImmutableList.of("//test:aspect.bzl%my_aspect"), "//test:xxx");
      assertThat(keepGoing()).isTrue();
      assertThat(analysisResult.hasError()).isTrue();
    } catch (ViewCreationFailedException e) {
      // expect exception
    }
    assertContainsEvent(
        "Aspect '//test:aspect.bzl%my_aspect', applied to '//test:xxx', "
            + "does not provide advertised provider 'foo'");
  }

  @Test
  public void aspectOnAspectInconsistentVisibility() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "a1p = provider()",
        "def _a1_impl(target,ctx):",
        "  return struct(a1p = a1p(text = 'random'))",
        "a1 = aspect(_a1_impl, attr_aspects = ['dep'], provides = ['a1p'])",
        "a2p = provider()",
        "def _a2_impl(target,ctx):",
        "  return struct(a2p = a2p(value = 'random'))",
        "a2 = aspect(_a2_impl, attr_aspects = ['dep'], required_aspect_providers = ['a1p'])",
        "def _r1_impl(ctx):",
        "  pass",
        "def _r2_impl(ctx):",
        "  return struct(result = ctx.attr.dep.a2p.value)",
        "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
        "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'r1', 'r2')",
        "r1(name = 'r0')",
        "r1(name = 'r1', dep = ':r0')",
        "r2(name = 'r2', dep = ':r1')",
        "r1(name = 'r1_1', dep = ':r2')",
        "r2(name = 'r2_1', dep = ':r1_1')");
    reporter.removeHandler(failFastHandler);

    try {
      AnalysisResult analysisResult = update("//test:r2_1");
      assertThat(analysisResult.hasError()).isTrue();
      assertThat(keepGoing()).isTrue();
    } catch (ViewCreationFailedException e) {
      // expected
    }
    assertContainsEvent(
        "ERROR /workspace/test/BUILD:3:3: Aspect //test:aspect.bzl%a2 is"
            + " applied twice, both before and after aspect //test:aspect.bzl%a1 "
            + "(when propagating to //test:r1)");
  }

  @Test
  public void aspectOnAspectInconsistentVisibilityIndirect() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "a1p = provider()",
        "def _a1_impl(target,ctx):",
        "  return struct(a1p = a1p(text = 'random'))",
        "a1 = aspect(_a1_impl, attr_aspects = ['dep'], provides = ['a1p'])",
        "a2p = provider()",
        "def _a2_impl(target,ctx):",
        "  return struct(a2p = a2p(value = 'random'))",
        "a2 = aspect(_a2_impl, attr_aspects = ['dep'], required_aspect_providers = ['a1p'])",
        "def _r1_impl(ctx):",
        "  pass",
        "def _r2_impl(ctx):",
        "  return struct(result = ctx.attr.dep.a2p.value)",
        "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
        "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})",
        "def _r0_impl(ctx):",
        "  pass",
        "r0 = rule(_r0_impl, attrs = { 'dep' : attr.label()})");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'r0', 'r1', 'r2')",
        "r0(name = 'r0')",
        "r1(name = 'r1', dep = ':r0')",
        "r2(name = 'r2', dep = ':r1')",
        "r1(name = 'r1_1', dep = ':r2')",
        "r2(name = 'r2_1', dep = ':r1_1')",
        "r0(name = 'r0_2', dep = ':r2_1')");
    reporter.removeHandler(failFastHandler);

    try {
      AnalysisResult analysisResult = update("//test:r0_2");
      assertThat(analysisResult.hasError()).isTrue();
      assertThat(keepGoing()).isTrue();
    } catch (ViewCreationFailedException e) {
      // expected
    }
    assertContainsEvent(
        "ERROR /workspace/test/BUILD:3:3: Aspect //test:aspect.bzl%a2 is"
            + " applied twice, both before and after aspect //test:aspect.bzl%a1 "
            + "(when propagating to //test:r1)");
  }

  /**
   * Aspect a3 sees aspect a2, aspect a2 sees aspect a1, but a3 does not see a1. All three aspects
   * should still propagate together.
   */
  @Test
  public void aspectOnAspectOnAspect() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "p1 = provider()",
        "def _a1_impl(target, ctx):",
        "   return [p1()]",
        "a1 = aspect(_a1_impl, attr_aspects = ['dep'], provides = [p1])",
        "p2 = provider()",
        "def _a2_impl(target, ctx):",
        "   value = True if p1 in target else False",
        "   return [p2(has_p1 = value)]",
        "a2 = aspect(_a2_impl, attr_aspects = ['dep'],",
        "   required_aspect_providers = [p1], provides = [p2])",
        "p3 = provider()",
        "def _a3_impl(target, ctx):",
        "   list = []",
        "   if ctx.rule.attr.dep:",
        "     list = ctx.rule.attr.dep[p3].value",
        "   my_value = str(target.label) +'=' + str(target[p2].has_p1 if p2 in target else False)",
        "   return [p3(value = list + [my_value])]",
        "a3 = aspect(_a3_impl, attr_aspects = ['dep'],",
        "   required_aspect_providers = [p2])",
        "def _r0_impl(ctx):",
        "  pass",
        "r0 = rule(_r0_impl, attrs = { 'dep' : attr.label()})",
        "def _r1_impl(ctx):",
        "  pass",
        "def _r2_impl(ctx):",
        "  pass",
        "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
        "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'r0', 'r1', 'r2')",
        "r0(name = 'r0_1')",
        "r0(name = 'r0_2', dep = ':r0_1')",
        "r0(name = 'r0_3', dep = ':r0_2')",
        "r1(name = 'r1_1', dep = ':r0_3')",
        "r2(name = 'r2_1', dep = ':r1_1')");

    AnalysisResult analysisResult = update(ImmutableList.of("//test:aspect.bzl%a3"), "//test:r2_1");
    ConfiguredAspect configuredAspect =
        Iterables.getOnlyElement(analysisResult.getAspectsMap().values());
    StarlarkProvider.Key p3 =
        new StarlarkProvider.Key(Label.parseCanonical("//test:aspect.bzl"), "p3");
    StructImpl p3Provider = (StructImpl) configuredAspect.get(p3);
    assertThat((Sequence<?>) p3Provider.getValue("value"))
        .containsExactly(
            "@//test:r0_1=True",
            "@//test:r0_2=True",
            "@//test:r0_3=True",
            "@//test:r1_1=False",
            "@//test:r2_1=False");
  }

  /**
   * r0 is a dependency of r1 via two attributes, dep1 and dep2. r1 sends an aspect 'a' along dep1
   * but not along dep2.
   *
   * <p>rcollect depends upon r1 and sends another aspect, 'collector', along its dep dependency.
   * 'collector' wants to see aspect 'a' and propagates along dep1 and dep2. It should be applied
   * both to r0 and to r0+a.
   */
  @Test
  public void multipleDepsDifferentAspects() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "PAspect = provider()",
        "PCollector = provider()",
        "def _aspect_impl(target, ctx):",
        "   return [PAspect()]",
        "a = aspect(_aspect_impl, attr_aspects = ['dep'], provides = [PAspect])",
        "def _collector_impl(target, ctx):",
        "   suffix = '+PAspect' if PAspect in target else ''",
        "   result = [str(target.label)+suffix]",
        "   for a in ['dep', 'dep1', 'dep2']:",
        "     if hasattr(ctx.rule.attr, a):",
        "        result += getattr(ctx.rule.attr, a)[PCollector].result",
        "   return [PCollector(result=result)]",
        "collector = aspect(_collector_impl, attr_aspects = ['*'], ",
        "                   required_aspect_providers = [PAspect])",
        "def _rimpl(ctx):",
        "   pass",
        "r0 = rule(_rimpl)",
        "r1 = rule(_rimpl, ",
        "          attrs = {",
        "             'dep1' : attr.label(),",
        "             'dep2' : attr.label(aspects = [a]),",
        "          },",
        ")",
        "def _rcollect_impl(ctx):",
        "    return [ctx.attr.dep[PCollector]]",
        "rcollect = rule(_rcollect_impl,",
        "                attrs = {",
        "                  'dep' : attr.label(aspects = [collector]),",
        "                })");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'r0', 'r1', 'rcollect')",
        "r0(name = 'r0')",
        "r1(name = 'r1', dep1 = ':r0', dep2 = ':r0')",
        "rcollect(name = 'rcollect', dep = ':r1')");

    AnalysisResult analysisResult = update(ImmutableList.of(), "//test:rcollect");
    ConfiguredTarget configuredTarget =
        Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    StarlarkProvider.Key pCollector =
        new StarlarkProvider.Key(Label.parseCanonical("//test:aspect.bzl"), "PCollector");
    StructImpl pCollectorProvider = (StructImpl) configuredTarget.get(pCollector);
    assertThat((Sequence<?>) pCollectorProvider.getValue("result"))
        .containsExactly("@//test:r1", "@//test:r0", "@//test:r0+PAspect");
  }

  @Test
  public void aspectSeesOtherAspectAttributes() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "PAspect = provider(fields = [])",
        "PCollector = provider(fields = ['aspect_attr'])",
        "def _a_impl(target, ctx):",
        "  return [PAspect()]",
        "a = aspect(_a_impl, ",
        "           provides = [PAspect],",
        "           attrs = {'_a_attr' : attr.label(default = '//test:foo')})",
        "def _rcollect(target, ctx):",
        "  if hasattr(ctx.rule.attr, '_a_attr'):",
        "     return [PCollector(aspect_attr = ctx.rule.attr._a_attr.label)]",
        "  if hasattr(ctx.rule.attr, 'dep'):",
        "     return [ctx.rule.attr.dep[PCollector]]",
        "  return [PCollector()]",
        "acollect = aspect(_rcollect, attr_aspects = ['*'], required_aspect_providers = [PAspect])",
        "def _rimpl(ctx):",
        "  pass",
        "r0 = rule(_rimpl)",
        "r = rule(_rimpl, attrs = { 'dep' : attr.label(aspects = [a]) })");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'r0', 'r')",
        "r0(name = 'foo')",
        "r0(name = 'bar')",
        "r(name = 'baz', dep = ':bar')");
    AnalysisResult analysisResult =
        update(ImmutableList.of("//test:aspect.bzl%acollect"), "//test:baz");
    ConfiguredAspect configuredAspect =
        Iterables.getOnlyElement(analysisResult.getAspectsMap().values());
    StarlarkProvider.Key pCollector =
        new StarlarkProvider.Key(Label.parseCanonical("//test:aspect.bzl"), "PCollector");
    StructImpl collector = (StructImpl) configuredAspect.get(pCollector);
    assertThat(collector.getValue("aspect_attr")).isEqualTo(Label.parseCanonical("//test:foo"));
  }

  @Test
  public void ruleAttributesWinOverAspects() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "PAspect = provider(fields = [])",
        "PCollector = provider(fields = ['attr_value'])",
        "def _a_impl(target, ctx):",
        "  return [PAspect()]",
        "a = aspect(_a_impl, ",
        "           provides = [PAspect],",
        "           attrs = {'_same_attr' : attr.int(default = 239)})",
        "def _rcollect(target, ctx):",
        "  if hasattr(ctx.rule.attr, '_same_attr'):",
        "     return [PCollector(attr_value = ctx.rule.attr._same_attr)]",
        "  if hasattr(ctx.rule.attr, 'dep'):",
        "     return [ctx.rule.attr.dep[PCollector]]",
        "  return [PCollector()]",
        "acollect = aspect(_rcollect, attr_aspects = ['*'], required_aspect_providers = [PAspect])",
        "def _rimpl(ctx):",
        "  pass",
        "r0 = rule(_rimpl)",
        "r = rule(_rimpl, ",
        "          attrs = { ",
        "                  'dep' : attr.label(aspects = [a]), ",
        "                  '_same_attr' : attr.int(default = 30)",
        "          })");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'r0', 'r')",
        "r0(name = 'foo')",
        "r0(name = 'bar')",
        "r(name = 'baz', dep = ':bar')");
    AnalysisResult analysisResult =
        update(ImmutableList.of("//test:aspect.bzl%acollect"), "//test:baz");
    ConfiguredAspect configuredAspect =
        Iterables.getOnlyElement(analysisResult.getAspectsMap().values());
    StarlarkProvider.Key pCollector =
        new StarlarkProvider.Key(Label.parseCanonical("//test:aspect.bzl"), "PCollector");
    StructImpl collector = (StructImpl) configuredAspect.get(pCollector);
    assertThat(collector.getValue("attr_value")).isEqualTo(StarlarkInt.of(30));
  }

  @Test
  public void earlyAspectAttributesWin() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "PAspect1 = provider(fields = [])",
        "PAspect2 = provider(fields = [])",
        "PCollector = provider(fields = ['attr_value'])",
        "def _a1_impl(target, ctx):",
        "  return [PAspect1()]",
        "def _a2_impl(target, ctx):",
        "  return [PAspect2()]",
        "a1 = aspect(_a1_impl, ",
        "            provides = [PAspect1],",
        "            attrs = {'_same_attr' : attr.int(default = 30)})",
        "a2 = aspect(_a2_impl, ",
        "            provides = [PAspect2],",
        "            attrs = {'_same_attr' : attr.int(default = 239)})",
        "def _rcollect(target, ctx):",
        "  if hasattr(ctx.rule.attr, 'dep'):",
        "     return [ctx.rule.attr.dep[PCollector]]",
        "  if hasattr(ctx.rule.attr, '_same_attr'):",
        "     return [PCollector(attr_value = ctx.rule.attr._same_attr)]",
        "  fail('???')",
        "  return [PCollector()]",
        "acollect = aspect(_rcollect, attr_aspects = ['*'], ",
        "                  required_aspect_providers = [[PAspect1], [PAspect2]])",
        "def _rimpl(ctx):",
        "  pass",
        "r0 = rule(_rimpl)",
        "r1 = rule(_rimpl, ",
        "          attrs = { ",
        "                  'dep' : attr.label(aspects = [a1]), ",
        "          })",
        "r2 = rule(_rimpl, ",
        "          attrs = { ",
        "                  'dep' : attr.label(aspects = [a2]), ",
        "          })");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'r0', 'r1', 'r2')",
        "r0(name = 'bar')",
        "r1(name = 'baz', dep = ':bar')",
        "r2(name = 'quux', dep = ':baz')");

    AnalysisResult analysisResult =
        update(ImmutableList.of("//test:aspect.bzl%acollect"), "//test:quux");
    ConfiguredAspect configuredAspect =
        Iterables.getOnlyElement(analysisResult.getAspectsMap().values());
    StarlarkProvider.Key pCollector =
        new StarlarkProvider.Key(Label.parseCanonical("//test:aspect.bzl"), "PCollector");
    StructImpl collector = (StructImpl) configuredAspect.get(pCollector);
    assertThat(collector.getValue("attr_value")).isEqualTo(StarlarkInt.of(30));
  }

  @Test
  public void aspectPropagatesOverOtherAspectAttributes() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "PAspect = provider(fields = [])",
        "PCollector = provider(fields = ['visited'])",
        "def _a_impl(target, ctx):",
        "  return [PAspect()]",
        "a = aspect(_a_impl, ",
        "       provides = [PAspect],",
        "       attrs = {'_a_attr' : attr.label(default = '//test:referenced_from_aspect_only')})",
        "def _rcollect(target, ctx):",
        "  transitive = []",
        "  if hasattr(ctx.rule.attr, 'dep') and ctx.rule.attr.dep:",
        "     transitive += [ctx.rule.attr.dep[PCollector].visited]",
        "  if hasattr(ctx.rule.attr, '_a_attr') and ctx.rule.attr._a_attr:",
        "     transitive += [ctx.rule.attr._a_attr[PCollector].visited] ",
        "  visited = depset([target.label], transitive = transitive, )",
        "  return [PCollector(visited = visited)]",
        "acollect = aspect(_rcollect, attr_aspects = ['*'], required_aspect_providers = [PAspect])",
        "def _rimpl(ctx):",
        "  pass",
        "r0 = rule(_rimpl)",
        "r = rule(_rimpl, attrs = { 'dep' : attr.label(aspects = [a]) })");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'r0', 'r')",
        "r0(name = 'referenced_from_aspect_only')",
        "r0(name = 'bar')",
        "r(name = 'baz', dep = ':bar')");
    AnalysisResult analysisResult =
        update(ImmutableList.of("//test:aspect.bzl%acollect"), "//test:baz");
    ConfiguredAspect configuredAspect =
        Iterables.getOnlyElement(analysisResult.getAspectsMap().values());
    StarlarkProvider.Key pCollector =
        new StarlarkProvider.Key(Label.parseCanonical("//test:aspect.bzl"), "PCollector");
    StructImpl collector = (StructImpl) configuredAspect.get(pCollector);
    assertThat(((Depset) collector.getValue("visited")).toList())
        .containsExactly(
            Label.parseCanonical("//test:referenced_from_aspect_only"),
            Label.parseCanonical("//test:bar"),
            Label.parseCanonical("//test:baz"));
  }

  @Test
  public void testAspectActionProvider() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "a1p = provider()",
        "def _a1_impl(target,ctx):",
        "  ctx.actions.run_shell(",
        "    outputs = [ctx.actions.declare_file('a1')],",
        "    command = 'touch $@'",
        "  )",
        "  return struct(a1p=a1p())",
        "a1 = aspect(_a1_impl, attr_aspects = ['dep'], provides = ['a1p'])",
        "a2p = provider()",
        "def _a2_impl(target, ctx):",
        "  value = []",
        "  if hasattr(ctx.rule.attr, 'dep') and hasattr(ctx.rule.attr.dep, 'a2p'):",
        "     value += ctx.rule.attr.dep.a2p.value",
        "  value += target.actions",
        "  return struct(a2p = a2p(value = value))",
        "a2 = aspect(_a2_impl, attr_aspects = ['dep'], required_aspect_providers = ['a1p'])",
        "def _r0_impl(ctx):",
        "  ctx.actions.run_shell(",
        "    outputs = [ctx.actions.declare_file('r0')],",
        "    command = 'touch $@'",
        "  )",
        "def _r1_impl(ctx):",
        "  pass",
        "def _r2_impl(ctx):",
        "  return struct(result = ctx.attr.dep.a2p.value)",
        "r0 = rule(_r0_impl)",
        "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
        "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'r0', 'r1', 'r2')",
        "r0(name = 'r0')",
        "r1(name = 'r1', dep = ':r0')",
        "r2(name = 'r2', dep = ':r1')");
    AnalysisResult analysisResult = update("//test:r2");
    ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    Sequence<?> result = (Sequence<?>) target.get("result");

    // We should see both the action from the 'r0' rule, and the action from the 'a1' aspect
    assertThat(result).hasSize(2);
    assertThat(
            result.stream()
                .map(a -> ((Action) a).getPrimaryOutput().getExecPath().getBaseName())
                .collect(toList()))
        .containsExactly("r0", "a1");
  }

  @Test
  public void testRuleAndAspectAttrConflict() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "MyInfo = provider()",
        "def _impl(target, ctx):",
        "   return [MyInfo(hidden_attr_label = str(ctx.attr._hiddenattr.label))]",
        "",
        "def _rule_impl(ctx):",
        "   return []",
        "",
        "my_rule = rule(implementation = _rule_impl,",
        "   attrs = { '_hiddenattr' : attr.label(default = Label('//test:xxx')) },",
        ")",
        "MyAspect = aspect(",
        "   implementation=_impl,",
        "   attrs = { '_hiddenattr' : attr.label(default = Label('//test:zzz')) },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:aspect.bzl', 'my_rule')",
        "cc_library(",
        "     name = 'xxx',",
        ")",
        "my_rule(",
        "     name = 'yyy',",
        ")",
        "cc_library(",
        "     name = 'zzz',",
        ")");
    AnalysisResult analysisResult =
        update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:yyy");
    ConfiguredAspect configuredAspect = analysisResult.getAspectsMap().values().iterator().next();
    assertThat(configuredAspect).isNotNull();

    StarlarkProvider.Key myInfoKey =
        new StarlarkProvider.Key(Label.parseCanonical("//test:aspect.bzl"), "MyInfo");
    StructImpl myInfo = (StructImpl) configuredAspect.get(myInfoKey);
    assertThat(myInfo.getValue("hidden_attr_label")).isEqualTo("@//test:zzz");
  }

  /** Simple straightforward linear aspects-on-aspects. */
  @Test
  public void aspectOnAspectAttrConflict() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "MyInfo = provider()",
        "a1p = provider()",
        "def _a1_impl(target,ctx):",
        "  return struct(a1p = a1p(text = 'random'))",
        "a1 = aspect(_a1_impl,",
        "   attrs = { '_hiddenattr' : attr.label(default = Label('//test:xxx')) },",
        "   attr_aspects = ['dep'], provides = ['a1p'])",
        "a2p = provider()",
        "def _a2_impl(target,ctx):",
        "   return [MyInfo(hidden_attr_label = str(ctx.attr._hiddenattr.label))]",
        "a2 = aspect(_a2_impl,",
        "  attrs = { '_hiddenattr' : attr.label(default = Label('//test:zzz')) },",
        "  attr_aspects = ['dep'], required_aspect_providers = ['a1p'])",
        "def _r1_impl(ctx):",
        "  pass",
        "def _r2_impl(ctx):",
        "  return struct(result = ctx.attr.dep[MyInfo].hidden_attr_label)",
        "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
        "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})");
    scratch.file(
        "test/BUILD",
        "load(':aspect.bzl', 'r1', 'r2')",
        "r1(name = 'r0')",
        "r1(name = 'r1', dep = ':r0')",
        "r2(name = 'r2', dep = ':r1')",
        "cc_library(",
        "     name = 'xxx',",
        ")",
        "cc_library(",
        "     name = 'zzz',",
        ")");
    AnalysisResult analysisResult = update("//test:r2");
    ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    String result = (String) target.get("result");

    assertThat(result).isEqualTo("@//test:zzz");
  }

  @Test
  public void testAllCcLibraryAttrsAreValidTypes() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "  for entry in dir(ctx.rule.attr):",
        "    val = getattr(ctx.rule.attr, entry, None)",
        "    # Only legitimate Starlark values can be passed to dir(), so this effectively",
        "    # verifies val is an appropriate Starlark type.",
        "    _test_dir = dir(val)",
        "  return []",
        "",
        "MyAspect = aspect(",
        "  implementation=_impl,",
        ")");
    scratch.file("test/BUILD", "cc_library(", "     name = 'xxx',", ")");
    AnalysisResult analysisResult =
        update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
    assertThat(analysisResult.getAspectsMap().values().iterator().next()).isNotNull();
  }

  @Test
  public void testApplyToGeneratingRules() throws Exception {
    // Create test rules:
    // dep_rule: a rule which may depend on other dep_rule targets and may optionally create
    //     an output file.
    // root_{with,no}_files: a rule which depends on dep_rule targets and attaches an aspect.
    //     The rule returns a RootInfo provider which contains two fields:
    //        'from_aspect' : a list of all labels that the aspect propagated to
    //        'non_aspect' : a list of all labels that information was obtained from without aspect
    //     root_with_files uses an aspect with apply_to_generating_rules=True, and root_no_files
    //     uses an aspect with apply_to_generating_rules=False.
    scratch.file(
        "test/lib.bzl",
        "RootInfo = provider()",
        "NonAspectInfo = provider()",
        "FromAspectInfo = provider()",
        "def _aspect_impl(target, ctx):",
        "  dep_labels = []",
        "  for dep in ctx.rule.attr.deps:",
        "    if FromAspectInfo in dep:",
        "      dep_labels += [dep[FromAspectInfo].labels]",
        "  return FromAspectInfo(labels = depset(direct = [ctx.label], transitive = dep_labels))",
        "",
        "def _rule_impl(ctx):",
        "  non_aspect = []",
        "  from_aspect = []",
        "  for dep in ctx.attr.deps:",
        "    if NonAspectInfo in dep:",
        "      non_aspect +=  dep[NonAspectInfo].labels.to_list()",
        "    if FromAspectInfo in dep:",
        "      from_aspect += dep[FromAspectInfo].labels.to_list()",
        "  return RootInfo(from_aspect = from_aspect, non_aspect = non_aspect)",
        "",
        "def _dep_rule_impl(ctx):",
        "  if ctx.outputs.output:",
        "    ctx.actions.run_shell(outputs = [ctx.outputs.output], command = 'dont run me')",
        "  dep_labels = []",
        "  for dep in ctx.attr.deps:",
        "    if NonAspectInfo in dep:",
        "      dep_labels += [dep[NonAspectInfo].labels]",
        "  return NonAspectInfo(labels = depset(direct = [ctx.label], transitive = dep_labels))",
        "",
        "aspect_with_files = aspect(",
        "  implementation = _aspect_impl,",
        "  attr_aspects = ['deps'],",
        "  apply_to_generating_rules = True)",
        "",
        "aspect_no_files = aspect(",
        "  implementation = _aspect_impl,",
        "  attr_aspects = ['deps'],",
        "  apply_to_generating_rules = False)",
        "",
        "root_with_files = rule(implementation = _rule_impl,",
        "  attrs = {'deps' : attr.label_list(aspects = [aspect_with_files])})",
        "",
        "root_no_files = rule(implementation = _rule_impl,",
        "  attrs = {'deps' : attr.label_list(aspects = [aspect_no_files])})",
        "",
        "dep_rule = rule(implementation = _dep_rule_impl,",
        "  attrs = {'deps' : attr.label_list(allow_files = True), 'output' : attr.output()})");

    // Create a target graph such that two graph roots each point to a common subgraph
    // alpha -> beta_output -> charlie, where beta_output is a generated output file of target
    // 'beta'.
    scratch.file(
        "test/BUILD",
        "load('//test:lib.bzl', 'root_with_files', 'root_no_files', 'dep_rule')",
        "",
        "root_with_files(name = 'test_with_files', deps = [':alpha'])",
        "root_no_files(name = 'test_no_files', deps = [':alpha'])",
        "dep_rule(name = 'alpha', deps = [':beta_output'])",
        "dep_rule(name = 'beta', deps = [':charlie'], output = 'beta_output')",
        "dep_rule(name = 'charlie')");

    StarlarkProvider.Key rootInfoKey =
        new StarlarkProvider.Key(Label.parseCanonical("//test:lib.bzl"), "RootInfo");

    AnalysisResult analysisResultWithFiles = update("//test:test_with_files");
    ConfiguredTarget targetWithFiles =
        Iterables.getOnlyElement(analysisResultWithFiles.getTargetsToBuild());
    StructImpl rootInfoWithFiles = (StructImpl) targetWithFiles.get(rootInfoKey);
    // With apply_to_generating_rules=True, the aspect should have traversed :beta_output and
    // applied to both :beta and :charlie.
    assertThat(rootInfoWithFiles.getValue("from_aspect", Sequence.class))
        .containsExactly(
            Label.parseCanonical("//test:charlie"),
            Label.parseCanonical("//test:beta"),
            Label.parseCanonical("//test:alpha"));
    assertThat(rootInfoWithFiles.getValue("non_aspect", Sequence.class))
        .containsExactly(Label.parseCanonical("//test:alpha"));

    AnalysisResult analysisResultNoFiles = update("//test:test_no_files");
    ConfiguredTarget targetNoFiles =
        Iterables.getOnlyElement(analysisResultNoFiles.getTargetsToBuild());
    StructImpl rootInfoNoFiles = (StructImpl) targetNoFiles.get(rootInfoKey);
    // With apply_to_generating_rules=False, the aspect should have only accessed :alpha, as it
    // must have stopped before :beta_output.
    assertThat(rootInfoNoFiles.getValue("from_aspect", Sequence.class))
        .containsExactly(Label.parseCanonical("//test:alpha"));
    assertThat(rootInfoWithFiles.getValue("non_aspect", Sequence.class))
        .containsExactly(Label.parseCanonical("//test:alpha"));
  }

  private void setupAspectOnAspectTargetGraph(
      boolean applyRootToGeneratingRules, boolean applyDepToGeneratingRules) throws Exception {
    // RootAspectInfo.both_labels returns a list of target labels which
    //     were evaluated as [root_aspect(dep_aspect(target))].
    // RootAspectInfo.root_only_labels returns a list of target labels which
    //     were evaluated as [root_aspect(target)].
    // DepAspectInfo.labels returns a list of target labels which were evaluated by dep_aspect.
    scratch.file(
        "test/lib.bzl",
        "RootAspectInfo = provider()",
        "DepAspectInfo = provider()",
        "def _root_aspect_impl(target, ctx):",
        "  both_labels = []",
        "  root_only_labels = []",
        "  for dep in ctx.rule.attr.deps:",
        "    if RootAspectInfo in dep:",
        "      both_labels += dep[RootAspectInfo].both_labels",
        "      root_only_labels += dep[RootAspectInfo].root_only_labels",
        "      if DepAspectInfo in dep:",
        "        both_labels += [dep.label]",
        "      else:",
        "        root_only_labels += [dep.label]",
        "  return RootAspectInfo(both_labels = both_labels, root_only_labels = root_only_labels)",
        "",
        "def _dep_aspect_impl(target, ctx):",
        "  dep_labels = [ctx.label]",
        "  for dep in ctx.rule.attr.deps:",
        "    if DepAspectInfo in dep:",
        "      dep_labels += dep[DepAspectInfo].labels",
        "  return DepAspectInfo(labels = dep_labels)",
        "",
        "def _root_rule_impl(ctx):",
        "  return [ctx.attr.deps[0][RootAspectInfo], ctx.attr.deps[0][DepAspectInfo]]",
        "",
        "def _dep_with_aspect_rule_impl(ctx):",
        "  return [ctx.attr.deps[0][DepAspectInfo]]",
        "",
        "def _dep_rule_impl(ctx):",
        "  if ctx.outputs.output:",
        "    ctx.actions.run_shell(outputs = [ctx.outputs.output], command = 'dont run me')",
        "  return []",
        "",
        "root_aspect = aspect(",
        "  implementation = _root_aspect_impl,",
        "  attr_aspects = ['deps'],",
        "  required_aspect_providers = [DepAspectInfo],",
        "  apply_to_generating_rules = " + (applyRootToGeneratingRules ? "True" : "False") + ")",
        "",
        "dep_aspect = aspect(",
        "  implementation = _dep_aspect_impl,",
        "  attr_aspects = ['deps'],",
        "  provides = [DepAspectInfo],",
        "  apply_to_generating_rules = " + (applyDepToGeneratingRules ? "True" : "False") + ")",
        "",
        "root_rule = rule(implementation = _root_rule_impl,",
        "  attrs = {'deps' : attr.label_list(aspects = [root_aspect])})",
        "",
        "dep_with_aspect_rule = rule(implementation = _dep_with_aspect_rule_impl,",
        "  attrs = {'deps' : attr.label_list(aspects = [dep_aspect])})",
        "",
        "dep_rule = rule(implementation = _dep_rule_impl,",
        "  attrs = {'deps' : attr.label_list(allow_files = True), 'output' : attr.output()})");

    // Target graph: test -> aspect_propagating_target -> alpha -> beta_output
    // beta_output is an output of target `beta`
    // beta -> charlie
    scratch.file(
        "test/BUILD",
        "load('//test:lib.bzl', 'root_rule', 'dep_with_aspect_rule', 'dep_rule')",
        "",
        "root_rule(name = 'test', deps = [':aspect_propagating_target'])",
        "dep_with_aspect_rule(name = 'aspect_propagating_target', deps = [':alpha'])",
        "dep_rule(name = 'alpha', deps = [':beta_output'])",
        "dep_rule(name = 'beta', deps = [':charlie'], output = 'beta_output')",
        "dep_rule(name = 'charlie')");
  }

  @Test
  public void testAspectOnAspectApplyToGeneratingRules_bothPropagate() throws Exception {
    setupAspectOnAspectTargetGraph(
        /* applyRootToGeneratingRules= */ true, /* applyDepToGeneratingRules= */ true);

    StarlarkProvider.Key rootInfoKey =
        new StarlarkProvider.Key(Label.parseCanonical("//test:lib.bzl"), "RootAspectInfo");
    StarlarkProvider.Key depInfoKey =
        new StarlarkProvider.Key(Label.parseCanonical("//test:lib.bzl"), "DepAspectInfo");

    AnalysisResult analysisResult = update("//test:test");
    ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    StructImpl rootInfo = (StructImpl) target.get(rootInfoKey);
    StructImpl depInfo = (StructImpl) target.get(depInfoKey);

    assertThat(rootInfo.getValue("both_labels", Sequence.class))
        .containsExactly(
            Label.parseCanonical("//test:alpha"),
            Label.parseCanonical("//test:beta_output"),
            Label.parseCanonical("//test:charlie"));
    assertThat(rootInfo.getValue("root_only_labels", Sequence.class)).isEmpty();
    assertThat(depInfo.getValue("labels", Sequence.class))
        .containsExactly(
            Label.parseCanonical("//test:alpha"),
            Label.parseCanonical("//test:beta"),
            Label.parseCanonical("//test:charlie"));
  }

  @Test
  public void testAspectOnAspectApplyToGeneratingRules_neitherPropagate() throws Exception {
    setupAspectOnAspectTargetGraph(
        /* applyRootToGeneratingRules= */ false, /* applyDepToGeneratingRules= */ false);

    StarlarkProvider.Key rootInfoKey =
        new StarlarkProvider.Key(Label.parseCanonical("//test:lib.bzl"), "RootAspectInfo");
    StarlarkProvider.Key depInfoKey =
        new StarlarkProvider.Key(Label.parseCanonical("//test:lib.bzl"), "DepAspectInfo");

    AnalysisResult analysisResult = update("//test:test");
    ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    StructImpl rootInfo = (StructImpl) target.get(rootInfoKey);
    StructImpl depInfo = (StructImpl) target.get(depInfoKey);

    assertThat(rootInfo.getValue("both_labels", Sequence.class))
        .containsExactly(Label.parseCanonical("//test:alpha"));
    assertThat(rootInfo.getValue("root_only_labels", Sequence.class)).isEmpty();
    assertThat(depInfo.getValue("labels", Sequence.class))
        .containsExactly(Label.parseCanonical("//test:alpha"));
  }

  @Test
  public void testAspectOnAspectApplyToGeneratingRules_rootOnly() throws Exception {
    setupAspectOnAspectTargetGraph(
        /* applyRootToGeneratingRules= */ true, /* applyDepToGeneratingRules= */ false);

    StarlarkProvider.Key rootInfoKey =
        new StarlarkProvider.Key(Label.parseCanonical("//test:lib.bzl"), "RootAspectInfo");
    StarlarkProvider.Key depInfoKey =
        new StarlarkProvider.Key(Label.parseCanonical("//test:lib.bzl"), "DepAspectInfo");

    AnalysisResult analysisResult = update("//test:test");
    ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    StructImpl rootInfo = (StructImpl) target.get(rootInfoKey);
    StructImpl depInfo = (StructImpl) target.get(depInfoKey);

    assertThat(rootInfo.getValue("both_labels", Sequence.class))
        .containsExactly(Label.parseCanonical("//test:alpha"));
    assertThat(rootInfo.getValue("root_only_labels", Sequence.class))
        .containsExactly(
            Label.parseCanonical("//test:beta_output"), Label.parseCanonical("//test:charlie"));
    assertThat(depInfo.getValue("labels", Sequence.class))
        .containsExactly(Label.parseCanonical("//test:alpha"));
  }

  @Test
  public void testAspectOnAspectApplyToGeneratingRules_depOnly() throws Exception {
    setupAspectOnAspectTargetGraph(
        /* applyRootToGeneratingRules= */ false, /* applyDepToGeneratingRules= */ true);

    StarlarkProvider.Key rootInfoKey =
        new StarlarkProvider.Key(Label.parseCanonical("//test:lib.bzl"), "RootAspectInfo");
    StarlarkProvider.Key depInfoKey =
        new StarlarkProvider.Key(Label.parseCanonical("//test:lib.bzl"), "DepAspectInfo");

    AnalysisResult analysisResult = update("//test:test");
    ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    StructImpl rootInfo = (StructImpl) target.get(rootInfoKey);
    StructImpl depInfo = (StructImpl) target.get(depInfoKey);

    assertThat(rootInfo.getValue("both_labels", Sequence.class))
        .containsExactly(Label.parseCanonical("//test:alpha"));
    assertThat(rootInfo.getValue("root_only_labels", Sequence.class)).isEmpty();
    assertThat(depInfo.getValue("labels", Sequence.class))
        .containsExactly(
            Label.parseCanonical("//test:alpha"),
            Label.parseCanonical("//test:beta"),
            Label.parseCanonical("//test:charlie"));
  }

  @Test
  public void testAspectActionsDontInheritExecProperties() throws Exception {
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'my_target',",
        "  deps = [':my_dep'],",
        ")",
        "cc_binary(",
        "  name = 'my_dep',",
        "  srcs = ['dep.cc'],",
        "  exec_properties = {'mem': '16g'},",
        ")");
    scratch.file(
        "test/defs.bzl",
        "def _aspect_impl(target, ctx):",
        "  f = ctx.actions.declare_file('f.txt')",
        "  ctx.actions.write(f, 'f')",
        "  return []",
        "my_aspect = aspect(",
        "  implementation = _aspect_impl,",
        "  attr_aspects = ['deps'],",
        ")",
        "def _rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _rule_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(aspects = [my_aspect])",
        "  },",
        ")");
    scratch.file("test/dep.cc", "int main() {return 0;}");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/defs.bzl%my_aspect"), "//test:my_target");
    assertThat(analysisResult).isNotNull();
    ActionOwner owner =
        Iterables.getOnlyElement(
                Iterables.getOnlyElement(analysisResult.getAspectsMap().values()).getActions())
            .getOwner();
    assertThat(owner.getExecProperties()).isEmpty();
  }

  @Test
  public void testAspectRequiredProviders_defaultNoRequiredProviders() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "prov_a = provider()",
        "prov_b = provider()",
        "",
        "def _my_aspect_impl(target, ctx):",
        "  targets_labels = [\"//test:defs.bzl%my_aspect({})\".format(target.label)]",
        "  for dep in ctx.rule.attr.deps:",
        "    if hasattr(dep, 'target_labels'):",
        "      targets_labels.extend(dep.target_labels)",
        "  return struct(target_labels = targets_labels)",
        "",
        "my_aspect = aspect(",
        "  implementation = _my_aspect_impl,",
        "  attr_aspects = ['deps'],",
        ")",
        "",
        "def _rule_without_providers_impl(ctx):",
        "  s = []",
        "  for dep in ctx.attr.deps:",
        "    if hasattr(dep, 'target_labels'):",
        "      s.extend(dep.target_labels)",
        "  return struct(rule_deps = s)",
        "",
        "rule_without_providers = rule(",
        "  implementation = _rule_without_providers_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(aspects = [my_aspect])",
        "  },",
        ")",
        "",
        "def _rule_with_providers_impl(ctx):",
        "  return [prov_a(), prov_b()]",
        "",
        "rule_with_providers = rule(",
        "  implementation = _rule_with_providers_impl,",
        "  attrs = {",
        "    'deps': attr.label_list()",
        "  },",
        "  provides = [prov_a, prov_b]",
        ")",
        "",
        "rule_with_providers_not_advertised = rule(",
        "  implementation = _rule_with_providers_impl,",
        "  attrs = {",
        "    'deps': attr.label_list()",
        "  },",
        "  provides = []",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'rule_with_providers', 'rule_without_providers',",
        "                        'rule_with_providers_not_advertised')",
        "rule_without_providers(",
        "  name = 'main',",
        "  deps = [':target_without_providers', ':target_with_providers',",
        "          ':target_with_providers_not_advertised'],",
        ")",
        "rule_without_providers(",
        "  name = 'target_without_providers',",
        ")",
        "rule_with_providers(",
        "  name = 'target_with_providers',",
        ")",
        "rule_with_providers(",
        "  name = 'target_with_providers_indeps',",
        ")",
        "rule_with_providers_not_advertised(",
        "  name = 'target_with_providers_not_advertised',",
        "  deps = [':target_with_providers_indeps'],",
        ")");

    AnalysisResult analysisResult = update("//test:main");

    // my_aspect does not require any providers so it will be applied to all the dependencies of
    // main target
    List<String> expected = new ArrayList<>();
    expected.add("//test:defs.bzl%my_aspect(@//test:target_without_providers)");
    expected.add("//test:defs.bzl%my_aspect(@//test:target_with_providers)");
    expected.add("//test:defs.bzl%my_aspect(@//test:target_with_providers_not_advertised)");
    expected.add("//test:defs.bzl%my_aspect(@//test:target_with_providers_indeps)");
    assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:main");
    ConfiguredTarget target = analysisResult.getTargetsToBuild().iterator().next();
    Object ruleDepsUnchecked = target.get("rule_deps");
    assertThat(ruleDepsUnchecked).isInstanceOf(StarlarkList.class);
    StarlarkList<?> ruleDeps = (StarlarkList) ruleDepsUnchecked;
    assertThat(Starlark.toIterable(ruleDeps)).containsExactlyElementsIn(expected);
  }

  @Test
  public void testAspectRequiredProviders_flatSetOfRequiredProviders() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "prov_a = provider()",
        "prov_b = provider()",
        "",
        "def _my_aspect_impl(target, ctx):",
        "  targets_labels = [\"//test:defs.bzl%my_aspect({})\".format(target.label)]",
        "  for dep in ctx.rule.attr.deps:",
        "    if hasattr(dep, 'target_labels'):",
        "      targets_labels.extend(dep.target_labels)",
        "  return struct(target_labels = targets_labels)",
        "",
        "my_aspect = aspect(",
        "  implementation = _my_aspect_impl,",
        "  attr_aspects = ['deps'],",
        "  required_providers = [prov_a, prov_b],",
        ")",
        "",
        "def _rule_without_providers_impl(ctx):",
        "  s = []",
        "  for dep in ctx.attr.deps:",
        "    if hasattr(dep, 'target_labels'):",
        "      s.extend(dep.target_labels)",
        "  return struct(rule_deps = s)",
        "",
        "rule_without_providers = rule(",
        "  implementation = _rule_without_providers_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(aspects=[my_aspect])",
        "  },",
        "  provides = []",
        ")",
        "",
        "def _rule_with_a_impl(ctx):",
        "  return [prov_a()]",
        "",
        "rule_with_a = rule(",
        "  implementation = _rule_with_a_impl,",
        "  attrs = {",
        "    'deps': attr.label_list()",
        "  },",
        "  provides = [prov_a]",
        ")",
        "",
        "def _rule_with_ab_impl(ctx):",
        "  return [prov_a(), prov_b()]",
        "",
        "rule_with_ab = rule(",
        "  implementation = _rule_with_ab_impl,",
        "  attrs = {",
        "    'deps': attr.label_list()",
        "  },",
        "  provides = [prov_a, prov_b]",
        ")",
        "",
        "rule_with_ab_not_advertised = rule(",
        "  implementation = _rule_with_ab_impl,",
        "  attrs = {",
        "    'deps': attr.label_list()",
        "  },",
        "  provides = []",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'rule_without_providers', 'rule_with_a', 'rule_with_ab',",
        "                        'rule_with_ab_not_advertised')",
        "rule_without_providers(",
        "  name = 'main',",
        "  deps = [':target_without_providers', ':target_with_a', ':target_with_ab',",
        "          ':target_with_ab_not_advertised'],",
        ")",
        "rule_without_providers(",
        "  name = 'target_without_providers',",
        ")",
        "rule_with_a(",
        "  name = 'target_with_a',",
        "  deps = [':target_with_ab_indeps_not_reached']",
        ")",
        "rule_with_ab(",
        "  name = 'target_with_ab',",
        "  deps = [':target_with_ab_indeps_reached']",
        ")",
        "rule_with_ab(",
        "  name = 'target_with_ab_indeps_not_reached',",
        ")",
        "rule_with_ab(",
        "  name = 'target_with_ab_indeps_reached',",
        ")",
        "rule_with_ab_not_advertised(",
        "  name = 'target_with_ab_not_advertised',",
        ")");

    AnalysisResult analysisResult = update("//test:main");

    // my_aspect will only be applied on target_with_ab and target_with_ab_indeps_reached since
    // their rule (rule_with_ab) is the only rule that advertises the aspect required providers.
    // However, my_aspect cannot be propagated to target_with_ab_indeps_not_reached because it was
    // not applied to its parent (target_with_a)
    List<String> expected = new ArrayList<>();
    expected.add("//test:defs.bzl%my_aspect(@//test:target_with_ab)");
    expected.add("//test:defs.bzl%my_aspect(@//test:target_with_ab_indeps_reached)");
    assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:main");
    ConfiguredTarget target = analysisResult.getTargetsToBuild().iterator().next();
    Object ruleDepsUnchecked = target.get("rule_deps");
    assertThat(ruleDepsUnchecked).isInstanceOf(StarlarkList.class);
    StarlarkList<?> ruleDeps = (StarlarkList) ruleDepsUnchecked;
    assertThat(Starlark.toIterable(ruleDeps)).containsExactlyElementsIn(expected);
  }

  @Test
  public void testAspectRequiredProviders_listOfRequiredProvidersLists() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "prov_a = provider()",
        "prov_b = provider()",
        "prov_c = provider()",
        "",
        "def _my_aspect_impl(target, ctx):",
        "  targets_labels = [\"//test:defs.bzl%my_aspect({})\".format(target.label)]",
        "  for dep in ctx.rule.attr.deps:",
        "    if hasattr(dep, 'target_labels'):",
        "      targets_labels.extend(dep.target_labels)",
        "  return struct(target_labels = targets_labels)",
        "",
        "my_aspect = aspect(",
        "  implementation = _my_aspect_impl,",
        "  attr_aspects = ['deps'],",
        "  required_providers = [[prov_a, prov_b], [prov_c]],",
        ")",
        "",
        "def _rule_without_providers_impl(ctx):",
        "  s = []",
        "  for dep in ctx.attr.deps:",
        "    if hasattr(dep, 'target_labels'):",
        "      s.extend(dep.target_labels)",
        "  return struct(rule_deps = s)",
        "",
        "rule_without_providers = rule(",
        "  implementation = _rule_without_providers_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(aspects=[my_aspect])",
        "  },",
        "  provides = []",
        ")",
        "",
        "def _rule_with_a_impl(ctx):",
        "  return [prov_a()]",
        "",
        "rule_with_a = rule(",
        "  implementation = _rule_with_a_impl,",
        "  attrs = {",
        "    'deps': attr.label_list()",
        "  },",
        "  provides = [prov_a]",
        ")",
        "",
        "def _rule_with_c_impl(ctx):",
        "  return [prov_c()]",
        "",
        "rule_with_c = rule(",
        "  implementation = _rule_with_c_impl,",
        "  attrs = {",
        "    'deps': attr.label_list()",
        "  },",
        "  provides = [prov_c]",
        ")",
        "",
        "def _rule_with_ab_impl(ctx):",
        "  return [prov_a(), prov_b()]",
        "",
        "rule_with_ab = rule(",
        "  implementation = _rule_with_ab_impl,",
        "  attrs = {",
        "    'deps': attr.label_list()",
        "  },",
        "  provides = [prov_a, prov_b]",
        ")",
        "",
        "rule_with_ab_not_advertised = rule(",
        "  implementation = _rule_with_ab_impl,",
        "  attrs = {",
        "    'deps': attr.label_list()",
        "  },",
        "  provides = []",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'rule_without_providers', 'rule_with_a', 'rule_with_c',",
        "                        'rule_with_ab', 'rule_with_ab_not_advertised')",
        "rule_without_providers(",
        "  name = 'main',",
        "  deps = [':target_without_providers', ':target_with_a', ':target_with_c',",
        "          ':target_with_ab', 'target_with_ab_not_advertised'],",
        ")",
        "rule_without_providers(",
        "  name = 'target_without_providers',",
        ")",
        "rule_with_a(",
        "  name = 'target_with_a',",
        "  deps = [':target_with_c_indeps_not_reached'],",
        ")",
        "rule_with_c(",
        "  name = 'target_with_c',",
        ")",
        "rule_with_c(",
        "  name = 'target_with_c_indeps_reached',",
        ")",
        "rule_with_c(",
        "  name = 'target_with_c_indeps_not_reached',",
        ")",
        "rule_with_ab(",
        "  name = 'target_with_ab',",
        "  deps = [':target_with_c_indeps_reached'],",
        ")",
        "rule_with_ab_not_advertised(",
        "  name = 'target_with_ab_not_advertised',",
        ")");

    AnalysisResult analysisResult = update("//test:main");

    // my_aspect will only be applied on target_with_ab, target_wtih_c and
    // target_with_c_indeps_reached because their rules (rule_with_ab and rule_with_c) are the only
    // rules advertising the aspect required providers
    // However, my_aspect cannot be propagated to target_with_c_indeps_not_reached because it was
    // not applied to its parent (target_with_a)
    List<String> expected = new ArrayList<>();
    expected.add("//test:defs.bzl%my_aspect(@//test:target_with_ab)");
    expected.add("//test:defs.bzl%my_aspect(@//test:target_with_c)");
    expected.add("//test:defs.bzl%my_aspect(@//test:target_with_c_indeps_reached)");
    assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:main");
    ConfiguredTarget target = analysisResult.getTargetsToBuild().iterator().next();
    Object ruleDepsUnchecked = target.get("rule_deps");
    assertThat(ruleDepsUnchecked).isInstanceOf(StarlarkList.class);
    StarlarkList<?> ruleDeps = (StarlarkList) ruleDepsUnchecked;
    assertThat(Starlark.toIterable(ruleDeps)).containsExactlyElementsIn(expected);
  }

  @Test
  public void testAspectRequiredByMultipleAspects_inheritsAttrAspects() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "prov_a = provider()",
        "prov_b = provider()",
        "prov_c = provider()",
        "",
        "def _aspect_c_impl(target, ctx):",
        "  res = ['aspect_c runs on target {}'.format(target.label)]",
        "  return [prov_c(val = res)]",
        "aspect_c = aspect(",
        "  implementation = _aspect_c_impl,",
        ")",
        "",
        "def _aspect_b_impl(target, ctx):",
        "  res = []",
        "  res += target[prov_c].val",
        "  res += ['aspect_b runs on target {}'.format(target.label)]",
        "  if ctx.rule.attr.dep_b:",
        "    res += ctx.rule.attr.dep_b[prov_b].val",
        "  return [prov_b(val = res)]",
        "aspect_b = aspect(",
        "  implementation = _aspect_b_impl,",
        "  attr_aspects = ['dep_b'],",
        "  requires = [aspect_c],",
        ")",
        "",
        "def _aspect_a_impl(target, ctx):",
        "  res = []",
        "  res += target[prov_c].val",
        "  res += ['aspect_a runs on target {}'.format(target.label)]",
        "  if ctx.rule.attr.dep_a:",
        "    res += ctx.rule.attr.dep_a[prov_a].val",
        "  return [prov_a(val = res)]",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep_a'],",
        "  requires = [aspect_c],",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "  attrs = {",
        "    'dep_a': attr.label(),",
        "    'dep_b': attr.label(),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        "  dep_a = ':dep_target_a',",
        "  dep_b = ':dep_target_b',",
        ")",
        "my_rule(",
        "  name = 'dep_target_a',",
        ")",
        "my_rule(",
        "  name = 'dep_target_b',",
        ")");

    AnalysisResult analysisResult =
        update(
            ImmutableList.of("test/defs.bzl%aspect_a", "test/defs.bzl%aspect_b"),
            "//test:main_target");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    // aspect_a should run on main_target and dep_target_a and can retrieve aspect_c provider value
    // on both of them
    ConfiguredAspect aspectA = getConfiguredAspect(configuredAspects, "aspect_a");
    assertThat(aspectA).isNotNull();
    StarlarkProvider.Key aResult =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "prov_a");
    StructImpl aResultProvider = (StructImpl) aspectA.get(aResult);
    assertThat((Sequence<?>) aResultProvider.getValue("val"))
        .containsExactly(
            "aspect_c runs on target @//test:dep_target_a",
            "aspect_a runs on target @//test:dep_target_a",
            "aspect_c runs on target @//test:main_target",
            "aspect_a runs on target @//test:main_target");

    // aspect_b should run on main_target and dep_target_b and can retrieve aspect_c provider value
    // on both of them
    ConfiguredAspect aspectB = getConfiguredAspect(configuredAspects, "aspect_b");
    assertThat(aspectA).isNotNull();
    StarlarkProvider.Key bResult =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "prov_b");
    StructImpl bResultProvider = (StructImpl) aspectB.get(bResult);
    assertThat((Sequence<?>) bResultProvider.getValue("val"))
        .containsExactly(
            "aspect_c runs on target @//test:dep_target_b",
            "aspect_b runs on target @//test:dep_target_b",
            "aspect_c runs on target @//test:main_target",
            "aspect_b runs on target @//test:main_target");
  }

  @Test
  public void testAspectRequiredByMultipleAspects_inheritsRequiredProviders() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "aspect_prov_a = provider()",
        "aspect_prov_b = provider()",
        "aspect_prov_c = provider()",
        "rule_prov_a = provider()",
        "rule_prov_b = provider()",
        "rule_prov_c = provider()",
        "",
        "def _aspect_c_impl(target, ctx):",
        "  res = ['aspect_c runs on target {}'.format(target.label)]",
        "  return [aspect_prov_c(val = res)]",
        "aspect_c = aspect(",
        "  implementation = _aspect_c_impl,",
        "  required_providers = [rule_prov_c],",
        ")",
        "",
        "def _aspect_b_impl(target, ctx):",
        "  res = []",
        "  if aspect_prov_c in target:",
        "    res += target[aspect_prov_c].val",
        "  res += ['aspect_b runs on target {}'.format(target.label)]",
        "  if ctx.rule.attr.deps:",
        "    for dep in ctx.rule.attr.deps:",
        "      if aspect_prov_b in dep:",
        "        res += dep[aspect_prov_b].val",
        "  return [aspect_prov_b(val = res)]",
        "aspect_b = aspect(",
        "  implementation = _aspect_b_impl,",
        "  attr_aspects = ['deps'],",
        "  required_providers = [[rule_prov_b], [rule_prov_c]],",
        "  requires = [aspect_c],",
        ")",
        "",
        "def _aspect_a_impl(target, ctx):",
        "  res = []",
        "  if aspect_prov_c in target:",
        "    res += target[aspect_prov_c].val",
        "  res += ['aspect_a runs on target {}'.format(target.label)]",
        "  if ctx.rule.attr.deps:",
        "    for dep in ctx.rule.attr.deps:",
        "      if aspect_prov_a in dep:",
        "        res += dep[aspect_prov_a].val",
        "  return [aspect_prov_a(val = res)]",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['deps'],",
        "  required_providers = [[rule_prov_a], [rule_prov_c]],",
        "  requires = [aspect_c],",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  return [rule_prov_a(), rule_prov_b()]",
        "",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(),",
        "  },",
        "  provides = [rule_prov_a, rule_prov_b]",
        ")",
        "",
        "def _rule_with_prov_a_impl(ctx):",
        "  return [rule_prov_a()]",
        "",
        "rule_with_prov_a = rule(",
        "  implementation = _rule_with_prov_a_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(),",
        "  },",
        "  provides = [rule_prov_a]",
        ")",
        "",
        "def _rule_with_prov_b_impl(ctx):",
        "  return [rule_prov_b()]",
        "",
        "rule_with_prov_b = rule(",
        "  implementation = _rule_with_prov_b_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(),",
        "  },",
        "  provides = [rule_prov_b]",
        ")",
        "",
        "def _rule_with_prov_c_impl(ctx):",
        "  return [rule_prov_c()]",
        "rule_with_prov_c = rule(",
        "  implementation = _rule_with_prov_c_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(),",
        "  },",
        "  provides = [rule_prov_c]",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule', 'rule_with_prov_a',",
        "                        'rule_with_prov_b', 'rule_with_prov_c')",
        "my_rule(",
        "  name = 'main_target',",
        "  deps = [':dep_target_with_prov_a', ':dep_target_with_prov_b']",
        ")",
        "rule_with_prov_a(",
        "  name = 'dep_target_with_prov_a',",
        "  deps = [':dep_target_with_prov_c'],",
        ")",
        "rule_with_prov_b(",
        "  name = 'dep_target_with_prov_b',",
        "  deps = [':dep_target_with_prov_c'],",
        ")",
        "rule_with_prov_c(",
        "  name = 'dep_target_with_prov_c',",
        ")");

    AnalysisResult analysisResult =
        update(
            ImmutableList.of("test/defs.bzl%aspect_a", "test/defs.bzl%aspect_b"),
            "//test:main_target");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    // aspect_a runs on main_target, dep_target_with_prov_a and dep_target_with_prov_c and it can
    // only retrieve aspect_c provider value on dep_target_with_prov_c
    ConfiguredAspect aspectA = getConfiguredAspect(configuredAspects, "aspect_a");
    assertThat(aspectA).isNotNull();
    StarlarkProvider.Key aResult =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "aspect_prov_a");
    StructImpl aResultProvider = (StructImpl) aspectA.get(aResult);
    assertThat((Sequence<?>) aResultProvider.getValue("val"))
        .containsExactly(
            "aspect_c runs on target @//test:dep_target_with_prov_c",
            "aspect_a runs on target @//test:dep_target_with_prov_c",
            "aspect_a runs on target @//test:dep_target_with_prov_a",
            "aspect_a runs on target @//test:main_target");

    // aspect_b runs on main_target, dep_target_with_prov_b and dep_target_with_prov_c and it can
    // only retrieve aspect_c provider value on dep_target_with_prov_c
    ConfiguredAspect aspectB = getConfiguredAspect(configuredAspects, "aspect_b");
    assertThat(aspectA).isNotNull();
    StarlarkProvider.Key bResult =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "aspect_prov_b");
    StructImpl bResultProvider = (StructImpl) aspectB.get(bResult);
    assertThat((Sequence<?>) bResultProvider.getValue("val"))
        .containsExactly(
            "aspect_c runs on target @//test:dep_target_with_prov_c",
            "aspect_b runs on target @//test:dep_target_with_prov_c",
            "aspect_b runs on target @//test:dep_target_with_prov_b",
            "aspect_b runs on target @//test:main_target");
  }

  @Test
  public void testAspectRequiredByMultipleAspects_withDifferentParametersValues() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "prov_a = provider()",
        "prov_b = provider()",
        "prov_c = provider()",
        "",
        "def _aspect_c_impl(target, ctx):",
        "  res = ['aspect_c runs on target {} and param = {}'.format(target.label, ctx.attr.p)]",
        "  return [prov_c(val = res)]",
        "aspect_c = aspect(",
        "  implementation = _aspect_c_impl,",
        "  attrs = {",
        "    'p': attr.string(values=['rule_1_val', 'rule_2_val']),",
        "  },",
        ")",
        "",
        "def _aspect_b_impl(target, ctx):",
        "  res = []",
        "  res += target[prov_c].val",
        "  res += ['aspect_b runs on target {}'.format(target.label)]",
        "  if ctx.rule.attr.dep:",
        "    res += ctx.rule.attr.dep[prov_b].val",
        "  return [prov_b(val = res)]",
        "aspect_b = aspect(",
        "  implementation = _aspect_b_impl,",
        "  attr_aspects = ['dep'],",
        "  requires = [aspect_c],",
        ")",
        "",
        "def _aspect_a_impl(target, ctx):",
        "  res = []",
        "  res += target[prov_c].val",
        "  res += ['aspect_a runs on target {}'.format(target.label)]",
        "  if ctx.rule.attr.dep:",
        "    res += ctx.rule.attr.dep[prov_a].val",
        "  return [prov_a(val = res)]",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  requires = [aspect_c],",
        ")",
        "",
        "def _rule_1_impl(ctx):",
        "  return ctx.attr.dep[prov_a]",
        "",
        "rule_1 = rule(",
        "  implementation = _rule_1_impl,",
        "  attrs = {",
        "    'dep': attr.label(aspects = [aspect_a]),",
        "    'p': attr.string(values = ['rule_1_val', 'rule_2_val'])",
        "  },",
        ")",
        "",
        "def _rule_2_impl(ctx):",
        "  return ctx.attr.dep[prov_b]",
        "",
        "rule_2 = rule(",
        "  implementation = _rule_2_impl,",
        "  attrs = {",
        "    'dep': attr.label(aspects = [aspect_b]),",
        "    'p': attr.string(values = ['rule_1_val', 'rule_2_val'])",
        "  },",
        ")",
        "",
        "def _rule_3_impl(ctx):",
        "  pass",
        "",
        "rule_3 = rule(",
        "  implementation = _rule_3_impl,",
        "  attrs = {",
        "    'dep': attr.label(),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'rule_1', 'rule_2', 'rule_3')",
        "rule_1(",
        "  name = 'target_1',",
        "  dep = ':dep_1',",
        "  p = 'rule_1_val'",
        ")",
        "rule_2(",
        "  name = 'target_2',",
        "  dep = ':dep_2',",
        "  p = 'rule_2_val'",
        ")",
        "rule_3(",
        "  name = 'dep_1',",
        "  dep = ':dep_3',",
        ")",
        "rule_3(",
        "  name = 'dep_2',",
        "  dep = ':dep_3',",
        ")",
        "rule_3(",
        "  name = 'dep_3',",
        ")");

    AnalysisResult analysisResult = update("//test:target_1", "//test:target_2");

    Iterator<ConfiguredTarget> it = analysisResult.getTargetsToBuild().iterator();
    // aspect_a runs on dep_1 and dep_3 and it can retrieve aspect_c provider value on them
    // aspect_c here should get its parameter value from rule_2
    ConfiguredTarget target1 = it.next();
    StarlarkProvider.Key provAkey =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "prov_a");
    StructImpl provA = (StructImpl) target1.get(provAkey);
    assertThat((Sequence<?>) provA.getValue("val"))
        .containsExactly(
            "aspect_c runs on target @//test:dep_1 and param = rule_1_val",
            "aspect_a runs on target @//test:dep_1",
            "aspect_c runs on target @//test:dep_3 and param = rule_1_val",
            "aspect_a runs on target @//test:dep_3");

    // aspect_b runs on dep_2 and dep_3 and it can retrieve aspect_c provider value on them.
    // aspect_c here should get its parameter value from rule_2
    ConfiguredTarget target2 = it.next();
    StarlarkProvider.Key provBkey =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "prov_b");
    StructImpl provB = (StructImpl) target2.get(provBkey);
    assertThat((Sequence<?>) provB.getValue("val"))
        .containsExactly(
            "aspect_c runs on target @//test:dep_2 and param = rule_2_val",
            "aspect_b runs on target @//test:dep_2",
            "aspect_c runs on target @//test:dep_3 and param = rule_2_val",
            "aspect_b runs on target @//test:dep_3");
  }

  @Test
  public void testAspectRequiresAspect_requireNativeAspect() throws Exception {
    exposeNativeAspectToStarlark();
    scratch.file(
        "test/defs.bzl",
        "prov_a = provider()",
        "def _impl(target, ctx):",
        "  res = 'aspect_a on target {} '.format(target.label)",
        "  if hasattr(target, 'native_aspect_prov'):",
        "    res += 'can see native aspect provider'",
        "  else:",
        "    res += 'cannot see native aspect provider'",
        "  complete_res = [res]",
        "  if hasattr(ctx.rule.attr, 'dep'):",
        "    complete_res += ctx.rule.attr.dep[prov_a].val",
        "  return [prov_a(val = complete_res)]",
        "aspect_a = aspect(implementation = _impl,",
        "                  requires = [starlark_native_aspect],",
        "                  attr_aspects = ['dep'],)",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(implementation = _my_rule_impl,",
        "               attrs = {'dep': attr.label()})");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        "  dep = ':dep_1',",
        ")",
        "my_rule(",
        "  name = 'dep_1',",
        "  dep = ':dep_2',",
        ")",
        "honest(",
        "  name = 'dep_2',",
        ")");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/defs.bzl%aspect_a"), "//test:main_target");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    // aspect_a runs on main_target, dep_1 and dep_2 but it can only see the required native aspect
    // run on dep_2 because its rule satisfies its required provider.
    ConfiguredAspect aspectA = getConfiguredAspect(configuredAspects, "aspect_a");
    assertThat(aspectA).isNotNull();
    StarlarkProvider.Key aResult =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "prov_a");
    StructImpl aResultProvider = (StructImpl) aspectA.get(aResult);
    assertThat((Sequence<?>) aResultProvider.getValue("val"))
        .containsExactly(
            "aspect_a on target @//test:main_target cannot see native aspect provider",
            "aspect_a on target @//test:dep_1 cannot see native aspect provider",
            "aspect_a on target @//test:dep_2 can see native aspect provider");
  }

  @Test
  public void testAspectRequiresAspect_aspectsParameters() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "RequiredAspectProv = provider()",
        "BaseAspectProv = provider()",
        "",
        "def _required_aspect_impl(target, ctx):",
        "  p1_val = 'In required_aspect, p1 = {} on target {}'.format(ctx.attr.p1, target.label)",
        "  p2_val = 'invalid value'",
        "  if not hasattr(ctx.attr, 'p2'):",
        "    p2_val = 'In required_aspect, p2 not found on target {}'.format(target.label)",
        "  return [RequiredAspectProv(p1_val = p1_val, p2_val = p2_val)]",
        "required_aspect = aspect(",
        "  implementation = _required_aspect_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = {'p1' : attr.string(values = ['p1_v1', 'p1_v2'])}",
        ")",
        "",
        "def _base_aspect_impl(target, ctx):",
        "  p2_val = 'In base_aspect, p2 = {} on target {}'.format(ctx.attr.p2, target.label)",
        "  p1_val = 'invalid value'",
        "  if not hasattr(ctx.attr, 'p1'):",
        "    p1_val = 'In base_aspect, p1 not found on target {}'.format(target.label)",
        "  return [BaseAspectProv(p1_val = p1_val, p2_val = p2_val)]",
        "base_aspect = aspect(",
        "  implementation = _base_aspect_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = {'p2' : attr.string(values = ['p2_v1', 'p2_v2'])},",
        "  requires = [required_aspect],",
        ")",
        "",
        "def _main_rule_impl(ctx):",
        "  return [ctx.attr.dep[RequiredAspectProv], ctx.attr.dep[BaseAspectProv]]",
        "def _dep_rule_impl(ctx):",
        "  pass",
        "",
        "main_rule = rule(",
        "  implementation = _main_rule_impl,",
        "  attrs = {",
        "    'dep': attr.label(aspects=[base_aspect]),",
        "    'p1' : attr.string(values = ['p1_v1', 'p1_v2']),",
        "    'p2' : attr.string(values = ['p2_v1', 'p2_v2'])",
        "  },",
        ")",
        "",
        "dep_rule = rule(",
        "  implementation = _dep_rule_impl,",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'main_rule', 'dep_rule')",
        "main_rule(",
        "  name = 'main',",
        "  dep = ':dep_target',",
        "  p1 = 'p1_v1',",
        "  p2 = 'p2_v1'",
        ")",
        "dep_rule(",
        "  name = 'dep_target',",
        ")");

    AnalysisResult analysisResult = update("//test:main");

    // Both base_aspect and required_aspect can get their parameters values from the base rule
    ConfiguredTarget configuredTarget =
        Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    StarlarkProvider.Key requiredAspectProv =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "RequiredAspectProv");
    StructImpl requiredAspectProvider = (StructImpl) configuredTarget.get(requiredAspectProv);
    assertThat(requiredAspectProvider.getValue("p1_val"))
        .isEqualTo("In required_aspect, p1 = p1_v1 on target @//test:dep_target");
    assertThat(requiredAspectProvider.getValue("p2_val"))
        .isEqualTo("In required_aspect, p2 not found on target @//test:dep_target");

    StarlarkProvider.Key baseAspectProv =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "BaseAspectProv");
    StructImpl baseAspectProvider = (StructImpl) configuredTarget.get(baseAspectProv);
    assertThat(baseAspectProvider.getValue("p1_val"))
        .isEqualTo("In base_aspect, p1 not found on target @//test:dep_target");
    assertThat(baseAspectProvider.getValue("p2_val"))
        .isEqualTo("In base_aspect, p2 = p2_v1 on target @//test:dep_target");
  }

  @Test
  public void testAspectRequiresAspect_ruleAttributes() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "RequiredAspectProv = provider()",
        "BaseAspectProv = provider()",
        "",
        "def _required_aspect_impl(target, ctx):",
        "  p_val = 'In required_aspect, p = {} on target {}'.format(ctx.rule.attr.p, target.label)",
        "  return [RequiredAspectProv(p_val = p_val)]",
        "required_aspect = aspect(",
        "  implementation = _required_aspect_impl,",
        ")",
        "",
        "def _base_aspect_impl(target, ctx):",
        "  p_val = 'In base_aspect, p = {} on target {}'.format(ctx.rule.attr.p, target.label)",
        "  return [BaseAspectProv(p_val = p_val)]",
        "base_aspect = aspect(",
        "  implementation = _base_aspect_impl,",
        "  attr_aspects = ['dep'],",
        "  requires = [required_aspect],",
        ")",
        "",
        "def _main_rule_impl(ctx):",
        "  return [ctx.attr.dep[RequiredAspectProv], ctx.attr.dep[BaseAspectProv]]",
        "def _dep_rule_impl(ctx):",
        "  pass",
        "",
        "main_rule = rule(",
        "  implementation = _main_rule_impl,",
        "  attrs = {",
        "    'dep': attr.label(aspects=[base_aspect]),",
        "  },",
        ")",
        "",
        "dep_rule = rule(",
        "  implementation = _dep_rule_impl,",
        "  attrs = {",
        "    'p' : attr.string(values = ['p_v1', 'p_v2']),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'main_rule', 'dep_rule')",
        "main_rule(",
        "  name = 'main',",
        "  dep = ':dep_target',",
        ")",
        "dep_rule(",
        "  name = 'dep_target',",
        "  p = 'p_v2',",
        ")");

    AnalysisResult analysisResult = update("//test:main");

    // Both base_aspect and required_aspect can see the attributes of the rule they run on
    ConfiguredTarget configuredTarget =
        Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    StarlarkProvider.Key requiredAspectProv =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "RequiredAspectProv");
    StructImpl requiredAspectProvider = (StructImpl) configuredTarget.get(requiredAspectProv);
    assertThat(requiredAspectProvider.getValue("p_val"))
        .isEqualTo("In required_aspect, p = p_v2 on target @//test:dep_target");

    StarlarkProvider.Key baseAspectProv =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "BaseAspectProv");
    StructImpl baseAspectProvider = (StructImpl) configuredTarget.get(baseAspectProv);
    assertThat(baseAspectProvider.getValue("p_val"))
        .isEqualTo("In base_aspect, p = p_v2 on target @//test:dep_target");
  }

  @Test
  public void testAspectRequiresAspect_inheritPropagationAttributes() throws Exception {
    // base_aspect propagates over base_dep attribute and requires first_required_aspect which
    // propagates over first_dep attribute and requires second_required aspect which propagates
    // over second_dep attribute
    scratch.file(
        "test/defs.bzl",
        "BaseAspectProv = provider()",
        "FirstRequiredAspectProv = provider()",
        "SecondRequiredAspectProv = provider()",
        "",
        "def _second_required_aspect_impl(target, ctx):",
        "  result = []",
        "  if getattr(ctx.rule.attr, 'second_dep'):",
        "    result += getattr(ctx.rule.attr, 'second_dep')[SecondRequiredAspectProv].result",
        "  result += ['second_required_aspect run on target {}'.format(target.label)]",
        "  return [SecondRequiredAspectProv(result = result)]",
        "second_required_aspect = aspect(",
        "  implementation = _second_required_aspect_impl,",
        "  attr_aspects = ['second_dep'],",
        ")",
        "",
        "def _first_required_aspect_impl(target, ctx):",
        "  result = []",
        "  result += target[SecondRequiredAspectProv].result",
        "  if getattr(ctx.rule.attr, 'first_dep'):",
        "    result += getattr(ctx.rule.attr, 'first_dep')[FirstRequiredAspectProv].result",
        "  result += ['first_required_aspect run on target {}'.format(target.label)]",
        "  return [FirstRequiredAspectProv(result = result)]",
        "first_required_aspect = aspect(",
        "  implementation = _first_required_aspect_impl,",
        "  attr_aspects = ['first_dep'],",
        "  requires = [second_required_aspect],",
        ")",
        "",
        "def _base_aspect_impl(target, ctx):",
        "  result = []",
        "  result += target[FirstRequiredAspectProv].result",
        "  if getattr(ctx.rule.attr, 'base_dep'):",
        "    result += getattr(ctx.rule.attr, 'base_dep')[BaseAspectProv].result",
        "  result += ['base_aspect run on target {}'.format(target.label)]",
        "  return [BaseAspectProv(result = result)]",
        "base_aspect = aspect(",
        "  implementation = _base_aspect_impl,",
        "  attr_aspects = ['base_dep'],",
        "  requires = [first_required_aspect],",
        ")",
        "",
        "def _main_rule_impl(ctx):",
        "  return [ctx.attr.dep[BaseAspectProv]]",
        "def _dep_rule_impl(ctx):",
        "  pass",
        "",
        "main_rule = rule(",
        "  implementation = _main_rule_impl,",
        "  attrs = {",
        "    'dep': attr.label(aspects=[base_aspect]),",
        "  },",
        ")",
        "",
        "dep_rule = rule(",
        "  implementation = _dep_rule_impl,",
        "  attrs = {",
        "    'base_dep': attr.label(),",
        "    'first_dep': attr.label(),",
        "    'second_dep': attr.label()",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'main_rule', 'dep_rule')",
        "main_rule(",
        "  name = 'main',",
        "  dep = ':dep_target',",
        ")",
        "dep_rule(",
        "  name = 'dep_target',",
        "  base_dep = ':base_dep_target',",
        "  first_dep = ':first_dep_target',",
        "  second_dep = ':second_dep_target',",
        ")",
        "dep_rule(",
        "  name = 'base_dep_target',",
        ")",
        "dep_rule(",
        "  name = 'first_dep_target',",
        ")",
        "dep_rule(",
        "  name = 'second_dep_target',",
        ")");

    AnalysisResult analysisResult = update("//test:main");

    // base_aspect should propagate only along its attr_aspects: 'base_dep'
    // first_required_aspect should propagate along 'base_dep' and 'first_dep'
    // second_required_aspect should propagate along 'base_dep', 'first_dep' and `second_dep`
    ConfiguredTarget configuredTarget =
        Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    StarlarkProvider.Key baseAspectProv =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "BaseAspectProv");
    StructImpl baseAspectProvider = (StructImpl) configuredTarget.get(baseAspectProv);
    assertThat((Sequence<?>) baseAspectProvider.getValue("result"))
        .containsExactly(
            "second_required_aspect run on target @//test:second_dep_target",
            "second_required_aspect run on target @//test:dep_target",
            "second_required_aspect run on target @//test:first_dep_target",
            "first_required_aspect run on target @//test:first_dep_target",
            "first_required_aspect run on target @//test:dep_target",
            "second_required_aspect run on target @//test:base_dep_target",
            "first_required_aspect run on target @//test:base_dep_target",
            "base_aspect run on target @//test:base_dep_target",
            "base_aspect run on target @//test:dep_target");
  }

  @Test
  public void testAspectRequiresAspect_inheritRequiredProviders() throws Exception {
    // aspect_a requires provider Prov_A and requires aspect_b which requires
    // provider Prov_B and requires aspect_c which requires provider Prov_C
    scratch.file(
        "test/defs.bzl",
        "Prov_A = provider()",
        "Prov_B = provider()",
        "Prov_C = provider()",
        "",
        "CollectorProv = provider()",
        "",
        "def _aspect_c_impl(target, ctx):",
        "  collector_result = ['aspect_c run on target {} and value of Prov_C ="
            + " {}'.format(target.label, target[Prov_C].val)]",
        "  return [CollectorProv(result = collector_result)]",
        "aspect_c = aspect(",
        "  implementation = _aspect_c_impl,",
        "  required_providers = [Prov_C],",
        "  attr_aspects = ['dep'],",
        ")",
        "",
        "def _aspect_b_impl(target, ctx):",
        "  collector_result = []",
        "  collector_result += ctx.rule.attr.dep[CollectorProv].result",
        "  collector_result += ['aspect_b run on target {} and value of Prov_B ="
            + " {}'.format(target.label, target[Prov_B].val)]",
        "  return [ CollectorProv(result = collector_result)]",
        "aspect_b = aspect(",
        "  implementation = _aspect_b_impl,",
        "  required_providers = [Prov_B],",
        "  requires = [aspect_c],",
        "  attr_aspects = ['dep'],",
        ")",
        "",
        "def _aspect_a_impl(target, ctx):",
        "  collector_result = []",
        "  collector_result += ctx.rule.attr.dep[CollectorProv].result",
        "  collector_result += ['aspect_a run on target {} and value of Prov_A ="
            + " {}'.format(target.label, target[Prov_A].val)]",
        "  return [CollectorProv(result = collector_result)]",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  required_providers = [Prov_A],",
        "  requires = [aspect_b],",
        ")",
        "",
        "def _main_rule_impl(ctx):",
        "  return [ctx.attr.dep[CollectorProv]]",
        "main_rule = rule(",
        "  implementation = _main_rule_impl,",
        "  attrs = {",
        "    'dep': attr.label(aspects = [aspect_a]),",
        "  },",
        ")",
        "",
        "def _rule_with_prov_a_impl(ctx):",
        "  return [Prov_A(val='val_a')]",
        "rule_with_prov_a = rule(",
        "  implementation = _rule_with_prov_a_impl,",
        "  attrs = {",
        "    'dep': attr.label(),",
        "  },",
        "  provides = [Prov_A]",
        ")",
        "",
        "def _rule_with_prov_b_impl(ctx):",
        "  return [Prov_B(val = 'val_b')]",
        "rule_with_prov_b = rule(",
        "  implementation = _rule_with_prov_b_impl,",
        "  attrs = {",
        "    'dep': attr.label(),",
        "  },",
        "  provides = [Prov_B]",
        ")",
        "",
        "def _rule_with_prov_c_impl(ctx):",
        "  return [Prov_C(val = 'val_c')]",
        "rule_with_prov_c = rule(",
        "  implementation = _rule_with_prov_c_impl,",
        "  provides = [Prov_C]",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'main_rule', 'rule_with_prov_a', 'rule_with_prov_b',"
            + " 'rule_with_prov_c')",
        "main_rule(",
        "  name = 'main',",
        "  dep = ':target_with_prov_a',",
        ")",
        "rule_with_prov_a(",
        "  name = 'target_with_prov_a',",
        "  dep = ':target_with_prov_b'",
        ")",
        "rule_with_prov_b(",
        "  name = 'target_with_prov_b',",
        "  dep = ':target_with_prov_c'",
        ")",
        "rule_with_prov_c(",
        "  name = 'target_with_prov_c'",
        ")");

    AnalysisResult analysisResult = update("//test:main");

    // aspect_a should only run on target_with_prov_a, aspect_b should only run on
    // target_with_prov_b and aspect_c should only run on target_with_prov_c.
    // aspect_c will reach target target_with_prov_c because it inherits the required_providers of
    // aspect_b otherwise it would have stopped propagating after target_with_prov_b.
    ConfiguredTarget configuredTarget =
        Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    StarlarkProvider.Key collectorProv =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "CollectorProv");
    StructImpl collectorProvider = (StructImpl) configuredTarget.get(collectorProv);
    assertThat((Sequence<?>) collectorProvider.getValue("result"))
        .containsExactly(
            "aspect_c run on target @//test:target_with_prov_c and value of Prov_C = val_c",
            "aspect_b run on target @//test:target_with_prov_b and value of Prov_B = val_b",
            "aspect_a run on target @//test:target_with_prov_a and value of Prov_A = val_a")
        .inOrder();
  }

  @Test
  public void testAspectRequiresAspect_inspectRequiredAspectActions() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _required_aspect_impl(target, ctx):",
        "  f = ctx.actions.declare_file('dummy.txt')",
        "  ctx.actions.run_shell(outputs = [f], command='echo xxx > $(location f)',",
        "                        mnemonic='RequiredAspectAction')",
        "  return struct()",
        "required_aspect = aspect(",
        "  implementation = _required_aspect_impl,",
        ")",
        "",
        "def _base_aspect_impl(target, ctx):",
        "  required_aspect_action = None",
        "  for action in target.actions:",
        "    if action.mnemonic == 'RequiredAspectAction':",
        "      required_aspect_action = action",
        "  if required_aspect_action:",
        "    return struct(result = 'base_aspect can see required_aspect action')",
        "  else:",
        "    return struct(result = 'base_aspect cannot see required_aspect action')",
        "base_aspect = aspect(",
        "  implementation = _base_aspect_impl,",
        "  attr_aspects = ['dep'],",
        "  requires = [required_aspect]",
        ")",
        "",
        "def _main_rule_impl(ctx):",
        "  return struct(result = ctx.attr.dep.result)",
        "main_rule = rule(",
        "  implementation = _main_rule_impl,",
        "  attrs = {",
        "    'dep': attr.label(aspects = [base_aspect]),",
        "  },",
        ")",
        "",
        "def _dep_rule_impl(ctx):",
        "  pass",
        "dep_rule = rule(",
        "  implementation = _dep_rule_impl,",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'main_rule', 'dep_rule')",
        "main_rule(",
        "  name = 'main',",
        "  dep = ':dep_target',",
        ")",
        "dep_rule(",
        "  name = 'dep_target',",
        ")");

    AnalysisResult analysisResult = update("//test:main");

    ConfiguredTarget configuredTarget =
        Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    String result = (String) configuredTarget.get("result");
    assertThat(result).isEqualTo("base_aspect can see required_aspect action");
  }

  @Test
  public void testAspectRequiresAspect_inspectRequiredAspectGeneratedFiles() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _required_aspect_impl(target, ctx):",
        "  file = ctx.actions.declare_file('required_aspect_file')",
        "  ctx.actions.write(file, 'data')",
        "  return [OutputGroupInfo(out = [file])]",
        "required_aspect = aspect(",
        "  implementation = _required_aspect_impl,",
        ")",
        "",
        "def _base_aspect_impl(target, ctx):",
        "  files = ['base_aspect can see file ' + f.path.split('/')[-1] for f in"
            + " target[OutputGroupInfo].out.to_list()]",
        "  return struct(my_files = files)",
        "base_aspect = aspect(",
        "  implementation = _base_aspect_impl,",
        "  attr_aspects = ['dep'],",
        "  requires = [required_aspect]",
        ")",
        "",
        "def _main_rule_impl(ctx):",
        "  return struct(my_files = ctx.attr.dep.my_files)",
        "main_rule = rule(",
        "  implementation = _main_rule_impl,",
        "  attrs = {",
        "    'dep': attr.label(aspects = [base_aspect]),",
        "  },",
        ")",
        "",
        "def _dep_rule_impl(ctx):",
        "  pass",
        "dep_rule = rule(",
        "  implementation = _dep_rule_impl,",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'main_rule', 'dep_rule')",
        "main_rule(",
        "  name = 'main',",
        "  dep = ':dep_target',",
        ")",
        "dep_rule(",
        "  name = 'dep_target',",
        ")");

    AnalysisResult analysisResult = update("//test:main");

    ConfiguredTarget configuredTarget =
        Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    StarlarkList<?> files = (StarlarkList) configuredTarget.get("my_files");
    assertThat(Starlark.toIterable(files))
        .containsExactly("base_aspect can see file required_aspect_file");
  }

  @Test
  public void testAspectRequiresAspect_withRequiredAspectProvidersSatisfied() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "prov_a = provider()",
        "prov_b = provider()",
        "prov_b_forwarded = provider()",
        "",
        "def _aspect_b_impl(target, ctx):",
        "  result = 'aspect_b on target {} '.format(target.label)",
        "  if prov_b in target:",
        "    result += 'found prov_b = {}'.format(target[prov_b].val)",
        "    return struct(aspect_b_result = result,",
        "                  providers = [prov_b_forwarded(val = target[prov_b].val)])",
        "  else:",
        "    result += 'cannot find prov_b'",
        "    return struct(aspect_b_result = result)",
        "aspect_b = aspect(",
        "  implementation = _aspect_b_impl,",
        "  required_aspect_providers = [prov_b]",
        ")",
        "",
        "def _aspect_a_impl(target, ctx):",
        "  result = 'aspect_a on target {} '.format(target.label)",
        "  if prov_a in target:",
        "    result += 'found prov_a = {}'.format(target[prov_a].val)",
        "  else:",
        "    result += 'cannot find prov_a'",
        "  if prov_b_forwarded in target:",
        "    result += ' and found prov_b = {}'.format(target[prov_b_forwarded].val)",
        "  else:",
        "    result += ' but cannot find prov_b'",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  required_aspect_providers = [prov_a],",
        "  attr_aspects = ['dep'],",
        "  requires = [aspect_b]",
        ")",
        "",
        "def _aspect_with_prov_a_impl(target, ctx):",
        "  return [prov_a(val = 'a1')]",
        "aspect_with_prov_a = aspect(",
        "  implementation = _aspect_with_prov_a_impl,",
        "  provides = [prov_a],",
        "  attr_aspects = ['dep'],",
        ")",
        "",
        "def _aspect_with_prov_b_impl(target, ctx):",
        "  return [prov_b(val = 'b1')]",
        "aspect_with_prov_b = aspect(",
        "  implementation = _aspect_with_prov_b_impl,",
        "  provides = [prov_b],",
        "  attr_aspects = ['dep'],",
        ")",
        "",
        "def _main_rule_impl(ctx):",
        "  return struct(aspect_a_result = ctx.attr.dep.aspect_a_result,",
        "                aspect_b_result = ctx.attr.dep.aspect_b_result)",
        "main_rule = rule(",
        "  implementation = _main_rule_impl,",
        "  attrs = {",
        "    'dep': attr.label(aspects = [aspect_with_prov_a, aspect_with_prov_b, aspect_a]),",
        "  },",
        ")",
        "",
        "def _dep_rule_impl(ctx):",
        "  pass",
        "dep_rule = rule(",
        "  implementation = _dep_rule_impl,",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'main_rule', 'dep_rule')",
        "main_rule(",
        "  name = 'main',",
        "  dep = ':dep_target',",
        ")",
        "dep_rule(",
        "  name = 'dep_target',",
        ")");

    AnalysisResult analysisResult = update("//test:main");

    ConfiguredTarget configuredTarget =
        Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    String aspectAResult = (String) configuredTarget.get("aspect_a_result");
    assertThat(aspectAResult)
        .isEqualTo("aspect_a on target @//test:dep_target found prov_a = a1 and found prov_b = b1");

    String aspectBResult = (String) configuredTarget.get("aspect_b_result");
    assertThat(aspectBResult).isEqualTo("aspect_b on target @//test:dep_target found prov_b = b1");
  }

  @Test
  public void testAspectRequiresAspect_withRequiredAspectProvidersNotFound() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "prov_a = provider()",
        "prov_b = provider()",
        "",
        "def _aspect_b_impl(target, ctx):",
        "  result = 'aspect_b on target {} '.format(target.label)",
        "  if prov_b in target:",
        "    result += 'found prov_b = {}'.format(target[prov_b].val)",
        "  else:",
        "    result += 'cannot find prov_b'",
        "  return struct(aspect_b_result = result)",
        "aspect_b = aspect(",
        "  implementation = _aspect_b_impl,",
        "  required_aspect_providers = [prov_b]",
        ")",
        "",
        "def _aspect_a_impl(target, ctx):",
        "  result = 'aspect_a on target {} '.format(target.label)",
        "  if prov_a in target:",
        "    result += 'found prov_a = {}'.format(target[prov_a].val)",
        "  else:",
        "    result += 'cannot find prov_a'",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  required_aspect_providers = [prov_a],",
        "  attr_aspects = ['dep'],",
        "  requires = [aspect_b]",
        ")",
        "",
        "def _aspect_with_prov_a_impl(target, ctx):",
        "  return [prov_a(val = 'a1')]",
        "aspect_with_prov_a = aspect(",
        "  implementation = _aspect_with_prov_a_impl,",
        "  provides = [prov_a],",
        "  attr_aspects = ['dep'],",
        ")",
        "",
        "def _main_rule_impl(ctx):",
        "  return struct(aspect_a_result = ctx.attr.dep.aspect_a_result,",
        "                aspect_b_result = ctx.attr.dep.aspect_b_result)",
        "main_rule = rule(",
        "  implementation = _main_rule_impl,",
        "  attrs = {",
        "    'dep': attr.label(aspects = [aspect_with_prov_a, aspect_a]),",
        "  },",
        ")",
        "",
        "def _dep_rule_impl(ctx):",
        "  pass",
        "dep_rule = rule(",
        "  implementation = _dep_rule_impl,",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'main_rule', 'dep_rule')",
        "main_rule(",
        "  name = 'main',",
        "  dep = ':dep_target',",
        ")",
        "dep_rule(",
        "  name = 'dep_target',",
        ")");

    AnalysisResult analysisResult = update("//test:main");

    ConfiguredTarget configuredTarget =
        Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    String aspectAResult = (String) configuredTarget.get("aspect_a_result");
    assertThat(aspectAResult).isEqualTo("aspect_a on target @//test:dep_target found prov_a = a1");

    String aspectBResult = (String) configuredTarget.get("aspect_b_result");
    assertThat(aspectBResult).isEqualTo("aspect_b on target @//test:dep_target cannot find prov_b");
  }

  /**
   * --aspects = a3, a2, a1: aspect a1 requires provider a1p, aspect a2 requires provider a2p and
   * provides a1p and aspect a3 provides a2p. The three aspects will propagate together but aspect
   * a1 will only see a1p and aspect a2 will only see a2p.
   */
  @Test
  public void testTopLevelAspectOnAspect_stackOfAspects() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "a1p = provider()",
        "a2p = provider()",
        "a1_result = provider()",
        "a2_result = provider()",
        "a3_result = provider()",
        "",
        "def _a1_impl(target, ctx):",
        "  result = 'aspect a1 on target {}'.format(target.label)",
        "  if a1p in target:",
        "    result += ' sees a1p = {}'.format(target[a1p].value)",
        "  else:",
        "    result += ' cannot see a1p'",
        "  if a2p in target:",
        "    result += ' and sees a2p = {}'.format(target[a2p].value)",
        "  else:",
        "    result += ' and cannot see a2p'",
        "  complete_result = []",
        "  if ctx.rule.attr.dep:",
        "    complete_result = ctx.rule.attr.dep[a1_result].value + [result]",
        "  else:",
        "    complete_result = [result]",
        "  return [a1_result(value = complete_result)]",
        "a1 = aspect(",
        "  implementation = _a1_impl,",
        "  attr_aspects = ['dep'],",
        "  required_aspect_providers = [a1p]",
        ")",
        "",
        "def _a2_impl(target, ctx):",
        "  result = 'aspect a2 on target {}'.format(target.label)",
        "  if a1p in target:",
        "    result += ' sees a1p = {}'.format(target[a1p].value)",
        "  else:",
        "    result += ' cannot see a1p'",
        "  if a2p in target:",
        "    result += ' and sees a2p = {}'.format(target[a2p].value)",
        "  else:",
        "    result += ' and cannot see a2p'",
        "  complete_result = []",
        "  if ctx.rule.attr.dep:",
        "    complete_result = ctx.rule.attr.dep[a2_result].value + [result]",
        "  else:",
        "    complete_result = [result]",
        "  return [a2_result(value = complete_result), a1p(value = 'a1p_val')]",
        "a2 = aspect(",
        "  implementation = _a2_impl,",
        "  attr_aspects = ['dep'],",
        "  provides = [a1p],",
        "  required_aspect_providers = [a2p],",
        ")",
        "",
        "def _a3_impl(target, ctx):",
        "  result = 'aspect a3 on target {}'.format(target.label)",
        "  if a1p in target:",
        "    result += ' sees a1p = {}'.format(target[a1p].value)",
        "  else:",
        "    result += ' cannot see a1p'",
        "  if a2p in target:",
        "    result += ' and sees a2p = {}'.format(target[a2p].value)",
        "  else:",
        "    result += ' and cannot see a2p'",
        "  complete_result = []",
        "  if ctx.rule.attr.dep:",
        "    complete_result = ctx.rule.attr.dep[a3_result].value + [result]",
        "  else:",
        "    complete_result = [result]",
        "  return [a3_result(value = complete_result), a2p(value = 'a2p_val')]",
        "a3 = aspect(",
        "  implementation = _a3_impl,",
        "  attr_aspects = ['dep'],",
        "  provides = [a2p],",
        ")",
        "",
        "def _simple_rule_impl(ctx):",
        "  pass",
        "simple_rule = rule(",
        "  implementation = _simple_rule_impl,",
        "  attrs = {",
        "    'dep': attr.label(),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'simple_rule')",
        "simple_rule(",
        "  name = 'main',",
        "  dep = ':dep_target',",
        ")",
        "simple_rule(",
        "  name = 'dep_target',",
        ")");

    AnalysisResult analysisResult =
        update(
            ImmutableList.of("test/defs.bzl%a3", "test/defs.bzl%a2", "test/defs.bzl%a1"),
            "//test:main");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect a3 = getConfiguredAspect(configuredAspects, "a3");
    assertThat(a3).isNotNull();
    StarlarkProvider.Key a3Result =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "a3_result");
    StructImpl a3ResultProvider = (StructImpl) a3.get(a3Result);
    assertThat((Sequence<?>) a3ResultProvider.getValue("value"))
        .containsExactly(
            "aspect a3 on target @//test:dep_target cannot see a1p and cannot see a2p",
            "aspect a3 on target @//test:main cannot see a1p and cannot see a2p");

    ConfiguredAspect a2 = getConfiguredAspect(configuredAspects, "a2");
    assertThat(a2).isNotNull();
    StarlarkProvider.Key a2Result =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "a2_result");
    StructImpl a2ResultProvider = (StructImpl) a2.get(a2Result);
    assertThat((Sequence<?>) a2ResultProvider.getValue("value"))
        .containsExactly(
            "aspect a2 on target @//test:dep_target cannot see a1p and sees a2p = a2p_val",
            "aspect a2 on target @//test:main cannot see a1p and sees a2p = a2p_val");

    ConfiguredAspect a1 = getConfiguredAspect(configuredAspects, "a1");
    assertThat(a1).isNotNull();
    StarlarkProvider.Key a1Result =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "a1_result");
    StructImpl a1ResultProvider = (StructImpl) a1.get(a1Result);
    assertThat((Sequence<?>) a1ResultProvider.getValue("value"))
        .containsExactly(
            "aspect a1 on target @//test:dep_target sees a1p = a1p_val and cannot see a2p",
            "aspect a1 on target @//test:main sees a1p = a1p_val and cannot see a2p");
  }

  /**
   * --aspects = a3, a2, a1: aspect a1 requires provider a1p, aspect a2 and aspect a3 provides a1p.
   * This should fail because provider a1p is provided twice.
   */
  @Test
  public void testTopLevelAspectOnAspect_requiredProviderProvidedTwiceFailed() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "a1p = provider()",
        "a1_result = provider()",
        "",
        "def _a1_impl(target, ctx):",
        "  result = 'aspect a1 on target {}'.format(target.label)",
        "  if a1p in target:",
        "    result += ' sees a1p = {}'.format(target[a1p].value)",
        "  else:",
        "    result += ' cannot see a1p'",
        "  complete_result = []",
        "  if ctx.rule.attr.dep:",
        "    complete_result = ctx.rule.attr.dep[a1_result].value + [result]",
        "  else:",
        "    complete_result = [result]",
        "  return [a1_result(value = complete_result)]",
        "a1 = aspect(",
        "  implementation = _a1_impl,",
        "  attr_aspects = ['dep'],",
        "  required_aspect_providers = [a1p]",
        ")",
        "",
        "def _a2_impl(target, ctx):",
        "  return [a1p(value = 'a1p_a2_val')]",
        "a2 = aspect(",
        "  implementation = _a2_impl,",
        "  attr_aspects = ['dep'],",
        "  provides = [a1p],",
        ")",
        "",
        "def _a3_impl(target, ctx):",
        "  return [a1p(value = 'a1p_a3_val')]",
        "a3 = aspect(",
        "  implementation = _a3_impl,",
        "  attr_aspects = ['dep'],",
        "  provides = [a1p],",
        ")",
        "",
        "def _simple_rule_impl(ctx):",
        "  pass",
        "simple_rule = rule(",
        "  implementation = _simple_rule_impl,",
        "  attrs = {",
        "    'dep': attr.label(),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'simple_rule')",
        "simple_rule(",
        "  name = 'main',",
        "  dep = ':dep_target',",
        ")",
        "simple_rule(",
        "  name = 'dep_target',",
        ")");
    reporter.removeHandler(failFastHandler);

    // The call to `update` does not throw an exception when "--keep_going" is passed in the
    // WithKeepGoing test suite. Otherwise, it throws ViewCreationFailedException.
    if (keepGoing()) {
      AnalysisResult result =
          update(
              ImmutableList.of("test/defs.bzl%a3", "test/defs.bzl%a2", "test/defs.bzl%a1"),
              "//test:main");
      assertThat(result.hasError()).isTrue();
    } else {
      assertThrows(
          ViewCreationFailedException.class,
          () ->
              update(
                  ImmutableList.of("test/defs.bzl%a3", "test/defs.bzl%a2", "test/defs.bzl%a1"),
                  "//test:main"));
    }
    assertContainsEvent("ERROR /workspace/test/BUILD:2:12: Provider a1p provided twice");
  }

  /**
   * --aspects = a3, a1, a2: aspect a1 requires provider a1p, aspect a2 and aspect a3 provide a1p.
   * a1 should see the value provided by a3 because a3 is listed before a1.
   */
  @Test
  public void testTopLevelAspectOnAspect_requiredProviderProvidedTwicePassed() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "a1p = provider()",
        "a1_result = provider()",
        "",
        "def _a1_impl(target, ctx):",
        "  result = 'aspect a1 on target {}'.format(target.label)",
        "  if a1p in target:",
        "    result += ' sees a1p = {}'.format(target[a1p].value)",
        "  else:",
        "    result += ' cannot see a1p'",
        "  complete_result = []",
        "  if ctx.rule.attr.dep:",
        "    complete_result = ctx.rule.attr.dep[a1_result].value + [result]",
        "  else:",
        "    complete_result = [result]",
        "  return [a1_result(value = complete_result)]",
        "a1 = aspect(",
        "  implementation = _a1_impl,",
        "  attr_aspects = ['dep'],",
        "  required_aspect_providers = [a1p]",
        ")",
        "",
        "def _a2_impl(target, ctx):",
        "  return [a1p(value = 'a1p_a2_val')]",
        "a2 = aspect(",
        "  implementation = _a2_impl,",
        "  attr_aspects = ['dep'],",
        "  provides = [a1p],",
        ")",
        "",
        "def _a3_impl(target, ctx):",
        "  return [a1p(value = 'a1p_a3_val')]",
        "a3 = aspect(",
        "  implementation = _a3_impl,",
        "  attr_aspects = ['dep'],",
        "  provides = [a1p],",
        ")",
        "",
        "def _simple_rule_impl(ctx):",
        "  pass",
        "simple_rule = rule(",
        "  implementation = _simple_rule_impl,",
        "  attrs = {",
        "    'dep': attr.label(),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'simple_rule')",
        "simple_rule(",
        "  name = 'main',",
        "  dep = ':dep_target',",
        ")",
        "simple_rule(",
        "  name = 'dep_target',",
        ")");

    AnalysisResult analysisResult =
        update(
            ImmutableList.of("test/defs.bzl%a3", "test/defs.bzl%a1", "test/defs.bzl%a2"),
            "//test:main");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect a1 = getConfiguredAspect(configuredAspects, "a1");
    assertThat(a1).isNotNull();
    StarlarkProvider.Key a1Result =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "a1_result");
    StructImpl a1ResultProvider = (StructImpl) a1.get(a1Result);
    assertThat((Sequence<?>) a1ResultProvider.getValue("value"))
        .containsExactly(
            "aspect a1 on target @//test:dep_target sees a1p = a1p_a3_val",
            "aspect a1 on target @//test:main sees a1p = a1p_a3_val");
  }

  @Test
  public void testTopLevelAspectOnAspect_requiredProviderNotProvided() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "a1p = provider()",
        "a2p = provider()",
        "a1_result = provider()",
        "",
        "def _a1_impl(target, ctx):",
        "  result = 'aspect a1 on target {}'.format(target.label)",
        "  if a1p in target:",
        "    result += ' sees a1p = {}'.format(target[a1p].value)",
        "  else:",
        "    result += ' cannot see a1p'",
        "  complete_result = []",
        "  if ctx.rule.attr.dep:",
        "    complete_result = ctx.rule.attr.dep[a1_result].value + [result]",
        "  else:",
        "    complete_result = [result]",
        "  return [a1_result(value = complete_result)]",
        "a1 = aspect(",
        "  implementation = _a1_impl,",
        "  attr_aspects = ['dep'],",
        "  required_aspect_providers = [a1p]",
        ")",
        "",
        "def _a2_impl(target, ctx):",
        "  return [a2p(value = 'a2p_val')]",
        "a2 = aspect(",
        "  implementation = _a2_impl,",
        "  attr_aspects = ['dep'],",
        "  provides = [a2p],",
        ")",
        "",
        "def _simple_rule_impl(ctx):",
        "  pass",
        "simple_rule = rule(",
        "  implementation = _simple_rule_impl,",
        "  attrs = {",
        "    'dep': attr.label(),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'simple_rule')",
        "simple_rule(",
        "  name = 'main',",
        "  dep = ':dep_target',",
        ")",
        "simple_rule(",
        "  name = 'dep_target',",
        ")");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/defs.bzl%a2", "test/defs.bzl%a1"), "//test:main");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect a1 = getConfiguredAspect(configuredAspects, "a1");
    assertThat(a1).isNotNull();
    StarlarkProvider.Key a1Result =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "a1_result");
    StructImpl a1ResultProvider = (StructImpl) a1.get(a1Result);
    assertThat((Sequence<?>) a1ResultProvider.getValue("value"))
        .containsExactly(
            "aspect a1 on target @//test:dep_target cannot see a1p",
            "aspect a1 on target @//test:main cannot see a1p");
  }

  /**
   * --aspects = a1, a2: aspect a1 requires provider a1p, aspect a2 provides a1p but it was listed
   * after a1 so aspect a1 cannot see a1p value.
   */
  @Test
  public void testTopLevelAspectOnAspect_requiredProviderProvidedAfterTheAspect() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "a1p = provider()",
        "a1_result = provider()",
        "",
        "def _a1_impl(target, ctx):",
        "  result = 'aspect a1 on target {}'.format(target.label)",
        "  if a1p in target:",
        "    result += ' sees a1p = {}'.format(target[a1p].value)",
        "  else:",
        "    result += ' cannot see a1p'",
        "  complete_result = []",
        "  if ctx.rule.attr.dep:",
        "    complete_result = ctx.rule.attr.dep[a1_result].value + [result]",
        "  else:",
        "    complete_result = [result]",
        "  return [a1_result(value = complete_result)]",
        "a1 = aspect(",
        "  implementation = _a1_impl,",
        "  attr_aspects = ['dep'],",
        "  required_aspect_providers = [a1p]",
        ")",
        "",
        "def _a2_impl(target, ctx):",
        "  return [a1p(value = 'a1p_val')]",
        "a2 = aspect(",
        "  implementation = _a2_impl,",
        "  attr_aspects = ['dep'],",
        "  provides = [a1p],",
        ")",
        "",
        "def _simple_rule_impl(ctx):",
        "  pass",
        "simple_rule = rule(",
        "  implementation = _simple_rule_impl,",
        "  attrs = {",
        "    'dep': attr.label(),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'simple_rule')",
        "simple_rule(",
        "  name = 'main',",
        "  dep = ':dep_target',",
        ")",
        "simple_rule(",
        "  name = 'dep_target',",
        ")");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/defs.bzl%a1", "test/defs.bzl%a2"), "//test:main");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect a1 = getConfiguredAspect(configuredAspects, "a1");
    assertThat(a1).isNotNull();
    StarlarkProvider.Key a1Result =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "a1_result");
    StructImpl a1ResultProvider = (StructImpl) a1.get(a1Result);
    assertThat((Sequence<?>) a1ResultProvider.getValue("value"))
        .containsExactly(
            "aspect a1 on target @//test:dep_target cannot see a1p",
            "aspect a1 on target @//test:main cannot see a1p");
  }

  /**
   * --aspects = a2, a1: aspect a1 requires provider a1p, aspect a2 provides a1p. But aspect a2
   * propagates along different attr_aspects from a1 so a1 cannot get a1p on all dependency targets.
   */
  @Test
  public void testTopLevelAspectOnAspect_differentAttrAspects() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "a1p = provider()",
        "a1_result = provider()",
        "",
        "def _a1_impl(target, ctx):",
        "  result = 'aspect a1 on target {}'.format(target.label)",
        "  if a1p in target:",
        "    result += ' sees a1p = {}'.format(target[a1p].value)",
        "  else:",
        "    result += ' cannot see a1p'",
        "  complete_result = []",
        "  if ctx.rule.attr.dep:",
        "    complete_result += ctx.rule.attr.dep[a1_result].value",
        "  if ctx.rule.attr.extra_dep:",
        "    complete_result += ctx.rule.attr.extra_dep[a1_result].value",
        "  complete_result += [result]",
        "  return [a1_result(value = complete_result)]",
        "a1 = aspect(",
        "  implementation = _a1_impl,",
        "  attr_aspects = ['dep', 'extra_dep'],",
        "  required_aspect_providers = [a1p]",
        ")",
        "",
        "def _a2_impl(target, ctx):",
        "  return [a1p(value = 'a1p_val')]",
        "a2 = aspect(",
        "  implementation = _a2_impl,",
        "  attr_aspects = ['dep'],",
        "  provides = [a1p],",
        ")",
        "",
        "def _simple_rule_impl(ctx):",
        "  pass",
        "simple_rule = rule(",
        "  implementation = _simple_rule_impl,",
        "  attrs = {",
        "    'dep': attr.label(),",
        "    'extra_dep': attr.label(),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'simple_rule')",
        "simple_rule(",
        "  name = 'main',",
        "  dep = ':dep_target',",
        "  extra_dep = ':extra_dep_target',",
        ")",
        "simple_rule(",
        "  name = 'dep_target',",
        ")",
        "simple_rule(",
        "  name = 'extra_dep_target',",
        ")");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/defs.bzl%a2", "test/defs.bzl%a1"), "//test:main");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect a1 = getConfiguredAspect(configuredAspects, "a1");
    assertThat(a1).isNotNull();
    StarlarkProvider.Key a1Result =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "a1_result");
    StructImpl a1ResultProvider = (StructImpl) a1.get(a1Result);
    assertThat((Sequence<?>) a1ResultProvider.getValue("value"))
        .containsExactly(
            "aspect a1 on target @//test:dep_target sees a1p = a1p_val",
            "aspect a1 on target @//test:extra_dep_target cannot see a1p",
            "aspect a1 on target @//test:main sees a1p = a1p_val");
  }

  /**
   * --aspects = a2, a1: aspect a1 requires provider a1p, aspect a2 provides a1p. But aspect a2
   * propagates along different required_providers from a1 so a1 cannot get a1p on all dependency
   * targets.
   */
  @Test
  public void testTopLevelAspectOnAspect_differentRequiredRuleProviders() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "a1p = provider()",
        "a1_result = provider()",
        "rule_prov_a = provider()",
        "rule_prov_b = provider()",
        "",
        "def _a1_impl(target, ctx):",
        "  result = 'aspect a1 on target {}'.format(target.label)",
        "  if a1p in target:",
        "    result += ' sees a1p = {}'.format(target[a1p].value)",
        "  else:",
        "    result += ' cannot see a1p'",
        "  complete_result = []",
        "  if hasattr(ctx.rule.attr, 'deps'):",
        "    for dep in ctx.rule.attr.deps:",
        "      complete_result += dep[a1_result].value",
        "  complete_result += [result]",
        "  return [a1_result(value = complete_result)]",
        "a1 = aspect(",
        "  implementation = _a1_impl,",
        "  attr_aspects = ['deps'],",
        "  required_aspect_providers = [a1p],",
        "  required_providers = [[rule_prov_a], [rule_prov_b]],",
        ")",
        "",
        "def _a2_impl(target, ctx):",
        "  return [a1p(value = 'a1p_val')]",
        "a2 = aspect(",
        "  implementation = _a2_impl,",
        "  attr_aspects = ['deps'],",
        "  provides = [a1p],",
        "  required_providers = [rule_prov_a],",
        ")",
        "",
        "def _main_rule_impl(ctx):",
        "  return [rule_prov_a(), rule_prov_b()]",
        "main_rule = rule(",
        "  implementation = _main_rule_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(),",
        "  },",
        ")",
        "",
        "def _rule_with_prov_a_impl(ctx):",
        "  return [rule_prov_a()]",
        "rule_with_prov_a = rule(",
        "  implementation = _rule_with_prov_a_impl,",
        "  provides = [rule_prov_a]",
        ")",
        "",
        "def _rule_with_prov_b_impl(ctx):",
        "  return [rule_prov_b()]",
        "rule_with_prov_b = rule(",
        "  implementation = _rule_with_prov_b_impl,",
        "  provides = [rule_prov_b]",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'main_rule', 'rule_with_prov_a', 'rule_with_prov_b')",
        "main_rule(",
        "  name = 'main',",
        "  deps = [':target_with_prov_a', ':target_with_prov_b'],",
        ")",
        "rule_with_prov_a(",
        "  name = 'target_with_prov_a',",
        ")",
        "rule_with_prov_b(",
        "  name = 'target_with_prov_b',",
        ")");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/defs.bzl%a2", "test/defs.bzl%a1"), "//test:main");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect a1 = getConfiguredAspect(configuredAspects, "a1");
    assertThat(a1).isNotNull();
    StarlarkProvider.Key a1Result =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "a1_result");
    StructImpl a1ResultProvider = (StructImpl) a1.get(a1Result);
    assertThat((Sequence<?>) a1ResultProvider.getValue("value"))
        .containsExactly(
            "aspect a1 on target @//test:target_with_prov_a sees a1p = a1p_val",
            "aspect a1 on target @//test:target_with_prov_b cannot see a1p",
            "aspect a1 on target @//test:main sees a1p = a1p_val");
  }

  /**
   * --aspects = a3, a2, a1: both aspects a1 and a2 require provider a3p, aspect a3 provides a3p. a1
   * and a2 should be able to read a3p.
   */
  @Test
  public void testTopLevelAspectOnAspect_providerRequiredByMultipleAspects() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "a3p = provider()",
        "a1_result = provider()",
        "a2_result = provider()",
        "",
        "def _a1_impl(target, ctx):",
        "  result = 'aspect a1 on target {}'.format(target.label)",
        "  if a3p in target:",
        "    result += ' sees a3p = {}'.format(target[a3p].value)",
        "  else:",
        "    result += ' cannot see a3p'",
        "  complete_result = []",
        "  if ctx.rule.attr.dep:",
        "    complete_result = ctx.rule.attr.dep[a1_result].value + [result]",
        "  else:",
        "    complete_result = [result]",
        "  return [a1_result(value = complete_result)]",
        "a1 = aspect(",
        "  implementation = _a1_impl,",
        "  attr_aspects = ['dep'],",
        "  required_aspect_providers = [a3p]",
        ")",
        "",
        "def _a2_impl(target, ctx):",
        "  result = 'aspect a2 on target {}'.format(target.label)",
        "  if a3p in target:",
        "    result += ' sees a3p = {}'.format(target[a3p].value)",
        "  else:",
        "    result += ' cannot see a3p'",
        "  complete_result = []",
        "  if ctx.rule.attr.dep:",
        "    complete_result = ctx.rule.attr.dep[a2_result].value + [result]",
        "  else:",
        "    complete_result = [result]",
        "  return [a2_result(value = complete_result)]",
        "a2 = aspect(",
        "  implementation = _a2_impl,",
        "  attr_aspects = ['dep'],",
        "  required_aspect_providers = [a3p]",
        ")",
        "",
        "def _a3_impl(target, ctx):",
        "  return [a3p(value = 'a3p_val')]",
        "a3 = aspect(",
        "  implementation = _a3_impl,",
        "  attr_aspects = ['dep'],",
        "  provides = [a3p],",
        ")",
        "",
        "def _simple_rule_impl(ctx):",
        "  pass",
        "simple_rule = rule(",
        "  implementation = _simple_rule_impl,",
        "  attrs = {",
        "    'dep': attr.label(),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'simple_rule')",
        "simple_rule(",
        "  name = 'main',",
        "  dep = ':dep_target',",
        ")",
        "simple_rule(",
        "  name = 'dep_target',",
        ")");

    AnalysisResult analysisResult =
        update(
            ImmutableList.of("test/defs.bzl%a3", "test/defs.bzl%a2", "test/defs.bzl%a1"),
            "//test:main");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect a2 = getConfiguredAspect(configuredAspects, "a2");
    assertThat(a2).isNotNull();
    StarlarkProvider.Key a2Result =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "a2_result");
    StructImpl a2ResultProvider = (StructImpl) a2.get(a2Result);
    assertThat((Sequence<?>) a2ResultProvider.getValue("value"))
        .containsExactly(
            "aspect a2 on target @//test:dep_target sees a3p = a3p_val",
            "aspect a2 on target @//test:main sees a3p = a3p_val");

    ConfiguredAspect a1 = getConfiguredAspect(configuredAspects, "a1");
    assertThat(a1).isNotNull();
    StarlarkProvider.Key a1Result =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "a1_result");
    StructImpl a1ResultProvider = (StructImpl) a1.get(a1Result);
    assertThat((Sequence<?>) a1ResultProvider.getValue("value"))
        .containsExactly(
            "aspect a1 on target @//test:dep_target sees a3p = a3p_val",
            "aspect a1 on target @//test:main sees a3p = a3p_val");
  }

  /**
   * --aspects = a1, a2, a3: aspect a3 requires a1p and a2p, a1 provides a1p and a2 provides a2p.
   *
   * <p>top level target (main) has two dependencies t1 and t2. Aspects a1 and a3 can propagate to
   * t1 and aspects a2 and a3 can propagate to t2. Both t1 and t2 have t0 as dependency, aspect a3
   * will run twice on t0 once with aspects path (a1, a3) and the other with (a2, a3).
   */
  @Test
  public void testTopLevelAspectOnAspect_diamondCase() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "a1p = provider()",
        "a2p = provider()",
        "a3_result = provider()",
        "",
        "r1p = provider()",
        "r2p = provider()",
        "",
        "def _a1_impl(target, ctx):",
        "  return [a1p(value = 'a1p_val')]",
        "a1 = aspect(",
        "  implementation = _a1_impl,",
        "  attr_aspects = ['deps'],",
        "  required_providers = [r1p],",
        "  provides = [a1p]",
        ")",
        "",
        "def _a2_impl(target, ctx):",
        "  return [a2p(value = 'a2p_val')]",
        "a2 = aspect(",
        "  implementation = _a2_impl,",
        "  attr_aspects = ['deps'],",
        "  required_providers = [r2p],",
        "  provides = [a2p]",
        ")",
        "",
        "def _a3_impl(target, ctx):",
        "  result = 'aspect a3 on target {}'.format(target.label)",
        "  if a1p in target:",
        "    result += ' sees a1p = {}'.format(target[a1p].value)",
        "  else:",
        "    result += ' cannot see a1p'",
        "  if a2p in target:",
        "    result += ' and sees a2p = {}'.format(target[a2p].value)",
        "  else:",
        "    result += ' and cannot see a2p'",
        "  complete_result = []",
        "  if ctx.rule.attr.deps:",
        "    for dep in ctx.rule.attr.deps:",
        "      complete_result.extend(dep[a3_result].value)",
        "  complete_result.append(result)",
        "  return [a3_result(value = complete_result)]",
        "a3 = aspect(",
        "  implementation = _a3_impl,",
        "  attr_aspects = ['deps'],",
        "  required_aspect_providers = [[a1p], [a2p]],",
        ")",
        "",
        "def _r0_impl(ctx):",
        "  return [r1p(), r2p()]",
        "r0 = rule(",
        "  implementation = _r0_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(),",
        "  },",
        "  provides = [r1p, r2p]",
        ")",
        "def _r1_impl(ctx):",
        "  return [r1p()]",
        "r1 = rule(",
        "  implementation = _r1_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(),",
        "  },",
        "  provides = [r1p]",
        ")",
        "def _r2_impl(ctx):",
        "  return [r2p()]",
        "r2 = rule(",
        "  implementation = _r2_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(),",
        "  },",
        "  provides = [r2p]",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'r0', 'r1', 'r2')",
        "r0(",
        "  name = 'main',",
        "  deps = [':t1', ':t2'],",
        ")",
        "r1(",
        "  name = 't1',",
        "  deps = [':t0'],",
        ")",
        "r2(",
        "  name = 't2',",
        "  deps = [':t0'],",
        ")",
        "r0(",
        "  name = 't0',",
        ")");

    AnalysisResult analysisResult =
        update(
            ImmutableList.of("test/defs.bzl%a1", "test/defs.bzl%a2", "test/defs.bzl%a3"),
            "//test:main");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect a3 = getConfiguredAspect(configuredAspects, "a3");
    assertThat(a3).isNotNull();
    StarlarkProvider.Key a3Result =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "a3_result");
    StructImpl a3ResultProvider = (StructImpl) a3.get(a3Result);
    assertThat((Sequence<?>) a3ResultProvider.getValue("value"))
        .containsExactly(
            "aspect a3 on target @//test:t0 sees a1p = a1p_val and cannot see a2p",
            "aspect a3 on target @//test:t0 cannot see a1p and sees a2p = a2p_val",
            "aspect a3 on target @//test:t1 sees a1p = a1p_val and cannot see a2p",
            "aspect a3 on target @//test:t2 cannot see a1p and sees a2p = a2p_val",
            "aspect a3 on target @//test:main sees a1p = a1p_val and sees a2p = a2p_val");
  }

  @Test
  public void testTopLevelAspectOnAspect_duplicateAspectsNotAllowed() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "a2p = provider()",
        "a1_result = provider()",
        "",
        "def _a1_impl(target, ctx):",
        "  result = 'aspect a1 on target {}'.format(target.label)",
        "  if a2p in target:",
        "    result += ' sees a2p = {}'.format(target[a2p].value)",
        "  else:",
        "    result += ' cannot see a2p'",
        "  complete_result = []",
        "  if ctx.rule.attr.deps:",
        "    for dep in ctx.rule.attr.deps:",
        "      complete_result.extend(dep[a1_result].value)",
        "  complete_result.append(result)",
        "  return [a1_result(value = complete_result)]",
        "a1 = aspect(",
        "  implementation = _a1_impl,",
        "  attr_aspects = ['deps'],",
        "  required_aspect_providers = [a2p]",
        ")",
        "",
        "def _a2_impl(target, ctx):",
        "  return [a2p(value = 'a2p_val')]",
        "a2 = aspect(",
        "  implementation = _a2_impl,",
        "  attr_aspects = ['deps'],",
        "  provides = [a2p]",
        ")",
        "",
        "def _simple_rule_impl(ctx):",
        "  pass",
        "simple_rule = rule(",
        "  implementation = _simple_rule_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'simple_rule')",
        "simple_rule(",
        "  name = 'main',",
        "  deps = [':dep_target'],",
        ")",
        "simple_rule(",
        "  name = 'dep_target',",
        ")");
    reporter.removeHandler(failFastHandler);

    // The call to `update` does not throw an exception when "--keep_going" is passed in the
    // WithKeepGoing test suite. Otherwise, it throws ViewCreationFailedException.
    if (keepGoing()) {
      AnalysisResult result =
          update(
              ImmutableList.of("test/defs.bzl%a1", "test/defs.bzl%a2", "test/defs.bzl%a1"),
              "//test:main");
      assertThat(result.hasError()).isTrue();
    } else {
      assertThrows(
          ViewCreationFailedException.class,
          () ->
              update(
                  ImmutableList.of("test/defs.bzl%a1", "test/defs.bzl%a2", "test/defs.bzl%a1"),
                  "//test:main"));
    }
    assertContainsEvent("aspect //test:defs.bzl%a1 added more than once");
  }

  /**
   * --aspects = a1 requires provider a2p provided by aspect a2. a1 is applied on top level target
   * `main` whose rule propagates aspect a2 to its `deps`. So a1 on `main` cannot see a2p but it can
   * see a2p on `main` deps.
   */
  @Test
  public void testTopLevelAspectOnAspect_requiredAspectProviderOnlyAvailableOnDep()
      throws Exception {
    scratch.file(
        "test/defs.bzl",
        "a2p = provider()",
        "a1_result = provider()",
        "",
        "def _a1_impl(target, ctx):",
        "  result = 'aspect a1 on target {}'.format(target.label)",
        "  if a2p in target:",
        "    result += ' sees a2p = {}'.format(target[a2p].value)",
        "  else:",
        "    result += ' cannot see a2p'",
        "  complete_result = []",
        "  if ctx.rule.attr.deps:",
        "    for dep in ctx.rule.attr.deps:",
        "      complete_result.extend(dep[a1_result].value)",
        "  complete_result.append(result)",
        "  return [a1_result(value = complete_result)]",
        "a1 = aspect(",
        "  implementation = _a1_impl,",
        "  attr_aspects = ['deps'],",
        "  required_aspect_providers = [a2p]",
        ")",
        "",
        "def _a2_impl(target, ctx):",
        "  return [a2p(value = 'a2p_val')]",
        "a2 = aspect(",
        "  implementation = _a2_impl,",
        "  attr_aspects = ['deps'],",
        "  provides = [a2p]",
        ")",
        "",
        "def _simple_rule_impl(ctx):",
        "  pass",
        "simple_rule = rule(",
        "  implementation = _simple_rule_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(aspects=[a2]),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'simple_rule')",
        "simple_rule(",
        "  name = 'main',",
        "  deps = [':dep_target'],",
        ")",
        "simple_rule(",
        "  name = 'dep_target',",
        ")");

    AnalysisResult analysisResult = update(ImmutableList.of("test/defs.bzl%a1"), "//test:main");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect a1 = getConfiguredAspect(configuredAspects, "a1");
    assertThat(a1).isNotNull();
    StarlarkProvider.Key a1Result =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "a1_result");
    StructImpl a1ResultProvider = (StructImpl) a1.get(a1Result);
    assertThat((Sequence<?>) a1ResultProvider.getValue("value"))
        .containsExactly(
            "aspect a1 on target @//test:dep_target sees a2p = a2p_val",
            "aspect a1 on target @//test:main cannot see a2p");
  }

  @Test
  public void testTopLevelAspectOnAspect_multipleTopLevelTargets() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "a2p = provider()",
        "a1_result = provider()",
        "",
        "def _a1_impl(target, ctx):",
        "  result = 'aspect a1 on target {}'.format(target.label)",
        "  if a2p in target:",
        "    result += ' sees a2p = {}'.format(target[a2p].value)",
        "  else:",
        "    result += ' cannot see a2p'",
        "  complete_result = []",
        "  if ctx.rule.attr.deps:",
        "    for dep in ctx.rule.attr.deps:",
        "      complete_result.extend(dep[a1_result].value)",
        "  complete_result.append(result)",
        "  return [a1_result(value = complete_result)]",
        "a1 = aspect(",
        "  implementation = _a1_impl,",
        "  attr_aspects = ['deps'],",
        "  required_aspect_providers = [a2p],",
        ")",
        "",
        "def _a2_impl(target, ctx):",
        "  return [a2p(value = 'a2p_val')]",
        "a2 = aspect(",
        "  implementation = _a2_impl,",
        "  attr_aspects = ['deps'],",
        "  provides = [a2p]",
        ")",
        "",
        "def _simple_rule_impl(ctx):",
        "  pass",
        "simple_rule = rule(",
        "  implementation = _simple_rule_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'simple_rule')",
        "simple_rule(",
        "  name = 't1',",
        ")",
        "simple_rule(",
        "  name = 't2',",
        ")");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/defs.bzl%a2", "test/defs.bzl%a1"), "//test:t2", "//test:t1");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect a1Ont1 = getConfiguredAspect(configuredAspects, "a1", "t1");
    assertThat(a1Ont1).isNotNull();
    StarlarkProvider.Key a1Result =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "a1_result");
    StructImpl a1ResultProvider = (StructImpl) a1Ont1.get(a1Result);
    assertThat((Sequence<?>) a1ResultProvider.getValue("value"))
        .containsExactly("aspect a1 on target @//test:t1 sees a2p = a2p_val");

    ConfiguredAspect a1Ont2 = getConfiguredAspect(configuredAspects, "a1", "t2");
    assertThat(a1Ont2).isNotNull();
    a1ResultProvider = (StructImpl) a1Ont2.get(a1Result);
    assertThat((Sequence<?>) a1ResultProvider.getValue("value"))
        .containsExactly("aspect a1 on target @//test:t2 sees a2p = a2p_val");
  }

  @Test
  public void testTopLevelAspectOnAspect_multipleRequiredProviders() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "a2p = provider()",
        "a3p = provider()",
        "a1_result = provider()",
        "",
        "def _a1_impl(target, ctx):",
        "  result = 'aspect a1 on target {}'.format(target.label)",
        "  if a2p in target:",
        "    result += ' sees a2p = {}'.format(target[a2p].value)",
        "  else:",
        "    result += ' cannot see a2p'",
        "  if a3p in target:",
        "    result += ' and sees a3p = {}'.format(target[a3p].value)",
        "  else:",
        "    result += ' and cannot see a3p'",
        "  complete_result = []",
        "  if ctx.rule.attr.deps:",
        "    for dep in ctx.rule.attr.deps:",
        "      complete_result.extend(dep[a1_result].value)",
        "  complete_result.append(result)",
        "  return [a1_result(value = complete_result)]",
        "a1 = aspect(",
        "  implementation = _a1_impl,",
        "  attr_aspects = ['deps'],",
        "  required_aspect_providers = [[a2p], [a3p]],",
        ")",
        "",
        "def _a2_impl(target, ctx):",
        "  return [a2p(value = 'a2p_val')]",
        "a2 = aspect(",
        "  implementation = _a2_impl,",
        "  attr_aspects = ['deps'],",
        "  provides = [a2p]",
        ")",
        "",
        "def _a3_impl(target, ctx):",
        "  return [a3p(value = 'a3p_val')]",
        "a3 = aspect(",
        "  implementation = _a3_impl,",
        "  attr_aspects = ['deps'],",
        "  provides = [a3p]",
        ")",
        "",
        "def _simple_rule_impl(ctx):",
        "  pass",
        "simple_rule = rule(",
        "  implementation = _simple_rule_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'simple_rule')",
        "simple_rule(",
        "  name = 'main',",
        "  deps = [':dep_target'],",
        ")",
        "simple_rule(",
        "  name = 'dep_target',",
        ")");

    AnalysisResult analysisResult =
        update(
            ImmutableList.of("test/defs.bzl%a3", "test/defs.bzl%a2", "test/defs.bzl%a1"),
            "//test:main");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect a1 = getConfiguredAspect(configuredAspects, "a1");
    assertThat(a1).isNotNull();
    StarlarkProvider.Key a1Result =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "a1_result");
    StructImpl a1ResultProvider = (StructImpl) a1.get(a1Result);
    assertThat((Sequence<?>) a1ResultProvider.getValue("value"))
        .containsExactly(
            "aspect a1 on target @//test:dep_target sees a2p = a2p_val and sees a3p = a3p_val",
            "aspect a1 on target @//test:main sees a2p = a2p_val and sees a3p = a3p_val");
  }

  @Test
  public void testTopLevelAspectOnAspect_multipleRequiredProviders2() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "a2p = provider()",
        "a3p = provider()",
        "a1_result = provider()",
        "a2_result = provider()",
        "",
        "def _a1_impl(target, ctx):",
        "  result = 'aspect a1 on target {}'.format(target.label)",
        "  if a2p in target:",
        "    result += ' sees a2p = {}'.format(target[a2p].value)",
        "  else:",
        "    result += ' cannot see a2p'",
        "  if a3p in target:",
        "    result += ' and sees a3p = {}'.format(target[a3p].value)",
        "  else:",
        "    result += ' and cannot see a3p'",
        "  complete_result = []",
        "  if ctx.rule.attr.deps:",
        "    for dep in ctx.rule.attr.deps:",
        "      complete_result.extend(dep[a1_result].value)",
        "  complete_result.append(result)",
        "  return [a1_result(value = complete_result)]",
        "a1 = aspect(",
        "  implementation = _a1_impl,",
        "  attr_aspects = ['deps'],",
        "  required_aspect_providers = [[a2p], [a3p]],",
        ")",
        "",
        "def _a2_impl(target, ctx):",
        "  result = 'aspect a2 on target {}'.format(target.label)",
        "  if a3p in target:",
        "    result += ' sees a3p = {}'.format(target[a3p].value)",
        "  else:",
        "    result += ' cannot see a3p'",
        "  complete_result = []",
        "  if ctx.rule.attr.deps:",
        "    for dep in ctx.rule.attr.deps:",
        "      complete_result.extend(dep[a2_result].value)",
        "  complete_result.append(result)",
        "  return [a2_result(value = complete_result), a2p(value = 'a2p_val')]",
        "a2 = aspect(",
        "  implementation = _a2_impl,",
        "  attr_aspects = ['deps'],",
        "  provides = [a2p],",
        "  required_aspect_providers = [a3p]",
        ")",
        "",
        "def _a3_impl(target, ctx):",
        "  return [a3p(value = 'a3p_val')]",
        "a3 = aspect(",
        "  implementation = _a3_impl,",
        "  attr_aspects = ['deps'],",
        "  provides = [a3p]",
        ")",
        "",
        "def _simple_rule_impl(ctx):",
        "  pass",
        "simple_rule = rule(",
        "  implementation = _simple_rule_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'simple_rule')",
        "simple_rule(",
        "  name = 'main',",
        "  deps = [':dep_target'],",
        ")",
        "simple_rule(",
        "  name = 'dep_target',",
        ")");

    AnalysisResult analysisResult =
        update(
            ImmutableList.of("test/defs.bzl%a3", "test/defs.bzl%a2", "test/defs.bzl%a1"),
            "//test:main");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect a1 = getConfiguredAspect(configuredAspects, "a1");
    assertThat(a1).isNotNull();
    StarlarkProvider.Key a1Result =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "a1_result");
    StructImpl a1ResultProvider = (StructImpl) a1.get(a1Result);
    assertThat((Sequence<?>) a1ResultProvider.getValue("value"))
        .containsExactly(
            "aspect a1 on target @//test:dep_target sees a2p = a2p_val and sees a3p = a3p_val",
            "aspect a1 on target @//test:main sees a2p = a2p_val and sees a3p = a3p_val");

    ConfiguredAspect a2 = getConfiguredAspect(configuredAspects, "a2");
    assertThat(a2).isNotNull();
    StarlarkProvider.Key a2Result =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "a2_result");
    StructImpl a2ResultProvider = (StructImpl) a2.get(a2Result);
    assertThat((Sequence<?>) a2ResultProvider.getValue("value"))
        .containsExactly(
            "aspect a2 on target @//test:dep_target sees a3p = a3p_val",
            "aspect a2 on target @//test:main sees a3p = a3p_val");
  }

  /**
   * aspects = a1, a2; aspect a1 provides a1p provider and aspect a2 requires a1p provider. These
   * top-level aspects are applied on top-level target `main` whose rule also provides a1p.
   *
   * <p>By default, the dependency between a1 and a2 will be established, the build will fail since
   * a2 will receive provider a1p twice (from a1 applied on `main` and from `main` target itself).
   */
  @Test
  public void testTopLevelAspects_duplicateRuleProviderError() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "a1p = provider()",
        "a2p = provider()",
        "",
        "def _a1_impl(target, ctx):",
        "  return [a1p(value = 'aspect_a1p_val')]",
        "a1 = aspect(",
        "  implementation = _a1_impl,",
        "  provides = [a1p],",
        ")",
        "",
        "def _a2_impl(target, ctx):",
        "  result = 'aspect a2 on target {}'.format(target.label)",
        "  if a1p in target:",
        "    result += ' sees a1p = {}'.format(target[a1p].value)",
        "  else:",
        "    result += ' cannot see a1p'",
        "  return [a2p(value = result)]",
        "a2 = aspect(",
        "  implementation = _a2_impl,",
        "  provides = [a2p],",
        "  required_aspect_providers = [a1p]",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  return [a1p(value = 'rule_a1p_val')]",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        ")");
    scratch.file("test/BUILD", "load('//test:defs.bzl', 'my_rule')", "my_rule(name = 'main')");
    reporter.removeHandler(failFastHandler);

    // The call to `update` does not throw an exception when "--keep_going" is passed in the
    // WithKeepGoing test suite. Otherwise, it throws ViewCreationFailedException.
    if (keepGoing()) {
      AnalysisResult result =
          update(ImmutableList.of("test/defs.bzl%a1", "test/defs.bzl%a2"), "//test:main");
      assertThat(result.hasError()).isTrue();
    } else {
      assertThrows(
          ViewCreationFailedException.class,
          () -> update(ImmutableList.of("test/defs.bzl%a1", "test/defs.bzl%a2"), "//test:main"));
    }
    assertContainsEvent("Provider a1p provided twice");
  }

  @Test
  public void testTopLevelAspectRequiresAspect_stackOfRequiredAspects() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _impl(target, ctx):",
        "   return []",
        "aspect_c = aspect(implementation = _impl)",
        "aspect_b = aspect(implementation = _impl, requires = [aspect_c])",
        "aspect_a = aspect(implementation = _impl, requires = [aspect_b])");
    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/defs.bzl%aspect_a"), "//test:main_target");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    assertThat(configuredAspects).hasSize(3);
    assertThat(getConfiguredAspect(configuredAspects, "aspect_a")).isNotNull();
    assertThat(getConfiguredAspect(configuredAspects, "aspect_b")).isNotNull();
    assertThat(getConfiguredAspect(configuredAspects, "aspect_c")).isNotNull();
  }

  @Test
  public void testTopLevelAspectRequiresAspect_aspectRequiredByMultipleAspects() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _impl(target, ctx):",
        "   return []",
        "aspect_c = aspect(implementation = _impl)",
        "aspect_b = aspect(implementation = _impl, requires = [aspect_c])",
        "aspect_a = aspect(implementation = _impl, requires = [aspect_c])");
    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");

    AnalysisResult analysisResult =
        update(
            ImmutableList.of("test/defs.bzl%aspect_a", "test/defs.bzl%aspect_b"),
            "//test:main_target");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    assertThat(configuredAspects).hasSize(3);
    assertThat(getConfiguredAspect(configuredAspects, "aspect_a")).isNotNull();
    assertThat(getConfiguredAspect(configuredAspects, "aspect_b")).isNotNull();
    assertThat(getConfiguredAspect(configuredAspects, "aspect_c")).isNotNull();
  }

  @Test
  public void testTopLevelAspectRequiresAspect_aspectRequiredByMultipleAspects2() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _impl(target, ctx):",
        "   return []",
        "aspect_d = aspect(implementation = _impl)",
        "aspect_c = aspect(implementation = _impl, requires = [aspect_d])",
        "aspect_b = aspect(implementation = _impl, requires = [aspect_d])",
        "aspect_a = aspect(implementation = _impl, requires = [aspect_b, aspect_c])");
    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/defs.bzl%aspect_a"), "//test:main_target");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    assertThat(configuredAspects).hasSize(4);
    assertThat(getConfiguredAspect(configuredAspects, "aspect_a")).isNotNull();
    assertThat(getConfiguredAspect(configuredAspects, "aspect_b")).isNotNull();
    assertThat(getConfiguredAspect(configuredAspects, "aspect_c")).isNotNull();
    assertThat(getConfiguredAspect(configuredAspects, "aspect_d")).isNotNull();
  }

  @Test
  public void testTopLevelAspectRequiresAspect_requireExistingAspect_passed() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _impl(target, ctx):",
        "   return []",
        "aspect_b = aspect(implementation = _impl)",
        "aspect_a = aspect(implementation = _impl, requires = [aspect_b])");
    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");

    AnalysisResult analysisResult =
        update(
            ImmutableList.of("test/defs.bzl%aspect_b", "test/defs.bzl%aspect_a"),
            "//test:main_target");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    assertThat(configuredAspects).hasSize(2);
    assertThat(getConfiguredAspect(configuredAspects, "aspect_a")).isNotNull();
    assertThat(getConfiguredAspect(configuredAspects, "aspect_b")).isNotNull();
  }

  @Test
  public void testTopLevelAspectRequiresAspect_requireExistingAspect_failed() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _impl(target, ctx):",
        "   return []",
        "aspect_b = aspect(implementation = _impl)",
        "aspect_a = aspect(implementation = _impl, requires = [aspect_b])");
    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
    reporter.removeHandler(failFastHandler);

    // The call to `update` does not throw an exception when "--keep_going" is passed in the
    // WithKeepGoing test suite. Otherwise, it throws ViewCreationFailedException.
    if (keepGoing()) {
      AnalysisResult result =
          update(
              ImmutableList.of("test/defs.bzl%aspect_a", "test/defs.bzl%aspect_b"),
              "//test:main_target");
      assertThat(result.hasError()).isTrue();
    } else {
      assertThrows(
          ViewCreationFailedException.class,
          () ->
              update(
                  ImmutableList.of("test/defs.bzl%aspect_a", "test/defs.bzl%aspect_b"),
                  "//test:main_target"));
    }
    assertContainsEvent(
        "aspect //test:defs.bzl%aspect_b was added before as a required"
            + " aspect of aspect //test:defs.bzl%aspect_a");
  }

  @Test
  public void testTopLevelAspectRequiresAspect_ruleAttributes() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "RequiredAspectProv = provider()",
        "BaseAspectProv = provider()",
        "",
        "def _required_aspect_impl(target, ctx):",
        "  p_val = ['In required_aspect, p = {} on target {}'",
        "              .format(ctx.rule.attr.p, target.label)]",
        "  if ctx.rule.attr.dep and RequiredAspectProv in ctx.rule.attr.dep:",
        "    p_val += ctx.rule.attr.dep[RequiredAspectProv].p_val",
        "  return [RequiredAspectProv(p_val = p_val)]",
        "required_aspect = aspect(",
        "  implementation = _required_aspect_impl,",
        ")",
        "",
        "def _base_aspect_impl(target, ctx):",
        "  p_val = []",
        "  p_val += target[RequiredAspectProv].p_val",
        "  p_val += ['In base_aspect, p = {} on target {}'.format(ctx.rule.attr.p, target.label)]",
        "  if ctx.rule.attr.dep:",
        "    p_val += ctx.rule.attr.dep[BaseAspectProv].p_val",
        "  return [BaseAspectProv(p_val = p_val)]",
        "base_aspect = aspect(",
        "  implementation = _base_aspect_impl,",
        "  attr_aspects = ['dep'],",
        "  requires = [required_aspect],",
        ")",
        "",
        "def _rule_impl(ctx):",
        "  pass",
        "",
        "my_rule = rule(",
        "  implementation = _rule_impl,",
        "  attrs = {",
        "    'dep': attr.label(),",
        "    'p' : attr.string(values = ['main_val', 'dep_val']),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        "  dep = ':dep_target',",
        "  p = 'main_val',",
        ")",
        "my_rule(",
        "  name = 'dep_target',",
        "  p = 'dep_val',",
        ")");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/defs.bzl%base_aspect"), "//test:main_target");

    // required_aspect can only run on main_target when propagated alone since its attr_aspects is
    // empty.
    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect requiredAspect = getConfiguredAspect(configuredAspects, "required_aspect");
    assertThat(requiredAspect).isNotNull();
    StarlarkProvider.Key requiredAspectProv =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "RequiredAspectProv");
    StructImpl requiredAspectProvider = (StructImpl) requiredAspect.get(requiredAspectProv);
    assertThat((Sequence<?>) requiredAspectProvider.getValue("p_val"))
        .containsExactly("In required_aspect, p = main_val on target @//test:main_target");

    // base_aspect can run on main_target and dep_target and it can also see the providers created
    // by running required_target on them.
    ConfiguredAspect baseAspect = getConfiguredAspect(configuredAspects, "base_aspect");
    assertThat(baseAspect).isNotNull();
    StarlarkProvider.Key baseAspectProv =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "BaseAspectProv");
    StructImpl baseAspectProvider = (StructImpl) baseAspect.get(baseAspectProv);
    assertThat((Sequence<?>) baseAspectProvider.getValue("p_val"))
        .containsExactly(
            "In base_aspect, p = dep_val on target @//test:dep_target",
            "In base_aspect, p = main_val on target @//test:main_target",
            "In required_aspect, p = dep_val on target @//test:dep_target",
            "In required_aspect, p = main_val on target @//test:main_target");
  }

  @Test
  public void testTopLevelAspectRequiresAspect_inheritPropagationAttributes() throws Exception {
    // base_aspect propagates over base_dep attribute and requires first_required_aspect which
    // propagates over first_dep attribute and requires second_required_aspect which propagates over
    // second_dep attribute
    scratch.file(
        "test/defs.bzl",
        "BaseAspectProv = provider()",
        "FirstRequiredAspectProv = provider()",
        "SecondRequiredAspectProv = provider()",
        "",
        "def _second_required_aspect_impl(target, ctx):",
        "  result = []",
        "  if getattr(ctx.rule.attr, 'second_dep'):",
        "    result += getattr(ctx.rule.attr, 'second_dep')[SecondRequiredAspectProv].result",
        "  result += ['second_required_aspect run on target {}'.format(target.label)]",
        "  return [SecondRequiredAspectProv(result = result)]",
        "second_required_aspect = aspect(",
        "  implementation = _second_required_aspect_impl,",
        "  attr_aspects = ['second_dep'],",
        ")",
        "",
        "def _first_required_aspect_impl(target, ctx):",
        "  result = []",
        "  result += target[SecondRequiredAspectProv].result",
        "  if getattr(ctx.rule.attr, 'first_dep'):",
        "    result += getattr(ctx.rule.attr, 'first_dep')[FirstRequiredAspectProv].result",
        "  result += ['first_required_aspect run on target {}'.format(target.label)]",
        "  return [FirstRequiredAspectProv(result = result)]",
        "first_required_aspect = aspect(",
        "  implementation = _first_required_aspect_impl,",
        "  attr_aspects = ['first_dep'],",
        "  requires = [second_required_aspect],",
        ")",
        "",
        "def _base_aspect_impl(target, ctx):",
        "  result = []",
        "  result += target[FirstRequiredAspectProv].result",
        "  if getattr(ctx.rule.attr, 'base_dep'):",
        "    result += getattr(ctx.rule.attr, 'base_dep')[BaseAspectProv].result",
        "  result += ['base_aspect run on target {}'.format(target.label)]",
        "  return [BaseAspectProv(result = result)]",
        "base_aspect = aspect(",
        "  implementation = _base_aspect_impl,",
        "  attr_aspects = ['base_dep'],",
        "  requires = [first_required_aspect],",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "  attrs = {",
        "    'base_dep': attr.label(),",
        "    'first_dep': attr.label(),",
        "    'second_dep': attr.label()",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        "  base_dep = ':base_dep_target',",
        "  first_dep = ':first_dep_target',",
        "  second_dep = ':second_dep_target',",
        ")",
        "my_rule(",
        "  name = 'base_dep_target',",
        ")",
        "my_rule(",
        "  name = 'first_dep_target',",
        ")",
        "my_rule(",
        "  name = 'second_dep_target',",
        ")");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/defs.bzl%base_aspect"), "//test:main_target");

    // base_aspect should propagate only along its attr_aspects: 'base_dep'
    // first_required_aspect should propagate along 'base_dep' and 'first_dep'
    // second_required_aspect should propagate along 'base_dep', 'first_dep' and 'second_dep'
    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect baseAspect = getConfiguredAspect(configuredAspects, "base_aspect");
    assertThat(baseAspect).isNotNull();
    StarlarkProvider.Key baseAspectProv =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "BaseAspectProv");
    StructImpl baseAspectProvider = (StructImpl) baseAspect.get(baseAspectProv);
    assertThat((Sequence<?>) baseAspectProvider.getValue("result"))
        .containsExactly(
            "second_required_aspect run on target @//test:second_dep_target",
            "second_required_aspect run on target @//test:main_target",
            "second_required_aspect run on target @//test:first_dep_target",
            "second_required_aspect run on target @//test:base_dep_target",
            "first_required_aspect run on target @//test:first_dep_target",
            "first_required_aspect run on target @//test:main_target",
            "first_required_aspect run on target @//test:base_dep_target",
            "base_aspect run on target @//test:base_dep_target",
            "base_aspect run on target @//test:main_target");
  }

  @Test
  public void testTopLevelAspectRequiresAspect_inheritRequiredProviders() throws Exception {
    // aspect_a requires provider Prov_A and requires aspect_b which requires
    // provider Prov_B and requires aspect_c which requires provider Prov_C
    scratch.file(
        "test/defs.bzl",
        "Prov_A = provider()",
        "Prov_B = provider()",
        "Prov_C = provider()",
        "",
        "CollectorProv = provider()",
        "",
        "def _aspect_c_impl(target, ctx):",
        "  collector_result = ['aspect_c run on target {} and value of Prov_C = {}'",
        "                                .format(target.label, target[Prov_C].val)]",
        "  return [CollectorProv(result = collector_result)]",
        "aspect_c = aspect(",
        "  implementation = _aspect_c_impl,",
        "  required_providers = [Prov_C],",
        "  attr_aspects = ['dep'],",
        ")",
        "",
        "def _aspect_b_impl(target, ctx):",
        "  collector_result = []",
        "  collector_result += ctx.rule.attr.dep[CollectorProv].result",
        "  collector_result += ['aspect_b run on target {} and value of Prov_B = {}'",
        "                                 .format(target.label, target[Prov_B].val)]",
        "  return [CollectorProv(result = collector_result)]",
        "aspect_b = aspect(",
        "  implementation = _aspect_b_impl,",
        "  required_providers = [Prov_B],",
        "  requires = [aspect_c],",
        "  attr_aspects = ['dep'],",
        ")",
        "",
        "def _aspect_a_impl(target, ctx):",
        "  collector_result = []",
        "  collector_result += ctx.rule.attr.dep[CollectorProv].result",
        "  collector_result += ['aspect_a run on target {} and value of Prov_A = {}'",
        "                                 .format(target.label, target[Prov_A].val)]",
        "  return [CollectorProv(result = collector_result)]",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  required_providers = [Prov_A],",
        "  requires = [aspect_b],",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  return [Prov_A(val='main_val_a')]",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "  attrs = {",
        "    'dep': attr.label(),",
        "  },",
        "  provides = [Prov_A]",
        ")",
        "",
        "def _rule_with_prov_a_impl(ctx):",
        "  return [Prov_A(val='val_a')]",
        "rule_with_prov_a = rule(",
        "  implementation = _rule_with_prov_a_impl,",
        "  attrs = {",
        "    'dep': attr.label(),",
        "  },",
        "  provides = [Prov_A]",
        ")",
        "",
        "def _rule_with_prov_b_impl(ctx):",
        "  return [Prov_B(val = 'val_b')]",
        "rule_with_prov_b = rule(",
        "  implementation = _rule_with_prov_b_impl,",
        "  attrs = {",
        "    'dep': attr.label(),",
        "  },",
        "  provides = [Prov_B]",
        ")",
        "",
        "def _rule_with_prov_c_impl(ctx):",
        "  return [Prov_C(val = 'val_c')]",
        "rule_with_prov_c = rule(",
        "  implementation = _rule_with_prov_c_impl,",
        "  provides = [Prov_C]",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule', 'rule_with_prov_a', 'rule_with_prov_b',"
            + " 'rule_with_prov_c')",
        "my_rule(",
        "  name = 'main_target',",
        "  dep = ':target_with_prov_a',",
        ")",
        "rule_with_prov_a(",
        "  name = 'target_with_prov_a',",
        "  dep = ':target_with_prov_b'",
        ")",
        "rule_with_prov_b(",
        "  name = 'target_with_prov_b',",
        "  dep = ':target_with_prov_c'",
        ")",
        "rule_with_prov_c(",
        "  name = 'target_with_prov_c'",
        ")");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/defs.bzl%aspect_a"), "//test:main_target");

    // aspect_a should run on main_target and target_with_prov_a
    // aspect_b can reach target_with_prov_b because it inherits the required_providers of aspect_a
    // aspect_c can reach target_with_prov_c because it inherits the required_providers of aspect_a
    // and aspect_b
    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect aspectA = getConfiguredAspect(configuredAspects, "aspect_a");
    assertThat(aspectA).isNotNull();
    StarlarkProvider.Key collectorProv =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "CollectorProv");
    StructImpl collectorProvider = (StructImpl) aspectA.get(collectorProv);
    assertThat((Sequence<?>) collectorProvider.getValue("result"))
        .containsExactly(
            "aspect_c run on target @//test:target_with_prov_c and value of Prov_C = val_c",
            "aspect_b run on target @//test:target_with_prov_b and value of Prov_B = val_b",
            "aspect_a run on target @//test:target_with_prov_a and value of Prov_A = val_a",
            "aspect_a run on target @//test:main_target and value of Prov_A = main_val_a")
        .inOrder();
  }

  @Test
  public void testTopLevelAspectRequiresAspect_inspectRequiredAspectActions() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "BaseAspectProvider = provider()",
        "def _required_aspect_impl(target, ctx):",
        "  f = ctx.actions.declare_file('dummy.txt')",
        "  ctx.actions.run_shell(outputs = [f], command='echo xxx > $(location f)',",
        "                        mnemonic='RequiredAspectAction')",
        "  return struct()",
        "required_aspect = aspect(",
        "  implementation = _required_aspect_impl,",
        ")",
        "",
        "def _base_aspect_impl(target, ctx):",
        "  required_aspect_action = None",
        "  for action in target.actions:",
        "    if action.mnemonic == 'RequiredAspectAction':",
        "      required_aspect_action = action",
        "  if required_aspect_action:",
        "    return [BaseAspectProvider(result = 'base_aspect can see required_aspect action')]",
        "  else:",
        "    return [BaseAspectProvider(result = 'base_aspect cannot see required_aspect action')]",
        "base_aspect = aspect(",
        "  implementation = _base_aspect_impl,",
        "  attr_aspects = ['dep'],",
        "  requires = [required_aspect]",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        ")");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/defs.bzl%base_aspect"), "//test:main_target");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect baseAspect = getConfiguredAspect(configuredAspects, "base_aspect");
    assertThat(baseAspect).isNotNull();
    StarlarkProvider.Key baseAspectProv =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "BaseAspectProvider");
    StructImpl baseAspectProvider = (StructImpl) baseAspect.get(baseAspectProv);
    assertThat(baseAspectProvider.getValue("result"))
        .isEqualTo("base_aspect can see required_aspect action");
  }

  @Test
  public void testTopLevelAspectRequiresAspect_inspectRequiredAspectGeneratedFiles()
      throws Exception {
    scratch.file(
        "test/defs.bzl",
        "BaseAspectProvider = provider()",
        "def _required_aspect_impl(target, ctx):",
        "  file = ctx.actions.declare_file('required_aspect_file')",
        "  ctx.actions.write(file, 'data')",
        "  return [OutputGroupInfo(out = [file])]",
        "required_aspect = aspect(",
        "  implementation = _required_aspect_impl,",
        ")",
        "",
        "def _base_aspect_impl(target, ctx):",
        "  files = ['base_aspect can see file ' + f.path.split('/')[-1] ",
        "               for f in target[OutputGroupInfo].out.to_list()]",
        "  return [BaseAspectProvider(my_files = files)]",
        "base_aspect = aspect(",
        "  implementation = _base_aspect_impl,",
        "  attr_aspects = ['dep'],",
        "  requires = [required_aspect]",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        ")");

    AnalysisResult analysisResult =
        update(ImmutableList.of("test/defs.bzl%base_aspect"), "//test:main_target");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect baseAspect = getConfiguredAspect(configuredAspects, "base_aspect");
    assertThat(baseAspect).isNotNull();
    StarlarkProvider.Key baseAspectProv =
        new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "BaseAspectProvider");
    StructImpl baseAspectProvider = (StructImpl) baseAspect.get(baseAspectProv);
    assertThat((Sequence<?>) baseAspectProvider.getValue("my_files"))
        .containsExactly("base_aspect can see file required_aspect_file");
  }

  @Test
  public void testTopLevelAspectRequiresAspect_withRequiredAspectProvidersSatisfied()
      throws Exception {
    scratch.file(
        "test/defs.bzl",
        "prov_a = provider()",
        "prov_b = provider()",
        "prov_b_forwarded = provider()",
        "",
        "def _aspect_b_impl(target, ctx):",
        "  result = 'aspect_b on target {} '.format(target.label)",
        "  if prov_b in target:",
        "    result += 'found prov_b = {}'.format(target[prov_b].val)",
        "    return struct(aspect_b_result = result,",
        "                  providers = [prov_b_forwarded(val = target[prov_b].val)])",
        "  else:",
        "    result += 'cannot find prov_b'",
        "    return struct(aspect_b_result = result)",
        "aspect_b = aspect(",
        "  implementation = _aspect_b_impl,",
        "  required_aspect_providers = [prov_b]",
        ")",
        "",
        "def _aspect_a_impl(target, ctx):",
        "  result = 'aspect_a on target {} '.format(target.label)",
        "  if prov_a in target:",
        "    result += 'found prov_a = {}'.format(target[prov_a].val)",
        "  else:",
        "    result += 'cannot find prov_a'",
        "  if prov_b_forwarded in target:",
        "    result += ' and found prov_b = {}'.format(target[prov_b_forwarded].val)",
        "  else:",
        "    result += ' but cannot find prov_b'",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  required_aspect_providers = [prov_a],",
        "  attr_aspects = ['dep'],",
        "  requires = [aspect_b]",
        ")",
        "",
        "def _aspect_with_prov_a_impl(target, ctx):",
        "  return [prov_a(val = 'a1')]",
        "aspect_with_prov_a = aspect(",
        "  implementation = _aspect_with_prov_a_impl,",
        "  provides = [prov_a],",
        "  attr_aspects = ['dep'],",
        ")",
        "",
        "def _aspect_with_prov_b_impl(target, ctx):",
        "  return [prov_b(val = 'b1')]",
        "aspect_with_prov_b = aspect(",
        "  implementation = _aspect_with_prov_b_impl,",
        "  provides = [prov_b],",
        "  attr_aspects = ['dep'],",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        ")");

    AnalysisResult analysisResult =
        update(
            ImmutableList.of(
                "test/defs.bzl%aspect_with_prov_a",
                "test/defs.bzl%aspect_with_prov_b", "test/defs.bzl%aspect_a"),
            "//test:main_target");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect aspectA = getConfiguredAspect(configuredAspects, "aspect_a");
    assertThat(aspectA).isNotNull();
    String aspectAResult = (String) aspectA.get("aspect_a_result");
    assertThat(aspectAResult)
        .isEqualTo(
            "aspect_a on target @//test:main_target found prov_a = a1 and found prov_b = b1");

    ConfiguredAspect aspectB = getConfiguredAspect(configuredAspects, "aspect_b");
    assertThat(aspectB).isNotNull();
    String aspectBResult = (String) aspectB.get("aspect_b_result");
    assertThat(aspectBResult).isEqualTo("aspect_b on target @//test:main_target found prov_b = b1");
  }

  @Test
  public void testTopLevelAspectRequiresAspect_withRequiredAspectProvidersNotFound()
      throws Exception {
    scratch.file(
        "test/defs.bzl",
        "prov_a = provider()",
        "prov_b = provider()",
        "",
        "def _aspect_b_impl(target, ctx):",
        "  result = 'aspect_b on target {} '.format(target.label)",
        "  if prov_b in target:",
        "    result += 'found prov_b = {}'.format(target[prov_b].val)",
        "  else:",
        "    result += 'cannot find prov_b'",
        "  return struct(aspect_b_result = result)",
        "aspect_b = aspect(",
        "  implementation = _aspect_b_impl,",
        "  required_aspect_providers = [prov_b]",
        ")",
        "",
        "def _aspect_a_impl(target, ctx):",
        "  result = 'aspect_a on target {} '.format(target.label)",
        "  if prov_a in target:",
        "    result += 'found prov_a = {}'.format(target[prov_a].val)",
        "  else:",
        "    result += 'cannot find prov_a'",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  required_aspect_providers = [prov_a],",
        "  attr_aspects = ['dep'],",
        "  requires = [aspect_b]",
        ")",
        "",
        "def _aspect_with_prov_a_impl(target, ctx):",
        "  return [prov_a(val = 'a1')]",
        "aspect_with_prov_a = aspect(",
        "  implementation = _aspect_with_prov_a_impl,",
        "  provides = [prov_a],",
        "  attr_aspects = ['dep'],",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        ")");

    AnalysisResult analysisResult =
        update(
            ImmutableList.of("test/defs.bzl%aspect_with_prov_a", "test/defs.bzl%aspect_a"),
            "//test:main_target");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect aspectA = getConfiguredAspect(configuredAspects, "aspect_a");
    assertThat(aspectA).isNotNull();
    String aspectAResult = (String) aspectA.get("aspect_a_result");
    assertThat(aspectAResult).isEqualTo("aspect_a on target @//test:main_target found prov_a = a1");

    ConfiguredAspect aspectB = getConfiguredAspect(configuredAspects, "aspect_b");
    assertThat(aspectB).isNotNull();
    String aspectBResult = (String) aspectB.get("aspect_b_result");
    assertThat(aspectBResult)
        .isEqualTo("aspect_b on target @//test:main_target cannot find prov_b");
  }

  @Ignore("TODO(b/206127051): Fix the crash, rename the test and add a check the error message.")
  @Test
  public void testDependentAspectWithNonExecutableTool_doesNotCrash() throws Exception {
    scratch.file("test/BUILD", "sh_binary(name='bin', srcs=['bin.sh'])", "sh_library(name='lib')");
    scratch.file(
        "test/defs.bzl",
        "AInfo = provider(fields={})",
        "def _aspect_a(target, ctx): return AInfo()",
        "aspect_a = aspect(",
        "  implementation = _aspect_a,",
        "  provides=[AInfo],",
        "  attrs = {'_attr':" + " attr.label(default=':lib')},",
        ")",
        "def _aspect_b(target, ctx):",
        "  print(str(ctx.executable._attr))",
        "  return []",
        "aspect_b = aspect(",
        "  implementation = _aspect_b,",
        "  required_aspect_providers = [AInfo],",
        "  attrs = {'_attr': attr.label(default=':bin', executable=True, cfg='exec')},",
        ")");
    scratch.file("test/bin.sh").setExecutable(true);

    // TODO(b/206127051): This currently crashes with an IllegalStateException.
    update(ImmutableList.of("test/defs.bzl%aspect_a", "test/defs.bzl%aspect_b"), "//test:bin");
  }

  @Test
  public void testTopLevelAspectsWithParameters() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _aspect_a_impl(target, ctx):",
        "  result = ['aspect_a on target {}, p1 = {} and a_p = {}'.",
        "                                    format(target.label, ctx.attr.p1, ctx.attr.a_p)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.aspect_a_result",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p1' : attr.string(values = ['p1_v1', 'p1_v2']),",
        "            'a_p' : attr.string(values = ['a_p_v1', 'a_p_v2'])},",
        ")",
        "",
        "def _aspect_b_impl(target, ctx):",
        "  result = ['aspect_b on target {}, p1 = {} and b_p = {}'.",
        "                                    format(target.label, ctx.attr.p1, ctx.attr.b_p)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.aspect_b_result",
        "  return struct(aspect_b_result = result)",
        "",
        "aspect_b = aspect(",
        "  implementation = _aspect_b_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p1' : attr.string(values = ['p1_v1', 'p1_v2']),",
        "            'b_p' : attr.string(values = ['b_p_v1', 'b_p_v2'])},",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "   attrs = { 'dep' : attr.label() },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        "  dep = ':dep_target_1',",
        ")",
        "my_rule(",
        "  name = 'dep_target_1',",
        "  dep = ':dep_target_2',",
        ")",
        "my_rule(",
        "  name = 'dep_target_2',",
        ")");

    AnalysisResult analysisResult =
        update(
            ImmutableList.of("//test:defs.bzl%aspect_a", "//test:defs.bzl%aspect_b"),
            ImmutableMap.of("p1", "p1_v1", "a_p", "a_p_v1", "b_p", "b_p_v1"),
            "//test:main_target");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect aspectA = getConfiguredAspect(configuredAspects, "aspect_a");
    assertThat(aspectA).isNotNull();
    StarlarkList<?> aspectAResult = (StarlarkList) aspectA.get("aspect_a_result");
    assertThat(Starlark.toIterable(aspectAResult))
        .containsExactly(
            "aspect_a on target @//test:main_target, p1 = p1_v1 and a_p = a_p_v1",
            "aspect_a on target @//test:dep_target_1, p1 = p1_v1 and a_p = a_p_v1",
            "aspect_a on target @//test:dep_target_2, p1 = p1_v1 and a_p = a_p_v1");

    ConfiguredAspect aspectB = getConfiguredAspect(configuredAspects, "aspect_b");
    assertThat(aspectB).isNotNull();
    StarlarkList<?> aspectBResult = (StarlarkList) aspectB.get("aspect_b_result");
    assertThat(Starlark.toIterable(aspectBResult))
        .containsExactly(
            "aspect_b on target @//test:main_target, p1 = p1_v1 and b_p = b_p_v1",
            "aspect_b on target @//test:dep_target_1, p1 = p1_v1 and b_p = b_p_v1",
            "aspect_b on target @//test:dep_target_2, p1 = p1_v1 and b_p = b_p_v1");
  }

  @Test
  public void testTopLevelAspectsWithParameters_differentAllowedValues() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _aspect_a_impl(target, ctx):",
        "  result = ['aspect_a on target {}, p1 = {} and p2 = {}'.",
        "                                    format(target.label, ctx.attr.p1, ctx.attr.p2)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.aspect_a_result",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p1' : attr.string(values = ['p1_v1', 'p1_v2']) },",
        ")",
        "",
        "def _aspect_b_impl(target, ctx):",
        "  result = ['aspect_b on target {}, p1 = {} and p2 = {}'.",
        "                                    format(target.label, ctx.attr.p1, ctx.attr.p2)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.aspect_b_result",
        "  return struct(aspect_b_result = result)",
        "",
        "aspect_b = aspect(",
        "  implementation = _aspect_b_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p1' : attr.string(values = ['p1_v2', 'p1_v3']) },",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "   attrs = { 'dep' : attr.label() },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        "  dep = ':dep_target_1',",
        ")",
        "my_rule(",
        "  name = 'dep_target_1',",
        "  dep = ':dep_target_2',",
        ")",
        "my_rule(",
        "  name = 'dep_target_2',",
        ")");
    reporter.removeHandler(failFastHandler);

    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a ViewCreationFailedException.
    if (keepGoing()) {
      AnalysisResult analysisResult =
          update(
              ImmutableList.of("//test:defs.bzl%aspect_a", "//test:defs.bzl%aspect_b"),
              ImmutableMap.of("p1", "p1_v1"),
              "//test:main_target");
      assertThat(analysisResult.hasError()).isTrue();
    } else {
      assertThrows(
          ViewCreationFailedException.class,
          () ->
              update(
                  ImmutableList.of("//test:defs.bzl%aspect_a", "//test:defs.bzl%aspect_b"),
                  ImmutableMap.of("p1", "p1_v1"),
                  "//test:main_target"));
    }
    assertContainsEvent(
        "//test:defs.bzl%aspect_b: invalid value in 'p1' attribute: has to be one of 'p1_v2' or"
            + " 'p1_v3' instead of 'p1_v1'");
  }

  @Test
  public void testTopLevelAspectsWithParameters_useDefaultValue() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _aspect_a_impl(target, ctx):",
        "  result = ['aspect_a on target {}, p1 = {} and p2 = {}'.",
        "                                    format(target.label, ctx.attr.p1, ctx.attr.p2)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.aspect_a_result",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p1' : attr.string(values = ['p1_v1', 'p1_v2'], default = 'p1_v1'),",
        "            'p2' : attr.string(values = ['p2_v1', 'p2_v2'], default = 'p2_v1')},",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "   attrs = { 'dep' : attr.label() },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        "  dep = ':dep_target_1',",
        ")",
        "my_rule(",
        "  name = 'dep_target_1',",
        "  dep = ':dep_target_2',",
        ")",
        "my_rule(",
        "  name = 'dep_target_2',",
        ")");

    AnalysisResult analysisResult =
        update(
            ImmutableList.of("//test:defs.bzl%aspect_a"),
            ImmutableMap.of("p1", "p1_v2"),
            "//test:main_target");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect aspectA = getConfiguredAspect(configuredAspects, "aspect_a");
    assertThat(aspectA).isNotNull();
    StarlarkList<?> aspectAResult = (StarlarkList) aspectA.get("aspect_a_result");
    assertThat(Starlark.toIterable(aspectAResult))
        .containsExactly(
            "aspect_a on target @//test:main_target, p1 = p1_v2 and p2 = p2_v1",
            "aspect_a on target @//test:dep_target_1, p1 = p1_v2 and p2 = p2_v1",
            "aspect_a on target @//test:dep_target_2, p1 = p1_v2 and p2 = p2_v1");
  }

  @Test
  public void testTopLevelAspectsWithParameters_passParametersToRequiredAspect() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _aspect_b_impl(target, ctx):",
        "  result = ['aspect_b on target {}, p1 = {} and p3 = {}'.",
        "                                    format(target.label, ctx.attr.p1, ctx.attr.p3)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.aspect_b_result",
        "  return struct(aspect_b_result = result)",
        "",
        "aspect_b = aspect(",
        "  implementation = _aspect_b_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p1' : attr.string(values = ['p1_v1', 'p1_v2']),",
        "            'p3' : attr.string(values = ['p3_v1', 'p3_v2', 'p3_v3'])},",
        ")",
        "",
        "def _aspect_a_impl(target, ctx):",
        "  result = ['aspect_a on target {}, p1 = {} and p2 = {}'.",
        "                                    format(target.label, ctx.attr.p1, ctx.attr.p2)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.aspect_a_result",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p1' : attr.string(values = ['p1_v1', 'p1_v2']),",
        "            'p2' : attr.string(values = ['p2_v1', 'p2_v2'])},",
        "  requires = [aspect_b],",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "   attrs = { 'dep' : attr.label() },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        "  dep = ':dep_target_1',",
        ")",
        "my_rule(",
        "  name = 'dep_target_1',",
        "  dep = ':dep_target_2',",
        ")",
        "my_rule(",
        "  name = 'dep_target_2',",
        ")");

    AnalysisResult analysisResult =
        update(
            ImmutableList.of("//test:defs.bzl%aspect_a"),
            ImmutableMap.of("p1", "p1_v1", "p2", "p2_v2", "p3", "p3_v3"),
            "//test:main_target");

    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect aspectA = getConfiguredAspect(configuredAspects, "aspect_a");
    assertThat(aspectA).isNotNull();
    StarlarkList<?> aspectAResult = (StarlarkList) aspectA.get("aspect_a_result");
    assertThat(Starlark.toIterable(aspectAResult))
        .containsExactly(
            "aspect_a on target @//test:main_target, p1 = p1_v1 and p2 = p2_v2",
            "aspect_a on target @//test:dep_target_1, p1 = p1_v1 and p2 = p2_v2",
            "aspect_a on target @//test:dep_target_2, p1 = p1_v1 and p2 = p2_v2");

    ConfiguredAspect aspectB = getConfiguredAspect(configuredAspects, "aspect_b");
    assertThat(aspectB).isNotNull();
    StarlarkList<?> aspectBResult = (StarlarkList) aspectB.get("aspect_b_result");
    assertThat(Starlark.toIterable(aspectBResult))
        .containsExactly(
            "aspect_b on target @//test:main_target, p1 = p1_v1 and p3 = p3_v3",
            "aspect_b on target @//test:dep_target_1, p1 = p1_v1 and p3 = p3_v3",
            "aspect_b on target @//test:dep_target_2, p1 = p1_v1 and p3 = p3_v3");
  }

  @Test
  public void testTopLevelAspectsWithParameters_invalidParameterValue() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _aspect_a_impl(target, ctx):",
        "  result = ['aspect_a on target {}, p = {}'.",
        "                                    format(target.label, ctx.attr.p)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.aspect_a_result",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p' : attr.string(values = ['p_v1', 'p_v2']) },",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "   attrs = { 'dep' : attr.label() },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        "  dep = ':dep_target_1',",
        ")",
        "my_rule(",
        "  name = 'dep_target_1',",
        "  dep = ':dep_target_2',",
        ")",
        "my_rule(",
        "  name = 'dep_target_2',",
        ")");
    reporter.removeHandler(failFastHandler);

    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a ViewCreationFailedException.
    if (keepGoing()) {
      AnalysisResult analysisResult =
          update(
              ImmutableList.of("//test:defs.bzl%aspect_a"),
              ImmutableMap.of("p", "p_v"),
              "//test:main_target");
      assertThat(analysisResult.hasError()).isTrue();
    } else {
      assertThrows(
          ViewCreationFailedException.class,
          () ->
              update(
                  ImmutableList.of("//test:defs.bzl%aspect_a"),
                  ImmutableMap.of("p", "p_v"),
                  "//test:main_target"));
    }
    assertContainsEvent(
        "//test:defs.bzl%aspect_a: invalid value in 'p' attribute: has to be one of 'p_v1' or"
            + " 'p_v2' instead of 'p_v'");
  }

  @Test
  public void testTopLevelAspectsWithParameters_missingMandatoryParameter() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _aspect_a_impl(target, ctx):",
        "  result = ['aspect_a on target {}, p1 = {}'.",
        "                                    format(target.label, ctx.attr.p1)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.aspect_a_result",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p1' : attr.string(mandatory = True, default = 'p1_v1',",
        "                               values = ['p1_v1', 'p1_v2']),",
        "            'p2' : attr.string(values = ['p2_v1', 'p2_v2'])},",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "   attrs = { 'dep' : attr.label() },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        "  dep = ':dep_target_1',",
        ")",
        "my_rule(",
        "  name = 'dep_target_1',",
        "  dep = ':dep_target_2',",
        ")",
        "my_rule(",
        "  name = 'dep_target_2',",
        ")");
    reporter.removeHandler(failFastHandler);

    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a ViewCreationFailedException.
    if (keepGoing()) {
      AnalysisResult analysisResult =
          update(
              ImmutableList.of("//test:defs.bzl%aspect_a"),
              ImmutableMap.of("p2", "p2_v1"),
              "//test:main_target");

      assertThat(analysisResult.hasError()).isTrue();
    } else {
      assertThrows(
          ViewCreationFailedException.class,
          () ->
              update(
                  ImmutableList.of("//test:defs.bzl%aspect_a"),
                  ImmutableMap.of("p2", "p2_v1"),
                  "//test:main_target"));
    }
    assertContainsEvent("Missing mandatory attribute 'p1' for aspect '//test:defs.bzl%aspect_a'");
  }

  @Test
  public void testTopLevelAspectsWithParameters_unusedParameter() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _aspect_a_impl(target, ctx):",
        "  result = ['aspect_a on target {}, p1 = {} and a_p = {}'.",
        "                                    format(target.label, ctx.attr.p1, ctx.attr.a_p)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.aspect_a_result",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p1' : attr.string(values = ['p1_v1', 'p1_v2']),",
        "            'a_p' : attr.string(values = ['a_p_v1', 'a_p_v2'])},",
        ")",
        "",
        "def _aspect_b_impl(target, ctx):",
        "  result = ['aspect_b on target {}, p1 = {} and b_p = {}'.",
        "                                    format(target.label, ctx.attr.p1, ctx.attr.b_p)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.aspect_b_result",
        "  return struct(aspect_b_result = result)",
        "",
        "aspect_b = aspect(",
        "  implementation = _aspect_b_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p1' : attr.string(values = ['p1_v1', 'p1_v2']),",
        "            'b_p' : attr.string(values = ['b_p_v1', 'b_p_v2'])},",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "   attrs = { 'dep' : attr.label() },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        "  dep = ':dep_target_1',",
        ")",
        "my_rule(",
        "  name = 'dep_target_1',",
        "  dep = ':dep_target_2',",
        ")",
        "my_rule(",
        "  name = 'dep_target_2',",
        ")");
    reporter.removeHandler(failFastHandler);

    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a ViewCreationFailedException.
    if (keepGoing()) {
      AnalysisResult analysisResult =
          update(
              ImmutableList.of("//test:defs.bzl%aspect_a", "//test:defs.bzl%aspect_b"),
              ImmutableMap.of("p2", "p2_v1", "b_p", "b_p_v1"),
              "//test:main_target");

      assertThat(analysisResult.hasError()).isTrue();
    } else {
      assertThrows(
          ViewCreationFailedException.class,
          () ->
              update(
                  ImmutableList.of("//test:defs.bzl%aspect_a", "//test:defs.bzl%aspect_b"),
                  ImmutableMap.of("p2", "p2_v1", "b_p", "b_p_v1"),
                  "//test:main_target"));
    }
    assertContainsEvent(
        "Parameters '[p2]' are not parameters of any of the top-level aspects but they are"
            + " specified in --aspects_parameters.");
  }

  @Test
  public void testTopLevelAspectsWithParameters_invalidDefaultParameterValue() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _aspect_a_impl(target, ctx):",
        "  result = ['aspect_a on target {}, p = {}'.",
        "                                    format(target.label, ctx.attr.p)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.aspect_a_result",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p' : attr.string(values = ['p_v1', 'p_v2']) },",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "   attrs = { 'dep' : attr.label() },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        "  dep = ':dep_target_1',",
        ")",
        "my_rule(",
        "  name = 'dep_target_1',",
        "  dep = ':dep_target_2',",
        ")",
        "my_rule(",
        "  name = 'dep_target_2',",
        ")");
    reporter.removeHandler(failFastHandler);

    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a ViewCreationFailedException.
    if (keepGoing()) {
      AnalysisResult analysisResult =
          update(ImmutableList.of("//test:defs.bzl%aspect_a"), "//test:main_target");
      assertThat(analysisResult.hasError()).isTrue();
    } else {
      assertThrows(
          ViewCreationFailedException.class,
          () -> update(ImmutableList.of("//test:defs.bzl%aspect_a"), "//test:main_target"));
    }
    assertContainsEvent(
        "//test:defs.bzl%aspect_a: invalid value in 'p' attribute: has to be one of 'p_v1' or"
            + " 'p_v2' instead of ''");
  }

  @Test
  public void testTopLevelAspectsWithParameters_noNeedForAllowedValues() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _aspect_a_impl(target, ctx):",
        "  result = ['aspect_a on target {}, p = {}'.",
        "                                    format(target.label, ctx.attr.p)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.aspect_a_result",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p' : attr.string(default='val') },",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "   attrs = { 'dep' : attr.label() },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        "  dep = ':dep_target_1',",
        ")",
        "my_rule(",
        "  name = 'dep_target_1',",
        "  dep = ':dep_target_2',",
        ")",
        "my_rule(",
        "  name = 'dep_target_2',",
        ")");

    AnalysisResult analysisResult =
        update(
            ImmutableList.of("//test:defs.bzl%aspect_a"),
            ImmutableMap.of("p", "p_v"),
            "//test:main_target");

    ImmutableMap<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect aspectA = getConfiguredAspect(configuredAspects, "aspect_a");
    assertThat(aspectA).isNotNull();
    StarlarkList<?> aspectAResult = (StarlarkList) aspectA.get("aspect_a_result");
    assertThat(Starlark.toIterable(aspectAResult))
        .containsExactly(
            "aspect_a on target @//test:main_target, p = p_v",
            "aspect_a on target @//test:dep_target_1, p = p_v",
            "aspect_a on target @//test:dep_target_2, p = p_v");
  }

  /**
   * Aspect parameter has to require set of values only if the aspect is used in a rule attribute.
   */
  @Test
  public void testAttrAspectParameterMissingRequiredValues() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _impl(target, ctx):",
        "   pass",
        "my_aspect = aspect(_impl,",
        "   attrs = { 'param' : attr.string(default = 'c') }",
        ")",
        "def _rule_impl(ctx):",
        "   pass",
        "r1 = rule(_rule_impl, attrs={'dep': attr.label(aspects = [my_aspect])})");
    scratch.file("test/BUILD", "load('//test:defs.bzl', 'r1')", "r1(name = 'main_target')");
    reporter.removeHandler(failFastHandler);

    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a TargetParsingException.
    if (keepGoing()) {
      AnalysisResult analysisResult = update("//test:main_target");
      assertThat(analysisResult.hasError()).isTrue();
    } else {
      assertThrows(TargetParsingException.class, () -> update("//test:main_target"));
    }
    assertContainsEvent(
        "Aspect //test:defs.bzl%my_aspect: Aspect parameter attribute 'param' must use the 'values'"
            + " restriction.");
  }

  /**
   * Aspect parameter has to require set of values only if the aspect is used in a rule attribute.
   */
  @Test
  public void testAttrRequiredAspectParameterMissingRequiredValues() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _impl(target, ctx):",
        "   pass",
        "required_aspect = aspect(_impl,",
        "   attrs = { 'p1' : attr.string(default = 'b') }",
        ")",
        "my_aspect = aspect(_impl,",
        "   attrs = { 'p2' : attr.string(default = 'c', values = ['c']) },",
        "   requires = [required_aspect],",
        ")",
        "def _rule_impl(ctx):",
        "   pass",
        "r1 = rule(_rule_impl, attrs={'dep': attr.label(aspects = [my_aspect])})");
    scratch.file("test/BUILD", "load('//test:defs.bzl', 'r1')", "r1(name = 'main_target')");
    reporter.removeHandler(failFastHandler);

    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a TargetParsingException.
    if (keepGoing()) {
      AnalysisResult analysisResult = update("//test:main_target");
      assertThat(analysisResult.hasError()).isTrue();
    } else {
      assertThrows(TargetParsingException.class, () -> update("//test:main_target"));
    }
    assertContainsEvent(
        "Aspect //test:defs.bzl%required_aspect: Aspect parameter attribute 'p1' must use the"
            + " 'values' restriction.");
  }

  @Test
  public void integerAspectParameter_mandatoryAttrNotCoveredByRule() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "def _rule_impl(ctx):",
        "   return struct()",
        "MyAspectUncovered = aspect(",
        "    implementation=_impl,",
        "    attrs = { 'my_attr' : attr.int(default = 1, values = [1, 2], mandatory = True) },",
        ")",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'deps' : attr.label_list(aspects=[MyAspectUncovered]) },",
        ")");
    scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name ='main')");
    reporter.removeHandler(failFastHandler);

    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a TargetParsingException.
    if (keepGoing()) {
      AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main");
      assertThat(analysisResult.hasError()).isTrue();
    } else {
      assertThrows(TargetParsingException.class, () -> update(ImmutableList.of(), "//test:main"));
    }

    assertContainsEvent(
        "Aspect //test:aspect.bzl%MyAspectUncovered requires rule my_rule to specify attribute "
            + "'my_attr' with type int.");
  }

  @Test
  public void integerAspectParameter_mandatoryAttrWithWrongTypeInRule() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "def _rule_impl(ctx):",
        "   return struct()",
        "MyAspectUncovered = aspect(",
        "    implementation=_impl,",
        "    attrs = { 'my_attr' : attr.int(default = 1, values = [1, 2], mandatory = True) },",
        ")",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'deps' : attr.label_list(aspects=[MyAspectUncovered]),",
        "              'my_attr': attr.string() },",
        ")");
    scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name ='main')");
    reporter.removeHandler(failFastHandler);

    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a TargetParsingException.
    if (keepGoing()) {
      AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main");
      assertThat(analysisResult.hasError()).isTrue();
    } else {
      assertThrows(TargetParsingException.class, () -> update(ImmutableList.of(), "//test:main"));
    }

    assertContainsEvent(
        "Aspect //test:aspect.bzl%MyAspectUncovered requires rule my_rule to specify attribute "
            + "'my_attr' with type int.");
  }

  @Test
  public void integerAspectParameter_attrWithoutDefaultNotCoveredByRule() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "def _rule_impl(ctx):",
        "   return struct()",
        "MyAspectUncovered = aspect(",
        "    implementation=_impl,",
        "    attrs = { 'my_attr' : attr.int(values = [1, 2]) },",
        ")",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'deps' : attr.label_list(aspects=[MyAspectUncovered]) },",
        ")");
    scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name ='main')");
    reporter.removeHandler(failFastHandler);

    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a TargetParsingException.
    if (keepGoing()) {
      AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main");
      assertThat(analysisResult.hasError()).isTrue();
    } else {
      assertThrows(TargetParsingException.class, () -> update(ImmutableList.of(), "//test:main"));
    }

    assertContainsEvent(
        "Aspect //test:aspect.bzl%MyAspectUncovered requires rule my_rule to specify attribute "
            + "'my_attr' with type int.");
  }

  @Test
  public void integerAspectParameter_attrWithoutDefaultWrongTypeInRule() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "def _rule_impl(ctx):",
        "   return struct()",
        "MyAspectUncovered = aspect(",
        "    implementation=_impl,",
        "    attrs = { 'my_attr' : attr.int(values = [1, 2]) },",
        ")",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'deps' : attr.label_list(aspects=[MyAspectUncovered]),",
        "              'my_attr': attr.string() },",
        ")");
    scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name ='main')");
    reporter.removeHandler(failFastHandler);

    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a TargetParsingException.
    if (keepGoing()) {
      AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main");
      assertThat(analysisResult.hasError()).isTrue();
    } else {
      assertThrows(TargetParsingException.class, () -> update(ImmutableList.of(), "//test:main"));
    }

    assertContainsEvent(
        "Aspect //test:aspect.bzl%MyAspectUncovered requires rule my_rule to specify attribute "
            + "'my_attr' with type int.");
  }

  @Test
  public void integerAspectParameter_missingValuesRestriction() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "def _rule_impl(ctx):",
        "   return struct()",
        "MyAspectUncovered = aspect(",
        "    implementation=_impl,",
        "    attrs = { 'my_attr' : attr.int() },",
        ")",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'deps' : attr.label_list(aspects=[MyAspectUncovered]),",
        "              'my_attr' : attr.int() },",
        ")");
    scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name ='main')");
    reporter.removeHandler(failFastHandler);

    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a TargetParsingException.
    if (keepGoing()) {
      AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main");
      assertThat(analysisResult.hasError()).isTrue();
    } else {
      assertThrows(TargetParsingException.class, () -> update(ImmutableList.of(), "//test:main"));
    }

    assertContainsEvent(
        "Aspect //test:aspect.bzl%MyAspectUncovered: Aspect parameter attribute 'my_attr' must use"
            + " the 'values' restriction.");
  }

  @Test
  public void integerAspectParameter_invalidDefault() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "def _rule_impl(ctx):",
        "   return struct()",
        "MyAspectUncovered = aspect(",
        "    implementation=_impl,",
        "    attrs = { 'my_attr' : attr.int(default = 2, values = [0, 1]) },",
        ")",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'deps' : attr.label_list(aspects=[MyAspectUncovered]) },",
        ")");
    scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name ='main')");
    reporter.removeHandler(failFastHandler);

    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a TargetParsingException.
    if (keepGoing()) {
      AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main");
      assertThat(analysisResult.hasError()).isTrue();
    } else {
      assertThrows(TargetParsingException.class, () -> update(ImmutableList.of(), "//test:main"));
    }

    assertContainsEvent(
        "Aspect parameter attribute 'my_attr' has a bad default value: has to be one of '0' or '1'"
            + " instead of '2'");
  }

  @Test
  public void aspectIntegerParameter_withDefaultValue() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _aspect_impl(target, ctx):",
        "  result = ['my_aspect on target {}, my_attr = {}'.",
        "                                    format(target.label, ctx.attr.my_attr)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.my_aspect_result",
        "  return struct(my_aspect_result = result)",
        "",
        "def _rule_impl(ctx):",
        "  if ctx.attr.dep:",
        "    return struct(my_rule_result = ctx.attr.dep.my_aspect_result)",
        "  pass",
        "",
        "MyAspect = aspect(",
        "    implementation = _aspect_impl,",
        "    attrs = { 'my_attr' : attr.int(default = 1, values = [1, 2, 3]) },",
        ")",
        "",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'dep' : attr.label(aspects=[MyAspect]) }",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:aspect.bzl', 'my_rule')",
        "my_rule(name = 'main_target',",
        "        dep = ':dep_target',",
        ")",
        "my_rule(name = 'dep_target')");

    AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main_target");

    ConfiguredTarget configuredTarget =
        Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    StarlarkList<?> ruleResult = (StarlarkList) configuredTarget.get("my_rule_result");
    assertThat(Starlark.toIterable(ruleResult))
        .containsExactly("my_aspect on target @//test:dep_target, my_attr = 1");
  }

  @Test
  public void aspectIntegerParameter_valueOverwrittenByRuleDefault() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _aspect_impl(target, ctx):",
        "  result = ['my_aspect on target {}, my_attr = {}'.",
        "                                    format(target.label, ctx.attr.my_attr)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.my_aspect_result",
        "  return struct(my_aspect_result = result)",
        "",
        "def _rule_impl(ctx):",
        "  if ctx.attr.dep:",
        "    return struct(my_rule_result = ctx.attr.dep.my_aspect_result)",
        "  pass",
        "",
        "MyAspect = aspect(",
        "    implementation = _aspect_impl,",
        "    attrs = { 'my_attr' : attr.int(default = 1, values = [1, 2, 3]) },",
        ")",
        "",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'dep' : attr.label(aspects=[MyAspect]),",
        "              'my_attr': attr.int(default = 2) }",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:aspect.bzl', 'my_rule')",
        "my_rule(name = 'main_target',",
        "        dep = ':dep_target',",
        ")",
        "my_rule(name = 'dep_target')");

    AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main_target");

    ConfiguredTarget configuredTarget =
        Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    StarlarkList<?> ruleResult = (StarlarkList) configuredTarget.get("my_rule_result");
    assertThat(Starlark.toIterable(ruleResult))
        .containsExactly("my_aspect on target @//test:dep_target, my_attr = 2");
  }

  @Test
  public void aspectIntegerParameter_valueOverwrittenByTargetValue() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _aspect_impl(target, ctx):",
        "  result = ['my_aspect on target {}, my_attr = {}'.",
        "                                    format(target.label, ctx.attr.my_attr)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.my_aspect_result",
        "  return struct(my_aspect_result = result)",
        "",
        "def _rule_impl(ctx):",
        "  if ctx.attr.dep:",
        "    return struct(my_rule_result = ctx.attr.dep.my_aspect_result)",
        "  pass",
        "",
        "MyAspect = aspect(",
        "    implementation = _aspect_impl,",
        "    attrs = { 'my_attr' : attr.int(default = 1, values = [1, 2, 3]) },",
        ")",
        "",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'dep' : attr.label(aspects=[MyAspect]),",
        "              'my_attr': attr.int(default = 2) }",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:aspect.bzl', 'my_rule')",
        "my_rule(name = 'main_target',",
        "        dep = ':dep_target',",
        "        my_attr = 3,",
        ")",
        "my_rule(name = 'dep_target')");

    AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main_target");

    ConfiguredTarget configuredTarget =
        Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    StarlarkList<?> ruleResult = (StarlarkList) configuredTarget.get("my_rule_result");
    assertThat(Starlark.toIterable(ruleResult))
        .containsExactly("my_aspect on target @//test:dep_target, my_attr = 3");
  }

  @Test
  public void testTopLevelAspectsWithParameters_invalidIntegerParameterValue() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _aspect_a_impl(target, ctx):",
        "  result = ['aspect_a on target {}, p = {}'.",
        "                                    format(target.label, ctx.attr.p)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.aspect_a_result",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p' : attr.int(values = [1, 2]) },",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "   attrs = { 'dep' : attr.label() },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        "  dep = ':dep_target_1',",
        ")",
        "my_rule(",
        "  name = 'dep_target_1',",
        "  dep = ':dep_target_2',",
        ")",
        "my_rule(",
        "  name = 'dep_target_2',",
        ")");
    reporter.removeHandler(failFastHandler);

    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a ViewCreationFailedException.
    if (keepGoing()) {
      AnalysisResult analysisResult =
          update(
              ImmutableList.of("//test:defs.bzl%aspect_a"),
              ImmutableMap.of("p", "3"),
              "//test:main_target");
      assertThat(analysisResult.hasError()).isTrue();
    } else {
      assertThrows(
          ViewCreationFailedException.class,
          () ->
              update(
                  ImmutableList.of("//test:defs.bzl%aspect_a"),
                  ImmutableMap.of("p", "3"),
                  "//test:main_target"));
    }
    assertContainsEvent(
        "//test:defs.bzl%aspect_a: invalid value in 'p' attribute: has to be one of '1' or"
            + " '2' instead of '3'");
  }

  @Test
  public void testTopLevelAspectsWithIntegerParameter() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _aspect_a_impl(target, ctx):",
        "  result = ['aspect_a on target {}, p = {}'.",
        "                                    format(target.label, ctx.attr.p)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.aspect_a_result",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p' : attr.int(values = [1, 2, 3]) },",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "   attrs = { 'dep' : attr.label() },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        "  dep = ':dep_target_1',",
        ")",
        "my_rule(",
        "  name = 'dep_target_1',",
        "  dep = ':dep_target_2',",
        ")",
        "my_rule(",
        "  name = 'dep_target_2',",
        ")");

    AnalysisResult analysisResult =
        update(
            ImmutableList.of("//test:defs.bzl%aspect_a"),
            ImmutableMap.of("p", "2"),
            "//test:main_target");

    ImmutableMap<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect aspectA = getConfiguredAspect(configuredAspects, "aspect_a");
    assertThat(aspectA).isNotNull();
    StarlarkList<?> aspectAResult = (StarlarkList) aspectA.get("aspect_a_result");
    assertThat(Starlark.toIterable(aspectAResult))
        .containsExactly(
            "aspect_a on target @//test:main_target, p = 2",
            "aspect_a on target @//test:dep_target_1, p = 2",
            "aspect_a on target @//test:dep_target_2, p = 2");
  }

  @Test
  public void testTopLevelAspectsWithIntegerParameter_useDefaultValue() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _aspect_a_impl(target, ctx):",
        "  result = ['aspect_a on target {}, p = {}'.",
        "                                    format(target.label, ctx.attr.p)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.aspect_a_result",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p' : attr.int(default = 1, values = [1, 2, 3]) },",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "   attrs = { 'dep' : attr.label() },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        "  dep = ':dep_target_1',",
        ")",
        "my_rule(",
        "  name = 'dep_target_1',",
        "  dep = ':dep_target_2',",
        ")",
        "my_rule(",
        "  name = 'dep_target_2',",
        ")");

    AnalysisResult analysisResult =
        update(ImmutableList.of("//test:defs.bzl%aspect_a"), "//test:main_target");

    ImmutableMap<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect aspectA = getConfiguredAspect(configuredAspects, "aspect_a");
    assertThat(aspectA).isNotNull();
    StarlarkList<?> aspectAResult = (StarlarkList) aspectA.get("aspect_a_result");
    assertThat(Starlark.toIterable(aspectAResult))
        .containsExactly(
            "aspect_a on target @//test:main_target, p = 1",
            "aspect_a on target @//test:dep_target_1, p = 1",
            "aspect_a on target @//test:dep_target_2, p = 1");
  }

  @Test
  public void booleanAspectParameter_mandatoryAttrNotCoveredByRule() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "def _rule_impl(ctx):",
        "   return struct()",
        "MyAspectUncovered = aspect(",
        "    implementation=_impl,",
        "    attrs = { 'my_attr' : attr.bool(default = True, mandatory = True) },",
        ")",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'deps' : attr.label_list(aspects=[MyAspectUncovered]) },",
        ")");
    scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name ='main')");
    reporter.removeHandler(failFastHandler);

    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a TargetParsingException.
    if (keepGoing()) {
      AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main");
      assertThat(analysisResult.hasError()).isTrue();
    } else {
      assertThrows(TargetParsingException.class, () -> update(ImmutableList.of(), "//test:main"));
    }

    assertContainsEvent(
        "Aspect //test:aspect.bzl%MyAspectUncovered requires rule my_rule to specify attribute "
            + "'my_attr' with type boolean.");
  }

  @Test
  public void booleanAspectParameter_mandatoryAttrWithWrongTypeInRule() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "def _rule_impl(ctx):",
        "   return struct()",
        "MyAspectUncovered = aspect(",
        "    implementation=_impl,",
        "    attrs = { 'my_attr' : attr.bool(default = True, mandatory = True) },",
        ")",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'deps' : attr.label_list(aspects=[MyAspectUncovered]),",
        "              'my_attr': attr.string() },",
        ")");
    scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name ='main')");
    reporter.removeHandler(failFastHandler);

    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a TargetParsingException.
    if (keepGoing()) {
      AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main");
      assertThat(analysisResult.hasError()).isTrue();
    } else {
      assertThrows(TargetParsingException.class, () -> update(ImmutableList.of(), "//test:main"));
    }

    assertContainsEvent(
        "Aspect //test:aspect.bzl%MyAspectUncovered requires rule my_rule to specify attribute "
            + "'my_attr' with type boolean.");
  }

  @Test
  public void booleanAspectParameter_attrWithoutDefaultNotCoveredByRule() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "def _rule_impl(ctx):",
        "   return struct()",
        "MyAspectUncovered = aspect(",
        "    implementation=_impl,",
        "    attrs = { 'my_attr' : attr.bool() },",
        ")",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'deps' : attr.label_list(aspects=[MyAspectUncovered]) },",
        ")");
    scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name ='main')");
    reporter.removeHandler(failFastHandler);

    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a TargetParsingException.
    if (keepGoing()) {
      AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main");
      assertThat(analysisResult.hasError()).isTrue();
    } else {
      assertThrows(TargetParsingException.class, () -> update(ImmutableList.of(), "//test:main"));
    }

    assertContainsEvent(
        "Aspect //test:aspect.bzl%MyAspectUncovered requires rule my_rule to specify attribute "
            + "'my_attr' with type boolean.");
  }

  @Test
  public void booleanAspectParameter_attrWithoutDefaultWrongTypeInRule() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _impl(target, ctx):",
        "   return struct()",
        "def _rule_impl(ctx):",
        "   return struct()",
        "MyAspectUncovered = aspect(",
        "    implementation=_impl,",
        "    attrs = { 'my_attr' : attr.bool() },",
        ")",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'deps' : attr.label_list(aspects=[MyAspectUncovered]),",
        "              'my_attr': attr.string() },",
        ")");
    scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name ='main')");
    reporter.removeHandler(failFastHandler);

    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a TargetParsingException.
    if (keepGoing()) {
      AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main");
      assertThat(analysisResult.hasError()).isTrue();
    } else {
      assertThrows(TargetParsingException.class, () -> update(ImmutableList.of(), "//test:main"));
    }

    assertContainsEvent(
        "Aspect //test:aspect.bzl%MyAspectUncovered requires rule my_rule to specify attribute "
            + "'my_attr' with type boolean.");
  }

  @Test
  public void aspectBooleanParameter_withDefaultValue() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _aspect_impl(target, ctx):",
        "  result = ['my_aspect on target {}, my_attr = {}'.",
        "                                    format(target.label, ctx.attr.my_attr)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.my_aspect_result",
        "  return struct(my_aspect_result = result)",
        "",
        "def _rule_impl(ctx):",
        "  if ctx.attr.dep:",
        "    return struct(my_rule_result = ctx.attr.dep.my_aspect_result)",
        "  pass",
        "",
        "MyAspect = aspect(",
        "    implementation = _aspect_impl,",
        "    attrs = { 'my_attr' : attr.bool(default = True) },",
        ")",
        "",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'dep' : attr.label(aspects=[MyAspect]) }",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:aspect.bzl', 'my_rule')",
        "my_rule(name = 'main_target',",
        "        dep = ':dep_target',",
        ")",
        "my_rule(name = 'dep_target')");

    AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main_target");

    ConfiguredTarget configuredTarget =
        Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    StarlarkList<?> ruleResult = (StarlarkList) configuredTarget.get("my_rule_result");
    assertThat(Starlark.toIterable(ruleResult))
        .containsExactly("my_aspect on target @//test:dep_target, my_attr = True");
  }

  @Test
  public void aspectBooleanParameter_valueOverwrittenByRuleDefault() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _aspect_impl(target, ctx):",
        "  result = ['my_aspect on target {}, my_attr = {}'.",
        "                                    format(target.label, ctx.attr.my_attr)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.my_aspect_result",
        "  return struct(my_aspect_result = result)",
        "",
        "def _rule_impl(ctx):",
        "  if ctx.attr.dep:",
        "    return struct(my_rule_result = ctx.attr.dep.my_aspect_result)",
        "  pass",
        "",
        "MyAspect = aspect(",
        "    implementation = _aspect_impl,",
        "    attrs = { 'my_attr' : attr.bool(default = True) },",
        ")",
        "",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'dep' : attr.label(aspects=[MyAspect]),",
        "              'my_attr': attr.bool(default = False) }",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:aspect.bzl', 'my_rule')",
        "my_rule(name = 'main_target',",
        "        dep = ':dep_target',",
        ")",
        "my_rule(name = 'dep_target')");

    AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main_target");

    ConfiguredTarget configuredTarget =
        Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    StarlarkList<?> ruleResult = (StarlarkList) configuredTarget.get("my_rule_result");
    assertThat(Starlark.toIterable(ruleResult))
        .containsExactly("my_aspect on target @//test:dep_target, my_attr = False");
  }

  @Test
  public void aspectBooleanParameter_valueOverwrittenByTargetValue() throws Exception {
    scratch.file(
        "test/aspect.bzl",
        "def _aspect_impl(target, ctx):",
        "  result = ['my_aspect on target {}, my_attr = {}'.",
        "                                    format(target.label, ctx.attr.my_attr)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.my_aspect_result",
        "  return struct(my_aspect_result = result)",
        "",
        "def _rule_impl(ctx):",
        "  if ctx.attr.dep:",
        "    return struct(my_rule_result = ctx.attr.dep.my_aspect_result)",
        "  pass",
        "",
        "MyAspect = aspect(",
        "    implementation = _aspect_impl,",
        "    attrs = { 'my_attr' : attr.bool(default = True) },",
        ")",
        "",
        "my_rule = rule(",
        "    implementation=_rule_impl,",
        "    attrs = { 'dep' : attr.label(aspects=[MyAspect]),",
        "              'my_attr': attr.bool(default = True) }",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:aspect.bzl', 'my_rule')",
        "my_rule(name = 'main_target',",
        "        dep = ':dep_target',",
        "        my_attr = False,",
        ")",
        "my_rule(name = 'dep_target')");

    AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main_target");

    ConfiguredTarget configuredTarget =
        Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    StarlarkList<?> ruleResult = (StarlarkList) configuredTarget.get("my_rule_result");
    assertThat(Starlark.toIterable(ruleResult))
        .containsExactly("my_aspect on target @//test:dep_target, my_attr = False");
  }

  @Test
  public void testTopLevelAspectsWithBooleanParameter() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _aspect_a_impl(target, ctx):",
        "  result = ['aspect_a on target {}, p = {}'.",
        "                                    format(target.label, ctx.attr.p)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.aspect_a_result",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p' : attr.bool() },",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "   attrs = { 'dep' : attr.label() },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        "  dep = ':dep_target_1',",
        ")",
        "my_rule(",
        "  name = 'dep_target_1',",
        "  dep = ':dep_target_2',",
        ")",
        "my_rule(",
        "  name = 'dep_target_2',",
        ")");

    AnalysisResult analysisResult =
        update(
            ImmutableList.of("//test:defs.bzl%aspect_a"),
            ImmutableMap.of("p", "y"),
            "//test:main_target");

    ImmutableMap<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect aspectA = getConfiguredAspect(configuredAspects, "aspect_a");
    assertThat(aspectA).isNotNull();
    StarlarkList<?> aspectAResult = (StarlarkList) aspectA.get("aspect_a_result");
    assertThat(Starlark.toIterable(aspectAResult))
        .containsExactly(
            "aspect_a on target @//test:main_target, p = True",
            "aspect_a on target @//test:dep_target_1, p = True",
            "aspect_a on target @//test:dep_target_2, p = True");
  }

  @Test
  public void testTopLevelAspectsWithBooleanParameter_useDefaultValue() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _aspect_a_impl(target, ctx):",
        "  result = ['aspect_a on target {}, p = {}'.",
        "                                    format(target.label, ctx.attr.p)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.aspect_a_result",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p' : attr.bool(default = False) },",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "   attrs = { 'dep' : attr.label() },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        "  dep = ':dep_target_1',",
        ")",
        "my_rule(",
        "  name = 'dep_target_1',",
        "  dep = ':dep_target_2',",
        ")",
        "my_rule(",
        "  name = 'dep_target_2',",
        ")");

    AnalysisResult analysisResult =
        update(ImmutableList.of("//test:defs.bzl%aspect_a"), "//test:main_target");

    ImmutableMap<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect aspectA = getConfiguredAspect(configuredAspects, "aspect_a");
    assertThat(aspectA).isNotNull();
    StarlarkList<?> aspectAResult = (StarlarkList) aspectA.get("aspect_a_result");
    assertThat(Starlark.toIterable(aspectAResult))
        .containsExactly(
            "aspect_a on target @//test:main_target, p = False",
            "aspect_a on target @//test:dep_target_1, p = False",
            "aspect_a on target @//test:dep_target_2, p = False");
  }

  @Test
  public void testTopLevelAspectsWithBooleanParameter_invalidValue() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _aspect_a_impl(target, ctx):",
        "  result = ['aspect_a on target {}, p = {}'.",
        "                                    format(target.label, ctx.attr.p)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.aspect_a_result",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p' : attr.bool() },",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "   attrs = { 'dep' : attr.label() },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        "  dep = ':dep_target_1',",
        ")",
        "my_rule(",
        "  name = 'dep_target_1',",
        "  dep = ':dep_target_2',",
        ")",
        "my_rule(",
        "  name = 'dep_target_2',",
        ")");
    reporter.removeHandler(failFastHandler);

    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a ViewCreationFailedException.
    if (keepGoing()) {
      AnalysisResult analysisResult =
          update(
              ImmutableList.of("//test:defs.bzl%aspect_a"),
              ImmutableMap.of("p", "x"),
              "//test:main_target");
      assertThat(analysisResult.hasError()).isTrue();
    } else {
      assertThrows(
          ViewCreationFailedException.class,
          () ->
              update(
                  ImmutableList.of("//test:defs.bzl%aspect_a"),
                  ImmutableMap.of("p", "x"),
                  "//test:main_target"));
    }
    assertContainsEvent(
        "//test:defs.bzl%aspect_a: expected value of type 'bool' for attribute 'p' but got 'x'");
  }

  @Test
  public void testRuleAspectWithMandatoryParameterNotProvided() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _aspect_a_impl(target, ctx):",
        "  result = ['aspect_a on target {}, p = {}'.",
        "                                    format(target.label, ctx.attr.p)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.aspect_a_result",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p' : attr.string(default = 'p_v', values = ['p_v'], mandatory = True) },",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "   attrs = { 'dep' : attr.label(aspects = [aspect_a]) },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        "  dep = ':dep_target_1',",
        ")",
        "my_rule(",
        "  name = 'dep_target_1',",
        "  dep = ':dep_target_2',",
        ")",
        "my_rule(",
        "  name = 'dep_target_2',",
        ")");
    reporter.removeHandler(failFastHandler);

    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a TargetParsingException.
    if (keepGoing()) {
      AnalysisResult analysisResult = update("//test:main_target");
      assertThat(analysisResult.hasError()).isTrue();
    } else {
      assertThrows(TargetParsingException.class, () -> update("//test:main_target"));
    }
    assertContainsEvent(
        "Aspect //test:defs.bzl%aspect_a requires rule my_rule to specify attribute 'p' with type"
            + " string");
  }

  @Test
  public void testRuleAspectWithMandatoryParameterProvidedWrongType() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _aspect_a_impl(target, ctx):",
        "  result = ['aspect_a on target {}, p = {}'.",
        "                                    format(target.label, ctx.attr.p)]",
        "  if ctx.rule.attr.dep:",
        "    result += ctx.rule.attr.dep.aspect_a_result",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p' : attr.string(default = 'p_v', values = ['p_v'], mandatory = True) },",
        ")",
        "",
        "def _my_rule_impl(ctx):",
        "  pass",
        "my_rule = rule(",
        "  implementation = _my_rule_impl,",
        "   attrs = { 'dep' : attr.label(aspects = [aspect_a]),",
        "             'p': attr.int() } ",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'my_rule')",
        "my_rule(",
        "  name = 'main_target',",
        "  dep = ':dep_target_1',",
        ")",
        "my_rule(",
        "  name = 'dep_target_1',",
        "  dep = ':dep_target_2',",
        ")",
        "my_rule(",
        "  name = 'dep_target_2',",
        ")");
    reporter.removeHandler(failFastHandler);

    // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
    // suite. Otherwise, it fails and throws a TargetParsingException.
    if (keepGoing()) {
      AnalysisResult analysisResult = update("//test:main_target");
      assertThat(analysisResult.hasError()).isTrue();
    } else {
      assertThrows(TargetParsingException.class, () -> update("//test:main_target"));
    }
    assertContainsEvent(
        "Aspect //test:defs.bzl%aspect_a requires rule my_rule to specify attribute 'p' with type"
            + " string");
  }

  @Test
  public void testRuleAspectWithMandatoryParameter_useRuleDefault() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _aspect_a_impl(target, ctx):",
        "  result = 'aspect_a on target {}, p = {}'.format(target.label, ctx.attr.p)",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p' : attr.string(default = 'p_v1', values = ['p_v1', 'p_v2'],",
        "                              mandatory = True) },",
        ")",
        "",
        "def _main_rule_impl(ctx):",
        "  if ctx.attr.dep:",
        "    return struct(aspect_a_result = ctx.attr.dep.aspect_a_result)",
        "  pass",
        "main_rule = rule(",
        "  implementation = _main_rule_impl,",
        "  attrs = {",
        "    'dep': attr.label(aspects = [aspect_a]),",
        "    'p' : attr.string(default = 'p_v2'),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'main_rule')",
        "main_rule(",
        "  name = 'main',",
        "  dep = ':dep_target',",
        ")",
        "main_rule(",
        "  name = 'dep_target',",
        ")");

    AnalysisResult analysisResult = update("//test:main");

    ConfiguredTarget configuredTarget =
        Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    String aspectAResult = (String) configuredTarget.get("aspect_a_result");
    assertThat(aspectAResult).isEqualTo("aspect_a on target @//test:dep_target, p = p_v2");
  }

  @Test
  public void testRuleAspectWithMandatoryParameterProvided() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _aspect_a_impl(target, ctx):",
        "  result = 'aspect_a on target {}, p = {}'.format(target.label, ctx.attr.p)",
        "  return struct(aspect_a_result = result)",
        "",
        "aspect_a = aspect(",
        "  implementation = _aspect_a_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = { 'p' : attr.string(default = 'p_v2', values = ['p_v1', 'p_v2'],",
        "                              mandatory = True) },",
        ")",
        "",
        "def _main_rule_impl(ctx):",
        "  if ctx.attr.dep:",
        "    return struct(aspect_a_result = ctx.attr.dep.aspect_a_result)",
        "  pass",
        "main_rule = rule(",
        "  implementation = _main_rule_impl,",
        "  attrs = {",
        "    'dep': attr.label(aspects = [aspect_a]),",
        "    'p' : attr.string(mandatory = True),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'main_rule')",
        "main_rule(",
        "  name = 'main',",
        "  dep = ':dep_target',",
        "  p = 'p_v1',",
        ")",
        "main_rule(",
        "  name = 'dep_target',",
        "  p = 'p_v2',",
        ")");

    AnalysisResult analysisResult = update("//test:main");

    ConfiguredTarget configuredTarget =
        Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    String aspectAResult = (String) configuredTarget.get("aspect_a_result");
    assertThat(aspectAResult).isEqualTo("aspect_a on target @//test:dep_target, p = p_v1");
  }

  @Test
  public void testAspectLabelIsRepoMapped() throws Exception {
    scratch.appendFile("WORKSPACE", "workspace(name = 'my_repo')");
    scratch.file(
        "test/aspect.bzl",
        "load(':rule.bzl', 'MyInfo')",
        "def _impl(target, ctx):",
        "   if MyInfo not in target:",
        "       fail('Provider identity mismatch')",
        "   return struct()",
        "MyAspect = aspect(implementation=_impl)");
    scratch.file(
        "test/rule.bzl",
        "MyInfo = provider()",
        "def _impl(ctx):",
        "    return [MyInfo()]",
        "my_rule = rule(implementation=_impl)");
    scratch.file("test/BUILD", "load(':rule.bzl', 'my_rule')", "my_rule(name = 'target')");

    AnalysisResult result =
        update(ImmutableList.of("@my_repo//test:aspect.bzl%MyAspect"), "//test:target");
    assertThat(result.hasError()).isFalse();
  }

  @Test
  public void testAspectKeyCreatedOnlyOnceForSameBaseKeysInDiffOrder() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "a_provider = provider()",
        "b_provider = provider()",
        "c_provider = provider()",
        "",
        "def _a_impl(target, ctx):",
        "  result = []",
        "  if ctx.rule.attr.deps:",
        "    for dep in ctx.rule.attr.deps:",
        "      result.extend(dep[a_provider].value)",
        "  result.append('aspect a on target {} aspect_ids {}'.format(target.label,",
        "                                                                ctx.aspect_ids))",
        "  return [a_provider(value = result)]",
        "a = aspect(",
        "  implementation = _a_impl,",
        "  attr_aspects = ['deps'],",
        "  required_aspect_providers = [[b_provider], [c_provider]],",
        ")",
        "",
        "def _b_impl(target, ctx):",
        "  return [b_provider(value = ['aspect b on target {}'.format(target.label)])]",
        "b = aspect(",
        "  implementation = _b_impl,",
        "  attr_aspects = ['deps'],",
        "  provides = [b_provider],",
        ")",
        "",
        "def _c_impl(target, ctx):",
        "  return [c_provider(value = ['aspect c on target {}'.format(target.label)])]",
        "c = aspect(",
        "  implementation = _c_impl,",
        "  attr_aspects = ['deps'],",
        "  provides = [c_provider]",
        ")",
        "",
        "def _r1_impl(ctx):",
        "  result = []",
        "  if ctx.attr.deps:",
        "    for dep in ctx.attr.deps:",
        "      result.extend(dep[a_provider].value)",
        "  return struct(aspect_a_collected_result = result)",
        "r1 = rule(",
        "  implementation = _r1_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(aspects = [c, b, a]),",
        "  },",
        ")",
        "",
        "def _r2_impl(ctx):",
        "  pass",
        "r2 = rule(",
        "  implementation = _r2_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(aspects = [b]),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'r1', 'r2')",
        "r1(",
        "  name = 't1',",
        // base_keys of aspect a on t3 are [c, b]
        "  deps = [':t2', ':t3'],",
        ")",
        "r2(",
        "  name = 't2',",
        // aspects reaching t3 will be [b, c, b, a], after deduplicating aspects path, it will be
        // [b, c, a] and as a result the base_keys of aspect a will be [b, c]
        "  deps = [':t3'],",
        ")",
        "r2(",
        "  name = 't3',",
        ")");

    update("//test:t1");

    // Aspect a should have a single AspectKey for its application on t3 and the baseKeys in it will
    // be sorted as [b, c]
    ImmutableList<AspectKey> keysForAspectAOnT3 = getAspectKeys("//test:t3", "//test:defs.bzl%a");
    assertThat(keysForAspectAOnT3).hasSize(1);

    ImmutableList<AspectKey> baseKeys = keysForAspectAOnT3.get(0).getBaseKeys();
    assertThat(baseKeys.stream().map(k -> k.getAspectClass().getName()))
        .containsExactly("//test:defs.bzl%b", "//test:defs.bzl%c")
        .inOrder();
  }

  @Test
  public void testAspectRunsTwiceWithDiffBaseAspectsDeps() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "a_provider = provider()",
        "b_provider = provider()",
        "c_provider = provider()",
        "",
        "def _a_impl(target, ctx):",
        "  result = []",
        "  if ctx.rule.attr.deps:",
        "    for dep in ctx.rule.attr.deps:",
        "      result.extend(dep[a_provider].value)",
        "  if b_provider in target:",
        "    result.append('aspect a on {} sees b_provider = {}'.format(target.label,",
        "                                                              target[b_provider].value))",
        "  return [a_provider(value = result)]",
        "a = aspect(",
        "  implementation = _a_impl,",
        "  attr_aspects = ['deps'],",
        "  required_aspect_providers = [[b_provider], [c_provider]],",
        ")",
        "",
        "def _b_impl(target, ctx):",
        "  result = 'aspect b cannot see c_provider'",
        "  if c_provider in target:",
        "    result = 'aspect b can see c_provider'",
        "  return [b_provider(value = result)]",
        "b = aspect(",
        "  implementation = _b_impl,",
        "  attr_aspects = ['deps'],",
        "  provides = [b_provider],",
        "  required_aspect_providers = [[c_provider]]",
        ")",
        "",
        "def _c_impl(target, ctx):",
        "  return [c_provider(value = ['aspect c on target {}'.format(target.label)])]",
        "c = aspect(",
        "  implementation = _c_impl,",
        "  attr_aspects = ['deps'],",
        "  provides = [c_provider]",
        ")",
        "",
        "def _r1_impl(ctx):",
        "  result = []",
        "  if ctx.attr.deps:",
        "    for dep in ctx.attr.deps:",
        "      result.extend(dep[a_provider].value)",
        "  return struct(aspect_a_collected_result = result)",
        "r1 = rule(",
        "  implementation = _r1_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(aspects = [a]),",
        "  },",
        ")",
        "",
        "def _r2_impl(ctx):",
        "  pass",
        "r2 = rule(",
        "  implementation = _r2_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(aspects = [c, b]),",
        "  },",
        ")",
        "",
        "def _r3_impl(ctx):",
        "  pass",
        "r3 = rule(",
        "  implementation = _r3_impl,",
        "  attrs = {",
        "    'deps': attr.label_list(aspects = [b, c]),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'r1', 'r2', 'r3')",
        "r1(",
        "  name = 't1',",
        // t1 propagate aspect (a) to targets (t2 and t3)
        "  deps = [':t2', ':t3'],",
        ")",
        "r2(",
        "  name = 't2',",
        // t2 propagates aspects (c, b) to target t4 and aspect a is propagated from the prev level
        // aspects path on t4 is [c, b, a], this means a can see b and b can see c
        "  deps = [':t4'],",
        ")",
        "r3(",
        "  name = 't3',",
        // t3 propagates aspects (b, c) to target t4 and aspect a is propagated from the prev level
        // aspects path on t4 is [b, c, a], this means a can see b but b cannot see c
        " deps = [':t4'],",
        ")",
        "r1(",
        "  name = 't4',",
        ")");

    AnalysisResult analysisResult = update("//test:t1");

    // Aspect a should have 2 AspectKeys for its application on t4, one where in the basekeys b can
    // see c and the other is where b cannot see c
    ImmutableList<AspectKey> keysForAspectAOnT4 = getAspectKeys("//test:t4", "//test:defs.bzl%a");
    assertThat(keysForAspectAOnT4).hasSize(2);

    ConfiguredTarget configuredTarget =
        Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
    StarlarkList<?> aspectAResult =
        (StarlarkList) configuredTarget.get("aspect_a_collected_result");
    assertThat(Starlark.toIterable(aspectAResult))
        .containsExactly(
            "aspect a on @//test:t4 sees b_provider = aspect b can see c_provider",
            "aspect a on @//test:t4 sees b_provider = aspect b cannot see c_provider");
  }

  @Test
  public void testAspectWithSameExplicitAttributeNameAsUnderlyingTarget() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _a_impl(target, ctx):",
        "  value = 'x from aspect = {}, x from target = {}'.format(ctx.attr.x, ctx.rule.attr.x)",
        "  return struct(aspect_result = value)",
        "a = aspect(",
        "  implementation = _a_impl,",
        "  attrs = {",
        "    'x': attr.string(default = 'xyz')",
        "  },",
        ")",
        "",
        "def _rule_impl(ctx):",
        "  pass",
        "r1 = rule(",
        "  implementation = _rule_impl,",
        "  attrs = {",
        "    'x': attr.int(default = 4)",
        "  },",
        ")");
    scratch.file("test/BUILD", "load('//test:defs.bzl', 'r1')", "r1(name = 't1')");

    AnalysisResult analysisResult = update(ImmutableList.of("//test:defs.bzl%a"), "//test:t1");

    ImmutableMap<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
    ConfiguredAspect aspectA = getConfiguredAspect(configuredAspects, "a");
    assertThat(aspectA).isNotNull();
    String aspectAResult = (String) aspectA.get("aspect_result");
    assertThat(aspectAResult).isEqualTo("x from aspect = xyz, x from target = 4");
  }

  @Test
  public void testAspectNotDependOnTargetDeps() throws Exception {
    scratch.file(
        "test/defs.bzl",
        "def _a_impl(target, ctx):",
        "  return []",
        "a = aspect(",
        "  implementation = _a_impl,",
        "  attr_aspects = ['dep'],",
        "  attrs = {",
        "    '_tool': attr.label(default = '//test:tool'),",
        "  },",
        ")",
        "",
        "def _rule_impl(ctx):",
        "  pass",
        "r1 = rule(",
        "  implementation = _rule_impl,",
        "  attrs = {",
        "    'dep': attr.label(),",
        "    'another_dep': attr.label(),",
        "    '_tool': attr.label(default = '//test:tool'),",
        "  },",
        ")");
    scratch.file(
        "test/BUILD",
        "load('//test:defs.bzl', 'r1')",
        "r1(name = 't1', dep = ':t2', another_dep = 't4')",
        "r1(name = 't2', dep = ':t3')",
        "r1(name = 't3')",
        "r1(name = 't4')",
        "sh_library(name = 'tool')");

    AnalysisResult analysisResult = update(ImmutableList.of("//test:defs.bzl%a"), "//test:t1");

    AspectKey key = Iterables.getOnlyElement(analysisResult.getAspectsMap().keySet());
    var aspectNode =
        skyframeExecutor.getEvaluator().getInMemoryGraph().getAllNodeEntries().stream()
            .filter(n -> n.getKey().equals(key))
            .findFirst()
            .orElse(null);
    assertThat(aspectNode).isNotNull();

    ImmutableList<String> configuredTargetsDeps =
        stream(Iterables.filter(aspectNode.getDirectDeps(), ConfiguredTargetKey.class))
            .map(k -> k.getLabel().toString())
            .collect(toImmutableList());
    // aspect depends only on its target and its implicit dependencies not the dependencies of its
    // target
    assertThat(configuredTargetsDeps).containsExactly("//test:tool", "//test:t1");

    ImmutableList<String> aspectsDeps =
        stream(Iterables.filter(aspectNode.getDirectDeps(), AspectKey.class))
            .map(k -> k.getLabel().toString())
            .collect(toImmutableList());
    // aspect depends on the result of its application on the target deps if it propagates to them
    assertThat(aspectsDeps).containsExactly("//test:t2");
  }

  private ImmutableList<AspectKey> getAspectKeys(String targetLabel, String aspectLabel) {
    return skyframeExecutor.getEvaluator().getDoneValues().entrySet().stream()
        .filter(
            entry ->
                entry.getKey() instanceof AspectKey
                    && ((AspectKey) entry.getKey()).getAspectClass().getName().equals(aspectLabel)
                    && ((AspectKey) entry.getKey()).getLabel().toString().equals(targetLabel))
        .map(e -> (AspectKey) e.getKey())
        .collect(toImmutableList());
  }

  private ConfiguredAspect getConfiguredAspect(
      Map<AspectKey, ConfiguredAspect> aspectsMap, String aspectName) {
    for (Map.Entry<AspectKey, ConfiguredAspect> entry : aspectsMap.entrySet()) {
      AspectClass aspectClass = entry.getKey().getAspectClass();
      if (aspectClass instanceof StarlarkAspectClass) {
        String aspectExportedName = ((StarlarkAspectClass) aspectClass).getExportedName();
        if (aspectExportedName.equals(aspectName)) {
          return entry.getValue();
        }
      }
    }
    return null;
  }

  private ConfiguredAspect getConfiguredAspect(
      Map<AspectKey, ConfiguredAspect> aspectsMap, String aspectName, String targetName) {
    for (Map.Entry<AspectKey, ConfiguredAspect> entry : aspectsMap.entrySet()) {
      AspectClass aspectClass = entry.getKey().getAspectClass();
      if (aspectClass instanceof StarlarkAspectClass) {
        String aspectExportedName = ((StarlarkAspectClass) aspectClass).getExportedName();
        String target = entry.getKey().getLabel().getName();
        if (aspectExportedName.equals(aspectName) && target.equals(targetName)) {
          return entry.getValue();
        }
      }
    }
    return null;
  }

  private void exposeNativeAspectToStarlark() throws Exception {
    ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder();
    TestRuleClassProvider.addStandardRules(builder);
    builder.addBzlToplevel(
        "starlark_native_aspect", TestAspects.STARLARK_NATIVE_ASPECT_WITH_PROVIDER);
    builder.addBzlToplevel(
        "parametrized_native_aspect",
        TestAspects.PARAMETRIZED_STARLARK_NATIVE_ASPECT_WITH_PROVIDER);
    builder.addNativeAspectClass(TestAspects.STARLARK_NATIVE_ASPECT_WITH_PROVIDER);
    builder.addNativeAspectClass(TestAspects.PARAMETRIZED_STARLARK_NATIVE_ASPECT_WITH_PROVIDER);
    builder.addRuleDefinition(TestAspects.BASE_RULE);
    builder.addRuleDefinition(TestAspects.HONEST_RULE);
    useRuleClassProvider(builder.build());
  }

  /** StarlarkAspectTest with "keep going" flag */
  @RunWith(JUnit4.class)
  public static final class WithKeepGoing extends StarlarkDefinedAspectsTest {
    @Override
    protected FlagBuilder defaultFlags() {
      return super.defaultFlags().with(Flag.KEEP_GOING);
    }

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