First commit

This commit is contained in:
Theodotos Andreou 2018-01-14 13:10:16 +00:00
commit c6e2478c40
13918 changed files with 2303184 additions and 0 deletions

View file

@ -0,0 +1,23 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test;
use Psy\Autoloader;
class AutoloaderTest extends \PHPUnit\Framework\TestCase
{
public function testRegister()
{
Autoloader::register();
$this->assertTrue(spl_autoload_unregister(array('Psy\Autoloader', 'autoload')));
}
}

View file

@ -0,0 +1,64 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use PhpParser\NodeTraverser;
use Psy\CodeCleaner\AbstractClassPass;
class AbstractClassPassTest extends CodeCleanerTestCase
{
public function setUp()
{
$this->pass = new AbstractClassPass();
$this->traverser = new NodeTraverser();
$this->traverser->addVisitor($this->pass);
}
/**
* @dataProvider invalidStatements
* @expectedException \Psy\Exception\FatalErrorException
*/
public function testProcessStatementFails($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
}
public function invalidStatements()
{
return array(
array('class A { abstract function a(); }'),
array('abstract class B { abstract function b() {} }'),
array('abstract class B { abstract function b() { echo "yep"; } }'),
);
}
/**
* @dataProvider validStatements
*/
public function testProcessStatementPasses($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
// @todo a better thing to assert here?
$this->assertTrue(true);
}
public function validStatements()
{
return array(
array('abstract class C { function c() {} }'),
array('abstract class D { abstract function d(); }'),
);
}
}

View file

@ -0,0 +1,65 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use PhpParser\NodeTraverser;
use Psy\CodeCleaner\AssignThisVariablePass;
class AssignThisVariablePassTest extends CodeCleanerTestCase
{
public function setUp()
{
$this->pass = new AssignThisVariablePass();
$this->traverser = new NodeTraverser();
$this->traverser->addVisitor($this->pass);
}
/**
* @dataProvider invalidStatements
* @expectedException \Psy\Exception\FatalErrorException
*/
public function testProcessStatementFails($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
}
public function invalidStatements()
{
return array(
array('$this = 3'),
array('strtolower($this = "this")'),
);
}
/**
* @dataProvider validStatements
*/
public function testProcessStatementPasses($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
// @todo a better thing to assert here?
$this->assertTrue(true);
}
public function validStatements()
{
return array(
array('$this'),
array('$a = $this'),
array('$a = "this"; $$a = 3'),
array('$$this = "b"'),
);
}
}

View file

@ -0,0 +1,76 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use PhpParser\NodeTraverser;
use Psy\CodeCleaner\CallTimePassByReferencePass;
class CallTimePassByReferencePassTest extends CodeCleanerTestCase
{
public function setUp()
{
$this->pass = new CallTimePassByReferencePass();
$this->traverser = new NodeTraverser();
$this->traverser->addVisitor($this->pass);
}
/**
* @dataProvider invalidStatements
* @expectedException \Psy\Exception\FatalErrorException
*/
public function testProcessStatementFails($code)
{
if (version_compare(PHP_VERSION, '5.4', '<')) {
$this->markTestSkipped();
}
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
}
public function invalidStatements()
{
return array(
array('f(&$arg)'),
array('$object->method($first, &$arg)'),
array('$closure($first, &$arg, $last)'),
array('A::b(&$arg)'),
);
}
/**
* @dataProvider validStatements
*/
public function testProcessStatementPasses($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
// @todo a better thing to assert here?
$this->assertTrue(true);
}
public function validStatements()
{
$data = array(
array('array(&$var)'),
array('$a = &$b'),
array('f(array(&$b))'),
);
if (version_compare(PHP_VERSION, '5.4', '<')) {
$data = array_merge($data, $this->invalidStatements());
}
return $data;
}
}

View file

@ -0,0 +1,104 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use PhpParser\NodeTraverser;
use Psy\CodeCleaner\CalledClassPass;
class CalledClassPassTest extends CodeCleanerTestCase
{
public function setUp()
{
$this->pass = new CalledClassPass();
$this->traverser = new NodeTraverser();
$this->traverser->addVisitor($this->pass);
}
/**
* @dataProvider invalidStatements
* @expectedException \Psy\Exception\ErrorException
*/
public function testProcessStatementFails($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
}
public function invalidStatements()
{
return array(
array('get_class()'),
array('get_class(null)'),
array('get_called_class()'),
array('get_called_class(null)'),
array('function foo() { return get_class(); }'),
array('function foo() { return get_class(null); }'),
array('function foo() { return get_called_class(); }'),
array('function foo() { return get_called_class(null); }'),
);
}
/**
* @dataProvider validStatements
*/
public function testProcessStatementPasses($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
// @todo a better thing to assert here?
$this->assertTrue(true);
}
public function validStatements()
{
return array(
array('get_class($foo)'),
array('get_class(bar())'),
array('get_called_class($foo)'),
array('get_called_class(bar())'),
array('function foo($bar) { return get_class($bar); }'),
array('function foo($bar) { return get_called_class($bar); }'),
array('class Foo { function bar() { return get_class(); } }'),
array('class Foo { function bar() { return get_class(null); } }'),
array('class Foo { function bar() { return get_called_class(); } }'),
array('class Foo { function bar() { return get_called_class(null); } }'),
array('$foo = function () {}; $foo()'),
);
}
/**
* @dataProvider validTraitStatements
*/
public function testProcessTraitStatementPasses($code)
{
if (version_compare(PHP_VERSION, '5.4', '<')) {
$this->markTestSkipped();
}
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
// @todo a better thing to assert here?
$this->assertTrue(true);
}
public function validTraitStatements()
{
return array(
array('trait Foo { function bar() { return get_class(); } }'),
array('trait Foo { function bar() { return get_class(null); } }'),
array('trait Foo { function bar() { return get_called_class(); } }'),
array('trait Foo { function bar() { return get_called_class(null); } }'),
);
}
}

View file

@ -0,0 +1,97 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use PhpParser\NodeTraverser;
use PhpParser\PrettyPrinter\Standard as Printer;
use Psy\CodeCleaner\CodeCleanerPass;
use Psy\Exception\ParseErrorException;
use Psy\ParserFactory;
class CodeCleanerTestCase extends \PHPUnit\Framework\TestCase
{
protected $pass;
protected $traverser;
private $parser;
private $printer;
protected function setPass(CodeCleanerPass $pass)
{
$this->pass = $pass;
if (!isset($this->traverser)) {
$this->traverser = new NodeTraverser();
}
$this->traverser->addVisitor($this->pass);
}
protected function parse($code, $prefix = '<?php ')
{
$code = $prefix . $code;
try {
return $this->getParser()->parse($code);
} catch (\PhpParser\Error $e) {
if (!$this->parseErrorIsEOF($e)) {
throw ParseErrorException::fromParseError($e);
}
try {
// Unexpected EOF, try again with an implicit semicolon
return $this->getParser()->parse($code . ';');
} catch (\PhpParser\Error $e) {
return false;
}
}
}
protected function traverse(array $stmts)
{
return $this->traverser->traverse($stmts);
}
protected function prettyPrint(array $stmts)
{
return $this->getPrinter()->prettyPrint($stmts);
}
protected function assertProcessesAs($from, $to)
{
$stmts = $this->parse($from);
$stmts = $this->traverse($stmts);
$this->assertEquals($to, $this->prettyPrint($stmts));
}
private function getParser()
{
if (!isset($this->parser)) {
$parserFactory = new ParserFactory();
$this->parser = $parserFactory->createParser();
}
return $this->parser;
}
private function getPrinter()
{
if (!isset($this->printer)) {
$this->printer = new Printer();
}
return $this->printer;
}
private function parseErrorIsEOF(\PhpParser\Error $e)
{
$msg = $e->getRawMessage();
return ($msg === 'Unexpected token EOF') || (strpos($msg, 'Syntax error, unexpected EOF') !== false);
}
}

View file

@ -0,0 +1,59 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use Psy\CodeCleaner\ExitPass;
class ExitPassTest extends CodeCleanerTestCase
{
/**
* @var string
*/
private $expectedExceptionString = '\\Psy\\Exception\\BreakException::exitShell()';
public function setUp()
{
$this->setPass(new ExitPass());
}
/**
* @dataProvider dataProviderExitStatement
*/
public function testExitStatement($from, $to)
{
$this->assertProcessesAs($from, $to);
}
/**
* Data provider for testExitStatement.
*
* @return array
*/
public function dataProviderExitStatement()
{
return array(
array('exit;', "{$this->expectedExceptionString};"),
array('exit();', "{$this->expectedExceptionString};"),
array('die;', "{$this->expectedExceptionString};"),
array('exit(die(die));', "{$this->expectedExceptionString};"),
array('if (true) { exit; }', "if (true) {\n {$this->expectedExceptionString};\n}"),
array('if (false) { exit; }', "if (false) {\n {$this->expectedExceptionString};\n}"),
array('1 and exit();', "1 and {$this->expectedExceptionString};"),
array('foo() or die', "foo() or {$this->expectedExceptionString};"),
array('exit and 1;', "{$this->expectedExceptionString} and 1;"),
array('if (exit) { echo $wat; }', "if ({$this->expectedExceptionString}) {\n echo \$wat;\n}"),
array('exit or die;', "{$this->expectedExceptionString} or {$this->expectedExceptionString};"),
array('switch (die) { }', "switch ({$this->expectedExceptionString}) {\n}"),
array('for ($i = 1; $i < 10; die) {}', "for (\$i = 1; \$i < 10; {$this->expectedExceptionString}) {\n}"),
);
}
}

View file

@ -0,0 +1,72 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use PhpParser\NodeTraverser;
use Psy\CodeCleaner\FinalClassPass;
class FinalClassPassTest extends CodeCleanerTestCase
{
public function setUp()
{
$this->pass = new FinalClassPass();
$this->traverser = new NodeTraverser();
$this->traverser->addVisitor($this->pass);
}
/**
* @dataProvider invalidStatements
* @expectedException \Psy\Exception\FatalErrorException
*/
public function testProcessStatementFails($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
}
public function invalidStatements()
{
$stmts = array(
array('final class A {} class B extends A {}'),
array('class A {} final class B extends A {} class C extends B {}'),
// array('namespace A { final class B {} } namespace C { class D extends \\A\\B {} }'),
);
if (!defined('HHVM_VERSION')) {
// For some reason Closure isn't final in HHVM?
$stmts[] = array('class A extends \\Closure {}');
}
return $stmts;
}
/**
* @dataProvider validStatements
*/
public function testProcessStatementPasses($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
// @todo a better thing to assert here?
$this->assertTrue(true);
}
public function validStatements()
{
return array(
array('class A extends \\stdClass {}'),
array('final class A extends \\stdClass {}'),
array('class A {} class B extends A {}'),
);
}
}

View file

@ -0,0 +1,20 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner\Fixtures;
class ClassWithCallStatic
{
public static function __callStatic($name, $arguments)
{
// wheee!
}
}

View file

@ -0,0 +1,20 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner\Fixtures;
class ClassWithStatic
{
public static function doStuff()
{
// Don't actually do stuff.
}
}

View file

@ -0,0 +1,67 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use PhpParser\NodeTraverser;
use Psy\CodeCleaner\FunctionContextPass;
class FunctionContextPassTest extends CodeCleanerTestCase
{
public function setUp()
{
$this->pass = new FunctionContextPass();
$this->traverser = new NodeTraverser();
$this->traverser->addVisitor($this->pass);
}
/**
* @dataProvider validStatements
*/
public function testProcessStatementPasses($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
// @todo a better thing to assert here?
$this->assertTrue(true);
}
public function validStatements()
{
return array(
array('function foo() { yield; }'),
array('if (function(){ yield; })'),
);
}
/**
* @dataProvider invalidYieldStatements
* @expectedException \Psy\Exception\FatalErrorException
*/
public function testInvalidYield($code)
{
if (version_compare(PHP_VERSION, '5.4', '<')) {
$this->markTestSkipped();
}
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
}
public function invalidYieldStatements()
{
return array(
array('yield'),
array('if (yield)'),
);
}
}

View file

