aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/analysis
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/analysis')
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/BuildView.java6
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/FileConfiguredTarget.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/OutputFileConfiguredTarget.java4
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java12
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/TargetCompleteEvent.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java6
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleContext.java4
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/test/BaselineCoverageAction.java124
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/test/BaselineCoverageResult.java40
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/test/CoverageReportActionFactory.java70
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/test/ExecutionInfoProvider.java48
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/test/InstrumentedFileManifestAction.java113
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/test/InstrumentedFilesCollector.java308
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/test/InstrumentedFilesProvider.java66
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/test/InstrumentedFilesProviderImpl.java86
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/test/TestActionBuilder.java327
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/test/TestActionContext.java40
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/test/TestConfiguration.java204
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/test/TestEnvironmentProvider.java47
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/test/TestProvider.java143
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/test/TestResult.java191
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java802
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/test/TestTargetExecutionSettings.java139
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/test/TestTargetProperties.java187
27 files changed, 2956 insertions, 21 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java b/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java
index 539b4e3b03..df7d1d5e13 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java
@@ -37,6 +37,7 @@ import com.google.devtools.build.lib.analysis.config.HostTransition;
import com.google.devtools.build.lib.analysis.config.PatchTransition;
import com.google.devtools.build.lib.analysis.config.RunUnder;
import com.google.devtools.build.lib.analysis.constraints.EnvironmentRule;
+import com.google.devtools.build.lib.analysis.test.TestConfiguration;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.Attribute.LateBoundLabel;
@@ -48,7 +49,6 @@ import com.google.devtools.build.lib.packages.RuleClass;
import com.google.devtools.build.lib.packages.RuleClass.Builder;
import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
import com.google.devtools.build.lib.packages.TestSize;
-import com.google.devtools.build.lib.rules.test.TestConfiguration;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.util.FileTypeSet;
import java.util.List;
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
index 5396334615..87cb74ad9c 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
@@ -46,6 +46,9 @@ import com.google.devtools.build.lib.analysis.config.DynamicTransitionMapper;
import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
import com.google.devtools.build.lib.analysis.config.PatchTransition;
import com.google.devtools.build.lib.analysis.constraints.TopLevelConstraintSemantics;
+import com.google.devtools.build.lib.analysis.test.CoverageReportActionFactory;
+import com.google.devtools.build.lib.analysis.test.CoverageReportActionFactory.CoverageReportActionsWrapper;
+import com.google.devtools.build.lib.analysis.test.InstrumentedFilesProvider;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
@@ -74,9 +77,6 @@ import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.packages.TargetUtils;
import com.google.devtools.build.lib.pkgcache.LoadingResult;
import com.google.devtools.build.lib.pkgcache.PackageManager.PackageManagerStatistics;
-import com.google.devtools.build.lib.rules.test.CoverageReportActionFactory;
-import com.google.devtools.build.lib.rules.test.CoverageReportActionFactory.CoverageReportActionsWrapper;
-import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider;
import com.google.devtools.build.lib.skyframe.AspectValue;
import com.google.devtools.build.lib.skyframe.AspectValue.AspectKey;
import com.google.devtools.build.lib.skyframe.AspectValue.AspectValueKey;
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/FileConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/FileConfiguredTarget.java
index 2768ceca1f..dc65db2c64 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/FileConfiguredTarget.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/FileConfiguredTarget.java
@@ -16,13 +16,13 @@ package com.google.devtools.build.lib.analysis;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.fileset.FilesetProvider;
+import com.google.devtools.build.lib.analysis.test.InstrumentedFilesProvider;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.packages.FileTarget;
import com.google.devtools.build.lib.packages.Info;
import com.google.devtools.build.lib.packages.Provider;
-import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider;
import com.google.devtools.build.lib.util.FileType;
/**
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/OutputFileConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/OutputFileConfiguredTarget.java
index b9959e1dcb..ba449aec84 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/OutputFileConfiguredTarget.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/OutputFileConfiguredTarget.java
@@ -15,10 +15,10 @@
package com.google.devtools.build.lib.analysis;
import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.test.InstrumentedFilesProvider;
+import com.google.devtools.build.lib.analysis.test.InstrumentedFilesProviderImpl;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.packages.OutputFile;
-import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider;
-import com.google.devtools.build.lib.rules.test.InstrumentedFilesProviderImpl;
import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.util.Preconditions;
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 a11536a035..67b4a6ca65 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
@@ -24,6 +24,12 @@ import com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics;
import com.google.devtools.build.lib.analysis.constraints.EnvironmentCollection;
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.test.ExecutionInfoProvider;
+import com.google.devtools.build.lib.analysis.test.InstrumentedFilesProvider;
+import com.google.devtools.build.lib.analysis.test.TestActionBuilder;
+import com.google.devtools.build.lib.analysis.test.TestEnvironmentProvider;
+import com.google.devtools.build.lib.analysis.test.TestProvider;
+import com.google.devtools.build.lib.analysis.test.TestProvider.TestParams;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
@@ -34,12 +40,6 @@ import com.google.devtools.build.lib.packages.NativeProvider;
import com.google.devtools.build.lib.packages.Provider;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.packages.TargetUtils;
-import com.google.devtools.build.lib.rules.test.ExecutionInfoProvider;
-import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider;
-import com.google.devtools.build.lib.rules.test.TestActionBuilder;
-import com.google.devtools.build.lib.rules.test.TestEnvironmentProvider;
-import com.google.devtools.build.lib.rules.test.TestProvider;
-import com.google.devtools.build.lib.rules.test.TestProvider.TestParams;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.util.Preconditions;
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TargetCompleteEvent.java b/src/main/java/com/google/devtools/build/lib/analysis/TargetCompleteEvent.java
index 7e50b36ba0..48ac8fb8c1 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/TargetCompleteEvent.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TargetCompleteEvent.java
@@ -21,6 +21,7 @@ import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.EventReportingArtifacts;
import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper.ArtifactsInOutputGroup;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.test.TestProvider;
import com.google.devtools.build.lib.buildeventstream.ArtifactGroupNamer;
import com.google.devtools.build.lib.buildeventstream.BuildEvent;
import com.google.devtools.build.lib.buildeventstream.BuildEventConverters;
@@ -39,7 +40,6 @@ import com.google.devtools.build.lib.collect.nestedset.NestedSetView;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.packages.AttributeMap;
import com.google.devtools.build.lib.packages.TestSize;
-import com.google.devtools.build.lib.rules.test.TestProvider;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.skyframe.SkyValue;
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java
index a9b77b4074..9e7d534597 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java
@@ -19,10 +19,10 @@ import static com.google.devtools.build.lib.util.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.test.TestProvider;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
-import com.google.devtools.build.lib.rules.test.TestProvider;
import com.google.devtools.build.lib.skyframe.AspectValue;
import javax.annotation.Nullable;
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 e716d00b11..f4f5084c16 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
@@ -42,6 +42,7 @@ import com.google.devtools.build.lib.analysis.OutputGroupProvider;
import com.google.devtools.build.lib.analysis.PlatformSemantics;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.skylark.SkylarkAttr.Descriptor;
+import com.google.devtools.build.lib.analysis.test.TestConfiguration;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.cmdline.LabelValidator;
@@ -72,7 +73,6 @@ import com.google.devtools.build.lib.packages.SkylarkExportable;
import com.google.devtools.build.lib.packages.SkylarkProvider;
import com.google.devtools.build.lib.packages.TargetUtils;
import com.google.devtools.build.lib.packages.TestSize;
-import com.google.devtools.build.lib.rules.test.TestConfiguration;
import com.google.devtools.build.lib.skylarkinterface.Param;
import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature;
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 599f6f5d4b..74a69ca17e 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
@@ -26,6 +26,9 @@ import com.google.devtools.build.lib.analysis.Runfiles;
import com.google.devtools.build.lib.analysis.RunfilesProvider;
import com.google.devtools.build.lib.analysis.RunfilesSupport;
import com.google.devtools.build.lib.analysis.SkylarkProviderValidationUtil;
+import com.google.devtools.build.lib.analysis.test.InstrumentedFilesCollector;
+import com.google.devtools.build.lib.analysis.test.InstrumentedFilesCollector.InstrumentationSpec;
+import com.google.devtools.build.lib.analysis.test.InstrumentedFilesProvider;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.events.Location;
@@ -33,9 +36,6 @@ import com.google.devtools.build.lib.packages.Info;
import com.google.devtools.build.lib.packages.NativeProvider;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.TargetUtils;
-import com.google.devtools.build.lib.rules.test.InstrumentedFilesCollector;
-import com.google.devtools.build.lib.rules.test.InstrumentedFilesCollector.InstrumentationSpec;
-import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider;
import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
import com.google.devtools.build.lib.syntax.BaseFunction;
import com.google.devtools.build.lib.syntax.ClassObject;
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleContext.java
index 916acabb2f..5f383d6763 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleContext.java
@@ -37,6 +37,8 @@ import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.FragmentCollection;
+import com.google.devtools.build.lib.analysis.test.InstrumentedFilesCollector;
+import com.google.devtools.build.lib.analysis.test.InstrumentedFilesProvider;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.events.Location;
@@ -52,8 +54,6 @@ import com.google.devtools.build.lib.packages.OutputFile;
import com.google.devtools.build.lib.packages.Package;
import com.google.devtools.build.lib.packages.Provider;
import com.google.devtools.build.lib.packages.RawAttributeMapper;
-import com.google.devtools.build.lib.rules.test.InstrumentedFilesCollector;
-import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider;
import com.google.devtools.build.lib.shell.ShellUtils;
import com.google.devtools.build.lib.shell.ShellUtils.TokenizationException;
import com.google.devtools.build.lib.skylarkinterface.Param;
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/BaselineCoverageAction.java b/src/main/java/com/google/devtools/build/lib/analysis/test/BaselineCoverageAction.java
new file mode 100644
index 0000000000..2953ed68de
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/BaselineCoverageAction.java
@@ -0,0 +1,124 @@
+// Copyright 2014 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.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.eventbus.EventBus;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.NotifyOnActionCacheHit;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.actions.AbstractFileWriteAction;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Generates baseline (empty) coverage for the given non-test target.
+ */
+@VisibleForTesting
+@Immutable
+public final class BaselineCoverageAction extends AbstractFileWriteAction
+ implements NotifyOnActionCacheHit {
+ private final NestedSet<Artifact> instrumentedFiles;
+
+ private BaselineCoverageAction(
+ ActionOwner owner, NestedSet<Artifact> instrumentedFiles, Artifact output) {
+ super(owner, ImmutableList.<Artifact>of(), output, false);
+ this.instrumentedFiles = instrumentedFiles;
+ }
+
+ @Override
+ public String getMnemonic() {
+ return "BaselineCoverage";
+ }
+
+ @Override
+ public String computeKey() {
+ return new Fingerprint()
+ .addStrings(getInstrumentedFilePathStrings())
+ .hexDigestAndReset();
+ }
+
+ private Iterable<String> getInstrumentedFilePathStrings() {
+ List<String> result = new ArrayList<>();
+ for (Artifact instrumentedFile : instrumentedFiles) {
+ result.add(instrumentedFile.getExecPathString());
+ }
+ return result;
+ }
+
+ @Override
+ public DeterministicWriter newDeterministicWriter(ActionExecutionContext ctx) {
+ return new DeterministicWriter() {
+ @Override
+ public void writeOutputFile(OutputStream out) throws IOException {
+ PrintWriter writer = new PrintWriter(out);
+ for (String execPath : getInstrumentedFilePathStrings()) {
+ writer.write("SF:" + execPath + "\n");
+ writer.write("end_of_record\n");
+ }
+ writer.flush();
+ }
+ };
+ }
+
+ @Override
+ protected void afterWrite(ActionExecutionContext actionExecutionContext) {
+ notifyAboutBaselineCoverage(actionExecutionContext.getEventBus());
+ }
+
+ @Override
+ public void actionCacheHit(ActionCachedContext context) {
+ notifyAboutBaselineCoverage(context.getEventBus());
+ }
+
+ /**
+ * Notify interested parties about new baseline coverage data.
+ */
+ private void notifyAboutBaselineCoverage(EventBus eventBus) {
+ Artifact output = Iterables.getOnlyElement(getOutputs());
+ String ownerString = Label.print(getOwner().getLabel());
+ eventBus.post(new BaselineCoverageResult(output, ownerString));
+ }
+
+ /**
+ * Returns collection of baseline coverage artifacts associated with the given target.
+ * Will always return 0 or 1 elements.
+ */
+ static NestedSet<Artifact> create(
+ RuleContext ruleContext, NestedSet<Artifact> instrumentedFiles) {
+ // Baseline coverage artifacts will still go into "testlogs" directory.
+ Artifact coverageData = ruleContext.getPackageRelativeArtifact(
+ PathFragment.create(ruleContext.getTarget().getName()).getChild("baseline_coverage.dat"),
+ ruleContext.getConfiguration().getTestLogsDirectory(
+ ruleContext.getRule().getRepository()));
+ ruleContext.registerAction(new BaselineCoverageAction(
+ ruleContext.getActionOwner(), instrumentedFiles, coverageData));
+ return NestedSetBuilder.create(Order.STABLE_ORDER, coverageData);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/BaselineCoverageResult.java b/src/main/java/com/google/devtools/build/lib/analysis/test/BaselineCoverageResult.java
new file mode 100644
index 0000000000..ec31264284
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/BaselineCoverageResult.java
@@ -0,0 +1,40 @@
+// Copyright 2014 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.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.util.Preconditions;
+
+/**
+ * This event is used to notify about a successfully built baseline coverage artifact.
+ */
+public class BaselineCoverageResult {
+
+ private final Artifact baselineCoverageData;
+ private final String ownerString;
+
+ public BaselineCoverageResult(Artifact baselineCoverageData, String ownerString) {
+ this.baselineCoverageData = Preconditions.checkNotNull(baselineCoverageData);
+ this.ownerString = Preconditions.checkNotNull(ownerString);
+ }
+
+ public Artifact getArtifact() {
+ return baselineCoverageData;
+ }
+
+ public String getOwnerString() {
+ return ownerString;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/CoverageReportActionFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/test/CoverageReportActionFactory.java
new file mode 100644
index 0000000000..e5678f389a
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/CoverageReportActionFactory.java
@@ -0,0 +1,70 @@
+// Copyright 2014 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.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ArtifactFactory;
+import com.google.devtools.build.lib.actions.ArtifactOwner;
+import com.google.devtools.build.lib.analysis.BlazeDirectories;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.events.EventHandler;
+import java.util.Collection;
+import javax.annotation.Nullable;
+
+/**
+ * A factory class to create coverage report actions.
+ */
+public interface CoverageReportActionFactory {
+ /**
+ * Wraps the necessary actions to get a coverage report as well as the final output artifacts.
+ * The lcovWriteAction creates a file containing a set of lcov files. This file is used as an
+ * input artifact for coverageReportAction. We are only interested about the output artifacts from
+ * coverageReportAction.
+ */
+ public static final class CoverageReportActionsWrapper {
+ private final ActionAnalysisMetadata lcovWriteAction;
+ private final ActionAnalysisMetadata coverageReportAction;
+
+ public CoverageReportActionsWrapper (
+ ActionAnalysisMetadata lcovWriteAction, ActionAnalysisMetadata coverageReportAction) {
+ this.lcovWriteAction = lcovWriteAction;
+ this.coverageReportAction = coverageReportAction;
+ }
+
+ public ImmutableList<ActionAnalysisMetadata> getActions() {
+ return ImmutableList.of(lcovWriteAction, coverageReportAction);
+ }
+
+ public ImmutableSet<Artifact> getCoverageOutputs() {
+ return coverageReportAction.getOutputs();
+ }
+ }
+
+ /**
+ * Returns a CoverageReportActionsWrapper. May return null if it's not necessary to create
+ * such Actions based on the input parameters and some other data available to the factory
+ * implementation, such as command line options.
+ */
+ @Nullable
+ CoverageReportActionsWrapper createCoverageReportActionsWrapper(
+ EventHandler eventHandler,
+ BlazeDirectories directories,
+ Collection<ConfiguredTarget> targetsToTest,
+ Iterable<Artifact> baselineCoverageArtifacts,
+ ArtifactFactory artifactFactory, ArtifactOwner artifactOwner);
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/ExecutionInfoProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/test/ExecutionInfoProvider.java
new file mode 100644
index 0000000000..616db25bb4
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/ExecutionInfoProvider.java
@@ -0,0 +1,48 @@
+// Copyright 2014 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.collect.ImmutableMap;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.packages.Info;
+import com.google.devtools.build.lib.packages.NativeProvider;
+import java.util.Map;
+
+/**
+ * This provider can be implemented by rules which need special environments to run in (especially
+ * tests).
+ */
+@Immutable
+public final class ExecutionInfoProvider extends Info {
+
+ /** Skylark constructor and identifier for ExecutionInfoProvider. */
+ public static final NativeProvider<ExecutionInfoProvider> SKYLARK_CONSTRUCTOR =
+ new NativeProvider<ExecutionInfoProvider>(ExecutionInfoProvider.class, "ExecutionInfo") {};
+
+ private final ImmutableMap<String, String> executionInfo;
+
+ public ExecutionInfoProvider(Map<String, String> requirements) {
+ super(SKYLARK_CONSTRUCTOR, ImmutableMap.<String, Object>of("requirements", requirements));
+ this.executionInfo = ImmutableMap.copyOf(requirements);
+ }
+
+ /**
+ * Returns a map to indicate special execution requirements, such as hardware
+ * platforms, etc. Rule tags, such as "requires-XXX", may also be added
+ * as keys to the map.
+ */
+ public ImmutableMap<String, String> getExecutionInfo() {
+ return executionInfo;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/InstrumentedFileManifestAction.java b/src/main/java/com/google/devtools/build/lib/analysis/test/InstrumentedFileManifestAction.java
new file mode 100644
index 0000000000..5e1c60f200
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/InstrumentedFileManifestAction.java
@@ -0,0 +1,113 @@
+// Copyright 2014 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 static java.nio.charset.StandardCharsets.ISO_8859_1;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.actions.AbstractFileWriteAction;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.build.lib.util.Preconditions;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Arrays;
+
+/**
+ * Writes a manifest of instrumented source and metadata files.
+ */
+@Immutable
+final class InstrumentedFileManifestAction extends AbstractFileWriteAction {
+ private static final Function<Artifact, String> TO_EXEC_PATH = new Function<Artifact, String>() {
+ @Override
+ public String apply(Artifact artifact) {
+ return artifact.getExecPathString();
+ }
+ };
+
+ private static final String GUID = "3833f0a3-7ea1-4d9f-b96f-66eff4c922b0";
+
+ private final NestedSet<Artifact> files;
+
+ @VisibleForTesting
+ InstrumentedFileManifestAction(ActionOwner owner, NestedSet<Artifact> files, Artifact output) {
+ super(owner, /*inputs=*/Artifact.NO_ARTIFACTS, output, /*makeExecutable=*/false);
+ this.files = files;
+ }
+
+ @Override
+ public DeterministicWriter newDeterministicWriter(ActionExecutionContext ctx) {
+ return new DeterministicWriter() {
+ @Override
+ public void writeOutputFile(OutputStream out) throws IOException {
+ // Sort the exec paths before writing them out.
+ String[] fileNames =
+ Iterables.toArray(Iterables.transform(files, TO_EXEC_PATH), String.class);
+ Arrays.sort(fileNames);
+ try (Writer writer = new OutputStreamWriter(out, ISO_8859_1)) {
+ for (String name : fileNames) {
+ writer.write(name);
+ writer.write('\n');
+ }
+ }
+ }
+ };
+ }
+
+ @Override
+ protected String computeKey() {
+ Fingerprint f = new Fingerprint();
+ f.addString(GUID);
+ // Not sorting here is probably cheaper, though it might lead to unnecessary re-execution.
+ f.addStrings(Iterables.transform(files, TO_EXEC_PATH));
+ return f.hexDigestAndReset();
+ }
+
+ /**
+ * Instantiates instrumented file manifest for the given target.
+ *
+ * @param ruleContext context of the executable configured target
+ * @param additionalSourceFiles additional instrumented source files, as
+ * collected by the {@link InstrumentedFilesCollector}
+ * @param metadataFiles *.gcno/*.em files collected by the {@link InstrumentedFilesCollector}
+ * @return instrumented file manifest artifact
+ */
+ public static Artifact getInstrumentedFileManifest(RuleContext ruleContext,
+ NestedSet<Artifact> additionalSourceFiles, NestedSet<Artifact> metadataFiles) {
+ // Instrumented manifest makes sense only for rules with binary output.
+ Preconditions.checkState(ruleContext.getRule().hasBinaryOutput());
+ Artifact instrumentedFileManifest = ruleContext.getBinArtifact(
+ ruleContext.getTarget().getName() + ".instrumented_files");
+
+ NestedSet<Artifact> inputs = NestedSetBuilder.<Artifact>stableOrder()
+ .addTransitive(additionalSourceFiles)
+ .addTransitive(metadataFiles)
+ .build();
+ ruleContext.registerAction(new InstrumentedFileManifestAction(
+ ruleContext.getActionOwner(), inputs, instrumentedFileManifest));
+
+ return instrumentedFileManifest;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/InstrumentedFilesCollector.java b/src/main/java/com/google/devtools/build/lib/analysis/test/InstrumentedFilesCollector.java
new file mode 100644
index 0000000000..30d7d8f7c4
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/InstrumentedFilesCollector.java
@@ -0,0 +1,308 @@
+// Copyright 2014 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.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.AnalysisEnvironment;
+import com.google.devtools.build.lib.analysis.FileProvider;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.packages.BuildType;
+import com.google.devtools.build.lib.util.FileType;
+import com.google.devtools.build.lib.util.FileTypeSet;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.util.Preconditions;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A helper class for collecting instrumented files and metadata for a target.
+ */
+public final class InstrumentedFilesCollector {
+
+ /**
+ * Forwards any instrumented files from the given target's dependencies (as defined in
+ * {@code dependencyAttributes}) for further export. No files from this target are considered
+ * instrumented.
+ *
+ * @return instrumented file provider of all dependencies in {@code dependencyAttributes}
+ */
+ public static InstrumentedFilesProvider forward(
+ RuleContext ruleContext, String... dependencyAttributes) {
+ return collect(
+ ruleContext,
+ new InstrumentationSpec(FileTypeSet.NO_FILE).withDependencyAttributes(dependencyAttributes),
+ null,
+ null);
+ }
+
+ public static InstrumentedFilesProvider collect(
+ RuleContext ruleContext,
+ InstrumentationSpec spec,
+ LocalMetadataCollector localMetadataCollector,
+ Iterable<Artifact> rootFiles) {
+ return collect(ruleContext, spec, localMetadataCollector, rootFiles,
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER),
+ NestedSetBuilder.<Pair<String, String>>emptySet(Order.STABLE_ORDER),
+ false);
+ }
+
+ /**
+ * Collects transitive instrumentation data from dependencies, collects local source files from
+ * dependencies, collects local metadata files by traversing the action graph of the current
+ * configured target, collect rule-specific instrumentation support file sand creates baseline
+ * coverage actions for the transitive closure of source files (if
+ * <code>withBaselineCoverage</code> is true).
+ */
+ public static InstrumentedFilesProvider collect(
+ RuleContext ruleContext,
+ InstrumentationSpec spec,
+ LocalMetadataCollector localMetadataCollector,
+ Iterable<Artifact> rootFiles,
+ NestedSet<Artifact> coverageSupportFiles,
+ NestedSet<Pair<String, String>> coverageEnvironment,
+ boolean withBaselineCoverage) {
+ Preconditions.checkNotNull(ruleContext);
+ Preconditions.checkNotNull(spec);
+
+ if (!ruleContext.getConfiguration().isCodeCoverageEnabled()) {
+ return InstrumentedFilesProviderImpl.EMPTY;
+ }
+
+ NestedSetBuilder<Artifact> instrumentedFilesBuilder = NestedSetBuilder.stableOrder();
+ NestedSetBuilder<Artifact> metadataFilesBuilder = NestedSetBuilder.stableOrder();
+ NestedSetBuilder<Artifact> baselineCoverageInstrumentedFilesBuilder =
+ NestedSetBuilder.stableOrder();
+ NestedSetBuilder<Artifact> coverageSupportFilesBuilder =
+ NestedSetBuilder.<Artifact>stableOrder()
+ .addTransitive(coverageSupportFiles);
+ NestedSetBuilder<Pair<String, String>> coverageEnvironmentBuilder =
+ NestedSetBuilder.<Pair<String, String>>compileOrder()
+ .addTransitive(coverageEnvironment);
+
+
+ // Transitive instrumentation data.
+ for (TransitiveInfoCollection dep :
+ getAllPrerequisites(ruleContext, spec.dependencyAttributes)) {
+ InstrumentedFilesProvider provider = dep.getProvider(InstrumentedFilesProvider.class);
+ if (provider != null) {
+ instrumentedFilesBuilder.addTransitive(provider.getInstrumentedFiles());
+ metadataFilesBuilder.addTransitive(provider.getInstrumentationMetadataFiles());
+ baselineCoverageInstrumentedFilesBuilder.addTransitive(
+ provider.getBaselineCoverageInstrumentedFiles());
+ coverageSupportFilesBuilder.addTransitive(provider.getCoverageSupportFiles());
+ coverageEnvironmentBuilder.addTransitive(provider.getCoverageEnvironment());
+ }
+ }
+
+ // Local sources.
+ NestedSet<Artifact> localSources = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
+ if (shouldIncludeLocalSources(ruleContext)) {
+ NestedSetBuilder<Artifact> localSourcesBuilder = NestedSetBuilder.stableOrder();
+ for (TransitiveInfoCollection dep :
+ getAllPrerequisites(ruleContext, spec.sourceAttributes)) {
+ if (!spec.splitLists && dep.getProvider(InstrumentedFilesProvider.class) != null) {
+ continue;
+ }
+ for (Artifact artifact : dep.getProvider(FileProvider.class).getFilesToBuild()) {
+ if (artifact.isSourceArtifact() &&
+ spec.instrumentedFileTypes.matches(artifact.getFilename())) {
+ localSourcesBuilder.add(artifact);
+ }
+ }
+ }
+ localSources = localSourcesBuilder.build();
+ }
+ instrumentedFilesBuilder.addTransitive(localSources);
+ if (withBaselineCoverage) {
+ // Also add the local sources to the baseline coverage instrumented sources, if the current
+ // rule supports baseline coverage.
+ // TODO(ulfjack): Generate a local baseline coverage action, and then merge at the leaves.
+ baselineCoverageInstrumentedFilesBuilder.addTransitive(localSources);
+ }
+
+ // Local metadata files.
+ if (localMetadataCollector != null) {
+ localMetadataCollector.collectMetadataArtifacts(rootFiles,
+ ruleContext.getAnalysisEnvironment(), metadataFilesBuilder);
+ }
+
+ // Baseline coverage actions.
+ NestedSet<Artifact> baselineCoverageFiles = baselineCoverageInstrumentedFilesBuilder.build();
+
+ // Create one baseline coverage action per target, but for the transitive closure of files.
+ NestedSet<Artifact> baselineCoverageArtifacts =
+ BaselineCoverageAction.create(ruleContext, baselineCoverageFiles);
+ return new InstrumentedFilesProviderImpl(
+ instrumentedFilesBuilder.build(),
+ metadataFilesBuilder.build(),
+ baselineCoverageFiles,
+ baselineCoverageArtifacts,
+ coverageSupportFilesBuilder.build(),
+ coverageEnvironmentBuilder.build());
+ }
+
+ /**
+ * Return whether the sources of the rule in {@code ruleContext} should be instrumented based on
+ * the --instrumentation_filter and --instrument_test_targets config settings.
+ */
+ public static boolean shouldIncludeLocalSources(RuleContext ruleContext) {
+ return shouldIncludeLocalSources(ruleContext.getConfiguration(), ruleContext.getLabel(),
+ ruleContext.isTestTarget());
+ }
+
+ /**
+ * Return whether the sources included by {@code target} (a {@link TransitiveInfoCollection}
+ * representing a rule) should be instrumented according the --instrumentation_filter and
+ * --instrument_test_targets settings in {@code config}.
+ */
+ public static boolean shouldIncludeLocalSources(BuildConfiguration config,
+ TransitiveInfoCollection target) {
+ return shouldIncludeLocalSources(config, target.getLabel(),
+ target.getProvider(TestProvider.class) != null);
+ }
+
+ private static boolean shouldIncludeLocalSources(BuildConfiguration config, Label label,
+ boolean isTest) {
+ return ((config.shouldInstrumentTestTargets() || !isTest)
+ && config.getInstrumentationFilter().isIncluded(label.toString()));
+ }
+
+ /**
+ * The set of file types and attributes to visit to collect instrumented files for a certain rule
+ * type. The class is intentionally immutable, so that a single instance is sufficient for all
+ * rules of the same type (and in some cases all rules of related types, such as all {@code foo_*}
+ * rules).
+ */
+ @Immutable
+ public static final class InstrumentationSpec {
+ private final FileTypeSet instrumentedFileTypes;
+
+ /** The list of attributes which should be checked for sources. */
+ private final Collection<String> sourceAttributes;
+
+ /** The list of attributes from which to collect transitive coverage information. */
+ private final Collection<String> dependencyAttributes;
+
+ /** Whether the source and dependency lists are separate. */
+ private final boolean splitLists;
+
+ public InstrumentationSpec(FileTypeSet instrumentedFileTypes,
+ String... instrumentedAttributes) {
+ this(instrumentedFileTypes, ImmutableList.copyOf(instrumentedAttributes));
+ }
+
+ public InstrumentationSpec(FileTypeSet instrumentedFileTypes,
+ Collection<String> instrumentedAttributes) {
+ this(instrumentedFileTypes, instrumentedAttributes, instrumentedAttributes, false);
+ }
+
+ private InstrumentationSpec(FileTypeSet instrumentedFileTypes,
+ Collection<String> instrumentedSourceAttributes,
+ Collection<String> instrumentedDependencyAttributes,
+ boolean splitLists) {
+ this.instrumentedFileTypes = instrumentedFileTypes;
+ this.sourceAttributes = ImmutableList.copyOf(instrumentedSourceAttributes);
+ this.dependencyAttributes =
+ ImmutableList.copyOf(instrumentedDependencyAttributes);
+ this.splitLists = splitLists;
+ }
+
+ /**
+ * Returns a new instrumentation spec with the given attribute names replacing the ones
+ * stored in this object.
+ */
+ public InstrumentationSpec withAttributes(String... instrumentedAttributes) {
+ return new InstrumentationSpec(instrumentedFileTypes, instrumentedAttributes);
+ }
+
+ /**
+ * Returns a new instrumentation spec with the given attribute names replacing the ones
+ * stored in this object.
+ */
+ public InstrumentationSpec withSourceAttributes(String... instrumentedAttributes) {
+ return new InstrumentationSpec(instrumentedFileTypes,
+ ImmutableList.copyOf(instrumentedAttributes), dependencyAttributes, true);
+ }
+
+ /**
+ * Returns a new instrumentation spec with the given attribute names replacing the ones
+ * stored in this object.
+ */
+ public InstrumentationSpec withDependencyAttributes(String... instrumentedAttributes) {
+ return new InstrumentationSpec(instrumentedFileTypes,
+ sourceAttributes, ImmutableList.copyOf(instrumentedAttributes), true);
+ }
+ }
+
+ /**
+ * The implementation for the local metadata collection. The intention is that implementations
+ * recurse over the locally (i.e., for that configured target) created actions and collect
+ * metadata files.
+ */
+ public abstract static class LocalMetadataCollector {
+ /**
+ * Recursively runs over the local actions and add metadata files to the metadataFilesBuilder.
+ */
+ public abstract void collectMetadataArtifacts(
+ Iterable<Artifact> artifacts, AnalysisEnvironment analysisEnvironment,
+ NestedSetBuilder<Artifact> metadataFilesBuilder);
+
+ /**
+ * Adds action output of a particular type to metadata files.
+ *
+ * <p>Only adds the first output that matches the given file type.
+ *
+ * @param metadataFilesBuilder builder to collect metadata files
+ * @param action the action whose outputs to scan
+ * @param fileType the filetype of outputs which should be collected
+ */
+ protected void addOutputs(NestedSetBuilder<Artifact> metadataFilesBuilder,
+ ActionAnalysisMetadata action, FileType fileType) {
+ for (Artifact output : action.getOutputs()) {
+ if (fileType.matches(output.getFilename())) {
+ metadataFilesBuilder.add(output);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * An explicit constant for a {@link LocalMetadataCollector} that doesn't collect anything.
+ */
+ public static final LocalMetadataCollector NO_METADATA_COLLECTOR = null;
+
+ private static Iterable<TransitiveInfoCollection> getAllPrerequisites(
+ RuleContext ruleContext, Collection<String> attributeNames) {
+ List<TransitiveInfoCollection> prerequisites = new ArrayList<>();
+ for (String attr : attributeNames) {
+ if (ruleContext.getRule().isAttrDefined(attr, BuildType.LABEL_LIST) ||
+ ruleContext.getRule().isAttrDefined(attr, BuildType.LABEL)) {
+ prerequisites.addAll(ruleContext.getPrerequisites(attr, Mode.DONT_CHECK));
+ }
+ }
+ return prerequisites;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/InstrumentedFilesProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/test/InstrumentedFilesProvider.java
new file mode 100644
index 0000000000..f4773d79f2
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/InstrumentedFilesProvider.java
@@ -0,0 +1,66 @@
+// Copyright 2014 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.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.util.Pair;
+
+/**
+ * A provider of instrumented file sources and instrumentation metadata.
+ */
+public interface InstrumentedFilesProvider extends TransitiveInfoProvider {
+
+ /**
+ * The transitive closure of instrumented source files.
+ */
+ NestedSet<Artifact> getInstrumentedFiles();
+
+ /**
+ * Returns a collection of instrumentation metadata files.
+ */
+ NestedSet<Artifact> getInstrumentationMetadataFiles();
+
+ /**
+ * The transitive closure of instrumented source files for which baseline coverage should be
+ * generated. In general, this is a subset of the instrumented source files: it only contains
+ * instrumented source files from rules that support baseline coverage.
+ */
+ NestedSet<Artifact> getBaselineCoverageInstrumentedFiles();
+
+ /**
+ * The output artifact of the baseline coverage action; this is only ever a single artifact, which
+ * contains baseline coverage for the entire transitive closure of source files.
+ */
+ // TODO(ulfjack): Change this to a single Artifact. Also change how it's generated. It's better to
+ // generate actions such that each action only covers the source files of a single rule, in
+ // particular because baseline coverage is language-specific (it requires a parser for the
+ // specific language), and we don't want to depend on all language parsers from any single rule.
+ NestedSet<Artifact> getBaselineCoverageArtifacts();
+
+ /**
+ * Extra files that are needed on the inputs of test actions for coverage collection to happen,
+ * for example, {@code gcov}.
+ *
+ * <p>They aren't mentioned in the instrumented files manifest.
+ */
+ NestedSet<Artifact> getCoverageSupportFiles();
+
+ /**
+ * Environment variables that need to be set for tests collecting code coverage.
+ */
+ NestedSet<Pair<String, String>> getCoverageEnvironment();
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/InstrumentedFilesProviderImpl.java b/src/main/java/com/google/devtools/build/lib/analysis/test/InstrumentedFilesProviderImpl.java
new file mode 100644
index 0000000000..d0ca5fdf69
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/InstrumentedFilesProviderImpl.java
@@ -0,0 +1,86 @@
+// Copyright 2014 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.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.util.Pair;
+
+/**
+ * An implementation class for the InstrumentedFilesProvider interface.
+ */
+public final class InstrumentedFilesProviderImpl implements InstrumentedFilesProvider {
+ public static final InstrumentedFilesProvider EMPTY =
+ new InstrumentedFilesProviderImpl(
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER),
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER),
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER),
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER),
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER),
+ NestedSetBuilder.<Pair<String, String>>emptySet(Order.COMPILE_ORDER));
+
+ private final NestedSet<Artifact> instrumentedFiles;
+ private final NestedSet<Artifact> instrumentationMetadataFiles;
+ private final NestedSet<Artifact> baselineCoverageFiles;
+ private final NestedSet<Artifact> baselineCoverageArtifacts;
+ private final NestedSet<Artifact> coverageSupportFiles;
+ private final NestedSet<Pair<String, String>> coverageEnvironment;
+
+ public InstrumentedFilesProviderImpl(
+ NestedSet<Artifact> instrumentedFiles,
+ NestedSet<Artifact> instrumentationMetadataFiles,
+ NestedSet<Artifact> baselineCoverageFiles,
+ NestedSet<Artifact> baselineCoverageArtifacts,
+ NestedSet<Artifact> coverageSupportFiles,
+ NestedSet<Pair<String, String>> coverageEnvironment) {
+ this.instrumentedFiles = instrumentedFiles;
+ this.instrumentationMetadataFiles = instrumentationMetadataFiles;
+ this.baselineCoverageFiles = baselineCoverageFiles;
+ this.baselineCoverageArtifacts = baselineCoverageArtifacts;
+ this.coverageSupportFiles = coverageSupportFiles;
+ this.coverageEnvironment = coverageEnvironment;
+ }
+
+ @Override
+ public NestedSet<Artifact> getInstrumentedFiles() {
+ return instrumentedFiles;
+ }
+
+ @Override
+ public NestedSet<Artifact> getInstrumentationMetadataFiles() {
+ return instrumentationMetadataFiles;
+ }
+
+ @Override
+ public NestedSet<Artifact> getBaselineCoverageInstrumentedFiles() {
+ return baselineCoverageFiles;
+ }
+
+ @Override
+ public NestedSet<Artifact> getBaselineCoverageArtifacts() {
+ return baselineCoverageArtifacts;
+ }
+
+ @Override
+ public NestedSet<Artifact> getCoverageSupportFiles() {
+ return coverageSupportFiles;
+ }
+
+ @Override
+ public NestedSet<Pair<String, String>> getCoverageEnvironment() {
+ return coverageEnvironment;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/TestActionBuilder.java b/src/main/java/com/google/devtools/build/lib/analysis/test/TestActionBuilder.java
new file mode 100644
index 0000000000..3194f3f529
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/TestActionBuilder.java
@@ -0,0 +1,327 @@
+// Copyright 2014 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 static com.google.devtools.build.lib.packages.BuildType.LABEL;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Root;
+import com.google.devtools.build.lib.analysis.AnalysisEnvironment;
+import com.google.devtools.build.lib.analysis.FileProvider;
+import com.google.devtools.build.lib.analysis.FilesToRunProvider;
+import com.google.devtools.build.lib.analysis.PrerequisiteArtifacts;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.RunfilesSupport;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.test.TestProvider.TestParams;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.packages.TargetUtils;
+import com.google.devtools.build.lib.packages.TestSize;
+import com.google.devtools.build.lib.packages.TestTimeout;
+import com.google.devtools.build.lib.syntax.Type;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.util.Preconditions;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.common.options.EnumConverter;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import javax.annotation.Nullable;
+
+/**
+ * Helper class to create test actions.
+ */
+public final class TestActionBuilder {
+
+ private final RuleContext ruleContext;
+ private RunfilesSupport runfilesSupport;
+ private Artifact executable;
+ private ExecutionInfoProvider executionRequirements;
+ private InstrumentedFilesProvider instrumentedFiles;
+ private int explicitShardCount;
+ private Map<String, String> extraEnv;
+
+ public TestActionBuilder(RuleContext ruleContext) {
+ this.ruleContext = ruleContext;
+ this.extraEnv = new TreeMap<>();
+ }
+
+ /**
+ * Creates the test actions and artifacts using the previously set parameters.
+ *
+ * @return ordered list of test status artifacts
+ */
+ public TestParams build() {
+ Preconditions.checkState(runfilesSupport != null);
+ boolean local = TargetUtils.isTestRuleAndRunsLocally(ruleContext.getRule());
+ TestShardingStrategy strategy =
+ ruleContext.getConfiguration().getFragment(TestConfiguration.class).testShardingStrategy();
+ int shards = strategy.getNumberOfShards(
+ local, explicitShardCount, isTestShardingCompliant(),
+ TestSize.getTestSize(ruleContext.getRule()));
+ Preconditions.checkState(shards >= 0);
+ return createTestAction(shards);
+ }
+
+ private boolean isTestShardingCompliant() {
+ // See if it has a data dependency on the special target
+ // //tools:test_sharding_compliant. Test runners add this dependency
+ // to show they speak the sharding protocol.
+ // There are certain cases where this heuristic may fail, giving
+ // a "false positive" (where we shard the test even though the
+ // it isn't supported). We may want to refine this logic, but
+ // heuristically sharding is currently experimental. Also, we do detect
+ // false-positive cases and return an error.
+ return runfilesSupport.getRunfilesSymlinkNames().contains(
+ PathFragment.create("tools/test_sharding_compliant"));
+ }
+
+ /**
+ * Set the runfiles and executable to be run as a test.
+ */
+ public TestActionBuilder setFilesToRunProvider(FilesToRunProvider provider) {
+ Preconditions.checkNotNull(provider.getRunfilesSupport());
+ Preconditions.checkNotNull(provider.getExecutable());
+ this.runfilesSupport = provider.getRunfilesSupport();
+ this.executable = provider.getExecutable();
+ return this;
+ }
+
+ public TestActionBuilder setInstrumentedFiles(
+ @Nullable InstrumentedFilesProvider instrumentedFiles) {
+ this.instrumentedFiles = instrumentedFiles;
+ return this;
+ }
+
+ public TestActionBuilder setExecutionRequirements(
+ @Nullable ExecutionInfoProvider executionRequirements) {
+ this.executionRequirements = executionRequirements;
+ return this;
+ }
+
+ public TestActionBuilder addExtraEnv(Map<String, String> extraEnv) {
+ this.extraEnv.putAll(extraEnv);
+ return this;
+ }
+
+ /**
+ * Set the explicit shard count. Note that this may be overridden by the sharding strategy.
+ */
+ public TestActionBuilder setShardCount(int explicitShardCount) {
+ this.explicitShardCount = explicitShardCount;
+ return this;
+ }
+
+ /**
+ * Converts to {@link TestActionBuilder.TestShardingStrategy}.
+ */
+ public static class ShardingStrategyConverter extends EnumConverter<TestShardingStrategy> {
+ public ShardingStrategyConverter() {
+ super(TestShardingStrategy.class, "test sharding strategy");
+ }
+ }
+
+ /**
+ * A strategy for running the same tests in many processes.
+ */
+ public static enum TestShardingStrategy {
+ EXPLICIT {
+ @Override public int getNumberOfShards(boolean isLocal, int shardCountFromAttr,
+ boolean testShardingCompliant, TestSize testSize) {
+ return Math.max(shardCountFromAttr, 0);
+ }
+ },
+
+ EXPERIMENTAL_HEURISTIC {
+ @Override public int getNumberOfShards(boolean isLocal, int shardCountFromAttr,
+ boolean testShardingCompliant, TestSize testSize) {
+ if (shardCountFromAttr >= 0) {
+ return shardCountFromAttr;
+ }
+ if (isLocal || !testShardingCompliant) {
+ return 0;
+ }
+ return testSize.getDefaultShards();
+ }
+ },
+
+ DISABLED {
+ @Override public int getNumberOfShards(boolean isLocal, int shardCountFromAttr,
+ boolean testShardingCompliant, TestSize testSize) {
+ return 0;
+ }
+ };
+
+ public abstract int getNumberOfShards(boolean isLocal, int shardCountFromAttr,
+ boolean testShardingCompliant, TestSize testSize);
+ }
+
+ /**
+ * Creates a test action and artifacts for the given rule. The test action will
+ * use the specified executable and runfiles.
+ *
+ * @return ordered list of test artifacts, one per action. These are used to drive
+ * execution in Skyframe, and by AggregatingTestListener and
+ * TestResultAnalyzer to keep track of completed and pending test runs.
+ */
+ private TestParams createTestAction(int shards) {
+ PathFragment targetName = PathFragment.create(ruleContext.getLabel().getName());
+ BuildConfiguration config = ruleContext.getConfiguration();
+ AnalysisEnvironment env = ruleContext.getAnalysisEnvironment();
+ Root root = config.getTestLogsDirectory(ruleContext.getRule().getRepository());
+
+ NestedSetBuilder<Artifact> inputsBuilder = NestedSetBuilder.stableOrder();
+ inputsBuilder.addTransitive(
+ NestedSetBuilder.create(Order.STABLE_ORDER, runfilesSupport.getRunfilesMiddleman()));
+ NestedSet<Artifact> testRuntime = PrerequisiteArtifacts.nestedSet(
+ ruleContext, "$test_runtime", Mode.HOST);
+ inputsBuilder.addTransitive(testRuntime);
+ TestTargetProperties testProperties = new TestTargetProperties(
+ ruleContext, executionRequirements);
+
+ // If the test rule does not provide InstrumentedFilesProvider, there's not much that we can do.
+ final boolean collectCodeCoverage = config.isCodeCoverageEnabled()
+ && instrumentedFiles != null;
+
+ TreeMap<String, String> extraTestEnv = new TreeMap<>();
+
+ TestTargetExecutionSettings executionSettings;
+ if (collectCodeCoverage) {
+ inputsBuilder.addTransitive(instrumentedFiles.getCoverageSupportFiles());
+ // Add instrumented file manifest artifact to the list of inputs. This file will contain
+ // exec paths of all source files that should be included into the code coverage output.
+ NestedSet<Artifact> metadataFiles = instrumentedFiles.getInstrumentationMetadataFiles();
+ inputsBuilder.addTransitive(metadataFiles);
+ inputsBuilder.addTransitive(PrerequisiteArtifacts.nestedSet(
+ ruleContext, "$coverage_support", Mode.DONT_CHECK));
+ // We don't add this attribute to non-supported test target
+ if (ruleContext.isAttrDefined("$lcov_merger", LABEL)) {
+ TransitiveInfoCollection lcovMerger =
+ ruleContext.getPrerequisite("$lcov_merger", Mode.TARGET);
+ FilesToRunProvider lcovFilesToRun = lcovMerger.getProvider(FilesToRunProvider.class);
+ if (lcovFilesToRun != null) {
+ extraTestEnv.put("LCOV_MERGER", lcovFilesToRun.getExecutable().getExecPathString());
+ inputsBuilder.addTransitive(lcovFilesToRun.getFilesToRun());
+ } else {
+ NestedSet<Artifact> filesToBuild =
+ lcovMerger.getProvider(FileProvider.class).getFilesToBuild();
+
+ if (Iterables.size(filesToBuild) == 1) {
+ Artifact lcovMergerArtifact = Iterables.getOnlyElement(filesToBuild);
+ extraTestEnv.put("LCOV_MERGER", lcovMergerArtifact.getExecPathString());
+ inputsBuilder.add(lcovMergerArtifact);
+ } else {
+ ruleContext.attributeError("$lcov_merger",
+ "the LCOV merger should be either an executable or a single artifact");
+ }
+ }
+ }
+
+ Artifact instrumentedFileManifest =
+ InstrumentedFileManifestAction.getInstrumentedFileManifest(ruleContext,
+ instrumentedFiles.getInstrumentedFiles(), metadataFiles);
+ executionSettings = new TestTargetExecutionSettings(ruleContext, runfilesSupport,
+ executable, instrumentedFileManifest, shards);
+ inputsBuilder.add(instrumentedFileManifest);
+ // TODO(ulfjack): Is this even ever set? If yes, does this cost us a lot of memory?
+ for (Pair<String, String> coverageEnvEntry : instrumentedFiles.getCoverageEnvironment()) {
+ extraTestEnv.put(coverageEnvEntry.getFirst(), coverageEnvEntry.getSecond());
+ }
+ } else {
+ executionSettings = new TestTargetExecutionSettings(ruleContext, runfilesSupport,
+ executable, null, shards);
+ }
+
+ extraTestEnv.putAll(extraEnv);
+
+ if (config.getRunUnder() != null) {
+ Artifact runUnderExecutable = executionSettings.getRunUnderExecutable();
+ if (runUnderExecutable != null) {
+ inputsBuilder.add(runUnderExecutable);
+ }
+ }
+
+ int runsPerTest =
+ config.getFragment(TestConfiguration.class).getRunsPerTestForLabel(ruleContext.getLabel());
+
+ Iterable<Artifact> inputs = inputsBuilder.build();
+ int shardRuns = (shards > 0 ? shards : 1);
+ List<Artifact> results = Lists.newArrayListWithCapacity(runsPerTest * shardRuns);
+ ImmutableList.Builder<Artifact> coverageArtifacts = ImmutableList.builder();
+
+ boolean useTestRunner = false;
+ if (ruleContext.attributes().has("use_testrunner", Type.BOOLEAN)) {
+ useTestRunner = ruleContext.attributes().get("use_testrunner", Type.BOOLEAN);
+ }
+
+ for (int run = 0; run < runsPerTest; run++) {
+ // Use a 1-based index for user friendliness.
+ String testRunDir =
+ runsPerTest > 1 ? String.format("run_%d_of_%d", run + 1, runsPerTest) : "";
+ for (int shard = 0; shard < shardRuns; shard++) {
+ String shardRunDir =
+ (shardRuns > 1 ? String.format("shard_%d_of_%d", shard + 1, shards) : "");
+ if (testRunDir.isEmpty()) {
+ shardRunDir = shardRunDir.isEmpty() ? "" : shardRunDir + PathFragment.SEPARATOR_CHAR;
+ } else {
+ testRunDir += PathFragment.SEPARATOR_CHAR;
+ shardRunDir = shardRunDir.isEmpty() ? testRunDir : shardRunDir + "_" + testRunDir;
+ }
+ Artifact testLog =
+ ruleContext.getPackageRelativeArtifact(
+ targetName.getRelative(shardRunDir + "test.log"), root);
+ Artifact cacheStatus =
+ ruleContext.getPackageRelativeArtifact(
+ targetName.getRelative(shardRunDir + "test.cache_status"), root);
+
+ Artifact coverageArtifact = null;
+ if (collectCodeCoverage) {
+ coverageArtifact = ruleContext.getPackageRelativeArtifact(
+ targetName.getRelative(shardRunDir + "coverage.dat"), root);
+ coverageArtifacts.add(coverageArtifact);
+ }
+
+ env.registerAction(new TestRunnerAction(
+ ruleContext.getActionOwner(), inputs, testRuntime,
+ testLog, cacheStatus,
+ coverageArtifact,
+ testProperties, extraTestEnv, executionSettings,
+ shard, run, config, ruleContext.getWorkspaceName(),
+ useTestRunner));
+ results.add(cacheStatus);
+ }
+ }
+ // TODO(bazel-team): Passing the reportGenerator to every TestParams is a bit strange.
+ Artifact reportGenerator = null;
+ if (config.isCodeCoverageEnabled()) {
+ // It's not enough to add this if the rule has coverage enabled because the command line may
+ // contain rules with baseline coverage but no test rules that have coverage enabled, and in
+ // that case, we still need the report generator.
+ reportGenerator = ruleContext.getPrerequisiteArtifact(
+ "$coverage_report_generator", Mode.HOST);
+ }
+
+ return new TestParams(runsPerTest, shards, TestTimeout.getTestTimeout(ruleContext.getRule()),
+ ruleContext.getRule().getRuleClass(), ImmutableList.copyOf(results),
+ coverageArtifacts.build(), reportGenerator);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/TestActionContext.java b/src/main/java/com/google/devtools/build/lib/analysis/test/TestActionContext.java
new file mode 100644
index 0000000000..cc483f6ab6
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/TestActionContext.java
@@ -0,0 +1,40 @@
+// Copyright 2014 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.devtools.build.lib.actions.ActionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.view.test.TestStatus.TestResultData;
+import java.io.IOException;
+
+/**
+ * A context for the execution of test actions ({@link TestRunnerAction}).
+ */
+public interface TestActionContext extends ActionContext {
+
+ /**
+ * Executes the test command, directing standard out / err to {@code outErr}. The status of
+ * the test should be communicated by posting a {@link TestResult} object to the eventbus.
+ */
+ void exec(TestRunnerAction action,
+ ActionExecutionContext actionExecutionContext) throws ExecException, InterruptedException;
+
+ /**
+ * Creates a cached test result.
+ */
+ TestResult newCachedTestResult(Path execRoot, TestRunnerAction action, TestResultData cached)
+ throws IOException;
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/TestConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/test/TestConfiguration.java
new file mode 100644
index 0000000000..5eebe89a4c
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/TestConfiguration.java
@@ -0,0 +1,204 @@
+// Copyright 2017 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.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration.RunsPerTestConverter;
+import com.google.devtools.build.lib.analysis.config.BuildOptions;
+import com.google.devtools.build.lib.analysis.config.ConfigurationEnvironment;
+import com.google.devtools.build.lib.analysis.config.ConfigurationFragmentFactory;
+import com.google.devtools.build.lib.analysis.config.FragmentOptions;
+import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
+import com.google.devtools.build.lib.analysis.config.PerLabelOptions;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.common.options.Option;
+import com.google.devtools.common.options.OptionDocumentationCategory;
+import com.google.devtools.common.options.OptionEffectTag;
+import com.google.devtools.common.options.TriState;
+import java.util.List;
+
+/** Test-related options. */
+public class TestConfiguration extends Fragment {
+
+ /** Command-line options. */
+ public static class TestOptions extends FragmentOptions {
+ @Option(
+ name = "test_filter",
+ allowMultiple = false,
+ defaultValue = "null",
+ category = "testing",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help =
+ "Specifies a filter to forward to the test framework. Used to limit "
+ + "the tests run. Note that this does not affect which targets are built."
+ )
+ public String testFilter;
+
+ @Option(
+ name = "cache_test_results",
+ defaultValue = "auto",
+ category = "testing",
+ abbrev = 't', // it's useful to toggle this on/off quickly
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help =
+ "If set to 'auto', Bazel reruns a test if and only if: "
+ + "(1) Bazel detects changes in the test or its dependencies, "
+ + "(2) the test is marked as external, "
+ + "(3) multiple test runs were requested with --runs_per_test, or"
+ + "(4) the test previously failed. "
+ + "If set to 'yes', Bazel caches all test results except for tests marked as "
+ + "external. If set to 'no', Bazel does not cache any test results."
+ )
+ public TriState cacheTestResults;
+
+ @Deprecated
+ @Option(
+ name = "test_result_expiration",
+ defaultValue = "-1", // No expiration by defualt.
+ category = "testing",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help = "This option is deprecated and has no effect."
+ )
+ public int testResultExpiration;
+
+ @Option(
+ name = "test_arg",
+ allowMultiple = true,
+ defaultValue = "",
+ category = "testing",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help =
+ "Specifies additional options and arguments that should be passed to the test "
+ + "executable. Can be used multiple times to specify several arguments. "
+ + "If multiple tests are executed, each of them will receive identical arguments. "
+ + "Used only by the 'bazel test' command."
+ )
+ public List<String> testArguments;
+
+ @Option(
+ name = "test_sharding_strategy",
+ defaultValue = "explicit",
+ category = "testing",
+ converter = TestActionBuilder.ShardingStrategyConverter.class,
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help =
+ "Specify strategy for test sharding: "
+ + "'explicit' to only use sharding if the 'shard_count' BUILD attribute is present. "
+ + "'disabled' to never use test sharding. "
+ + "'experimental_heuristic' to enable sharding on remotely executed tests without an "
+ + "explicit 'shard_count' attribute which link in a supported framework. Considered "
+ + "experimental."
+ )
+ public TestActionBuilder.TestShardingStrategy testShardingStrategy;
+
+ @Option(
+ name = "runs_per_test",
+ allowMultiple = true,
+ defaultValue = "1",
+ category = "testing",
+ converter = RunsPerTestConverter.class,
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help =
+ "Specifies number of times to run each test. If any of those attempts "
+ + "fail for any reason, the whole test would be considered failed. "
+ + "Normally the value specified is just an integer. Example: --runs_per_test=3 "
+ + "will run all tests 3 times. "
+ + "Alternate syntax: regex_filter@runs_per_test. Where runs_per_test stands for "
+ + "an integer value and regex_filter stands "
+ + "for a list of include and exclude regular expression patterns (Also see "
+ + "--instrumentation_filter). Example: "
+ + "--runs_per_test=//foo/.*,-//foo/bar/.*@3 runs all tests in //foo/ "
+ + "except those under foo/bar three times. "
+ + "This option can be passed multiple times. "
+ )
+ public List<PerLabelOptions> runsPerTest;
+ }
+
+ /** Configuration loader for test options */
+ public static class Loader implements ConfigurationFragmentFactory {
+ @Override
+ public Fragment create(ConfigurationEnvironment env, BuildOptions buildOptions)
+ throws InvalidConfigurationException {
+ return new TestConfiguration(buildOptions.get(TestOptions.class));
+ }
+
+ @Override
+ public Class<? extends Fragment> creates() {
+ return TestConfiguration.class;
+ }
+
+ @Override
+ public ImmutableSet<Class<? extends FragmentOptions>> requiredOptions() {
+ return ImmutableSet.of(TestOptions.class);
+ }
+ }
+
+ private final TestOptions options;
+
+ TestConfiguration(TestOptions options) {
+ this.options = options;
+ }
+
+ @Override
+ public void reportInvalidOptions(EventHandler reporter, BuildOptions buildOptions) {
+ if (options.testShardingStrategy
+ == TestActionBuilder.TestShardingStrategy.EXPERIMENTAL_HEURISTIC) {
+ reporter.handle(
+ Event.warn(
+ "Heuristic sharding is intended as a one-off experimentation tool for determing the "
+ + "benefit from sharding certain tests. Please don't keep this option in your "
+ + ".blazerc or continuous build"));
+ }
+ }
+
+ public String getTestFilter() {
+ return options.testFilter;
+ }
+
+ public TriState cacheTestResults() {
+ return options.cacheTestResults;
+ }
+
+ public List<String> getTestArguments() {
+ return options.testArguments;
+ }
+
+ public TestActionBuilder.TestShardingStrategy testShardingStrategy() {
+ return options.testShardingStrategy;
+ }
+
+ /**
+ * @return number of times the given test should run. If the test doesn't match any of the
+ * filters, runs it once.
+ */
+ public int getRunsPerTestForLabel(Label label) {
+ for (PerLabelOptions perLabelRuns : options.runsPerTest) {
+ if (perLabelRuns.isIncluded(label)) {
+ return Integer.parseInt(Iterables.getOnlyElement(perLabelRuns.getOptions()));
+ }
+ }
+ return 1;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/TestEnvironmentProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/test/TestEnvironmentProvider.java
new file mode 100644
index 0000000000..fb1125f31e
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/TestEnvironmentProvider.java
@@ -0,0 +1,47 @@
+// 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.analysis.test;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.packages.Info;
+import com.google.devtools.build.lib.packages.NativeProvider;
+import java.util.Map;
+
+/** Provider containing any additional environment variables for use in the test action. */
+@Immutable
+public final class TestEnvironmentProvider extends Info {
+
+ /** Skylark constructor and identifier for TestEnvironmentProvider. */
+ public static final NativeProvider<TestEnvironmentProvider> SKYLARK_CONSTRUCTOR =
+ new NativeProvider<TestEnvironmentProvider>(
+ TestEnvironmentProvider.class, "TestEnvironment") {};
+
+ private final Map<String, String> environment;
+
+ /** Constructs a new provider with the given variable name to variable value mapping. */
+ public TestEnvironmentProvider(Map<String, String> environment) {
+ super(SKYLARK_CONSTRUCTOR, ImmutableMap.<String, Object>of("environment", environment));
+ this.environment = Preconditions.checkNotNull(environment);
+ }
+
+ /**
+ * Returns environment variables which should be set on the test action.
+ */
+ public Map<String, String> getEnvironment() {
+ return environment;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/TestProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/test/TestProvider.java
new file mode 100644
index 0000000000..ce99d72e69
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/TestProvider.java
@@ -0,0 +1,143 @@
+// Copyright 2014 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.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.packages.TestTimeout;
+import java.util.List;
+
+/**
+ * A {@link TransitiveInfoProvider} for configured targets that implement test rules.
+ */
+@Immutable
+public final class TestProvider implements TransitiveInfoProvider {
+ private final TestParams testParams;
+ private final ImmutableList<String> testTags;
+
+ public TestProvider(TestParams testParams, ImmutableList<String> testTags) {
+ this.testParams = testParams;
+ this.testTags = testTags;
+ }
+
+ /**
+ * Returns the {@link TestParams} object for the test represented by the corresponding configured
+ * target.
+ */
+ public TestParams getTestParams() {
+ return testParams;
+ }
+
+ /**
+ * Temporary hack to allow dependencies on test_suite targets to continue to work for the time
+ * being.
+ */
+ public List<String> getTestTags() {
+ return testTags;
+ }
+
+ /**
+ * Returns the test status artifacts for a specified configured target
+ *
+ * @param target the configured target. Should belong to a test rule.
+ * @return the test status artifacts
+ */
+ public static ImmutableList<Artifact> getTestStatusArtifacts(TransitiveInfoCollection target) {
+ return target.getProvider(TestProvider.class).getTestParams().getTestStatusArtifacts();
+ }
+
+ /**
+ * A value class describing the properties of a test.
+ */
+ public static class TestParams {
+ private final int runs;
+ private final int shards;
+ private final TestTimeout timeout;
+ private final String testRuleClass;
+ private final ImmutableList<Artifact> testStatusArtifacts;
+ private final ImmutableList<Artifact> coverageArtifacts;
+ private final Artifact coverageReportGenerator;
+
+ /**
+ * Don't call this directly. Instead use
+ * {@link com.google.devtools.build.lib.analysis.test.TestActionBuilder}.
+ */
+ TestParams(int runs, int shards, TestTimeout timeout, String testRuleClass,
+ ImmutableList<Artifact> testStatusArtifacts,
+ ImmutableList<Artifact> coverageArtifacts,
+ Artifact coverageReportGenerator) {
+ this.runs = runs;
+ this.shards = shards;
+ this.timeout = timeout;
+ this.testRuleClass = testRuleClass;
+ this.testStatusArtifacts = testStatusArtifacts;
+ this.coverageArtifacts = coverageArtifacts;
+ this.coverageReportGenerator = coverageReportGenerator;
+ }
+
+ /**
+ * Returns the number of times this test should be run.
+ */
+ public int getRuns() {
+ return runs;
+ }
+
+ /**
+ * Returns the number of shards for this test.
+ */
+ public int getShards() {
+ return shards;
+ }
+
+ /**
+ * Returns the timeout of this test.
+ */
+ public TestTimeout getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * Returns the test rule class.
+ */
+ public String getTestRuleClass() {
+ return testRuleClass;
+ }
+
+ /**
+ * Returns a list of test status artifacts that represent serialized test status protobuffers
+ * produced by testing this target.
+ */
+ public ImmutableList<Artifact> getTestStatusArtifacts() {
+ return testStatusArtifacts;
+ }
+
+ /**
+ * Returns the coverageArtifacts
+ */
+ public ImmutableList<Artifact> getCoverageArtifacts() {
+ return coverageArtifacts;
+ }
+
+ /**
+ * Returns the coverage report generator tool.
+ */
+ public Artifact getCoverageReportGenerator() {
+ return coverageReportGenerator;
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/TestResult.java b/src/main/java/com/google/devtools/build/lib/analysis/test/TestResult.java
new file mode 100644
index 0000000000..11f95a4656
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/TestResult.java
@@ -0,0 +1,191 @@
+// Copyright 2014 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.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.test.TestRunnerAction.ResolvedPaths;
+import com.google.devtools.build.lib.buildeventstream.TestFileNameConstants;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.util.Preconditions;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.view.test.TestStatus.BlazeTestStatus;
+import com.google.devtools.build.lib.view.test.TestStatus.TestResultData;
+import java.util.Collection;
+import javax.annotation.Nullable;
+
+/**
+ * This is the event passed from the various test strategies to the {@code RecordingTestListener}
+ * upon test completion.
+ */
+@ThreadSafe
+@Immutable
+public class TestResult {
+
+ private final TestRunnerAction testAction;
+ private final TestResultData data;
+ private final boolean cached;
+ @Nullable protected final Path execRoot;
+
+ /**
+ * Construct the TestResult for the given test / status.
+ *
+ * @param testAction The test that was run.
+ * @param data test result protobuffer.
+ * @param cached true if this is a locally cached test result.
+ * @param execRoot The execution root in which the action was carried out; can be null, in which
+ * case everything depending on the execution root is ignored.
+ */
+ public TestResult(
+ TestRunnerAction testAction, TestResultData data, boolean cached, @Nullable Path execRoot) {
+ this.testAction = Preconditions.checkNotNull(testAction);
+ this.data = data;
+ this.cached = cached;
+ this.execRoot = execRoot;
+ }
+
+ public TestResult(TestRunnerAction testAction, TestResultData data, boolean cached) {
+ this(testAction, data, cached, null);
+ }
+
+ public static boolean isBlazeTestStatusPassed(BlazeTestStatus status) {
+ return status == BlazeTestStatus.PASSED || status == BlazeTestStatus.FLAKY;
+ }
+
+ /**
+ * @return The test action.
+ */
+ public TestRunnerAction getTestAction() {
+ return testAction;
+ }
+
+ /**
+ * @return The test log path. Note, that actual log file may no longer
+ * correspond to this artifact - use getActualLogPath() method if
+ * you need log location.
+ */
+ public Path getTestLogPath() {
+ return testAction.getTestLog().getPath();
+ }
+
+ /**
+ * Return if result was loaded from local action cache.
+ */
+ public final boolean isCached() {
+ return cached;
+ }
+
+ /**
+ * @return Coverage data artifact, if available and null otherwise.
+ */
+ public Path getCoverageData() {
+ if (data.getHasCoverage()) {
+ return testAction.getCoverageData().getPath();
+ }
+ return null;
+ }
+
+ /**
+ * @return The test status artifact.
+ */
+ public Artifact getTestStatusArtifact() {
+ // these artifacts are used to keep track of the number of pending and completed tests.
+ return testAction.getCacheStatusArtifact();
+ }
+
+
+ /**
+ * Gets the test name in a user-friendly format.
+ * Will generally include the target name and shard number, if applicable.
+ *
+ * @return The test name.
+ */
+ public String getTestName() {
+ return testAction.getTestName();
+ }
+
+ /**
+ * @return The test label.
+ */
+ public String getLabel() {
+ return Label.print(testAction.getOwner().getLabel());
+ }
+
+ /**
+ * @return The test shard number.
+ */
+ public int getShardNum() {
+ return testAction.getShardNum();
+ }
+
+ /**
+ * @return Total number of test shards. 0 means
+ * no sharding, whereas 1 means degenerate sharding.
+ */
+ public int getTotalShards() {
+ return testAction.getExecutionSettings().getTotalShards();
+ }
+
+ public TestResultData getData() {
+ return data;
+ }
+
+ /**
+ * @return Collection of files created by the test, tagged by their name indicating usage (e.g.,
+ * "test.log").
+ */
+ public Collection<Pair<String, Path>> getFiles() {
+ ImmutableList.Builder<Pair<String, Path>> builder = new ImmutableList.Builder<>();
+ if (testAction.getTestLog().getPath().exists()) {
+ builder.add(Pair.of(TestFileNameConstants.TEST_LOG, testAction.getTestLog().getPath()));
+ }
+ if (execRoot != null) {
+ ResolvedPaths resolvedPaths = testAction.resolve(execRoot);
+ if (resolvedPaths.getXmlOutputPath().exists()) {
+ builder.add(Pair.of(TestFileNameConstants.TEST_XML, resolvedPaths.getXmlOutputPath()));
+ }
+ if (resolvedPaths.getSplitLogsPath().exists()) {
+ builder.add(Pair.of(TestFileNameConstants.SPLIT_LOGS, resolvedPaths.getSplitLogsPath()));
+ }
+ if (resolvedPaths.getTestWarningsPath().exists()) {
+ builder.add(Pair.of(TestFileNameConstants.TEST_WARNINGS, resolvedPaths.getSplitLogsPath()));
+ }
+ if (resolvedPaths.getUndeclaredOutputsZipPath().exists()) {
+ builder.add(Pair.of(TestFileNameConstants.UNDECLARED_OUTPUTS_ZIP,
+ resolvedPaths.getUndeclaredOutputsZipPath()));
+ }
+ if (resolvedPaths.getUndeclaredOutputsManifestPath().exists()) {
+ builder.add(Pair.of(TestFileNameConstants.UNDECLARED_OUTPUTS_MANIFEST,
+ resolvedPaths.getUndeclaredOutputsManifestPath()));
+ }
+ if (resolvedPaths.getUndeclaredOutputsAnnotationsPath().exists()) {
+ builder.add(Pair.of(TestFileNameConstants.UNDECLARED_OUTPUTS_ANNOTATIONS,
+ resolvedPaths.getUndeclaredOutputsManifestPath()));
+ }
+ if (resolvedPaths.getUnusedRunfilesLogPath().exists()) {
+ builder.add(Pair.of(TestFileNameConstants.UNUSED_RUNFILES_LOG,
+ resolvedPaths.getUnusedRunfilesLogPath()));
+ }
+ if (resolvedPaths.getInfrastructureFailureFile().exists()) {
+ builder.add(Pair.of(TestFileNameConstants.TEST_INFRASTRUCTURE_FAILURE,
+ resolvedPaths.getInfrastructureFailureFile()));
+ }
+ }
+ return builder.build();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java b/src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java
new file mode 100644
index 0000000000..bb572202cd
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java
@@ -0,0 +1,802 @@
+// Copyright 2014 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.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.AbstractAction;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.ActionInput;
+import com.google.devtools.build.lib.actions.ActionInputHelper;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.actions.NotifyOnActionCacheHit;
+import com.google.devtools.build.lib.actions.UserExecException;
+import com.google.devtools.build.lib.analysis.RunfilesSupplierImpl;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.config.RunUnder;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.collect.ImmutableIterable;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.build.lib.util.LoggingUtil;
+import com.google.devtools.build.lib.util.Preconditions;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.view.test.TestStatus.TestResultData;
+import com.google.devtools.common.options.TriState;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import javax.annotation.Nullable;
+
+/**
+ * An Action representing a test with the associated environment (runfiles,
+ * environment variables, test result, etc). It consumes test executable and
+ * runfiles artifacts and produces test result and test status artifacts.
+ */
+// Not final so that we can mock it in tests.
+public class TestRunnerAction extends AbstractAction implements NotifyOnActionCacheHit {
+ public static final PathFragment COVERAGE_TMP_ROOT = PathFragment.create("_coverage");
+
+ // Used for selecting subset of testcase / testmethods.
+ private static final String TEST_BRIDGE_TEST_FILTER_ENV = "TESTBRIDGE_TEST_ONLY";
+
+ private static final String GUID = "cc41f9d0-47a6-11e7-8726-eb6ce83a8cc8";
+
+ private final NestedSet<Artifact> runtime;
+ private final BuildConfiguration configuration;
+ private final TestConfiguration testConfiguration;
+ private final Artifact testLog;
+ private final Artifact cacheStatus;
+ private final PathFragment testWarningsPath;
+ private final PathFragment unusedRunfilesLogPath;
+ private final PathFragment splitLogsPath;
+ private final PathFragment splitLogsDir;
+ private final PathFragment undeclaredOutputsDir;
+ private final PathFragment undeclaredOutputsZipPath;
+ private final PathFragment undeclaredOutputsAnnotationsDir;
+ private final PathFragment undeclaredOutputsManifestPath;
+ private final PathFragment undeclaredOutputsAnnotationsPath;
+ private final PathFragment xmlOutputPath;
+ @Nullable
+ private final PathFragment testShard;
+ private final PathFragment testExitSafe;
+ private final PathFragment testStderr;
+ private final PathFragment testInfrastructureFailure;
+ private final PathFragment baseDir;
+ private final Artifact coverageData;
+ private final TestTargetProperties testProperties;
+ private final TestTargetExecutionSettings executionSettings;
+ private final int shardNum;
+ private final int runNumber;
+ private final String workspaceName;
+ private final boolean useTestRunner;
+
+ // Mutable state related to test caching. Lazily initialized: null indicates unknown.
+ private Boolean unconditionalExecution;
+
+ /** Any extra environment variables (and values) added by the rule that created this action. */
+ private final ImmutableMap<String, String> extraTestEnv;
+
+ /**
+ * The set of environment variables that are inherited from the client environment. These are
+ * handled explicitly by the ActionCacheChecker and so don't have to be included in the cache key.
+ */
+ private final ImmutableIterable<String> requiredClientEnvVariables;
+
+ private static ImmutableList<Artifact> list(Artifact... artifacts) {
+ ImmutableList.Builder<Artifact> builder = ImmutableList.builder();
+ for (Artifact artifact : artifacts) {
+ if (artifact != null) {
+ builder.add(artifact);
+ }
+ }
+ return builder.build();
+ }
+
+ /**
+ * Create new TestRunnerAction instance. Should not be called directly.
+ * Use {@link TestActionBuilder} instead.
+ *
+ * @param shardNum The shard number. Must be 0 if totalShards == 0
+ * (no sharding). Otherwise, must be >= 0 and < totalShards.
+ * @param runNumber test run number
+ */
+ TestRunnerAction(
+ ActionOwner owner,
+ Iterable<Artifact> inputs,
+ NestedSet<Artifact> runtime, // Must be a subset of inputs
+ Artifact testLog,
+ Artifact cacheStatus,
+ Artifact coverageArtifact,
+ TestTargetProperties testProperties,
+ Map<String, String> extraTestEnv,
+ TestTargetExecutionSettings executionSettings,
+ int shardNum,
+ int runNumber,
+ BuildConfiguration configuration,
+ String workspaceName,
+ boolean useTestRunner) {
+ super(
+ owner,
+ inputs,
+ // Note that this action only cares about the runfiles, not the mapping.
+ new RunfilesSupplierImpl(PathFragment.create("runfiles"), executionSettings.getRunfiles()),
+ list(testLog, cacheStatus, coverageArtifact));
+ this.runtime = runtime;
+ this.configuration = Preconditions.checkNotNull(configuration);
+ this.testConfiguration =
+ Preconditions.checkNotNull(configuration.getFragment(TestConfiguration.class));
+ this.testLog = testLog;
+ this.cacheStatus = cacheStatus;
+ this.coverageData = coverageArtifact;
+ this.shardNum = shardNum;
+ this.runNumber = runNumber;
+ this.testProperties = Preconditions.checkNotNull(testProperties);
+ this.executionSettings = Preconditions.checkNotNull(executionSettings);
+
+ this.baseDir = cacheStatus.getExecPath().getParentDirectory();
+
+ int totalShards = executionSettings.getTotalShards();
+ Preconditions.checkState((totalShards == 0 && shardNum == 0) ||
+ (totalShards > 0 && 0 <= shardNum && shardNum < totalShards));
+ this.testExitSafe = baseDir.getChild("test.exited_prematurely");
+ // testShard Path should be set only if sharding is enabled.
+ this.testShard = totalShards > 1
+ ? baseDir.getChild("test.shard")
+ : null;
+ this.xmlOutputPath = baseDir.getChild("test.xml");
+ this.testWarningsPath = baseDir.getChild("test.warnings");
+ this.unusedRunfilesLogPath = baseDir.getChild("test.unused_runfiles_log");
+ this.testStderr = baseDir.getChild("test.err");
+ this.splitLogsDir = baseDir.getChild("test.raw_splitlogs");
+ // See note in {@link #getSplitLogsPath} on the choice of file name.
+ this.splitLogsPath = splitLogsDir.getChild("test.splitlogs");
+ this.undeclaredOutputsDir = baseDir.getChild("test.outputs");
+ this.undeclaredOutputsZipPath = undeclaredOutputsDir.getChild("outputs.zip");
+ this.undeclaredOutputsAnnotationsDir = baseDir.getChild("test.outputs_manifest");
+ this.undeclaredOutputsManifestPath = undeclaredOutputsAnnotationsDir.getChild("MANIFEST");
+ this.undeclaredOutputsAnnotationsPath = undeclaredOutputsAnnotationsDir.getChild("ANNOTATIONS");
+ this.testInfrastructureFailure = baseDir.getChild("test.infrastructure_failure");
+ this.workspaceName = workspaceName;
+ this.useTestRunner = useTestRunner;
+
+ this.extraTestEnv = ImmutableMap.copyOf(extraTestEnv);
+ this.requiredClientEnvVariables =
+ ImmutableIterable.from(Iterables.concat(
+ configuration.getActionEnvironment().getInheritedEnv(),
+ configuration.getTestActionEnvironment().getInheritedEnv()));
+ }
+
+ public BuildConfiguration getConfiguration() {
+ return configuration;
+ }
+
+ public final PathFragment getBaseDir() {
+ return baseDir;
+ }
+
+ @Override
+ public boolean showsOutputUnconditionally() {
+ return true;
+ }
+
+ public List<ActionInput> getSpawnOutputs() {
+ final List<ActionInput> outputs = new ArrayList<>();
+ outputs.add(ActionInputHelper.fromPath(getXmlOutputPath()));
+ outputs.add(ActionInputHelper.fromPath(getExitSafeFile()));
+ if (isSharded()) {
+ outputs.add(ActionInputHelper.fromPath(getTestShard()));
+ }
+ outputs.add(ActionInputHelper.fromPath(getTestWarningsPath()));
+ outputs.add(ActionInputHelper.fromPath(getSplitLogsPath()));
+ outputs.add(ActionInputHelper.fromPath(getUnusedRunfilesLogPath()));
+ outputs.add(ActionInputHelper.fromPath(getInfrastructureFailureFile()));
+ outputs.add(ActionInputHelper.fromPath(getUndeclaredOutputsZipPath()));
+ outputs.add(ActionInputHelper.fromPath(getUndeclaredOutputsManifestPath()));
+ outputs.add(ActionInputHelper.fromPath(getUndeclaredOutputsAnnotationsPath()));
+ if (isCoverageMode()) {
+ outputs.add(getCoverageData());
+ }
+ return outputs;
+ }
+
+ @Override
+ protected String computeKey() {
+ Fingerprint f = new Fingerprint();
+ f.addString(GUID);
+ f.addStrings(executionSettings.getArgs().arguments());
+ f.addString(executionSettings.getTestFilter() == null ? "" : executionSettings.getTestFilter());
+ RunUnder runUnder = executionSettings.getRunUnder();
+ f.addString(runUnder == null ? "" : runUnder.getValue());
+ f.addStringMap(extraTestEnv);
+ // TODO(ulfjack): It might be better for performance to hash the action and test envs in config,
+ // and only add a hash here.
+ configuration.getActionEnvironment().addTo(f);
+ configuration.getTestActionEnvironment().addTo(f);
+ // The 'requiredClientEnvVariables' are handled by Skyframe and don't need to be added here.
+ f.addString(testProperties.getSize().toString());
+ f.addString(testProperties.getTimeout().toString());
+ f.addStrings(testProperties.getTags());
+ f.addInt(testProperties.isLocal() ? 1 : 0);
+ f.addInt(shardNum);
+ f.addInt(executionSettings.getTotalShards());
+ f.addInt(runNumber);
+ f.addInt(testConfiguration.getRunsPerTestForLabel(getOwner().getLabel()));
+ f.addInt(configuration.isCodeCoverageEnabled() ? 1 : 0);
+ return f.hexDigestAndReset();
+ }
+
+ @Override
+ public boolean executeUnconditionally() {
+ // Note: isVolatile must return true if executeUnconditionally can ever return true
+ // for this instance.
+ if (unconditionalExecution == null) {
+ unconditionalExecution = computeExecuteUnconditionallyFromTestStatus();
+ }
+ return unconditionalExecution;
+ }
+
+ @Override
+ public boolean isVolatile() {
+ return true;
+ }
+
+ /**
+ * Saves cache status to disk.
+ */
+ public void saveCacheStatus(TestResultData data) throws IOException {
+ try (OutputStream out = cacheStatus.getPath().getOutputStream()) {
+ data.writeTo(out);
+ }
+ }
+
+ /**
+ * Returns the cache from disk, or null if the file doesn't exist or if there is an error.
+ */
+ @Nullable
+ private TestResultData readCacheStatus() {
+ try (InputStream in = cacheStatus.getPath().getInputStream()) {
+ return TestResultData.parseFrom(in);
+ } catch (IOException expected) {
+ return null;
+ }
+ }
+
+ private boolean computeExecuteUnconditionallyFromTestStatus() {
+ return !canBeCached(
+ testConfiguration.cacheTestResults(),
+ readCacheStatus(),
+ testProperties.isExternal(),
+ testConfiguration.getRunsPerTestForLabel(getOwner().getLabel()));
+ }
+
+ @VisibleForTesting
+ static boolean canBeCached(
+ TriState cacheTestResults, TestResultData prevStatus, boolean isExternal, int runsPerTest) {
+ if (cacheTestResults == TriState.NO) {
+ return false;
+ }
+ if (isExternal) {
+ return false;
+ }
+ if (cacheTestResults == TriState.AUTO && (runsPerTest > 1)) {
+ return false;
+ }
+ // Test will not be executed unconditionally - check whether test result exists and is
+ // valid. If it is, method will return false and we will rely on the dependency checker
+ // to make a decision about test execution.
+ if (cacheTestResults == TriState.AUTO && prevStatus != null && !prevStatus.getTestPassed()) {
+ return false;
+ }
+ // Rely on the dependency checker to determine if the test can be cached. Note that the status
+ // is a declared output, so its non-existence also triggers a re-run.
+ return true;
+ }
+
+ /**
+ * Returns whether caching has been deemed safe by looking at the previous test run
+ * (for local caching). If the previous run is not present, return "true" here, as
+ * remote execution caching should be safe.
+ */
+ public boolean shouldCacheResult() {
+ return !executeUnconditionally();
+ }
+
+ @Override
+ public void actionCacheHit(ActionCachedContext executor) {
+ unconditionalExecution = null;
+ try {
+ executor.getEventBus().post(
+ executor.getContext(TestActionContext.class).newCachedTestResult(
+ executor.getExecRoot(), this, readCacheStatus()));
+ } catch (IOException e) {
+ LoggingUtil.logToRemote(Level.WARNING, "Failed creating cached protocol buffer", e);
+ }
+ }
+
+ @Override
+ protected String getRawProgressMessage() {
+ return "Testing " + getTestName();
+ }
+
+ /**
+ * Deletes <b>all</b> possible test outputs.
+ *
+ * TestRunnerAction potentially can create many more non-declared outputs - xml output,
+ * coverage data file and logs for failed attempts. All those outputs are uniquely
+ * identified by the test log base name with arbitrary prefix and extension.
+ */
+ @Override
+ protected void deleteOutputs(Path execRoot) throws IOException {
+ super.deleteOutputs(execRoot);
+
+ // We do not rely on globs, as it causes quadratic behavior in --runs_per_test and test
+ // shard count.
+
+ // We also need to remove *.(xml|data|shard|warnings|zip) files if they are present.
+ execRoot.getRelative(xmlOutputPath).delete();
+ execRoot.getRelative(testWarningsPath).delete();
+ execRoot.getRelative(unusedRunfilesLogPath).delete();
+ // Note that splitLogsPath points to a file inside the splitLogsDir so
+ // it's not necessary to delete it explicitly.
+ FileSystemUtils.deleteTree(execRoot.getRelative(splitLogsDir));
+ FileSystemUtils.deleteTree(execRoot.getRelative(undeclaredOutputsDir));
+ FileSystemUtils.deleteTree(execRoot.getRelative(undeclaredOutputsAnnotationsDir));
+ execRoot.getRelative(testStderr).delete();
+ execRoot.getRelative(testExitSafe).delete();
+ if (testShard != null) {
+ execRoot.getRelative(testShard).delete();
+ }
+ execRoot.getRelative(testInfrastructureFailure).delete();
+
+ // Coverage files use "coverage" instead of "test".
+ String coveragePrefix = "coverage";
+
+ // We cannot use coverageData artifact since it may be null. Generate coverage name instead.
+ execRoot.getRelative(baseDir.getChild(coveragePrefix + ".dat")).delete();
+
+ // Delete files fetched from remote execution.
+ execRoot.getRelative(baseDir.getChild("test.zip")).delete();
+ deleteTestAttemptsDirMaybe(execRoot.getRelative(baseDir), "test");
+ }
+
+ private void deleteTestAttemptsDirMaybe(Path outputDir, String namePrefix) throws IOException {
+ Path testAttemptsDir = outputDir.getChild(namePrefix + "_attempts");
+ if (testAttemptsDir.exists()) {
+ // Normally we should have used deleteTree(testAttemptsDir). However, if test output is
+ // in a FUSE filesystem implemented with the high-level API, there may be .fuse???????
+ // entries, which prevent removing the directory. As a workaround, code below will throw
+ // IOException if it will fail to remove something inside testAttemptsDir, but will
+ // silently suppress any exceptions when deleting testAttemptsDir itself.
+ FileSystemUtils.deleteTreesBelow(testAttemptsDir);
+ try {
+ testAttemptsDir.delete();
+ } catch (IOException e) {
+ // Do nothing.
+ }
+ }
+ }
+
+ public void setupEnvVariables(Map<String, String> env, Duration timeout) {
+ env.put("TEST_SIZE", getTestProperties().getSize().toString());
+ env.put("TEST_TIMEOUT", Long.toString(timeout.getSeconds()));
+ env.put("TEST_WORKSPACE", getRunfilesPrefix());
+ env.put(
+ "TEST_BINARY",
+ getExecutionSettings().getExecutable().getRootRelativePath().getCallablePathString());
+
+ // When we run test multiple times, set different TEST_RANDOM_SEED values for each run.
+ // Don't override any previous setting.
+ if (testConfiguration.getRunsPerTestForLabel(getOwner().getLabel()) > 1
+ && !env.containsKey("TEST_RANDOM_SEED")) {
+ env.put("TEST_RANDOM_SEED", Integer.toString(getRunNumber() + 1));
+ }
+
+ String testFilter = getExecutionSettings().getTestFilter();
+ if (testFilter != null) {
+ env.put(TEST_BRIDGE_TEST_FILTER_ENV, testFilter);
+ }
+
+ env.put("TEST_WARNINGS_OUTPUT_FILE", getTestWarningsPath().getPathString());
+ env.put("TEST_UNUSED_RUNFILES_LOG_FILE", getUnusedRunfilesLogPath().getPathString());
+
+ env.put("TEST_LOGSPLITTER_OUTPUT_FILE", getSplitLogsPath().getPathString());
+
+ env.put("TEST_UNDECLARED_OUTPUTS_ZIP", getUndeclaredOutputsZipPath().getPathString());
+ env.put("TEST_UNDECLARED_OUTPUTS_DIR", getUndeclaredOutputsDir().getPathString());
+ env.put("TEST_UNDECLARED_OUTPUTS_MANIFEST", getUndeclaredOutputsManifestPath().getPathString());
+ env.put(
+ "TEST_UNDECLARED_OUTPUTS_ANNOTATIONS",
+ getUndeclaredOutputsAnnotationsPath().getPathString());
+ env.put(
+ "TEST_UNDECLARED_OUTPUTS_ANNOTATIONS_DIR",
+ getUndeclaredOutputsAnnotationsDir().getPathString());
+
+ env.put("TEST_PREMATURE_EXIT_FILE", getExitSafeFile().getPathString());
+ env.put("TEST_INFRASTRUCTURE_FAILURE_FILE", getInfrastructureFailureFile().getPathString());
+
+ if (isSharded()) {
+ env.put("TEST_SHARD_INDEX", Integer.toString(getShardNum()));
+ env.put("TEST_TOTAL_SHARDS", Integer.toString(getExecutionSettings().getTotalShards()));
+ env.put("TEST_SHARD_STATUS_FILE", getTestShard().getPathString());
+ }
+ env.put("XML_OUTPUT_FILE", getXmlOutputPath().getPathString());
+
+ if (!isEnableRunfiles()) {
+ // If runfiles are disabled, tell remote-runtest.sh/local-runtest.sh about that.
+ env.put("RUNFILES_MANIFEST_ONLY", "1");
+ }
+
+ if (isCoverageMode()) {
+ // Instruct remote-runtest.sh/local-runtest.sh not to cd into the runfiles directory.
+ // TODO(ulfjack): Find a way to avoid setting this variable.
+ env.put("RUNTEST_PRESERVE_CWD", "1");
+
+ env.put("COVERAGE_MANIFEST", getCoverageManifest().getExecPathString());
+ env.put("COVERAGE_DIR", getCoverageDirectory().getPathString());
+ env.put("COVERAGE_OUTPUT_FILE", getCoverageData().getExecPathString());
+ // TODO(elenairina): Remove this after the next blaze release (after 2017.07.30).
+ env.put("NEW_JAVA_COVERAGE_IMPL", "True");
+ }
+ }
+
+ /**
+ * Gets the test name in a user-friendly format.
+ * Will generally include the target name and run/shard numbers, if applicable.
+ */
+ public String getTestName() {
+ String suffix = getTestSuffix();
+ String label = Label.print(getOwner().getLabel());
+ return suffix.isEmpty() ? label : label + " " + suffix;
+ }
+
+ /**
+ * Gets the test suffix in a user-friendly format, eg "(shard 1 of 7)".
+ * Will include the target name and run/shard numbers, if applicable.
+ */
+ public String getTestSuffix() {
+ int totalShards = executionSettings.getTotalShards();
+ // Use a 1-based index for user friendliness.
+ int runsPerTest = testConfiguration.getRunsPerTestForLabel(getOwner().getLabel());
+ if (totalShards > 1 && runsPerTest > 1) {
+ return String.format("(shard %d of %d, run %d of %d)", shardNum + 1, totalShards,
+ runNumber + 1, runsPerTest);
+ } else if (totalShards > 1) {
+ return String.format("(shard %d of %d)", shardNum + 1, totalShards);
+ } else if (runsPerTest > 1) {
+ return String.format("(run %d of %d)", runNumber + 1, runsPerTest);
+ } else {
+ return "";
+ }
+ }
+
+ public Artifact getTestLog() {
+ return testLog;
+ }
+
+ /**
+ * Returns all environment variables which must be set in order to run this test.
+ */
+ public Map<String, String> getExtraTestEnv() {
+ return extraTestEnv;
+ }
+
+ @Override
+ public Iterable<String> getClientEnvironmentVariables() {
+ return requiredClientEnvVariables;
+ }
+
+ public ResolvedPaths resolve(Path execRoot) {
+ return new ResolvedPaths(execRoot);
+ }
+
+ public Artifact getCacheStatusArtifact() {
+ return cacheStatus;
+ }
+
+ public PathFragment getTestWarningsPath() {
+ return testWarningsPath;
+ }
+
+ public PathFragment getUnusedRunfilesLogPath() {
+ return unusedRunfilesLogPath;
+ }
+
+ public PathFragment getSplitLogsPath() {
+ return splitLogsPath;
+ }
+
+ public PathFragment getUndeclaredOutputsDir() {
+ return undeclaredOutputsDir;
+ }
+
+ /**
+ * @return path to the optional zip file of undeclared test outputs.
+ */
+ public PathFragment getUndeclaredOutputsZipPath() {
+ return undeclaredOutputsZipPath;
+ }
+
+ /**
+ * @return path to the undeclared output manifest file.
+ */
+ public PathFragment getUndeclaredOutputsManifestPath() {
+ return undeclaredOutputsManifestPath;
+ }
+
+ public PathFragment getUndeclaredOutputsAnnotationsDir() {
+ return undeclaredOutputsAnnotationsDir;
+ }
+
+ /**
+ * @return path to the undeclared output annotations file.
+ */
+ public PathFragment getUndeclaredOutputsAnnotationsPath() {
+ return undeclaredOutputsAnnotationsPath;
+ }
+
+ public PathFragment getTestShard() {
+ return testShard;
+ }
+
+ public PathFragment getExitSafeFile() {
+ return testExitSafe;
+ }
+
+ public PathFragment getInfrastructureFailureFile() {
+ return testInfrastructureFailure;
+ }
+
+ /**
+ * @return path to the optionally created XML output file created by the test.
+ */
+ public PathFragment getXmlOutputPath() {
+ return xmlOutputPath;
+ }
+
+ /**
+ * @return coverage data artifact or null if code coverage was not requested.
+ */
+ @Nullable public Artifact getCoverageData() {
+ return coverageData;
+ }
+
+ @Nullable public Artifact getCoverageManifest() {
+ return getExecutionSettings().getInstrumentedFileManifest();
+ }
+
+ /** Returns true if coverage data should be gathered. */
+ public boolean isCoverageMode() {
+ return coverageData != null;
+ }
+
+ /**
+ * Returns a directory to temporarily store coverage results for the given action relative to the
+ * execution root. This directory is used to store all coverage results related to the test
+ * execution with exception of the locally generated *.gcda files. Those are stored separately
+ * using relative path within coverage directory.
+ *
+ * <p>The directory name for the given test runner action is constructed as: {@code
+ * _coverage/target_path/test_log_name} where {@code test_log_name} is usually a target name but
+ * potentially can include extra suffix, such as a shard number (if test execution was sharded).
+ */
+ public PathFragment getCoverageDirectory() {
+ return COVERAGE_TMP_ROOT.getRelative(
+ FileSystemUtils.removeExtension(getTestLog().getRootRelativePath()));
+ }
+
+ public TestTargetProperties getTestProperties() {
+ return testProperties;
+ }
+
+ public TestTargetExecutionSettings getExecutionSettings() {
+ return executionSettings;
+ }
+
+ public boolean useTestRunner() {
+ return useTestRunner;
+ }
+
+ public boolean isSharded() {
+ return testShard != null;
+ }
+
+ /**
+ * @return the shard number for this action.
+ * If getTotalShards() > 0, must be >= 0 and < getTotalShards().
+ * Otherwise, must be 0.
+ */
+ public int getShardNum() {
+ return shardNum;
+ }
+
+ /**
+ * @return run number.
+ */
+ public int getRunNumber() {
+ return runNumber;
+ }
+
+ /**
+ * @return the workspace name.
+ */
+ public String getRunfilesPrefix() {
+ return workspaceName;
+ }
+
+ @Override
+ public Artifact getPrimaryOutput() {
+ return testLog;
+ }
+
+ @Override
+ public void execute(ActionExecutionContext actionExecutionContext)
+ throws ActionExecutionException, InterruptedException {
+ TestActionContext context = actionExecutionContext.getContext(TestActionContext.class);
+ try {
+ context.exec(this, actionExecutionContext);
+ } catch (ExecException e) {
+ throw e.toActionExecutionException(this);
+ } finally {
+ unconditionalExecution = null;
+ }
+ }
+
+ @Override
+ public String getMnemonic() {
+ return "TestRunner";
+ }
+
+ @Override
+ public ImmutableSet<Artifact> getMandatoryOutputs() {
+ return getOutputs();
+ }
+
+ public Artifact getRuntimeArtifact(String basename) throws ExecException {
+ for (Artifact runtimeArtifact : runtime) {
+ if (runtimeArtifact.getExecPath().getBaseName().equals(basename)) {
+ return runtimeArtifact;
+ }
+ }
+
+ throw new UserExecException("'" + basename + "' not found in test runtime");
+ }
+
+ public PathFragment getShExecutable() {
+ return configuration.getShellExecutable();
+ }
+
+ public ImmutableMap<String, String> getLocalShellEnvironment() {
+ return configuration.getLocalShellEnvironment();
+ }
+
+ public boolean isEnableRunfiles() {
+ return configuration.runfilesEnabled();
+ }
+
+ /**
+ * The same set of paths as the parent test action, resolved against a given exec root.
+ */
+ public final class ResolvedPaths {
+ private final Path execRoot;
+
+ ResolvedPaths(Path execRoot) {
+ this.execRoot = Preconditions.checkNotNull(execRoot);
+ }
+
+ private Path getPath(PathFragment relativePath) {
+ return execRoot.getRelative(relativePath);
+ }
+
+ public final Path getBaseDir() {
+ return getPath(baseDir);
+ }
+
+ /**
+ * In rare cases, error messages will be printed to stderr instead of stdout. The test action is
+ * responsible for appending anything in the stderr file to the real test.log.
+ */
+ public Path getTestStderr() {
+ return getPath(testStderr);
+ }
+
+ public Path getTestWarningsPath() {
+ return getPath(testWarningsPath);
+ }
+
+ public Path getSplitLogsPath() {
+ return getPath(splitLogsPath);
+ }
+
+ public Path getUnusedRunfilesLogPath() {
+ return getPath(unusedRunfilesLogPath);
+ }
+
+ /**
+ * @return path to the directory containing the split logs (raw and proto file).
+ */
+ public Path getSplitLogsDir() {
+ return getPath(splitLogsDir);
+ }
+
+ /**
+ * @return path to the optional zip file of undeclared test outputs.
+ */
+ public Path getUndeclaredOutputsZipPath() {
+ return getPath(undeclaredOutputsZipPath);
+ }
+
+ /**
+ * @return path to the directory to hold undeclared test outputs.
+ */
+ public Path getUndeclaredOutputsDir() {
+ return getPath(undeclaredOutputsDir);
+ }
+
+ /**
+ * @return path to the directory to hold undeclared output annotations parts.
+ */
+ public Path getUndeclaredOutputsAnnotationsDir() {
+ return getPath(undeclaredOutputsAnnotationsDir);
+ }
+
+ /**
+ * @return path to the undeclared output manifest file.
+ */
+ public Path getUndeclaredOutputsManifestPath() {
+ return getPath(undeclaredOutputsManifestPath);
+ }
+
+ /**
+ * @return path to the undeclared output annotations file.
+ */
+ public Path getUndeclaredOutputsAnnotationsPath() {
+ return getPath(undeclaredOutputsAnnotationsPath);
+ }
+
+ @Nullable
+ public Path getTestShard() {
+ return testShard == null ? null : getPath(testShard);
+ }
+
+ public Path getExitSafeFile() {
+ return getPath(testExitSafe);
+ }
+
+ public Path getInfrastructureFailureFile() {
+ return getPath(testInfrastructureFailure);
+ }
+
+ /**
+ * @return path to the optionally created XML output file created by the test.
+ */
+ public Path getXmlOutputPath() {
+ return getPath(xmlOutputPath);
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/TestTargetExecutionSettings.java b/src/main/java/com/google/devtools/build/lib/analysis/test/TestTargetExecutionSettings.java
new file mode 100644
index 0000000000..5a2c520dc0
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/TestTargetExecutionSettings.java
@@ -0,0 +1,139 @@
+// Copyright 2014 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.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.FilesToRunProvider;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.Runfiles;
+import com.google.devtools.build.lib.analysis.RunfilesSupport;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.actions.CommandLine;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.config.RunUnder;
+import com.google.devtools.build.lib.packages.TargetUtils;
+import com.google.devtools.build.lib.util.Preconditions;
+import com.google.devtools.build.lib.vfs.Path;
+
+/**
+ * Container for common test execution settings shared by all
+ * all TestRunnerAction instances for the given test target.
+ */
+public final class TestTargetExecutionSettings {
+
+ private final CommandLine testArguments;
+ private final String testFilter;
+ private final int totalShards;
+ private final RunUnder runUnder;
+ private final Artifact runUnderExecutable;
+ private final Artifact executable;
+ private final boolean runfilesSymlinksCreated;
+ private final Path runfilesDir;
+ private final Runfiles runfiles;
+ private final Artifact runfilesInputManifest;
+ private final Artifact instrumentedFileManifest;
+
+ TestTargetExecutionSettings(RuleContext ruleContext, RunfilesSupport runfilesSupport,
+ Artifact executable, Artifact instrumentedFileManifest, int shards) {
+ Preconditions.checkArgument(TargetUtils.isTestRule(ruleContext.getRule()));
+ Preconditions.checkArgument(shards >= 0);
+ BuildConfiguration config = ruleContext.getConfiguration();
+ TestConfiguration testConfig = config.getFragment(TestConfiguration.class);
+
+ CommandLine targetArgs = runfilesSupport.getArgs();
+ testArguments =
+ CommandLine.concat(targetArgs, ImmutableList.copyOf(testConfig.getTestArguments()));
+
+ totalShards = shards;
+ runUnder = config.getRunUnder();
+ runUnderExecutable = getRunUnderExecutable(ruleContext);
+
+ this.testFilter = testConfig.getTestFilter();
+ this.executable = executable;
+ this.runfilesSymlinksCreated = runfilesSupport.getCreateSymlinks();
+ this.runfilesDir = runfilesSupport.getRunfilesDirectory();
+ this.runfiles = runfilesSupport.getRunfiles();
+ this.runfilesInputManifest = runfilesSupport.getRunfilesInputManifest();
+ this.instrumentedFileManifest = instrumentedFileManifest;
+ }
+
+ private static Artifact getRunUnderExecutable(RuleContext ruleContext) {
+ TransitiveInfoCollection runUnderTarget = ruleContext
+ .getPrerequisite(":run_under", Mode.DATA);
+ return runUnderTarget == null
+ ? null
+ : runUnderTarget.getProvider(FilesToRunProvider.class).getExecutable();
+ }
+
+ public CommandLine getArgs() {
+ return testArguments;
+ }
+
+ public String getTestFilter() {
+ return testFilter;
+ }
+
+ public int getTotalShards() {
+ return totalShards;
+ }
+
+ public RunUnder getRunUnder() {
+ return runUnder;
+ }
+
+ public Artifact getRunUnderExecutable() {
+ return runUnderExecutable;
+ }
+
+ public Artifact getExecutable() {
+ return executable;
+ }
+
+ /** @return whether or not the runfiles symlinks were created */
+ public boolean getRunfilesSymlinksCreated() {
+ return runfilesSymlinksCreated;
+ }
+
+ /** @return the directory of the runfiles */
+ public Path getRunfilesDir() {
+ return runfilesDir;
+ }
+
+ /** @return the runfiles for the test */
+ public Runfiles getRunfiles() {
+ return runfiles;
+ }
+
+ /**
+ * Returns the input runfiles manifest for this test.
+ *
+ * <p>This always returns the input manifest outside of the runfiles tree.
+ *
+ * @see com.google.devtools.build.lib.analysis.RunfilesSupport#getRunfilesInputManifest()
+ */
+ public Artifact getInputManifest() {
+ return runfilesInputManifest;
+ }
+
+ /**
+ * Returns instrumented file manifest or null if code coverage is not
+ * collected.
+ */
+ public Artifact getInstrumentedFileManifest() {
+ return instrumentedFileManifest;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/TestTargetProperties.java b/src/main/java/com/google/devtools/build/lib/analysis/test/TestTargetProperties.java
new file mode 100644
index 0000000000..d55d066966
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/TestTargetProperties.java
@@ -0,0 +1,187 @@
+// Copyright 2014 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.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.devtools.build.lib.actions.ExecutionRequirements;
+import com.google.devtools.build.lib.actions.ExecutionRequirements.ParseableRequirement.ValidationException;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.build.lib.actions.Spawn;
+import com.google.devtools.build.lib.actions.UserExecException;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.TargetUtils;
+import com.google.devtools.build.lib.packages.TestSize;
+import com.google.devtools.build.lib.packages.TestTimeout;
+import com.google.devtools.build.lib.syntax.Type;
+import com.google.devtools.build.lib.util.Preconditions;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Container for test target properties available to the
+ * TestRunnerAction instance.
+ */
+public class TestTargetProperties {
+
+ /**
+ * Resources used by local tests of various sizes.
+ *
+ * <p>When changing these values, remember to update the documentation at
+ * attributes/test/size.html.
+ */
+ private static final ResourceSet SMALL_RESOURCES = ResourceSet.create(20, 0.9, 0.00, 1);
+ private static final ResourceSet MEDIUM_RESOURCES = ResourceSet.create(100, 0.9, 0.1, 1);
+ private static final ResourceSet LARGE_RESOURCES = ResourceSet.create(300, 0.8, 0.1, 1);
+ private static final ResourceSet ENORMOUS_RESOURCES = ResourceSet.create(800, 0.7, 0.4, 1);
+ private static final ResourceSet LOCAL_TEST_JOBS_BASED_RESOURCES =
+ ResourceSet.createWithLocalTestCount(1);
+
+ private static ResourceSet getResourceSetFromSize(TestSize size) {
+ switch (size) {
+ case SMALL: return SMALL_RESOURCES;
+ case MEDIUM: return MEDIUM_RESOURCES;
+ case LARGE: return LARGE_RESOURCES;
+ default: return ENORMOUS_RESOURCES;
+ }
+ }
+
+ private final TestSize size;
+ private final TestTimeout timeout;
+ private final List<String> tags;
+ private final boolean isLocal;
+ private final boolean isFlaky;
+ private final boolean isExternal;
+ private final String language;
+ private final ImmutableMap<String, String> executionInfo;
+
+ /**
+ * Creates test target properties instance. Constructor expects that it
+ * will be called only for test configured targets.
+ */
+ TestTargetProperties(RuleContext ruleContext,
+ ExecutionInfoProvider executionRequirements) {
+ Rule rule = ruleContext.getRule();
+
+ Preconditions.checkState(TargetUtils.isTestRule(rule));
+ size = TestSize.getTestSize(rule);
+ timeout = TestTimeout.getTestTimeout(rule);
+ tags = ruleContext.attributes().get("tags", Type.STRING_LIST);
+ boolean isTaggedLocal = TargetUtils.isLocalTestRule(rule)
+ || TargetUtils.isExclusiveTestRule(rule);
+
+ // We need to use method on ruleConfiguredTarget to perform validation.
+ isFlaky = ruleContext.attributes().get("flaky", Type.BOOLEAN);
+ isExternal = TargetUtils.isExternalTestRule(rule);
+
+ Map<String, String> executionInfo = Maps.newLinkedHashMap();
+ executionInfo.putAll(TargetUtils.getExecutionInfo(rule));
+ if (isTaggedLocal) {
+ executionInfo.put("local", "");
+ }
+
+ boolean isRequestedLocalByProvider = false;
+ if (executionRequirements != null) {
+ // This will overwrite whatever TargetUtils put there, which might be confusing.
+ executionInfo.putAll(executionRequirements.getExecutionInfo());
+
+ // We also need to mark it as local if the execution requirements specifies it.
+ isRequestedLocalByProvider = executionRequirements.getExecutionInfo().containsKey("local");
+ }
+ this.executionInfo = ImmutableMap.copyOf(executionInfo);
+
+ isLocal = isTaggedLocal || isRequestedLocalByProvider;
+
+ language = TargetUtils.getRuleLanguage(rule);
+ }
+
+ public TestSize getSize() {
+ return size;
+ }
+
+ public TestTimeout getTimeout() {
+ return timeout;
+ }
+
+ public List<String> getTags() {
+ return tags;
+ }
+
+ public boolean isLocal() {
+ return isLocal;
+ }
+
+ public boolean isFlaky() {
+ return isFlaky;
+ }
+
+ public boolean isExternal() {
+ return isExternal;
+ }
+
+ public ResourceSet getLocalResourceUsage(Label label, boolean usingLocalTestJobs)
+ throws UserExecException {
+ if (usingLocalTestJobs) {
+ return LOCAL_TEST_JOBS_BASED_RESOURCES;
+ }
+
+ ResourceSet testResourcesFromSize = TestTargetProperties.getResourceSetFromSize(size);
+
+ // Tests can override their CPU reservation with a "cpus:<n>" tag.
+ ResourceSet testResourcesFromTag = null;
+ for (String tag : executionInfo.keySet()) {
+ try {
+ String cpus = ExecutionRequirements.CPU.parseIfMatches(tag);
+ if (cpus != null) {
+ if (testResourcesFromTag != null) {
+ throw new UserExecException(
+ String.format(
+ "%s has more than one '%s' tag, but duplicate tags aren't allowed",
+ label, ExecutionRequirements.CPU.userFriendlyName()));
+ }
+ testResourcesFromTag =
+ ResourceSet.create(
+ testResourcesFromSize.getMemoryMb(),
+ Float.parseFloat(cpus),
+ testResourcesFromSize.getIoUsage(),
+ testResourcesFromSize.getLocalTestCount());
+ }
+ } catch (ValidationException e) {
+ throw new UserExecException(
+ String.format(
+ "%s has a '%s' tag, but its value '%s' didn't pass validation: %s",
+ label,
+ ExecutionRequirements.CPU.userFriendlyName(),
+ e.getTagValue(),
+ e.getMessage()));
+ }
+ }
+
+ return testResourcesFromTag != null ? testResourcesFromTag : testResourcesFromSize;
+ }
+
+ /**
+ * Returns a map of execution info. See {@link Spawn#getExecutionInfo}.
+ */
+ public ImmutableMap<String, String> getExecutionInfo() {
+ return executionInfo;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+}