From b6be940fc0ed6711972220fc1db5187f85fecd9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Franco?= Date: Sat, 18 Jul 2026 10:55:31 +0200 Subject: [PATCH] Make library compatible with FrankenPHP worker mode. Implement ResetInterface, document safe DI usage, detect processes via /proc on Linux, and guard disabled shell_exec so long-lived workers do not leak request state. --- .gitignore | 1 + README.md | 38 ++++++- composer.json | 20 +++- docs/FRANKENPHP.md | 73 +++++++++++++ phpunit.xml.dist | 15 +-- src/BackgroundProcess.php | 168 +++++++++++++++++++---------- src/Factory.php | 14 ++- tests/BackgroundProcessTest.php | 186 ++++++++++++++++---------------- tests/FactoryTest.php | 50 ++++----- 9 files changed, 374 insertions(+), 191 deletions(-) create mode 100644 docs/FRANKENPHP.md diff --git a/.gitignore b/.gitignore index d7324d3..df7baf7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ /vendor/ /composer.lock /phpunit.xml +/.phpunit.cache/ /tests/fixtures/*.log diff --git a/README.md b/README.md index b1dfa9c..1acb660 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,15 @@ cocur/background-process ======================== -> Start processes in the background that continue running when the PHP process exists. +> Start processes in the background that continue running when the PHP process exits. [![Latest Stable Version](http://img.shields.io/packagist/v/cocur/background-process.svg)](https://packagist.org/packages/cocur/background-process) [![Build Status](http://img.shields.io/travis/cocur/background-process.svg)](https://travis-ci.org/cocur/background-process) [![Windows Build status](https://ci.appveyor.com/api/projects/status/odmyynd522vuef1y?svg=true)](https://ci.appveyor.com/project/florianeckerstorfer/background-process) [![Code Coverage](https://scrutinizer-ci.com/g/cocur/background-process/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/cocur/background-process/?branch=master) +**FrankenPHP worker mode: Supported** — see [docs/FRANKENPHP.md](docs/FRANKENPHP.md). + Installation ------------ @@ -18,6 +20,8 @@ You can install Cocur\BackgroundProcess using [Composer](http://getcomposer.org) $ composer require cocur/background-process ``` +Requirements: **PHP >= 8.1** and `symfony/service-contracts` (for `ResetInterface` / worker reset). + Usage ----- @@ -48,6 +52,9 @@ while ($process->isRunning()) { echo "\nDone.\n"; ``` +> **FrankenPHP worker mode:** do **not** use the `while (isRunning()) { sleep(1); }` pattern inside an HTTP +> request — it blocks the worker thread. Fire the process, persist the PID, and return. See [docs/FRANKENPHP.md](docs/FRANKENPHP.md). + If the process runs you can stop it: ```php @@ -60,6 +67,18 @@ if ($process->isRunning()) { *Please note: If the parent process continues to run while the child process(es) run(s) in the background you should use a more robust solution, for example, the [Symfony Process](https://github.com/symfony/Process) component.* +### Factory (recommended for DI / FrankenPHP) + +`Factory` is stateless and safe as a shared service. Always create a new `BackgroundProcess` per operation: + +```php +use Cocur\BackgroundProcess\Factory; + +$factory = new Factory(); +$process = $factory->newProcess('bin/console app:job'); +$process->run(); +``` + ### Windows Support Since Version 0.5 Cocur\BackgroundProcess has basic support for Windows included. However, support is very limited at @@ -84,9 +103,26 @@ $process->isRunning(); // -> true $process->stop(); // -> true ``` +### FrankenPHP worker mode + +Compatible with FrankenPHP classic and worker mode. `BackgroundProcess` implements `ResetInterface` so Symfony can +clear `$command` / `$pid` between requests when the instance is a shared service. + +Full guidance: [docs/FRANKENPHP.md](docs/FRANKENPHP.md). + + Change Log ---------- +### Version 0.8 (18 July 2026) + +- FrankenPHP **worker mode** compatibility: implement `ResetInterface`, add `reset()`, document safe usage +- Require PHP >= 8.1 and `symfony/service-contracts` +- Prefer `Factory` as shared DI service; create a fresh process per operation +- Guard when `shell_exec` is disabled; treat missing PID as not running +- Detect running processes via `/proc` on Linux (no `ps` binary required) +- Escape output file path on *nix; modernize tests for PHPUnit 10/11 + ### Version 0.7 (11 February 2017) - [#19](https://github.com/cocur/background-process/pull/19) Create `BackgroundProcess` object from PID (by [socieboy](https://github.com/socieboy) and [florianeckerstorfer](https://github.com/florianeckerstorfer)) diff --git a/composer.json b/composer.json index bf7071d..daca8ad 100644 --- a/composer.json +++ b/composer.json @@ -1,18 +1,30 @@ { "name": "cocur/background-process", - "description": "Start processes in the background that continue running when the PHP process exists.", + "description": "Start processes in the background that continue running when the PHP process exits. Compatible with FrankenPHP worker mode.", "type": "library", - "keywords": ["process", "background", "unix"], + "keywords": ["process", "background", "unix", "frankenphp", "worker"], "license": "MIT", "require": { - "php": ">=5.5" + "php": ">=8.1", + "symfony/service-contracts": "^2.5|^3.0" }, "require-dev": { - "phpunit/phpunit": "~4.8|~5.1" + "phpunit/phpunit": "^10.5|^11.0" }, "autoload": { "psr-4": { "Cocur\\BackgroundProcess\\": "src/" } + }, + "autoload-dev": { + "psr-4": { + "Cocur\\BackgroundProcess\\Tests\\": "tests/" + } + }, + "scripts": { + "test": "phpunit" + }, + "config": { + "sort-packages": true } } diff --git a/docs/FRANKENPHP.md b/docs/FRANKENPHP.md new file mode 100644 index 0000000..1c704c0 --- /dev/null +++ b/docs/FRANKENPHP.md @@ -0,0 +1,73 @@ +# FrankenPHP (runtime and worker mode) + +This library is **compatible with FrankenPHP** in classic HTTP mode and in **worker mode**. + +## Compatibility summary + +| Concern | Status | +|---------|--------| +| Mutable class static state | None (only `createFromPID()` as a pure factory) | +| Globals / `$_ENV` writes | None | +| `exit()` / `die()` / `pcntl_fork` | None | +| Request-scoped instance state | `$command`, `$pid` — cleared by `reset()` | +| Shared DI service | Use `Factory` (stateless) or tag `BackgroundProcess` with `kernel.reset` | +| `isRunning()` on Linux | Uses `/proc/` (works when `ps` is not installed in the image) | + +**FrankenPHP worker mode: Supported.** + +## Recommended usage under worker mode + +### 1. Prefer a fresh instance per operation + +```php +use Cocur\BackgroundProcess\Factory; + +// Register Factory as a shared service — safe across requests +$factory = $container->get(Factory::class); +$process = $factory->newProcess('bin/console app:heavy-job'); +$process->run(); + +// Persist PID if you need to manage the OS process later +$pid = $process->getPid(); +``` + +### 2. If you inject `BackgroundProcess` as a shared service + +Implementations of `Symfony\Contracts\Service\ResetInterface` are reset by Symfony between requests when the kernel runs in a long-lived worker (FrankenPHP, `runtime/frankenphp-symfony`, etc.). + +```yaml +services: + Cocur\BackgroundProcess\BackgroundProcess: + shared: true + tags: + - { name: kernel.reset, method: reset } +``` + +`reset()` clears `$command` and `$pid` on the PHP object. It does **not** kill the OS child. Store the PID (DB, Redis, file) before the request ends if you still need `isRunning()` / `stop()` later via `BackgroundProcess::createFromPID($pid)`. + +### 3. Do not block the worker thread + +Avoid polling loops inside an HTTP request handled by a FrankenPHP worker: + +```php +// BAD in worker mode — blocks the worker until the child exits +while ($process->isRunning()) { + sleep(1); +} +``` + +Fire the process, persist the PID, and return. Check status from another request, a Messenger handler, or a cron/CLI command. + +### 4. `shell_exec` must be available + +FrankenPHP images sometimes disable `shell_exec` via `disable_functions`. This library throws a clear `RuntimeException` when it is unavailable. Alternatives: Symfony Messenger, a dedicated CLI worker, or `symfony/process` when the parent stays alive. + +## Runtime (non-worker) mode + +Behaviour is identical to PHP-FPM / classic SAPIs: each request gets a new PHP process, so instance state never leaks. Worker-specific rules above still apply as good practice. + +## Recommendations + +- Limit requests per worker (`frankenphp_loop_max` / `MAX_REQUESTS`) so workers recycle and memory is released. +- Do not register a single `BackgroundProcess` instance with a live PID as an application-wide singleton without `reset()`. +- Prefer queues for work that must outlive the HTTP request in production Symfony apps. diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 7319db0..9bfeb1a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,15 +1,18 @@ - - + tests/ - - + + src/ - - + + diff --git a/src/BackgroundProcess.php b/src/BackgroundProcess.php index 599265c..1de139d 100755 --- a/src/BackgroundProcess.php +++ b/src/BackgroundProcess.php @@ -1,5 +1,7 @@ * @copyright 2013-2015 Florian Eckerstorfer * @license http://opensource.org/licenses/MIT The MIT License * @link https://florian.ec/articles/running-background-processes-in-php/ Running background processes in PHP */ -class BackgroundProcess +class BackgroundProcess implements ResetInterface { - const OS_WINDOWS = 1; - const OS_NIX = 2; - const OS_OTHER = 3; + public const OS_WINDOWS = 1; + public const OS_NIX = 2; + public const OS_OTHER = 3; - /** - * @var string - */ - private $command; + private ?string $command; - /** - * @var int - */ - private $pid; + private ?int $pid = null; + + protected int $serverOS; /** - * @var int + * @param string|null $command The command to execute + * + * @codeCoverageIgnore */ - protected $serverOS; + public function __construct(?string $command = null) + { + $this->command = $command; + $this->serverOS = $this->detectOS(); + } /** - * @param string $command The command to execute + * Clears request-scoped state so the instance is safe to reuse in long-lived workers + * (FrankenPHP worker mode, RoadRunner, etc.). * - * @codeCoverageIgnore + * Does not stop an already spawned OS process; persist the PID yourself if you still + * need to manage it after reset (e.g. store it and use {@see createFromPID()}). */ - public function __construct($command = null) + public function reset(): void { - $this->command = $command; - $this->serverOS = $this->getOS(); + $this->command = null; + $this->pid = null; } /** @@ -58,20 +68,30 @@ public function __construct($command = null) * * @param string $outputFile File to write the output of the process to; defaults to /dev/null * currently $outputFile has no effect when used in conjunction with a Windows server - * @param bool $append - set to true if output should be appended to $outputfile + * @param bool $append set to true if output should be appended to $outputFile + * + * @throws RuntimeException if shell_exec is disabled or the OS is unsupported */ - public function run($outputFile = '/dev/null', $append = false) + public function run(string $outputFile = '/dev/null', bool $append = false): void { - if($this->command === null) { + if ($this->command === null) { return; } - switch ($this->getOS()) { + $this->assertShellExecAvailable(); + + switch ($this->serverOS) { case self::OS_WINDOWS: - shell_exec(sprintf('%s &', $this->command, $outputFile)); + shell_exec(sprintf('%s &', $this->command)); break; case self::OS_NIX: - $this->pid = (int)shell_exec(sprintf('%s %s %s 2>&1 & echo $!', $this->command, ($append) ? '>>' : '>', $outputFile)); + $pid = shell_exec(sprintf( + '%s %s %s 2>&1 & echo $!', + $this->command, + $append ? '>>' : '>', + escapeshellarg($outputFile) + )); + $this->pid = $pid !== null && $pid !== '' ? (int) $pid : null; break; default: throw new RuntimeException(sprintf( @@ -87,39 +107,57 @@ public function run($outputFile = '/dev/null', $append = false) * Returns if the process is currently running. * * @return bool TRUE if the process is running, FALSE if not. + * + * @throws RuntimeException if the OS is not supported */ - public function isRunning() + public function isRunning(): bool { $this->checkSupportingOS('Cocur\BackgroundProcess can only check if a process is running on *nix-based '. 'systems, such as Unix, Linux or Mac OS X. You are running "%s".'); - try { - $result = shell_exec(sprintf('ps %d 2>&1', $this->pid)); - if (count(preg_split("/\n/", $result)) > 2 && !preg_match('/ERROR: Process ID out of range/', $result)) { - return true; - } - } catch (Exception $e) { + if ($this->pid === null) { + return false; } - return false; + // Prefer /proc on Linux (FrankenPHP/Docker often omit the `ps` binary). + if (is_dir('/proc')) { + return file_exists('/proc/'.$this->pid); + } + + $this->assertShellExecAvailable(); + + $result = shell_exec(sprintf('ps %d 2>&1', $this->pid)); + if ($result === null) { + return false; + } + + return count(preg_split("/\n/", $result)) > 2 + && !preg_match('/ERROR: Process ID out of range/', $result); } /** * Stops the process. * * @return bool `true` if the processes was stopped, `false` otherwise. + * + * @throws RuntimeException if the OS is not supported */ - public function stop() + public function stop(): bool { $this->checkSupportingOS('Cocur\BackgroundProcess can only stop a process on *nix-based systems, such as '. 'Unix, Linux or Mac OS X. You are running "%s".'); - try { - $result = shell_exec(sprintf('kill %d 2>&1', $this->pid)); - if (!preg_match('/No such process/', $result)) { - return true; - } - } catch (Exception $e) { + if ($this->pid === null) { + return false; + } + + $this->assertShellExecAvailable(); + + $result = shell_exec(sprintf('kill %d 2>&1', $this->pid)); + if ($result === null || !preg_match('/No such process/', $result)) { + $this->pid = null; + + return true; } return false; @@ -128,9 +166,9 @@ public function stop() /** * Returns the ID of the process. * - * @return int The ID of the process + * @throws RuntimeException if the OS is not supported */ - public function getPid() + public function getPid(): ?int { $this->checkSupportingOS('Cocur\BackgroundProcess can only return the PID of a process on *nix-based systems, '. 'such as Unix, Linux or Mac OS X. You are running "%s".'); @@ -140,24 +178,32 @@ public function getPid() /** * Set the process id. - * - * @param $pid */ - protected function setPid($pid) + protected function setPid(int $pid): void { $this->pid = $pid; } /** - * @return int + * @deprecated use the cached $serverOS; kept for BC with subclasses + */ + protected function getOS(): int + { + return $this->serverOS; + } + + /** + * @codeCoverageIgnore */ - protected function getOS() + protected function detectOS(): int { $os = strtoupper(PHP_OS); - if (substr($os, 0, 3) === 'WIN') { + if (str_starts_with($os, 'WIN')) { return self::OS_WINDOWS; - } else if ($os === 'LINUX' || $os === 'FREEBSD' || $os === 'DARWIN') { + } + + if ($os === 'LINUX' || $os === 'FREEBSD' || $os === 'DARWIN') { return self::OS_NIX; } @@ -171,19 +217,31 @@ protected function getOS() * * @codeCoverageIgnore */ - protected function checkSupportingOS($message) + protected function checkSupportingOS(string $message): void { - if ($this->getOS() !== self::OS_NIX) { + if ($this->serverOS !== self::OS_NIX) { throw new RuntimeException(sprintf($message, PHP_OS)); } } + /** + * @throws RuntimeException if shell_exec is unavailable (common in hardened FrankenPHP images) + */ + protected function assertShellExecAvailable(): void + { + if (!function_exists('shell_exec') || in_array('shell_exec', array_map('trim', explode(',', (string) ini_get('disable_functions'))), true)) { + throw new RuntimeException( + 'Cocur\BackgroundProcess requires shell_exec(), which is disabled in this PHP runtime. '. + 'Enable it or spawn background work via a queue/Messenger worker instead.' + ); + } + } + /** * @param int $pid PID of process to resume - * - * @return Cocur\BackgroundProcess\BackgroundProcess */ - static public function createFromPID($pid) { + public static function createFromPID(int $pid): self + { $process = new self(); $process->setPid($pid); diff --git a/src/Factory.php b/src/Factory.php index 42d0f5d..3f66848 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -1,4 +1,7 @@ * @copyright 2013-2014 Florian Eckerstorfer @@ -18,17 +24,17 @@ */ class Factory { - /** @var string */ - private $className; + private string $className; - public function __construct($className) + public function __construct(string $className = BackgroundProcess::class) { $this->className = $className; } - public function newProcess($command) + public function newProcess(?string $command = null): BackgroundProcess { $className = $this->className; + return new $className($command); } } diff --git a/tests/BackgroundProcessTest.php b/tests/BackgroundProcessTest.php index 3f5707c..2aeec1d 100644 --- a/tests/BackgroundProcessTest.php +++ b/tests/BackgroundProcessTest.php @@ -1,36 +1,84 @@ - * @copyright 2013-2104 Florian Eckerstorfer - * @license http://opensource.org/licenses/MIT The MIT License - * @group functional + * @group functional */ -class BackgroundProcessTest extends \PHPUnit_Framework_TestCase +class BackgroundProcessTest extends TestCase { - /** - * @test - * @covers Cocur\BackgroundProcess\BackgroundProcess::run() - * @covers Cocur\BackgroundProcess\BackgroundProcess::isRunning() - * @covers Cocur\BackgroundProcess\BackgroundProcess::getOS() - */ - public function runShouldRunCommand() + public function testImplementsResetInterfaceForFrankenPhpWorkerMode(): void + { + $process = new BackgroundProcess('sleep 1'); + + $this->assertInstanceOf(ResetInterface::class, $process); + } + + public function testResetClearsCommandAndPidWithoutStaticLeak(): void + { + if (preg_match('/^WIN/', PHP_OS)) { + $this->markTestSkipped('PID handling is not supported on Windows.'); + } + + $process = new BackgroundProcess('sleep 5'); + $process->run(); + $pid = $process->getPid(); + + $this->assertNotNull($pid); + $this->assertTrue($process->isRunning()); + + $process->reset(); + + $this->assertNull($process->getPid()); + $this->assertFalse($process->isRunning()); + + // OS child may still be alive; clean up via a fresh handle + $orphan = BackgroundProcess::createFromPID($pid); + if ($orphan->isRunning()) { + $orphan->stop(); + } + } + + public function testFactoryCreatesFreshInstancesSafeForSharedWorkerService(): void + { + $factory = new Factory(); + $a = $factory->newProcess('sleep 1'); + $b = $factory->newProcess('sleep 2'); + + $this->assertNotSame($a, $b); + $this->assertInstanceOf(BackgroundProcess::class, $a); + } + + public function testIsRunningReturnsFalseWhenPidIsNull(): void { if (preg_match('/^WIN/', PHP_OS)) { - $command = sprintf('tests\\fixtures\\cmd.bat', __DIR__); + $this->markTestSkipped('isRunning() is not supported on Windows.'); + } + + $process = new BackgroundProcess(); + $this->assertFalse($process->isRunning()); + } + + public function testRunShouldRunCommand(): void + { + if (preg_match('/^WIN/', PHP_OS)) { + $command = 'tests\\fixtures\\cmd.bat'; } else { - $command = sprintf('./tests/fixtures/cmd.sh', __DIR__); + $command = './tests/fixtures/cmd.sh'; } $checkFile = __DIR__.DIRECTORY_SEPARATOR.'fixtures'.DIRECTORY_SEPARATOR.'runShouldRunCommand.log'; @@ -40,23 +88,15 @@ public function runShouldRunCommand() sleep(1); - $this->assertStringStartsWith('ok', file_get_contents($checkFile)); + $this->assertStringStartsWith('ok', (string) file_get_contents($checkFile)); unlink($checkFile); } - /** - * @test - * @covers Cocur\BackgroundProcess\BackgroundProcess::isRunning() - * @covers Cocur\BackgroundProcess\BackgroundProcess::getOS() - */ - public function isRunningShouldReturnIfProcessIsRunning() + public function testIsRunningShouldReturnIfProcessIsRunning(): void { if (preg_match('/^WIN/', PHP_OS)) { - $this->markTestSkipped('Cocur\BackgroundProcess\BackgroundProcess::isRunning() is not supported on '. - 'Windows.'); - - return; + $this->markTestSkipped('Cocur\BackgroundProcess\BackgroundProcess::isRunning() is not supported on Windows.'); } $process = new BackgroundProcess('sleep 3'); @@ -64,38 +104,25 @@ public function isRunningShouldReturnIfProcessIsRunning() $this->assertFalse($process->isRunning()); $process->run(); $this->assertTrue($process->isRunning()); + $process->stop(); } - /** - * @test - * @covers Cocur\BackgroundProcess\BackgroundProcess::isRunning() - * @expectedException \RuntimeException - */ - public function isRunningShouldThrowExceptionIfWindows() + public function testIsRunningShouldThrowExceptionIfWindows(): void { if (!preg_match('/^WIN/', PHP_OS)) { - $this->markTestSkipped('Cocur\BackgroundProcess\BackgroundProcess::isRunning() is supported on *nix '. - 'systems and does not need to throw an exception.'); - - return; + $this->markTestSkipped('Cocur\BackgroundProcess\BackgroundProcess::isRunning() is supported on *nix systems.'); } + $this->expectException(RuntimeException::class); + $process = new BackgroundProcess('sleep 1'); $process->isRunning(); } - /** - * @test - * @covers Cocur\BackgroundProcess\BackgroundProcess::run() - * @covers Cocur\BackgroundProcess\BackgroundProcess::getOS() - */ - public function runShouldWriteOutputToFile() + public function testRunShouldWriteOutputToFile(): void { if (preg_match('/^WIN/', PHP_OS)) { - $this->markTestSkipped('Cocur\BackgroundProcess\BackgroundProcess::run() does not support writing output '. - 'into a file on Windows.'); - - return; + $this->markTestSkipped('Writing output into a file is not supported on Windows.'); } $outputFile = __DIR__.'/fixtures/runShouldWriteOutputToFile.log'; @@ -104,58 +131,39 @@ public function runShouldWriteOutputToFile() $process->run($outputFile); sleep(1); - $this->assertNotNull(file_get_contents($outputFile)); + $this->assertNotFalse(file_get_contents($outputFile)); unlink($outputFile); } - /** - * @test - * @covers Cocur\BackgroundProcess\BackgroundProcess::getPid() - * @covers Cocur\BackgroundProcess\BackgroundProcess::getOS() - */ - public function getPidShouldReturnPidOfProcess() + public function testGetPidShouldReturnPidOfProcess(): void { if (preg_match('/^WIN/', PHP_OS)) { $this->markTestSkipped('Cocur\BackgroundProcess\BackgroundProcess::getPid() is not supported on Windows.'); - - return; } $process = new BackgroundProcess('sleep 3'); $process->run(); $this->assertNotNull($process->getPid()); + $process->stop(); } - /** - * @test - * @covers Cocur\BackgroundProcess\BackgroundProcess::getPid() - * @expectedException \RuntimeException - */ - public function getPidShouldThrowExceptionIfWindows() + public function testGetPidShouldThrowExceptionIfWindows(): void { if (!preg_match('/^WIN/', PHP_OS)) { - $this->markTestSkipped('Cocur\BackgroundProcess\BackgroundProcess::getPid() is supported on *nix systems '. - 'and does not need to throw an exception.'); - - return; + $this->markTestSkipped('Cocur\BackgroundProcess\BackgroundProcess::getPid() is supported on *nix systems.'); } + $this->expectException(RuntimeException::class); + $process = new BackgroundProcess('sleep 1'); $process->getPid(); } - /** - * @test - * @covers Cocur\BackgroundProcess\BackgroundProcess::stop() - * @covers Cocur\BackgroundProcess\BackgroundProcess::getOS() - */ - public function stopShouldStopRunningProcess() + public function testStopShouldStopRunningProcess(): void { if (preg_match('/^WIN/', PHP_OS)) { $this->markTestSkipped('Cocur\BackgroundProcess\BackgroundProcess::stop() is not supported on Windows.'); - - return; } $process = new BackgroundProcess('sleep 5'); @@ -165,38 +173,28 @@ public function stopShouldStopRunningProcess() $this->assertFalse($process->isRunning()); } - /** - * @test - * @covers Cocur\BackgroundProcess\BackgroundProcess::stop() - * @expectedException \RuntimeException - */ - public function stopShouldThrowExceptionIfWindows() + public function testStopShouldThrowExceptionIfWindows(): void { if (!preg_match('/^WIN/', PHP_OS)) { - $this->markTestSkipped('Cocur\BackgroundProcess\BackgroundProcess::stop() is supported on *nix systems '. - 'and does not need to throw an exception.'); - - return; + $this->markTestSkipped('Cocur\BackgroundProcess\BackgroundProcess::stop() is supported on *nix systems.'); } + $this->expectException(RuntimeException::class); + $process = new BackgroundProcess('sleep 1'); $process->stop(); } - /** - * @test - * @covers Cocur\BackgroundProcess\BackgroundProcess::createFromPID() - */ - public function createFromPIDShouldCreateObjectFromPID() + public function testCreateFromPIDShouldCreateObjectFromPID(): void { if (preg_match('/^WIN/', PHP_OS)) { $this->markTestSkipped('Cocur\BackgroundProcess\BackgroundProcess::createFromPID() is not supported on Windows.'); - - return; } + $process = new BackgroundProcess('sleep 1'); $process->run(); $pid = $process->getPid(); + $this->assertNotNull($pid); $newProcess = BackgroundProcess::createFromPID($pid); diff --git a/tests/FactoryTest.php b/tests/FactoryTest.php index 64b60e4..f20245c 100644 --- a/tests/FactoryTest.php +++ b/tests/FactoryTest.php @@ -1,41 +1,37 @@ - * @copyright 2013-2014 Florian Eckerstorfer - * @license http://opensource.org/licenses/MIT The MIT License - * @link http://braincrafted.com/php-background-processes/ Running background processes in PHP - * @group unit + * @group unit */ -class FactoryTest extends \PHPUnit_Framework_TestCase +class FactoryTest extends TestCase { - /** @var string */ - private $mockClass = 'Cocur\BackgroundProcess\MockBackgroundProcess'; - - /** - * Tests the newProcess method. - * - * @covers Cocur\BackgroundProcess\Factory::__construct() - * @covers Cocur\BackgroundProcess\Factory::newProcess() - */ - public function testNewProcess() + public function testNewProcessCreatesBackgroundProcess(): void { - $factory = new Factory($this->mockClass); - $this->assertInstanceOf($this->mockClass, $factory->newProcess('sleep 1')); + $factory = new Factory(BackgroundProcess::class); + $process = $factory->newProcess('sleep 1'); + + $this->assertInstanceOf(BackgroundProcess::class, $process); } -} -class MockBackgroundProcess -{ + public function testDefaultClassNameIsBackgroundProcess(): void + { + $factory = new Factory(); + $process = $factory->newProcess('true'); + + $this->assertInstanceOf(BackgroundProcess::class, $process); + } }