This commit is contained in:
chrosey
2017-09-13 07:52:34 +02:00
parent a1f16c37f4
commit 2340b0226b
24621 changed files with 2912161 additions and 149 deletions
@@ -0,0 +1,362 @@
<?php
namespace spec\Prophecy\Doubler\Generator;
use phpDocumentor\Reflection\DocBlock\Tags\Method;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Prophecy\Doubler\Generator\Node\ArgumentNode;
use Prophecy\Doubler\Generator\Node\ClassNode;
use Prophecy\Doubler\Generator\Node\MethodNode;
class ClassCodeGeneratorSpec extends ObjectBehavior
{
function it_generates_proper_php_code_for_specific_ClassNode(
ClassNode $class,
MethodNode $method1,
MethodNode $method2,
MethodNode $method3,
MethodNode $method4,
ArgumentNode $argument11,
ArgumentNode $argument12,
ArgumentNode $argument21,
ArgumentNode $argument31
) {
$class->getParentClass()->willReturn('RuntimeException');
$class->getInterfaces()->willReturn(array(
'Prophecy\Doubler\Generator\MirroredInterface', 'ArrayAccess', 'ArrayIterator'
));
$class->getProperties()->willReturn(array('name' => 'public', 'email' => 'private'));
$class->getMethods()->willReturn(array($method1, $method2, $method3, $method4));
$method1->getName()->willReturn('getName');
$method1->getVisibility()->willReturn('public');
$method1->returnsReference()->willReturn(false);
$method1->isStatic()->willReturn(true);
$method1->getArguments()->willReturn(array($argument11, $argument12));
$method1->hasReturnType()->willReturn(true);
$method1->getReturnType()->willReturn('string');
$method1->hasNullableReturnType()->willReturn(true);
$method1->getCode()->willReturn('return $this->name;');
$method2->getName()->willReturn('getEmail');
$method2->getVisibility()->willReturn('protected');
$method2->returnsReference()->willReturn(false);
$method2->isStatic()->willReturn(false);
$method2->getArguments()->willReturn(array($argument21));
$method2->hasReturnType()->willReturn(false);
$method2->hasNullableReturnType()->willReturn(true);
$method2->getCode()->willReturn('return $this->email;');
$method3->getName()->willReturn('getRefValue');
$method3->getVisibility()->willReturn('public');
$method3->returnsReference()->willReturn(true);
$method3->isStatic()->willReturn(false);
$method3->getArguments()->willReturn(array($argument31));
$method3->hasReturnType()->willReturn(true);
$method3->getReturnType()->willReturn('string');
$method3->hasNullableReturnType()->willReturn(false);
$method3->getCode()->willReturn('return $this->refValue;');
$method4->getName()->willReturn('doSomething');
$method4->getVisibility()->willReturn('public');
$method4->returnsReference()->willReturn(false);
$method4->isStatic()->willReturn(false);
$method4->getArguments()->willReturn(array());
$method4->hasReturnType()->willReturn(true);
$method4->getReturnType()->willReturn('void');
$method4->hasNullableReturnType()->willReturn(false);
$method4->getCode()->willReturn('return;');
$argument11->getName()->willReturn('fullname');
$argument11->getTypeHint()->willReturn('array');
$argument11->isOptional()->willReturn(true);
$argument11->getDefault()->willReturn(null);
$argument11->isPassedByReference()->willReturn(false);
$argument11->isVariadic()->willReturn(false);
$argument11->isNullable()->willReturn(false);
$argument12->getName()->willReturn('class');
$argument12->getTypeHint()->willReturn('ReflectionClass');
$argument12->isOptional()->willReturn(false);
$argument12->isPassedByReference()->willReturn(false);
$argument12->isVariadic()->willReturn(false);
$argument12->isNullable()->willReturn(false);
$argument21->getName()->willReturn('default');
$argument21->getTypeHint()->willReturn('string');
$argument21->isOptional()->willReturn(true);
$argument21->getDefault()->willReturn('ever.zet@gmail.com');
$argument21->isPassedByReference()->willReturn(false);
$argument21->isVariadic()->willReturn(false);
$argument21->isNullable()->willReturn(true);
$argument31->getName()->willReturn('refValue');
$argument31->getTypeHint()->willReturn(null);
$argument31->isOptional()->willReturn(false);
$argument31->getDefault()->willReturn();
$argument31->isPassedByReference()->willReturn(false);
$argument31->isVariadic()->willReturn(false);
$argument31->isNullable()->willReturn(false);
$code = $this->generate('CustomClass', $class);
if (version_compare(PHP_VERSION, '7.1', '>=')) {
$expected = <<<'PHP'
namespace {
class CustomClass extends \RuntimeException implements \Prophecy\Doubler\Generator\MirroredInterface, \ArrayAccess, \ArrayIterator {
public $name;
private $email;
public static function getName(array $fullname = NULL, \ReflectionClass $class): ?string {
return $this->name;
}
protected function getEmail(?string $default = 'ever.zet@gmail.com') {
return $this->email;
}
public function &getRefValue( $refValue): string {
return $this->refValue;
}
public function doSomething(): void {
return;
}
}
}
PHP;
} elseif (version_compare(PHP_VERSION, '7.0', '>=')) {
$expected = <<<'PHP'
namespace {
class CustomClass extends \RuntimeException implements \Prophecy\Doubler\Generator\MirroredInterface, \ArrayAccess, \ArrayIterator {
public $name;
private $email;
public static function getName(array $fullname = NULL, \ReflectionClass $class): string {
return $this->name;
}
protected function getEmail(string $default = 'ever.zet@gmail.com') {
return $this->email;
}
public function &getRefValue( $refValue): string {
return $this->refValue;
}
public function doSomething() {
return;
}
}
}
PHP;
} else {
$expected = <<<'PHP'
namespace {
class CustomClass extends \RuntimeException implements \Prophecy\Doubler\Generator\MirroredInterface, \ArrayAccess, \ArrayIterator {
public $name;
private $email;
public static function getName(array $fullname = NULL, \ReflectionClass $class) {
return $this->name;
}
protected function getEmail(\string $default = 'ever.zet@gmail.com') {
return $this->email;
}
public function &getRefValue( $refValue) {
return $this->refValue;
}
public function doSomething() {
return;
}
}
}
PHP;
}
$expected = strtr($expected, array("\r\n" => "\n", "\r" => "\n"));
$code->shouldBe($expected);
}
function it_generates_proper_php_code_for_variadics(
ClassNode $class,
MethodNode $method1,
MethodNode $method2,
MethodNode $method3,
MethodNode $method4,
ArgumentNode $argument1,
ArgumentNode $argument2,
ArgumentNode $argument3,
ArgumentNode $argument4
) {
$class->getParentClass()->willReturn('stdClass');
$class->getInterfaces()->willReturn(array('Prophecy\Doubler\Generator\MirroredInterface'));
$class->getProperties()->willReturn(array());
$class->getMethods()->willReturn(array(
$method1, $method2, $method3, $method4
));
$method1->getName()->willReturn('variadic');
$method1->getVisibility()->willReturn('public');
$method1->returnsReference()->willReturn(false);
$method1->isStatic()->willReturn(false);
$method1->getArguments()->willReturn(array($argument1));
$method1->hasReturnType()->willReturn(false);
$method1->getCode()->willReturn('');
$method2->getName()->willReturn('variadicByRef');
$method2->getVisibility()->willReturn('public');
$method2->returnsReference()->willReturn(false);
$method2->isStatic()->willReturn(false);
$method2->getArguments()->willReturn(array($argument2));
$method2->hasReturnType()->willReturn(false);
$method2->getCode()->willReturn('');
$method3->getName()->willReturn('variadicWithType');
$method3->getVisibility()->willReturn('public');
$method3->returnsReference()->willReturn(false);
$method3->isStatic()->willReturn(false);
$method3->getArguments()->willReturn(array($argument3));
$method3->hasReturnType()->willReturn(false);
$method3->getCode()->willReturn('');
$method4->getName()->willReturn('variadicWithTypeByRef');
$method4->getVisibility()->willReturn('public');
$method4->returnsReference()->willReturn(false);
$method4->isStatic()->willReturn(false);
$method4->getArguments()->willReturn(array($argument4));
$method4->hasReturnType()->willReturn(false);
$method4->getCode()->willReturn('');
$argument1->getName()->willReturn('args');
$argument1->getTypeHint()->willReturn(null);
$argument1->isOptional()->willReturn(false);
$argument1->isPassedByReference()->willReturn(false);
$argument1->isVariadic()->willReturn(true);
$argument1->isNullable()->willReturn(false);
$argument2->getName()->willReturn('args');
$argument2->getTypeHint()->willReturn(null);
$argument2->isOptional()->willReturn(false);
$argument2->isPassedByReference()->willReturn(true);
$argument2->isVariadic()->willReturn(true);
$argument2->isNullable()->willReturn(false);
$argument3->getName()->willReturn('args');
$argument3->getTypeHint()->willReturn('\ReflectionClass');
$argument3->isOptional()->willReturn(false);
$argument3->isPassedByReference()->willReturn(false);
$argument3->isVariadic()->willReturn(true);
$argument3->isNullable()->willReturn(false);
$argument4->getName()->willReturn('args');
$argument4->getTypeHint()->willReturn('\ReflectionClass');
$argument4->isOptional()->willReturn(false);
$argument4->isPassedByReference()->willReturn(true);
$argument4->isVariadic()->willReturn(true);
$argument4->isNullable()->willReturn(false);
$code = $this->generate('CustomClass', $class);
$expected = <<<'PHP'
namespace {
class CustomClass extends \stdClass implements \Prophecy\Doubler\Generator\MirroredInterface {
public function variadic( ...$args) {
}
public function variadicByRef( &...$args) {
}
public function variadicWithType(\\ReflectionClass ...$args) {
}
public function variadicWithTypeByRef(\\ReflectionClass &...$args) {
}
}
}
PHP;
$expected = strtr($expected, array("\r\n" => "\n", "\r" => "\n"));
$code->shouldBe($expected);
}
function it_overrides_properly_methods_with_args_passed_by_reference(
ClassNode $class,
MethodNode $method,
ArgumentNode $argument
) {
$class->getParentClass()->willReturn('RuntimeException');
$class->getInterfaces()->willReturn(array('Prophecy\Doubler\Generator\MirroredInterface'));
$class->getProperties()->willReturn(array());
$class->getMethods()->willReturn(array($method));
$method->getName()->willReturn('getName');
$method->getVisibility()->willReturn('public');
$method->isStatic()->willReturn(false);
$method->getArguments()->willReturn(array($argument));
$method->hasReturnType()->willReturn(false);
$method->returnsReference()->willReturn(false);
$method->getCode()->willReturn('return $this->name;');
$argument->getName()->willReturn('fullname');
$argument->getTypeHint()->willReturn('array');
$argument->isOptional()->willReturn(true);
$argument->getDefault()->willReturn(null);
$argument->isPassedByReference()->willReturn(true);
$argument->isVariadic()->willReturn(false);
$argument->isNullable()->willReturn(false);
$code = $this->generate('CustomClass', $class);
$expected =<<<'PHP'
namespace {
class CustomClass extends \RuntimeException implements \Prophecy\Doubler\Generator\MirroredInterface {
public function getName(array &$fullname = NULL) {
return $this->name;
}
}
}
PHP;
$expected = strtr($expected, array("\r\n" => "\n", "\r" => "\n"));
$code->shouldBe($expected);
}
function it_generates_empty_class_for_empty_ClassNode(ClassNode $class)
{
$class->getParentClass()->willReturn('stdClass');
$class->getInterfaces()->willReturn(array('Prophecy\Doubler\Generator\MirroredInterface'));
$class->getProperties()->willReturn(array());
$class->getMethods()->willReturn(array());
$code = $this->generate('CustomClass', $class);
$expected =<<<'PHP'
namespace {
class CustomClass extends \stdClass implements \Prophecy\Doubler\Generator\MirroredInterface {
}
}
PHP;
$expected = strtr($expected, array("\r\n" => "\n", "\r" => "\n"));
$code->shouldBe($expected);
}
function it_wraps_class_in_namespace_if_it_is_namespaced(ClassNode $class)
{
$class->getParentClass()->willReturn('stdClass');
$class->getInterfaces()->willReturn(array('Prophecy\Doubler\Generator\MirroredInterface'));
$class->getProperties()->willReturn(array());
$class->getMethods()->willReturn(array());
$code = $this->generate('My\Awesome\CustomClass', $class);
$expected =<<<'PHP'
namespace My\Awesome {
class CustomClass extends \stdClass implements \Prophecy\Doubler\Generator\MirroredInterface {
}
}
PHP;
$expected = strtr($expected, array("\r\n" => "\n", "\r" => "\n"));
$code->shouldBe($expected);
}
}
@@ -0,0 +1,37 @@
<?php
namespace spec\Prophecy\Doubler\Generator;
use PhpSpec\ObjectBehavior;
use Prophecy\Doubler\Generator\ClassCodeGenerator;
use Prophecy\Doubler\Generator\Node\ClassNode;
class ClassCreatorSpec extends ObjectBehavior
{
function let(ClassCodeGenerator $generator)
{
$this->beConstructedWith($generator);
}
function it_evaluates_code_generated_by_ClassCodeGenerator($generator, ClassNode $class)
{
$generator->generate('stdClass', $class)->shouldBeCalled()->willReturn(
'return 42;'
);
$this->create('stdClass', $class)->shouldReturn(42);
}
function it_throws_an_exception_if_class_does_not_exist_after_evaluation($generator, ClassNode $class)
{
$generator->generate('CustomClass', $class)->shouldBeCalled()->willReturn(
'return 42;'
);
$class->getParentClass()->willReturn('stdClass');
$class->getInterfaces()->willReturn(array('Interface1', 'Interface2'));
$this->shouldThrow('Prophecy\Exception\Doubler\ClassCreatorException')
->duringCreate('CustomClass', $class);
}
}
@@ -0,0 +1,92 @@
<?php
namespace spec\Prophecy\Doubler\Generator\Node;
use PhpSpec\ObjectBehavior;
class ArgumentNodeSpec extends ObjectBehavior
{
function let()
{
$this->beConstructedWith('name');
}
function it_is_not_be_passed_by_reference_by_default()
{
$this->shouldNotBePassedByReference();
}
function it_is_passed_by_reference_if_marked()
{
$this->setAsPassedByReference();
$this->shouldBePassedByReference();
}
function it_is_not_variadic_by_default()
{
$this->shouldNotBeVariadic();
}
function it_is_variadic_if_marked()
{
$this->setAsVariadic();
$this->shouldBeVariadic();
}
function it_does_not_have_default_by_default()
{
$this->shouldNotHaveDefault();
}
function it_does_not_have_default_if_variadic()
{
$this->setDefault(null);
$this->setAsVariadic();
$this->shouldNotHaveDefault();
}
function it_does_have_default_if_not_variadic()
{
$this->setDefault(null);
$this->setAsVariadic(false);
$this->hasDefault()->shouldReturn(true);
}
function it_has_name_with_which_it_was_been_constructed()
{
$this->getName()->shouldReturn('name');
}
function it_has_no_typehint_by_default()
{
$this->getTypeHint()->shouldReturn(null);
}
function its_typeHint_is_mutable()
{
$this->setTypeHint('array');
$this->getTypeHint()->shouldReturn('array');
}
function it_does_not_have_default_value_by_default()
{
$this->getDefault()->shouldReturn(null);
}
function it_is_not_optional_by_default()
{
$this->isOptional()->shouldReturn(false);
}
function its_default_is_mutable()
{
$this->setDefault(array());
$this->getDefault()->shouldReturn(array());
}
function it_is_marked_as_optional_when_default_is_set()
{
$this->setDefault(null);
$this->isOptional()->shouldReturn(true);
}
}
@@ -0,0 +1,185 @@
<?php
namespace spec\Prophecy\Doubler\Generator\Node;
use PhpSpec\ObjectBehavior;
use Prophecy\Doubler\Generator\Node\MethodNode;
use Prophecy\Exception\Doubler\MethodNotExtendableException;
class ClassNodeSpec extends ObjectBehavior
{
function its_parentClass_is_a_stdClass_by_default()
{
$this->getParentClass()->shouldReturn('stdClass');
}
function its_parentClass_is_mutable()
{
$this->setParentClass('Exception');
$this->getParentClass()->shouldReturn('Exception');
}
function its_parentClass_is_set_to_stdClass_if_user_set_null()
{
$this->setParentClass(null);
$this->getParentClass()->shouldReturn('stdClass');
}
function it_does_not_implement_any_interface_by_default()
{
$this->getInterfaces()->shouldHaveCount(0);
}
function its_addInterface_adds_item_to_the_list_of_implemented_interfaces()
{
$this->addInterface('MyInterface');
$this->getInterfaces()->shouldHaveCount(1);
}
function its_hasInterface_returns_true_if_class_implements_interface()
{
$this->addInterface('MyInterface');
$this->hasInterface('MyInterface')->shouldReturn(true);
}
function its_hasInterface_returns_false_if_class_does_not_implements_interface()
{
$this->hasInterface('MyInterface')->shouldReturn(false);
}
function it_supports_implementation_of_multiple_interfaces()
{
$this->addInterface('MyInterface');
$this->addInterface('MySecondInterface');
$this->getInterfaces()->shouldHaveCount(2);
}
function it_ignores_same_interfaces_added_twice()
{
$this->addInterface('MyInterface');
$this->addInterface('MyInterface');
$this->getInterfaces()->shouldHaveCount(1);
$this->getInterfaces()->shouldReturn(array('MyInterface'));
}
function it_does_not_have_methods_by_default()
{
$this->getMethods()->shouldHaveCount(0);
}
function it_can_has_methods(MethodNode $method1, MethodNode $method2)
{
$method1->getName()->willReturn('__construct');
$method2->getName()->willReturn('getName');
$this->addMethod($method1);
$this->addMethod($method2);
$this->getMethods()->shouldReturn(array(
'__construct' => $method1,
'getName' => $method2
));
}
function its_hasMethod_returns_true_if_method_exists(MethodNode $method)
{
$method->getName()->willReturn('getName');
$this->addMethod($method);
$this->hasMethod('getName')->shouldReturn(true);
}
function its_getMethod_returns_method_by_name(MethodNode $method)
{
$method->getName()->willReturn('getName');
$this->addMethod($method);
$this->getMethod('getName')->shouldReturn($method);
}
function its_hasMethod_returns_false_if_method_does_not_exists()
{
$this->hasMethod('getName')->shouldReturn(false);
}
function its_hasMethod_returns_false_if_method_has_been_removed(MethodNode $method)
{
$method->getName()->willReturn('getName');
$this->addMethod($method);
$this->removeMethod('getName');
$this->hasMethod('getName')->shouldReturn(false);
}
function it_does_not_have_properties_by_default()
{
$this->getProperties()->shouldHaveCount(0);
}
function it_is_able_to_have_properties()
{
$this->addProperty('title');
$this->addProperty('text', 'private');
$this->getProperties()->shouldReturn(array(
'title' => 'public',
'text' => 'private'
));
}
function its_addProperty_does_not_accept_unsupported_visibility()
{
$this->shouldThrow('InvalidArgumentException')->duringAddProperty('title', 'town');
}
function its_addProperty_lowercases_visibility_before_setting()
{
$this->addProperty('text', 'PRIVATE');
$this->getProperties()->shouldReturn(array('text' => 'private'));
}
function its_has_no_unextendable_methods_by_default()
{
$this->getUnextendableMethods()->shouldHaveCount(0);
}
function its_addUnextendableMethods_adds_an_unextendable_method()
{
$this->addUnextendableMethod('testMethod');
$this->getUnextendableMethods()->shouldHaveCount(1);
}
function its_methods_are_extendable_by_default()
{
$this->isExtendable('testMethod')->shouldReturn(true);
}
function its_unextendable_methods_are_not_extendable()
{
$this->addUnextendableMethod('testMethod');
$this->isExtendable('testMethod')->shouldReturn(false);
}
function its_addUnextendableMethods_doesnt_create_duplicates()
{
$this->addUnextendableMethod('testMethod');
$this->addUnextendableMethod('testMethod');
$this->getUnextendableMethods()->shouldHaveCount(1);
}
function it_throws_an_exception_when_adding_a_method_that_isnt_extendable(MethodNode $method)
{
$this->addUnextendableMethod('testMethod');
$method->getName()->willReturn('testMethod');
$expectedException = new MethodNotExtendableException(
"Method `testMethod` is not extendable, so can not be added.",
"stdClass",
"testMethod"
);
$this->shouldThrow($expectedException)->duringAddMethod($method);
}
}
@@ -0,0 +1,134 @@
<?php
namespace spec\Prophecy\Doubler\Generator\Node;
use PhpSpec\ObjectBehavior;
use Prophecy\Doubler\Generator\Node\ArgumentNode;
class MethodNodeSpec extends ObjectBehavior
{
function let()
{
$this->beConstructedWith('getTitle');
}
function it_has_a_name()
{
$this->getName()->shouldReturn('getTitle');
}
function it_has_public_visibility_by_default()
{
$this->getVisibility()->shouldReturn('public');
}
function its_visibility_is_mutable()
{
$this->setVisibility('private');
$this->getVisibility()->shouldReturn('private');
}
function it_is_not_static_by_default()
{
$this->shouldNotBeStatic();
}
function it_does_not_return_a_reference_by_default()
{
$this->returnsReference()->shouldReturn(false);
}
function it_should_be_settable_as_returning_a_reference_through_setter()
{
$this->setReturnsReference();
$this->returnsReference()->shouldReturn(true);
}
function it_should_be_settable_as_static_through_setter()
{
$this->setStatic();
$this->shouldBeStatic();
}
function it_accepts_only_supported_visibilities()
{
$this->shouldThrow('InvalidArgumentException')->duringSetVisibility('stealth');
}
function it_lowercases_visibility_before_setting_it()
{
$this->setVisibility('Public');
$this->getVisibility()->shouldReturn('public');
}
function its_useParentCode_causes_method_to_call_parent(ArgumentNode $argument1, ArgumentNode $argument2)
{
$argument1->getName()->willReturn('objectName');
$argument2->getName()->willReturn('default');
$argument1->isVariadic()->willReturn(false);
$argument2->isVariadic()->willReturn(true);
$this->addArgument($argument1);
$this->addArgument($argument2);
$this->useParentCode();
$this->getCode()->shouldReturn(
'return parent::getTitle($objectName, ...$default);'
);
}
function its_code_is_mutable()
{
$this->setCode('echo "code";');
$this->getCode()->shouldReturn('echo "code";');
}
function its_reference_returning_methods_will_generate_exceptions()
{
$this->setCode('echo "code";');
$this->setReturnsReference();
$this->getCode()->shouldReturn("throw new \Prophecy\Exception\Doubler\ReturnByReferenceException('Returning by reference not supported', get_class(\$this), 'getTitle');");
}
function its_setCode_provided_with_null_cleans_method_body()
{
$this->setCode(null);
$this->getCode()->shouldReturn('');
}
function it_is_constructable_with_code()
{
$this->beConstructedWith('getTitle', 'die();');
$this->getCode()->shouldReturn('die();');
}
function it_does_not_have_arguments_by_default()
{
$this->getArguments()->shouldHaveCount(0);
}
function it_supports_adding_arguments(ArgumentNode $argument1, ArgumentNode $argument2)
{
$this->addArgument($argument1);
$this->addArgument($argument2);
$this->getArguments()->shouldReturn(array($argument1, $argument2));
}
function it_does_not_have_return_type_by_default()
{
$this->hasReturnType()->shouldReturn(false);
}
function it_setReturnType_sets_return_type()
{
$returnType = 'string';
$this->setReturnType($returnType);
$this->hasReturnType()->shouldReturn(true);
$this->getReturnType()->shouldReturn($returnType);
}
}