Skip to content

Commit

Permalink
Merge pull request #767 from lucatume/v4-better-pid-check
Browse files Browse the repository at this point in the history
fix(PidBasedController) check PID maps to running process
  • Loading branch information
lucatume authored Nov 29, 2024
2 parents 7163a8e + 8993ad5 commit fe3e069
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 4 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).

## [unreleased] Unreleased

## Fixed

- Check the PID file for the PHP built-in server, MySQL and Chromedriver controllers to make sure the PID maps to an actually running process.

## [4.3.9] 2024-11-29;

## Changed
Expand Down
2 changes: 1 addition & 1 deletion src/Extension/BuiltInServerController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public function start(OutputInterface $output): void
{
$pidFile = $this->getPidFile();

if (is_file($pidFile)) {
if ($this->isProcessRunning($pidFile)) {
$output->writeln('PHP built-in server already running.');
return;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Extension/ChromeDriverController.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public function start(OutputInterface $output): void
{
$pidFile = $this->getPidFile();

if (is_file($pidFile)) {
if ($this->isProcessRunning($pidFile)) {
$output->writeln('ChromeDriver already running.');

return;
Expand Down
2 changes: 1 addition & 1 deletion src/Extension/MysqlServerController.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public function start(OutputInterface $output): void
{
$pidFile = $this->getPidFile();

if (is_file($pidFile)) {
if ($this->isProcessRunning($pidFile)) {
$output->writeln('MySQL server already running.');

return;
Expand Down
50 changes: 50 additions & 0 deletions src/Extension/PidBasedController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace lucatume\WPBrowser\Extension;

use Codeception\Exception\ExtensionException;
use lucatume\WPBrowser\Exceptions\RuntimeException;

trait PidBasedController
{
Expand Down Expand Up @@ -49,4 +50,53 @@ protected function removePidFile(string $pidFile): void
);
}
}

/**
* @throws RuntimeException
*/
protected function isProcessRunning(string $pidFile):bool
{
if (!is_file($pidFile)) {
return false;
}

try {
$pidFileContents = file_get_contents($pidFile);
if ($pidFileContents === false) {
throw new \Exception();
}
} catch (\Exception $e) {
if (!unlink($pidFile)) {
throw new RuntimeException("Failed to delete PID file: $pidFile");
}

return false;
}

$pid = trim($pidFileContents);

if (!is_numeric($pid) || (int)$pid === 0) {
return false;
}

if (PHP_OS_FAMILY === 'Windows') {
$output = [];
exec("tasklist /FI \"PID eq $pid\" 2>NUL", $output);

return str_contains(implode("\n", $output), $pid);
} else {
// Check if the process is running on POSIX (Mac or Linux)
exec("ps -p $pid", $output, $resultCode);
if ($resultCode === 0 && count($output) > 1) {
// Process is running
return true;
}
}

if (!unlink($pidFile)) {
throw new RuntimeException("Failed to delete PID file: $pidFile");
}

return false;
}
}
86 changes: 86 additions & 0 deletions tests/unit/lucatume/WPBrowser/Extension/PidBasedControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php


namespace Unit\lucatume\WPBrowser\Extension;

use lucatume\WPBrowser\Extension\PidBasedController;
use lucatume\WPBrowser\Exceptions\RuntimeException;
use Symfony\Component\Process\Process;

class PidBasedControllerTest extends \Codeception\Test\Unit
{
public function test_isProcessRunning_on_posix():void{
$testClass = new class {
use PidBasedController;

public function openIsProcessRunning(string $pidFile):bool{
return $this->isProcessRunning($pidFile);
}
};
$hash = md5(microtime());
$pidFile = sys_get_temp_dir()."/test-{$hash}.pid";
$pid = posix_getpid();
if(!file_put_contents($pidFile,$pid)){
$this->fail('Could not write pid to file '.$pidFile);
}

$this->assertTrue($testClass->openIsProcessRunning($pidFile));
$this->assertFileExists($pidFile);
}

public function test_isProcessRunning_returns_false_if_pid_file_not_exists():void{
$testClass = new class {
use PidBasedController;

public function openIsProcessRunning(string $pidFile):bool{
return $this->isProcessRunning($pidFile);
}
};
$pid = posix_getpid();

$this->assertFalse($testClass->openIsProcessRunning(__DIR__ .'/test.pid'));
}

public function test_isProcessRunning_throws_if_pid_file_cannot_be_read():void{
$testClass = new class {
use PidBasedController;

public function openIsProcessRunning(string $pidFile):bool{
return $this->isProcessRunning($pidFile);
}
};
$pid = posix_getpid();
$hash = md5(microtime());
$pidFile = sys_get_temp_dir()."/test-{$hash}.pid";
$pid = posix_getpid();
if(!file_put_contents($pidFile,$pid)){
$this->fail('Could not write pid to file '.$pidFile);
}
// Change the file mode to not be readable by the current user.
chmod($pidFile,0000);

$this->assertFalse($testClass->openIsProcessRunning($pidFile));
}

public function test_isProcessRunning_returns_false_if_process_is_not_running():void{
$testClass = new class {
use PidBasedController;

public function openIsProcessRunning(string $pidFile):bool{
return $this->isProcessRunning($pidFile);
}
};
$hash = md5(microtime());
$pidFile = sys_get_temp_dir()."/test-{$hash}.pid";
$process = new Process(['echo', '23']);
$process->start();
$pid = $process->getPid();
$process->wait();
if(!file_put_contents($pidFile,$pid)){
$this->fail('Could not write pid to file '.$pidFile);
}

$this->assertFalse($testClass->openIsProcessRunning($pidFile));
$this->assertFileNotExists($pidFile);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
use Codeception\Lib\Di;
use Codeception\Lib\ModuleContainer;
use Codeception\Test\Unit;
use lucatume\WPBrowser\Module\WPLoader;
use lucatume\WPBrowser\Tests\Traits\DatabaseAssertions;
use lucatume\WPBrowser\Tests\Traits\LoopIsolation;
use lucatume\WPBrowser\Tests\Traits\MainInstallationAccess;
Expand Down Expand Up @@ -56,6 +55,72 @@ private function module(array $moduleContainerConfig = [], ?array $moduleConfig
return new WPLoader($this->mockModuleContainer, ($moduleConfig ?? $this->config));
}

public function test_loads_plugins_from_default_location_correctly(): void
{
$projectDir = FS::tmpDir('wploader_');
$installation = Installation::scaffold($projectDir);
$dbName = Random::dbName();
$dbHost = Env::get('WORDPRESS_DB_HOST');
$dbUser = Env::get('WORDPRESS_DB_USER');
$dbPassword = Env::get('WORDPRESS_DB_PASSWORD');
$installationDb = new MysqlDatabase($dbName, $dbUser, $dbPassword, $dbHost, 'wp_');
if (!mkdir($projectDir . '/wp-content/plugins/test-one', 0777, true)) {
throw new \RuntimeException('Unable to create test directory for plugin test-one');
}
if (!file_put_contents(
$projectDir . '/wp-content/plugins/test-one/plugin.php',
<<< PHP
<?php
/**
* Plugin Name: Test One
*/
function test_one_loaded(){}
PHP
)) {
throw new \RuntimeException('Unable to create test plugin file for plugin test-one');
}
if (!mkdir($projectDir . '/wp-content/plugins/test-two', 0777, true)) {
throw new \RuntimeException('Unable to create test directory for plugin test-two');
}
if (!file_put_contents(
$projectDir . '/wp-content/plugins/test-two/plugin.php',
<<< PHP
<?php
/**
* Plugin Name: Test Two
*/
function test_two_loaded(){}
PHP
)) {
throw new \RuntimeException('Unable to create test plugin file for plugin test-two');
}

$this->config = [
'wpRootFolder' => $projectDir,
'dbUrl' => $installationDb->getDbUrl(),
'tablePrefix' => 'test_',
'plugins' => [
'test-one/plugin.php',
'test-two/plugin.php',
]
];
$wpLoader = $this->module();
$projectDirname = basename($projectDir);

$this->assertInIsolation(
static function () use ($wpLoader, $projectDir) {
chdir($projectDir);

$wpLoader->_initialize();

Assert::assertTrue(function_exists('test_one_loaded'));
Assert::assertTrue(function_exists('test_two_loaded'));
}
);
}

/**
* It should allow loading a plugin from an arbitrary path
*
Expand Down

0 comments on commit fe3e069

Please sign in to comment.