Write a test script based on analysis tests' AnalysisTestResultInfo
The generated script will result in test pass/failure based on the info object returned by the implementation function.
Progress toward #6237
RELNOTES: None.
PiperOrigin-RevId: 218424616
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
index 1679f60..222125c 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
@@ -30,6 +30,8 @@
import com.google.devtools.build.lib.analysis.constraints.SupportedEnvironments;
import com.google.devtools.build.lib.analysis.constraints.SupportedEnvironmentsProvider;
import com.google.devtools.build.lib.analysis.constraints.SupportedEnvironmentsProvider.RemovedEnvironmentCulprit;
+import com.google.devtools.build.lib.analysis.test.AnalysisTestActionBuilder;
+import com.google.devtools.build.lib.analysis.test.AnalysisTestResultInfo;
import com.google.devtools.build.lib.analysis.test.ExecutionInfo;
import com.google.devtools.build.lib.analysis.test.InstrumentedFilesProvider;
import com.google.devtools.build.lib.analysis.test.TestActionBuilder;
@@ -147,13 +149,24 @@
}
TransitiveInfoProviderMap providers = providersBuilder.build();
- AnalysisEnvironment analysisEnvironment = ruleContext.getAnalysisEnvironment();
- GeneratingActions generatingActions =
- Actions.filterSharedActionsAndThrowActionConflict(
- analysisEnvironment.getActionKeyContext(), analysisEnvironment.getRegisteredActions());
+
if (ruleContext.getRule().isAnalysisTest()) {
- Preconditions.checkState(generatingActions.getActions().isEmpty());
+ // If the target is an analysis test that returned AnalysisTestResultInfo, register a
+ // test pass/fail action on behalf of the target.
+ AnalysisTestResultInfo testResultInfo =
+ providers.get(AnalysisTestResultInfo.SKYLARK_CONSTRUCTOR);
+
+ if (testResultInfo == null) {
+ ruleContext.ruleError(
+ "rules with analysis_test=true must return an instance of AnalysisTestResultInfo");
+ return null;
+ }
+
+ AnalysisTestActionBuilder.writeAnalysisTestAction(ruleContext, testResultInfo);
}
+ AnalysisEnvironment analysisEnvironment = ruleContext.getAnalysisEnvironment();
+ GeneratingActions generatingActions = Actions.filterSharedActionsAndThrowActionConflict(
+ analysisEnvironment.getActionKeyContext(), analysisEnvironment.getRegisteredActions());
return new RuleConfiguredTarget(
ruleContext,
providers,
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
index 66d071e..d59175c 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
@@ -93,6 +93,7 @@
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.syntax.Type.LabelClass;
import com.google.devtools.build.lib.util.FileTypeSet;
+import com.google.devtools.build.lib.util.OS;
import com.google.devtools.build.lib.util.OrderedSetMultimap;
import com.google.devtools.build.lib.util.StringUtil;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
@@ -549,7 +550,31 @@
* which this target (which must be an OutputFile or a Rule) is associated.
*/
public Artifact createOutputArtifact() {
- return internalCreateOutputArtifact(getTarget(), OutputFile.Kind.FILE);
+ Target target = getTarget();
+ PathFragment rootRelativePath = getPackageDirectory()
+ .getRelative(PathFragment.create(target.getName()));
+
+ return internalCreateOutputArtifact(rootRelativePath, target, OutputFile.Kind.FILE);
+ }
+
+ /**
+ * Returns an artifact beneath the root of either the "bin" or "genfiles"
+ * tree, whose path is based on the name of this target and the current
+ * configuration, with a script suffix appropriate for the current host platform. ({@code .cmd}
+ * for Windows, otherwise {@code .sh}). The choice of which tree to use is based on the rule with
+ * which this target (which must be an OutputFile or a Rule) is associated.
+ */
+ public Artifact createOutputArtifactScript() {
+ Target target = getTarget();
+ // TODO(laszlocsomor): Use the execution platform, not the host platform.
+ boolean isExecutedOnWindows = OS.getCurrent() == OS.WINDOWS;
+
+ String fileExtension = isExecutedOnWindows ? ".cmd" : ".sh";
+
+ PathFragment rootRelativePath = getPackageDirectory()
+ .getRelative(PathFragment.create(target.getName() + fileExtension));
+
+ return internalCreateOutputArtifact(rootRelativePath, target, OutputFile.Kind.FILE);
}
/**
@@ -558,7 +583,9 @@
* @see #createOutputArtifact()
*/
public Artifact createOutputArtifact(OutputFile out) {
- return internalCreateOutputArtifact(out, out.getKind());
+ PathFragment packageRelativePath = getPackageDirectory()
+ .getRelative(PathFragment.create(out.getName()));
+ return internalCreateOutputArtifact(packageRelativePath, out, out.getKind());
}
/**
@@ -567,19 +594,19 @@
* {@link #createOutputArtifact(OutputFile)} can have a more specific
* signature.
*/
- private Artifact internalCreateOutputArtifact(Target target, OutputFile.Kind outputFileKind) {
+ private Artifact internalCreateOutputArtifact(PathFragment rootRelativePath,
+ Target target, OutputFile.Kind outputFileKind) {
Preconditions.checkState(
target.getLabel().getPackageIdentifier().equals(getLabel().getPackageIdentifier()),
"Creating output artifact for target '%s' in different package than the rule '%s' "
+ "being analyzed", target.getLabel(), getLabel());
ArtifactRoot root = getBinOrGenfilesDirectory();
- PathFragment packageRelativePath = getPackageDirectory()
- .getRelative(PathFragment.create(target.getName()));
+
switch (outputFileKind) {
case FILE:
- return getDerivedArtifact(packageRelativePath, root);
+ return getDerivedArtifact(rootRelativePath, root);
case FILESET:
- return getAnalysisEnvironment().getFilesetArtifact(packageRelativePath, root);
+ return getAnalysisEnvironment().getFilesetArtifact(rootRelativePath, root);
default:
throw new IllegalStateException();
}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java
index fa17854..c24d151 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java
@@ -285,6 +285,8 @@
+ "general use. It is subject to change at any time. It may be enabled by specifying "
+ "--experimental_analysis_testing_improvements");
}
+ // analysis_test=true implies test=true.
+ test |= Boolean.TRUE.equals(analysisTest);
RuleClassType type = test ? RuleClassType.TEST : RuleClassType.NORMAL;
RuleClass parent =
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java
index e110c46..954f655 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java
@@ -13,6 +13,7 @@
// limitations under the License.
package com.google.devtools.build.lib.analysis.skylark;
+import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -491,6 +492,16 @@
}
}
+ if (context.getRuleContext().getRule().isAnalysisTest()) {
+ // The Starlark Build API should already throw exception if the rule implementation attempts
+ // to register any actions. This is just a sanity check of this invariant.
+ Preconditions.checkState(
+ context.getRuleContext().getAnalysisEnvironment().getRegisteredActions().isEmpty(),
+ "%s", context.getRuleContext().getLabel());
+
+ executable = context.getRuleContext().createOutputArtifactScript();
+ }
+
if (executable == null && context.isExecutable()) {
if (context.isDefaultExecutableCreated()) {
// This doesn't actually create a new Artifact just returns the one
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/AnalysisTestActionBuilder.java b/src/main/java/com/google/devtools/build/lib/analysis/test/AnalysisTestActionBuilder.java
new file mode 100644
index 0000000..18a9eb6
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/AnalysisTestActionBuilder.java
@@ -0,0 +1,68 @@
+// Copyright 2018 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.test;
+
+import com.google.common.base.Splitter;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
+import com.google.devtools.build.lib.util.OS;
+
+/**
+ * Helper for writing test actions for analysis test rules. Analysis test rules are
+ * restricted to disallow the rule implementation functions from registering actions themselves;
+ * such rules register test success/failure via {@link AnalysisTestResultInfo}. This helper
+ * registers the appropriate test script simulating success or failure of the test.
+ */
+public class AnalysisTestActionBuilder {
+
+ /**
+ * Register and return an action to write a test script to the default executable location
+ * reflecting the given info object.
+ */
+ public static FileWriteAction writeAnalysisTestAction(
+ RuleContext ruleContext,
+ AnalysisTestResultInfo infoObject) {
+ FileWriteAction action;
+ // TODO(laszlocsomor): Use the execution platform, not the host platform.
+ boolean isExecutedOnWindows = OS.getCurrent() == OS.WINDOWS;
+
+ if (isExecutedOnWindows) {
+ StringBuilder sb = new StringBuilder().append("@echo off\n");
+ for (String line : Splitter.on("\n").split(infoObject.getMessage())) {
+ sb.append("echo ").append(line).append("\n");
+ }
+ String content = sb
+ .append("exit /b ").append(infoObject.getSuccess() ? "0" : "1")
+ .toString();
+
+ action = FileWriteAction.create(ruleContext,
+ ruleContext.createOutputArtifactScript(), content, /* executable */ true);
+
+ } else {
+ String content =
+ "cat << EOF\n"
+ + infoObject.getMessage()
+ + "\n"
+ + "EOF\n"
+ + "exit "
+ + (infoObject.getSuccess() ? "0" : "1");
+ action = FileWriteAction.create(ruleContext,
+ ruleContext.createOutputArtifactScript(), content, /* executable */ true);
+ }
+
+ ruleContext.registerAction(action);
+ return action;
+ }
+}