Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add coverage support for java test.
(series 4/4 of open-sourcing coverage command for java test)

--
PiperOrigin-RevId: 141292977
MOS_MIGRATED_REVID=141292977
  • Loading branch information
hermione521 authored and laszlocsomor committed Dec 7, 2016
1 parent b9aadcb commit af878d0
Show file tree
Hide file tree
Showing 13 changed files with 383 additions and 61 deletions.
Expand Up @@ -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)
Expand Down
Expand Up @@ -20,26 +20,27 @@
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;
import com.google.devtools.build.lib.analysis.Runfiles;
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;
Expand All @@ -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;
Expand All @@ -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() {
}

Expand Down Expand Up @@ -158,17 +163,6 @@ public ImmutableList<Artifact> 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<Artifact> outputs, BuildConfiguration configuration,
Builder result, Label targetLabel) {
}

@Override
public Artifact createStubAction(
RuleContext ruleContext,
Expand Down Expand Up @@ -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)));
Expand Down Expand Up @@ -415,12 +423,79 @@ public Iterable<String> 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<Artifact> 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
Expand Down
Expand Up @@ -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")))
/* <!-- #BLAZE_RULE(java_test).ATTRIBUTE(test_class) -->
The Java class to be loaded by the test runner.<br/>
<p>
Expand Down
Expand Up @@ -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}"
Expand Down
Expand Up @@ -216,6 +216,20 @@ public ImmutableList<Artifact> getBootclasspathOrDefault() {
}
}

/**
* Returns the instrumentation metadata files to be generated for a given output jar.
*
* <p>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.
Expand Down Expand Up @@ -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) {
Expand Down
Expand Up @@ -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 =
Expand Down Expand Up @@ -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<Artifact> 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.
Expand Down Expand Up @@ -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,
Expand Down
Expand Up @@ -233,19 +233,6 @@ void checkForProtoLibraryAndJavaProtoLibraryOnSameProto(
*/
ImmutableList<Artifact> 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<Artifact> 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}.
Expand Down

0 comments on commit af878d0

Please sign in to comment.