-
-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(Module/WPLoader) load WordPress in _beforeSuite method
fixes #744 Change the code of the `WPLoader` module to load WordPress, whether the `loadOnly` flag is set to `true` or `false`, in the `_beforeSuite` method. This removes the need, for the module, to rely on the dispatching, subscribing and need to do so, to the main Codeception system through the Codeception event dispatcher. Kudos to @lxbdr for the proposed solution.
- Loading branch information
Showing
4 changed files
with
367 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace lucatume\WPBrowser\Tests\Traits; | ||
|
||
use lucatume\WPBrowser\Opis\Closure\SerializableClosure; | ||
use lucatume\WPBrowser\Process\SerializableThrowable; | ||
|
||
/** | ||
* Class Fork. | ||
* | ||
* @since TBD | ||
* | ||
* @package lucatume\WPBrowser\Tests; | ||
*/ | ||
class Fork | ||
{ | ||
const DEFAULT_TERMINATOR = '__WPBROWSER_SEPARATOR__'; | ||
private \Closure $callback; | ||
private bool $quiet = false; | ||
/** | ||
* @var int<0, max> | ||
*/ | ||
private int $ipcSocketChunkSize = 2048; | ||
private string $terminator = self::DEFAULT_TERMINATOR; | ||
|
||
public static function executeClosure( | ||
\Closure $callback, | ||
bool $quiet = false, | ||
int $ipcSocketChunkSize = 2048, | ||
string $terminator = self::DEFAULT_TERMINATOR | ||
): mixed { | ||
return (new self($callback)) | ||
->setQuiet($quiet) | ||
->setIpcSocketChunkSize($ipcSocketChunkSize) | ||
->setTerminator($terminator) | ||
->execute(); | ||
} | ||
|
||
public function __construct(\Closure $callback) | ||
{ | ||
$this->callback = $callback; | ||
} | ||
|
||
public function setQuiet(bool $quiet): self | ||
{ | ||
$this->quiet = $quiet; | ||
return $this; | ||
} | ||
|
||
public function execute(): mixed | ||
{ | ||
if (!(function_exists('pcntl_fork') && function_exists('posix_kill'))) { | ||
throw new \RuntimeException('pcntl and posix extensions missing.'); | ||
} | ||
|
||
$sockets = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); | ||
|
||
if ($sockets === false) { | ||
throw new \RuntimeException('Failed to create socket pair'); | ||
} | ||
|
||
/** @var array{0: resource, 1: resource} $sockets */ | ||
|
||
$pid = pcntl_fork(); | ||
if ($pid === -1) { | ||
throw new \RuntimeException('Failed to fork'); | ||
} | ||
|
||
|
||
if ($pid === 0) { | ||
$this->executeFork($sockets); | ||
} | ||
|
||
return $this->executeMain($pid, $sockets); | ||
} | ||
|
||
public function setIpcSocketChunkSize(int $ipcSocketChunkSize): self | ||
{ | ||
if ($ipcSocketChunkSize < 0) { | ||
throw new \InvalidArgumentException('ipcSocketChunkSize must be a positive integer'); | ||
} | ||
|
||
$this->ipcSocketChunkSize = $ipcSocketChunkSize; | ||
return $this; | ||
} | ||
|
||
public function setTerminator(string $terminator): self | ||
{ | ||
$this->terminator = $terminator; | ||
return $this; | ||
} | ||
|
||
/** | ||
* @param array{0: resource, 1: resource} $sockets | ||
*/ | ||
private function executeFork(array $sockets): void | ||
{ | ||
fclose($sockets[1]); | ||
$ipcSocket = $sockets[0]; | ||
$pid = getmypid(); | ||
$didWriteTerminator = false; | ||
$terminator = $this->terminator; | ||
|
||
if ($pid === false) { | ||
die('Failed to get pid'); | ||
} | ||
|
||
if ($this->quiet) { | ||
fclose(STDOUT); | ||
fclose(STDERR); | ||
} | ||
|
||
register_shutdown_function(static function () use ($pid, $ipcSocket, &$didWriteTerminator, $terminator) { | ||
if (!$didWriteTerminator) { | ||
fwrite($ipcSocket, $terminator); | ||
$didWriteTerminator = true; | ||
} | ||
fclose($ipcSocket); | ||
/** @noinspection PhpComposerExtensionStubsInspection */ | ||
posix_kill($pid, 9 /* SIGKILL */); | ||
}); | ||
|
||
try { | ||
$result = ($this->callback)(); | ||
$resultClosure = new SerializableClosure(static function () use ($result) { | ||
return $result; | ||
}); | ||
$resultPayload = serialize($resultClosure); | ||
} catch (\Throwable $throwable) { | ||
$resultPayload = serialize(new SerializableThrowable($throwable)); | ||
} finally { | ||
if (!isset($resultPayload)) { | ||
// Something went wrong. | ||
fwrite($ipcSocket, serialize(null)); | ||
fwrite($ipcSocket, $this->terminator); | ||
$didWriteTerminator = true; | ||
/** @noinspection PhpComposerExtensionStubsInspection */ | ||
posix_kill($pid, 9 /* SIGKILL */); | ||
} | ||
} | ||
|
||
$offset = 0; | ||
while (true) { | ||
$chunk = substr($resultPayload, $offset, $this->ipcSocketChunkSize); | ||
|
||
if ($chunk === '') { | ||
break; | ||
} | ||
|
||
fwrite($ipcSocket, $chunk); | ||
$offset += $this->ipcSocketChunkSize; | ||
} | ||
fwrite($ipcSocket, $this->terminator); | ||
$didWriteTerminator = true; | ||
fclose($ipcSocket); | ||
|
||
// Kill the child process now with a signal that will not run shutdown handlers. | ||
/** @noinspection PhpComposerExtensionStubsInspection */ | ||
posix_kill($pid, 9 /* SIGKILL */); | ||
} | ||
|
||
/** | ||
* @param array{0: resource, 1: resource} $sockets | ||
* @throws \Throwable | ||
*/ | ||
private function executeMain(int $pid, array $sockets): mixed | ||
{ | ||
fclose($sockets[0]); | ||
$resultPayload = ''; | ||
|
||
/** @noinspection PhpComposerExtensionStubsInspection */ | ||
while (pcntl_wait($status, 1 /* WNOHANG */) <= 0) { | ||
$chunk = fread($sockets[1], $this->ipcSocketChunkSize); | ||
$resultPayload .= $chunk; | ||
} | ||
|
||
while (!str_ends_with($resultPayload, $this->terminator)) { | ||
$chunk = fread($sockets[1], $this->ipcSocketChunkSize); | ||
$resultPayload .= $chunk; | ||
} | ||
|
||
fclose($sockets[1]); | ||
|
||
if (str_ends_with($resultPayload, $this->terminator)) { | ||
$resultPayload = substr($resultPayload, 0, -strlen($this->terminator)); | ||
} | ||
|
||
try { | ||
/** @var SerializableClosure|SerializableThrowable $unserializedPayload */ | ||
$unserializedPayload = @unserialize($resultPayload); | ||
$result = $unserializedPayload instanceof SerializableThrowable ? | ||
$unserializedPayload->getThrowable() : $unserializedPayload->getClosure()(); | ||
} catch (\Throwable $t) { | ||
$result = $resultPayload; | ||
} | ||
|
||
if ($result instanceof \Throwable) { | ||
throw $result; | ||
} | ||
|
||
/** @noinspection PhpComposerExtensionStubsInspection */ | ||
posix_kill($pid, 9 /* SIGKILL */); | ||
|
||
return $result; | ||
} | ||
} |
Oops, something went wrong.