The main code
I have a method which uses a service in order to push a job and wait for such service to create a file somewhere.
Such service exposes two methods:
String pushJob(...)
<-- will push the job and will return the job id as a stringboolean isJobReady(String id)
<-- will returntrue
if job is finished, false otherwise
In order to wait, this method does something like this:
CompletableFuture<Void> waiter = new CompletableFuture<>();
waitFor(waiter, jobId); //<-- this will poll on the isJobReady endpoint until it responds with true
// when this happens, it will complete the future.
waiter.get(1, TimeUnit.HOUR); //<-- wait for the job to complete within the hour
//do something with the file that was created once the get returns without exceptions
The unit test
This works fine, but I would like to test it. Hence, I've made a mock implementation of the service exposing the two methods pushJob
and isJobReady
:
public class MockService implements Service {
private volatile boolean isReady;
@Override
public String pushJob() {
isReady = false;
CompletableFuture.runAsync(() -> {
Thread.sleep(10000); //<-- simulates the time spent to perform the job on service side
File file = new File("...");
if (file.createNewFile()) {
isReady = true;
} else {
throw new IllegalStateException("Could not create test file");
}
});
return "12345";
}
@Override
public boolean isJobReady(String id) {
return isReady;
}
}
I then inject such mock implementation inside my code in order to test it.
The issue
After the mock implementation creates the file, it sets the volatile field isReady
to true so when the main code sees it, it will right after go and search for the file that was created.
However, it finds it once out of two times.
If I set a breakpoint right before searching for the file and wait a second, the code will systematically succeed. But if I go at normal execution speed (or simply compile time), the test turns to be extremely flaky.
I figure out that this is a multi-threading issue but I really don't see how it is possible because the test code will not set the variable isReady = true
until when it has a response from createNewFile()
which (according to the javadoc) Atomically creates a new, empty file named by this abstract pathname if and only if a file with this name does not yet exist.
Why is this happening and how can I correctly synchronize the file system so that the creation of the file inside the mock service is correctly visible on the main code?