Search code examples
bazel

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


I am adding tests in Bazel, but I don't want to write a test rule for every single test file. However, each test rule requires a test_class - the test class that is being ran, so there is no easy way to just run all tests with a single java_test rule. Is there a work around for where I wouldn't need to specify a test_class and just run all tests at once?


Solution

  • 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;
      }
    }