@ -0,0 +1,79 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use PhpParser\NodeTraverser;
use Psy\CodeCleaner\FunctionReturnInWriteContextPass;
use Psy\Exception\FatalErrorException;
class FunctionReturnInWriteContextPassTest extends CodeCleanerTestCase
{
public function setUp()
{
$this->pass = new FunctionReturnInWriteContextPass();
$this->traverser = new NodeTraverser();
$this->traverser->addVisitor($this->pass);
}
/**
* @dataProvider invalidStatements
* @expectedException \Psy\Exception\FatalErrorException
* @expectedExceptionMessage Can't use function return value in write context
*/
public function testProcessStatementFails($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
}
public function invalidStatements()
{
return array(
array('f(&g())'),
array('array(& $object->method())'),
array('$a->method(& $closure())'),
array('array(& A::b())'),
array('f() = 5'),
array('unset(h())'),
);
}
public function testIsset()
{
try {
$this->traverser->traverse($this->parse('isset(strtolower("A"))'));
$this->fail();
} catch (FatalErrorException $e) {
if (version_compare(PHP_VERSION, '5.5', '>=')) {
$this->assertContains(
'Cannot use isset() on the result of a function call (you can use "null !== func()" instead)',
$e->getMessage()
);
} else {
$this->assertContains("Can't use function return value in write context", $e->getMessage());
}
}
}
/**
* @expectedException \Psy\Exception\FatalErrorException
* @expectedExceptionMessage Can't use function return value in write context
*/
public function testEmpty()
{
if (version_compare(PHP_VERSION, '5.5', '>=')) {
$this->markTestSkipped();
}
$this->traverser->traverse($this->parse('empty(strtolower("A"))'));
}
}

View file

@ -0,0 +1,96 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use Psy\CodeCleaner\ImplicitReturnPass;
class ImplicitReturnPassTest extends CodeCleanerTestCase
{
public function setUp()
{
$this->setPass(new ImplicitReturnPass());
}
/**
* @dataProvider implicitReturns
*/
public function testProcess($from, $to)
{
$this->assertProcessesAs($from, $to);
}
public function implicitReturns()
{
$values = array(
array('4', 'return 4;'),
array('foo()', 'return foo();'),
array('return 1', 'return 1;'),
);
$from = 'if (true) { 1; } elseif (true) { 2; } else { 3; }';
$to = <<<'EOS'
if (true) {
return 1;
} elseif (true) {
return 2;
} else {
return 3;
}
return new \Psy\CodeCleaner\NoReturnValue();
EOS;
$values[] = array($from, $to);
$from = 'class A {}';
$to = <<<'EOS'
class A
{
}
return new \Psy\CodeCleaner\NoReturnValue();
EOS;
$values[] = array($from, $to);
$from = <<<'EOS'
switch (false) {
case 0:
0;
case 1:
1;
break;
case 2:
2;
return;
}
EOS;
$to = <<<'EOS'
switch (false) {
case 0:
0;
case 1:
return 1;
break;
case 2:
2;
return;
}
return new \Psy\CodeCleaner\NoReturnValue();
EOS;
$values[] = array($from, $to);
if (version_compare(PHP_VERSION, '5.4', '<')) {
$values[] = array('exit()', 'die;');
} else {
$values[] = array('exit()', 'exit;');
}
return $values;
}
}

View file

@ -0,0 +1,76 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use Psy\CodeCleaner\InstanceOfPass;
class InstanceOfPassTest extends CodeCleanerTestCase
{
protected function setUp()
{
$this->setPass(new InstanceOfPass());
}
/**
* @dataProvider invalidStatements
* @expectedException \Psy\Exception\FatalErrorException
*/
public function testProcessInvalidStatement($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
}
public function invalidStatements()
{
return array(
array('null instanceof stdClass'),
array('true instanceof stdClass'),
array('9 instanceof stdClass'),
array('1.0 instanceof stdClass'),
array('"foo" instanceof stdClass'),
array('__DIR__ instanceof stdClass'),
array('PHP_SAPI instanceof stdClass'),
array('1+1 instanceof stdClass'),
array('true && false instanceof stdClass'),
array('"a"."b" instanceof stdClass'),
array('!5 instanceof stdClass'),
);
}
/**
* @dataProvider validStatements
*/
public function testProcessValidStatement($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
// @todo a better thing to assert here?
$this->assertTrue(true);
}
public function validStatements()
{
$data = array(
array('$a instanceof stdClass'),
array('strtolower("foo") instanceof stdClass'),
array('array(1) instanceof stdClass'),
array('(string) "foo" instanceof stdClass'),
array('(1+1) instanceof stdClass'),
array('"foo ${foo} $bar" instanceof stdClass'),
array('DateTime::ISO8601 instanceof stdClass'),
);
return $data;
}
}

View file

@ -0,0 +1,75 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use Psy\CodeCleaner\LeavePsyshAlonePass;
class LeavePsyshAlonePassTest extends CodeCleanerTestCase
{
public function setUp()
{
$this->setPass(new LeavePsyshAlonePass());
}
public function testPassesInlineHtmlThroughJustFine()
{
$inline = $this->parse('not php at all!', '');
$this->traverse($inline);
// @todo a better thing to assert here?
$this->assertTrue(true);
}
/**
* @dataProvider validStatements
*/
public function testProcessStatementPasses($code)
{
$stmts = $this->parse($code);
$this->traverse($stmts);
// @todo a better thing to assert here?
$this->assertTrue(true);
}
public function validStatements()
{
return array(
array('array_merge()'),
array('__psysh__()'),
array('$this'),
array('$psysh'),
array('$__psysh'),
array('$banana'),
);
}
/**
* @dataProvider invalidStatements
* @expectedException \Psy\Exception\RuntimeException
*/
public function testProcessStatementFails($code)
{
$stmts = $this->parse($code);
$this->traverse($stmts);
}
public function invalidStatements()
{
return array(
array('$__psysh__'),
array('var_dump($__psysh__)'),
array('$__psysh__ = "your mom"'),
array('$__psysh__->fakeFunctionCall()'),
);
}
}

View file

@ -0,0 +1,80 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use Psy\CodeCleaner\LegacyEmptyPass;
class LegacyEmptyPassTest extends CodeCleanerTestCase
{
public function setUp()
{
$this->setPass(new LegacyEmptyPass());
}
/**
* @dataProvider invalidStatements
* @expectedException \Psy\Exception\ParseErrorException
*/
public function testProcessInvalidStatement($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
}
public function invalidStatements()
{
if (version_compare(PHP_VERSION, '5.5', '>=')) {
return array(
array('empty()'),
);
}
return array(
array('empty()'),
array('empty(null)'),
array('empty(PHP_EOL)'),
array('empty("wat")'),
array('empty(1.1)'),
array('empty(Foo::$bar)'),
);
}
/**
* @dataProvider validStatements
*/
public function testProcessValidStatement($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
// @todo a better thing to assert here?
$this->assertTrue(true);
}
public function validStatements()
{
if (version_compare(PHP_VERSION, '5.5', '<')) {
return array(
array('empty($foo)'),
);
}
return array(
array('empty($foo)'),
array('empty(null)'),
array('empty(PHP_EOL)'),
array('empty("wat")'),
array('empty(1.1)'),
array('empty(Foo::$bar)'),
);
}
}

View file

@ -0,0 +1,137 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use PhpParser\NodeTraverser;
use Psy\CodeCleaner\LoopContextPass;
class LoopContextPassTest extends CodeCleanerTestCase
{
public function setUp()
{
$this->pass = new LoopContextPass();
$this->traverser = new NodeTraverser();
$this->traverser->addVisitor($this->pass);
}
/**
* @dataProvider invalidStatements
* @expectedException \Psy\Exception\FatalErrorException
*/
public function testProcessStatementFails($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
}
public function invalidStatements()
{
return array(
array('continue'),
array('break'),
array('if (true) { continue; }'),
array('if (true) { break; }'),
array('if (false) { continue; }'),
array('if (false) { break; }'),
array('function foo() { break; }'),
array('function foo() { continue; }'),
// actually enforce break/continue depth argument
array('do { break 2; } while (true)'),
array('do { continue 2; } while (true)'),
array('for ($a; $b; $c) { break 2; }'),
array('for ($a; $b; $c) { continue 2; }'),
array('foreach ($a as $b) { break 2; }'),
array('foreach ($a as $b) { continue 2; }'),
array('switch (true) { default: break 2; }'),
array('switch (true) { default: continue 2; }'),
array('while (true) { break 2; }'),
array('while (true) { continue 2; }'),
// invalid in 5.4+ because they're floats
// ... in 5.3 because the number is too big
array('while (true) { break 2.0; }'),
array('while (true) { continue 2.0; }'),
// and once with nested loops, just for good measure
array('while (true) { while (true) { break 3; } }'),
array('while (true) { while (true) { continue 3; } }'),
);
}
/**
* @dataProvider invalidPHP54Statements
* @expectedException \Psy\Exception\FatalErrorException
*/
public function testPHP54ProcessStatementFails($code)
{
if (version_compare(PHP_VERSION, '5.4.0', '<')) {
$this->markTestSkipped();
}
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
}
public function invalidPHP54Statements()
{
return array(
// In PHP 5.4+, only positive literal integers are allowed
array('while (true) { break $n; }'),
array('while (true) { continue $n; }'),
array('while (true) { break N; }'),
array('while (true) { continue N; }'),
array('while (true) { break 0; }'),
array('while (true) { continue 0; }'),
array('while (true) { break -1; }'),
array('while (true) { continue -1; }'),
array('while (true) { break 1.0; }'),
array('while (true) { continue 1.0; }'),
);
}
/**
* @dataProvider validStatements
*/
public function testProcessStatementPasses($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
// @todo a better thing to assert here?
$this->assertTrue(true);
}
public function validStatements()
{
return array(
array('do { break; } while (true)'),
array('do { continue; } while (true)'),
array('for ($a; $b; $c) { break; }'),
array('for ($a; $b; $c) { continue; }'),
array('foreach ($a as $b) { break; }'),
array('foreach ($a as $b) { continue; }'),
array('switch (true) { default: break; }'),
array('switch (true) { default: continue; }'),
array('while (true) { break; }'),
array('while (true) { continue; }'),
// `break 1` is redundant, but not invalid
array('while (true) { break 1; }'),
array('while (true) { continue 1; }'),
// and once with nested loops just for good measure
array('while (true) { while (true) { break 2; } }'),
array('while (true) { while (true) { continue 2; } }'),
);
}
}

View file

@ -0,0 +1,39 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use Psy\CodeCleaner\MagicConstantsPass;
class MagicConstantsPassTest extends CodeCleanerTestCase
{
public function setUp()
{
$this->setPass(new MagicConstantsPass());
}
/**
* @dataProvider magicConstants
*/
public function testProcess($from, $to)
{
$this->assertProcessesAs($from, $to);
}
public function magicConstants()
{
return array(
array('__DIR__;', 'getcwd();'),
array('__FILE__;', "'';"),
array('___FILE___;', '___FILE___;'),
);
}
}

View file

@ -0,0 +1,56 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use Psy\CodeCleaner;
use Psy\CodeCleaner\NamespacePass;
class NamespacePassTest extends CodeCleanerTestCase
{
private $cleaner;
public function setUp()
{
$this->cleaner = new CodeCleaner();
$this->setPass(new NamespacePass($this->cleaner));
}
public function testProcess()
{
$this->process('array_merge()');
$this->assertNull($this->cleaner->getNamespace());
// A non-block namespace statement should set the current namespace.
$this->process('namespace Alpha');
$this->assertEquals(array('Alpha'), $this->cleaner->getNamespace());
// A new non-block namespace statement should override the current namespace.
$this->process('namespace Beta; class B {}');
$this->assertEquals(array('Beta'), $this->cleaner->getNamespace());
// @todo Figure out if we can detect when the last namespace block is
// bracketed or unbracketed, because this should really clear the
// namespace at the end...
$this->process('namespace Gamma { array_merge(); }');
$this->assertEquals(array('Gamma'), $this->cleaner->getNamespace());
// A null namespace clears out the current namespace.
$this->process('namespace { array_merge(); }');
$this->assertNull($this->cleaner->getNamespace());
}
private function process($code)
{
$stmts = $this->parse($code);
$this->traverse($stmts);
}
}

View file

@ -0,0 +1,115 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use PhpParser\NodeTraverser;
use Psy\CodeCleaner\PassableByReferencePass;
class PassableByReferencePassTest extends CodeCleanerTestCase
{
public function setUp()
{
$this->pass = new PassableByReferencePass();
$this->traverser = new NodeTraverser();
$this->traverser->addVisitor($this->pass);
}
/**
* @dataProvider invalidStatements
* @expectedException \Psy\Exception\FatalErrorException
*/
public function testProcessStatementFails($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
}
public function invalidStatements()
{
return array(
array('array_pop(array())'),
array('array_pop(array($foo))'),
array('array_shift(array())'),
);
}
/**
* @dataProvider validStatements
*/
public function testProcessStatementPasses($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
// @todo a better thing to assert here?
$this->assertTrue(true);
}
public function validStatements()
{
return array(
array('array_pop(json_decode("[]"))'),
array('array_pop($foo)'),
array('array_pop($foo->bar)'),
array('array_pop($foo::baz)'),
array('array_pop(Foo::qux)'),
);
}
/**
* @dataProvider validArrayMultisort
*/
public function testArrayMultisort($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
// @todo a better thing to assert here?
$this->assertTrue(true);
}
public function validArrayMultisort()
{
return array(
array('array_multisort($a)'),
array('array_multisort($a, $b)'),
array('array_multisort($a, SORT_NATURAL, $b)'),
array('array_multisort($a, SORT_NATURAL | SORT_FLAG_CASE, $b)'),
array('array_multisort($a, SORT_ASC, SORT_NATURAL | SORT_FLAG_CASE, $b)'),
array('array_multisort($a, SORT_NATURAL | SORT_FLAG_CASE, SORT_ASC, $b)'),
array('array_multisort($a, $b, SORT_ASC, SORT_NATURAL | SORT_FLAG_CASE)'),
array('array_multisort($a, SORT_NATURAL | SORT_FLAG_CASE, $b, SORT_ASC, SORT_NATURAL | SORT_FLAG_CASE)'),
array('array_multisort($a, 1, $b)'),
array('array_multisort($a, 1 + 2, $b)'),
array('array_multisort($a, getMultisortFlags(), $b)'),
);
}
/**
* @dataProvider invalidArrayMultisort
* @expectedException \Psy\Exception\FatalErrorException
*/
public function testInvalidArrayMultisort($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
}
public function invalidArrayMultisort()
{
return array(
array('array_multisort(1)'),
array('array_multisort(array(1, 2, 3))'),
array('array_multisort($a, SORT_NATURAL, SORT_ASC, SORT_NATURAL, $b)'),
);
}
}

