Skip to content

Commit 8bac4c2

Browse files
committed
interrupt signal is converted to InterruptException and handled by CliTester
allows to interrupt watch mode
1 parent 6b9b34f commit 8bac4c2

File tree

4 files changed

+61
-58
lines changed

4 files changed

+61
-58
lines changed

src/Runner/CliTester.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ public function run(): ?int
6666
$runner->setEnvironmentVariable(Environment::RUNNER, '1');
6767
$runner->setEnvironmentVariable(Environment::COLORS, (string) (int) Environment::$useColors);
6868

69+
$this->installInterruptHandler();
70+
6971
if ($this->options['--coverage']) {
7072
$coverageFile = $this->prepareCodeCoverage($runner);
7173
}
@@ -360,7 +362,9 @@ private function setupErrors(): void
360362
});
361363

362364
set_exception_handler(function (\Throwable $e) {
363-
$this->displayException($e);
365+
if (!$e instanceof InterruptException) {
366+
$this->displayException($e);
367+
}
364368
exit(2);
365369
});
366370
}
@@ -374,4 +378,21 @@ private function displayException(\Throwable $e): void
374378
: Dumper::color('white/red', 'Error: ' . $e->getMessage());
375379
echo "\n";
376380
}
381+
382+
383+
private function installInterruptHandler(): void
384+
{
385+
if (function_exists('pcntl_signal')) {
386+
pcntl_signal(SIGINT, function (): void {
387+
pcntl_signal(SIGINT, SIG_DFL);
388+
throw new InterruptException;
389+
});
390+
pcntl_async_signals(true);
391+
392+
} elseif (function_exists('sapi_windows_set_ctrl_handler') && PHP_SAPI === 'cli') {
393+
sapi_windows_set_ctrl_handler(function (): void {
394+
throw new InterruptException;
395+
});
396+
}
397+
}
377398
}

src/Runner/Runner.php

Lines changed: 23 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -125,36 +125,37 @@ public function run(): bool
125125

126126
$threads = range(1, $this->threadCount);
127127

128-
$this->installInterruptHandler();
129128
$async = $this->threadCount > 1 && count($this->jobs) > 1;
130129

131-
while (($this->jobs || $running) && !$this->isInterrupted()) {
132-
while ($threads && $this->jobs) {
133-
$running[] = $job = array_shift($this->jobs);
134-
$job->setEnvironmentVariable(Environment::THREAD, (string) array_shift($threads));
135-
$job->run($async ? $job::RUN_ASYNC : 0);
136-
}
137-
138-
if ($async) {
139-
usleep(Job::RUN_USLEEP); // stream_select() doesn't work with proc_open()
140-
}
130+
try {
131+
while (($this->jobs || $running) && !$this->interrupted) {
132+
while ($threads && $this->jobs) {
133+
$running[] = $job = array_shift($this->jobs);
134+
$job->setEnvironmentVariable(Environment::THREAD, (string) array_shift($threads));
135+
$job->run($async ? $job::RUN_ASYNC : 0);
136+
}
141137

142-
foreach ($running as $key => $job) {
143-
if ($this->isInterrupted()) {
144-
break 2;
138+
if ($async) {
139+
usleep(Job::RUN_USLEEP); // stream_select() doesn't work with proc_open()
145140
}
146141

147-
if (!$job->isRunning()) {
148-
$threads[] = $job->getEnvironmentVariable(Environment::THREAD);
149-
$this->testHandler->assess($job);
150-
unset($running[$key]);
142+
foreach ($running as $key => $job) {
143+
if ($this->interrupted) {
144+
break 2;
145+
}
146+
147+
if (!$job->isRunning()) {
148+
$threads[] = $job->getEnvironmentVariable(Environment::THREAD);
149+
$this->testHandler->assess($job);
150+
unset($running[$key]);
151+
}
151152
}
152153
}
153-
}
154-
$this->removeInterruptHandler();
155154

156-
foreach ($this->outputHandlers as $handler) {
157-
$handler->end();
155+
} finally {
156+
foreach ($this->outputHandlers as $handler) {
157+
$handler->end();
158+
}
158159
}
159160

160161
return $this->result;
@@ -235,41 +236,6 @@ public function getInterpreter(): PhpInterpreter
235236
}
236237

237238

238-
private function installInterruptHandler(): void
239-
{
240-
if (function_exists('pcntl_signal')) {
241-
pcntl_signal(SIGINT, function (): void {
242-
pcntl_signal(SIGINT, SIG_DFL);
243-
$this->interrupted = true;
244-
});
245-
} elseif (function_exists('sapi_windows_set_ctrl_handler') && PHP_SAPI === 'cli') {
246-
sapi_windows_set_ctrl_handler(function () {
247-
$this->interrupted = true;
248-
});
249-
}
250-
}
251-
252-
253-
private function removeInterruptHandler(): void
254-
{
255-
if (function_exists('pcntl_signal')) {
256-
pcntl_signal(SIGINT, SIG_DFL);
257-
} elseif (function_exists('sapi_windows_set_ctrl_handler') && PHP_SAPI === 'cli') {
258-
sapi_windows_set_ctrl_handler(null);
259-
}
260-
}
261-
262-
263-
private function isInterrupted(): bool
264-
{
265-
if (function_exists('pcntl_signal_dispatch')) {
266-
pcntl_signal_dispatch();
267-
}
268-
269-
return $this->interrupted;
270-
}
271-
272-
273239
private function getLastResult(Test $test): int
274240
{
275241
$signature = $test->getSignature();

src/Runner/exceptions.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the Nette Tester.
5+
* Copyright (c) 2009 David Grudl (https://davidgrudl.com)
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Tester\Runner;
11+
12+
13+
class InterruptException extends \Exception
14+
{
15+
}

src/tester.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
declare(strict_types=1);
99

10+
require __DIR__ . '/Runner/exceptions.php';
1011
require __DIR__ . '/Runner/Test.php';
1112
require __DIR__ . '/Runner/PhpInterpreter.php';
1213
require __DIR__ . '/Runner/Runner.php';

0 commit comments

Comments
 (0)