/** * Construct a new call verifier. * * @param Call $call The call. * @param MatcherFactory $matcherFactory The matcher factory to use. * @param MatcherVerifier $matcherVerifier The matcher verifier to use. * @param GeneratorVerifierFactory $generatorVerifierFactory The generator verifier factory to use. * @param IterableVerifierFactory $iterableVerifierFactory The iterable verifier factory to use. * @param AssertionRecorder $assertionRecorder The assertion recorder to use. * @param AssertionRenderer $assertionRenderer The assertion renderer to use. */ public function __construct(Call $call, MatcherFactory $matcherFactory, MatcherVerifier $matcherVerifier, GeneratorVerifierFactory $generatorVerifierFactory, IterableVerifierFactory $iterableVerifierFactory, AssertionRecorder $assertionRecorder, AssertionRenderer $assertionRenderer) { parent::__construct(); $this->call = $call; $this->matcherFactory = $matcherFactory; $this->matcherVerifier = $matcherVerifier; $this->generatorVerifierFactory = $generatorVerifierFactory; $this->iterableVerifierFactory = $iterableVerifierFactory; $this->assertionRecorder = $assertionRecorder; $this->assertionRenderer = $assertionRenderer; $this->argumentCount = count($call->arguments()); }
/** * Create a new generator spy. * * @param Call $call The call from which the generator originated. * @param Generator $generator The generator. * @param CallEventFactory $callEventFactory The call event factory to use. * @param bool $isGeneratorImplicitNextSupported True if implicit generator next() behavior is supported. * * @return Generator The newly created generator spy. */ public static function createGeneratorSpy(Call $call, Generator $generator, CallEventFactory $callEventFactory, $isGeneratorImplicitNextSupported) { $call->addIterableEvent($callEventFactory->createUsed()); $isFirst = true; $received = null; $receivedException = null; while (true) { $thrown = null; try { if ($isFirst) { if (!$isGeneratorImplicitNextSupported) { $generator->next(); } } else { if ($receivedException) { $generator->throw($receivedException); } else { $generator->send($received); } } if (!$generator->valid()) { $call->setEndEvent($callEventFactory->createReturned(null)); break; } } catch (Throwable $thrown) { // re-thrown after recording } catch (Exception $thrown) { // re-thrown after recording } if ($thrown) { $call->setEndEvent($callEventFactory->createThrew($thrown)); throw $thrown; } $key = $generator->key(); $value = $generator->current(); $received = null; $receivedException = null; $call->addIterableEvent($callEventFactory->createProduced($key, $value)); try { $received = (yield $key => $value); $call->addIterableEvent($callEventFactory->createReceived($received)); } catch (Exception $receivedException) { $call->addIterableEvent($callEventFactory->createReceivedException($receivedException)); } $isFirst = false; unset($value); } }
/** * Create a new generator spy. * * @param Call $call The call from which the generator originated. * @param Generator $generator The generator. * @param CallEventFactory $callEventFactory The call event factory to use. * * @return Generator The newly created generator spy. */ public static function createGeneratorSpy(Call $call, Generator $generator, CallEventFactory $callEventFactory) { $call->addIterableEvent($callEventFactory->createUsed()); $isFirst = true; $received = null; $receivedException = null; while (true) { $thrown = null; try { if (!$isFirst) { if ($receivedException) { $generator->throw($receivedException); } else { $generator->send($received); } } if (!$generator->valid()) { $returnValue = $generator->getReturn(); $call->setEndEvent($callEventFactory->createReturned($returnValue)); return $returnValue; } } catch (Throwable $thrown) { // re-thrown after recording // @codeCoverageIgnoreStart } catch (Exception $thrown) { // re-thrown after recording } // @codeCoverageIgnoreEnd if ($thrown) { $call->setEndEvent($callEventFactory->createThrew($thrown)); throw $thrown; } $key = $generator->key(); $value = $generator->current(); $received = null; $receivedException = null; $call->addIterableEvent($callEventFactory->createProduced($key, $value)); try { $received = (yield $key => $value); $call->addIterableEvent($callEventFactory->createReceived($received)); } catch (Exception $receivedException) { $call->addIterableEvent($callEventFactory->createReceivedException($receivedException)); } $isFirst = false; unset($value); } }
/** * Render a failed generator threw() verification. * * @param Spy|Call $subject The subject. * @param Cardinality $cardinality The cardinality. * @param Matcher|Exception|Error|string|null $type The type of exception. * * @return string The rendered failure message. */ public function renderGeneratorThrew($subject, Cardinality $cardinality, $type) { $isCall = $subject instanceof Call; if ($isCall) { $calls = array($subject); $renderedCallee = $this->exporter->exportCallable($subject->callback()); } else { $calls = $subject->allCalls(); $renderedCallee = $this->exporter->exportCallable($subject); } $renderedSubject = $this->bold . $renderedCallee . $this->reset; $minimum = $cardinality->minimum(); $maximum = $cardinality->maximum(); $isNever = null !== $maximum && $maximum < 1; if ($isCall) { $totalCount = 1; $iterableCount = 1; $renderedIterableCount = ''; } else { $iterableCount = 0; foreach ($calls as $call) { if ($call->isGenerator()) { ++$iterableCount; } } $totalCount = $iterableCount; if ($cardinality->matches($iterableCount, $iterableCount)) { $iterableResultStart = $this->passStart; } else { $iterableResultStart = $this->failStart; } $matchOrMatches = 1 === $iterableCount ? 'match' : 'matches'; $renderedIterableCount = ' ' . $iterableResultStart . $this->faint . '(' . $iterableCount . ' ' . $matchOrMatches . ')' . $this->reset; } if ($iterableCount xor $isNever) { $iterableResult = $this->pass; } else { $iterableResult = $this->fail; } if ($type instanceof Matcher) { $renderedType = $type->describe($this->exporter); } elseif (is_string($type)) { $renderedType = $type; } else { $renderedType = '<any>'; } $renderedCriteria = 'behave like:' . PHP_EOL . ' ' . $iterableResult . ' Returned Generator, then:' . $renderedIterableCount . PHP_EOL . ' ' . $this->fail . ' Threw ' . $renderedType; if ($isCall) { if ($isNever) { $expected = 'Expected ' . $renderedSubject . ' call #' . $subject->index() . ' not to ' . $renderedCriteria; } else { $expected = 'Expected ' . $renderedSubject . ' call #' . $subject->index() . ' to ' . $renderedCriteria; } } else { if ($isNever) { $expected = 'Expected ' . $renderedSubject . ' generator calls not to ' . $renderedCriteria; } elseif ($cardinality->isAlways()) { $expected = 'Expected all ' . $renderedSubject . ' generator calls to ' . $renderedCriteria; } else { $expected = 'Expected ' . $renderedSubject . ' generator calls to ' . $renderedCriteria; } } $renderedCalls = array(); $matchCount = 0; foreach ($calls as $call) { $callIsRelevant = $call->isGenerator(); if ($callIsRelevant) { $callStart = ''; $callEnd = ''; } else { $callStart = $this->faint; $callEnd = $this->reset; } $isMatchingCall = false; $renderedArguments = array(); foreach ($call->arguments()->all() as $argument) { $renderedArguments[] = $this->exporter->export($argument, 0); } $responseEvent = $call->responseEvent(); if ($responseEvent instanceof ReturnedEvent) { $returnValue = $responseEvent->value(); if (is_array($returnValue) || $returnValue instanceof Traversable) { $iterableEvents = $call->iterableEvents(); $renderedIterableEvents = array(); foreach ($iterableEvents as $event) { if ($event instanceof UsedEvent) { $renderedIterableEvents[] = ' - Started iterating'; } elseif ($event instanceof ProducedEvent) { $iterableKey = $event->key(); $iterableValue = $event->value(); $renderedIterableEvents[] = ' - Produced ' . $this->exporter->export($iterableKey) . ' => ' . $this->exporter->export($iterableValue); } elseif ($event instanceof ReceivedEvent) { $iterableValue = $event->value(); $renderedIterableEvents[] = ' - Received ' . $this->exporter->export($iterableValue); } elseif ($event instanceof ReceivedExceptionEvent) { $iterableException = $event->exception(); $renderedIterableEvents[] = ' - Received exception ' . $this->exporter->export($iterableException); } } $endEvent = $call->endEvent(); if (empty($iterableEvents)) { if ($callIsRelevant) { if ($isNever) { $eventResult = $this->pass; } else { $eventResult = $this->fail; } } else { $eventResult = '-'; } $renderedIterableEvents[] = ' ' . $eventResult . ' Never started iterating'; } elseif ($endEvent instanceof ConsumedEvent) { $renderedIterableEvents[] = ' - Finished iterating'; } elseif ($endEvent instanceof ReturnedEvent) { $iterableValue = $endEvent->value(); if ($isNever) { $eventResult = $this->pass; } else { $eventResult = $this->fail; } $renderedIterableEvents[] = ' ' . $eventResult . ' Returned ' . $this->exporter->export($iterableValue); } elseif ($endEvent instanceof ThrewEvent) { $iterableException = $endEvent->exception(); $renderedIterableException = $this->exporter->export($iterableException); if ($type instanceof Matcher) { $eventIsMatch = $type->matches($iterableException); } elseif (is_string($type)) { $eventIsMatch = is_a($iterableException, $type); } else { $eventIsMatch = true; } if ($eventIsMatch) { ++$matchCount; $isMatchingCall = true; } elseif ($type instanceof EqualToMatcher) { $renderedIterableException = $this->differenceEngine->difference($renderedType, $renderedIterableException); } if ($eventIsMatch xor $isNever) { $eventResult = $this->pass; } else { $eventResult = $this->fail; } $renderedIterableEvents[] = ' ' . $eventResult . ' Threw ' . $renderedIterableException; } else { if ($callIsRelevant) { if ($isNever) { $eventResult = $this->pass; } else { $eventResult = $this->fail; } } else { $eventResult = '-'; } $renderedIterableEvents[] = ' ' . $eventResult . ' Never finished iterating'; } $renderedResponse = 'Returned ' . $this->exporter->export($returnValue, 0) . ', then:' . $callEnd . PHP_EOL . $callStart . implode($callEnd . PHP_EOL . $callStart, $renderedIterableEvents); } else { $renderedResponse = 'Returned ' . $this->exporter->export($returnValue); } } elseif ($responseEvent instanceof ThrewEvent) { $exception = $responseEvent->exception(); $renderedResponse = 'Threw ' . $this->exporter->export($exception); } else { $renderedResponse = 'Never responded'; } if ($callIsRelevant) { if ($isMatchingCall xor $isNever) { $renderedResult = $this->pass; } else { $renderedResult = $this->fail; } } else { $renderedResult = '-'; } $renderedCalls[] = $callStart . $renderedResult . ' Call #' . $call->index() . ' - ' . $renderedCallee . '(' . implode(', ', $renderedArguments) . '):' . $callEnd . PHP_EOL . $callStart . ' ' . $renderedResult . ' ' . $renderedResponse . $callEnd; } $actual = PHP_EOL . implode(PHP_EOL, $renderedCalls); if ($isCall) { $cardinality = ''; } else { $cardinality = $this->renderCardinality($minimum, $maximum, $matchCount, $totalCount, $totalCount, false); } return $this->reset . $expected . $cardinality . $actual; }