View file

@ -0,0 +1,95 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use Psy\CodeCleaner\RequirePass;
class RequirePassTest extends CodeCleanerTestCase
{
public function setUp()
{
$this->setPass(new RequirePass());
}
/**
* @dataProvider exitStatements
*/
public function testExitStatement($from, $to)
{
$this->assertProcessesAs($from, $to);
}
public function exitStatements()
{
$resolve = '\\Psy\\CodeCleaner\\RequirePass::resolve';
if (version_compare(PHP_VERSION, '5.4', '<')) {
return array(
array('require $foo', "require $resolve(\$foo, 1);"),
array('$bar = require $baz', "\$bar = (require $resolve(\$baz, 1));"),
);
}
return array(
// The basics
array('require "a"', "require $resolve(\"a\", 1);"),
array('require "b.php"', "require $resolve(\"b.php\", 1);"),
array('require_once "c"', "require_once $resolve(\"c\", 1);"),
array('require_once "d.php"', "require_once $resolve(\"d.php\", 1);"),
// Ensure that line numbers work correctly
array("null;\nrequire \"e.php\"", "null;\nrequire $resolve(\"e.php\", 2);"),
array("null;\nrequire_once \"f.php\"", "null;\nrequire_once $resolve(\"f.php\", 2);"),
// Things with expressions
array('require $foo', "require $resolve(\$foo, 1);"),
array('require_once $foo', "require_once $resolve(\$foo, 1);"),
array('require ($bar = "g.php")', "require $resolve(\$bar = \"g.php\", 1);"),
array('require_once ($bar = "h.php")', "require_once $resolve(\$bar = \"h.php\", 1);"),
array('$bar = require ($baz = "i.php")', "\$bar = (require $resolve(\$baz = \"i.php\", 1));"),
array('$bar = require_once ($baz = "j.php")', "\$bar = (require_once $resolve(\$baz = \"j.php\", 1));"),
);
}
/**
* @expectedException \Psy\Exception\FatalErrorException
* @expectedExceptionMessage Failed opening required 'not a file name' in eval()'d code on line 2
*/
public function testResolve()
{
RequirePass::resolve('not a file name', 2);
}
/**
* @dataProvider emptyWarnings
*
* @expectedException \Psy\Exception\ErrorException
* @expectedExceptionMessage Filename cannot be empty on line 1
*/
public function testResolveEmptyWarnings($file)
{
if (!E_WARNING & error_reporting()) {
$this->markTestSkipped();
}
RequirePass::resolve($file, 1);
}
public function emptyWarnings()
{
return array(
array(null),
array(false),
array(''),
);
}
}

View file

@ -0,0 +1,94 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use Psy\CodeCleaner\StaticConstructorPass;
class StaticConstructorPassTest extends CodeCleanerTestCase
{
protected function setUp()
{
$this->setPass(new StaticConstructorPass());
}
/**
* @dataProvider invalidStatements
* @expectedException \Psy\Exception\FatalErrorException
*/
public function testProcessInvalidStatement($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
}
/**
* @dataProvider invalidParserStatements
* @expectedException \Psy\Exception\ParseErrorException
*/
public function testProcessInvalidStatementCatchedByParser($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
}
public function invalidStatements()
{
$statements = array(
array('class A { public static function A() {}}'),
array('class A { private static function A() {}}'),
);
if (version_compare(PHP_VERSION, '5.3.3', '<')) {
$statements[] = array('namespace B; class A { private static function A() {}}');
}
return $statements;
}
public function invalidParserStatements()
{
$statements = array(
array('class A { public static function __construct() {}}'),
array('class A { private static function __construct() {}}'),
array('class A { private static function __construct() {} public function A() {}}'),
array('namespace B; class A { private static function __construct() {}}'),
);
return $statements;
}
/**
* @dataProvider validStatements
*/
public function testProcessValidStatement($code)
{
$stmts = $this->parse($code);
$this->traverser->traverse($stmts);
// @todo a better thing to assert here?
$this->assertTrue(true);
}
public function validStatements()
{
$statements = array(
array('class A { public static function A() {} public function __construct() {}}'),
array('class A { private function __construct() {} public static function A() {}}'),
);
if (version_compare(PHP_VERSION, '5.3.3', '>=')) {
$statements[] = array('namespace B; class A { private static function A() {}}');
}
return $statements;
}
}

View file

@ -0,0 +1,53 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use Psy\CodeCleaner\StrictTypesPass;
class StrictTypesPassTest extends CodeCleanerTestCase
{
public function setUp()
{
if (version_compare(PHP_VERSION, '7.0', '<')) {
$this->markTestSkipped();
}
$this->setPass(new StrictTypesPass());
}
public function testProcess()
{
$this->assertProcessesAs('declare(strict_types=1)', 'declare (strict_types=1);');
$this->assertProcessesAs('null', "declare (strict_types=1);\nnull;");
$this->assertProcessesAs('declare(strict_types=0)', 'declare (strict_types=0);');
$this->assertProcessesAs('null', 'null;');
}
/**
* @dataProvider invalidDeclarations
* @expectedException \Psy\Exception\FatalErrorException
*/
public function testInvalidDeclarations($declaration)
{
$stmts = $this->parse($declaration);
$this->traverser->traverse($stmts);
}
public function invalidDeclarations()
{
return array(
array('declare(strict_types=-1)'),
array('declare(strict_types=2)'),
array('declare(strict_types="foo")'),
);
}
}

View file

@ -0,0 +1,52 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use Psy\CodeCleaner\UseStatementPass;
class UseStatementPassTest extends CodeCleanerTestCase
{
public function setUp()
{
$this->setPass(new UseStatementPass());
}
/**
* @dataProvider useStatements
*/
public function testProcess($from, $to)
{
$this->assertProcessesAs($from, $to);
}
public function useStatements()
{
return array(
array(
"use StdClass as NotSoStd;\n\$std = new NotSoStd();",
'$std = new \\StdClass();',
),
array(
"namespace Foo;\n\nuse StdClass as S;\n\$std = new S();",
"namespace Foo;\n\n\$std = new \\StdClass();",
),
array(
"namespace Foo;\n\nuse \\StdClass as S;\n\$std = new S();",
"namespace Foo;\n\n\$std = new \\StdClass();",
),
array(
"use Foo\\Bar as fb;\n\$baz = new fb\\Baz();",
'$baz = new \\Foo\\Bar\\Baz();',
),
);
}
}

View file

