The solution to this turned out to smaller and trickier than I thought. Gradle was using my custom test runner and correctly invoking the filter
method. However, my runner reloads all test classes through its own classloader for Javaassist enhancements.
This lead to the issue that SlowTest
annotation was loaded through the Gradle classloader but when passed to my custom runner, the runner checked if the class was annotated with that annotation. This check never resolved correctly as the equality of the SlowTest
annotation loaded through two different classloaders was different.
--
Since I've already done the research, I'll just leave this here. After days of digging through the Gradle and the (cryptic) JUnit sources, here's what I got.
Gradle simply doesn't handle any advanced JUnit functionality except the test categorization. When you create a Gradle task with the include-categories or the exclude-categories conditions, it builds a CategoryFilter. If you don't know, a Filter
is what JUnit gives to the test-runner to decide whether a test or a test method should be filtered out. The test runner must implement the Filterable
interface.
JUnit comes with multiple runners, the Categories
is just another one of them. It extends a family of test runners called Suite
. These suite based runners are designed to run a "suite" of tests. A suite of tests could be built by annotation introspection, by explicitly defining tests in a suite or any other method that builds a suite of tests.
In the case of the Categories
runner, JUnit has it's own CategoryFilter
but Gradle doesn't use that, it uses it's own CategoryFilter
. Both provide more or less the same functionality and are JUnit filters so that can be used by any suite that implements Filterable
.
The actual class in the Gradle responsible for running the JUnit tests is called JUnitTestClassExecuter
. Once it has parsed the command line options it requests JUnit to check the runner should be used for a test. This method is invoked for every test as seen here.
The rest is simply up to JUnit. Gradle just created a custom RunNotifier
to generate the standard XML files representing test results.
I hope someone finds this useful and saved themselves countless hours of debugging.
TLDR: You can use any runner in Gradle. Gradle has no specifics pertaining to runners. It is JUnit that decided the runners. If you'd like to know what runner will be used for your test, you can debug this by calling
Request.aClass(testClass).getRunner()
. Hack this somewhere into your codebase and print it to the console. (I wasn't very successful in attaching a debugger to Gradle.)