Last active
August 29, 2015 14:15
-
-
Save MartinMystikJonas/d97a25b9ddcc195036da to your computer and use it in GitHub Desktop.
TestingServer
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 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