You really should rewrite the array obtaining algorithm itself, both because of the non-determinism of the return value, and just because you cannot test it, in particular, you can significantly improve the work with array generation by turning it from a separate stream in a separate task.
In this particular situation, you are bound hand and foot for two reasons:
- You generate a new thread yourself in your code.
- The thread model assumes any order of their execution.
Therefore, until you either substitute the link to the thread itself before calling the above method, or do not put an endless task into this thread, Java formally has the right to complete the computation in the thread before you try to call InterruptedException on the waiting thread.
Looking ahead, it is pretty tightly connected with the so-called. Purity of the function: the pure function returns a deterministic result for each set of input parameters and has no external effects (side effects, any state change in the external world for the function). The method you cited is too much tied to these very side effects, over which you have no direct power, and, strictly speaking, you can formally test only more or less pure functionality (because otherwise it becomes impossible to verify and maintain expensively - if the code is tied to state of mind, the test must inevitably recreate it before the test).
Specifically, in this case, you can’t manage the external state (execution or non-fulfillment of the thread at the right moment), because your code involves a direct call to change the external state (starting the thread); at the same time, without referring to the external state, you cannot test your code. However, if you delegate the task to someone else, you can easily recreate this state in the test. To solve all the problems, I suggest using the ExecutorService interface functionality - this is a service that allows you to register tasks for their execution, while the interface itself does not guarantee where and how tasks will be performed - it can be the same thread, another thread, another machine, or it will completely print it out on paper and send it to mathematicians for a solution. In this case, your code might look like this:
public class MapContainer { private final ExecutorService executor; private volatile Future generationTask; private volatile float[][] map; public MapContainer(ExecutorService executor) { this.executor = executor; } public synchronized void generate() { if (map != null) { return; } generationTask = executor.submit(() -> { map = compute(); }); } public float[][] get() { generate(); try { generationTask.get(); } catch (Exception e) { } return map; } }
I will warn you in advance that the code is very dirty and incorrect - except for the moment with the task delegation. Here you send the task to the ExecutorService , which will figure out where to perform the task, and then wait for it to be completed. Now you can use the result of one of the Executors.* Methods in the program code, and use your own implementation in the tests, which will return the Future following nature:
public void get() throws InterruptedException { while (true) { Thread.sleep(1000); } }
Thus, the execution time of this method is limited only by calling .interrupt() on the executing thread and throwing the corresponding exception, which is necessary for the test. Now you can create a thread, call in it mapContainer.get() and interrupt the thread itself, to force the passage of code on the specified branch.
Is this all right?
Actually, not so much, because the code still shows through appeals to the external state, and the proposed option with the Future with careless handling will really run forever, which can hang you CI-server. But, I am afraid, I cannot offer a better option without having to reassemble everything in general here. It would be much easier to consider InterruptedException as something that prevents the normal execution of the program (and as long as you do not call .interrupt for your purposes, this is true) - in this case, you can and should throw an exception to the top, catching it at the upper levels and ending the program.
In any case, we should strive for cleanliness of the code, because, among other things, it helps to prevent such situations and push the processing of exceptional cases to the periphery of the program (instead of smearing this processing across all components)
As a postscript: even better than all the above, the CompletableFuture handles the task, but I decided not to complicate the already curved text.