aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/analysis
diff options
context:
space:
mode:
authorGravatar ulfjack <ulfjack@google.com>2017-08-10 15:36:14 +0200
committerGravatar Marcel Hlopko <hlopko@google.com>2017-08-11 12:53:15 +0200
commitab21d18ddb5b53e887b4fd51ddc5d021621673d4 (patch)
tree2fe2d5819769a6672b5839381b1e447124b18b72 /src/main/java/com/google/devtools/build/lib/analysis
parent53641e5d303e23b89bd4653e5f0a510161bd71a2 (diff)
Move core test classes to lib.analysis.test
These are depended upon by analysis code, so need to live in the same library as lib.analysis. Moving them here makes it possible to split the build-base library into separate libraries for analysis, execution, and rules. PiperOrigin-RevId: 164847161
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;
+ }
+}