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 6ad0617071e30a..ebffbf8e423eb5 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 @@ -132,7 +132,6 @@ public Object getDefault(AttributeMap rule) { env.getToolsLabel("//tools/test:runtime")))) // Input files for test actions collecting code coverage .add(attr("$coverage_support", LABEL) - .cfg(HOST) .value(env.getLabel("//tools/defaults:coverage_support"))) // Used in the one-per-build coverage report generation action. .add(attr("$coverage_report_generator", LABEL) diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java index 58ea07a541d580..4c14c73b5bac13 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java @@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.FileProvider; import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; import com.google.devtools.build.lib.analysis.RuleContext; @@ -27,19 +28,19 @@ import com.google.devtools.build.lib.analysis.RunfilesProvider; import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; -import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.Builder; import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction; import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.ComputedSubstitution; import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Substitution; import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Template; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.bazel.rules.BazelConfiguration; -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.packages.BuildType; import com.google.devtools.build.lib.rules.java.DeployArchiveBuilder; import com.google.devtools.build.lib.rules.java.DeployArchiveBuilder.Compression; import com.google.devtools.build.lib.rules.java.JavaCommon; +import com.google.devtools.build.lib.rules.java.JavaCompilationArgsProvider; import com.google.devtools.build.lib.rules.java.JavaCompilationArtifacts; import com.google.devtools.build.lib.rules.java.JavaCompilationHelper; import com.google.devtools.build.lib.rules.java.JavaConfiguration; @@ -53,6 +54,7 @@ import com.google.devtools.build.lib.rules.java.Jvm; import com.google.devtools.build.lib.rules.java.proto.GeneratedExtensionRegistryProvider; import com.google.devtools.build.lib.syntax.Type; +import com.google.devtools.build.lib.util.FileType; import com.google.devtools.build.lib.util.OS; import com.google.devtools.build.lib.util.Preconditions; import com.google.devtools.build.lib.util.ShellEscaper; @@ -79,6 +81,9 @@ public class BazelJavaSemantics implements JavaSemantics { private static final String JAVABUILDER_CLASS_NAME = "com.google.devtools.build.buildjar.BazelJavaBuilder"; + private static final String JACOCO_COVERAGE_RUNNER_MAIN_CLASS = + "com.google.testing.coverage.JacocoCoverageRunner"; + private BazelJavaSemantics() { } @@ -158,17 +163,6 @@ public ImmutableList collectResources(RuleContext ruleContext) { return ruleContext.getPrerequisiteArtifacts("resources", Mode.TARGET).list(); } - @Override - public Artifact createInstrumentationMetadataArtifact( - RuleContext ruleContext, Artifact outputJar) { - return null; - } - - @Override - public void buildJavaCommandLine(Collection outputs, BuildConfiguration configuration, - Builder result, Label targetLabel) { - } - @Override public Artifact createStubAction( RuleContext ruleContext, @@ -208,6 +202,20 @@ public String getValue() { } }); + JavaCompilationArtifacts javaArtifacts = javaCommon.getJavaCompilationArtifacts(); + String path = + javaArtifacts.getInstrumentedJar() != null + ? "${JAVA_RUNFILES}/" + + workspacePrefix + + javaArtifacts.getInstrumentedJar().getRootRelativePath().getPathString() + : ""; + arguments.add( + Substitution.of( + "%set_jacoco_metadata%", + ruleContext.getConfiguration().isCodeCoverageEnabled() + ? "export JACOCO_METADATA_JAR=" + path + : "")); + arguments.add(Substitution.of("%java_start_class%", ShellEscaper.escapeString(javaStartClass))); arguments.add(Substitution.ofSpaceSeparatedList("%jvm_flags%", ImmutableList.copyOf(jvmFlags))); @@ -415,12 +423,79 @@ public Iterable getJvmFlags( return jvmFlags.build(); } + /** + * Returns whether coverage has instrumented artifacts. + */ + public static boolean hasInstrumentationMetadata(JavaTargetAttributes.Builder attributes) { + return !attributes.getInstrumentationMetadata().isEmpty(); + } + + // TODO(yueg): refactor this (only mainClass different for now) @Override - public String addCoverageSupport(JavaCompilationHelper helper, + public String addCoverageSupport( + JavaCompilationHelper helper, JavaTargetAttributes.Builder attributes, - Artifact executable, Artifact instrumentationMetadata, - JavaCompilationArtifacts.Builder javaArtifactsBuilder, String mainClass) { - return mainClass; + Artifact executable, + Artifact instrumentationMetadata, + JavaCompilationArtifacts.Builder javaArtifactsBuilder, + String mainClass) + throws InterruptedException { + // This method can be called only for *_binary/*_test targets. + Preconditions.checkNotNull(executable); + // Add our own metadata artifact (if any). + if (instrumentationMetadata != null) { + attributes.addInstrumentationMetadataEntries(ImmutableList.of(instrumentationMetadata)); + } + + if (!hasInstrumentationMetadata(attributes)) { + return mainClass; + } else { + Artifact instrumentedJar = + helper + .getRuleContext() + .getBinArtifact(helper.getRuleContext().getLabel().getName() + "_instrumented.jar"); + + // Create an instrumented Jar. This will be referenced on the runtime classpath prior + // to all other Jars. + JavaCommon.createInstrumentedJarAction( + helper.getRuleContext(), + this, + attributes.getInstrumentationMetadata(), + instrumentedJar, + mainClass); + javaArtifactsBuilder.setInstrumentedJar(instrumentedJar); + + // Add the coverage runner to the list of dependencies when compiling in coverage mode. + TransitiveInfoCollection runnerTarget = + helper.getRuleContext().getPrerequisite("$jacocorunner", Mode.TARGET); + if (runnerTarget.getProvider(JavaCompilationArgsProvider.class) != null) { + helper.addLibrariesToAttributes(ImmutableList.of(runnerTarget)); + } else { + helper + .getRuleContext() + .ruleError( + "this rule depends on " + + helper.getRuleContext().attributes().get("$jacocorunner", BuildType.LABEL) + + " which is not a java_library rule, or contains errors"); + } + // In offline instrumentation mode, add the Jacoco runtime to the classpath as well (this + // jar is not included by the coverage runner). Note that $jacoco is provided via a + // filegroup because the same jar can be used for online instrumentation, by simply adding + // it to -javaagent and -Xbootclasspath/p (similar to the Reverifier setup). The $jacoco jar + // has a "Premain-Class:" entry in its manifest, which would get erased by ijar filtering, + // hence the filegroup. + TransitiveInfoCollection agentTarget = + helper.getRuleContext().getPrerequisite("$jacoco_runtime", Mode.TARGET); + NestedSet filesToBuild = + agentTarget.getProvider(FileProvider.class).getFilesToBuild(); + for (Artifact jar : FileType.filter(filesToBuild, JavaSemantics.JAR)) { + attributes.addRuntimeClassPathEntry(jar); + } + } + + // We do not add the instrumented jar to the runtime classpath, but provide it in the shell + // script via an environment variable. + return JACOCO_COVERAGE_RUNNER_MAIN_CLASS; } @Override diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaTestRule.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaTestRule.java index fb990f448ffd93..ac294bd3d90594 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaTestRule.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaTestRule.java @@ -58,6 +58,19 @@ public RuleClass build(Builder builder, RuleDefinitionEnvironment env) { .override(attr("stamp", TRISTATE).value(TriState.NO)) .override(attr("use_testrunner", BOOLEAN).value(true)) .override(attr(":java_launcher", LABEL).value(JavaSemantics.JAVA_LAUNCHER)) + // Input files for test actions collecting code coverage + .add( + attr("$lcov_merger", LABEL) + .value(env.getLabel("@bazel_tools//tools/test:LcovMerger_deploy.jar"))) + // Used in the one-per-build coverage report generation action. + .add( + attr("$jacoco_runtime", LABEL) + .value(env.getLabel("@bazel_tools//third_party/java/jacoco:blaze-agent"))) + .add( + attr("$jacocorunner", LABEL) + .value( + env.getLabel( + "@bazel_tools//src/java_tools/junitrunner/java/com/google/testing/coverage:JacocoCoverage"))) /* The Java class to be loaded by the test runner.

diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/java_stub_template.txt b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/java_stub_template.txt index f77051fb2c516d..df667e4c71aa06 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/java_stub_template.txt +++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/java_stub_template.txt @@ -207,6 +207,11 @@ else CLASSPATH=%classpath% fi +# If using Jacoco in offline instrumentation mode, the CLASSPATH contains instrumented files. +# We need to make the metadata jar with uninstrumented classes available for generating +# the lcov-compatible coverage report, and we don't want it on the classpath. +%set_jacoco_metadata% + if [[ -n "$JVM_DEBUG_PORT" ]]; then JVM_DEBUG_SUSPEND=${DEFAULT_JVM_DEBUG_SUSPEND:-"y"} JVM_DEBUG_FLAGS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=${JVM_DEBUG_SUSPEND},address=${JVM_DEBUG_PORT}" diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java index 77e2b02a138ef7..b5f2dae458a41f 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java @@ -216,6 +216,20 @@ public ImmutableList getBootclasspathOrDefault() { } } + /** + * Returns the instrumentation metadata files to be generated for a given output jar. + * + *

Only called if the output jar actually needs to be instrumented. + */ + @Nullable + private static Artifact createInstrumentationMetadataArtifact( + RuleContext ruleContext, Artifact outputJar) { + PathFragment packageRelativePath = outputJar.getRootRelativePath().relativeTo( + ruleContext.getPackageDirectory()); + return ruleContext.getPackageRelativeArtifact( + FileSystemUtils.replaceExtension(packageRelativePath, ".em"), outputJar.getRoot()); + } + /** * Creates the Action that compiles Java source files and optionally instruments them for * coverage. @@ -246,13 +260,14 @@ public void createCompileActionWithInstrumentation( * @return the instrumentation metadata artifact or null if instrumentation is * disabled */ + @Nullable public Artifact createInstrumentationMetadata(Artifact outputJar, JavaCompilationArtifacts.Builder javaArtifactsBuilder) { // If we need to instrument the jar, add additional output (the coverage metadata file) to the // JavaCompileAction. Artifact instrumentationMetadata = null; if (shouldInstrumentJar()) { - instrumentationMetadata = semantics.createInstrumentationMetadataArtifact( + instrumentationMetadata = createInstrumentationMetadataArtifact( getRuleContext(), outputJar); if (instrumentationMetadata != null) { diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileAction.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileAction.java index d1cc48ec1e4ea0..6d55d4e7e8fb4b 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileAction.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileAction.java @@ -89,6 +89,8 @@ */ @ThreadCompatible @Immutable public final class JavaCompileAction extends AbstractAction { + private static final String JACOCO_INSTRUMENTATION_PROCESSOR = "jacoco"; + private static final String GUID = "786e174d-ed97-4e79-9f61-ae74430714cf"; private static final ResourceSet LOCAL_RESOURCES = @@ -994,6 +996,36 @@ public Artifact create(PathFragment rootRelativePath, Root root) { ruleContext.getConfiguration(), semantics); } + /** + * May add extra command line options to the Java compile command line. + */ + private static void buildJavaCommandLine( + Collection outputs, + BuildConfiguration configuration, + CustomCommandLine.Builder result, + Label targetLabel) { + Artifact metadata = null; + for (Artifact artifact : outputs) { + if (artifact.getExecPathString().endsWith(".em")) { + metadata = artifact; + break; + } + } + + if (metadata == null) { + return; + } + + result.add("--post_processor"); + result.addExecPath(JACOCO_INSTRUMENTATION_PROCESSOR, metadata); + result.addPath( + configuration + .getCoverageMetadataDirectory(targetLabel.getPackageIdentifier().getRepository()) + .getExecPath()); + result.add("-*Test"); + result.add("-*TestCase"); + } + public JavaCompileAction build() { // TODO(bazel-team): all the params should be calculated before getting here, and the various // aggregation code below should go away. @@ -1094,7 +1126,7 @@ public JavaCompileAction build() { strictJavaDeps, compileTimeDependencyArtifacts ); - semantics.buildJavaCommandLine( + buildJavaCommandLine( outputs, configuration, paramFileContentsBuilder, targetLabel); CommandLine paramFileContents = paramFileContentsBuilder.build(); Action parameterFileWriteAction = new ParameterFileWriteAction(owner, paramFile, diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java index 0e45fbbf907da3..c336ba25fcafe7 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java @@ -233,19 +233,6 @@ void checkForProtoLibraryAndJavaProtoLibraryOnSameProto( */ ImmutableList collectResources(RuleContext ruleContext); - /** - * Creates the instrumentation metadata artifact for the specified output .jar . - */ - @Nullable - Artifact createInstrumentationMetadataArtifact(RuleContext ruleContext, Artifact outputJar); - - /** - * May add extra command line options to the Java compile command line. - */ - void buildJavaCommandLine(Collection outputs, BuildConfiguration configuration, - CustomCommandLine.Builder result, Label targetLabel); - - /** * Constructs the command line to call SingleJar to join all artifacts from * {@code classpath} (java code) and {@code resources} into {@code output}. diff --git a/src/main/java/com/google/devtools/build/lib/rules/test/StandaloneTestStrategy.java b/src/main/java/com/google/devtools/build/lib/rules/test/StandaloneTestStrategy.java index 85c8ccf3e37af4..bbef175a7dc588 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/test/StandaloneTestStrategy.java +++ b/src/main/java/com/google/devtools/build/lib/rules/test/StandaloneTestStrategy.java @@ -35,9 +35,8 @@ import com.google.devtools.build.lib.events.EventKind; import com.google.devtools.build.lib.events.Reporter; import com.google.devtools.build.lib.util.io.FileOutErr; -import com.google.devtools.build.lib.vfs.FileSystemUtils; import com.google.devtools.build.lib.vfs.Path; -import com.google.devtools.build.lib.vfs.Symlinks; +import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.lib.view.test.TestStatus.BlazeTestStatus; import com.google.devtools.build.lib.view.test.TestStatus.TestCase; import com.google.devtools.build.lib.view.test.TestStatus.TestResultData; @@ -53,8 +52,8 @@ @ExecutionStrategy(contextType = TestActionContext.class, name = { "standalone" }) public class StandaloneTestStrategy extends TestStrategy { // TODO(bazel-team) - add tests for this strategy. - private static final String COLLECT_COVERAGE = - "external/bazel_tools/tools/coverage/collect-coverage.sh"; + public static final String COLLECT_COVERAGE = + "external/bazel_tools/tools/test/collect_coverage.sh"; private final Path workspace; @@ -70,6 +69,9 @@ public StandaloneTestStrategy( @Override public void exec(TestRunnerAction action, ActionExecutionContext actionExecutionContext) throws ExecException, InterruptedException { + Path execRoot = actionExecutionContext.getExecutor().getExecRoot(); + Path coverageDir = execRoot.getRelative(TestStrategy.getCoverageDirectory(action)); + Path runfilesDir = null; try { runfilesDir = @@ -83,12 +85,11 @@ public void exec(TestRunnerAction action, ActionExecutionContext actionExecution throw new TestExecException(e.getMessage()); } - Path testTmpDir = TestStrategy.getTmpRoot( - workspace, actionExecutionContext.getExecutor().getExecRoot(), executionOptions) - .getChild(getTmpDirName(action.getExecutionSettings().getExecutable().getExecPath())); + Path testTmpDir = + TestStrategy.getTmpRoot(workspace, execRoot, executionOptions) + .getChild(getTmpDirName(action.getExecutionSettings().getExecutable().getExecPath())); Path workingDirectory = runfilesDir.getRelative(action.getRunfilesPrefix()); - Path execRoot = actionExecutionContext.getExecutor().getExecRoot(); TestRunnerAction.ResolvedPaths resolvedPaths = action.resolve(execRoot); Map env = getEnv(action, execRoot, runfilesDir, testTmpDir, resolvedPaths.getXmlOutputPath()); @@ -114,17 +115,7 @@ public void exec(TestRunnerAction action, ActionExecutionContext actionExecution Executor executor = actionExecutionContext.getExecutor(); try { - if (testTmpDir.exists(Symlinks.NOFOLLOW)) { - FileSystemUtils.deleteTree(testTmpDir); - } - FileSystemUtils.createDirectoryAndParents(testTmpDir); - } catch (IOException e) { - executor.getEventHandler().handle(Event.error("Could not create TEST_TMPDIR: " + e)); - throw new EnvironmentalExecException("Could not create TEST_TMPDIR " + testTmpDir, e); - } - - try { - FileSystemUtils.createDirectoryAndParents(workingDirectory); + prepareFileSystem(action, testTmpDir, coverageDir, workingDirectory); ResourceSet resources = action.getTestProperties().getLocalResourceUsage(executionOptions.usingLocalTestJobs()); @@ -172,13 +163,13 @@ private Map getEnv( if (!action.isEnableRunfiles()) { vars.put("RUNFILES_MANIFEST_ONLY", "1"); } + + PathFragment coverageDir = TestStrategy.getCoverageDirectory(action); if (isCoverageMode(action)) { - vars.put("COVERAGE_MANIFEST", - action.getExecutionSettings().getInstrumentedFileManifest().getExecPathString()); + vars.put("COVERAGE_DIR", coverageDir.toString()); vars.put("COVERAGE_OUTPUT_FILE", action.getCoverageData().getExecPathString()); - // Instruct test-setup.sh not to cd into the runfiles directory. - vars.put("RUNTEST_PRESERVE_CWD", "1"); } + return vars; } @@ -231,6 +222,10 @@ private TestResultData execute( if (details != null) { builder.setTestCase(details); } + + if (isCoverageMode(action)) { + builder.setHasCoverage(true); + } return builder.build(); } catch (IOException e) { diff --git a/src/main/java/com/google/devtools/build/lib/rules/test/TestActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/test/TestActionBuilder.java index d71986193bde62..b7becb4eaf6882 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/test/TestActionBuilder.java +++ b/src/main/java/com/google/devtools/build/lib/rules/test/TestActionBuilder.java @@ -14,6 +14,8 @@ package com.google.devtools.build.lib.rules.test; +import static com.google.devtools.build.lib.packages.BuildType.LABEL; + import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.devtools.build.lib.actions.Artifact; @@ -36,11 +38,9 @@ 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; /** @@ -207,7 +207,17 @@ private TestParams createTestAction(int shards) { NestedSet metadataFiles = instrumentedFiles.getInstrumentationMetadataFiles(); inputsBuilder.addTransitive(metadataFiles); inputsBuilder.addTransitive(PrerequisiteArtifacts.nestedSet( - ruleContext, "$coverage_support", Mode.HOST)); + ruleContext, "$coverage_support", Mode.DONT_CHECK)); + // We don't add this attribute to non-supported test target + if (ruleContext.isAttrDefined("$lcov_merger", LABEL)) { + Artifact lcovMerger = ruleContext.getPrerequisiteArtifact("$lcov_merger", Mode.TARGET); + if (lcovMerger != null) { + inputsBuilder.addTransitive( + PrerequisiteArtifacts.nestedSet(ruleContext, "$lcov_merger", Mode.TARGET)); + // Pass this LcovMerger_deploy.jar path to collect_coverage.sh + testEnv.put("LCOV_MERGER", lcovMerger.getExecPathString()); + } + } Artifact instrumentedFileManifest = InstrumentedFileManifestAction.getInstrumentedFileManifest(ruleContext, diff --git a/src/main/java/com/google/devtools/build/lib/rules/test/TestStrategy.java b/src/main/java/com/google/devtools/build/lib/rules/test/TestStrategy.java index 7590ef1d2b90ec..ee6d67098a3137 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/test/TestStrategy.java +++ b/src/main/java/com/google/devtools/build/lib/rules/test/TestStrategy.java @@ -58,6 +58,8 @@ * A strategy for executing a {@link TestRunnerAction}. */ public abstract class TestStrategy implements TestActionContext { + public static final PathFragment COVERAGE_TMP_ROOT = new PathFragment("_coverage"); + public static final String TEST_SETUP_BASENAME = "test-setup.sh"; /** @@ -168,6 +170,27 @@ public TestStrategy( public abstract void exec(TestRunnerAction action, ActionExecutionContext actionExecutionContext) throws ExecException, InterruptedException; + /** Returns true if coverage data should be gathered. */ + protected static boolean isMicroCoverageMode(TestRunnerAction action) { + return action.getMicroCoverageData() != null; + } + + /** + * Returns directory to 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. + * + *

Coverage directory name for the given test runner action is constructed as: {@code $(blaze + * info execution_root)/_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). + */ + protected static PathFragment getCoverageDirectory(TestRunnerAction action) { + return COVERAGE_TMP_ROOT.getRelative( + FileSystemUtils.removeExtension(action.getTestLog().getRootRelativePath())); + } + /** * Returns mutable map of default testing shell environment. By itself it is incomplete and is * modified further by the specific test strategy implementations (mostly due to the fact that @@ -198,6 +221,15 @@ protected Map getDefaultTestEnvironment(TestRunnerAction action) env.put(TEST_BRIDGE_TEST_FILTER_ENV, testFilter); } + if (isCoverageMode(action)) { + env.put( + "COVERAGE_MANIFEST", + action.getExecutionSettings().getInstrumentedFileManifest().getExecPathString()); + // Instruct remote-runtest.sh/local-runtest.sh not to cd into the runfiles directory. + env.put("RUNTEST_PRESERVE_CWD", "1"); + env.put("MICROCOVERAGE_REQUESTED", isMicroCoverageMode(action) ? "true" : "false"); + } + return env; } diff --git a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java index 395ce3d04d642a..d00591172e6335 100644 --- a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java +++ b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java @@ -131,10 +131,51 @@ public void setupMockClient(MockToolsConfig config) throws IOException { "java_import(name = 'jarjar_import',", " jars = [ 'jarjar.jar' ])"); + config.create( + "/bazel_tools_workspace/src/java_tools/junitrunner/java/com/google/testing/coverage/BUILD", + "java_binary(name = 'JacocoCoverage', srcs = [", + "'BranchCoverageDetail.java',", + "'BranchDetailAnalyzer.java',", + "'BranchExp.java',", + "'ClassProbesMapper.java',", + "'CovExp.java',", + "'JacocoCoverageRunner.java',", + "'JacocoLCOVFormatter.java',", + "'MethodProbesMapper.java',", + "'ProbeExp.java',],", + "deps = [", + "':bitfield',", + "'//third_party/java/jacoco:blaze-agent-neverlink',", + "'//third_party/java/jacoco:core',", + "'//third_party/java/jacoco:report'],)", + "java_library(name = 'bitfield', srcs = ['BitField.java', 'IllegalStringException.java'],", + "deps = ['//third_party:apache_commons_lang'])"); + config.create( + "/bazel_tools_workspace/third_party/java/jacoco/BUILD", + "package(default_visibility=['//visibility:public'])", + "licenses(['notice'])", + "java_import(name = 'blaze-agent', jars = ['jacocoagent.jar'],)", + "java_import(name = 'blaze-agent-neverlink', jars = ['jacocoagent.jar'], neverlink = 1)", + "java_import(name = 'core', jars = ['org.jacoco.core-0.7.5.201505241946.jar'],", + "srcjar = 'org.jacoco.core-0.7.5.201505241946-src.jar', exports = [", + "'//third_party:asm', '//third_party:asm-commons', '//third_party:asm-tree'])", + "java_import(name = 'report', jars = ['org.jacoco.report-0.7.5.201505241946.jar'],", + "srcjar = 'org.jacoco.report-0.7.5.201505241946-src.jar', exports = [", + "':core', '//third_party:asm'])"); + config.create( + "/bazel_tools_workspace/third_party/BUILD", + "java_import(name = 'asm', jars = ['asm/asm-5.0.4.jar'])", + "java_import(name = 'asm-commons', jars = ['asm/asm-commons-5.0.4.jar'])", + "java_import(name = 'asm-tree', jars = ['asm/asm-tree-5.0.4.jar'])", + "java_import(name='apache_commons_lang',jars=['apache_commons_lang/commons-lang-2.6.jar'])"); + config.create("/bazel_tools_workspace/tools/test/BUILD", - "filegroup(name = 'runtime')", - "filegroup(name = 'coverage_support')", + "filegroup(name = 'runtime', srcs = ['test-setup.sh'],)", + "filegroup(name='coverage_support', srcs=['collect_coverage.sh',':LcovMerger_deploy.jar'])", "filegroup(name = 'coverage_report_generator', srcs = ['coverage_report_generator.sh'])"); +// "java_binary(name = 'LcovMerger'," +// + "srcs = glob(['LcovMerger/**/*.java'])," +// + "main_class = 'com.google.devtools.lcovmerger.Main')"); config.create( "/bazel_tools_workspace/tools/python/BUILD", diff --git a/src/test/shell/bazel/BUILD b/src/test/shell/bazel/BUILD index ca1f882cbc487c..e7cf472feb6676 100644 --- a/src/test/shell/bazel/BUILD +++ b/src/test/shell/bazel/BUILD @@ -104,6 +104,13 @@ sh_test( tags = ["local"], ) +sh_test( + name = "bazel_coverage_test", + srcs = ["bazel_coverage_test.sh"], + data = [":test-deps"], + tags = ["local"], +) + sh_test( name = "bazel_localtest_test", srcs = ["bazel_localtest_test.sh"], diff --git a/src/test/shell/bazel/bazel_coverage_test.sh b/src/test/shell/bazel/bazel_coverage_test.sh new file mode 100755 index 00000000000000..d2e710025604a4 --- /dev/null +++ b/src/test/shell/bazel/bazel_coverage_test.sh @@ -0,0 +1,111 @@ +#!/bin/bash +# +# 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. + +set -eu + +# Load the test setup defined in the parent directory +CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${CURRENT_DIR}/../integration_test_setup.sh" \ + || { echo "integration_test_setup.sh not found!" >&2; exit 1; } + +function test_java_test_coverage() { + mkdir java_test + + cat < java_test/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 java_test/src/main/com/example + cat < java_test/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 java_test/src/test/com/example + cat < java_test/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 //java_test:test &>$TEST_log || fail "Coverage for //java_test:test failed" + + ending_part=$(sed -n -e '/PASSED/,$p' $TEST_log) + coverage_file_path=$(grep -Eo "/[/a-zA-Z0-9\.\_\-]+\.dat$" <<< "$ending_part") + [ -e $coverage_file_path ] || fail "Coverage output file not exists!" + + cat < java_test/result.dat +SF:com/example/Collatz.java +FN:3,com/example/Collatz:: ()V +FNDA:0,com/example/Collatz:: ()V +FN:6,com/example/Collatz::getCollatzFinal (I)I +FNDA:1,com/example/Collatz::getCollatzFinal (I)I +BA:6,2 +BA:6,2 +BA:9,2 +BA:9,2 +DA:3,0 +DA:6,3 +DA:7,2 +DA:9,4 +DA:10,5 +DA:12,7 +end_of_record +EOF + + if ! cmp java_test/result.dat $coverage_file_path; then + fail "Coverage output file is different with expected" + fi +} + +run_suite "test tests"