/** * 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; }