Add an actions provider for testing Skylark rules.

The new provider gathers actions generated by any Skylark-based RuleConfiguredTarget, so long as the rule definition has _skylark_test=True set. For the moment this flag is under the user's control, but the intention is that it will be set by a test runner.

--
MOS_MIGRATED_REVID=134687396
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ActionsProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/ActionsProvider.java
new file mode 100644
index 0000000..ea3e5d3
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ActionsProvider.java
@@ -0,0 +1,47 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.analysis;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.packages.SkylarkClassObject;
+import com.google.devtools.build.lib.packages.SkylarkClassObjectConstructor;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This provides a view over the actions that were created during the analysis of a rule
+ * (not including actions for its transitive dependencies).
+ */
+public final class ActionsProvider{
+
+  public static final SkylarkClassObjectConstructor ACTIONS_PROVIDER =
+      SkylarkClassObjectConstructor.createNative("Actions");
+
+  public static SkylarkClassObject create(Iterable<ActionAnalysisMetadata> actions) {
+    Map<Artifact, ActionAnalysisMetadata> map = new HashMap<>();
+    for (ActionAnalysisMetadata action : actions) {
+      for (Artifact artifact : action.getOutputs()) {
+        // In the case that two actions generated the same artifact, the first wins. They
+        // ought to be equal anyway.
+        if (!map.containsKey(artifact)) {
+          map.put(artifact, action);
+        }
+      }
+    }
+    ImmutableMap<String, Object> fields = ImmutableMap.of("by_file", map);
+    return new SkylarkClassObject(ACTIONS_PROVIDER, fields);
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/SkylarkProviderValidationUtil.java b/src/main/java/com/google/devtools/build/lib/analysis/SkylarkProviderValidationUtil.java
index 869a2b0..ee88f97 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/SkylarkProviderValidationUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/SkylarkProviderValidationUtil.java
@@ -17,6 +17,7 @@
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.events.Location;
@@ -105,6 +106,7 @@
         || type.equals(Integer.class)
         || type.equals(Boolean.class)
         || Artifact.class.isAssignableFrom(type)
+        || ActionAnalysisMetadata.class.isAssignableFrom(type)
         || type.equals(Label.class)
         || type.equals(com.google.devtools.build.lib.syntax.Runtime.NoneType.class);
   }
diff --git a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
index 53aabfa..9fcb2c4 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
@@ -476,6 +476,7 @@
     private String name;
     private final RuleClassType type;
     private final boolean skylark;
+    private boolean skylarkTestable = false;
     private boolean documented;
     private boolean publicByDefault = false;
     private boolean binaryOutput = true;
@@ -589,6 +590,7 @@
           name,
           skylark,
           skylarkExecutable,
+          skylarkTestable,
           documented,
           publicByDefault,
           binaryOutput,
@@ -662,6 +664,12 @@
       return this;
     }
 
+    public Builder setSkylarkTestable() {
+      Preconditions.checkState(skylark, "Cannot set skylarkTestable on a non-Skylark rule");
+      skylarkTestable = true;
+      return this;
+    }
+
     /**
      * Sets the policy for the case where the configuration is missing required fragments (see
      * {@link #requiresConfigurationFragments}).
@@ -953,6 +961,7 @@
 
   private final boolean isSkylark;
   private final boolean skylarkExecutable;
+  private final boolean skylarkTestable;
   private final boolean documented;
   private final boolean publicByDefault;
   private final boolean binaryOutput;
@@ -1063,6 +1072,7 @@
       String name,
       boolean isSkylark,
       boolean skylarkExecutable,
+      boolean skylarkTestable,
       boolean documented,
       boolean publicByDefault,
       boolean binaryOutput,
@@ -1086,6 +1096,7 @@
     this.isSkylark = isSkylark;
     this.targetKind = name + " rule";
     this.skylarkExecutable = skylarkExecutable;
+    this.skylarkTestable = skylarkTestable;
     this.documented = documented;
     this.publicByDefault = publicByDefault;
     this.binaryOutput = binaryOutput;
@@ -1886,7 +1897,7 @@
     return ruleDefinitionEnvironmentHashCode;
   }
 
-  /** Returns true if this RuleClass is a skylark-defined RuleClass. */
+  /** Returns true if this RuleClass is a Skylark-defined RuleClass. */
   public boolean isSkylark() {
     return isSkylark;
   }
