Hoa central
Runtime.php
Go to the documentation of this file.
1 <?php
2 
38 
39 use Hoa\Praspel;
40 
50 {
56  protected $_visitorPraspel = null;
57 
58 
59 
68  public function evaluate(&$trace = false)
69  {
70  // Start.
71  $registry = Praspel::getRegistry();
72  $verdict = true;
73  $callable = $this->getCallable();
74  $reflection = $callable->getReflection();
75  $specification = $this->getSpecification();
76  $exceptions = new Praspel\Exception\Group(
77  'The Runtime Assertion Checker has detected failures for %s.',
78  0,
79  $callable
80  );
81  $classname = null;
82  $isConstructor = false;
83 
84  if ($reflection instanceof \ReflectionMethod) {
85  $reflection->setAccessible(true);
86 
87  if ('__construct' === $reflection->getName()) {
88  $isConstructor = true;
89  }
90 
91  if (false === $reflection->isStatic()) {
92  $_callback = $callable->getValidCallback();
93  $_object = $_callback[0];
94  $specification->getImplicitVariable('this')->bindTo($_object);
95  }
96 
97  $classname = $reflection->getDeclaringClass()->getName();
98  }
99 
100  if (false !== $trace && !($trace instanceof Trace)) {
101  $trace = new Praspel\Trace();
102  }
103 
104  // Prepare data.
105  if (null === $data = $this->getData()) {
106  if (true === $this->canGenerateData()) {
107  $data = static::generateData($specification);
108  $this->setData($data);
109  } else {
111  'No data were given. The System Under Test %s needs data ' .
112  'to be executed.',
113  1,
114  $callable
115  );
116  }
117  }
118 
119  $arguments = $this->getArgumentData(
120  $reflection,
121  $data,
122  $numberOfRequiredArguments
123  );
124 
125  // Check invariant.
126  $invariant = $specification->getClause('invariant');
127  $attributes = $this->getAttributeData($callable);
128 
129  foreach ($attributes as $name => $_) {
130  $entryName = $classname . '::$' . $name;
131 
132  if (!isset($registry[$entryName])) {
133  continue;
134  }
135 
136  $entry = $registry[$entryName];
137 
138  if (true === $entry->clauseExists('invariant')) {
139  foreach ($entry->getClause('invariant') as $variable) {
140  $invariant->addVariable($variable->getName(), $variable);
141  }
142  }
143  }
144 
145  if (false === $isConstructor) {
146  $verdict &= $this->checkClause(
147  $invariant,
148  $attributes,
149  $exceptions,
150  'Hoa\Praspel\Exception\Failure\Invariant',
151  true,
152  $trace
153  );
154 
155  if (0 < count($exceptions)) {
156  throw $exceptions;
157  }
158  }
159 
160  // Check requires and behaviors.
161  $behavior = $specification;
162  $verdict &= $this->checkBehavior(
163  $behavior,
164  $arguments,
165  $exceptions,
166  true,
167  $trace
168  );
169 
170  if (0 < count($exceptions)) {
171  throw $exceptions;
172  }
173 
174  $rootBehavior = $behavior instanceof Praspel\Model\Specification;
175  $numberOfArguments = count($arguments);
176 
177  if ($numberOfArguments < $numberOfRequiredArguments) {
178  $exceptions[] = new Praspel\Exception\Failure\Precondition(
179  'Callable %s needs %d arguments; %d given.',
180  2,
181  [$callable, $numberOfRequiredArguments, $numberOfArguments]
182  );
183 
184  throw $exceptions;
185  }
186 
187  $_exceptions =
188  true === $rootBehavior
189  ? $exceptions
191  'Behavior %s is broken.',
192  3,
193  $behavior->getIdentifier()
194  );
195 
196  try {
197  // Invoke.
198  $return = $this->invoke(
199  $callable,
200  $reflection,
201  $arguments,
202  $isConstructor
203  );
204  $arguments['\result'] = $return;
205 
206  // Check normal postcondition.
207  if (true === $behavior->clauseExists('ensures')) {
208  $ensures = $behavior->getClause('ensures');
209  $verdict &= $this->checkClause(
210  $ensures,
211  $arguments,
212  $_exceptions,
213  'Hoa\Praspel\Exception\Failure\Postcondition',
214  false,
215  $trace
216  );
217  }
218  } catch (Praspel\Exception $internalException) {
220  'The System Under Test has broken an internal contract.',
221  4,
222  null,
223  $internalException
224  );
225  } catch (\Exception $exception) {
226  $arguments['\result'] = $exception;
227 
228  // Check exceptional postcondition.
229  if (true === $behavior->clauseExists('throwable')) {
230  $throwable = $behavior->getClause('throwable');
231  $verdict &= $this->checkExceptionalClause(
232  $throwable,
233  $arguments
234  );
235 
236  if (false == $verdict) {
237  $_exceptions[] = new Praspel\Exception\Failure\Exceptional(
238  'The exception %s has been unexpectedly thrown.',
239  5,
240  get_class($arguments['\result']),
241  $exception
242  );
243  }
244  } else {
245  $verdict &= false;
246  $_exceptions[] = new Praspel\Exception\Failure\Exceptional(
247  'The System Under Test cannot terminate exceptionally ' .
248  'because no exceptional postcondition has been specified ' .
249  '(there is no @throwable clause).',
250  6,
251  [],
252  $exception
253  );
254  }
255  }
256 
257  if (0 < count($_exceptions) &&
258  false === $rootBehavior) {
259  $_behavior = $behavior;
260 
261  while (
262  (null !== $_behavior = $_behavior->getParent()) &&
263  !($_behavior instanceof Praspel\Model\Specification)
264  ) {
265  $handle = new Praspel\Exception\Group(
266  'Behavior %s is broken.',
267  7,
268  $_behavior->getIdentifier()
269  );
270  $handle[] = $_exceptions;
271  $_exceptions = $handle;
272  }
273 
274  $exceptions[] = $_exceptions;
275  }
276 
277  if (0 < count($exceptions)) {
278  throw $exceptions;
279  }
280 
281  // Check invariant.
282  $attributes = $this->getAttributeData($callable);
283  $verdict &= $this->checkClause(
284  $invariant,
285  $attributes,
286  $exceptions,
287  'Hoa\Praspel\Exception\Failure\Invariant',
288  true,
289  $trace
290  );
291 
292  if (0 < count($exceptions)) {
293  throw $exceptions;
294  }
295 
296  return (bool) $verdict;
297  }
298 
309  protected function getArgumentData(
310  \ReflectionFunctionAbstract $reflection,
311  Array &$data,
312  &$numberOfRequiredArguments
313  ) {
314  $arguments = [];
315  $numberOfRequiredArguments = 0;
316 
317  foreach ($reflection->getParameters() as $parameter) {
318  $name = $parameter->getName();
319 
320  if (true === array_key_exists($name, $data)) {
321  $arguments[$name] = &$data[$name];
322 
323  if (false === $parameter->isOptional()) {
324  ++$numberOfRequiredArguments;
325  }
326 
327  continue;
328  }
329 
330  if (false === $parameter->isOptional()) {
331  ++$numberOfRequiredArguments;
332 
333  // Let the error be caught by a @requires clause.
334  continue;
335  }
336 
337  $arguments[$name] = $parameter->getDefaultValue();
338  }
339 
340  return $arguments;
341  }
342 
349  protected function getAttributeData(Core\Consistency\Xcallable $callable)
350  {
351  $callback = $callable->getValidCallback();
352 
353  if ($callback instanceof \Closure) {
354  return [];
355  }
356 
357  $object = $callback[0];
358 
359  if (!is_object($object)) {
360  return [];
361  }
362 
363  $reflectionObject = new \ReflectionObject($object);
364  $attributes = [];
365 
366  foreach ($reflectionObject->getProperties() as $property) {
367  $property->setAccessible(true);
368  $attributes[$property->getName()] = $property->getValue($object);
369  }
370 
371  return $attributes;
372  }
373 
387  protected function invoke(
388  Core\Consistency\Xcallable &$callable,
389  \ReflectionFunctionAbstract &$reflection,
390  Array &$arguments,
391  $isConstructor
392  ) {
393  if ($reflection instanceof \ReflectionFunction) {
394  return $reflection->invokeArgs($arguments);
395  }
396 
397  if (false === $isConstructor) {
398  $_callback = $callable->getValidCallback();
399  $_object = $_callback[0];
400 
401  return $reflection->invokeArgs($_object, $arguments);
402  }
403 
404  $class = $reflection->getDeclaringClass();
405  $instance = $class->newInstanceArgs($arguments);
406  $callable = xcallable($instance, '__construct');
407  $reflection = $callable->getReflection();
408 
409  return void;
410  }
411 
424  protected function checkBehavior(
425  Praspel\Model\Behavior &$behavior,
426  Array &$data,
427  Praspel\Exception\Group $exceptions,
428  $assign = false,
429  $trace = false
430  ) {
431  $verdict = true;
432 
433  // Check precondition.
434  if (true === $behavior->clauseExists('requires')) {
435  $requires = $behavior->getClause('requires');
436  $verdict = $this->checkClause(
437  $requires,
438  $data,
439  $exceptions,
440  'Hoa\Praspel\Exception\Failure\Precondition',
441  $assign,
442  $trace
443  );
444 
445  if (false === $verdict) {
446  return false;
447  }
448  }
449 
450  // Check behaviors.
451  if (true === $behavior->clauseExists('behavior')) {
452  $_verdict = false;
453  $behaviors = $behavior->getClause('behavior');
454  $exceptions->beginTransaction();
455 
456  foreach ($behaviors as $_behavior) {
457  $_exceptions = new Praspel\Exception\Group(
458  'Behavior %s is broken.',
459  8,
460  $_behavior->getIdentifier()
461  );
462 
463  $_trace = null;
464 
465  if (!empty($trace)) {
466  $_trace = new Praspel\Model\Behavior($trace);
467  $_trace->setIdentifier($_behavior->getIdentifier());
468  }
469 
470  $_verdict = $this->checkBehavior(
471  $_behavior,
472  $data,
473  $_exceptions,
474  $assign,
475  $_trace
476  );
477 
478  if (true === $_verdict) {
479  if (!empty($trace)) {
480  $trace->addClause($_trace);
481  }
482 
483  break;
484  }
485 
486  $exceptions[] = $_exceptions;
487  unset($_trace);
488  }
489 
490  if (false === $_verdict) {
491  if (true === $behavior->clauseExists('default')) {
492  $exceptions->rollbackTransaction();
493  $_verdict = true;
494  $behavior = $behavior->getClause('default');
495  } else {
496  $exceptions->commitTransaction();
497  }
498  } else {
499  $exceptions->rollbackTransaction();
500  $behavior = $_behavior;
501  }
502 
503  $verdict &= $_verdict;
504  }
505 
506  return (bool) $verdict;
507  }
508 
523  protected function checkClause(
524  Praspel\Model\Declaration $clause,
525  Array &$data,
526  \Hoa\Praspel\Exception\Group $exceptions,
527  $exception,
528  $assign = false,
529  $trace = false
530  ) {
531  $verdict = true;
532  $traceClause = null;
533 
534  if (!empty($trace)) {
535  $traceClause = clone $clause;
536  }
537 
538  foreach ($clause as $name => $variable) {
539  if (false === array_key_exists($name, $data)) {
540  $exceptions[] = new $exception(
541  'Variable %s in @%s is required and has no value.',
542  9,
543  [$name, $clause->getName()]
544  );
545 
546  continue;
547  }
548 
549  $datum = &$data[$name];
550  $_verdict = false;
551  $traceVariable = null;
552 
553  if (null !== $traceClause) {
554  $traceVariable = clone $variable;
555  $traceVariableDomains = $traceVariable->getDomains();
556  }
557 
558  $i = 0;
559 
560  foreach ($variable->getDomains() as $realdom) {
561  if (false === $_verdict && true === $realdom->predicate($datum)) {
562  $_verdict = true;
563  } elseif (null !== $traceClause) {
564  unset($traceVariableDomains[$i--]);
565  }
566 
567  ++$i;
568  }
569 
570  if (false === $_verdict) {
571  if (null !== $traceClause) {
572  unset($traceClause[$name]);
573  }
574 
575  $exceptions[] = new $exception(
576  'Variable %s does not verify the constraint @%s %s.',
577  10,
578  [
579  $name,
580  $clause->getName(),
581  $this->getVisitorPraspel()->visit($variable)
582  ]
583  );
584  } else {
585  if (true === $assign) {
586  $variable->setValue($datum);
587  }
588 
589  if (null !== $traceClause) {
590  unset($traceClause[$name]);
591  $traceClause->addVariable($name, $traceVariable);
592  }
593  }
594 
595  $verdict &= $_verdict;
596  }
597 
598  $predicateEvaluator = function ($__hoa_arguments, $__hoa_code) {
599  extract($__hoa_arguments);
600 
601  return true == eval('return ' . $__hoa_code . ';');
602  };
603 
604  foreach ($clause->getPredicates() as $predicate) {
605  $_predicate = $predicate;
606 
607  preg_match_all(
608  '#(?<!\\\)\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)#',
609  $_predicate,
610  $matches
611  );
612 
613  $predicateArguments = [];
614 
615  foreach ($matches[1] as $variable) {
616  if (true === array_key_exists($variable, $data)) {
617  $predicateArguments[$variable] = $data[$variable];
618  }
619  }
620 
621  if (false !== strpos($_predicate, '\result')) {
622  if (!($clause instanceof Praspel\Model\Ensures) &&
623  !($clause instanceof Praspel\Model\Throwable)) {
624  $verdict &= false;
625  $exceptions[] = new $exception(
626  'Illegal \result in the following predicate: %s.',
627  11,
628  $predicate
629  );
630 
631  continue;
632  }
633 
634  $placeholder = '__hoa_praspel_' . uniqid();
635  $_predicate = str_replace(
636  '\\result',
637  '$' . $placeholder,
638  $_predicate
639  );
640  $predicateArguments[$placeholder] = $data['\result'];
641  }
642 
643  $_verdict = $predicateEvaluator($predicateArguments, $_predicate);
644 
645  if (false === $_verdict) {
646  $exceptions[] = new $exception(
647  'Violation of the following predicate: %s.',
648  11,
649  $predicate
650  );
651  }
652 
653  $verdict &= $_verdict;
654  }
655 
656  if (!empty($trace)) {
657  $trace->addClause($traceClause);
658  }
659 
660  return (bool) $verdict;
661  }
662 
671  protected function checkExceptionalClause(
672  Praspel\Model\Throwable $clause,
673  Array &$data
674  ) {
675  $verdict = false;
676 
677  foreach ($clause as $identifier) {
678  $_exception = $clause[$identifier];
679  $instanceName = $_exception->getInstanceName();
680 
681  if ($data['\result'] instanceof $instanceName) {
682  $verdict = true;
683 
684  break;
685  }
686 
687  foreach ((array) $_exception->getDisjunction() as $_identifier) {
688  $__exception = $clause[$_identifier];
689  $_instanceName = $__exception->getInstanceName();
690 
691  if ($exception instanceof $_instanceName) {
692  $verdict = true;
693 
694  break;
695  }
696  }
697  }
698 
699  return $verdict;
700  }
701 }
getArgumentData(\ReflectionFunctionAbstract $reflection, Array &$data, &$numberOfRequiredArguments)
Definition: Runtime.php:309
invoke(Core\Consistency\Xcallable &$callable,\ReflectionFunctionAbstract &$reflection, Array &$arguments, $isConstructor)
Definition: Runtime.php:387
static getRegistry()
Definition: Praspel.php:121
getAttributeData(Core\Consistency\Xcallable $callable)
Definition: Runtime.php:349
checkExceptionalClause(Praspel\Model\Throwable $clause, Array &$data)
Definition: Runtime.php:671
checkClause(Praspel\Model\Declaration $clause, Array &$data,\Hoa\Praspel\Exception\Group $exceptions, $exception, $assign=false, $trace=false)
Definition: Runtime.php:523
checkBehavior(Praspel\Model\Behavior &$behavior, Array &$data, Praspel\Exception\Group $exceptions, $assign=false, $trace=false)
Definition: Runtime.php:424