Skip to content

Commit

Permalink
Use our java test runner in Bazel
Browse files Browse the repository at this point in the history
RELNOTES[NEW]: A new java test runner that support XML output and test filtering is supported.
          It can be used by specifying --nolegacy_bazel_java_test or by speicifying the test_class
          attribute on a java_test.

--
MOS_MIGRATED_REVID=112028955
  • Loading branch information
damienmg committed Jan 13, 2016
1 parent 45fdee1 commit 0e396b8
Show file tree
Hide file tree
Showing 11 changed files with 260 additions and 27 deletions.
10 changes: 10 additions & 0 deletions examples/java-native/src/test/java/com/example/myproject/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ java_test(
],
)

java_test(
name = "custom_with_test_class",
srcs = glob(["Test*.java"]),
test_class = "com.example.myproject.TestCustomGreeting",
deps = [
"//examples/java-native/src/main/java/com/example/myproject:custom-greeting",
"//third_party:junit4",
],
)

java_test(
name = "fail",
srcs = ["Fail.java"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@
import static com.google.devtools.build.lib.syntax.Type.STRING_LIST;

import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.Constants;
import com.google.devtools.build.lib.analysis.BaseRuleClasses;
import com.google.devtools.build.lib.analysis.RuleDefinition;
import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
import com.google.devtools.build.lib.bazel.rules.cpp.BazelCppRuleClasses;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.AttributeMap;
import com.google.devtools.build.lib.packages.ImplicitOutputsFunction;
import com.google.devtools.build.lib.packages.PredicateWithMessage;
import com.google.devtools.build.lib.packages.Rule;
Expand All @@ -38,6 +41,7 @@
import com.google.devtools.build.lib.packages.RuleClass.PackageNameConstraint;
import com.google.devtools.build.lib.packages.TriState;
import com.google.devtools.build.lib.rules.java.JavaSemantics;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.util.FileTypeSet;

import java.util.Set;
Expand All @@ -50,6 +54,9 @@ public class BazelJavaRuleClasses {
public static final PredicateWithMessage<Rule> JAVA_PACKAGE_NAMES = new PackageNameConstraint(
PackageNameConstraint.ANY_SEGMENT, "java", "javatests");

protected static final String JUNIT_TESTRUNNER =
Constants.TOOLS_REPOSITORY + "//tools/jdk:TestRunner_deploy.jar";

public static final ImplicitOutputsFunction JAVA_BINARY_IMPLICIT_OUTPUTS =
fromFunctions(
JavaSemantics.JAVA_BINARY_CLASS_JAR,
Expand Down Expand Up @@ -268,6 +275,7 @@ public Metadata getMetadata() {
* Base class for rule definitions producing Java binaries.
*/
public static final class BaseJavaBinaryRule implements RuleDefinition {

@Override
public RuleClass build(Builder builder, final RuleDefinitionEnvironment env) {
return builder
Expand Down Expand Up @@ -298,6 +306,23 @@ generated by the wrapper script includes the name of the main class followed by
</p>
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
.add(attr("jvm_flags", STRING_LIST))
/* <!-- #BLAZE_RULE($base_java_binary).ATTRIBUTE(use_testrunner) -->
Use the
<code>com.google.testing.junit.runner.GoogleTestRunner</code> class as the
main entry point for a Java program.
${SYNOPSIS}
You can use this to override the default
behavior, which is to use <code>BazelTestRunner</code> for
<code>java_test</code> rules,
and not use it for <code>java_binary</code> rules. It is unlikely
you will want to do this. One use is for <code>AllTest</code>
rules that are invoked by another rule (to set up a database
before running the tests, for example). The <code>AllTest</code>
rule must be declared as a <code>java_binary</code>, but should
still use the test runner as its main entry point.
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
.add(attr("use_testrunner", BOOLEAN).value(false))
/* <!-- #BLAZE_RULE($base_java_binary).ATTRIBUTE(main_class) -->
Name of class with <code>main()</code> method to use as entry point.
${SYNOPSIS}
Expand All @@ -322,6 +347,15 @@ provided by direct or transitive dependencies (through <code>runtime_deps</code>
.add(attr("create_executable", BOOLEAN)
.nonconfigurable("internal")
.value(true))
.add(attr("$testsupport", LABEL).value(
new Attribute.ComputedDefault("use_testrunner") {
@Override
public Object getDefault(AttributeMap rule) {
return rule.get("use_testrunner", Type.BOOLEAN)
? env.getLabel(JUNIT_TESTRUNNER)
: null;
}
}))
/* <!-- #BLAZE_RULE($base_java_binary).ATTRIBUTE(deploy_manifest_lines) -->
${SYNOPSIS}
A list of lines to add to the <code>META-INF/manifest.mf</code> file generated for the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.Artifact;
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.TemplateExpansionAction;
Expand All @@ -38,6 +41,7 @@
import com.google.devtools.build.lib.rules.java.JavaConfiguration;
import com.google.devtools.build.lib.rules.java.JavaHelper;
import com.google.devtools.build.lib.rules.java.JavaPrimaryClassProvider;
import com.google.devtools.build.lib.rules.java.JavaRunfilesProvider;
import com.google.devtools.build.lib.rules.java.JavaSemantics;
import com.google.devtools.build.lib.rules.java.JavaTargetAttributes;
import com.google.devtools.build.lib.rules.java.JavaUtil;
Expand All @@ -51,6 +55,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;

/**
* Semantics for Bazel Java rules
Expand Down Expand Up @@ -80,23 +85,37 @@ public void checkRule(RuleContext ruleContext, JavaCommon javaCommon) {
}
}

private String getMainClassInternal(RuleContext ruleContext) {
return ruleContext.getRule().isAttrDefined("main_class", Type.STRING)
private String getMainClassInternal(RuleContext ruleContext, JavaCommon javaCommon) {
String mainClass = ruleContext.getRule().isAttrDefined("main_class", Type.STRING)
? ruleContext.attributes().get("main_class", Type.STRING) : "";
boolean createExecutable = ruleContext.attributes().get("create_executable", Type.BOOLEAN);
boolean useTestrunner = ruleContext.attributes().get("use_testrunner", Type.BOOLEAN)
&& !useLegacyJavaTest(ruleContext);

if (createExecutable) {
if (useTestrunner) {
mainClass = "com.google.testing.junit.runner.BazelTestRunner";
} else { /* java_binary or non-Junit java_test */
if (mainClass.isEmpty()) {
mainClass = javaCommon.determinePrimaryClass(javaCommon.getSrcsArtifacts());
}
}
}

return mainClass;
}

private void checkMainClass(RuleContext ruleContext, JavaCommon javaCommon) {
boolean createExecutable = ruleContext.attributes().get("create_executable", Type.BOOLEAN);
String mainClass = getMainClassInternal(ruleContext);
String mainClass = getMainClassInternal(ruleContext, javaCommon);

if (!createExecutable && !mainClass.isEmpty()) {
ruleContext.ruleError("main class must not be specified when executable is not created");
}

if (createExecutable && mainClass.isEmpty()) {
if (javaCommon.getSrcsArtifacts().isEmpty()) {
ruleContext.ruleError(
"need at least one of 'main_class', 'use_testrunner' or Java source files");
ruleContext.ruleError("need at least one of 'main_class' or Java source files");
}
mainClass = javaCommon.determinePrimaryClass(javaCommon.getSrcsArtifacts());
if (mainClass == null) {
Expand All @@ -111,7 +130,7 @@ private void checkMainClass(RuleContext ruleContext, JavaCommon javaCommon) {
@Override
public String getMainClass(RuleContext ruleContext, JavaCommon javaCommon) {
checkMainClass(ruleContext, javaCommon);
return getMainClassInternal(ruleContext);
return getMainClassInternal(ruleContext, javaCommon);
}

@Override
Expand Down Expand Up @@ -198,9 +217,30 @@ private static void appendRunfilesRelativeEntries(StringBuilder buffer,
}
}

private TransitiveInfoCollection getTestSupport(RuleContext ruleContext) {
if (!isJavaBinaryOrJavaTest(ruleContext)) {
return null;
}
if (useLegacyJavaTest(ruleContext)) {
return null;
}

boolean createExecutable = ruleContext.attributes().get("create_executable", Type.BOOLEAN);
if (createExecutable && ruleContext.attributes().get("use_testrunner", Type.BOOLEAN)) {
return Iterables.getOnlyElement(ruleContext.getPrerequisites("$testsupport", Mode.TARGET));
} else {
return null;
}
}

@Override
public void addRunfilesForBinary(RuleContext ruleContext, Artifact launcher,
Runfiles.Builder runfilesBuilder) {
TransitiveInfoCollection testSupport = getTestSupport(ruleContext);
if (testSupport != null) {
runfilesBuilder.addTarget(testSupport, JavaRunfilesProvider.TO_RUNFILES);
runfilesBuilder.addTarget(testSupport, RunfilesProvider.DEFAULT_RUNFILES);
}
}

@Override
Expand All @@ -210,6 +250,13 @@ public void addRunfilesForLibrary(RuleContext ruleContext, Runfiles.Builder runf
@Override
public void collectTargetsTreatedAsDeps(
RuleContext ruleContext, ImmutableList.Builder<TransitiveInfoCollection> builder) {
TransitiveInfoCollection testSupport = getTestSupport(ruleContext);
if (testSupport != null) {
// TODO(bazel-team): The testsupport is used as the test framework
// and really only needs to be on the runtime, not compile-time
// classpath.
builder.add(testSupport);
}
}

@Override
Expand All @@ -230,17 +277,87 @@ public void addProviders(RuleContext ruleContext,
NestedSetBuilder<Artifact> filesBuilder,
RuleConfiguredTargetBuilder ruleBuilder) {
if (isJavaBinaryOrJavaTest(ruleContext)) {
boolean createExec = ruleContext.attributes().get("create_executable", Type.BOOLEAN);
ruleBuilder.add(JavaPrimaryClassProvider.class,
new JavaPrimaryClassProvider(createExec ? getMainClassInternal(ruleContext) : null));
ruleBuilder.add(
JavaPrimaryClassProvider.class,
new JavaPrimaryClassProvider(getPrimaryClass(ruleContext, javaCommon)));
}
}

// TODO(dmarting): simplify that logic when we remove the legacy Bazel java_test behavior.
private String getPrimaryClassLegacy(RuleContext ruleContext, JavaCommon javaCommon) {
boolean createExecutable = ruleContext.attributes().get("create_executable", Type.BOOLEAN);
if (!createExecutable) {
return null;
}
return getMainClassInternal(ruleContext, javaCommon);
}

private String getPrimaryClassNew(RuleContext ruleContext, JavaCommon javaCommon) {
boolean createExecutable = ruleContext.attributes().get("create_executable", Type.BOOLEAN);
Set<Artifact> sourceFiles = ImmutableSet.copyOf(javaCommon.getSrcsArtifacts());

if (!createExecutable) {
return null;
}

boolean useTestrunner = ruleContext.attributes().get("use_testrunner", Type.BOOLEAN);

String testClass = ruleContext.getRule().isAttrDefined("test_class", Type.STRING)
? ruleContext.attributes().get("test_class", Type.STRING) : "";

if (useTestrunner) {
if (testClass.isEmpty()) {
testClass = javaCommon.determinePrimaryClass(sourceFiles);
if (testClass == null) {
ruleContext.ruleError("cannot determine junit.framework.Test class "
+ "(Found no source file '" + ruleContext.getTarget().getName()
+ ".java' and package name doesn't include 'java' or 'javatests'. "
+ "You might want to rename the rule or add a 'test_class' "
+ "attribute.)");
}
}
return testClass;
} else {
if (!testClass.isEmpty()) {
ruleContext.attributeError("test_class", "this attribute is only meaningful to "
+ "BazelTestRunner, but you are not using it (use_testrunner = 0)");
}

return getMainClassInternal(ruleContext, javaCommon);
}
}

private String getPrimaryClass(RuleContext ruleContext, JavaCommon javaCommon) {
return useLegacyJavaTest(ruleContext) ? getPrimaryClassLegacy(ruleContext, javaCommon)
: getPrimaryClassNew(ruleContext, javaCommon);
}

@Override
public Iterable<String> getJvmFlags(
RuleContext ruleContext, JavaCommon javaCommon, List<String> userJvmFlags) {
return userJvmFlags;
ImmutableList.Builder<String> jvmFlags = ImmutableList.builder();
jvmFlags.addAll(userJvmFlags);

if (!useLegacyJavaTest(ruleContext)) {
if (ruleContext.attributes().get("use_testrunner", Type.BOOLEAN)) {
String testClass = ruleContext.getRule().isAttrDefined("test_class", Type.STRING)
? ruleContext.attributes().get("test_class", Type.STRING) : "";
if (testClass.isEmpty()) {
testClass = javaCommon.determinePrimaryClass(javaCommon.getSrcsArtifacts());
}

if (testClass == null) {
ruleContext.ruleError("cannot determine test class");
} else {
// Always run junit tests with -ea (enable assertion)
jvmFlags.add("-ea");
// "suite" is a misnomer.
jvmFlags.add("-Dbazel.test_suite=" + ShellEscaper.escapeString(testClass));
}
}
}

return jvmFlags.build();
}

@Override
Expand Down Expand Up @@ -309,22 +426,29 @@ public PathFragment getDefaultJavaResourcePath(PathFragment path) {
@Override
public List<String> getExtraArguments(RuleContext ruleContext, JavaCommon javaCommon) {
if (ruleContext.getRule().getRuleClass().equals("java_test")) {
if (ruleContext.getConfiguration().getTestArguments().isEmpty()
&& !ruleContext.attributes().isAttributeValueExplicitlySpecified("args")) {
ImmutableList.Builder<String> builder = ImmutableList.builder();
for (Artifact artifact : javaCommon.getSrcsArtifacts()) {
PathFragment path = artifact.getRootRelativePath();
String className = JavaUtil.getJavaFullClassname(FileSystemUtils.removeExtension(path));
if (className != null) {
builder.add(className);
if (useLegacyJavaTest(ruleContext)) {
if (ruleContext.getConfiguration().getTestArguments().isEmpty()
&& !ruleContext.attributes().isAttributeValueExplicitlySpecified("args")) {
ImmutableList.Builder<String> builder = ImmutableList.builder();
for (Artifact artifact : javaCommon.getSrcsArtifacts()) {
PathFragment path = artifact.getRootRelativePath();
String className = JavaUtil.getJavaFullClassname(FileSystemUtils.removeExtension(path));
if (className != null) {
builder.add(className);
}
}
return builder.build();
}
return builder.build();
}
}
return ImmutableList.<String>of();
}

private boolean useLegacyJavaTest(RuleContext ruleContext) {
return !ruleContext.attributes().isAttributeValueExplicitlySpecified("test_class")
&& ruleContext.getFragment(JavaConfiguration.class).useLegacyBazelJavaTest();
}

@Override
public String getJavaBuilderMainClass() {
return JAVABUILDER_CLASS_NAME;
Expand Down
Loading

0 comments on commit 0e396b8

Please sign in to comment.