Skip to content

Commit

Permalink
Add coverage support for java test.
Browse files Browse the repository at this point in the history
(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.
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit af878d0

Please sign in to comment.