Skip to content

Instantly share code, notes, and snippets.

@nmfzone
Created February 6, 2021 19:07
Show Gist options
  • Save nmfzone/54d159a77dc517ed98ebca5a8641f6b6 to your computer and use it in GitHub Desktop.
Save nmfzone/54d159a77dc517ed98ebca5a8641f6b6 to your computer and use it in GitHub Desktop.
Start http server before Laravel Test
<?php
namespace App\Console\Commands;
use Dotenv\Dotenv;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Arr;
use RuntimeException;
use Symfony\Component\Process\Exception\ProcessSignaledException;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\Process;
class TestCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'test:feature';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Run feature tests.';
protected $stopServer = false;
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
$this->setupTestEnvironment();
// $this->startHttpServer();
$appProcess = $this->startHttpServer(true);
$testProcess = (new Process([
$this->phpBinary(),
base_path('artisan'),
'test',
base_path('tests/Feature')
]))->setTimeout(null);
try {
$testProcess->setTty(true);
} catch (RuntimeException $e) {
$this->output->writeln('Warning: ' . $e->getMessage());
}
$this->withTestEnvironment(function () use ($appProcess, $testProcess) {
$isStopped = false;
try {
$testProcess->run(function ($type, $line) {
$this->output->write($line);
});
} catch (ProcessSignaledException $e) {
$appProcess->stop();
$testProcess->stop();
$isStopped = true;
if (extension_loaded('pcntl') && $e->getSignal() !== SIGINT) {
throw $e;
}
}
if (! $isStopped) {
$this->killChildsProcess($appProcess->getPid(), 15, false);
$appProcess->stop();
}
});
}
protected function startHttpServer($subSite = false): Process
{
if ($subSite) {
Arr::set($_SERVER, 'APP_HOST', env('APP_SUB_HOST'));
}
return $this->withTestEnvironment(function () {
$appPort = Arr::get(explode(':', env('APP_HOST')), '1', 8000);
$arguments = [
$this->phpBinary(),
base_path('artisan'),
'serve',
'--port=' . $appPort,
'--tries=0'
];
$process = (new Process(array_filter($arguments)))
->setTimeout(null);
try {
$process->start();
usleep(2 * 1000 * 1000);
$this->output->writeln($process->getOutput());
} catch (ProcessSignaledException $e) {
$this->killChildsProcess($process->getPid(), 15, false);
$process->stop();
if (extension_loaded('pcntl') && $e->getSignal() !== SIGINT) {
throw $e;
}
}
return $process;
});
}
protected function withTestEnvironment($callback)
{
try {
return $callback();
} finally {
$this->teardownTestEnviroment();
}
}
protected function setupSignalHandler()
{
if (extension_loaded('pcntl')) {
pcntl_async_signals(true);
pcntl_signal(SIGINT, function () {
$this->teardownTestEnviroment();
});
}
}
protected function setupTestEnvironment()
{
if (file_exists(base_path($this->testEnvironmentFile()))) {
if (file_get_contents(base_path('.env')) !==
file_get_contents(base_path($this->testEnvironmentFile()))) {
$this->backupEnvironment();
}
$this->refreshEnvironment();
}
$this->setupSignalHandler();
}
protected function backupEnvironment()
{
copy(base_path('.env'), base_path('.env.backup'));
copy(base_path($this->testEnvironmentFile()), base_path('.env'));
}
protected function teardownTestEnviroment()
{
if (file_exists(base_path($this->testEnvironmentFile())) &&
file_exists(base_path('.env.backup'))) {
$this->restoreEnvironment();
}
}
protected function restoreEnvironment()
{
copy(base_path('.env.backup'), base_path('.env'));
unlink(base_path('.env.backup'));
}
protected function refreshEnvironment(): void
{
Dotenv::createMutable(base_path())->load();
}
protected function testEnvironmentFile(): string
{
return '.env.testing';
}
protected function phpBinary(): string
{
return (new PhpExecutableFinder)->find(false);
}
protected function killChildsProcess($pid, int $signal, bool $throwException): bool
{
try {
if ('\\' === \DIRECTORY_SEPARATOR) {
$ret = proc_open(
sprintf('wmic process where ParentProcessId=%d get ProcessId', $pid),
[1 => ['pipe', 'w']],
$pipes
);
} else {
$ret = proc_open(
sprintf('pgrep -P %d', $pid),
[1 => ['pipe', 'w']],
$pipes
);
}
} catch (Exception $e) {
return false;
}
if ($ret && $output = fgets($pipes[1])) {
proc_close($ret);
$pids = explode("\n", $output);
foreach ($pids as $childPid) {
if (empty($childPid)) {
continue;
}
$childPid = (int) $childPid;
if ('\\' === \DIRECTORY_SEPARATOR) {
exec(
sprintf('taskkill /F /T /PID %d 2>&1', $childPid),
$output,
$exitCode
);
if ($exitCode) {
if ($throwException) {
throw new RuntimeException(sprintf(
'Unable to kill the child process (%s).',
is_array($output) ? implode(' ', $output): $output
));
}
return false;
}
} else {
if (\function_exists('posix_kill')) {
$ok = @posix_kill($childPid, $signal);
} elseif (
$ok = proc_open(
sprintf('kill -%d %d', $signal, $childPid),
[2 => ['pipe', 'w']],
$pipes
)
) {
$resource = $ok;
$ok = false === fgets($pipes[2]);
proc_close($resource);
}
if (! $ok) {
if ($throwException) {
throw new RuntimeException(sprintf(
'Error while sending signal "%s" to child.',
$signal
));
}
return false;
}
}
}
return true;
}
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment