4 * This file is part of Psy Shell.
6 * (c) 2012-2018 Justin Hileman
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
14 use Psy\Configuration;
15 use Psy\Exception\ErrorException;
16 use Psy\Exception\ParseErrorException;
18 use Psy\TabCompletion\Matcher\ClassMethodsMatcher;
19 use Symfony\Component\Console\Output\StreamOutput;
21 class ShellTest extends \PHPUnit\Framework\TestCase
23 private $streams = [];
25 public function tearDown()
27 foreach ($this->streams as $stream) {
32 public function testScopeVariables()
36 $three = new \StdClass();
37 $__psysh__ = 'ignore this';
41 $shell = new Shell($this->getConfig());
42 $shell->setScopeVariables(compact('one', 'two', 'three', '__psysh__', '_', '_e', 'this'));
44 $this->assertNotContains('__psysh__', $shell->getScopeVariableNames());
45 $this->assertSame(['one', 'two', 'three', '_'], $shell->getScopeVariableNames());
46 $this->assertSame('banana', $shell->getScopeVariable('one'));
47 $this->assertSame(123, $shell->getScopeVariable('two'));
48 $this->assertSame($three, $shell->getScopeVariable('three'));
49 $this->assertNull($shell->getScopeVariable('_'));
51 $shell->setScopeVariables([]);
52 $this->assertSame(['_'], $shell->getScopeVariableNames());
54 $shell->setBoundObject($this);
55 $this->assertSame(['_', 'this'], $shell->getScopeVariableNames());
56 $this->assertSame($this, $shell->getScopeVariable('this'));
57 $this->assertSame(['_' => null], $shell->getScopeVariables(false));
58 $this->assertSame(['_' => null, 'this' => $this], $shell->getScopeVariables());
62 * @expectedException \InvalidArgumentException
64 public function testUnknownScopeVariablesThrowExceptions()
66 $shell = new Shell($this->getConfig());
67 $shell->setScopeVariables(['foo' => 'FOO', 'bar' => 1]);
68 $shell->getScopeVariable('baz');
71 public function testIncludesWithScopeVariables()
75 $three = new \StdClass();
76 $__psysh__ = 'ignore this';
80 $config = $this->getConfig(['usePcntl' => false]);
82 $shell = new Shell($config);
83 $shell->setScopeVariables(compact('one', 'two', 'three', '__psysh__', '_', '_e', 'this'));
84 $shell->addInput('exit', true);
86 // This is super slow and we shouldn't do this :(
87 $shell->run(null, $this->getOutput());
89 $this->assertNotContains('__psysh__', $shell->getScopeVariableNames());
90 $this->assertSame(['one', 'two', 'three', '_', '_e'], $shell->getScopeVariableNames());
91 $this->assertSame('banana', $shell->getScopeVariable('one'));
92 $this->assertSame(123, $shell->getScopeVariable('two'));
93 $this->assertSame($three, $shell->getScopeVariable('three'));
94 $this->assertNull($shell->getScopeVariable('_'));
97 public function testIncludes()
99 $config = $this->getConfig(['configFile' => __DIR__ . '/fixtures/empty.php']);
101 $shell = new Shell($config);
102 $this->assertEmpty($shell->getIncludes());
103 $shell->setIncludes(['foo', 'bar', 'baz']);
104 $this->assertSame(['foo', 'bar', 'baz'], $shell->getIncludes());
107 public function testIncludesConfig()
109 $config = $this->getConfig([
110 'defaultIncludes' => ['/file.php'],
111 'configFile' => __DIR__ . '/fixtures/empty.php',
114 $shell = new Shell($config);
116 $includes = $shell->getIncludes();
117 $this->assertSame('/file.php', $includes[0]);
120 public function testAddMatchersViaConfig()
122 $shell = new FakeShell();
123 $matcher = new ClassMethodsMatcher();
125 $config = $this->getConfig([
126 'matchers' => [$matcher],
128 $config->setShell($shell);
130 $this->assertSame([$matcher], $shell->matchers);
133 public function testAddMatchersViaConfigAfterShell()
135 $shell = new FakeShell();
136 $matcher = new ClassMethodsMatcher();
138 $config = $this->getConfig([]);
139 $config->setShell($shell);
140 $config->addMatchers([$matcher]);
142 $this->assertSame([$matcher], $shell->matchers);
145 public function testRenderingExceptions()
147 $shell = new Shell($this->getConfig());
148 $output = $this->getOutput();
149 $stream = $output->getStream();
150 $e = new ParseErrorException('message', 13);
152 $shell->setOutput($output);
153 $shell->addCode('code');
154 $this->assertTrue($shell->hasCode());
155 $this->assertNotEmpty($shell->getCodeBuffer());
157 $shell->writeException($e);
159 $this->assertSame($e, $shell->getScopeVariable('_e'));
160 $this->assertFalse($shell->hasCode());
161 $this->assertEmpty($shell->getCodeBuffer());
164 $streamContents = stream_get_contents($stream);
166 $this->assertContains('PHP Parse error', $streamContents);
167 $this->assertContains('message', $streamContents);
168 $this->assertContains('line 13', $streamContents);
171 public function testHandlingErrors()
173 $shell = new Shell($this->getConfig());
174 $output = $this->getOutput();
175 $stream = $output->getStream();
176 $shell->setOutput($output);
178 $oldLevel = error_reporting();
179 error_reporting($oldLevel & ~E_USER_NOTICE);
182 $shell->handleError(E_USER_NOTICE, 'wheee', null, 13);
183 } catch (ErrorException $e) {
184 error_reporting($oldLevel);
185 $this->fail('Unexpected error exception');
187 error_reporting($oldLevel);
190 $streamContents = stream_get_contents($stream);
192 $this->assertContains('PHP Notice:', $streamContents);
193 $this->assertContains('wheee', $streamContents);
194 $this->assertContains('line 13', $streamContents);
198 * @expectedException \Psy\Exception\ErrorException
200 public function testNotHandlingErrors()
202 $shell = new Shell($this->getConfig());
203 $oldLevel = error_reporting();
204 error_reporting($oldLevel | E_USER_NOTICE);
207 $shell->handleError(E_USER_NOTICE, 'wheee', null, 13);
208 } catch (ErrorException $e) {
209 error_reporting($oldLevel);
214 public function testVersion()
216 $shell = new Shell($this->getConfig());
218 $this->assertInstanceOf('Symfony\Component\Console\Application', $shell);
219 $this->assertContains(Shell::VERSION, $shell->getVersion());
220 $this->assertContains(phpversion(), $shell->getVersion());
221 $this->assertContains(php_sapi_name(), $shell->getVersion());
224 public function testCodeBuffer()
226 $shell = new Shell($this->getConfig());
228 $shell->addCode('class');
229 $this->assertNull($shell->flushCode());
230 $this->assertTrue($shell->hasCode());
232 $shell->addCode('a');
233 $this->assertNull($shell->flushCode());
234 $this->assertTrue($shell->hasCode());
236 $shell->addCode('{}');
237 $code = $shell->flushCode();
238 $this->assertFalse($shell->hasCode());
239 $code = preg_replace('/\s+/', ' ', $code);
240 $this->assertNotNull($code);
241 $this->assertSame('class a { } return new \\Psy\\CodeCleaner\\NoReturnValue();', $code);
244 public function testKeepCodeBufferOpen()
246 $shell = new Shell($this->getConfig());
248 $shell->addCode('1 \\');
249 $this->assertNull($shell->flushCode());
250 $this->assertTrue($shell->hasCode());
252 $shell->addCode('+ 1 \\');
253 $this->assertNull($shell->flushCode());
254 $this->assertTrue($shell->hasCode());
256 $shell->addCode('+ 1');
257 $code = $shell->flushCode();
258 $this->assertFalse($shell->hasCode());
259 $code = preg_replace('/\s+/', ' ', $code);
260 $this->assertNotNull($code);
261 $this->assertSame('return 1 + 1 + 1;', $code);
265 * @expectedException \Psy\Exception\ParseErrorException
267 public function testCodeBufferThrowsParseExceptions()
269 $shell = new Shell($this->getConfig());
270 $shell->addCode('this is not valid');
274 public function testClosuresSupport()
276 $shell = new Shell($this->getConfig());
277 $code = '$test = function () {}';
278 $shell->addCode($code);
281 $shell->addCode($code);
282 $this->assertSame($shell->flushCode(), 'return $test();');
285 public function testWriteStdout()
287 $output = $this->getOutput();
288 $stream = $output->getStream();
289 $shell = new Shell($this->getConfig());
290 $shell->setOutput($output);
292 $shell->writeStdout("{{stdout}}\n");
295 $streamContents = stream_get_contents($stream);
297 $this->assertSame('{{stdout}}' . PHP_EOL, $streamContents);
300 public function testWriteStdoutWithoutNewline()
302 $output = $this->getOutput();
303 $stream = $output->getStream();
304 $shell = new Shell($this->getConfig());
305 $shell->setOutput($output);
307 $shell->writeStdout('{{stdout}}');
310 $streamContents = stream_get_contents($stream);
312 $this->assertSame('{{stdout}}<aside>⏎</aside>' . PHP_EOL, $streamContents);
316 * @dataProvider getReturnValues
318 public function testWriteReturnValue($input, $expected)
320 $output = $this->getOutput();
321 $stream = $output->getStream();
322 $shell = new Shell($this->getConfig());
323 $shell->setOutput($output);
325 $shell->writeReturnValue($input);
327 $this->assertEquals($expected, stream_get_contents($stream));
330 public function getReturnValues()
333 ['{{return value}}', "=> \"\033[32m{{return value}}\033[39m\"" . PHP_EOL],
334 [1, "=> \033[35m1\033[39m" . PHP_EOL],
339 * @dataProvider getRenderedExceptions
341 public function testWriteException($exception, $expected)
343 $output = $this->getOutput();
344 $stream = $output->getStream();
345 $shell = new Shell($this->getConfig());
346 $shell->setOutput($output);
348 $shell->writeException($exception);
350 $this->assertSame($expected, stream_get_contents($stream));
353 public function getRenderedExceptions()
356 [new \Exception('{{message}}'), "Exception with message '{{message}}'" . PHP_EOL],
361 * @dataProvider getExecuteValues
363 public function testShellExecute($input, $expected)
365 $output = $this->getOutput();
366 $stream = $output->getStream();
367 $shell = new Shell($this->getConfig());
368 $shell->setOutput($output);
369 $this->assertEquals($expected, $shell->execute($input));
371 $this->assertSame('', stream_get_contents($stream));
374 public function getExecuteValues()
378 ['"{{return value}}"', '{{return value}}'],
383 private function getOutput()
385 $stream = fopen('php://memory', 'w+');
386 $this->streams[] = $stream;
388 $output = new StreamOutput($stream, StreamOutput::VERBOSITY_NORMAL, false);
393 private function getConfig(array $config = [])
395 // Mebbe there's a better way than this?
396 $dir = tempnam(sys_get_temp_dir(), 'psysh_shell_test_');
402 'runtimeDir' => $dir,
405 return new Configuration(array_merge($defaults, $config));