@@ -1900,6 +1911,14 @@
   }
 
   /**
+   * Returns true if this RuleClass is Skylark-defined and is subject to analysis-time
+   * tests.
+   */
+  public boolean isSkylarkTestable() {
+    return skylarkTestable;
+  }
+
+  /**
    * Returns true if this rule class outputs a default executable for every rule.
    */
   public boolean outputsDefaultExecutable() {
diff --git a/src/main/java/com/google/devtools/build/lib/packages/SkylarkClassObjectConstructor.java b/src/main/java/com/google/devtools/build/lib/packages/SkylarkClassObjectConstructor.java
index 7666e2c..0854822 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/SkylarkClassObjectConstructor.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkClassObjectConstructor.java
@@ -60,14 +60,14 @@
   }
 
   /**
-   * Create a native Declared Provider({@link SkylarkClassObject} constructor)
+   * Create a native Declared Provider ({@link SkylarkClassObject} constructor)
    */
   public static SkylarkClassObjectConstructor createNative(String name) {
     return new SkylarkClassObjectConstructor(name);
   }
 
   /**
-   * Create a Skylark-defined Declared Provider({@link SkylarkClassObject} constructor)
+   * Create a Skylark-defined Declared Provider ({@link SkylarkClassObject} constructor)
    *
    * Needs to be exported later.
    */
diff --git a/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java b/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java
index 190a46b..776dfa6 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java
@@ -43,7 +43,6 @@
     + "The following functions are also available:")
 public class SkylarkNativeModule {
 
-  // TODO(bazel-team): shouldn't we return a SkylarkList instead?
   @SkylarkSignature(
     name = "glob",
     objectType = SkylarkNativeModule.class,
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
index 4144ea6..95db141 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
@@ -35,6 +35,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.ActionsProvider;
 import com.google.devtools.build.lib.analysis.BaseRuleClasses;
 import com.google.devtools.build.lib.analysis.OutputGroupProvider;
 import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
@@ -200,6 +201,18 @@
   private static final SkylarkClassObjectConstructor struct =
       SkylarkClassObjectConstructor.STRUCT;
 
+  // TODO(bazel-team): Move to a "testing" namespace module. Normally we'd pass an objectType
+  // to @SkylarkSignature to do this, but that doesn't work here because we're exposing an already-
+  // configured BaseFunction, rather than defining a new BuiltinFunction. This should wait for
+  // better support from the Skylark/Java interface, or perhaps support for first-class modules.
+  @SkylarkSignature(name = "Actions", returnType = SkylarkClassObjectConstructor.class, doc =
+      "Provides access to the actions generated by a rule. This is designed for testing rules, "
+          + "and should not be accessed outside of test logic. This provider is only available for "
+          + "targets generated by rules marked with <pre class=\"language-python\">"
+          + "_skylark_testable=True</pre>."
+  )
+  private static final SkylarkClassObjectConstructor actions = ActionsProvider.ACTIONS_PROVIDER;
+
   @SkylarkSignature(name = "provider", returnType = SkylarkClassObjectConstructor.class, doc =
       "Creates a declared provider 'constructor'. The return value of this"
           + "function can be used to create \"struct-like\" values. Example:<br>"
@@ -267,14 +280,21 @@
         @Param(name = "host_fragments", type = SkylarkList.class, generic1 = String.class,
             defaultValue = "[]",
             doc = "List of names of configuration fragments that the rule requires "
-            + "in host configuration.")},
+            + "in host configuration."),
+        @Param(name = "_skylark_testable", type = Boolean.class, defaultValue = "False",
+            doc = "<i>(Experimental)</i> "
+                + "If true, this rule will expose its actions for inspection by rules that depend "
+                + "on it via an <a href=\"ActionsSkylarkApiProvider.html\">actions</a> provider."
+                + "This should only be used for testing the analysis-time behavior of Skylark "
+                + "rules. This flag may be removed in the future.")},
       useAst = true, useEnvironment = true)
   private static final BuiltinFunction rule = new BuiltinFunction("rule") {
     @SuppressWarnings({"rawtypes", "unchecked"}) // castMap produces
     // an Attribute.Builder instead of a Attribute.Builder<?> but it's OK.
     public BaseFunction invoke(BaseFunction implementation, Boolean test, Object attrs,
         Object implicitOutputs, Boolean executable, Boolean outputToGenfiles, SkylarkList fragments,
-        SkylarkList hostFragments, FuncallExpression ast, Environment funcallEnv)
+        SkylarkList hostFragments, Boolean skylarkTestable, FuncallExpression ast,
+        Environment funcallEnv)
         throws EvalException, ConversionException {
       funcallEnv.checkLoadingOrWorkspacePhase("rule", ast.getLocation());
       RuleClassType type = test ? RuleClassType.TEST : RuleClassType.NORMAL;
@@ -285,6 +305,11 @@
       RuleClass.Builder builder = new RuleClass.Builder("", type, true, parent);
       ImmutableList<Pair<String, SkylarkAttr.Descriptor>> attributes =
           attrObjectToAttributesList(attrs, ast);
+
+      if (skylarkTestable) {
+        builder.setSkylarkTestable();
+      }
+
       if (executable || test) {
         addAttribute(
             ast.getLocation(),
@@ -872,7 +897,7 @@
           )
       }
   )
