Runs Java Lambda in Many Threads
With this small Java library you can test your objects for thread safety by doing some manipulations with them in multiple parallel threads. You may read this blog post, in order to understand the motivation for this type of testing better: How I Test My Java Classes for Thread-Safety.
By the way, there are similar libraries for Java, but they are
either more complex or less tests-oriented, for example
lincheck,
ConcurrentUnit,
RunsInThreads from cactoos-matchers,
or
Threads from Cactoos.
First, you add this library to your pom.xml in Maven:
<dependency>
<groupId>com.yegor256</groupId>
<artifactId>together</artifactId>
<version>0.2.0</version>
</dependency>
Then, you use it like this, in your JUnit5 test (with Hamcrest):
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import com.yegor256.Together;
class FooTest {
@Test
void worksAsExpected() {
MatcherAssert.assertThat(
"processes all lambdas successfully",
new Together<>(
thread -> {
// Do the job and use "thread" as a number
// of the thread currently running (they are all unique).
return true;
}
),
Matchers.not(Matchers.hasItem(Matchers.is(false)))
);
}
}
Here, the Together class runs the "job" in multiple threads
and makes sure that all of them return true. If at least
one of them returns false, the test fails. If at least one of the
threads throws an exception, the test also fails.
The existing constructors remain unchanged, but you may now make the test stronger without changing the original style:
import java.util.concurrent.TimeUnit;
new Together<>(
8,
thread -> service.process(thread)
).repeated(100)
.withTimeout(1, TimeUnit.SECONDS)
.failFast()
.asList();
Use repeated(...) when you want to run the same race many times,
withTimeout(...) when you don't want a stuck thread to freeze the test,
and failFast() when you want the remaining threads to be interrupted as
soon as one execution fails.
When a thread fails, the library now throws TogetherFailure, which is still
an IllegalArgumentException for backward compatibility, but it also tells
you which round failed, which thread failed, whether timeout was exceeded,
and how long the execution lasted.
Here are a few focused examples.
Repeat the same race many times:
MatcherAssert.assertThat(
new Together<>(
4,
thread -> cache.get("hello")
).repeated(250),
Matchers.everyItem(Matchers.equalTo("world"))
);
Stop a deadlocked or stuck test after a timeout:
Assertions.assertThrows(
TogetherFailure.class,
() -> new Together<>(
2,
thread -> {
Thread.sleep(10_000L);
return thread;
}
).withTimeout(50L, TimeUnit.MILLISECONDS).asList()
);
Fail immediately when the first thread breaks:
Assertions.assertThrows(
TogetherFailure.class,
() -> new Together<>(
8,
thread -> service.process(thread)
).failFast().asList()
);
Inspect the richer failure details:
final TogetherFailure failure = Assertions.assertThrows(
TogetherFailure.class,
() -> new Together<>(
1,
thread -> {
throw new IllegalStateException("boom");
}
).repeated(10).asList()
);
MatcherAssert.assertThat(failure.happenedInRound(0), Matchers.is(true));
MatcherAssert.assertThat(failure.happenedInThread(0), Matchers.is(true));
Together guarantees that all threads start exactly simultaneously,
thus simulating race condition as much as it's possible. This is exactly
what you need for your tests: making sure your object under test
experiences troubles that are very similar to what it might experience
in real life.
For even better/stronger testing, you can use
@RepeatedTest.
How to Contribute
Fork repository, make changes, send us a
pull request.
We will review your changes and apply them to the master branch shortly,
provided they don't violate our quality standards. To avoid frustration,
before sending us your pull request please run full Maven build:
mvn clean install -Pqulice
You will need Maven 3.3+ and Java 11+.