Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
TestingServer
<?php
namespace Metis\Tester;
use Nette\Object;
use Tracy\Dumper;
class TestingServer extends Object {
public $onOutput;
/** @var resource */
private $serverProcess;
/** @var array */
private $pipes;
/** @var int */
private $port;
/** @var int */
private $defaultPort = 8801;
/** @var string */
private $script;
/** @var string */
private $logFile = "server.log";
/** @var string Server execution command template */
private $command = 'exec %PHP% -t %DOCROOT% -S localhost:%PORT% %SCRIPT% >> %LOG_FILE% 2>&1';
/**
* @param string $script Script to which server forwards all requests
*/
public function __construct($script) {
$this->script = realpath($script);
register_shutdown_function(array($this, "stop"));
}
/**
* Start server if not funning (automatically select avaliable port)
*
* @return int Selected port
*/
public function start() {
if($this->isRunning()) {
return $this->port;
}
$tryCount = 50; // number of ports which will by tried to run on
for($i = 0; $i < $tryCount; $i++) {
$port = $this->defaultPort + $i;
if($this->startOnPort($port)) {
break;
}
}
$this->check();
$this->onOutput("Testing server listening on port {$this->port} -> {$this->script}");
return $this->port;
}
/**
* Start server on specified port if not running
*
* @param int $port
* @return bool True if port is available
* @throws \RuntimeException Server unable to start
*/
public function startOnPort($port) {
if($this->isRunning()) {
if($this->port != $port) {
throw new \RuntimeException(
"Cannot start testing server on port $port. Already running on port {$this->port}"
);
}
return true;
}
$pidFile = $this->getPidFile($port);
if(file_exists($pidFile)) {
$this->onOutput("Testing server PID file $pidFile exists. Skipping port $port.");
return false;
}
$command = $this->command;
$command = str_replace("%PHP%", escapeshellcmd(PHP_BINARY), $command);
$command = str_replace("%PORT%", $port, $command);
$command = str_replace("%SCRIPT%", escapeshellarg($this->script), $command);
$command = str_replace("%DOCROOT%", escapeshellarg(dirname($this->script)), $command);
$command = str_replace("%LOG_FILE%", escapeshellarg($this->logFile), $command);
$this->onOutput($command);
$parts = explode(' ', $command);
$parts[0] = exec('which '.$parts[0]);
if($parts[0] != '') {
$command = implode(' ', $parts);
}
$descriptors = array(
0 => array(
'pipe',
'r'
), // Stdout
1 => array(
'pipe',
'w'
), // Stdin
2 => array(
'pipe',
'w'
) // Stderr
);
$this->serverProcess = proc_open($command, $descriptors, $this->pipes);
$this->port = $port;
sleep(1); // wait for server to start
$running = $this->isRunning();
if($running) {
$status = proc_get_status($this->serverProcess);
file_put_contents($this->getPidFile(), $status['pid']);
}
return $running;
}
/**
* Stop server
*
* @return void
* @throws \RuntimeException If server exit with error
*/
public function stop() {
if(!$this->isRunning()) {
return;
}
$this->onOutput("Stopping testing server");
fclose($this->pipes[1]);
fclose($this->pipes[2]);
proc_terminate($this->serverProcess, 9);
sleep(1); // wait till server shut down
$status = proc_get_status($this->serverProcess);
if($status['running']) {
throw new \RuntimeException(
"Testing server failed to exit. Status:" . Dumper::toText($status)
);
}
unlink($this->getPidFile());
$this->onOutput("Testing server stopped");
}
/**
* Check if server is running correctly. Throw exception if not.
*
* @return void
* @throws \RuntimeException If server not running
*/
public function check() {
if(!is_resource($this->serverProcess)) {
throw new \RuntimeException("Testing server failed to start.");
}
$status = proc_get_status($this->serverProcess);
if(!$status["running"]) {
throw new \RuntimeException(
"Testing server stopped unexpectedly with exit code ".$status['exitcode']
);
}
}
public function isRunning() {
if(!is_resource($this->serverProcess)) {
return false;
}
$status = proc_get_status($this->serverProcess);
return $status["running"];
}
public function getPort() {
$this->check();
return $this->port;
}
private function getPidFile($port = null) {
if($port == null) {
$port = $this->port;
}
return TEMP_DIR . "/server-{$port}.pid";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment