-
-
Notifications
You must be signed in to change notification settings - Fork 73
Description
Version: 2.3.0
Bug Description
When the tests are run in parallel, they are waited for in the Runner loop (https://github.com/nette/tester/blob/master/src/Runner/Runner.php#L134). However, when there is the last Job remaining, the condition is false and the loop runs without usleep (the job was started along with other jobs, i.e. with RUN_ASYNC flag, so it does not usleep either). This causes high CPU usage in the Runner process.
Steps To Reproduce
Run two tests in parallel, with one taking long to complete (IRL, there could be a network/DB/hard drive operation instead of sleep):
command: time vendor/bin/tester -j 2 *.phpt
Test1.phpt
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
Tester\Assert::true(true);
Test2.phpt
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
sleep(10);
Tester\Assert::true(true);
time output:
real 0m10,079s
user 0m7,052s
sys 0m3,039s
Expected Behavior
The last Job should be waited for in the same manner. When I comment out the condition (https://github.com/nette/tester/blob/master/src/Runner/Runner.php#L133), I get much lower CPU usage:
real 0m10,103s
user 0m0,119s
sys 0m0,066s
Possible Solution
A possible solution would be to determine the sync/async mode (as well as whether to wait) ahead of starting the jobs. Now it is determined for each job separately (https://github.com/nette/tester/blob/master/src/Runner/Runner.php#L128), but (unless I am missing something) it only differs for the last job and only if all other jobs finish before it starts (e.g. 2 threads, 3 jobs, the first two jobs finish at once).
The loop would be:
$async = $this->threadCount > 1 && count($this->jobs) > 1;
while (($this->jobs || $running) && !$this->isInterrupted()) {
while ($threads && $this->jobs) {
$running[] = $job = array_shift($this->jobs);
$job->setEnvironmentVariable(Environment::THREAD, (string) array_shift($threads));
$job->run($async ? $job::RUN_ASYNC : 0);
}
if ($async) {
usleep(Job::RUN_USLEEP); // stream_select() doesn't work with proc_open()
}
foreach ($running as $key => $job) {
if ($this->isInterrupted()) {
break 2;
}
if (!$job->isRunning()) {
$threads[] = $job->getEnvironmentVariable(Environment::THREAD);
$this->testHandler->assess($job);
unset($running[$key]);
}
}
}
If this is OK, I would gladly prepare the PR, although I would probably need some hints regarding writing the test for this.