AggregateInvoker.php 4.91 KB
Newer Older
Ketan's avatar
Ketan committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Magento\Framework\App\Utility;

/**
 * Runs given callback across given array of data and collects all PhpUnit assertion results.
 * Should be used in case data provider is huge to minimize overhead.
 */
class AggregateInvoker
{
    /**
     * @var \PHPUnit\Framework\TestCase
     */
    protected $_testCase;

    /**
     * There is no PHPUnit internal API to determine whether --verbose or --debug options are passed.
     * When verbose is true, data sets are gathered for any result, includind incomplete and skipped test.
     * Only data sets for failed assertions are gathered otherwise.
     *
     * @var array
     */
    protected $_options = ['verbose' => false];

    /**
     * @param \PHPUnit\Framework\TestCase $testCase
     * @param array $options
     */
    public function __construct($testCase, array $options = [])
    {
        $this->_testCase = $testCase;
        $this->_options = $options + $this->_options;
    }

    /**
     * Collect all failed assertions and fail test in case such list is not empty.
     * Incomplete and skipped test results are aggregated as well.
     *
     * @param callable $callback
     * @param array[] $dataSource
     * @return void
     */
    public function __invoke(callable $callback, array $dataSource)
    {
        $results = [
            \PHPUnit\Framework\IncompleteTestError::class => [],
            \PHPUnit\Framework\SkippedTestError::class => [],
            \PHPUnit\Framework\AssertionFailedError::class => [],
        ];
        $passed = 0;
        foreach ($dataSource as $dataSetName => $dataSet) {
            try {
                call_user_func_array($callback, $dataSet);
                $passed++;
            } catch (\PHPUnit\Framework\IncompleteTestError $exception) {
                $results[get_class($exception)][] = $this->prepareMessage($exception, $dataSetName, $dataSet);
            } catch (\PHPUnit\Framework\SkippedTestError $exception) {
                $results[get_class($exception)][] = $this->prepareMessage($exception, $dataSetName, $dataSet);
            } catch (\PHPUnit\Framework\AssertionFailedError $exception) {
                $results[\PHPUnit\Framework\AssertionFailedError::class][] = $this->prepareMessage(
                    $exception,
                    $dataSetName,
                    $dataSet
                );
            }
        }
        $this->processResults($results, $passed);
    }

    /**
     * @param \Exception $exception
     * @param string $dataSetName
     * @param mixed $dataSet
     * @return string
     */
    protected function prepareMessage(\Exception $exception, $dataSetName, $dataSet)
    {
        if (!is_string($dataSetName)) {
            $dataSetName = var_export($dataSet, true);
        }
        if ($exception instanceof \PHPUnit\Framework\AssertionFailedError
            && !$exception instanceof \PHPUnit\Framework\IncompleteTestError
            && !$exception instanceof \PHPUnit\Framework\SkippedTestError
            || $this->_options['verbose']) {
            $dataSetName = 'Data set: ' . $dataSetName . PHP_EOL;
        } else {
            $dataSetName = '';
        }
        return $dataSetName . $exception->getMessage() . PHP_EOL
        . \PHPUnit\Util\Filter::getFilteredStacktrace($exception);
    }

    /**
     * Analyze results of aggregated tests execution and complete test case appropriately
     *
     * @param array $results
     * @param int $passed
     * @return void
     */
    protected function processResults(array $results, $passed)
    {
        $totalCountsMessage = sprintf(
            'Passed: %d, Failed: %d, Incomplete: %d, Skipped: %d.',
            $passed,
            count($results[\PHPUnit\Framework\AssertionFailedError::class]),
            count($results[\PHPUnit\Framework\IncompleteTestError::class]),
            count($results[\PHPUnit\Framework\SkippedTestError::class])
        );
        if ($results[\PHPUnit\Framework\AssertionFailedError::class]) {
            $this->_testCase->fail(
                $totalCountsMessage . PHP_EOL .
                implode(PHP_EOL, $results[\PHPUnit\Framework\AssertionFailedError::class])
            );
        }
        if (!$results[\PHPUnit\Framework\IncompleteTestError::class] &&
            !$results[\PHPUnit\Framework\SkippedTestError::class]) {
            return;
        }
        $message = $totalCountsMessage . PHP_EOL . implode(
            PHP_EOL,
            $results[\PHPUnit\Framework\IncompleteTestError::class]
        ) . PHP_EOL . implode(
            PHP_EOL,
            $results[\PHPUnit\Framework\SkippedTestError::class]
        );
        if ($results[\PHPUnit\Framework\IncompleteTestError::class]) {
            $this->_testCase->markTestIncomplete($message);
        } elseif ($results[\PHPUnit\Framework\SkippedTestError::class]) {
            $this->_testCase->markTestSkipped($message);
        }
    }
}