@ -0,0 +1,336 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use Psy\CodeCleaner\ValidClassNamePass;
use Psy\Exception\Exception;
class ValidClassNamePassTest extends CodeCleanerTestCase
{
public function setUp()
{
$this->setPass(new ValidClassNamePass());
}
/**
* @dataProvider getInvalid
*/
public function testProcessInvalid($code, $php54 = false)
{
try {
$stmts = $this->parse($code);
$this->traverse($stmts);
$this->fail();
} catch (Exception $e) {
if ($php54 && version_compare(PHP_VERSION, '5.4', '<')) {
$this->assertInstanceOf('Psy\Exception\ParseErrorException', $e);
} else {
$this->assertInstanceOf('Psy\Exception\FatalErrorException', $e);
}
}
}
public function getInvalid()
{
// class declarations
return array(
// core class
array('class stdClass {}'),
// capitalization
array('class stdClass {}'),
// collisions with interfaces and traits
array('interface stdClass {}'),
array('trait stdClass {}', true),
// collisions inside the same code snippet
array('
class Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
class Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
'),
array('
class Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
trait Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
', true),
array('
trait Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
class Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
', true),
array('
trait Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
interface Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
', true),
array('
interface Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
trait Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
', true),
array('
interface Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
class Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
'),
array('
class Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
interface Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
'),
// namespaced collisions
array('
namespace Psy\\Test\\CodeCleaner {
class ValidClassNamePassTest {}
}
'),
array('
namespace Psy\\Test\\CodeCleaner\\ValidClassNamePass {
class Beta {}
}
namespace Psy\\Test\\CodeCleaner\\ValidClassNamePass {
class Beta {}
}
'),
// extends and implements
array('class ValidClassNamePassTest extends NotAClass {}'),
array('class ValidClassNamePassTest extends ArrayAccess {}'),
array('class ValidClassNamePassTest implements stdClass {}'),
array('class ValidClassNamePassTest implements ArrayAccess, stdClass {}'),
array('interface ValidClassNamePassTest extends stdClass {}'),
array('interface ValidClassNamePassTest extends ArrayAccess, stdClass {}'),
// class instantiations
array('new Psy_Test_CodeCleaner_ValidClassNamePass_Gamma();'),
array('
namespace Psy\\Test\\CodeCleaner\\ValidClassNamePass {
new Psy_Test_CodeCleaner_ValidClassNamePass_Delta();
}
'),
// class constant fetch
array('Psy\\Test\\CodeCleaner\\ValidClassNamePass\\NotAClass::FOO'),
// static call
array('Psy\\Test\\CodeCleaner\\ValidClassNamePass\\NotAClass::foo()'),
array('Psy\\Test\\CodeCleaner\\ValidClassNamePass\\NotAClass::$foo()'),
);
}
/**
* @dataProvider getValid
*/
public function testProcessValid($code)
{
$stmts = $this->parse($code);
$this->traverse($stmts);
// @todo a better thing to assert here?
$this->assertTrue(true);
}
public function getValid()
{
$valid = array(
// class declarations
array('class Psy_Test_CodeCleaner_ValidClassNamePass_Epsilon {}'),
array('namespace Psy\Test\CodeCleaner\ValidClassNamePass; class Zeta {}'),
array('
namespace { class Psy_Test_CodeCleaner_ValidClassNamePass_Eta {}; }
namespace Psy\\Test\\CodeCleaner\\ValidClassNamePass {
class Psy_Test_CodeCleaner_ValidClassNamePass_Eta {}
}
'),
array('namespace Psy\Test\CodeCleaner\ValidClassNamePass { class stdClass {} }'),
// class instantiations
array('new stdClass();'),
array('new stdClass();'),
array('
namespace Psy\\Test\\CodeCleaner\\ValidClassNamePass {
class Theta {}
}
namespace Psy\\Test\\CodeCleaner\\ValidClassNamePass {
new Theta();
}
'),
array('
namespace Psy\\Test\\CodeCleaner\\ValidClassNamePass {
class Iota {}
new Iota();
}
'),
array('
namespace Psy\\Test\\CodeCleaner\\ValidClassNamePass {
class Kappa {}
}
namespace {
new \\Psy\\Test\\CodeCleaner\\ValidClassNamePass\\Kappa();
}
'),
// Class constant fetch (ValidConstantPassTest validates the actual constant)
array('class A {} A::FOO'),
array('$a = new DateTime; $a::ATOM'),
array('interface A { const B = 1; } A::B'),
// static call
array('DateTime::createFromFormat()'),
array('DateTime::$someMethod()'),
array('Psy\Test\CodeCleaner\Fixtures\ClassWithStatic::doStuff()'),
array('Psy\Test\CodeCleaner\Fixtures\ClassWithCallStatic::doStuff()'),
// Allow `self` and `static` as class names.
array('
class Psy_Test_CodeCleaner_ValidClassNamePass_ClassWithStatic {
public static function getInstance() {
return new self();
}
}
'),
array('
class Psy_Test_CodeCleaner_ValidClassNamePass_ClassWithStatic {
public static function getInstance() {
return new SELF();
}
}
'),
array('
class Psy_Test_CodeCleaner_ValidClassNamePass_ClassWithStatic {
public static function getInstance() {
return new self;
}
}
'),
array('
class Psy_Test_CodeCleaner_ValidClassNamePass_ClassWithStatic {
public static function getInstance() {
return new static();
}
}
'),
array('
class Psy_Test_CodeCleaner_ValidClassNamePass_ClassWithStatic {
public static function getInstance() {
return new Static();
}
}
'),
array('
class Psy_Test_CodeCleaner_ValidClassNamePass_ClassWithStatic {
public static function getInstance() {
return new static;
}
}
'),
array('
class Psy_Test_CodeCleaner_ValidClassNamePass_ClassWithStatic {
public static function foo() {
return parent::bar();
}
}
'),
array('
class Psy_Test_CodeCleaner_ValidClassNamePass_ClassWithStatic {
public static function foo() {
return self::bar();
}
}
'),
array('
class Psy_Test_CodeCleaner_ValidClassNamePass_ClassWithStatic {
public static function foo() {
return static::bar();
}
}
'),
array('class A { static function b() { return new A; } }'),
array('
class A {
const B = 123;
function c() {
return A::B;
}
}
'),
array('class A {} class B { function c() { return new A; } }'),
// recursion
array('class A { function a() { A::a(); } }'),
// conditionally defined classes
array('
class A {}
if (false) {
class A {}
}
'),
array('
class A {}
if (true) {
class A {}
} else if (false) {
class A {}
} else {
class A {}
}
'),
// ewww
array('
class A {}
if (true):
class A {}
elseif (false):
class A {}
else:
class A {}
endif;
'),
array('
class A {}
while (false) { class A {} }
'),
array('
class A {}
do { class A {} } while (false);
'),
array('
class A {}
switch (1) {
case 0:
class A {}
break;
case 1:
class A {}
break;
case 2:
class A {}
break;
}
'),
);
// Ugh. There's gotta be a better way to test for this.
if (class_exists('PhpParser\ParserFactory')) {
// PHP 7.0 anonymous classes, only supported by PHP Parser v2.x
$valid[] = array('$obj = new class() {}');
}
if (version_compare(PHP_VERSION, '5.5', '>=')) {
$valid[] = array('interface A {} A::class');
$valid[] = array('interface A {} A::CLASS');
$valid[] = array('class A {} A::class');
$valid[] = array('class A {} A::CLASS');
$valid[] = array('A::class');
$valid[] = array('A::CLASS');
}
return $valid;
}
}

View file

@ -0,0 +1,69 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use Psy\CodeCleaner\ValidConstantPass;
class ValidConstantPassTest extends CodeCleanerTestCase
{
public function setUp()
{
$this->setPass(new ValidConstantPass());
}
/**
* @dataProvider getInvalidReferences
* @expectedException \Psy\Exception\FatalErrorException
*/
public function testProcessInvalidConstantReferences($code)
{
$stmts = $this->parse($code);
$this->traverse($stmts);
}
public function getInvalidReferences()
{
return array(
array('Foo\BAR'),
// class constant fetch
array('Psy\Test\CodeCleaner\ValidConstantPassTest::FOO'),
array('DateTime::BACON'),
);
}
/**
* @dataProvider getValidReferences
*/
public function testProcessValidConstantReferences($code)
{
$stmts = $this->parse($code);
$this->traverse($stmts);
// @todo a better thing to assert here?
$this->assertTrue(true);
}
public function getValidReferences()
{
return array(
array('PHP_EOL'),
// class constant fetch
array('NotAClass::FOO'),
array('DateTime::ATOM'),
array('$a = new DateTime; $a::ATOM'),
array('DateTime::class'),
array('$a = new DateTime; $a::class'),
);
}
}

View file

@ -0,0 +1,184 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\CodeCleaner;
use Psy\CodeCleaner\ValidFunctionNamePass;
class ValidFunctionNamePassTest extends CodeCleanerTestCase
{
public function setUp()
{
$this->setPass(new ValidFunctionNamePass());
}
/**
* @dataProvider getInvalidFunctions
* @expectedException \Psy\Exception\FatalErrorException
*/
public function testProcessInvalidFunctionCallsAndDeclarations($code)
{
$stmts = $this->parse($code);
$this->traverse($stmts);
}
public function getInvalidFunctions()
{
return array(
// function declarations
array('function array_merge() {}'),
array('function Array_Merge() {}'),
array('
function psy_test_codecleaner_validfunctionnamepass_alpha() {}
function psy_test_codecleaner_validfunctionnamepass_alpha() {}
'),
array('
namespace Psy\\Test\\CodeCleaner\\ValidFunctionNamePass {
function beta() {}
}
namespace Psy\\Test\\CodeCleaner\\ValidFunctionNamePass {
function beta() {}
}
'),
// function calls
array('psy_test_codecleaner_validfunctionnamepass_gamma()'),
array('
namespace Psy\\Test\\CodeCleaner\\ValidFunctionNamePass {
delta();
}
'),
// recursion
array('function a() { a(); } function a() {}'),
);
}
/**
* @dataProvider getValidFunctions
*/
public function testProcessValidFunctionCallsAndDeclarations($code)
{
$stmts = $this->parse($code);
$this->traverse($stmts);
// @todo a better thing to assert here?
$this->assertTrue(true);
}
public function getValidFunctions()
{
return array(
array('function psy_test_codecleaner_validfunctionnamepass_epsilon() {}'),
array('
namespace Psy\\Test\\CodeCleaner\\ValidFunctionNamePass {
function zeta() {}
}
'),
array('
namespace {
function psy_test_codecleaner_validfunctionnamepass_eta() {}
}
namespace Psy\\Test\\CodeCleaner\\ValidFunctionNamePass {
function psy_test_codecleaner_validfunctionnamepass_eta() {}
}
'),
array('
namespace Psy\\Test\\CodeCleaner\\ValidFunctionNamePass {
function psy_test_codecleaner_validfunctionnamepass_eta() {}
}
namespace {
function psy_test_codecleaner_validfunctionnamepass_eta() {}
}
'),
array('
namespace Psy\\Test\\CodeCleaner\\ValidFunctionNamePass {
function array_merge() {}
}
'),
// function calls
array('array_merge();'),
array('
namespace Psy\\Test\\CodeCleaner\\ValidFunctionNamePass {
function theta() {}
}
namespace Psy\\Test\\CodeCleaner\\ValidFunctionNamePass {
theta();
}
'),
// closures
array('$test = function(){};$test()'),
array('
namespace Psy\\Test\\CodeCleaner\\ValidFunctionNamePass {
function theta() {}
}
namespace {
Psy\\Test\\CodeCleaner\\ValidFunctionNamePass\\theta();
}
'),
// recursion
array('function a() { a(); }'),
// conditionally defined functions
array('
function a() {}
if (false) {
function a() {}
}
'),
array('
function a() {}
if (true) {
function a() {}
} else if (false) {
function a() {}
} else {
function a() {}
}
'),
// ewww
array('
function a() {}
if (true):
function a() {}
elseif (false):
function a() {}
else:
function a() {}
endif;
'),
array('
function a() {}
while (false) { function a() {} }
'),
array('
function a() {}
do { function a() {} } while (false);
'),
array('
function a() {}
switch (1) {
case 0:
function a() {}
break;
case 1:
function a() {}
break;
case 2:
function a() {}
break;
}
'),
);
}
}

View file

@ -0,0 +1,132 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test;
use Psy\CodeCleaner;
class CodeCleanerTest extends \PHPUnit\Framework\TestCase
{
/**
* @dataProvider semicolonCodeProvider
*/
public function testAutomaticSemicolons(array $lines, $requireSemicolons, $expected)
{
$cc = new CodeCleaner();
$this->assertEquals($expected, $cc->clean($lines, $requireSemicolons));
}
public function semicolonCodeProvider()
{
$values = array(
array(array('true'), false, 'return true;'),
array(array('true;'), false, 'return true;'),
array(array('true;'), true, 'return true;'),
array(array('true'), true, false),
array(array('echo "foo";', 'true'), true, false),
);
if (version_compare(PHP_VERSION, '5.4', '<')) {
$values[] = array(array('echo "foo";', 'true'), false, "echo 'foo';\nreturn true;");
} else {
$values[] = array(array('echo "foo";', 'true'), false, "echo \"foo\";\nreturn true;");
}
return $values;
}
/**
* @dataProvider unclosedStatementsProvider
*/
public function testUnclosedStatements(array $lines, $isUnclosed)
{
$cc = new CodeCleaner();
$res = $cc->clean($lines);
if ($isUnclosed) {
$this->assertFalse($res);
} else {
$this->assertNotFalse($res);
}
}
public function unclosedStatementsProvider()
{
return array(
array(array('echo "'), true),
array(array('echo \''), true),
array(array('if (1) {'), true),
array(array('echo ""'), false),
array(array("echo ''"), false),
array(array('if (1) {}'), false),
array(array('// closed comment'), false),
array(array('function foo() { /**'), true),
array(array('var_dump(1, 2,'), true),
array(array('var_dump(1, 2,', '3)'), false),
);
}
/**
* @dataProvider moreUnclosedStatementsProvider
*/
public function testMoreUnclosedStatements(array $lines)
{
if (defined('HHVM_VERSION')) {
$this->markTestSkipped('HHVM not supported.');
}
$cc = new CodeCleaner();
$res = $cc->clean($lines);
$this->assertFalse($res);
}
public function moreUnclosedStatementsProvider()
{
return array(
array(array("\$content = <<<EOS\n")),
array(array("\$content = <<<'EOS'\n")),
array(array('/* unclosed comment')),
array(array('/** unclosed comment')),
);
}
/**
* @dataProvider invalidStatementsProvider
* @expectedException \Psy\Exception\ParseErrorException
*/
public function testInvalidStatementsThrowParseErrors($code)
{
$cc = new CodeCleaner();
$cc->clean(array($code));
}
public function invalidStatementsProvider()
{
return array(
array('function "what'),
array("function 'what"),
array('echo }'),
array('echo {'),
array('if (1) }'),
array('echo """'),
array("echo '''"),
array('$foo "bar'),
array('$foo \'bar'),
array('var_dump(1,2,)'),
);
}
}

View file

@ -0,0 +1,260 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test;
use Psy\CodeCleaner;
use Psy\Configuration;
use Psy\ExecutionLoop\Loop;
use Psy\Output\PassthruPager;
use Psy\VersionUpdater\GitHubChecker;
use Symfony\Component\Console\Output\ConsoleOutput;
class ConfigurationTest extends \PHPUnit\Framework\TestCase
{
private function getConfig($configFile = null)
{
return new Configuration(array(
'configFile' => $configFile ?: __DIR__ . '/../../fixtures/empty.php',
));
}
public function testDefaults()
{
$config = $this->getConfig();
$this->assertEquals(function_exists('readline'), $config->hasReadline());
$this->assertEquals(function_exists('readline'), $config->useReadline());
$this->assertEquals(function_exists('pcntl_signal'), $config->hasPcntl());
$this->assertEquals(function_exists('pcntl_signal'), $config->usePcntl());
$this->assertFalse($config->requireSemicolons());
$this->assertSame(Configuration::COLOR_MODE_AUTO, $config->colorMode());
$this->assertNull($config->getStartupMessage());
}
public function testGettersAndSetters()
{
$config = $this->getConfig();
$this->assertNull($config->getDataDir());
$config->setDataDir('wheee');
$this->assertEquals('wheee', $config->getDataDir());
$this->assertNull($config->getConfigDir());
$config->setConfigDir('wheee');
$this->assertEquals('wheee', $config->getConfigDir());
}
/**
* @dataProvider directories
*/
public function testFilesAndDirectories($home, $configFile, $historyFile, $manualDbFile)
{
$oldHome = getenv('HOME');
putenv("HOME=$home");
$config = new Configuration();
$this->assertEquals(realpath($configFile), realpath($config->getConfigFile()));
$this->assertEquals(realpath($historyFile), realpath($config->getHistoryFile()));
$this->assertEquals(realpath($manualDbFile), realpath($config->getManualDbFile()));
putenv("HOME=$oldHome");
}
public function directories()
{
$base = realpath(__DIR__ . '/../../fixtures');
return array(
array(
$base . '/default',
$base . '/default/.config/psysh/config.php',
$base . '/default/.config/psysh/psysh_history',
$base . '/default/.local/share/psysh/php_manual.sqlite',
),
array(
$base . '/legacy',
$base . '/legacy/.psysh/rc.php',
$base . '/legacy/.psysh/history',
$base . '/legacy/.psysh/php_manual.sqlite',
),
array(
$base . '/mixed',
$base . '/mixed/.psysh/config.php',
$base . '/mixed/.psysh/psysh_history',
null,
),
);
}
public function testLoadConfig()
{
$config = $this->getConfig();
$cleaner = new CodeCleaner();
$pager = new PassthruPager(new ConsoleOutput());
$loop = new Loop($config);
$config->loadConfig(array(
'useReadline' => false,
'usePcntl' => false,
'codeCleaner' => $cleaner,
'pager' => $pager,
'loop' => $loop,
'requireSemicolons' => true,
'errorLoggingLevel' => E_ERROR | E_WARNING,
'colorMode' => Configuration::COLOR_MODE_FORCED,
'startupMessage' => 'Psysh is awesome!',
));
$this->assertFalse($config->useReadline());
$this->assertFalse($config->usePcntl());
$this->assertSame($cleaner, $config->getCodeCleaner());
$this->assertSame($pager, $config->getPager());
$this->assertSame($loop, $config->getLoop());
$this->assertTrue($config->requireSemicolons());
$this->assertEquals(E_ERROR | E_WARNING, $config->errorLoggingLevel());
$this->assertSame(Configuration::COLOR_MODE_FORCED, $config->colorMode());
$this->assertSame('Psysh is awesome!', $config->getStartupMessage());
}
public function testLoadConfigFile()
{
$config = $this->getConfig(__DIR__ . '/../../fixtures/config.php');
$runtimeDir = $this->joinPath(realpath(sys_get_temp_dir()), 'psysh_test', 'withconfig', 'temp');
$this->assertStringStartsWith($runtimeDir, realpath($config->getTempFile('foo', 123)));
$this->assertStringStartsWith($runtimeDir, realpath(dirname($config->getPipe('pipe', 123))));
$this->assertStringStartsWith($runtimeDir, realpath($config->getRuntimeDir()));
$this->assertEquals(function_exists('readline'), $config->useReadline());
$this->assertFalse($config->usePcntl());
$this->assertEquals(E_ALL & ~E_NOTICE, $config->errorLoggingLevel());
}
public function testLoadLocalConfigFile()
{
$oldPwd = getcwd();
chdir(realpath(__DIR__ . '/../../fixtures/project/'));
$config = new Configuration();
// When no configuration file is specified local project config is merged
$this->assertFalse($config->useReadline());
$this->assertTrue($config->usePcntl());
$config = new Configuration(array('configFile' => __DIR__ . '/../../fixtures/config.php'));
// Defining a configuration file skips loading local project config
$this->assertTrue($config->useReadline());
$this->assertFalse($config->usePcntl());
chdir($oldPwd);
}
/**
* @expectedException \Psy\Exception\DeprecatedException
*/
public function testBaseDirConfigIsDeprecated()
{
$config = new Configuration(array('baseDir' => 'fake'));
}
private function joinPath()
{
return implode(DIRECTORY_SEPARATOR, func_get_args());
}
public function testConfigIncludes()
{
$config = new Configuration(array(
'defaultIncludes' => array('/file.php'),
'configFile' => __DIR__ . '/../../fixtures/empty.php',
));
$includes = $config->getDefaultIncludes();
$this->assertCount(1, $includes);
$this->assertEquals('/file.php', $includes[0]);
}
public function testGetOutput()
{
$config = $this->getConfig();
$output = $config->getOutput();
$this->assertInstanceOf('\Psy\Output\ShellOutput', $output);
}
public function getOutputDecoratedProvider()
{
return array(
'auto' => array(
null,
Configuration::COLOR_MODE_AUTO,
),
'forced' => array(
true,
Configuration::COLOR_MODE_FORCED,
),
'disabled' => array(
false,
Configuration::COLOR_MODE_DISABLED,
),
);
}
/** @dataProvider getOutputDecoratedProvider */
public function testGetOutputDecorated($expectation, $colorMode)
{
$config = $this->getConfig();
$config->setColorMode($colorMode);
$this->assertSame($expectation, $config->getOutputDecorated());
}
public function setColorModeValidProvider()
{
return array(
'auto' => array(Configuration::COLOR_MODE_AUTO),
'forced' => array(Configuration::COLOR_MODE_FORCED),
'disabled' => array(Configuration::COLOR_MODE_DISABLED),
);
}
/** @dataProvider setColorModeValidProvider */
public function testSetColorModeValid($colorMode)
{
$config = $this->getConfig();
$config->setColorMode($colorMode);
$this->assertEquals($colorMode, $config->colorMode());
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage invalid color mode: some invalid mode
*/
public function testSetColorModeInvalid()
{
$config = $this->getConfig();
$config->setColorMode('some invalid mode');
}
public function testSetCheckerValid()
{
$config = $this->getConfig();
$checker = new GitHubChecker();
$config->setChecker($checker);
$this->assertSame($checker, $config->getChecker());
}
}

View file

@ -0,0 +1,51 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test;
use Psy\Configuration;
use Psy\ConsoleColorFactory;
class ConsoleColorFactoryTest extends \PHPUnit\Framework\TestCase
{
public function testGetConsoleColorAuto()
{
$colorMode = Configuration::COLOR_MODE_AUTO;
$factory = new ConsoleColorFactory($colorMode);
$colors = $factory->getConsoleColor();
$themes = $colors->getThemes();
$this->assertFalse($colors->isStyleForced());
$this->assertEquals(array('blue'), $themes['line_number']);
}
public function testGetConsoleColorForced()
{
$colorMode = Configuration::COLOR_MODE_FORCED;
$factory = new ConsoleColorFactory($colorMode);
$colors = $factory->getConsoleColor();
$themes = $colors->getThemes();
$this->assertTrue($colors->isStyleForced());
$this->assertEquals(array('blue'), $themes['line_number']);
}
public function testGetConsoleColorDisabled()
{
$colorMode = Configuration::COLOR_MODE_DISABLED;
$factory = new ConsoleColorFactory($colorMode);
$colors = $factory->getConsoleColor();
$themes = $colors->getThemes();
$this->assertFalse($colors->isStyleForced());
$this->assertEquals(array('none'), $themes['line_number']);
}
}

View file

@ -0,0 +1,34 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\Exception;
use Psy\Exception\BreakException;
use Psy\Exception\Exception;
class BreakExceptionTest extends \PHPUnit\Framework\TestCase
{
public function testInstance()
{
$e = new BreakException();
$this->assertTrue($e instanceof Exception);
$this->assertTrue($e instanceof BreakException);
}
public function testMessage()
{
$e = new BreakException('foo');
$this->assertContains('foo', $e->getMessage());
$this->assertEquals('foo', $e->getRawMessage());
}
}

View file

@ -0,0 +1,108 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\Exception;
use Psy\Exception\ErrorException;
use Psy\Exception\Exception;
class ErrorExceptionTest extends \PHPUnit\Framework\TestCase
{
public function testInstance()
{
$e = new ErrorException();
$this->assertTrue($e instanceof Exception);
$this->assertTrue($e instanceof \ErrorException);
$this->assertTrue($e instanceof ErrorException);
}
public function testMessage()
{
$e = new ErrorException('foo');
$this->assertContains('foo', $e->getMessage());
$this->assertEquals('foo', $e->getRawMessage());
}
/**
* @dataProvider getLevels
*/
public function testErrorLevels($level, $type)
{
$e = new ErrorException('foo', 0, $level);
$this->assertContains('PHP ' . $type, $e->getMessage());
}
/**
* @dataProvider getLevels
*/
public function testThrowException($level, $type)
{
try {
ErrorException::throwException($level, '{whot}', '{file}', '13');
} catch (ErrorException $e) {
$this->assertContains('PHP ' . $type, $e->getMessage());
$this->assertContains('{whot}', $e->getMessage());
$this->assertContains('in {file}', $e->getMessage());
$this->assertContains('on line 13', $e->getMessage());
}
}
public function getLevels()
{
return array(
array(E_WARNING, 'Warning'),
array(E_CORE_WARNING, 'Warning'),
array(E_COMPILE_WARNING, 'Warning'),
array(E_USER_WARNING, 'Warning'),
array(E_STRICT, 'Strict error'),
array(0, 'Error'),
);
}
/**
* @dataProvider getUserLevels
*/
public function testThrowExceptionAsErrorHandler($level, $type)
{
set_error_handler(array('Psy\Exception\ErrorException', 'throwException'));
try {
trigger_error('{whot}', $level);
} catch (ErrorException $e) {
$this->assertContains('PHP ' . $type, $e->getMessage());
$this->assertContains('{whot}', $e->getMessage());
}
restore_error_handler();
}
public function getUserLevels()
{
return array(
array(E_USER_ERROR, 'Error'),
array(E_USER_WARNING, 'Warning'),
array(E_USER_NOTICE, 'Notice'),
array(E_USER_DEPRECATED, 'Deprecated'),
);
}
public function testIgnoreExecutionLoopFilename()
{
$e = new ErrorException('{{message}}', 0, 1, '/fake/path/to/Psy/ExecutionLoop/Loop.php');
$this->assertEmpty($e->getFile());
$e = new ErrorException('{{message}}', 0, 1, 'c:\fake\path\to\Psy\ExecutionLoop\Loop.php');
$this->assertEmpty($e->getFile());
$e = new ErrorException('{{message}}', 0, 1, '/fake/path/to/Psy/File.php');
$this->assertNotEmpty($e->getFile());
}
}

View file

@ -0,0 +1,46 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\Exception;
use Psy\Exception\Exception;
use Psy\Exception\FatalErrorException;
class FatalErrorExceptionTest extends \PHPUnit\Framework\TestCase
{
public function testInstance()
{
$e = new FatalErrorException();
$this->assertTrue($e instanceof Exception);
$this->assertTrue($e instanceof \ErrorException);
$this->assertTrue($e instanceof FatalErrorException);
}
public function testMessage()
{
$e = new FatalErrorException('{msg}', 0, 0, '{filename}', 13);
$this->assertEquals('{msg}', $e->getRawMessage());
$this->assertContains('{msg}', $e->getMessage());
$this->assertContains('{filename}', $e->getMessage());
$this->assertContains('line 13', $e->getMessage());
}
public function testMessageWithNoFilename()
{
$e = new FatalErrorException('{msg}');
$this->assertEquals('{msg}', $e->getRawMessage());
$this->assertContains('{msg}', $e->getMessage());
$this->assertContains('eval()\'d code', $e->getMessage());
}
}

View file

@ -0,0 +1,43 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\Exception;
use Psy\Exception\Exception;
use Psy\Exception\ParseErrorException;
class ParseErrorExceptionTest extends \PHPUnit\Framework\TestCase
{
public function testInstance()
{
$e = new ParseErrorException();
$this->assertTrue($e instanceof Exception);
$this->assertTrue($e instanceof \PhpParser\Error);
$this->assertTrue($e instanceof ParseErrorException);
}
public function testMessage()
{
$e = new ParseErrorException('{msg}', 1);
$this->assertContains('{msg}', $e->getMessage());
$this->assertContains('PHP Parse error:', $e->getMessage());
}
public function testConstructFromParseError()
{
$e = ParseErrorException::fromParseError(new \PhpParser\Error('{msg}'));
$this->assertContains('{msg}', $e->getRawMessage());
$this->assertContains('PHP Parse error:', $e->getMessage());
}
}

View file

@ -0,0 +1,31 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\Exception;
use Psy\Exception\Exception;
use Psy\Exception\RuntimeException;
class RuntimeExceptionTest extends \PHPUnit\Framework\TestCase
{
public function testException()
{
$msg = 'bananas';
$e = new RuntimeException($msg);
$this->assertTrue($e instanceof Exception);
$this->assertTrue($e instanceof \RuntimeException);
$this->assertTrue($e instanceof RuntimeException);
$this->assertEquals($msg, $e->getMessage());
$this->assertEquals($msg, $e->getRawMessage());
}
}

View file

@ -0,0 +1,65 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\Formatter;
use Psy\Formatter\CodeFormatter;
class CodeFormatterTest extends \PHPUnit\Framework\TestCase
{
private function ignoreThisMethod($arg)
{
echo 'whot!';
}
public function testFormat()
{
$expected = <<<'EOS'
> 18| private function ignoreThisMethod($arg)
19| {
20| echo 'whot!';
21| }
EOS;
$formatted = CodeFormatter::format(new \ReflectionMethod($this, 'ignoreThisMethod'));
$formattedWithoutColors = preg_replace('#' . chr(27) . '\[\d\d?m#', '', $formatted);
$this->assertEquals($expected, rtrim($formattedWithoutColors));
$this->assertNotEquals($expected, rtrim($formatted));
}
/**
* @dataProvider filenames
* @expectedException \Psy\Exception\RuntimeException
*/
public function testCodeFormatterThrowsException($filename)
{
$reflector = $this->getMockBuilder('ReflectionClass')
->disableOriginalConstructor()
->getMock();
$reflector
->expects($this->once())
->method('getFileName')
->will($this->returnValue($filename));
CodeFormatter::format($reflector);
}
public function filenames()
{
if (defined('HHVM_VERSION')) {
$this->markTestSkipped('We have issues with PHPUnit mocks on HHVM.');
}
return array(array(null), array('not a file'));
}
}

View file

@ -0,0 +1,63 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\Formatter;
use Psy\Formatter\DocblockFormatter;
class DocblockFormatterTest extends \PHPUnit\Framework\TestCase
{
/**
* This is a docblock!
*
* @author Justin Hileman <justin@justinhileman.info>
*
* @throws InvalidArgumentException if $foo is empty
*
* @param mixed $foo It's a foo thing
* @param int $bar This is definitely bar
*
* @return string A string of no consequence
*/
private function methodWithDocblock($foo, $bar = 1)
{
if (empty($foo)) {
throw new \InvalidArgumentException();
}
return 'method called';
}
public function testFormat()
{
$expected = <<<EOS
<comment>Description:</comment>
This is a docblock!
<comment>Throws:</comment>
<info>InvalidArgumentException </info> if \$foo is empty
<comment>Param:</comment>
<info>mixed </info> <strong>\$foo </strong> It's a foo thing
<info>int </info> <strong>\$bar </strong> This is definitely bar
<comment>Return:</comment>
<info>string </info> A string of no consequence
<comment>Author:</comment> Justin Hileman \<justin@justinhileman.info>
EOS;
$this->assertEquals(
$expected,
DocblockFormatter::format(new \ReflectionMethod($this, 'methodWithDocblock'))
);
}
}

View file

@ -0,0 +1,70 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\Formatter;
use Psy\Formatter\SignatureFormatter;
use Psy\Reflection\ReflectionConstant;
class SignatureFormatterTest extends \PHPUnit\Framework\TestCase
{
const FOO = 'foo value';
private static $bar = 'bar value';
private function someFakeMethod(array $one, $two = 'TWO', \Reflector $three = null)
{
}
/**
* @dataProvider signatureReflectors
*/
public function testFormat($reflector, $expected)
{
$this->assertEquals($expected, strip_tags(SignatureFormatter::format($reflector)));
}
public function signatureReflectors()
{
return array(
array(
new \ReflectionFunction('implode'),
defined('HHVM_VERSION') ? 'function implode($arg1, $arg2 = null)' : 'function implode($glue, $pieces)',
),
array(
new ReflectionConstant($this, 'FOO'),
'const FOO = "foo value"',
),
array(
new \ReflectionMethod($this, 'someFakeMethod'),
'private function someFakeMethod(array $one, $two = \'TWO\', Reflector $three = null)',
),
array(
new \ReflectionProperty($this, 'bar'),
'private static $bar',
),
array(
new \ReflectionClass('Psy\CodeCleaner\CodeCleanerPass'),
'abstract class Psy\CodeCleaner\CodeCleanerPass '
. 'extends PhpParser\NodeVisitorAbstract '
. 'implements PhpParser\NodeVisitor',
),
);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testSignatureFormatterThrowsUnknownReflectorExpeption()
{
$refl = $this->getMockBuilder('Reflector')->getMock();
SignatureFormatter::format($refl);
}
}

View file

@ -0,0 +1,52 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Tests\Input;
use Psy\Input\CodeArgument;
use Symfony\Component\Console\Input\InputArgument;
class CodeArgumentTest extends \PHPUnit\Framework\TestCase
{
/**
* @dataProvider getInvalidModes
* @expectedException \InvalidArgumentException
*/
public function testInvalidModes($mode)
{
new CodeArgument('wat', $mode);
}
public function getInvalidModes()
{
return array(
array(InputArgument::IS_ARRAY),
array(InputArgument::IS_ARRAY | InputArgument::REQUIRED),
array(InputArgument::IS_ARRAY | InputArgument::OPTIONAL),
);
}
/**
* @dataProvider getValidModes
*/
public function testValidModes($mode)
{
$this->assertInstanceOf('Psy\Input\CodeArgument', new CodeArgument('yeah', $mode));
}
public function getValidModes()
{
return array(
array(InputArgument::REQUIRED),
array(InputArgument::OPTIONAL),
);
}
}

View file

@ -0,0 +1,204 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Tests\Input;
use Psy\Input\CodeArgument;
use Psy\Input\ShellInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
class ShellInputTest extends \PHPUnit\Framework\TestCase
{
/**
* @dataProvider getTokenizeData
*/
public function testTokenize($input, $tokens, $message)
{
$input = new ShellInput($input);
$r = new \ReflectionClass('Psy\Input\ShellInput');
$p = $r->getProperty('tokenPairs');
$p->setAccessible(true);
$this->assertEquals($tokens, $p->getValue($input), $message);
}
public function testInputOptionWithGivenString()
{
$definition = new InputDefinition(array(
new InputOption('foo', null, InputOption::VALUE_REQUIRED),
new CodeArgument('code', null, InputOption::VALUE_REQUIRED),
));
$input = new ShellInput('--foo=bar echo "baz\n";');
$input->bind($definition);
$this->assertEquals('bar', $input->getOption('foo'));
$this->assertEquals('echo "baz\n";', $input->getArgument('code'));
}
public function testInputOptionWithoutCodeArguments()
{
$definition = new InputDefinition(array(
new InputOption('foo', null, InputOption::VALUE_REQUIRED),
new InputArgument('bar', null, InputOption::VALUE_REQUIRED),
new InputArgument('baz', null, InputOption::VALUE_REQUIRED),
));
$input = new ShellInput('--foo=foo bar "baz\n"');
$input->bind($definition);
$this->assertEquals('foo', $input->getOption('foo'));
$this->assertEquals('bar', $input->getArgument('bar'));
$this->assertEquals("baz\n", $input->getArgument('baz'));
}
public function getTokenizeData()
{
// Test all the cases from StringInput test, ensuring they have an appropriate $rest token.
return array(
array(
'',
array(),
'->tokenize() parses an empty string',
),
array(
'foo',
array(array('foo', 'foo')),
'->tokenize() parses arguments',
),
array(
' foo bar ',
array(array('foo', 'foo bar '), array('bar', 'bar ')),
'->tokenize() ignores whitespaces between arguments',
),
array(
'"quoted"',
array(array('quoted', '"quoted"')),
'->tokenize() parses quoted arguments',
),
array(
"'quoted'",
array(array('quoted', "'quoted'")),
'->tokenize() parses quoted arguments',
),
array(
"'a\rb\nc\td'",
array(array("a\rb\nc\td", "'a\rb\nc\td'")),
'->tokenize() parses whitespace chars in strings',
),
array(
"'a'\r'b'\n'c'\t'd'",
array(
array('a', "'a'\r'b'\n'c'\t'd'"),
array('b', "'b'\n'c'\t'd'"),
array('c', "'c'\t'd'"),
array('d', "'d'"),
),
'->tokenize() parses whitespace chars between args as spaces',
),
array(
'\"quoted\"',
array(array('"quoted"', '\"quoted\"')),
'->tokenize() parses escaped-quoted arguments',
),
array(
"\'quoted\'",
array(array('\'quoted\'', "\'quoted\'")),
'->tokenize() parses escaped-quoted arguments',
),
array(
'-a',
array(array('-a', '-a')),
'->tokenize() parses short options',
),
array(
'-azc',
array(array('-azc', '-azc')),
'->tokenize() parses aggregated short options',
),
array(
'-awithavalue',
array(array('-awithavalue', '-awithavalue')),
'->tokenize() parses short options with a value',
),
array(
'-a"foo bar"',
array(array('-afoo bar', '-a"foo bar"')),
'->tokenize() parses short options with a value',
),
array(
'-a"foo bar""foo bar"',
array(array('-afoo barfoo bar', '-a"foo bar""foo bar"')),
'->tokenize() parses short options with a value',
),
array(
'-a\'foo bar\'',
array(array('-afoo bar', '-a\'foo bar\'')),
'->tokenize() parses short options with a value',
),
array(
'-a\'foo bar\'\'foo bar\'',
array(array('-afoo barfoo bar', '-a\'foo bar\'\'foo bar\'')),
'->tokenize() parses short options with a value',
),
array(
'-a\'foo bar\'"foo bar"',
array(array('-afoo barfoo bar', '-a\'foo bar\'"foo bar"')),
'->tokenize() parses short options with a value',
),
array(
'--long-option',
array(array('--long-option', '--long-option')),
'->tokenize() parses long options',
),
array(
'--long-option=foo',
array(array('--long-option=foo', '--long-option=foo')),
'->tokenize() parses long options with a value',
),
array(
'--long-option="foo bar"',
array(array('--long-option=foo bar', '--long-option="foo bar"')),
'->tokenize() parses long options with a value',
),
array(
'--long-option="foo bar""another"',
array(array('--long-option=foo baranother', '--long-option="foo bar""another"')),
'->tokenize() parses long options with a value',
),
array(
'--long-option=\'foo bar\'',
array(array('--long-option=foo bar', '--long-option=\'foo bar\'')),
'->tokenize() parses long options with a value',
),
array(
"--long-option='foo bar''another'",
array(array('--long-option=foo baranother', "--long-option='foo bar''another'")),
'->tokenize() parses long options with a value',
),
array(
"--long-option='foo bar'\"another\"",
array(array('--long-option=foo baranother', "--long-option='foo bar'\"another\"")),
'->tokenize() parses long options with a value',
),
array(
'foo -a -ffoo --long bar',
array(
array('foo', 'foo -a -ffoo --long bar'),
array('-a', '-a -ffoo --long bar'),
array('-ffoo', '-ffoo --long bar'),
array('--long', '--long bar'),
array('bar', 'bar'),
),
'->tokenize() parses when several arguments and options',
),
);
}
}

View file

@ -0,0 +1,80 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\Readline;
use Psy\Readline\GNUReadline;
class GNUReadlineTest extends \PHPUnit\Framework\TestCase
{
private $historyFile;
public function setUp()
{
if (!GNUReadline::isSupported()) {
$this->markTestSkipped('GNUReadline not enabled');
}
$this->historyFile = tempnam(sys_get_temp_dir(), 'psysh_test_history');
file_put_contents($this->historyFile, "_HiStOrY_V2_\n");
}
public function testHistory()
{
$readline = new GNUReadline($this->historyFile);
$this->assertEmpty($readline->listHistory());
$readline->addHistory('foo');
$this->assertEquals(array('foo'), $readline->listHistory());
$readline->addHistory('bar');
$this->assertEquals(array('foo', 'bar'), $readline->listHistory());
$readline->addHistory('baz');
$this->assertEquals(array('foo', 'bar', 'baz'), $readline->listHistory());
$readline->clearHistory();
$this->assertEmpty($readline->listHistory());
}
/**
* @depends testHistory
*/
public function testHistorySize()
{
$readline = new GNUReadline($this->historyFile, 2);
$this->assertEmpty($readline->listHistory());
$readline->addHistory('foo');
$readline->addHistory('bar');
$this->assertEquals(array('foo', 'bar'), $readline->listHistory());
$readline->addHistory('baz');
$this->assertEquals(array('bar', 'baz'), $readline->listHistory());
$readline->addHistory('w00t');
$this->assertEquals(array('baz', 'w00t'), $readline->listHistory());
$readline->clearHistory();
$this->assertEmpty($readline->listHistory());
}
/**
* @depends testHistory
*/
public function testHistoryEraseDups()
{
$readline = new GNUReadline($this->historyFile, 0, true);
$this->assertEmpty($readline->listHistory());
$readline->addHistory('foo');
$readline->addHistory('bar');
$readline->addHistory('foo');
$this->assertEquals(array('bar', 'foo'), $readline->listHistory());
$readline->addHistory('baz');
$readline->addHistory('w00t');
$readline->addHistory('baz');
$this->assertEquals(array('bar', 'foo', 'w00t', 'baz'), $readline->listHistory());
$readline->clearHistory();
$this->assertEmpty($readline->listHistory());
}
}

View file

@ -0,0 +1,31 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\Readline;
use Psy\Readline\HoaConsole;
class HoaConsoleTest extends \PHPUnit\Framework\TestCase
{
public function testHistory()
{
$readline = new HoaConsole();
$this->assertEmpty($readline->listHistory());
$readline->addHistory('foo');
$this->assertEquals(array('foo'), $readline->listHistory());
$readline->addHistory('bar');
$this->assertEquals(array('foo', 'bar'), $readline->listHistory());
$readline->addHistory('baz');
$this->assertEquals(array('foo', 'bar', 'baz'), $readline->listHistory());
$readline->clearHistory();
$this->assertEmpty($readline->listHistory());
}
}

View file

@ -0,0 +1,128 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\Readline;
use Psy\Readline\Libedit;
class LibeditTest extends \PHPUnit\Framework\TestCase
{
private $historyFile;
public function setUp()
{
if (!Libedit::isSupported()) {
$this->markTestSkipped('Libedit not enabled');
}
$this->historyFile = tempnam(sys_get_temp_dir(), 'psysh_test_history');
if (false === file_put_contents($this->historyFile, "_HiStOrY_V2_\n")) {
$this->fail('Unable to write history file: ' . $this->historyFile);
}
// Calling readline_read_history before readline_clear_history
// avoids segfault with PHP 5.5.7 & libedit v3.1
readline_read_history($this->historyFile);
readline_clear_history();
}
public function tearDown()
{
if (is_file($this->historyFile)) {
unlink($this->historyFile);
}
}
public function testHistory()
{
$readline = new Libedit($this->historyFile);
$this->assertEmpty($readline->listHistory());
$readline->addHistory('foo');
$this->assertEquals(array('foo'), $readline->listHistory());
$readline->addHistory('bar');
$this->assertEquals(array('foo', 'bar'), $readline->listHistory());
$readline->addHistory('baz');
$this->assertEquals(array('foo', 'bar', 'baz'), $readline->listHistory());
$readline->clearHistory();
$this->assertEmpty($readline->listHistory());
}
/**
* @depends testHistory
*/
public function testHistorySize()
{
$readline = new Libedit($this->historyFile, 2);
$this->assertEmpty($readline->listHistory());
$readline->addHistory('foo');
$readline->addHistory('bar');
$this->assertEquals(array('foo', 'bar'), $readline->listHistory());
$readline->addHistory('baz');
$this->assertEquals(array('bar', 'baz'), $readline->listHistory());
$readline->addHistory('w00t');
$this->assertEquals(array('baz', 'w00t'), $readline->listHistory());
$readline->clearHistory();
$this->assertEmpty($readline->listHistory());
}
/**
* @depends testHistory
*/
public function testHistoryEraseDups()
{
$readline = new Libedit($this->historyFile, 0, true);
$this->assertEmpty($readline->listHistory());
$readline->addHistory('foo');
$readline->addHistory('bar');
$readline->addHistory('foo');
$this->assertEquals(array('bar', 'foo'), $readline->listHistory());
$readline->addHistory('baz');
$readline->addHistory('w00t');
$readline->addHistory('baz');
$this->assertEquals(array('bar', 'foo', 'w00t', 'baz'), $readline->listHistory());
$readline->clearHistory();
$this->assertEmpty($readline->listHistory());
}
public function testListHistory()
{
$readline = new Libedit($this->historyFile);
file_put_contents(
$this->historyFile,
"This is an entry\n\0This is a comment\nThis is an entry\0With a comment\n",
FILE_APPEND
);
$this->assertEquals(array(
'This is an entry',
'This is an entry',
), $readline->listHistory());
$readline->clearHistory();
}
/**
* Libedit being a BSD library,
* it doesn't support non-unix line separators.
*/
public function testLinebreaksSupport()
{
$readline = new Libedit($this->historyFile);
file_put_contents(
$this->historyFile,
"foo\rbar\nbaz\r\nw00t",
FILE_APPEND
);
$this->assertEquals(array(
"foo\rbar",
"baz\r",
'w00t',
), $readline->listHistory());
$readline->clearHistory();
}
}

View file

@ -0,0 +1,76 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\Readline;
use Psy\Readline\Transient;
class TransientTest extends \PHPUnit\Framework\TestCase
{
public function testHistory()
{
$readline = new Transient();
$this->assertEmpty($readline->listHistory());
$readline->addHistory('foo');
$this->assertEquals(array('foo'), $readline->listHistory());
$readline->addHistory('bar');
$this->assertEquals(array('foo', 'bar'), $readline->listHistory());
$readline->addHistory('baz');
$this->assertEquals(array('foo', 'bar', 'baz'), $readline->listHistory());
$readline->clearHistory();
$this->assertEmpty($readline->listHistory());
}
/**
* @depends testHistory
*/
public function testHistorySize()
{
$readline = new Transient(null, 2);
$this->assertEmpty($readline->listHistory());
$readline->addHistory('foo');
$readline->addHistory('bar');
$this->assertEquals(array('foo', 'bar'), $readline->listHistory());
$readline->addHistory('baz');
$this->assertEquals(array('bar', 'baz'), $readline->listHistory());
$readline->addHistory('w00t');
$this->assertEquals(array('baz', 'w00t'), $readline->listHistory());
$readline->clearHistory();
$this->assertEmpty($readline->listHistory());
}
/**
* @depends testHistory
*/
public function testHistoryEraseDups()
{
$readline = new Transient(null, 0, true);
$this->assertEmpty($readline->listHistory());
$readline->addHistory('foo');
$readline->addHistory('bar');
$readline->addHistory('foo');
$this->assertEquals(array('bar', 'foo'), $readline->listHistory());
$readline->addHistory('baz');
$readline->addHistory('w00t');
$readline->addHistory('baz');
$this->assertEquals(array('bar', 'foo', 'w00t', 'baz'), $readline->listHistory());
$readline->clearHistory();
$this->assertEmpty($readline->listHistory());
}
public function testSomeThingsAreAlwaysTrue()
{
$readline = new Transient();
$this->assertTrue(Transient::isSupported());
$this->assertTrue($readline->readHistory());
$this->assertTrue($readline->writeHistory());
}
}

View file

@ -0,0 +1,60 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\Reflection;
use Psy\Reflection\ReflectionConstant;
class ReflectionConstantTest extends \PHPUnit\Framework\TestCase
{
const CONSTANT_ONE = 'one';
public function testConstruction()
{
$refl = new ReflectionConstant($this, 'CONSTANT_ONE');
$class = $refl->getDeclaringClass();
$this->assertTrue($class instanceof \ReflectionClass);
$this->assertEquals('Psy\Test\Reflection\ReflectionConstantTest', $class->getName());
$this->assertEquals('CONSTANT_ONE', $refl->getName());
$this->assertEquals('CONSTANT_ONE', (string) $refl);
$this->assertEquals('one', $refl->getValue());
$this->assertEquals(null, $refl->getFileName());
$this->assertFalse($refl->getDocComment());
}
/**
* @expectedException \InvalidArgumentException
*/
public function testUnknownConstantThrowsException()
{
new ReflectionConstant($this, 'UNKNOWN_CONSTANT');
}
/**
* @expectedException \RuntimeException
* @dataProvider notYetImplemented
*/
public function testNotYetImplemented($method)
{
$refl = new ReflectionConstant($this, 'CONSTANT_ONE');
$refl->$method();
}
public function notYetImplemented()
{
return array(
array('getStartLine'),
array('getEndLine'),
array('export'),
);
}
}

View file

@ -0,0 +1,346 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test;
use Psy\Configuration;
use Psy\Exception\ErrorException;
use Psy\Exception\ParseErrorException;
use Psy\Shell;
use Psy\TabCompletion\Matcher\ClassMethodsMatcher;
use Symfony\Component\Console\Output\StreamOutput;
class ShellTest extends \PHPUnit\Framework\TestCase
{
private $streams = array();
public function tearDown()
{
foreach ($this->streams as $stream) {
fclose($stream);
}
}
public function testScopeVariables()
{
$one = 'banana';
$two = 123;
$three = new \StdClass();
$__psysh__ = 'ignore this';
$_ = 'ignore this';
$_e = 'ignore this';
$shell = new Shell($this->getConfig());
$shell->setScopeVariables(compact('one', 'two', 'three', '__psysh__', '_', '_e', 'this'));
$this->assertNotContains('__psysh__', $shell->getScopeVariableNames());
$this->assertEquals(array('one', 'two', 'three', '_'), $shell->getScopeVariableNames());
$this->assertEquals('banana', $shell->getScopeVariable('one'));
$this->assertEquals(123, $shell->getScopeVariable('two'));
$this->assertSame($three, $shell->getScopeVariable('three'));
$this->assertNull($shell->getScopeVariable('_'));
$shell->setScopeVariables(array());
$this->assertEquals(array('_'), $shell->getScopeVariableNames());
$shell->setBoundObject($this);
$this->assertEquals(array('_', 'this'), $shell->getScopeVariableNames());
$this->assertSame($this, $shell->getScopeVariable('this'));
$this->assertEquals(array('_' => null), $shell->getScopeVariables(false));
$this->assertEquals(array('_' => null, 'this' => $this), $shell->getScopeVariables());
}
/**
* @expectedException \InvalidArgumentException
*/
public function testUnknownScopeVariablesThrowExceptions()
{
$shell = new Shell($this->getConfig());
$shell->setScopeVariables(array('foo' => 'FOO', 'bar' => 1));
$shell->getScopeVariable('baz');
}
public function testIncludes()
{
$config = $this->getConfig(array('configFile' => __DIR__ . '/../../fixtures/empty.php'));
$shell = new Shell($config);
$this->assertEmpty($shell->getIncludes());
$shell->setIncludes(array('foo', 'bar', 'baz'));
$this->assertEquals(array('foo', 'bar', 'baz'), $shell->getIncludes());
}
public function testIncludesConfig()
{
$config = $this->getConfig(array(
'defaultIncludes' => array('/file.php'),
'configFile' => __DIR__ . '/../../fixtures/empty.php',
));
$shell = new Shell($config);
$includes = $shell->getIncludes();
$this->assertEquals('/file.php', $includes[0]);
}
public function testAddMatchersViaConfig()
{
$config = $this->getConfig(array(
'tabCompletionMatchers' => array(
new ClassMethodsMatcher(),
),
));
$matchers = $config->getTabCompletionMatchers();
$this->assertTrue(array_pop($matchers) instanceof ClassMethodsMatcher);
}
public function testRenderingExceptions()
{
$shell = new Shell($this->getConfig());
$output = $this->getOutput();
$stream = $output->getStream();
$e = new ParseErrorException('message', 13);
$shell->setOutput($output);
$shell->addCode('code');
$this->assertTrue($shell->hasCode());
$this->assertNotEmpty($shell->getCodeBuffer());
$shell->writeException($e);
$this->assertSame($e, $shell->getScopeVariable('_e'));
$this->assertFalse($shell->hasCode());
$this->assertEmpty($shell->getCodeBuffer());
rewind($stream);
$streamContents = stream_get_contents($stream);
$this->assertContains('PHP Parse error', $streamContents);
$this->assertContains('message', $streamContents);
$this->assertContains('line 13', $streamContents);
}
public function testHandlingErrors()
{
$shell = new Shell($this->getConfig());
$output = $this->getOutput();
$stream = $output->getStream();
$shell->setOutput($output);
$oldLevel = error_reporting();
error_reporting($oldLevel & ~E_USER_NOTICE);
try {
$shell->handleError(E_USER_NOTICE, 'wheee', null, 13);
} catch (ErrorException $e) {
error_reporting($oldLevel);
$this->fail('Unexpected error exception');
}
error_reporting($oldLevel);
rewind($stream);
$streamContents = stream_get_contents($stream);
$this->assertContains('PHP Notice:', $streamContents);
$this->assertContains('wheee', $streamContents);
$this->assertContains('line 13', $streamContents);
}
/**
* @expectedException \Psy\Exception\ErrorException
*/
public function testNotHandlingErrors()
{
$shell = new Shell($this->getConfig());
$oldLevel = error_reporting();
error_reporting($oldLevel | E_USER_NOTICE);
try {
$shell->handleError(E_USER_NOTICE, 'wheee', null, 13);
} catch (ErrorException $e) {
error_reporting($oldLevel);
throw $e;
}
}
public function testVersion()
{
$shell = new Shell($this->getConfig());
$this->assertInstanceOf('Symfony\Component\Console\Application', $shell);
$this->assertContains(Shell::VERSION, $shell->getVersion());
$this->assertContains(phpversion(), $shell->getVersion());
$this->assertContains(php_sapi_name(), $shell->getVersion());
}
public function testCodeBuffer()
{
$shell = new Shell($this->getConfig());
$shell->addCode('class');
$this->assertNull($shell->flushCode());
$this->assertTrue($shell->hasCode());
$shell->addCode('a');
$this->assertNull($shell->flushCode());
$this->assertTrue($shell->hasCode());
$shell->addCode('{}');
$code = $shell->flushCode();
$this->assertFalse($shell->hasCode());
$code = preg_replace('/\s+/', ' ', $code);
$this->assertNotNull($code);
$this->assertEquals('class a { } return new \\Psy\\CodeCleaner\\NoReturnValue();', $code);
}
public function testKeepCodeBufferOpen()
{
$shell = new Shell($this->getConfig());
$shell->addCode('1 \\');
$this->assertNull($shell->flushCode());
$this->assertTrue($shell->hasCode());
$shell->addCode('+ 1 \\');
$this->assertNull($shell->flushCode());
$this->assertTrue($shell->hasCode());
$shell->addCode('+ 1');
$code = $shell->flushCode();
$this->assertFalse($shell->hasCode());
$code = preg_replace('/\s+/', ' ', $code);
$this->assertNotNull($code);
$this->assertEquals('return 1 + 1 + 1;', $code);
}
/**
* @expectedException \Psy\Exception\ParseErrorException
*/
public function testCodeBufferThrowsParseExceptions()
{
$shell = new Shell($this->getConfig());
$shell->addCode('this is not valid');
$shell->flushCode();
}
public function testClosuresSupport()
{
$shell = new Shell($this->getConfig());
$code = '$test = function () {}';
$shell->addCode($code);
$shell->flushCode();
$code = '$test()';
$shell->addCode($code);
$this->assertEquals($shell->flushCode(), 'return $test();');
}
public function testWriteStdout()
{
$output = $this->getOutput();
$stream = $output->getStream();
$shell = new Shell($this->getConfig());
$shell->setOutput($output);
$shell->writeStdout("{{stdout}}\n");
rewind($stream);
$streamContents = stream_get_contents($stream);
$this->assertEquals('{{stdout}}' . PHP_EOL, $streamContents);
}
public function testWriteStdoutWithoutNewline()
{
$output = $this->getOutput();
$stream = $output->getStream();
$shell = new Shell($this->getConfig());
$shell->setOutput($output);
$shell->writeStdout('{{stdout}}');
rewind($stream);
$streamContents = stream_get_contents($stream);
$this->assertEquals('{{stdout}}<aside>⏎</aside>' . PHP_EOL, $streamContents);
}
/**
* @dataProvider getReturnValues
*/
public function testWriteReturnValue($input, $expected)
{
$output = $this->getOutput();
$stream = $output->getStream();
$shell = new Shell($this->getConfig());
$shell->setOutput($output);
$shell->writeReturnValue($input);
rewind($stream);
$this->assertEquals($expected, stream_get_contents($stream));
}
public function getReturnValues()
{
return array(
array('{{return value}}', "=> \"\033[32m{{return value}}\033[39m\"" . PHP_EOL),
array(1, "=> \033[35m1\033[39m" . PHP_EOL),
);
}
/**
* @dataProvider getRenderedExceptions
*/
public function testWriteException($exception, $expected)
{
$output = $this->getOutput();
$stream = $output->getStream();
$shell = new Shell($this->getConfig());
$shell->setOutput($output);
$shell->writeException($exception);
rewind($stream);
$this->assertEquals($expected, stream_get_contents($stream));
}
public function getRenderedExceptions()
{
return array(
array(new \Exception('{{message}}'), "Exception with message '{{message}}'" . PHP_EOL),
);
}
private function getOutput()
{
$stream = fopen('php://memory', 'w+');
$this->streams[] = $stream;
$output = new StreamOutput($stream, StreamOutput::VERBOSITY_NORMAL, false);
return $output;
}
private function getConfig(array $config = array())
{
// Mebbe there's a better way than this?
$dir = tempnam(sys_get_temp_dir(), 'psysh_shell_test_');
unlink($dir);
$defaults = array(
'configDir' => $dir,
'dataDir' => $dir,
'runtimeDir' => $dir,
);
return new Configuration(array_merge($defaults, $config));
}
}

View file

@ -0,0 +1,145 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\TabCompletion;
use Psy\Command\ListCommand;
use Psy\Command\ShowCommand;
use Psy\Configuration;
use Psy\Context;
use Psy\ContextAware;
use Psy\TabCompletion\Matcher;
class AutoCompleterTest extends \PHPUnit\Framework\TestCase
{
/**
* @param $line
* @param $mustContain
* @param $mustNotContain
* @dataProvider classesInput
*/
public function testClassesCompletion($line, $mustContain, $mustNotContain)
{
$context = new Context();
$commands = array(
new ShowCommand(),
new ListCommand(),
);
$matchers = array(
new Matcher\VariablesMatcher(),
new Matcher\ClassNamesMatcher(),
new Matcher\ConstantsMatcher(),
new Matcher\FunctionsMatcher(),
new Matcher\ObjectMethodsMatcher(),
new Matcher\ObjectAttributesMatcher(),
new Matcher\KeywordsMatcher(),
new Matcher\ClassAttributesMatcher(),
new Matcher\ClassMethodsMatcher(),
new Matcher\CommandsMatcher($commands),
);
$config = new Configuration();
$tabCompletion = $config->getAutoCompleter();
foreach ($matchers as $matcher) {
if ($matcher instanceof ContextAware) {
$matcher->setContext($context);
}
$tabCompletion->addMatcher($matcher);
}
$context->setAll(array('foo' => 12, 'bar' => new \DOMDocument()));
$code = $tabCompletion->processCallback('', 0, array(
'line_buffer' => $line,
'point' => 0,
'end' => strlen($line),
));
foreach ($mustContain as $mc) {
$this->assertContains($mc, $code);
}
foreach ($mustNotContain as $mnc) {
$this->assertNotContains($mnc, $code);
}
}
/**
* TODO
* ====
* draft, open to modifications
* - [ ] if the variable is an array, return the square bracket for completion
* - [ ] if the variable is a constructor or method, reflect to complete as a function call
* - [ ] if the preceding token is a variable, call operators or keywords compatible for completion
* - [X] a command always should be the second token after php_open_tag
* - [X] keywords are never consecutive
* - [X] namespacing completion should work just fine
* - [X] after a new keyword, should always be a class constructor, never a function call or keyword, constant,
* or variable that does not contain a existing class name.
* - [X] on a namespaced constructor the completion must show the classes related, not constants.
*
* @return array
*/
public function classesInput()
{
return array(
// input, must had, must not had
array('T_OPE', array('T_OPEN_TAG'), array()),
array('st', array('stdClass'), array()),
array('stdCla', array('stdClass'), array()),
array('new s', array('stdClass'), array()),
array(
'new ',
array('stdClass', 'Psy\\Context', 'Psy\\Configuration'),
array('require', 'array_search', 'T_OPEN_TAG', '$foo'),
),
array('new Psy\\C', array('Context'), array('CASE_LOWER')),
array('\s', array('stdClass'), array()),
array('array_', array('array_search', 'array_map', 'array_merge'), array()),
array('$bar->', array('load'), array()),
array('$b', array('bar'), array()),
array('6 + $b', array('bar'), array()),
array('$f', array('foo'), array()),
array('l', array('ls'), array()),
array('ls ', array(), array('ls')),
array('sho', array('show'), array()),
array('12 + clone $', array('foo'), array()),
// array(
// '$foo ',
// array('+', 'clone'),
// array('$foo', 'DOMDocument', 'array_map')
// ), requires a operator matcher?
array('$', array('foo', 'bar'), array('require', 'array_search', 'T_OPEN_TAG', 'Psy')),
array(
'Psy\\',
array('Context', 'TabCompletion\\Matcher\\AbstractMatcher'),
array('require', 'array_search'),
),
array(
'Psy\Test\TabCompletion\StaticSample::CO',
array('Psy\Test\TabCompletion\StaticSample::CONSTANT_VALUE'),
array(),
),
array(
'Psy\Test\TabCompletion\StaticSample::',
array('Psy\Test\TabCompletion\StaticSample::$staticVariable'),
array(),
),
array(
'Psy\Test\TabCompletion\StaticSample::',
array('Psy\Test\TabCompletion\StaticSample::staticFunction'),
array(),
),
);
}
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\TabCompletion;
/**
* Class StaticSample.
*/
class StaticSample
{
const CONSTANT_VALUE = 12;
public static $staticVariable;
public static function staticFunction()
{
return self::CONSTANT_VALUE;
}
}

View file

@ -0,0 +1,100 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\Util;
use Psy\Util\Docblock;
class DocblockTest extends \PHPUnit\Framework\TestCase
{
/**
* @dataProvider comments
*/
public function testDocblockParsing($comment, $body, $tags)
{
$reflector = $this
->getMockBuilder('ReflectionClass')
->disableOriginalConstructor()
->getMock();
$reflector->expects($this->once())
->method('getDocComment')
->will($this->returnValue($comment));
$docblock = new Docblock($reflector);
$this->assertEquals($body, $docblock->desc);
foreach ($tags as $tag => $value) {
$this->assertTrue($docblock->hasTag($tag));
$this->assertEquals($value, $docblock->tag($tag));
}
}
public function comments()
{
if (defined('HHVM_VERSION')) {
$this->markTestSkipped('We have issues with PHPUnit mocks on HHVM.');
}
return array(
array('', '', array()),
array(
'/**
* This is a docblock
*
* @throws \Exception with a description
*/',
'This is a docblock',
array(
'throws' => array(array('type' => '\Exception', 'desc' => 'with a description')),
),
),
array(
'/**
* This is a slightly longer docblock
*
* @param int $foo Is a Foo
* @param string $bar With some sort of description
* @param \ClassName $baz is cool too
*
* @return int At least it isn\'t a string
*/',
'This is a slightly longer docblock',
array(
'param' => array(
array('type' => 'int', 'desc' => 'Is a Foo', 'var' => '$foo'),
array('type' => 'string', 'desc' => 'With some sort of description', 'var' => '$bar'),
array('type' => '\ClassName', 'desc' => 'is cool too', 'var' => '$baz'),
),
'return' => array(
array('type' => 'int', 'desc' => 'At least it isn\'t a string'),
),
),
),
array(
'/**
* This is a docblock!
*
* It spans lines, too!
*
* @tagname plus a description
*
* @return
*/',
"This is a docblock!\n\nIt spans lines, too!",
array(
'tagname' => array('plus a description'),
),
),
);
}
}

View file

@ -0,0 +1,80 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\Util;
use Psy\Reflection\ReflectionConstant;
use Psy\Util\Mirror;
class MirrorTest extends \PHPUnit\Framework\TestCase
{
const FOO = 1;
private $bar = 2;
private static $baz = 3;
public function aPublicMethod()
{
// nada
}
public function testMirror()
{
$refl = Mirror::get('sort');
$this->assertTrue($refl instanceof \ReflectionFunction);
$refl = Mirror::get('Psy\Test\Util\MirrorTest');
$this->assertTrue($refl instanceof \ReflectionClass);
$refl = Mirror::get($this);
$this->assertTrue($refl instanceof \ReflectionObject);
$refl = Mirror::get($this, 'FOO');
$this->assertTrue($refl instanceof ReflectionConstant);
$refl = Mirror::get($this, 'bar');
$this->assertTrue($refl instanceof \ReflectionProperty);
$refl = Mirror::get($this, 'baz');
$this->assertTrue($refl instanceof \ReflectionProperty);
$refl = Mirror::get($this, 'aPublicMethod');
$this->assertTrue($refl instanceof \ReflectionMethod);
$refl = Mirror::get($this, 'baz', Mirror::STATIC_PROPERTY);
$this->assertTrue($refl instanceof \ReflectionProperty);
}
/**
* @expectedException \RuntimeException
*/
public function testMirrorThrowsExceptions()
{
Mirror::get($this, 'notAMethod');
}
/**
* @expectedException \InvalidArgumentException
* @dataProvider invalidArguments
*/
public function testMirrorThrowsInvalidArgumentExceptions($value)
{
Mirror::get($value);
}
public function invalidArguments()
{
return array(
array('not_a_function_or_class'),
array(array()),
array(1),
);
}
}

View file

@ -0,0 +1,31 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\Util;
use Psy\Util\Str;
class StrTest extends \PHPUnit\Framework\TestCase
{
/**
* @dataProvider unvisProvider
*/
public function testUnvis($input, $expected)
{
$this->assertEquals($expected, Str::unvis($input));
}
public function unvisProvider()
{
//return require_once(__DIR__.'/../../../fixtures/unvis_fixtures.php');
return json_decode(file_get_contents(__DIR__ . '/../../../fixtures/unvis_fixtures.json'));
}
}

View file

@ -0,0 +1,82 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Test\VersionUpdater;
use Psy\Shell;
class GitHubCheckerTest extends \PHPUnit\Framework\TestCase
{
/**
* @dataProvider malformedResults
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Unable to check for updates
*
* @param $input
*/
public function testExceptionInvocation($input)
{
$checker = $this->getMockBuilder('Psy\\VersionUpdater\\GitHubChecker')
->setMethods(array('fetchLatestRelease'))
->getMock();
$checker->expects($this->once())->method('fetchLatestRelease')->willReturn($input);
$checker->isLatest();
}
/**
* @dataProvider jsonResults
*
* @param $assertion
* @param $input
*/
public function testDataSetResults($assertion, $input)
{
$checker = $this->getMockBuilder('Psy\\VersionUpdater\\GitHubChecker')
->setMethods(array('fetchLatestRelease'))
->getMock();
$checker->expects($this->once())->method('fetchLatestRelease')->willReturn($input);
$this->assertSame($assertion, $checker->isLatest());
}
/**
* @return array
*/
public function jsonResults()
{
return array(
array(false, json_decode('{"tag_name":"v9.0.0"}')),
array(true, json_decode('{"tag_name":"v' . Shell::VERSION . '"}')),
array(true, json_decode('{"tag_name":"v0.0.1"}')),
array(true, json_decode('{"tag_name":"v0.4.1-alpha"}')),
array(true, json_decode('{"tag_name":"v0.4.2-beta3"}')),
array(true, json_decode('{"tag_name":"v0.0.1"}')),
array(true, json_decode('{"tag_name":""}')),
);
}
/**
* @return array
*/
public function malformedResults()
{
return array(
array(null),
array(false),
array(true),
array(json_decode('{"foo":"bar"}')),
array(json_decode('{}')),
array(json_decode('[]')),
array(array()),
array(json_decode('{"tag_name":false"}')),
array(json_decode('{"tag_name":true"}')),
);
}
}