AggregateInvoker.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\App\Utility;
  7. /**
  8. * Runs given callback across given array of data and collects all PhpUnit assertion results.
  9. * Should be used in case data provider is huge to minimize overhead.
  10. */
  11. class AggregateInvoker
  12. {
  13. /**
  14. * @var \PHPUnit\Framework\TestCase
  15. */
  16. protected $_testCase;
  17. /**
  18. * There is no PHPUnit internal API to determine whether --verbose or --debug options are passed.
  19. * When verbose is true, data sets are gathered for any result, includind incomplete and skipped test.
  20. * Only data sets for failed assertions are gathered otherwise.
  21. *
  22. * @var array
  23. */
  24. protected $_options = ['verbose' => false];
  25. /**
  26. * @param \PHPUnit\Framework\TestCase $testCase
  27. * @param array $options
  28. */
  29. public function __construct($testCase, array $options = [])
  30. {
  31. $this->_testCase = $testCase;
  32. $this->_options = $options + $this->_options;
  33. }
  34. /**
  35. * Collect all failed assertions and fail test in case such list is not empty.
  36. * Incomplete and skipped test results are aggregated as well.
  37. *
  38. * @param callable $callback
  39. * @param array[] $dataSource
  40. * @return void
  41. */
  42. public function __invoke(callable $callback, array $dataSource)
  43. {
  44. $results = [
  45. \PHPUnit\Framework\IncompleteTestError::class => [],
  46. \PHPUnit\Framework\SkippedTestError::class => [],
  47. \PHPUnit\Framework\AssertionFailedError::class => [],
  48. ];
  49. $passed = 0;
  50. foreach ($dataSource as $dataSetName => $dataSet) {
  51. try {
  52. call_user_func_array($callback, $dataSet);
  53. $passed++;
  54. } catch (\PHPUnit\Framework\IncompleteTestError $exception) {
  55. $results[get_class($exception)][] = $this->prepareMessage($exception, $dataSetName, $dataSet);
  56. } catch (\PHPUnit\Framework\SkippedTestError $exception) {
  57. $results[get_class($exception)][] = $this->prepareMessage($exception, $dataSetName, $dataSet);
  58. } catch (\PHPUnit\Framework\AssertionFailedError $exception) {
  59. $results[\PHPUnit\Framework\AssertionFailedError::class][] = $this->prepareMessage(
  60. $exception,
  61. $dataSetName,
  62. $dataSet
  63. );
  64. }
  65. }
  66. $this->processResults($results, $passed);
  67. }
  68. /**
  69. * @param \Exception $exception
  70. * @param string $dataSetName
  71. * @param mixed $dataSet
  72. * @return string
  73. */
  74. protected function prepareMessage(\Exception $exception, $dataSetName, $dataSet)
  75. {
  76. if (!is_string($dataSetName)) {
  77. $dataSetName = var_export($dataSet, true);
  78. }
  79. if ($exception instanceof \PHPUnit\Framework\AssertionFailedError
  80. && !$exception instanceof \PHPUnit\Framework\IncompleteTestError
  81. && !$exception instanceof \PHPUnit\Framework\SkippedTestError
  82. || $this->_options['verbose']) {
  83. $dataSetName = 'Data set: ' . $dataSetName . PHP_EOL;
  84. } else {
  85. $dataSetName = '';
  86. }
  87. return $dataSetName . $exception->getMessage() . PHP_EOL
  88. . \PHPUnit\Util\Filter::getFilteredStacktrace($exception);
  89. }
  90. /**
  91. * Analyze results of aggregated tests execution and complete test case appropriately
  92. *
  93. * @param array $results
  94. * @param int $passed
  95. * @return void
  96. */
  97. protected function processResults(array $results, $passed)
  98. {
  99. $totalCountsMessage = sprintf(
  100. 'Passed: %d, Failed: %d, Incomplete: %d, Skipped: %d.',
  101. $passed,
  102. count($results[\PHPUnit\Framework\AssertionFailedError::class]),
  103. count($results[\PHPUnit\Framework\IncompleteTestError::class]),
  104. count($results[\PHPUnit\Framework\SkippedTestError::class])
  105. );
  106. if ($results[\PHPUnit\Framework\AssertionFailedError::class]) {
  107. $this->_testCase->fail(
  108. $totalCountsMessage . PHP_EOL .
  109. implode(PHP_EOL, $results[\PHPUnit\Framework\AssertionFailedError::class])
  110. );
  111. }
  112. if (!$results[\PHPUnit\Framework\IncompleteTestError::class] &&
  113. !$results[\PHPUnit\Framework\SkippedTestError::class]) {
  114. return;
  115. }
  116. $message = $totalCountsMessage . PHP_EOL . implode(
  117. PHP_EOL,
  118. $results[\PHPUnit\Framework\IncompleteTestError::class]
  119. ) . PHP_EOL . implode(
  120. PHP_EOL,
  121. $results[\PHPUnit\Framework\SkippedTestError::class]
  122. );
  123. if ($results[\PHPUnit\Framework\IncompleteTestError::class]) {
  124. $this->_testCase->markTestIncomplete($message);
  125. } elseif ($results[\PHPUnit\Framework\SkippedTestError::class]) {
  126. $this->_testCase->markTestSkipped($message);
  127. }
  128. }
  129. }