/** * Invoke the encapsulation shunter. * * @param \Hoa\Praspel\Preambler\Handler $preambler Preambler. * @return void * @throws \Hoa\Praspel\Exception\Preambler */ public function __invoke(Handler $preambler) { $callable = $preambler->__getCallable(); $reflection = $callable->getReflection(); $registry = Praspel::getRegistry(); if ($reflection instanceof \ReflectionClass) { $_object = $reflection->newInstance(); $preambler->__setCallable(xcallable($_object, '__construct')); } elseif (!$reflection instanceof \ReflectionMethod) { throw new Praspel\Exception\Preambler('The callable must be a class and a (dynamic) method name.', 0); } else { $callback = $callable->getValidCallback(); if (!is_object($callback[0])) { $reflectionClass = $reflection->getDeclaringClass(); $_reflectionClass = $reflectionClass; while (null === ($constructor = $_reflectionClass->getConstructor()) && false !== ($_reflectionClass = $_reflectionClass->getParentClass())) { } if (null === $constructor) { $_object = $reflectionClass->newInstance(); } else { $className = $_reflectionClass->getName(); $id = $className . '::__construct'; if (!isset($registry[$id])) { $registry[$id] = Praspel::interpret(Praspel::extractFromComment($constructor->getDocComment()), $className); } $assertionChecker = $this->getAssertionChecker(); if (null === $assertionChecker) { $assertionChecker = '\\Hoa\\Praspel\\AssertionChecker'; } $arguments = $assertionChecker::generateData($registry[$id]); $_object = $reflectionClass->newInstanceArgs($arguments); } $preambler->__setCallable(xcallable($_object, $callback[1])); } } $reflectionObject = $preambler->__getReflectionObject($object); $className = $reflectionObject->getName(); $properties = $reflectionObject->getProperties(); foreach ($properties as $property) { $propertyName = $property->getName(); $id = $className . '::$' . $propertyName; if (false === isset($registry[$id])) { $registry[$id] = Praspel::interpret(Praspel::extractFromComment($property->getDocComment()), $className); } $specification = $registry[$id]; if (false === $specification->clauseExists('invariant')) { throw new Praspel\Exception\Preambler('Cannot generate a value from %s because it has no ' . '@invariant clause.', 1, $id); } $preambler->{$propertyName} = $specification->getClause('invariant')->getVariable($propertyName)->sample(); } return; }
/** * Runtime assertion checker. * * @param \Hoa\Praspel\Trace $trace Trace. * @return bool * @throws \Hoa\Praspel\Exception\AssertionChecker * @throws \Hoa\Praspel\Exception\Group */ public function evaluate(&$trace = false) { // Start. $registry = Praspel::getRegistry(); $verdict = true; $callable = $this->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; }
/** * Compute dynamic resolution. * * @param string $name Name. * @return void * @throws \Hoa\Praspel\Exception\Model */ protected function computeDynamicResolution($name) { $this->_type = static::TYPE_EXTERNAL; $clause = $this->getClause(); $parts = explode('->', $name); $head = array_shift($parts); if ('this' !== $head) { throw new Praspel\Exception\Model('Not yet implemented!'); } $registry = Praspel::getRegistry(); $root = $clause->getRoot(); $bindedClass = $root->getBindedClass(); if (null === $bindedClass) { throw new Praspel\Exception\Model('Cannot resolve the dynamic identifier %s; ' . '%s::getBindedClass returned null.', 3, [$name, get_class($root)]); } $attribute = array_shift($parts); $id = $bindedClass . '::$' . $attribute; if (!isset($registry[$id])) { throw new Praspel\Exception\Model('The contract identifier %s does not exist in the registry.', 4, $name); } $entry = $registry[$id]; if (false === $entry->clauseExists('invariant')) { throw new Praspel\Exception\Model('%s is not declared with an @invariant clause.', 5, $id); } $targetedClause = $entry->getClause('invariant'); if (!isset($targetedClause[$attribute])) { throw new Praspel\Exception\Model('The identifier %s does not exist.', 6, $attribute); } $variable = $targetedClause[$attribute]; $this->_variable = $variable; return; }
/** * Post simple validation. * * @param bool $verdict Verdict. * @param bool &$value Value. * @param \Hoa\Xyl\Interpreter\Html\Concrete $element Element that * requires it. * @param bool $postVerification Whether we run post-verification or * not. * @return bool */ public static function postValidation($verdict, &$value, Concrete $element, $postVerification = true) { // Order is important. $validates = []; if (true === $element->abstract->attributeExists('validate')) { $validates['@'] = $element->abstract->readAttribute('validate'); } $validates = array_merge($validates, $element->abstract->readCustomAttributes('validate')); if (empty($validates)) { if (true === $postVerification) { static::postVerification($verdict, $element); } return $verdict; } // Order is not important. $errors = $element->abstract->readCustomAttributesAsList('error'); if (true === $element->abstract->attributeExists('error')) { $errors['@'] = $element->abstract->readAttributeAsList('error'); } if (ctype_digit($value)) { $value = (int) $value; } elseif (is_numeric($value)) { $value = (double) $value; } $decision = true; foreach ($validates as $name => $realdom) { $praspel = Praspel::interpret('@requires i: ' . $realdom . ';'); $clause = $praspel->getClause('requires'); $variable = $clause['i']; $decision = $variable->predicate($value); if ('@' === $name) { $decision = $verdict && $decision; } if (true === $decision) { unset($errors[$name]); continue; } if (!isset($errors[$name])) { continue; } $handle = $element->xpath('//__current_ns:error[@id="' . implode('" or @id="', $errors[$name]) . '"]'); foreach ($handle as $error) { $element->getConcreteElement($error)->setVisibility(true); } unset($errors[$name]); break; } $verdict = $decision; if (true === $postVerification) { static::postVerification($verdict, $element, isset($errors['@'])); } return $verdict; }
/** * Set a value to a specific offset of the current attribute. * * @access public * @param int $offset Offset. * @param mixed $value Value. * @return bool * @throw \Hoa\Model\Exception */ public function offsetSet($offset, $value) { if (false === $this->isValidationEnabled()) { $this->{$this->__currentAccess}[$offset] = $value; return null; } $oldOffset = false !== $this->_offsetExists($offset) ? $this->{$this->__currentAccess}[$offset] : null; $this->{$this->__currentAccess}[$offset] = $value; $name = substr($this->__currentAccess, 1); $attribute =& $this->getAttribute($name); if (false !== $attribute['comment']) { if (null === $attribute['contract']) { $attribute['contract'] = \Hoa\Praspel::interpret($attribute['comment']); } $verdict = $attribute['contract']->getClause('invariant')->getVariable($name)->predicate($this->{$this->__currentAccess}); } else { $verdict = true; } if (false === $verdict) { if (null !== $oldOffset) { $this->{$this->__currentAccess}[$offset] = $oldOffset; } else { unset($this->{$this->__currentAccess}[$offset]); } throw new Exception('Try to set the %s attribute with an invalid data.', 2, $name); } if (null !== ($validator = $attribute['validator']) && false === $this->{$validator}($value)) { if (null !== $oldOffset) { $this->{$this->__currentAccess}[$offset] = $oldOffset; } throw new Exception('Try to set the %s attribute with an invalid data.', 3, $name); } return $this->__currentAccess = null; }