-  public static final BuiltinFunction output_group = new BuiltinFunction("output_group") {
+  private static final BuiltinFunction output_group = new BuiltinFunction("output_group") {
       public SkylarkNestedSet invoke(TransitiveInfoCollection self, String group) {
         OutputGroupProvider provider = self.getProvider(OutputGroupProvider.class);
         NestedSet<Artifact> result = provider != null
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
index 0645548..50133da 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
@@ -16,6 +16,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.ActionsProvider;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
 import com.google.devtools.build.lib.analysis.RuleContext;
@@ -156,7 +157,8 @@
       filesToBuild.add(executable);
     }
     builder.setFilesToBuild(filesToBuild.build());
-    return addStructFields(ruleContext, builder, target, executable, registeredProviderTypes);
+    return addStructFieldsAndBuild(
+        ruleContext, builder, target, executable, registeredProviderTypes);
   }
 
   private static Artifact getExecutable(RuleContext ruleContext, Object target)
@@ -220,7 +222,7 @@
     }
   }
 
-  private static ConfiguredTarget addStructFields(
+  private static ConfiguredTarget addStructFieldsAndBuild(
       RuleContext ruleContext,
       RuleConfiguredTargetBuilder builder,
       Object target,
@@ -343,6 +345,13 @@
           ? null : RunfilesSupport.withExecutable(ruleContext, computedDefaultRunfiles, executable);
       builder.setRunfilesSupport(runfilesSupport, executable);
     }
+
+    if (ruleContext.getRule().getRuleClassObject().isSkylarkTestable()) {
+      SkylarkClassObject actions = ActionsProvider.create(
+          ruleContext.getAnalysisEnvironment().getRegisteredActions());
+      builder.addSkylarkDeclaredProvider(actions, loc);
+    }
+
     try {
       return builder.build();
     } catch (IllegalArgumentException e) {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java
index d3d8cfc..dfe738c 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java
@@ -307,7 +307,7 @@
       }
       filesBuilder.put(
           skyname, ruleContext.getPrerequisiteArtifacts(a.getName(), Mode.DONT_CHECK).list());
-      List<?> allPrereq = ruleContext.getPrerequisites(a.getName(), Mode.DONT_CHECK);
+
       if (type == BuildType.LABEL && !a.hasSplitConfigurationTransition()) {
         Object prereq = ruleContext.getPrerequisite(a.getName(), Mode.DONT_CHECK);
         if (prereq == null) {
@@ -316,6 +316,7 @@
         attrBuilder.put(skyname, prereq);
       } else {
         // Type.LABEL_LIST
+        List<?> allPrereq = ruleContext.getPrerequisites(a.getName(), Mode.DONT_CHECK);
         attrBuilder.put(skyname, SkylarkList.createImmutable(allPrereq));
       }
     }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
index 0245ea6..2782bc89 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
@@ -114,7 +114,7 @@
                         callable.parameters().length == 0 || !callable.structField(),
                         "Method "
                             + method
-                            + " was annotated with both structField amd parameters.");
+                            + " was annotated with both structField and parameters.");
                     if (callable.parameters().length > 0 || callable.mandatoryPositionals() >= 0) {
                       int nbArgs =
                           callable.parameters().length