From 62a57a8bd4c132de4d938ccf2cb192c298971baf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20H=C5=AFla?= Date: Sun, 10 Apr 2016 21:20:45 +0200 Subject: [PATCH 1/9] Helpers::escapeArgs(): chars '=, /, :' do not need to escape --- src/Framework/Helpers.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Framework/Helpers.php b/src/Framework/Helpers.php index dc126ed7..00b60e0a 100644 --- a/src/Framework/Helpers.php +++ b/src/Framework/Helpers.php @@ -81,7 +81,7 @@ public static function errorTypeToString($type) */ public static function escapeArg($s) { - if (preg_match('#^[a-z0-9._-]+\z#i', $s)) { + if (preg_match('#^[a-z0-9._=/:-]+\z#i', $s)) { return $s; } From 9ed983aaa8a0599351de33546b04627462565701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20H=C5=AFla?= Date: Sun, 10 Apr 2016 20:15:47 +0200 Subject: [PATCH 2/9] PhpInterpreter: renamed methods (BC break) - hasXdebug() => canMeasureCodeCoverage() - getErrorOutput() => getStartupError() --- src/Runner/CliTester.php | 6 +++--- src/Runner/HhvmPhpInterpreter.php | 4 ++-- src/Runner/PhpInterpreter.php | 4 ++-- src/Runner/ZendPhpDbgInterpreter.php | 4 ++-- src/Runner/ZendPhpInterpreter.php | 4 ++-- tests/Runner/HhvmPhpInterpreter.phpt | 2 +- tests/Runner/ZendPhpExecutable.phpt | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Runner/CliTester.php b/src/Runner/CliTester.php index 2c95bfa0..5919193f 100644 --- a/src/Runner/CliTester.php +++ b/src/Runner/CliTester.php @@ -183,8 +183,8 @@ private function createPhpInterpreter() $this->interpreter = new ZendPhpInterpreter($this->options['-p'], $args); } - if ($this->interpreter->getErrorOutput()) { - echo Dumper::color('red', 'PHP startup error: ' . $this->interpreter->getErrorOutput()) . "\n"; + if ($this->interpreter->getStartupError()) { + echo Dumper::color('red', 'PHP startup error: ' . $this->interpreter->getStartupError()) . "\n"; if ($this->interpreter->isCgi()) { echo "(note that PHP CLI generates better error messages)\n"; } @@ -230,7 +230,7 @@ private function createRunner() /** @return string */ private function prepareCodeCoverage() { - if (!$this->interpreter->hasXdebug()) { + if (!$this->interpreter->canMeasureCodeCoverage()) { $alternative = PHP_VERSION_ID >= 70000 ? ' or phpdbg SAPI' : ''; throw new \Exception("Code coverage functionality requires Xdebug extension$alternative (used {$this->interpreter->getCommandLine()})"); } diff --git a/src/Runner/HhvmPhpInterpreter.php b/src/Runner/HhvmPhpInterpreter.php index 345d565b..62ceff70 100644 --- a/src/Runner/HhvmPhpInterpreter.php +++ b/src/Runner/HhvmPhpInterpreter.php @@ -80,7 +80,7 @@ public function getVersion() /** * @return bool */ - public function hasXdebug() + public function canMeasureCodeCoverage() { return FALSE; } @@ -98,7 +98,7 @@ public function isCgi() /** * @return string */ - public function getErrorOutput() + public function getStartupError() { return $this->error; } diff --git a/src/Runner/PhpInterpreter.php b/src/Runner/PhpInterpreter.php index 401b5982..c9948b6e 100644 --- a/src/Runner/PhpInterpreter.php +++ b/src/Runner/PhpInterpreter.php @@ -24,7 +24,7 @@ function getVersion(); /** * @return bool */ - function hasXdebug(); + function canMeasureCodeCoverage(); /** * @return bool @@ -34,6 +34,6 @@ function isCgi(); /** * @return string */ - function getErrorOutput(); + function getStartupError(); } diff --git a/src/Runner/ZendPhpDbgInterpreter.php b/src/Runner/ZendPhpDbgInterpreter.php index cdc60b67..158cd9cd 100644 --- a/src/Runner/ZendPhpDbgInterpreter.php +++ b/src/Runner/ZendPhpDbgInterpreter.php @@ -76,7 +76,7 @@ public function getVersion() /** * @return bool */ - public function hasXdebug() + public function canMeasureCodeCoverage() { return TRUE; } @@ -94,7 +94,7 @@ public function isCgi() /** * @return string */ - public function getErrorOutput() + public function getStartupError() { return $this->error; } diff --git a/src/Runner/ZendPhpInterpreter.php b/src/Runner/ZendPhpInterpreter.php index a00d8ceb..2ede5499 100644 --- a/src/Runner/ZendPhpInterpreter.php +++ b/src/Runner/ZendPhpInterpreter.php @@ -84,7 +84,7 @@ public function getVersion() /** * @return bool */ - public function hasXdebug() + public function canMeasureCodeCoverage() { return $this->xdebug; } @@ -102,7 +102,7 @@ public function isCgi() /** * @return string */ - public function getErrorOutput() + public function getStartupError() { return $this->error; } diff --git a/tests/Runner/HhvmPhpInterpreter.phpt b/tests/Runner/HhvmPhpInterpreter.phpt index a682b14a..10deb60d 100644 --- a/tests/Runner/HhvmPhpInterpreter.phpt +++ b/tests/Runner/HhvmPhpInterpreter.phpt @@ -13,5 +13,5 @@ $executable = createInterpreter(); Assert::contains(PHP_BINARY, $executable->getCommandLine()); Assert::same(PHP_VERSION, $executable->getVersion()); -Assert::same(FALSE, $executable->hasXdebug()); +Assert::same(FALSE, $executable->canMeasureCodeCoverage()); Assert::same(FALSE, $executable->isCgi()); diff --git a/tests/Runner/ZendPhpExecutable.phpt b/tests/Runner/ZendPhpExecutable.phpt index 20944e5f..9e9a8d1e 100644 --- a/tests/Runner/ZendPhpExecutable.phpt +++ b/tests/Runner/ZendPhpExecutable.phpt @@ -13,5 +13,5 @@ $interpreter = createInterpreter(); Assert::contains(PHP_BINARY, $interpreter->getCommandLine()); Assert::same(PHP_VERSION, $interpreter->getVersion()); -Assert::same(extension_loaded('xdebug') || defined('PHPDBG_VERSION'), $interpreter->hasXdebug()); +Assert::same(extension_loaded('xdebug') || defined('PHPDBG_VERSION'), $interpreter->canMeasureCodeCoverage()); Assert::same(strpos(PHP_SAPI, 'cgi') !== FALSE, $interpreter->isCgi()); From 80e04315882491299ade76ea7b1f31e0d562d9f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20H=C5=AFla?= Date: Sun, 10 Apr 2016 20:33:41 +0200 Subject: [PATCH 3/9] PhpInterpreter: added addPhpIniOption() (BC break) --- src/Runner/HhvmPhpInterpreter.php | 12 +++++++++++- src/Runner/PhpInterpreter.php | 6 ++++++ src/Runner/TestHandler.php | 3 ++- src/Runner/ZendPhpDbgInterpreter.php | 12 +++++++++++- src/Runner/ZendPhpInterpreter.php | 12 +++++++++++- tests/Runner/Runner.multiple-fails.phpt | 3 ++- 6 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/Runner/HhvmPhpInterpreter.php b/src/Runner/HhvmPhpInterpreter.php index 62ceff70..8b259bd4 100644 --- a/src/Runner/HhvmPhpInterpreter.php +++ b/src/Runner/HhvmPhpInterpreter.php @@ -16,7 +16,7 @@ class HhvmPhpInterpreter implements PhpInterpreter { /** @var string HHVM arguments */ - public $arguments; + private $arguments; /** @var string HHVM executable */ private $path; @@ -59,6 +59,16 @@ public function __construct($path, $args = NULL) } + /** + * @param string + * @param string + */ + public function addPhpIniOption($name, $value = NULL) + { + $this->arguments .= ' -d ' . Helpers::escapeArg($name . ($value === NULL ? '' : "=$value")); + } + + /** * @return string */ diff --git a/src/Runner/PhpInterpreter.php b/src/Runner/PhpInterpreter.php index c9948b6e..16a4d1f4 100644 --- a/src/Runner/PhpInterpreter.php +++ b/src/Runner/PhpInterpreter.php @@ -11,6 +11,12 @@ interface PhpInterpreter { + /** + * @param string + * @param string + */ + function addPhpIniOption($name, $value = NULL); + /** * @return string */ diff --git a/src/Runner/TestHandler.php b/src/Runner/TestHandler.php index fabac6c5..d8e1c9ab 100644 --- a/src/Runner/TestHandler.php +++ b/src/Runner/TestHandler.php @@ -113,7 +113,8 @@ private function initiatePhpVersion($version, PhpInterpreter $interpreter) private function initiatePhpIni($value, PhpInterpreter $interpreter) { - $interpreter->arguments .= ' -d ' . Helpers::escapeArg($value); + list($name, $value) = explode('=', $value, 2) + [1 => NULL]; + $interpreter->addPhpIniOption($name, $value); } diff --git a/src/Runner/ZendPhpDbgInterpreter.php b/src/Runner/ZendPhpDbgInterpreter.php index 158cd9cd..474e4092 100644 --- a/src/Runner/ZendPhpDbgInterpreter.php +++ b/src/Runner/ZendPhpDbgInterpreter.php @@ -16,7 +16,7 @@ class ZendPhpDbgInterpreter implements PhpInterpreter { /** @var string PHP arguments */ - public $arguments; + private $arguments; /** @var string PHP executable */ private $path; @@ -55,6 +55,16 @@ public function __construct($path, $args = NULL) } + /** + * @param string + * @param string + */ + public function addPhpIniOption($name, $value = NULL) + { + $this->arguments .= ' -d ' . Helpers::escapeArg($name . ($value === NULL ? '' : "=$value")); + } + + /** * @return string */ diff --git a/src/Runner/ZendPhpInterpreter.php b/src/Runner/ZendPhpInterpreter.php index 2ede5499..57d40aa3 100644 --- a/src/Runner/ZendPhpInterpreter.php +++ b/src/Runner/ZendPhpInterpreter.php @@ -16,7 +16,7 @@ class ZendPhpInterpreter implements PhpInterpreter { /** @var string PHP arguments */ - public $arguments; + private $arguments; /** @var string PHP executable */ private $path; @@ -63,6 +63,16 @@ public function __construct($path, $args = NULL) } + /** + * @param string + * @param string + */ + public function addPhpIniOption($name, $value = NULL) + { + $this->arguments .= ' -d ' . Helpers::escapeArg($name . ($value === NULL ? '' : "=$value")); + } + + /** * @return string */ diff --git a/tests/Runner/Runner.multiple-fails.phpt b/tests/Runner/Runner.multiple-fails.phpt index 89d512af..f31cd7c6 100644 --- a/tests/Runner/Runner.multiple-fails.phpt +++ b/tests/Runner/Runner.multiple-fails.phpt @@ -24,7 +24,8 @@ class Logger implements Tester\Runner\OutputHandler } $interpreter = createInterpreter(); -$interpreter->arguments .= ' -d display_errors=On -d html_errors=Off'; +$interpreter->addPhpIniOption('display_errors', 'on'); +$interpreter->addPhpIniOption('html_errors', 'off'); $runner = new Runner($interpreter); $runner->paths[] = __DIR__ . '/multiple-fails/*.phptx'; From a23ff5513c583a834b751fd4e7d62d23c69d74cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20H=C5=AFla?= Date: Sun, 10 Apr 2016 20:37:57 +0200 Subject: [PATCH 4/9] PHP interpreters use info.php to fetch info about self --- src/Runner/HhvmPhpInterpreter.php | 10 +++--- src/Runner/ZendPhpDbgInterpreter.php | 18 ++++++----- src/Runner/ZendPhpInterpreter.php | 23 ++++++++------ src/Runner/info.php | 47 +++++++++++++++------------- 4 files changed, 56 insertions(+), 42 deletions(-) diff --git a/src/Runner/HhvmPhpInterpreter.php b/src/Runner/HhvmPhpInterpreter.php index 8b259bd4..1cf71cc3 100644 --- a/src/Runner/HhvmPhpInterpreter.php +++ b/src/Runner/HhvmPhpInterpreter.php @@ -34,8 +34,10 @@ class HhvmPhpInterpreter implements PhpInterpreter public function __construct($path, $args = NULL) { $this->path = Helpers::escapeArg($path); + $this->arguments = ' --php -d hhvm.log.always_log_unhandled_exceptions=false' . $args; // HHVM issue #3019 + $proc = proc_open( - "$this->path --php $args -r " . Helpers::escapeArg('echo HHVM_VERSION . "|" . PHP_VERSION;'), + "$this->path $this->arguments " . Helpers::escapeArg(__DIR__ . '/info.php' . ' serialized'), [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], $pipes, NULL, @@ -47,15 +49,15 @@ public function __construct($path, $args = NULL) if (proc_close($proc)) { throw new \Exception("Unable to run '$path': " . preg_replace('#[\r\n ]+#', ' ', $this->error)); - } elseif (count($tmp = explode('|', $output)) !== 2) { + } elseif (!($info = @unserialize($output))) { throw new \Exception("Unable to detect HHVM version (output: $output)."); } - list($this->version, $this->phpVersion) = $tmp; + $this->phpVersion = $info->version; + $this->version = $info->hhvmVersion; if (version_compare($this->version, '3.3.0', '<')) { throw new \Exception('HHVM below version 3.3.0 is not supported.'); } - $this->arguments = ' --php -d hhvm.log.always_log_unhandled_exceptions=false' . ($args ? " $args" : ''); // HHVM issue #3019 } diff --git a/src/Runner/ZendPhpDbgInterpreter.php b/src/Runner/ZendPhpDbgInterpreter.php index 474e4092..7ec42bc9 100644 --- a/src/Runner/ZendPhpDbgInterpreter.php +++ b/src/Runner/ZendPhpDbgInterpreter.php @@ -31,8 +31,10 @@ class ZendPhpDbgInterpreter implements PhpInterpreter public function __construct($path, $args = NULL) { $this->path = Helpers::escapeArg($path); + $this->arguments = ' -qrrb -S cli' . $args; + $proc = proc_open( - "$this->path -n $args -V", + "$this->path -n $this->arguments " . Helpers::escapeArg(__DIR__ . '/info.php') . ' serialized', [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], $pipes, NULL, @@ -40,18 +42,18 @@ public function __construct($path, $args = NULL) ['bypass_shell' => TRUE] ); $output = stream_get_contents($pipes[1]); - $this->error = trim(stream_get_contents($pipes[2])); + if (proc_close($proc)) { throw new \Exception("Unable to run '$path': " . preg_replace('#[\r\n ]+#', ' ', $this->error)); - } elseif (!preg_match('#^PHP ([\w.-]+)#im', $output, $matches)) { + } elseif (!($info = @unserialize($output))) { throw new \Exception("Unable to detect PHP version (output: $output)."); - } elseif (version_compare($matches[1], '7.0.0', '<')) { - throw new \Exception('Unable to use phpdbg on PHP < 7.0.0.'); } - $this->version = $matches[1]; - $this->arguments = $args; + $this->version = $info->version; + if (version_compare($this->version, '7.0.0', '<')) { + throw new \Exception('Unable to use phpdbg on PHP < 7.0.0.'); + } } @@ -70,7 +72,7 @@ public function addPhpIniOption($name, $value = NULL) */ public function getCommandLine() { - return $this->path . ' -qrrb -S cli' . $this->arguments; + return $this->path . $this->arguments; } diff --git a/src/Runner/ZendPhpInterpreter.php b/src/Runner/ZendPhpInterpreter.php index 57d40aa3..f3b41b2b 100644 --- a/src/Runner/ZendPhpInterpreter.php +++ b/src/Runner/ZendPhpInterpreter.php @@ -37,8 +37,10 @@ class ZendPhpInterpreter implements PhpInterpreter public function __construct($path, $args = NULL) { $this->path = Helpers::escapeArg($path); + $this->arguments = $args; + $proc = proc_open( - "$this->path -n $args -v", // -v must be the last + "$this->path -n $this->arguments " . Helpers::escapeArg(__DIR__ . '/info.php') . ' serialized', [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], $pipes, NULL, @@ -47,19 +49,22 @@ public function __construct($path, $args = NULL) ); $output = stream_get_contents($pipes[1]); $this->error = trim(stream_get_contents($pipes[2])); + if (proc_close($proc)) { throw new \Exception("Unable to run '$path': " . preg_replace('#[\r\n ]+#', ' ', $this->error)); - } elseif (!preg_match('#^PHP (\S+).*c(g|l)i#i', $output, $matches)) { - throw new \Exception("Unable to detect PHP version (output: $output)."); } - $this->version = $matches[1]; - $this->cgi = strcasecmp($matches[2], 'g') === 0; - $this->arguments = $args; + $this->cgi = stripos($output, 'cgi') !== FALSE; + if ($this->cgi) { + list(, $output) = explode("\r\n\r\n", $output, 2); + } + + if (!($info = @unserialize($output))) { + throw new \Exception("Unable to detect PHP version (output: $output)."); + } - $job = new Job(__DIR__ . '/info.php', $this, ['xdebug']); - $job->run(); - $this->xdebug = !$job->getExitCode(); + $this->version = $info->version; + $this->xdebug = in_array('xdebug', $info->extensions, TRUE); } diff --git a/src/Runner/info.php b/src/Runner/info.php index 13ddba8d..76fb4056 100644 --- a/src/Runner/info.php +++ b/src/Runner/info.php @@ -4,32 +4,37 @@ * @internal */ -if (isset($_SERVER['argv'][1])) { - die(extension_loaded($_SERVER['argv'][1]) ? 0 : 1); -} - -$iniFiles = array_merge( - ($tmp = php_ini_loaded_file()) === FALSE ? [] : [$tmp], - (function_exists('php_ini_scanned_files') && strlen($tmp = php_ini_scanned_files())) ? explode(",\n", trim($tmp)) : [] -); - +$isPhpDbg = defined('PHPDBG_VERSION'); +$isHhvm = defined('HHVM_VERSION'); $extensions = get_loaded_extensions(); natcasesort($extensions); -$isHhvm = defined('HHVM_VERSION'); - -$values = [ - 'PHP binary' => defined('PHP_BINARY') ? PHP_BINARY : '(not available)', - - 'PHP version' . ($isHhvm ? '; HHVM version' : '') => PHP_VERSION . ' (' . PHP_SAPI . ')' . ($isHhvm ? '; ' . HHVM_VERSION : ''), - - 'Loaded php.ini files' => count($iniFiles) ? implode(', ', $iniFiles) : ($isHhvm ? '(unable to detect under HHVM)' : '(none)'), - - 'Loaded extensions' => count($extensions) ? implode(', ', $extensions) : '(none)', +$info = (object) [ + 'binary' => defined('PHP_BINARY') ? PHP_BINARY : NULL, + 'version' => PHP_VERSION, + 'phpDbgVersion' => $isPhpDbg ? PHPDBG_VERSION : NULL, + 'sapi' => PHP_SAPI, + 'hhvmVersion' => $isHhvm ? HHVM_VERSION : NULL, + 'iniFiles' => array_merge( + ($tmp = php_ini_loaded_file()) === FALSE ? [] : [$tmp], + (function_exists('php_ini_scanned_files') && strlen($tmp = php_ini_scanned_files())) ? explode(",\n", trim($tmp)) : [] + ), + 'extensions' => $extensions, ]; -foreach ($values as $title => $value) { +if (isset($_SERVER['argv'][1])) { + echo serialize($info); + die(); +} + +foreach ([ + 'PHP binary' => $info->binary ?: '(not available)', + 'PHP version' . ($isPhpDbg ? '; PHPDBG version' : '') . ($isHhvm ? '; HHVM version' : '') + => "$info->version ($info->sapi)" . ($isPhpDbg ? "; $info->phpDbgVersion" : '') . ($isHhvm ? "; $info->hhvmVersion" : ''), + 'Loaded php.ini files' => count($info->iniFiles) ? implode(', ', $info->iniFiles) : ($isHhvm ? '(unable to detect under HHVM)' : '(none)'), + 'Loaded extensions' => count($info->extensions) ? implode(', ', $info->extensions) : '(none)', +] as $title => $value) { echo "\033[1;32m$title\033[0m:\n$value\n\n"; } -echo "\n\n"; +echo "\n"; From f96569bf369f80642c275116bb637585f0f2d3cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20H=C5=AFla?= Date: Wed, 30 Mar 2016 07:56:42 +0200 Subject: [PATCH 5/9] Job: -n argument moved to PHP interpreters --- src/Runner/HhvmPhpInterpreter.php | 2 +- src/Runner/Job.php | 3 ++- src/Runner/ZendPhpDbgInterpreter.php | 4 ++-- src/Runner/ZendPhpInterpreter.php | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Runner/HhvmPhpInterpreter.php b/src/Runner/HhvmPhpInterpreter.php index 1cf71cc3..b0c22f70 100644 --- a/src/Runner/HhvmPhpInterpreter.php +++ b/src/Runner/HhvmPhpInterpreter.php @@ -34,7 +34,7 @@ class HhvmPhpInterpreter implements PhpInterpreter public function __construct($path, $args = NULL) { $this->path = Helpers::escapeArg($path); - $this->arguments = ' --php -d hhvm.log.always_log_unhandled_exceptions=false' . $args; // HHVM issue #3019 + $this->arguments = ' --php -n -d hhvm.log.always_log_unhandled_exceptions=false' . $args; // HHVM issue #3019 $proc = proc_open( "$this->path $this->arguments " . Helpers::escapeArg(__DIR__ . '/info.php' . ' serialized'), diff --git a/src/Runner/Job.php b/src/Runner/Job.php index e8a05d33..72b84e23 100644 --- a/src/Runner/Job.php +++ b/src/Runner/Job.php @@ -8,6 +8,7 @@ namespace Tester\Runner; use Tester\Environment; +use Tester\Helpers; /** @@ -83,7 +84,7 @@ public function run($flags = NULL) putenv(Environment::COLORS . '=' . (int) Environment::$useColors); $this->proc = proc_open( $this->interpreter->getCommandLine() - . ' -n -d register_argc_argv=on ' . \Tester\Helpers::escapeArg($this->file) . ' ' . implode(' ', $this->args), + . ' -d register_argc_argv=on ' . Helpers::escapeArg($this->file) . ' ' . implode(' ', $this->args), [ ['pipe', 'r'], ['pipe', 'w'], diff --git a/src/Runner/ZendPhpDbgInterpreter.php b/src/Runner/ZendPhpDbgInterpreter.php index 7ec42bc9..57c64a48 100644 --- a/src/Runner/ZendPhpDbgInterpreter.php +++ b/src/Runner/ZendPhpDbgInterpreter.php @@ -31,10 +31,10 @@ class ZendPhpDbgInterpreter implements PhpInterpreter public function __construct($path, $args = NULL) { $this->path = Helpers::escapeArg($path); - $this->arguments = ' -qrrb -S cli' . $args; + $this->arguments = ' -qrrb -S cli -n' . $args; $proc = proc_open( - "$this->path -n $this->arguments " . Helpers::escapeArg(__DIR__ . '/info.php') . ' serialized', + "$this->path $this->arguments " . Helpers::escapeArg(__DIR__ . '/info.php') . ' serialized', [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], $pipes, NULL, diff --git a/src/Runner/ZendPhpInterpreter.php b/src/Runner/ZendPhpInterpreter.php index f3b41b2b..561815b3 100644 --- a/src/Runner/ZendPhpInterpreter.php +++ b/src/Runner/ZendPhpInterpreter.php @@ -37,10 +37,10 @@ class ZendPhpInterpreter implements PhpInterpreter public function __construct($path, $args = NULL) { $this->path = Helpers::escapeArg($path); - $this->arguments = $args; + $this->arguments = ' -n' . $args; $proc = proc_open( - "$this->path -n $this->arguments " . Helpers::escapeArg(__DIR__ . '/info.php') . ' serialized', + "$this->path $this->arguments " . Helpers::escapeArg(__DIR__ . '/info.php') . ' serialized', [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], $pipes, NULL, From 33102662bd5984bb4b13cb0732bc886f829652b9 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 17 Apr 2016 18:00:05 +0200 Subject: [PATCH 6/9] Interpreters and factory CliTester::createPhpInterpreter() merged to class PhpInterpreter (BC break) --- src/Runner/CliTester.php | 33 +---- src/Runner/HhvmPhpInterpreter.php | 118 ----------------- src/Runner/PhpInterpreter.php | 116 ++++++++++++++-- src/Runner/ZendPhpDbgInterpreter.php | 114 ---------------- src/Runner/ZendPhpInterpreter.php | 125 ------------------ src/Runner/info.php | 1 + src/tester.php | 3 - ...erpreter.phpt => PhpInterpreter.HHVM.phpt} | 0 ...ecutable.phpt => PhpInterpreter.Zend.phpt} | 0 tests/bootstrap.php | 19 ++- 10 files changed, 119 insertions(+), 410 deletions(-) delete mode 100644 src/Runner/HhvmPhpInterpreter.php delete mode 100644 src/Runner/ZendPhpDbgInterpreter.php delete mode 100644 src/Runner/ZendPhpInterpreter.php rename tests/Runner/{HhvmPhpInterpreter.phpt => PhpInterpreter.HHVM.phpt} (100%) rename tests/Runner/{ZendPhpExecutable.phpt => PhpInterpreter.Zend.phpt} (100%) diff --git a/src/Runner/CliTester.php b/src/Runner/CliTester.php index 5919193f..d0dcc092 100644 --- a/src/Runner/CliTester.php +++ b/src/Runner/CliTester.php @@ -157,37 +157,10 @@ private function createPhpInterpreter() $args .= ' -d ' . Helpers::escapeArg($item); } - // Is the executable Zend PHP or HHVM? - $proc = @proc_open( // @ is escalated to exception - $this->options['-p'] . ' --version', - [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], - $pipes, - NULL, - NULL, - ['bypass_shell' => TRUE] - ); - if ($proc === FALSE) { - throw new \Exception('Cannot run PHP interpreter ' . $this->options['-p'] . '. Use -p option.'); - } - $output = stream_get_contents($pipes[1]); - $error = stream_get_contents($pipes[2]); - if (proc_close($proc)) { - throw new \Exception("Unable to run '{$this->options['-p']}': " . preg_replace('#[\r\n ]+#', ' ', $error)); - } - - if (preg_match('#HipHop VM#', $output)) { - $this->interpreter = new HhvmPhpInterpreter($this->options['-p'], $args); - } elseif (strpos($output, 'phpdbg') !== FALSE) { - $this->interpreter = new ZendPhpDbgInterpreter($this->options['-p'], $args); - } else { - $this->interpreter = new ZendPhpInterpreter($this->options['-p'], $args); - } + $this->interpreter = new PhpInterpreter($this->options['-p'], $args); - if ($this->interpreter->getStartupError()) { - echo Dumper::color('red', 'PHP startup error: ' . $this->interpreter->getStartupError()) . "\n"; - if ($this->interpreter->isCgi()) { - echo "(note that PHP CLI generates better error messages)\n"; - } + if ($error = $this->interpreter->getStartupError()) { + echo Dumper::color('red', "PHP startup error: $error") . "\n"; } } diff --git a/src/Runner/HhvmPhpInterpreter.php b/src/Runner/HhvmPhpInterpreter.php deleted file mode 100644 index b0c22f70..00000000 --- a/src/Runner/HhvmPhpInterpreter.php +++ /dev/null @@ -1,118 +0,0 @@ -path = Helpers::escapeArg($path); - $this->arguments = ' --php -n -d hhvm.log.always_log_unhandled_exceptions=false' . $args; // HHVM issue #3019 - - $proc = proc_open( - "$this->path $this->arguments " . Helpers::escapeArg(__DIR__ . '/info.php' . ' serialized'), - [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], - $pipes, - NULL, - NULL, - ['bypass_shell' => TRUE] - ); - $output = stream_get_contents($pipes[1]); - $this->error = trim(stream_get_contents($pipes[2])); - - if (proc_close($proc)) { - throw new \Exception("Unable to run '$path': " . preg_replace('#[\r\n ]+#', ' ', $this->error)); - } elseif (!($info = @unserialize($output))) { - throw new \Exception("Unable to detect HHVM version (output: $output)."); - } - - $this->phpVersion = $info->version; - $this->version = $info->hhvmVersion; - if (version_compare($this->version, '3.3.0', '<')) { - throw new \Exception('HHVM below version 3.3.0 is not supported.'); - } - } - - - /** - * @param string - * @param string - */ - public function addPhpIniOption($name, $value = NULL) - { - $this->arguments .= ' -d ' . Helpers::escapeArg($name . ($value === NULL ? '' : "=$value")); - } - - - /** - * @return string - */ - public function getCommandLine() - { - return $this->path . $this->arguments; - } - - - /** - * @return string - */ - public function getVersion() - { - return $this->phpVersion; - } - - - /** - * @return bool - */ - public function canMeasureCodeCoverage() - { - return FALSE; - } - - - /** - * @return bool - */ - public function isCgi() - { - return FALSE; - } - - - /** - * @return string - */ - public function getStartupError() - { - return $this->error; - } - -} diff --git a/src/Runner/PhpInterpreter.php b/src/Runner/PhpInterpreter.php index 16a4d1f4..edaadc86 100644 --- a/src/Runner/PhpInterpreter.php +++ b/src/Runner/PhpInterpreter.php @@ -7,39 +7,137 @@ namespace Tester\Runner; +use Tester\Helpers; -interface PhpInterpreter + +/** + * PHP command-line executable. + */ +class PhpInterpreter { + /** @var string PHP arguments */ + private $arguments; + + /** @var string PHP executable */ + private $path; + + /** @var bool is CGI? */ + private $cgi; + + /** @var \stdClass created by info.php */ + private $info; + + /** @var string */ + private $error; + + + public function __construct($path, $args = NULL) + { + $this->path = Helpers::escapeArg($path); + $proc = @proc_open( // @ is escalated to exception + $this->path . ' --version', + [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], + $pipes, + NULL, + NULL, + ['bypass_shell' => TRUE] + ); + if ($proc === FALSE) { + throw new \Exception("Cannot run PHP interpreter $path. Use -p option."); + } + $output = stream_get_contents($pipes[1]); + proc_close($proc); + + $this->arguments = ' -n ' . $args; + if (preg_match('#HipHop VM#', $output)) { + $this->arguments = ' --php' . $this->arguments . ' -d hhvm.log.always_log_unhandled_exceptions=false'; // HHVM issue #3019 + } elseif (strpos($output, 'phpdbg') !== FALSE) { + $this->arguments = ' -qrrb -S cli' . $this->arguments; + } + + $proc = proc_open( + "$this->path $this->arguments " . Helpers::escapeArg(__DIR__ . '/info.php') . ' serialized', + [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], + $pipes, + NULL, + NULL, + ['bypass_shell' => TRUE] + ); + $output = stream_get_contents($pipes[1]); + $this->error = trim(stream_get_contents($pipes[2])); + if (proc_close($proc)) { + throw new \Exception("Unable to run $path: " . preg_replace('#[\r\n ]+#', ' ', $this->error)); + } + + $parts = explode("\r\n\r\n", $output, 2); + $this->cgi = count($parts) === 2; + if (!($this->info = @unserialize($parts[$this->cgi]))) { + throw new \Exception("Unable to detect PHP version (output: $output)."); + + } elseif ($this->info->hhvmVersion && version_compare($this->info->hhvmVersion, '3.3.0', '<')) { + throw new \Exception('HHVM below version 3.3.0 is not supported.'); + + } elseif ($this->info->phpDbgVersion && version_compare($this->info->version, '7.0.0', '<')) { + throw new \Exception('Unable to use phpdbg on PHP < 7.0.0.'); + + } elseif ($this->cgi && $this->error) { + $this->error .= "\n(note that PHP CLI generates better error messages)"; + } + } + /** - * @param string - * @param string + * @param string + * @param string */ - function addPhpIniOption($name, $value = NULL); + public function addPhpIniOption($name, $value = NULL) + { + $this->arguments .= ' -d ' . Helpers::escapeArg($name . ($value === NULL ? '' : "=$value")); + } + /** * @return string */ - function getCommandLine(); + public function getCommandLine() + { + return $this->path . $this->arguments; + } + /** * @return string */ - function getVersion(); + public function getVersion() + { + return $this->info->version; + } + /** * @return bool */ - function canMeasureCodeCoverage(); + public function canMeasureCodeCoverage() + { + return $this->info->canMeasureCodeCoverage; + } + /** * @return bool */ - function isCgi(); + public function isCgi() + { + return $this->cgi; + } + /** * @return string */ - function getStartupError(); + public function getStartupError() + { + return $this->error; + } } diff --git a/src/Runner/ZendPhpDbgInterpreter.php b/src/Runner/ZendPhpDbgInterpreter.php deleted file mode 100644 index 57c64a48..00000000 --- a/src/Runner/ZendPhpDbgInterpreter.php +++ /dev/null @@ -1,114 +0,0 @@ -path = Helpers::escapeArg($path); - $this->arguments = ' -qrrb -S cli -n' . $args; - - $proc = proc_open( - "$this->path $this->arguments " . Helpers::escapeArg(__DIR__ . '/info.php') . ' serialized', - [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], - $pipes, - NULL, - NULL, - ['bypass_shell' => TRUE] - ); - $output = stream_get_contents($pipes[1]); - $this->error = trim(stream_get_contents($pipes[2])); - - if (proc_close($proc)) { - throw new \Exception("Unable to run '$path': " . preg_replace('#[\r\n ]+#', ' ', $this->error)); - } elseif (!($info = @unserialize($output))) { - throw new \Exception("Unable to detect PHP version (output: $output)."); - } - - $this->version = $info->version; - if (version_compare($this->version, '7.0.0', '<')) { - throw new \Exception('Unable to use phpdbg on PHP < 7.0.0.'); - } - } - - - /** - * @param string - * @param string - */ - public function addPhpIniOption($name, $value = NULL) - { - $this->arguments .= ' -d ' . Helpers::escapeArg($name . ($value === NULL ? '' : "=$value")); - } - - - /** - * @return string - */ - public function getCommandLine() - { - return $this->path . $this->arguments; - } - - - /** - * @return string - */ - public function getVersion() - { - return $this->version; - } - - - /** - * @return bool - */ - public function canMeasureCodeCoverage() - { - return TRUE; - } - - - /** - * @return bool - */ - public function isCgi() - { - return FALSE; - } - - - /** - * @return string - */ - public function getStartupError() - { - return $this->error; - } - -} diff --git a/src/Runner/ZendPhpInterpreter.php b/src/Runner/ZendPhpInterpreter.php deleted file mode 100644 index 561815b3..00000000 --- a/src/Runner/ZendPhpInterpreter.php +++ /dev/null @@ -1,125 +0,0 @@ -path = Helpers::escapeArg($path); - $this->arguments = ' -n' . $args; - - $proc = proc_open( - "$this->path $this->arguments " . Helpers::escapeArg(__DIR__ . '/info.php') . ' serialized', - [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], - $pipes, - NULL, - NULL, - ['bypass_shell' => TRUE] - ); - $output = stream_get_contents($pipes[1]); - $this->error = trim(stream_get_contents($pipes[2])); - - if (proc_close($proc)) { - throw new \Exception("Unable to run '$path': " . preg_replace('#[\r\n ]+#', ' ', $this->error)); - } - - $this->cgi = stripos($output, 'cgi') !== FALSE; - if ($this->cgi) { - list(, $output) = explode("\r\n\r\n", $output, 2); - } - - if (!($info = @unserialize($output))) { - throw new \Exception("Unable to detect PHP version (output: $output)."); - } - - $this->version = $info->version; - $this->xdebug = in_array('xdebug', $info->extensions, TRUE); - } - - - /** - * @param string - * @param string - */ - public function addPhpIniOption($name, $value = NULL) - { - $this->arguments .= ' -d ' . Helpers::escapeArg($name . ($value === NULL ? '' : "=$value")); - } - - - /** - * @return string - */ - public function getCommandLine() - { - return $this->path . $this->arguments; - } - - - /** - * @return string - */ - public function getVersion() - { - return $this->version; - } - - - /** - * @return bool - */ - public function canMeasureCodeCoverage() - { - return $this->xdebug; - } - - - /** - * @return bool - */ - public function isCgi() - { - return $this->cgi; - } - - - /** - * @return string - */ - public function getStartupError() - { - return $this->error; - } - -} diff --git a/src/Runner/info.php b/src/Runner/info.php index 76fb4056..f28cbb44 100644 --- a/src/Runner/info.php +++ b/src/Runner/info.php @@ -20,6 +20,7 @@ (function_exists('php_ini_scanned_files') && strlen($tmp = php_ini_scanned_files())) ? explode(",\n", trim($tmp)) : [] ), 'extensions' => $extensions, + 'canMeasureCodeCoverage' => $isPhpDbg || (!$isHhvm && in_array('xdebug', $extensions, TRUE)), ]; if (isset($_SERVER['argv'][1])) { diff --git a/src/tester.php b/src/tester.php index fc1e5a98..552cca4f 100644 --- a/src/tester.php +++ b/src/tester.php @@ -6,9 +6,6 @@ */ require __DIR__ . '/Runner/PhpInterpreter.php'; -require __DIR__ . '/Runner/ZendPhpInterpreter.php'; -require __DIR__ . '/Runner/ZendPhpDbgInterpreter.php'; -require __DIR__ . '/Runner/HhvmPhpInterpreter.php'; require __DIR__ . '/Runner/Runner.php'; require __DIR__ . '/Runner/CliTester.php'; require __DIR__ . '/Runner/Job.php'; diff --git a/tests/Runner/HhvmPhpInterpreter.phpt b/tests/Runner/PhpInterpreter.HHVM.phpt similarity index 100% rename from tests/Runner/HhvmPhpInterpreter.phpt rename to tests/Runner/PhpInterpreter.HHVM.phpt diff --git a/tests/Runner/ZendPhpExecutable.phpt b/tests/Runner/PhpInterpreter.Zend.phpt similarity index 100% rename from tests/Runner/ZendPhpExecutable.phpt rename to tests/Runner/PhpInterpreter.Zend.phpt diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 5a42c42c..53ca7f6f 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,10 +1,10 @@ Date: Sun, 17 Apr 2016 18:10:21 +0200 Subject: [PATCH 7/9] PhpInterpreter: $args are passed as array --- src/Runner/CliTester.php | 9 ++++----- src/Runner/PhpInterpreter.php | 26 ++++++++++++-------------- tests/bootstrap.php | 2 +- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/Runner/CliTester.php b/src/Runner/CliTester.php index d0dcc092..9d62e2b9 100644 --- a/src/Runner/CliTester.php +++ b/src/Runner/CliTester.php @@ -9,7 +9,6 @@ use Tester\CodeCoverage; use Tester\Environment; -use Tester\Helpers; use Tester\Dumper; @@ -142,19 +141,19 @@ private function loadOptions() /** @return void */ private function createPhpInterpreter() { - $args = ''; + $args = []; if ($this->options['-c']) { - $args .= ' -c ' . Helpers::escapeArg($this->options['-c']); + array_push($args, '-c', $this->options['-c']); } elseif (!$this->options['--info']) { echo "Note: No php.ini is used.\n"; } if (in_array($this->options['-o'], ['tap', 'junit'])) { - $args .= ' -d html_errors=off'; + array_push($args, '-d', 'html_errors=off'); } foreach ($this->options['-d'] as $item) { - $args .= ' -d ' . Helpers::escapeArg($item); + array_push($args, '-d', $item); } $this->interpreter = new PhpInterpreter($this->options['-p'], $args); diff --git a/src/Runner/PhpInterpreter.php b/src/Runner/PhpInterpreter.php index edaadc86..3942cb0a 100644 --- a/src/Runner/PhpInterpreter.php +++ b/src/Runner/PhpInterpreter.php @@ -15,11 +15,8 @@ */ class PhpInterpreter { - /** @var string PHP arguments */ - private $arguments; - - /** @var string PHP executable */ - private $path; + /** @var string */ + private $commandLine; /** @var bool is CGI? */ private $cgi; @@ -31,11 +28,11 @@ class PhpInterpreter private $error; - public function __construct($path, $args = NULL) + public function __construct($path, array $args = []) { - $this->path = Helpers::escapeArg($path); + $this->commandLine = Helpers::escapeArg($path); $proc = @proc_open( // @ is escalated to exception - $this->path . ' --version', + $this->commandLine . ' --version', [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], $pipes, NULL, @@ -48,15 +45,16 @@ public function __construct($path, $args = NULL) $output = stream_get_contents($pipes[1]); proc_close($proc); - $this->arguments = ' -n ' . $args; + $args = ' -n ' . implode(' ', array_map(['Tester\Helpers', 'escapeArg'], $args)); if (preg_match('#HipHop VM#', $output)) { - $this->arguments = ' --php' . $this->arguments . ' -d hhvm.log.always_log_unhandled_exceptions=false'; // HHVM issue #3019 + $args = ' --php' . $args . ' -d hhvm.log.always_log_unhandled_exceptions=false'; // HHVM issue #3019 } elseif (strpos($output, 'phpdbg') !== FALSE) { - $this->arguments = ' -qrrb -S cli' . $this->arguments; + $args = ' -qrrb -S cli' . $args; } + $this->commandLine .= $args; $proc = proc_open( - "$this->path $this->arguments " . Helpers::escapeArg(__DIR__ . '/info.php') . ' serialized', + $this->commandLine . ' ' . Helpers::escapeArg(__DIR__ . '/info.php') . ' serialized', [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], $pipes, NULL, @@ -92,7 +90,7 @@ public function __construct($path, $args = NULL) */ public function addPhpIniOption($name, $value = NULL) { - $this->arguments .= ' -d ' . Helpers::escapeArg($name . ($value === NULL ? '' : "=$value")); + $this->commandLine .= ' -d ' . Helpers::escapeArg($name . ($value === NULL ? '' : "=$value")); } @@ -101,7 +99,7 @@ public function addPhpIniOption($name, $value = NULL) */ public function getCommandLine() { - return $this->path . $this->arguments; + return $this->commandLine; } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 53ca7f6f..f87d48ca 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -20,6 +20,6 @@ function createInterpreter() { return new PhpInterpreter( PHP_BINARY, - defined('HHVM_VERSION') ? '' : '-c ' . php_ini_loaded_file() + defined('HHVM_VERSION') ? [] : ['-c', php_ini_loaded_file()] ); } From 653d39f45d02f258d3223dc886b0ecfce2649394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20H=C5=AFla?= Date: Wed, 30 Mar 2016 18:10:09 +0200 Subject: [PATCH 8/9] PhpInterpreter: added getShortInfo() --- src/Runner/Output/ConsolePrinter.php | 2 +- src/Runner/PhpInterpreter.php | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Runner/Output/ConsolePrinter.php b/src/Runner/Output/ConsolePrinter.php index 29281585..a286b7b4 100644 --- a/src/Runner/Output/ConsolePrinter.php +++ b/src/Runner/Output/ConsolePrinter.php @@ -40,7 +40,7 @@ public function __construct(Runner $runner, $displaySkipped = FALSE) public function begin() { $this->time = -microtime(TRUE); - echo 'PHP ' . $this->runner->getInterpreter()->getVersion() + echo $this->runner->getInterpreter()->getShortInfo() . ' | ' . $this->runner->getInterpreter()->getCommandLine() . " | {$this->runner->threadCount} thread" . ($this->runner->threadCount > 1 ? 's' : '') . "\n\n"; } diff --git a/src/Runner/PhpInterpreter.php b/src/Runner/PhpInterpreter.php index 3942cb0a..25358930 100644 --- a/src/Runner/PhpInterpreter.php +++ b/src/Runner/PhpInterpreter.php @@ -138,4 +138,15 @@ public function getStartupError() return $this->error; } + + /** + * @return string + */ + public function getShortInfo() + { + return "PHP {$this->info->version} ({$this->info->sapi})" + . ($this->info->phpDbgVersion ? "; PHPDBG {$this->info->phpDbgVersion}" : '') + . ($this->info->hhvmVersion ? "; HHVM {$this->info->hhvmVersion}" : ''); + } + } From ded79352e4e343ecd307a6a0764766ff45a7f1b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20H=C5=AFla?= Date: Wed, 30 Mar 2016 18:17:57 +0200 Subject: [PATCH 9/9] PhpInterpreter: added hasExtension() --- src/Runner/PhpInterpreter.php | 10 ++++++++++ tests/Runner/PhpInterpreter.phpt | 10 ++++++++++ 2 files changed, 20 insertions(+) create mode 100644 tests/Runner/PhpInterpreter.phpt diff --git a/src/Runner/PhpInterpreter.php b/src/Runner/PhpInterpreter.php index 25358930..d8d3651f 100644 --- a/src/Runner/PhpInterpreter.php +++ b/src/Runner/PhpInterpreter.php @@ -149,4 +149,14 @@ public function getShortInfo() . ($this->info->hhvmVersion ? "; HHVM {$this->info->hhvmVersion}" : ''); } + + /** + * @param string + * @return bool + */ + public function hasExtension($name) + { + return in_array(strtolower($name), array_map('strtolower', $this->info->extensions), TRUE); + } + } diff --git a/tests/Runner/PhpInterpreter.phpt b/tests/Runner/PhpInterpreter.phpt new file mode 100644 index 00000000..a5993231 --- /dev/null +++ b/tests/Runner/PhpInterpreter.phpt @@ -0,0 +1,10 @@ +hasExtension('DaTe')); +Assert::false($interpreter->hasExtension('foo-bar'));