Created
February 6, 2021 19:07
-
-
Save nmfzone/54d159a77dc517ed98ebca5a8641f6b6 to your computer and use it in GitHub Desktop.
Start http server before Laravel Test
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
<?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