How to run all tests in Bazel from a single java_test() rule?

In Bazel, we wrote a custom Junit Suite that finds all the Junit classes on the classpath in or under the package of the annotated class. You can find the code here. It's pretty short and straight forward and you can copy it into your project or do something similar.

You can then have your rules like this:

java_library(
    name = "tests",
    testonly = 1,
    srcs = glob(["*.java"])
)

java_test(
   name = "MyTests",
   test_class = "MyTests",
   runtime_deps = [":tests"],
)

and the MyTests.java file should look like this:

import package.ClasspathSuite;

import org.junit.runner.RunWith;

@RunWith(ClasspathSuite.class)
public class MyTests { } 

You can write a JUnit test suite class, which will run your other tests. For example, if you have test classes Test1.java and Test2.java, you can do something like this:

AllTests.java

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

@RunWith(Suite.class)
@SuiteClasses({
    Test1.class,
    Test2.class
})
public class AllTests {}

BUILD

java_test(
    name = "AllTests",
    test_class = "AllTests",
    srcs = [
        "AllTests.java",
        "Test1.java",
        "Test2.java",
    ],
)

EDIT in response to comment:

If you don't want to specify the test class names in your test suite, you could do something via reflection. The following example assumes all your tests are in the "com.foo" package and that all the tests are srcs of the java_test rule:

package com.foo;

import java.io.File;
import java.io.IOException;
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.Set;
import java.util.TreeSet;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import junit.framework.JUnit4TestAdapter;
import junit.framework.TestSuite;
import org.junit.runner.RunWith;

@RunWith(org.junit.runners.AllTests.class)
public class AllTests {
  public static TestSuite suite() throws IOException {
    TestSuite suite = new TestSuite();
    URLClassLoader classLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
    // The first entry on the classpath contains the srcs from java_test
    findClassesInJar(new File(classLoader.getURLs()[0].getPath()))
        .stream()
        .map(c -> {
          try {
            return Class.forName(c);
          } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
          }
        })
        .filter(clazz -> !clazz.equals(AllTests.class))
        .map(JUnit4TestAdapter::new)
        .forEach(suite::addTest);
    return suite;
  }

  private static Set<String> findClassesInJar(File jarFile) {
    Set<String> classNames = new TreeSet<>();
    try {
      try (ZipFile zipFile = new ZipFile(jarFile)) {
        Enumeration<? extends ZipEntry> entries = zipFile.entries();
        while (entries.hasMoreElements()) {
          String entryName = entries.nextElement().getName();
          if (entryName.startsWith("com/foo") && entryName.endsWith(".class")) {
            int classNameEnd = entryName.length() - ".class".length();
            classNames.add(entryName.substring(0, classNameEnd).replace('/', '.'));
          }
        }
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    return classNames;
  }
}

Tags:

Bazel