updated backend

This commit is contained in:
Torsten 2020-01-17 10:58:01 +01:00
parent 1fea0f4fe7
commit 11521c2339
3459 changed files with 72735 additions and 168535 deletions

View file

@ -1,3 +1,28 @@
1.10.1 / 2019/12/22
===================
* [fixed] identical callables no longer match as arguments (@ciaranmcnulty)
1.10.0 / 2019/12/17
===================
* [added] shouldHaveBeenCalled evaluation happens later so un-stubbed calls don't throw (@elvetemedve)
* [added] methods can now be doubled case-insensitively to match PHP semantics (@michalbundyra)
* [fixed] reduced memory usage by optimising CachedDoubler (@DonCallisto)
* [fixed] removed fatal error nesting level when comparing large objects (@scroach)
1.9.0 / 2019/10/03
==================
* [added] Add willYield feature to Method Prophecy(@tkotosz)
* [fixed] Allow `MethodProphecy::willThrow()` to accept Throwable as string (@timoschinkel )
* [fixed] Allow new version of phpdocumentor/reflection-docblock (@ricpelo)
1.8.1 / 2019/06/13
==================
* [fixed] Don't try to patch final constructors (@NiR)
1.8.0 / 2018/08/05
==================

View file

@ -12,7 +12,7 @@ enough to be used inside any testing framework out there with minimal effort.
```php
<?php
class UserTest extends PHPUnit_Framework_TestCase
class UserTest extends PHPUnit\Framework\TestCase
{
private $prophet;
@ -28,7 +28,7 @@ class UserTest extends PHPUnit_Framework_TestCase
$this->assertEquals('hashed_pass', $user->getPassword());
}
protected function setup()
protected function setUp()
{
$this->prophet = new \Prophecy\Prophet;
}
@ -206,6 +206,17 @@ $user->setName('everzet')->will(function ($args) use ($user) {
And now it doesn't matter how many times or in which order your methods are called.
What matters is their behaviors and how well you faked it.
Note: If the method is called several times, you can use the following syntax to return different
values for each call:
```php
$prophecy->read('123')->willReturn(1, 2, 3);
```
This feature is actually not recommended for most cases. Relying on the order of
calls for the same arguments tends to make test fragile, as adding one more call
can break everything.
#### Arguments wildcarding
The previous example is awesome (at least I hope it is for you), but that's not

View file

@ -19,20 +19,20 @@
"require": {
"php": "^5.3|^7.0",
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
"sebastian/comparator": "^1.1|^2.0|^3.0",
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0",
"sebastian/comparator": "^1.2.3|^2.0|^3.0",
"doctrine/instantiator": "^1.0.2",
"sebastian/recursion-context": "^1.0|^2.0|^3.0"
},
"require-dev": {
"phpspec/phpspec": "^2.5|^3.2",
"phpspec/phpspec": "^2.5 || ^3.2",
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
},
"autoload": {
"psr-0": {
"Prophecy\\": "src/"
"psr-4": {
"Prophecy\\": "src/Prophecy"
}
},
@ -44,7 +44,7 @@
"extra": {
"branch-alias": {
"dev-master": "1.8.x-dev"
"dev-master": "1.10.x-dev"
}
}
}

View file

@ -59,7 +59,9 @@ class ExactValueToken implements TokenInterface
try {
$comparator->assertEquals($argument, $this->value);
return 10;
} catch (ComparisonFailure $failure) {}
} catch (ComparisonFailure $failure) {
return false;
}
}
// If either one is an object it should be castable to a string

View file

@ -12,11 +12,11 @@
namespace Prophecy\Call;
use Prophecy\Exception\Prophecy\MethodProphecyException;
use Prophecy\Prophecy\MethodProphecy;
use Prophecy\Prophecy\ObjectProphecy;
use Prophecy\Argument\ArgumentsWildcard;
use Prophecy\Util\StringUtil;
use Prophecy\Exception\Call\UnexpectedCallException;
use SplObjectStorage;
/**
* Calls receiver & manager.
@ -32,6 +32,11 @@ class CallCenter
*/
private $recordedCalls = array();
/**
* @var SplObjectStorage
*/
private $unexpectedCalls;
/**
* Initializes call center.
*
@ -40,6 +45,7 @@ class CallCenter
public function __construct(StringUtil $util = null)
{
$this->util = $util ?: new StringUtil;
$this->unexpectedCalls = new SplObjectStorage();
}
/**
@ -74,23 +80,21 @@ class CallCenter
}
// If no method prophecies defined, then it's a dummy, so we'll just return null
if ('__destruct' === $methodName || 0 == count($prophecy->getMethodProphecies())) {
if ('__destruct' === strtolower($methodName) || 0 == count($prophecy->getMethodProphecies())) {
$this->recordedCalls[] = new Call($methodName, $arguments, null, null, $file, $line);
return null;
}
// There are method prophecies, so it's a fake/stub. Searching prophecy for this call
$matches = array();
foreach ($prophecy->getMethodProphecies($methodName) as $methodProphecy) {
if (0 < $score = $methodProphecy->getArgumentsWildcard()->scoreArguments($arguments)) {
$matches[] = array($score, $methodProphecy);
}
}
$matches = $this->findMethodProphecies($prophecy, $methodName, $arguments);
// If fake/stub doesn't have method prophecy for this call - throw exception
if (!count($matches)) {
throw $this->createUnexpectedCallException($prophecy, $methodName, $arguments);
$this->unexpectedCalls->attach(new Call($methodName, $arguments, null, null, $file, $line), $prophecy);
$this->recordedCalls[] = new Call($methodName, $arguments, null, null, $file, $line);
return null;
}
// Sort matches by their score value
@ -138,15 +142,33 @@ class CallCenter
*/
public function findCalls($methodName, ArgumentsWildcard $wildcard)
{
$methodName = strtolower($methodName);
return array_values(
array_filter($this->recordedCalls, function (Call $call) use ($methodName, $wildcard) {
return $methodName === $call->getMethodName()
return $methodName === strtolower($call->getMethodName())
&& 0 < $call->getScore($wildcard)
;
})
);
}
/**
* @throws UnexpectedCallException
*/
public function checkUnexpectedCalls()
{
/** @var Call $call */
foreach ($this->unexpectedCalls as $call) {
$prophecy = $this->unexpectedCalls[$call];
// If fake/stub doesn't have method prophecy for this call - throw exception
if (!count($this->findMethodProphecies($prophecy, $call->getMethodName(), $call->getArguments()))) {
throw $this->createUnexpectedCallException($prophecy, $call->getMethodName(), $call->getArguments());
}
}
}
private function createUnexpectedCallException(ObjectProphecy $prophecy, $methodName,
array $arguments)
{
@ -194,28 +216,6 @@ class CallCenter
);
}
private function formatExceptionMessage(MethodProphecy $methodProphecy)
{
return sprintf(
" - %s(\n".
"%s\n".
" )",
$methodProphecy->getMethodName(),
implode(
",\n",
$this->indentArguments(
array_map(
function ($token) {
return (string) $token;
},
$methodProphecy->getArgumentsWildcard()->getTokens()
),
$indentationLength
)
)
);
}
private function indentArguments(array $arguments, $indentationLength)
{
return preg_replace_callback(
@ -226,4 +226,23 @@ class CallCenter
$arguments
);
}
/**
* @param ObjectProphecy $prophecy
* @param string $methodName
* @param array $arguments
*
* @return array
*/
private function findMethodProphecies(ObjectProphecy $prophecy, $methodName, array $arguments)
{
$matches = array();
foreach ($prophecy->getMethodProphecies($methodName) as $methodProphecy) {
if (0 < $score = $methodProphecy->getArgumentsWildcard()->scoreArguments($arguments)) {
$matches[] = array($score, $methodProphecy);
}
}
return $matches;
}
}

View file

@ -27,16 +27,18 @@ final class ClosureComparator extends Comparator
&& is_object($actual) && $actual instanceof \Closure;
}
public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false)
public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false, array &$processed = array())
{
throw new ComparisonFailure(
$expected,
$actual,
// we don't need a diff
'',
'',
false,
'all closures are born different'
);
if ($expected !== $actual) {
throw new ComparisonFailure(
$expected,
$actual,
// we don't need a diff
'',
'',
false,
'all closures are different if not identical'
);
}
}
}

View file

@ -21,17 +21,7 @@ use ReflectionClass;
*/
class CachedDoubler extends Doubler
{
private $classes = array();
/**
* {@inheritdoc}
*/
public function registerClassPatch(ClassPatch\ClassPatchInterface $patch)
{
$this->classes[] = array();
parent::registerClassPatch($patch);
}
private static $classes = array();
/**
* {@inheritdoc}
@ -39,11 +29,11 @@ class CachedDoubler extends Doubler
protected function createDoubleClass(ReflectionClass $class = null, array $interfaces)
{
$classId = $this->generateClassId($class, $interfaces);
if (isset($this->classes[$classId])) {
return $this->classes[$classId];
if (isset(self::$classes[$classId])) {
return self::$classes[$classId];
}
return $this->classes[$classId] = parent::createDoubleClass($class, $interfaces);
return self::$classes[$classId] = parent::createDoubleClass($class, $interfaces);
}
/**
@ -61,8 +51,16 @@ class CachedDoubler extends Doubler
foreach ($interfaces as $interface) {
$parts[] = $interface->getName();
}
foreach ($this->getClassPatches() as $patch) {
$parts[] = get_class($patch);
}
sort($parts);
return md5(implode('', $parts));
}
public function resetCache()
{
self::$classes = array();
}
}

View file

@ -41,6 +41,10 @@ class DisableConstructorPatch implements ClassPatchInterface
*/
public function apply(ClassNode $node)
{
if (!$node->isExtendable('__construct')) {
return;
}
if (!$node->hasMethod('__construct')) {
$node->addMethod(new MethodNode('__construct', ''));

View file

@ -43,7 +43,7 @@ class ProphecySubjectPatch implements ClassPatchInterface
public function apply(ClassNode $node)
{
$node->addInterface('Prophecy\Prophecy\ProphecySubjectInterface');
$node->addProperty('objectProphecy', 'private');
$node->addProperty('objectProphecyClosure', 'private');
foreach ($node->getMethods() as $name => $method) {
if ('__construct' === strtolower($name)) {
@ -65,10 +65,10 @@ class ProphecySubjectPatch implements ClassPatchInterface
$prophecyArgument = new ArgumentNode('prophecy');
$prophecyArgument->setTypeHint('Prophecy\Prophecy\ProphecyInterface');
$prophecySetter->addArgument($prophecyArgument);
$prophecySetter->setCode('$this->objectProphecy = $prophecy;');
$prophecySetter->setCode('$this->objectProphecyClosure = function () use ($prophecy) { return $prophecy; };');
$prophecyGetter = new MethodNode('getProphecy');
$prophecyGetter->setCode('return $this->objectProphecy;');
$prophecyGetter->setCode('return call_user_func($this->objectProphecyClosure);');
if ($node->hasMethod('__call')) {
$__call = $node->getMethod('__call');

View file

@ -144,7 +144,7 @@ class ClassMirror
}
if (version_compare(PHP_VERSION, '7.0', '>=') && $method->hasReturnType()) {
$returnType = (string) $method->getReturnType();
$returnType = PHP_VERSION_ID >= 70100 ? $method->getReturnType()->getName() : (string) $method->getReturnType();
$returnTypeLower = strtolower($returnType);
if ('self' === $returnTypeLower) {
@ -231,7 +231,7 @@ class ClassMirror
}
if (version_compare(PHP_VERSION, '7.0', '>=') && true === $parameter->hasType()) {
return (string) $parameter->getType();
return PHP_VERSION_ID >= 70100 ? $parameter->getType()->getName() : (string) $parameter->getType();
}
return null;

View file

@ -41,7 +41,7 @@ class ThrowPromise implements PromiseInterface
public function __construct($exception)
{
if (is_string($exception)) {
if (!class_exists($exception) || !$this->isAValidThrowable($exception)) {
if ((!class_exists($exception) && !interface_exists($exception)) || !$this->isAValidThrowable($exception)) {
throw new InvalidArgumentException(sprintf(
'Exception / Throwable class or instance expected as argument to ThrowPromise, but got %s.',
$exception
@ -94,6 +94,7 @@ class ThrowPromise implements PromiseInterface
*/
private function isAValidThrowable($exception)
{
return is_a($exception, 'Exception', true) || is_subclass_of($exception, 'Throwable', true);
return is_a($exception, 'Exception', true)
|| is_a($exception, 'Throwable', true);
}
}

View file

@ -71,7 +71,7 @@ class MethodProphecy
}
if (version_compare(PHP_VERSION, '7.0', '>=') && true === $reflectedMethod->hasReturnType()) {
$type = (string) $reflectedMethod->getReturnType();
$type = PHP_VERSION_ID >= 70100 ? $reflectedMethod->getReturnType()->getName() : (string) $reflectedMethod->getReturnType();
if ('void' === $type) {
$this->voidReturnType = true;
@ -180,6 +180,40 @@ class MethodProphecy
return $this->will(new Promise\ReturnPromise(func_get_args()));
}
/**
* @param array $items
*
* @return $this
*
* @throws \Prophecy\Exception\InvalidArgumentException
*/
public function willYield($items)
{
if ($this->voidReturnType) {
throw new MethodProphecyException(
"The method \"$this->methodName\" has a void return type, and so cannot yield anything",
$this
);
}
if (!is_array($items)) {
throw new InvalidArgumentException(sprintf(
'Expected array, but got %s.',
gettype($items)
));
}
// Remove eval() when minimum version >=5.5
/** @var callable $generator */
$generator = eval('return function() use ($items) {
foreach ($items as $key => $value) {
yield $key => $value;
}
};');
return $this->will($generator);
}
/**
* Sets return argument promise to the prophecy.
*

View file

@ -146,7 +146,7 @@ class ObjectProphecy implements ProphecyInterface
), $methodProphecy);
}
$methodName = $methodProphecy->getMethodName();
$methodName = strtolower($methodProphecy->getMethodName());
if (!isset($this->methodProphecies[$methodName])) {
$this->methodProphecies[$methodName] = array();
@ -168,6 +168,8 @@ class ObjectProphecy implements ProphecyInterface
return $this->methodProphecies;
}
$methodName = strtolower($methodName);
if (!isset($this->methodProphecies[$methodName])) {
return array();
}
@ -208,12 +210,15 @@ class ObjectProphecy implements ProphecyInterface
* Checks that registered method predictions do not fail.
*
* @throws \Prophecy\Exception\Prediction\AggregateException If any of registered predictions fail
* @throws \Prophecy\Exception\Call\UnexpectedCallException
*/
public function checkProphecyMethodsPredictions()
{
$exception = new AggregateException(sprintf("%s:\n", get_class($this->reveal())));
$exception->setObjectProphecy($this);
$this->callCenter->checkUnexpectedCalls();
foreach ($this->methodProphecies as $prophecies) {
foreach ($prophecies as $prophecy) {
try {

View file

@ -11,6 +11,7 @@
namespace Prophecy;
use Prophecy\Doubler\CachedDoubler;
use Prophecy\Doubler\Doubler;
use Prophecy\Doubler\LazyDouble;
use Prophecy\Doubler\ClassPatch;
@ -45,11 +46,13 @@ class Prophet
* @param null|RevealerInterface $revealer
* @param null|StringUtil $util
*/
public function __construct(Doubler $doubler = null, RevealerInterface $revealer = null,
StringUtil $util = null)
{
public function __construct(
Doubler $doubler = null,
RevealerInterface $revealer = null,
StringUtil $util = null
) {
if (null === $doubler) {
$doubler = new Doubler;
$doubler = new CachedDoubler();
$doubler->registerClassPatch(new ClassPatch\SplFileInfoPatch);
$doubler->registerClassPatch(new ClassPatch\TraversablePatch);
$doubler->registerClassPatch(new ClassPatch\ThrowablePatch);

View file

@ -181,9 +181,7 @@ class ExportUtil
if (is_object($value)) {
$class = get_class($value);
if ($value instanceof ProphecyInterface) {
return sprintf('%s Object (*Prophecy*)', $class);
} elseif ($hash = $processed->contains($value)) {
if ($hash = $processed->contains($value)) {
return sprintf('%s:%s Object', $class, $hash);
}