getCallable(); $reflection = $callable->getReflection(); $specification = $this->getSpecification(); $exceptions = new Praspel\Exception\Group( 'The Runtime Assertion Checker has detected failures for %s.', 0, $callable ); $classname = null; $isConstructor = false; if ($reflection instanceof \ReflectionMethod) { $reflection->setAccessible(true); if ('__construct' === $reflection->getName()) { $isConstructor = true; } if (false === $reflection->isStatic()) { $_callback = $callable->getValidCallback(); $_object = $_callback[0]; $specification->getImplicitVariable('this')->bindTo($_object); } $classname = $reflection->getDeclaringClass()->getName(); } if (false !== $trace && !($trace instanceof Trace)) { $trace = new Praspel\Trace(); } // Prepare data. if (null === $data = $this->getData()) { if (true === $this->canGenerateData()) { $data = static::generateData($specification); $this->setData($data); } else { throw new Praspel\Exception\AssertionChecker( 'No data were given. The System Under Test %s needs data ' . 'to be executed.', 1, $callable ); } } $arguments = $this->getArgumentData( $reflection, $data, $numberOfRequiredArguments ); // Check invariant. $invariant = $specification->getClause('invariant'); $attributes = $this->getAttributeData($callable); foreach ($attributes as $name => $_) { $entryName = $classname . '::$' . $name; if (!isset($registry[$entryName])) { continue; } $entry = $registry[$entryName]; if (true === $entry->clauseExists('invariant')) { foreach ($entry->getClause('invariant') as $variable) { $invariant->addVariable($variable->getName(), $variable); } } } if (false === $isConstructor) { $verdict &= $this->checkClause( $invariant, $attributes, $exceptions, 'Hoa\Praspel\Exception\Failure\Invariant', true, $trace ); if (0 < count($exceptions)) { throw $exceptions; } } // Check requires and behaviors. $behavior = $specification; $verdict &= $this->checkBehavior( $behavior, $arguments, $exceptions, true, $trace ); if (0 < count($exceptions)) { throw $exceptions; } $rootBehavior = $behavior instanceof Praspel\Model\Specification; $numberOfArguments = count($arguments); if ($numberOfArguments < $numberOfRequiredArguments) { $exceptions[] = new Praspel\Exception\Failure\Precondition( 'Callable %s needs %d arguments; %d given.', 2, [$callable, $numberOfRequiredArguments, $numberOfArguments] ); throw $exceptions; } $_exceptions = true === $rootBehavior ? $exceptions : new Praspel\Exception\Group( 'Behavior %s is broken.', 3, $behavior->getIdentifier() ); try { // Invoke. $return = $this->invoke( $callable, $reflection, $arguments, $isConstructor ); $arguments['\result'] = $return; // Check normal postcondition. if (true === $behavior->clauseExists('ensures')) { $ensures = $behavior->getClause('ensures'); $verdict &= $this->checkClause( $ensures, $arguments, $_exceptions, 'Hoa\Praspel\Exception\Failure\Postcondition', false, $trace ); } } catch (Praspel\Exception $internalException) { $_exceptions[] = new Praspel\Exception\Failure\InternalPrecondition( 'The System Under Test has broken an internal contract.', 4, null, $internalException ); } catch (\Exception $exception) { $arguments['\result'] = $exception; // Check exceptional postcondition. if (true === $behavior->clauseExists('throwable')) { $throwable = $behavior->getClause('throwable'); $verdict &= $this->checkExceptionalClause( $throwable, $arguments ); if (false == $verdict) { $_exceptions[] = new Praspel\Exception\Failure\Exceptional( 'The exception %s has been unexpectedly thrown.', 5, get_class($arguments['\result']), $exception ); } } else { $verdict &= false; $_exceptions[] = new Praspel\Exception\Failure\Exceptional( 'The System Under Test cannot terminate exceptionally ' . 'because no exceptional postcondition has been specified ' . '(there is no @throwable clause).', 6, [], $exception ); } } if (0 < count($_exceptions) && false === $rootBehavior) { $_behavior = $behavior; while ( (null !== $_behavior = $_behavior->getParent()) && !($_behavior instanceof Praspel\Model\Specification) ) { $handle = new Praspel\Exception\Group( 'Behavior %s is broken.', 7, $_behavior->getIdentifier() ); $handle[] = $_exceptions; $_exceptions = $handle; } $exceptions[] = $_exceptions; } if (0 < count($exceptions)) { throw $exceptions; } // Check invariant. $attributes = $this->getAttributeData($callable); $verdict &= $this->checkClause( $invariant, $attributes, $exceptions, 'Hoa\Praspel\Exception\Failure\Invariant', true, $trace ); if (0 < count($exceptions)) { throw $exceptions; } return (bool) $verdict; } /** * Get argument data. * * @param \ReflectionFunctionAbstract $reflection Reflection. * @param array &$data Data. * @param int $numberOfRequiredArguments Number of * required * arguments. * @return array */ protected function getArgumentData( \ReflectionFunctionAbstract $reflection, Array &$data, &$numberOfRequiredArguments ) { $arguments = []; $numberOfRequiredArguments = 0; foreach ($reflection->getParameters() as $parameter) { $name = $parameter->getName(); if (true === array_key_exists($name, $data)) { $arguments[$name] = &$data[$name]; if (false === $parameter->isOptional()) { ++$numberOfRequiredArguments; } continue; } if (false === $parameter->isOptional()) { ++$numberOfRequiredArguments; // Let the error be caught by a @requires clause. continue; } $arguments[$name] = $parameter->getDefaultValue(); } return $arguments; } /** * Get attribute data. * * @param \Hoa\Core\Consistency\Xcallable $callable Callable. * @return array */ protected function getAttributeData(Core\Consistency\Xcallable $callable) { $callback = $callable->getValidCallback(); if ($callback instanceof \Closure) { return []; } $object = $callback[0]; if (!is_object($object)) { return []; } $reflectionObject = new \ReflectionObject($object); $attributes = []; foreach ($reflectionObject->getProperties() as $property) { $property->setAccessible(true); $attributes[$property->getName()] = $property->getValue($object); } return $attributes; } /** * Invoke. * * @acccess protected * @param \Hoa\Core\Consistency\Xcallable &$reflection Callable. * @param \ReflectionFunctionAbstract &$reflection Reflection. * @param array &$arguments Arguments. * @param bool $isConstructor Whether * it is a * constructor. * @return mixed * @throws \Exception */ protected function invoke( Core\Consistency\Xcallable &$callable, \ReflectionFunctionAbstract &$reflection, Array &$arguments, $isConstructor ) { if ($reflection instanceof \ReflectionFunction) { return $reflection->invokeArgs($arguments); } if (false === $isConstructor) { $_callback = $callable->getValidCallback(); $_object = $_callback[0]; return $reflection->invokeArgs($_object, $arguments); } $class = $reflection->getDeclaringClass(); $instance = $class->newInstanceArgs($arguments); $callable = xcallable($instance, '__construct'); $reflection = $callable->getReflection(); return void; } /** * Check behavior clauses. * * @param \Hoa\Praspel\Model\Behavior &$behavior Behavior clause. * @param array &$data Data. * @param \Hoa\Praspel\Exception\Group $exceptions Exceptions group. * @param bool $assign Assign data to * variable. * @param \Hoa\Praspel\Trace $trace Trace. * @return bool * @throws \Hoa\Praspel\Exception */ protected function checkBehavior( Praspel\Model\Behavior &$behavior, Array &$data, Praspel\Exception\Group $exceptions, $assign = false, $trace = false ) { $verdict = true; // Check precondition. if (true === $behavior->clauseExists('requires')) { $requires = $behavior->getClause('requires'); $verdict = $this->checkClause( $requires, $data, $exceptions, 'Hoa\Praspel\Exception\Failure\Precondition', $assign, $trace ); if (false === $verdict) { return false; } } // Check behaviors. if (true === $behavior->clauseExists('behavior')) { $_verdict = false; $behaviors = $behavior->getClause('behavior'); $exceptions->beginTransaction(); foreach ($behaviors as $_behavior) { $_exceptions = new Praspel\Exception\Group( 'Behavior %s is broken.', 8, $_behavior->getIdentifier() ); $_trace = null; if (!empty($trace)) { $_trace = new Praspel\Model\Behavior($trace); $_trace->setIdentifier($_behavior->getIdentifier()); } $_verdict = $this->checkBehavior( $_behavior, $data, $_exceptions, $assign, $_trace ); if (true === $_verdict) { if (!empty($trace)) { $trace->addClause($_trace); } break; } $exceptions[] = $_exceptions; unset($_trace); } if (false === $_verdict) { if (true === $behavior->clauseExists('default')) { $exceptions->rollbackTransaction(); $_verdict = true; $behavior = $behavior->getClause('default'); } else { $exceptions->commitTransaction(); } } else { $exceptions->rollbackTransaction(); $behavior = $_behavior; } $verdict &= $_verdict; } return (bool) $verdict; } /** * Check a clause. * * @param \Hoa\Praspel\Model\Declaration $clause Clause. * @param array &$data Data. * @param \Hoa\Praspel\Exception\Group $exceptions Exceptions group. * @param string $exception Exception to * throw. * @param bool $assign Assign data to * variable. * @param \Hoa\Praspel\Trace $trace Trace. * @return bool * @throws \Hoa\Praspel\Exception */ protected function checkClause( Praspel\Model\Declaration $clause, Array &$data, \Hoa\Praspel\Exception\Group $exceptions, $exception, $assign = false, $trace = false ) { $verdict = true; $traceClause = null; if (!empty($trace)) { $traceClause = clone $clause; } foreach ($clause as $name => $variable) { if (false === array_key_exists($name, $data)) { $exceptions[] = new $exception( 'Variable %s in @%s is required and has no value.', 9, [$name, $clause->getName()] ); continue; } $datum = &$data[$name]; $_verdict = false; $traceVariable = null; if (null !== $traceClause) { $traceVariable = clone $variable; $traceVariableDomains = $traceVariable->getDomains(); } $i = 0; foreach ($variable->getDomains() as $realdom) { if (false === $_verdict && true === $realdom->predicate($datum)) { $_verdict = true; } elseif (null !== $traceClause) { unset($traceVariableDomains[$i--]); } ++$i; } if (false === $_verdict) { if (null !== $traceClause) { unset($traceClause[$name]); } $exceptions[] = new $exception( 'Variable %s does not verify the constraint @%s %s.', 10, [ $name, $clause->getName(), $this->getVisitorPraspel()->visit($variable) ] ); } else { if (true === $assign) { $variable->setValue($datum); } if (null !== $traceClause) { unset($traceClause[$name]); $traceClause->addVariable($name, $traceVariable); } } $verdict &= $_verdict; } $predicateEvaluator = function ($__hoa_arguments, $__hoa_code) { extract($__hoa_arguments); return true == eval('return ' . $__hoa_code . ';'); }; foreach ($clause->getPredicates() as $predicate) { $_predicate = $predicate; preg_match_all( '#(?addClause($traceClause); } return (bool) $verdict; } /** * Check an exceptional clause. * * @param \Hoa\Praspel\Model\Throwable $clause Clause. * @param array &$data Data. * @return bool * @throws \Hoa\Praspel\Exception */ protected function checkExceptionalClause( Praspel\Model\Throwable $clause, Array &$data ) { $verdict = false; foreach ($clause as $identifier) { $_exception = $clause[$identifier]; $instanceName = $_exception->getInstanceName(); if ($data['\result'] instanceof $instanceName) { $verdict = true; break; } foreach ((array) $_exception->getDisjunction() as $_identifier) { $__exception = $clause[$_identifier]; $_instanceName = $__exception->getInstanceName(); if ($exception instanceof $_instanceName) { $verdict = true; break; } } } return $verdict; } }