Skip to content

Commit 7380e44

Browse files
committed
Merge pull request #306 from dg/pull-interpret
PHP interpreters refactoring II.
2 parents 5c4f5ec + ded7935 commit 7380e44

16 files changed

+192
-402
lines changed

src/Framework/Helpers.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public static function errorTypeToString($type)
8181
*/
8282
public static function escapeArg($s)
8383
{
84-
if (preg_match('#^[a-z0-9._-]+\z#i', $s)) {
84+
if (preg_match('#^[a-z0-9._=/:-]+\z#i', $s)) {
8585
return $s;
8686
}
8787

src/Runner/CliTester.php

Lines changed: 8 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
use Tester\CodeCoverage;
1111
use Tester\Environment;
12-
use Tester\Helpers;
1312
use Tester\Dumper;
1413

1514

@@ -142,52 +141,25 @@ private function loadOptions()
142141
/** @return void */
143142
private function createPhpInterpreter()
144143
{
145-
$args = '';
144+
$args = [];
146145
if ($this->options['-c']) {
147-
$args .= ' -c ' . Helpers::escapeArg($this->options['-c']);
146+
array_push($args, '-c', $this->options['-c']);
148147
} elseif (!$this->options['--info']) {
149148
echo "Note: No php.ini is used.\n";
150149
}
151150

152151
if (in_array($this->options['-o'], ['tap', 'junit'])) {
153-
$args .= ' -d html_errors=off';
152+
array_push($args, '-d', 'html_errors=off');
154153
}
155154

156155
foreach ($this->options['-d'] as $item) {
157-
$args .= ' -d ' . Helpers::escapeArg($item);
156+
array_push($args, '-d', $item);
158157
}
159158

160-
// Is the executable Zend PHP or HHVM?
161-
$proc = @proc_open( // @ is escalated to exception
162-
$this->options['-p'] . ' --version',
163-
[['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']],
164-
$pipes,
165-
NULL,
166-
NULL,
167-
['bypass_shell' => TRUE]
168-
);
169-
if ($proc === FALSE) {
170-
throw new \Exception('Cannot run PHP interpreter ' . $this->options['-p'] . '. Use -p option.');
171-
}
172-
$output = stream_get_contents($pipes[1]);
173-
$error = stream_get_contents($pipes[2]);
174-
if (proc_close($proc)) {
175-
throw new \Exception("Unable to run '{$this->options['-p']}': " . preg_replace('#[\r\n ]+#', ' ', $error));
176-
}
177-
178-
if (preg_match('#HipHop VM#', $output)) {
179-
$this->interpreter = new HhvmPhpInterpreter($this->options['-p'], $args);
180-
} elseif (strpos($output, 'phpdbg') !== FALSE) {
181-
$this->interpreter = new ZendPhpDbgInterpreter($this->options['-p'], $args);
182-
} else {
183-
$this->interpreter = new ZendPhpInterpreter($this->options['-p'], $args);
184-
}
159+
$this->interpreter = new PhpInterpreter($this->options['-p'], $args);
185160

186-
if ($this->interpreter->getErrorOutput()) {
187-
echo Dumper::color('red', 'PHP startup error: ' . $this->interpreter->getErrorOutput()) . "\n";
188-
if ($this->interpreter->isCgi()) {
189-
echo "(note that PHP CLI generates better error messages)\n";
190-
}
161+
if ($error = $this->interpreter->getStartupError()) {
162+
echo Dumper::color('red', "PHP startup error: $error") . "\n";
191163
}
192164
}
193165

@@ -230,7 +202,7 @@ private function createRunner()
230202
/** @return string */
231203
private function prepareCodeCoverage()
232204
{
233-
if (!$this->interpreter->hasXdebug()) {
205+
if (!$this->interpreter->canMeasureCodeCoverage()) {
234206
$alternative = PHP_VERSION_ID >= 70000 ? ' or phpdbg SAPI' : '';
235207
throw new \Exception("Code coverage functionality requires Xdebug extension$alternative (used {$this->interpreter->getCommandLine()})");
236208
}

src/Runner/HhvmPhpInterpreter.php

Lines changed: 0 additions & 106 deletions
This file was deleted.

src/Runner/Job.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace Tester\Runner;
99

1010
use Tester\Environment;
11+
use Tester\Helpers;
1112

1213

1314
/**
@@ -83,7 +84,7 @@ public function run($flags = NULL)
8384
putenv(Environment::COLORS . '=' . (int) Environment::$useColors);
8485
$this->proc = proc_open(
8586
$this->interpreter->getCommandLine()
86-
. ' -n -d register_argc_argv=on ' . \Tester\Helpers::escapeArg($this->file) . ' ' . implode(' ', $this->args),
87+
. ' -d register_argc_argv=on ' . Helpers::escapeArg($this->file) . ' ' . implode(' ', $this->args),
8788
[
8889
['pipe', 'r'],
8990
['pipe', 'w'],

src/Runner/Output/ConsolePrinter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public function __construct(Runner $runner, $displaySkipped = FALSE)
4040
public function begin()
4141
{
4242
$this->time = -microtime(TRUE);
43-
echo 'PHP ' . $this->runner->getInterpreter()->getVersion()
43+
echo $this->runner->getInterpreter()->getShortInfo()
4444
. ' | ' . $this->runner->getInterpreter()->getCommandLine()
4545
. " | {$this->runner->threadCount} thread" . ($this->runner->threadCount > 1 ? 's' : '') . "\n\n";
4646
}

src/Runner/PhpInterpreter.php

Lines changed: 129 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,156 @@
77

88
namespace Tester\Runner;
99

10+
use Tester\Helpers;
1011

11-
interface PhpInterpreter
12+
13+
/**
14+
* PHP command-line executable.
15+
*/
16+
class PhpInterpreter
1217
{
18+
/** @var string */
19+
private $commandLine;
20+
21+
/** @var bool is CGI? */
22+
private $cgi;
23+
24+
/** @var \stdClass created by info.php */
25+
private $info;
26+
27+
/** @var string */
28+
private $error;
29+
30+
31+
public function __construct($path, array $args = [])
32+
{
33+
$this->commandLine = Helpers::escapeArg($path);
34+
$proc = @proc_open( // @ is escalated to exception
35+
$this->commandLine . ' --version',
36+
[['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']],
37+
$pipes,
38+
NULL,
39+
NULL,
40+
['bypass_shell' => TRUE]
41+
);
42+
if ($proc === FALSE) {
43+
throw new \Exception("Cannot run PHP interpreter $path. Use -p option.");
44+
}
45+
$output = stream_get_contents($pipes[1]);
46+
proc_close($proc);
47+
48+
$args = ' -n ' . implode(' ', array_map(['Tester\Helpers', 'escapeArg'], $args));
49+
if (preg_match('#HipHop VM#', $output)) {
50+
$args = ' --php' . $args . ' -d hhvm.log.always_log_unhandled_exceptions=false'; // HHVM issue #3019
51+
} elseif (strpos($output, 'phpdbg') !== FALSE) {
52+
$args = ' -qrrb -S cli' . $args;
53+
}
54+
$this->commandLine .= $args;
55+
56+
$proc = proc_open(
57+
$this->commandLine . ' ' . Helpers::escapeArg(__DIR__ . '/info.php') . ' serialized',
58+
[['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']],
59+
$pipes,
60+
NULL,
61+
NULL,
62+
['bypass_shell' => TRUE]
63+
);
64+
$output = stream_get_contents($pipes[1]);
65+
$this->error = trim(stream_get_contents($pipes[2]));
66+
if (proc_close($proc)) {
67+
throw new \Exception("Unable to run $path: " . preg_replace('#[\r\n ]+#', ' ', $this->error));
68+
}
69+
70+
$parts = explode("\r\n\r\n", $output, 2);
71+
$this->cgi = count($parts) === 2;
72+
if (!($this->info = @unserialize($parts[$this->cgi]))) {
73+
throw new \Exception("Unable to detect PHP version (output: $output).");
74+
75+
} elseif ($this->info->hhvmVersion && version_compare($this->info->hhvmVersion, '3.3.0', '<')) {
76+
throw new \Exception('HHVM below version 3.3.0 is not supported.');
77+
78+
} elseif ($this->info->phpDbgVersion && version_compare($this->info->version, '7.0.0', '<')) {
79+
throw new \Exception('Unable to use phpdbg on PHP < 7.0.0.');
80+
81+
} elseif ($this->cgi && $this->error) {
82+
$this->error .= "\n(note that PHP CLI generates better error messages)";
83+
}
84+
}
85+
86+
87+
/**
88+
* @param string
89+
* @param string
90+
*/
91+
public function addPhpIniOption($name, $value = NULL)
92+
{
93+
$this->commandLine .= ' -d ' . Helpers::escapeArg($name . ($value === NULL ? '' : "=$value"));
94+
}
95+
1396

1497
/**
1598
* @return string
1699
*/
17-
function getCommandLine();
100+
public function getCommandLine()
101+
{
102+
return $this->commandLine;
103+
}
104+
18105

19106
/**
20107
* @return string
21108
*/
22-
function getVersion();
109+
public function getVersion()
110+
{
111+
return $this->info->version;
112+
}
113+
23114

24115
/**
25116
* @return bool
26117
*/
27-
function hasXdebug();
118+
public function canMeasureCodeCoverage()
119+
{
120+
return $this->info->canMeasureCodeCoverage;
121+
}
122+
28123

29124
/**
30125
* @return bool
31126
*/
32-
function isCgi();
127+
public function isCgi()
128+
{
129+
return $this->cgi;
130+
}
131+
132+
133+
/**
134+
* @return string
135+
*/
136+
public function getStartupError()
137+
{
138+
return $this->error;
139+
}
140+
33141

34142
/**
35143
* @return string
36144
*/
37-
function getErrorOutput();
145+
public function getShortInfo()
146+
{
147+
return "PHP {$this->info->version} ({$this->info->sapi})"
148+
. ($this->info->phpDbgVersion ? "; PHPDBG {$this->info->phpDbgVersion}" : '')
149+
. ($this->info->hhvmVersion ? "; HHVM {$this->info->hhvmVersion}" : '');
150+
}
151+
152+
153+
/**
154+
* @param string
155+
* @return bool
156+
*/
157+
public function hasExtension($name)
158+
{
159+
return in_array(strtolower($name), array_map('strtolower', $this->info->extensions), TRUE);
160+
}
38161

39162
}

src/Runner/TestHandler.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ private function initiatePhpVersion($version, PhpInterpreter $interpreter)
113113

114114
private function initiatePhpIni($value, PhpInterpreter $interpreter)
115115
{
116-
$interpreter->arguments .= ' -d ' . Helpers::escapeArg($value);
116+
list($name, $value) = explode('=', $value, 2) + [1 => NULL];
117+
$interpreter->addPhpIniOption($name, $value);
117118
}
118119

119120

0 commit comments

Comments
 (0)