aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar elenairina <elenairina@google.com>2018-07-06 03:06:21 -0700
committerGravatar Copybara-Service <copybara-piper@google.com>2018-07-06 03:07:25 -0700
commit9438d1042df9e70570d245f6bd259acdfbb3455e (patch)
tree106816a832efd2a1c351966de94fe52277c70f32
parentc4622ac9205d2f1b42dac8c598e83113d39e7f11 (diff)
Create Bazel coverage report action.
Fixes #5246 RELNOTES: None. PiperOrigin-RevId: 203453340
-rw-r--r--src/main/java/com/google/devtools/build/lib/BUILD20
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java3
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/BuildView.java3
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java3
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/test/CoverageReportActionFactory.java4
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/test/TestActionBuilder.java7
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/test/TestProvider.java7
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/Bazel.java1
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/coverage/BazelCoverageReportModule.java121
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/coverage/CoverageArgs.java73
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/coverage/CoverageReportActionBuilder.java260
-rwxr-xr-xsrc/test/shell/bazel/bazel_coverage_test.sh88
-rw-r--r--tools/test/BUILD2
-rw-r--r--tools/test/LcovMerger/java/com/google/devtools/lcovmerger/BUILD.tools1
-rw-r--r--tools/test/LcovMerger/java/com/google/devtools/lcovmerger/Main.java61
-rw-r--r--tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/MainTest.java4
16 files changed, 636 insertions, 22 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD
index fedf4990a2..6b27d1d86b 100644
--- a/src/main/java/com/google/devtools/build/lib/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/BUILD
@@ -708,6 +708,7 @@ java_library(
"bazel/rules/sh/sh_stub_template_windows.txt",
],
deps = [
+ ":bazel-coverage",
":bazel/BazelRepositoryModule",
":exitcode-external",
"//src/main/java/com/google/devtools/build/lib:bazel",
@@ -796,6 +797,25 @@ java_library(
)
java_library(
+ name = "bazel-coverage",
+ srcs = glob(["bazel/coverage/*.java"]),
+ deps = [
+ "//src/main/java/com/google/devtools/build/lib:build-base",
+ "//src/main/java/com/google/devtools/build/lib:events",
+ "//src/main/java/com/google/devtools/build/lib:runtime",
+ "//src/main/java/com/google/devtools/build/lib:util",
+ "//src/main/java/com/google/devtools/build/lib/actions",
+ "//src/main/java/com/google/devtools/build/lib/concurrent",
+ "//src/main/java/com/google/devtools/build/lib/vfs",
+ "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
+ "//src/main/java/com/google/devtools/common/options",
+ "//third_party:auto_value",
+ "//third_party:guava",
+ "//third_party:jsr305",
+ ],
+)
+
+java_library(
name = "bazel-repository",
srcs = glob(
[
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 11b1ec3eab..4c4c501d07 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
@@ -190,8 +190,7 @@ public class BaseRuleClasses {
.cfg(HostTransition.INSTANCE)
.value(
coverageReportGeneratorAttribute(
- env.getToolsLabel(DEFAULT_COVERAGE_REPORT_GENERATOR_VALUE)))
- .singleArtifact())
+ env.getToolsLabel(DEFAULT_COVERAGE_REPORT_GENERATOR_VALUE))))
// The target itself and run_under both run on the same machine.
.add(attr(":run_under", LABEL).value(RUN_UNDER).skipPrereqValidatorCheck())
.executionPlatformConstraintsAllowed(ExecutionPlatformConstraintsAllowed.PER_TARGET)
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 204d94931e..2143efaa19 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
@@ -404,7 +404,8 @@ public class BuildView {
allTargetsToTest,
baselineCoverageArtifacts,
getArtifactFactory(),
- CoverageReportValue.COVERAGE_REPORT_KEY);
+ CoverageReportValue.COVERAGE_REPORT_KEY,
+ loadingResult.getWorkspaceName());
if (actionsWrapper != null) {
ImmutableList<ActionAnalysisMetadata> actions = actionsWrapper.getActions();
skyframeExecutor.injectCoverageReportData(actions);
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 ad4ecc4086..34cd54fd8c 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
@@ -197,8 +197,7 @@ public class SkylarkRuleClassFunctions implements SkylarkRuleFunctionsApi<Artifa
BaseRuleClasses.coverageReportGeneratorAttribute(
labelCache.getUnchecked(
toolsRepository
- + BaseRuleClasses.DEFAULT_COVERAGE_REPORT_GENERATOR_VALUE)))
- .singleArtifact())
+ + BaseRuleClasses.DEFAULT_COVERAGE_REPORT_GENERATOR_VALUE))))
.add(attr(":run_under", LABEL).value(RUN_UNDER))
.executionPlatformConstraintsAllowed(ExecutionPlatformConstraintsAllowed.PER_TARGET)
.build();
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
index e5678f389a..24750a4505 100644
--- 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
@@ -66,5 +66,7 @@ public interface CoverageReportActionFactory {
BlazeDirectories directories,
Collection<ConfiguredTarget> targetsToTest,
Iterable<Artifact> baselineCoverageArtifacts,
- ArtifactFactory artifactFactory, ArtifactOwner artifactOwner);
+ ArtifactFactory artifactFactory,
+ ArtifactOwner artifactOwner,
+ String workspaceName);
}
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
index e421c15596..7fe18a2300 100644
--- 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
@@ -323,13 +323,14 @@ public final class TestActionBuilder {
}
}
// TODO(bazel-team): Passing the reportGenerator to every TestParams is a bit strange.
- Artifact reportGenerator = null;
+ FilesToRunProvider 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);
+ TransitiveInfoCollection reportGeneratorTarget =
+ ruleContext.getPrerequisite(":coverage_report_generator", Mode.HOST);
+ reportGenerator = reportGeneratorTarget.getProvider(FilesToRunProvider.class);
}
return new TestParams(runsPerTest, shards, TestTimeout.getTestTimeout(ruleContext.getRule()),
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
index ce99d72e69..f356deb4bc 100644
--- 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
@@ -16,6 +16,7 @@ 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.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
@@ -71,7 +72,7 @@ public final class TestProvider implements TransitiveInfoProvider {
private final String testRuleClass;
private final ImmutableList<Artifact> testStatusArtifacts;
private final ImmutableList<Artifact> coverageArtifacts;
- private final Artifact coverageReportGenerator;
+ private final FilesToRunProvider coverageReportGenerator;
/**
* Don't call this directly. Instead use
@@ -80,7 +81,7 @@ public final class TestProvider implements TransitiveInfoProvider {
TestParams(int runs, int shards, TestTimeout timeout, String testRuleClass,
ImmutableList<Artifact> testStatusArtifacts,
ImmutableList<Artifact> coverageArtifacts,
- Artifact coverageReportGenerator) {
+ FilesToRunProvider coverageReportGenerator) {
this.runs = runs;
this.shards = shards;
this.timeout = timeout;
@@ -136,7 +137,7 @@ public final class TestProvider implements TransitiveInfoProvider {
/**
* Returns the coverage report generator tool.
*/
- public Artifact getCoverageReportGenerator() {
+ public FilesToRunProvider getCoverageReportGenerator() {
return coverageReportGenerator;
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/Bazel.java b/src/main/java/com/google/devtools/build/lib/bazel/Bazel.java
index 38a8ae77e1..96fa9a8d38 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/Bazel.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/Bazel.java
@@ -44,6 +44,7 @@ public final class Bazel {
com.google.devtools.build.lib.bazel.BazelDiffAwarenessModule.class,
com.google.devtools.build.lib.bazel.BazelRepositoryModule.class,
com.google.devtools.build.lib.bazel.debug.WorkspaceRuleModule.class,
+ com.google.devtools.build.lib.bazel.coverage.BazelCoverageReportModule.class,
com.google.devtools.build.lib.skylarkdebug.module.SkylarkDebuggerModule.class,
com.google.devtools.build.lib.bazel.repository.RepositoryResolvedModule.class,
com.google.devtools.build.lib.bazel.SpawnLogModule.class,
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/coverage/BazelCoverageReportModule.java b/src/main/java/com/google/devtools/build/lib/bazel/coverage/BazelCoverageReportModule.java
new file mode 100644
index 0000000000..1e7344ab3d
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/coverage/BazelCoverageReportModule.java
@@ -0,0 +1,121 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.bazel.coverage;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+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.analysis.test.CoverageReportActionFactory;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.runtime.BlazeModule;
+import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.common.options.EnumConverter;
+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.OptionsBase;
+import com.google.devtools.common.options.OptionsClassProvider;
+import java.util.Collection;
+
+/** Adds support for coverage report generation. */
+public class BazelCoverageReportModule extends BlazeModule {
+
+ /** Options that affect coverage report generation. */
+ public static class Options extends OptionsBase {
+
+ @Option(
+ name = "combined_report",
+ converter = ReportTypeConverter.class,
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ defaultValue = "none",
+ help =
+ "Specifies desired cumulative coverage report type. At this point only HTML "
+ + "and LCOV reports are supported."
+ )
+ public ReportType combinedReport;
+ }
+
+ /** Possible values for the --combined_report option. */
+ public static enum ReportType {
+ NONE,
+ LCOV,
+ }
+
+ /** Converter for the --combined_report option. */
+ public static class ReportTypeConverter extends EnumConverter<ReportType> {
+ public ReportTypeConverter() {
+ super(ReportType.class, "combined coverage report type");
+ }
+ }
+
+ @Override
+ public Iterable<Class<? extends OptionsBase>> getCommandOptions(Command command) {
+ return "build".equals(command.name())
+ ? ImmutableList.<Class<? extends OptionsBase>>of(Options.class)
+ : ImmutableList.<Class<? extends OptionsBase>>of();
+ }
+
+ @Override
+ public CoverageReportActionFactory getCoverageReportFactory(OptionsClassProvider commandOptions) {
+ final Options options = commandOptions.getOptions(Options.class);
+ return new CoverageReportActionFactory() {
+ @Override
+ public CoverageReportActionsWrapper createCoverageReportActionsWrapper(
+ EventHandler eventHandler,
+ BlazeDirectories directories,
+ Collection<ConfiguredTarget> targetsToTest,
+ Iterable<Artifact> baselineCoverageArtifacts,
+ ArtifactFactory artifactFactory,
+ ArtifactOwner artifactOwner,
+ String workspaceName) {
+ if (options == null || options.combinedReport == ReportType.NONE) {
+ return null;
+ }
+ Preconditions.checkArgument(options.combinedReport == ReportType.LCOV);
+ CoverageReportActionBuilder builder = new CoverageReportActionBuilder();
+ return builder.createCoverageActionsWrapper(
+ eventHandler,
+ directories,
+ targetsToTest,
+ baselineCoverageArtifacts,
+ artifactFactory,
+ artifactOwner,
+ workspaceName,
+ this::getArgs,
+ this::getLocationMessage,
+ /*htmlReport=*/ false);
+ }
+
+ private ImmutableList<String> getArgs(CoverageArgs args) {
+ ImmutableList.Builder<String> argsBuilder = ImmutableList.<String>builder().add(
+ args.reportGenerator().getExecutable().getExecPathString(),
+ // A file that contains all the exec paths to the coverage artifacts
+ "--reports_file=" + args.lcovArtifact().getExecPathString(),
+ "--output_file=" + args.lcovOutput().getExecPathString());
+ return argsBuilder.build();
+ }
+
+ private String getLocationMessage(CoverageArgs args) {
+ return "LCOV coverage report is located at " + args.lcovOutput().getPath().getPathString()
+ + "\n and execpath is " + args.lcovOutput().getExecPathString();
+ }
+ };
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/coverage/CoverageArgs.java b/src/main/java/com/google/devtools/build/lib/bazel/coverage/CoverageArgs.java
new file mode 100644
index 0000000000..1e964d08ec
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/coverage/CoverageArgs.java
@@ -0,0 +1,73 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.bazel.coverage;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+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.FilesToRunProvider;
+import com.google.devtools.build.lib.bazel.coverage.CoverageReportActionBuilder.ArgsFunc;
+import com.google.devtools.build.lib.bazel.coverage.CoverageReportActionBuilder.LocationFunc;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import javax.annotation.Nullable;
+
+/**
+ * A value class that holds arguments for
+ * {@link CoverageReportActionBuilder#generateCoverageReportAction}, {@link ArgsFunc} and
+ * {@link LocationFunc}.
+ */
+@AutoValue
+public abstract class CoverageArgs {
+ public abstract BlazeDirectories directories();
+ public abstract ImmutableList<Artifact> coverageArtifacts();
+ public abstract Artifact lcovArtifact();
+ public abstract ArtifactFactory factory();
+ public abstract ArtifactOwner artifactOwner();
+ public abstract FilesToRunProvider reportGenerator();
+ public abstract String workspaceName();
+ public abstract boolean htmlReport();
+ @Nullable
+ public abstract PathFragment coverageDir();
+ @Nullable
+ public abstract Artifact lcovOutput();
+
+ public static CoverageArgs create(
+ BlazeDirectories directories,
+ ImmutableList<Artifact> coverageArtifacts,
+ Artifact lcovArtifact,
+ ArtifactFactory factory,
+ ArtifactOwner artifactOwner,
+ FilesToRunProvider reportGenerator,
+ String workspaceName,
+ boolean htmlReport) {
+ return new AutoValue_CoverageArgs(directories, coverageArtifacts, lcovArtifact, factory,
+ artifactOwner, reportGenerator, workspaceName, htmlReport,
+ /*coverageDir=*/ null,
+ /*lcovOutput=*/ null);
+ }
+
+ public static CoverageArgs createCopyWithCoverageDirAndLcovOutput(
+ CoverageArgs args,
+ PathFragment coverageDir,
+ Artifact lcovOutput) {
+ return new AutoValue_CoverageArgs(
+ args.directories(), args.coverageArtifacts(), args.lcovArtifact(),
+ args.factory(), args.artifactOwner(), args.reportGenerator(), args.workspaceName(),
+ args.htmlReport(), coverageDir, lcovOutput);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/coverage/CoverageReportActionBuilder.java b/src/main/java/com/google/devtools/build/lib/bazel/coverage/CoverageReportActionBuilder.java
new file mode 100644
index 0000000000..5d65e5f06c
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/coverage/CoverageReportActionBuilder.java
@@ -0,0 +1,260 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.bazel.coverage;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.actions.AbstractAction;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.ActionKeyContext;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.ActionResult;
+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.actions.ArtifactRoot;
+import com.google.devtools.build.lib.actions.BaseSpawn;
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.actions.NotifyOnActionCacheHit;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.build.lib.actions.RunfilesSupplier;
+import com.google.devtools.build.lib.actions.Spawn;
+import com.google.devtools.build.lib.actions.SpawnActionContext;
+import com.google.devtools.build.lib.actions.SpawnResult;
+import com.google.devtools.build.lib.analysis.BlazeDirectories;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.FilesToRunProvider;
+import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
+import com.google.devtools.build.lib.analysis.test.CoverageReportActionFactory.CoverageReportActionsWrapper;
+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.analysis.test.TestRunnerAction;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A class to create the coverage report generator action.
+ *
+ * <p>The coverage report action is created after every test shard action is created, at the
+ * very end of the analysis phase. There is only one coverage report action per coverage
+ * command invocation. It can also be viewed as a single sink node of the action graph.
+ *
+ * <p>Its inputs are the individual coverage.dat files from the test outputs (each shard produces
+ * one) and the baseline coverage artifacts. Note that each ConfiguredTarget among the
+ * transitive dependencies of the top level test targets may provide baseline coverage artifacts.
+ *
+ * <p>The coverage report generation can have two phases, though they both run in the same action.
+ * The source code of the coverage report tool {@code lcov_merger} is in the {@code
+ * testing/coverage/lcov_merger} directory. The deployed binaries used by Blaze are under
+ * {@code tools/coverage}.
+ *
+ * <p>The first phase is merging the individual coverage files into a single report file. The
+ * location of this file is reported by Blaze. This phase always happens if the {@code
+ * --combined_report=lcov} or {@code --combined_report=html}.
+ *
+ * <p>The second phase is generating an html report. It only happens if {@code
+ * --combined_report=html}. The action generates an html output file potentially for every
+ * tested source file into the report. Since this set of files is unknown in the analysis
+ * phase (the tool figures it out from the contents of the merged coverage report file)
+ * the action always runs locally when {@code --combined_report=html}.
+ */
+public final class CoverageReportActionBuilder {
+
+ private static final ResourceSet LOCAL_RESOURCES =
+ ResourceSet.createWithRamCpuIo(750 /*MB*/, 0.5 /*CPU*/, 0.0 /*IO*/);
+
+ private static final ActionOwner ACTION_OWNER = ActionOwner.SYSTEM_ACTION_OWNER;
+
+ // SpawnActions can't be used because they need the AnalysisEnvironment and this action is
+ // created specially at the very end of the analysis phase when we don't have it anymore.
+ @Immutable
+ private static final class CoverageReportAction extends AbstractAction
+ implements NotifyOnActionCacheHit {
+ private final ImmutableList<String> command;
+ private final boolean remotable;
+ private final String locationMessage;
+ private final RunfilesSupplier runfilesSupplier;
+
+ protected CoverageReportAction(ActionOwner owner, Iterable<Artifact> inputs,
+ Iterable<Artifact> outputs, ImmutableList<String> command, String locationMessage,
+ boolean remotable, RunfilesSupplier runfilesSupplier) {
+ super(owner, inputs, outputs);
+ this.command = command;
+ this.remotable = remotable;
+ this.locationMessage = locationMessage;
+ this.runfilesSupplier = runfilesSupplier;
+ }
+
+ @Override
+ public ActionResult execute(ActionExecutionContext actionExecutionContext)
+ throws ActionExecutionException, InterruptedException {
+ try {
+ ImmutableMap<String, String> executionInfo = remotable
+ ? ImmutableMap.<String, String>of()
+ : ImmutableMap.<String, String>of("local", "");
+ Spawn spawn = new BaseSpawn(
+ command,
+ ImmutableMap.<String, String>of(),
+ executionInfo,
+ runfilesSupplier,
+ this,
+ LOCAL_RESOURCES);
+ List<SpawnResult> spawnResults =
+ actionExecutionContext.getContext(SpawnActionContext.class)
+ .exec(spawn, actionExecutionContext);
+ actionExecutionContext.getEventHandler().handle(Event.info(locationMessage));
+ return ActionResult.create(spawnResults);
+ } catch (ExecException e) {
+ throw e.toActionExecutionException(
+ "Coverage report generation failed: ",
+ actionExecutionContext.getVerboseFailures(),
+ this);
+ }
+ }
+
+ @Override
+ public String getMnemonic() {
+ return "CoverageReport";
+ }
+
+ @Override
+ protected void computeKey(ActionKeyContext actionKeyContext, Fingerprint fp) {
+ fp.addStrings(command);
+ }
+
+ @Override
+ public void actionCacheHit(ActionCachedContext context) {
+ context.getEventHandler().handle(Event.info(locationMessage));
+ }
+ }
+
+ public CoverageReportActionBuilder() {
+ }
+
+ /**
+ * Returns the coverage report action. May return null in case of an error.
+ */
+ public CoverageReportActionsWrapper createCoverageActionsWrapper(
+ EventHandler reporter,
+ BlazeDirectories directories,
+ Collection<ConfiguredTarget> targetsToTest,
+ Iterable<Artifact> baselineCoverageArtifacts,
+ ArtifactFactory factory,
+ ArtifactOwner artifactOwner,
+ String workspaceName,
+ ArgsFunc argsFunction,
+ LocationFunc locationFunc,
+ boolean htmlReport) {
+
+ if (targetsToTest == null || targetsToTest.isEmpty()) {
+ return null;
+ }
+ ImmutableList.Builder<Artifact> builder = ImmutableList.<Artifact>builder();
+ FilesToRunProvider reportGenerator = null;
+ for (ConfiguredTarget target : targetsToTest) {
+ TestParams testParams = target.getProvider(TestProvider.class).getTestParams();
+ builder.addAll(testParams.getCoverageArtifacts());
+ if (reportGenerator == null) {
+ reportGenerator = testParams.getCoverageReportGenerator();
+ }
+ }
+ builder.addAll(baselineCoverageArtifacts);
+
+ ImmutableList<Artifact> coverageArtifacts = builder.build();
+ if (!coverageArtifacts.isEmpty()) {
+ PathFragment coverageDir = TestRunnerAction.COVERAGE_TMP_ROOT;
+ Artifact lcovArtifact = factory.getDerivedArtifact(
+ coverageDir.getRelative("lcov_files.tmp"),
+ directories.getBuildDataDirectory(workspaceName),
+ artifactOwner);
+ Action lcovFileAction = generateLcovFileWriteAction(lcovArtifact, coverageArtifacts);
+ Action coverageReportAction = generateCoverageReportAction(
+ CoverageArgs.create(directories, coverageArtifacts, lcovArtifact, factory, artifactOwner,
+ reportGenerator, workspaceName, htmlReport),
+ argsFunction, locationFunc);
+ return new CoverageReportActionsWrapper(lcovFileAction, coverageReportAction);
+ } else {
+ reporter.handle(
+ Event.error("Cannot generate coverage report - no coverage information was collected"));
+ return null;
+ }
+ }
+
+ private FileWriteAction generateLcovFileWriteAction(
+ Artifact lcovArtifact, ImmutableList<Artifact>coverageArtifacts) {
+ List<String> filepaths = new ArrayList<>(coverageArtifacts.size());
+ for (Artifact artifact : coverageArtifacts) {
+ filepaths.add(artifact.getExecPathString());
+ }
+ return FileWriteAction.create(
+ ACTION_OWNER,
+ lcovArtifact,
+ Joiner.on('\n').join(filepaths),
+ /*makeExecutable=*/ false,
+ FileWriteAction.Compression.DISALLOW);
+ }
+
+ /**
+ * Computes the arguments passed to the coverage report generator.
+ */
+ @FunctionalInterface
+ public interface ArgsFunc {
+ ImmutableList<String> apply(CoverageArgs args);
+ }
+
+ /**
+ * Computes the location message for the {@link CoverageReportAction}.
+ */
+ @FunctionalInterface
+ public interface LocationFunc {
+ String apply(CoverageArgs args);
+ }
+
+ private CoverageReportAction generateCoverageReportAction(
+ CoverageArgs args,
+ ArgsFunc argsFunc,
+ LocationFunc locationFunc) {
+ ArtifactRoot root = args.directories().getBuildDataDirectory(args.workspaceName());
+ PathFragment coverageDir = TestRunnerAction.COVERAGE_TMP_ROOT;
+ Artifact lcovOutput = args.factory().getDerivedArtifact(
+ coverageDir.getRelative("_coverage_report.dat"), root, args.artifactOwner());
+ Artifact reportGeneratorExec = args.reportGenerator().getExecutable();
+ args = CoverageArgs.createCopyWithCoverageDirAndLcovOutput(args, coverageDir, lcovOutput);
+ ImmutableList<String> actionArgs = argsFunc.apply(args);
+
+ ImmutableList<Artifact> inputs = ImmutableList.<Artifact>builder()
+ .addAll(args.coverageArtifacts())
+ .add(reportGeneratorExec)
+ .add(args.lcovArtifact())
+ .build();
+ return new CoverageReportAction(
+ ACTION_OWNER,
+ inputs,
+ ImmutableList.of(lcovOutput),
+ actionArgs,
+ locationFunc.apply(args),
+ !args.htmlReport(),
+ args.reportGenerator().getRunfilesSupplier());
+ }
+}
diff --git a/src/test/shell/bazel/bazel_coverage_test.sh b/src/test/shell/bazel/bazel_coverage_test.sh
index 41a51a65cf..8235f7cf79 100755
--- a/src/test/shell/bazel/bazel_coverage_test.sh
+++ b/src/test/shell/bazel/bazel_coverage_test.sh
@@ -232,6 +232,94 @@ EOF
fi
}
+function test_java_test_coverage_combined_report() {
+
+ cat <<EOF > BUILD
+java_test(
+ name = "test",
+ srcs = glob(["src/test/**/*.java"]),
+ test_class = "com.example.TestCollatz",
+ deps = [":collatz-lib"],
+)
+
+java_library(
+ name = "collatz-lib",
+ srcs = glob(["src/main/**/*.java"]),
+)
+EOF
+
+ mkdir -p src/main/com/example
+ cat <<EOF > src/main/com/example/Collatz.java
+package com.example;
+
+public class Collatz {
+
+ public static int getCollatzFinal(int n) {
+ if (n == 1) {
+ return 1;
+ }
+ if (n % 2 == 0) {
+ return getCollatzFinal(n / 2);
+ } else {
+ return getCollatzFinal(n * 3 + 1);
+ }
+ }
+
+}
+EOF
+
+ mkdir -p src/test/com/example
+ cat <<EOF > src/test/com/example/TestCollatz.java
+package com.example;
+
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
+public class TestCollatz {
+
+ @Test
+ public void testGetCollatzFinal() {
+ assertEquals(Collatz.getCollatzFinal(1), 1);
+ assertEquals(Collatz.getCollatzFinal(5), 1);
+ assertEquals(Collatz.getCollatzFinal(10), 1);
+ assertEquals(Collatz.getCollatzFinal(21), 1);
+ }
+
+}
+EOF
+
+ bazel coverage //:test --coverage_report_generator=@bazel_tools//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:Main --combined_report=lcov &>$TEST_log \
+ || echo "Coverage for //:test failed"
+
+ cat <<EOF > result.dat
+SF:com/example/Collatz.java
+FN:3,com/example/Collatz::<init> ()V
+FN:6,com/example/Collatz::getCollatzFinal (I)I
+FNDA:0,com/example/Collatz::<init> ()V
+FNDA:1,com/example/Collatz::getCollatzFinal (I)I
+FNF:2
+FNH:1
+BA:6,2
+BA:9,2
+BRF:2
+BRH:2
+DA:3,0
+DA:6,3
+DA:7,2
+DA:9,4
+DA:10,5
+DA:12,7
+LH:5
+LF:6
+end_of_record
+EOF
+
+ if ! cmp result.dat ./bazel-out/_coverage/_coverage_report.dat; then
+ diff result.dat bazel-out/_coverage/_coverage_report.dat >> $TEST_log
+ fail "Coverage output file is different with expected"
+ fi
+}
+
function test_java_test_java_import_coverage() {
cat <<EOF > BUILD
diff --git a/tools/test/BUILD b/tools/test/BUILD
index 3f7d28b72a..4a4e56b284 100644
--- a/tools/test/BUILD
+++ b/tools/test/BUILD
@@ -24,7 +24,7 @@ filegroup(
filegroup(
name = "coverage_report_generator",
- srcs = ["//tools/coverage:coverage_report_generator"],
+ srcs = ["@bazel_tools//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:Main"],
)
filegroup(
diff --git a/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/BUILD.tools b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/BUILD.tools
index 37cf8e4657..29a60f0fef 100644
--- a/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/BUILD.tools
+++ b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/BUILD.tools
@@ -7,6 +7,7 @@ java_import(
java_binary(
name = "Main",
+ create_executable = 1,
main_class = "com.google.devtools.lcovmerger.Main",
runtime_deps = [":all_lcov_merger_lib"],
)
diff --git a/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/Main.java b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/Main.java
index 5bac98e80d..2984d7b274 100644
--- a/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/Main.java
+++ b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/Main.java
@@ -15,12 +15,15 @@
package com.google.devtools.lcovmerger;
import static com.google.devtools.lcovmerger.LcovConstants.TRACEFILE_EXTENSION;
+import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.annotations.VisibleForTesting;
+import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -48,14 +51,23 @@ public class Main {
System.exit(1);
}
- List<File> lcovTracefiles = getLcovTracefiles(flags.get("coverage_dir"));
+ List<File> lcovTracefiles = new ArrayList<>();
+ if (flags.containsKey("coverage_dir")) {
+ logger.log(Level.SEVERE, "Retrieving tracefiles from coverage_dir.");
+ lcovTracefiles = getLcovTracefilesFromDir(flags.get("coverage_dir"));
+ } else if (flags.containsKey("reports_file")) {
+ logger.log(Level.SEVERE, "Retrieving tracefiles from reports_file.");
+ lcovTracefiles = getLcovTracefilesFromFile(flags.get("reports_file"));
+ }
if (lcovTracefiles.isEmpty()) {
logger.log(Level.SEVERE, "No lcov file found.");
System.exit(1);
}
+ logger.log(Level.SEVERE, "Found " + lcovTracefiles.size() + " tracefiles.");
Coverage coverage = new Coverage();
for (File tracefile : lcovTracefiles) {
try {
+ logger.log(Level.SEVERE, "Parsing tracefile " + tracefile.toString());
List<SourceFileCoverage> sourceFilesCoverage =
LcovParser.parse(new FileInputStream(tracefile));
for (SourceFileCoverage sourceFileCoverage : sourceFilesCoverage) {
@@ -72,7 +84,8 @@ public class Main {
File coverageFile = new File(outputFile);
LcovPrinter.print(new FileOutputStream(coverageFile), coverage);
} catch (IOException e) {
- logger.log(Level.SEVERE, "Could not write to output file " + outputFile);
+ logger.log(Level.SEVERE,
+ "Could not write to output file " + outputFile + " due to " + e.getMessage());
exitStatus = 1;
}
System.exit(exitStatus);
@@ -83,19 +96,37 @@ public class Main {
* directory.
*/
@VisibleForTesting
- static List<File> getLcovTracefiles(String coverageDir) {
+ static List<File> getLcovTracefilesFromDir(String coverageDir) {
List<File> datFiles = new ArrayList<>();
try (Stream<Path> stream = Files.walk(Paths.get(coverageDir))) {
datFiles = stream.filter(p -> p.toString().endsWith(TRACEFILE_EXTENSION))
.map(path -> path.toFile())
.collect(Collectors.toList());
} catch (IOException ex) {
- logger.log(Level.SEVERE, "error reading folder " + coverageDir + ": " + ex.getMessage());
+ logger.log(Level.SEVERE, "Error reading folder " + coverageDir + ": " + ex.getMessage());
}
return datFiles;
}
+ static List<File> getLcovTracefilesFromFile(String reportsFile) {
+ List<File> datFiles = new ArrayList<>();
+ try (FileInputStream inputStream = new FileInputStream(reportsFile)) {
+ InputStreamReader inputStreamReader = new InputStreamReader(inputStream, UTF_8);
+ BufferedReader reader = new BufferedReader(inputStreamReader);
+ for (String tracefile = reader.readLine(); tracefile != null; tracefile = reader.readLine()) {
+ // TODO(elenairina): baseline coverage contains some file names that need to be modified
+ if (!tracefile.endsWith("baseline_coverage.dat")) {
+ datFiles.add(new File(tracefile));
+ }
+ }
+
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, "Error reading file " + reportsFile + ": " + e.getMessage());
+ }
+ return datFiles;
+ }
+
/**
* Parse flags in the form of "--coverage_dir=... -output_file=..."
*/
@@ -114,12 +145,28 @@ public class Main {
}
// Validate flags
- if (!flags.containsKey("coverage_dir")) {
- throw new IllegalArgumentException("coverage_dir was not specified");
+ for (String flag : flags.keySet()) {
+ switch (flag) {
+ case "coverage_dir":
+ case "reports_file":
+ case "output_file":
+ continue;
+ default:
+ throw new IllegalArgumentException("Unknown flag --" + flag);
+ }
+ }
+
+ if (!flags.containsKey("coverage_dir") && !flags.containsKey("reports_file")) {
+ throw new IllegalArgumentException(
+ "At least one of --coverage_dir or --reports_file should be specified.");
+ }
+ if (flags.containsKey("coverage_dir") && flags.containsKey("reports_file")) {
+ throw new IllegalArgumentException(
+ "Only one of --coverage_dir or --reports_file must be specified.");
}
if (!flags.containsKey("output_file")) {
// Different from blaze, this should be mandatory
- throw new IllegalArgumentException("output_file was not specified");
+ throw new IllegalArgumentException("--output_file was not specified");
}
return flags;
diff --git a/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/MainTest.java b/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/MainTest.java
index 07be4215e5..a6b99fb12e 100644
--- a/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/MainTest.java
+++ b/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/MainTest.java
@@ -41,7 +41,7 @@ public class MainTest {
@Test
public void testMainEmptyCoverageDir() {
- assertThat(Main.getLcovTracefiles(coverageDir.toAbsolutePath().toString())).isEmpty();
+ assertThat(Main.getLcovTracefilesFromDir(coverageDir.toAbsolutePath().toString())).isEmpty();
}
@Test
@@ -52,7 +52,7 @@ public class MainTest {
Files.createTempFile(ccCoverageDir, "tracefile1", ".dat");
Files.createTempFile(javaCoverageDir, "tracefile2", ".dat");
- List<File> tracefiles = Main.getLcovTracefiles(coverageDir.toAbsolutePath().toString());
+ List<File> tracefiles = Main.getLcovTracefilesFromDir(coverageDir.toAbsolutePath().toString());
assertThat(tracefiles).hasSize(2);
}
}