My case is as follows:
Using JUnit5, my junit-platform.properties are:
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.mode.classes.default=concurrent
My first approach was to annotate test class I wanted to be separated with @Isolated
, but doing that resulted in all tests inside this class to be run sequentially too, which I need to avoid, because those tests are long-running
What could be the other way to achieve this partial sequentiality?
I'm guessing in this class there's a particular set of resources which is being modified in a @BeforeAll
and reset in an @AfterAll
method in a way which is incompatible with other test classes but self consistent for all methods in the class in question.
The problem is caused because @Isolated
is essentially an alias for
@ResourceLock(Resources.GLOBAL)
, and that annotation is inherited by all the test methods in the class.
As far as I'm aware it's not possible to acquire a lock in a @BeforeAll
method only to release it in an @AfterAll
method, at least not with the annotation-based locks controlled by @ResourceLock
and co.
If you're prepared to have the parallelizable test classes share a common base class (or manually add @BeforeAll
and @AfterAll
methods to them), there is a way to acheive what you want. The solution is based on a ReadWriteLock
, and on the fact that parallel read access is allowed, but read-write access is sequentialized.
First, a utility class to help with sequentialized access:
public class Sequentializer {
private static final ReadWriteLock LOCK = new ReentrantReadWriteLock();
public static void beforeParallelClass() {
LOCK.readLock().lock();
}
public static void afterParallelClass() {
LOCK.readLock().unlock();
}
public static void beforeIsolatedClass() {
LOCK.writeLock().lock();
}
public static void afterIsolatedClass() {
LOCK.writeLock().unlock();
}
}
Next, a base class for parallel tests:
public class ParallelTestBase {
@BeforeAll
public static void beforeAll() {
Sequentializer.beforeParallelClass();
}
@AfterAll
public static void afterAll() {
Sequentializer.afterParallelClass();
}
}
Finally, (optional) a base class for test classes which should run separately from all other code:
public class IsolatedTestBase {
@BeforeAll
static void setUpBeforeClass() {
Sequentializer.beforeIsolatedClass();
}
@AfterAll
static void tearDownAfterClass() {
Sequentializer.afterIsolatedClass();
}
}
If you're doing complex setup and teardown in the test classes, you may want to call the appropriate Sequentializer
methods explicitly rather than relying on inheritance.
Alternatively, you could build the locking mechanism into whatever resource is being modified by the test classes in question.