Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 621 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php declare(strict_types=1);
2
/*
3
 * This file is part of PHPUnit.
4
 *
5
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace PHPUnit\Util\TestDox;
11
 
12
use const PHP_EOL;
13
use function array_map;
14
use function ceil;
15
use function count;
16
use function explode;
17
use function get_class;
18
use function implode;
19
use function preg_match;
20
use function sprintf;
21
use function strlen;
22
use function strpos;
23
use function trim;
24
use PHPUnit\Framework\Test;
25
use PHPUnit\Framework\TestCase;
26
use PHPUnit\Framework\TestResult;
27
use PHPUnit\Runner\BaseTestRunner;
28
use PHPUnit\Runner\PhptTestCase;
29
use PHPUnit\Util\Color;
30
use SebastianBergmann\Timer\ResourceUsageFormatter;
31
use SebastianBergmann\Timer\Timer;
32
use Throwable;
33
 
34
/**
35
 * @internal This class is not covered by the backward compatibility promise for PHPUnit
36
 */
37
class CliTestDoxPrinter extends TestDoxPrinter
38
{
39
    /**
40
     * The default Testdox left margin for messages is a vertical line.
41
     */
42
    private const PREFIX_SIMPLE = [
43
        'default' => '│',
44
        'start'   => '│',
45
        'message' => '│',
46
        'diff'    => '│',
47
        'trace'   => '│',
48
        'last'    => '│',
49
    ];
50
 
51
    /**
52
     * Colored Testdox use box-drawing for a more textured map of the message.
53
     */
54
    private const PREFIX_DECORATED = [
55
        'default' => '│',
56
        'start'   => '┐',
57
        'message' => '├',
58
        'diff'    => '┊',
59
        'trace'   => '╵',
60
        'last'    => 'â”´',
61
    ];
62
 
63
    private const SPINNER_ICONS = [
64
        " \e[36m◐\e[0m running tests",
65
        " \e[36mâ—“\e[0m running tests",
66
        " \e[36mâ—‘\e[0m running tests",
67
        " \e[36mâ—’\e[0m running tests",
68
    ];
69
    private const STATUS_STYLES = [
621 lars 70
        BaseTestRunner::STATUS_PASSED => [
148 lars 71
            'symbol' => '✔',
72
            'color'  => 'fg-green',
73
        ],
621 lars 74
        BaseTestRunner::STATUS_ERROR => [
148 lars 75
            'symbol'  => '✘',
76
            'color'   => 'fg-yellow',
77
            'message' => 'bg-yellow,fg-black',
78
        ],
621 lars 79
        BaseTestRunner::STATUS_FAILURE => [
148 lars 80
            'symbol'  => '✘',
81
            'color'   => 'fg-red',
82
            'message' => 'bg-red,fg-white',
83
        ],
621 lars 84
        BaseTestRunner::STATUS_SKIPPED => [
148 lars 85
            'symbol'  => '↩',
86
            'color'   => 'fg-cyan',
87
            'message' => 'fg-cyan',
88
        ],
621 lars 89
        BaseTestRunner::STATUS_RISKY => [
148 lars 90
            'symbol'  => '☢',
91
            'color'   => 'fg-yellow',
92
            'message' => 'fg-yellow',
93
        ],
94
        BaseTestRunner::STATUS_INCOMPLETE => [
95
            'symbol'  => '∅',
96
            'color'   => 'fg-yellow',
97
            'message' => 'fg-yellow',
98
        ],
621 lars 99
        BaseTestRunner::STATUS_WARNING => [
148 lars 100
            'symbol'  => '⚠',
101
            'color'   => 'fg-yellow',
102
            'message' => 'fg-yellow',
103
        ],
621 lars 104
        BaseTestRunner::STATUS_UNKNOWN => [
148 lars 105
            'symbol'  => '?',
106
            'color'   => 'fg-blue',
107
            'message' => 'fg-white,bg-blue',
108
        ],
109
    ];
110
 
111
    /**
112
     * @var int[]
113
     */
114
    private $nonSuccessfulTestResults = [];
115
 
116
    /**
117
     * @var Timer
118
     */
119
    private $timer;
120
 
121
    /**
122
     * @param null|resource|string $out
123
     * @param int|string           $numberOfColumns
124
     *
125
     * @throws \PHPUnit\Framework\Exception
126
     */
127
    public function __construct($out = null, bool $verbose = false, string $colors = self::COLOR_DEFAULT, bool $debug = false, $numberOfColumns = 80, bool $reverse = false)
128
    {
129
        parent::__construct($out, $verbose, $colors, $debug, $numberOfColumns, $reverse);
130
 
131
        $this->timer = new Timer;
132
 
133
        $this->timer->start();
134
    }
135
 
136
    public function printResult(TestResult $result): void
137
    {
138
        $this->printHeader($result);
139
 
140
        $this->printNonSuccessfulTestsSummary($result->count());
141
 
142
        $this->printFooter($result);
143
    }
144
 
145
    protected function printHeader(TestResult $result): void
146
    {
147
        $this->write("\n" . (new ResourceUsageFormatter)->resourceUsage($this->timer->stop()) . "\n\n");
148
    }
149
 
150
    protected function formatClassName(Test $test): string
151
    {
152
        if ($test instanceof TestCase) {
153
            return $this->prettifier->prettifyTestClass(get_class($test));
154
        }
155
 
156
        return get_class($test);
157
    }
158
 
159
    /**
160
     * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
161
     */
162
    protected function registerTestResult(Test $test, ?Throwable $t, int $status, float $time, bool $verbose): void
163
    {
164
        if ($status !== BaseTestRunner::STATUS_PASSED) {
165
            $this->nonSuccessfulTestResults[] = $this->testIndex;
166
        }
167
 
168
        parent::registerTestResult($test, $t, $status, $time, $verbose);
169
    }
170
 
171
    /**
172
     * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
173
     */
174
    protected function formatTestName(Test $test): string
175
    {
176
        if ($test instanceof TestCase) {
177
            return $this->prettifier->prettifyTestCase($test);
178
        }
179
 
180
        return parent::formatTestName($test);
181
    }
182
 
183
    protected function writeTestResult(array $prevResult, array $result): void
184
    {
185
        // spacer line for new suite headers and after verbose messages
186
        if ($prevResult['testName'] !== '' &&
187
            (!empty($prevResult['message']) || $prevResult['className'] !== $result['className'])) {
188
            $this->write(PHP_EOL);
189
        }
190
 
191
        // suite header
192
        if ($prevResult['className'] !== $result['className']) {
193
            $this->write($this->colorizeTextBox('underlined', $result['className']) . PHP_EOL);
194
        }
195
 
196
        // test result line
197
        if ($this->colors && $result['className'] === PhptTestCase::class) {
198
            $testName = Color::colorizePath($result['testName'], $prevResult['testName'], true);
199
        } else {
200
            $testName = $result['testMethod'];
201
        }
202
 
203
        $style = self::STATUS_STYLES[$result['status']];
204
        $line  = sprintf(
205
            ' %s %s%s' . PHP_EOL,
206
            $this->colorizeTextBox($style['color'], $style['symbol']),
207
            $testName,
208
            $this->verbose ? ' ' . $this->formatRuntime($result['time'], $style['color']) : ''
209
        );
210
 
211
        $this->write($line);
212
 
213
        // additional information when verbose
214
        $this->write($result['message']);
215
    }
216
 
217
    protected function formatThrowable(Throwable $t, ?int $status = null): string
218
    {
219
        return trim(\PHPUnit\Framework\TestFailure::exceptionToString($t));
220
    }
221
 
222
    protected function colorizeMessageAndDiff(string $style, string $buffer): array
223
    {
224
        $lines      = $buffer ? array_map('\rtrim', explode(PHP_EOL, $buffer)) : [];
225
        $message    = [];
226
        $diff       = [];
227
        $insideDiff = false;
228
 
229
        foreach ($lines as $line) {
230
            if ($line === '--- Expected') {
231
                $insideDiff = true;
232
            }
233
 
234
            if (!$insideDiff) {
235
                $message[] = $line;
236
            } else {
237
                if (strpos($line, '-') === 0) {
238
                    $line = Color::colorize('fg-red', Color::visualizeWhitespace($line, true));
239
                } elseif (strpos($line, '+') === 0) {
240
                    $line = Color::colorize('fg-green', Color::visualizeWhitespace($line, true));
241
                } elseif ($line === '@@ @@') {
242
                    $line = Color::colorize('fg-cyan', $line);
243
                }
244
                $diff[] = $line;
245
            }
246
        }
247
        $diff = implode(PHP_EOL, $diff);
248
 
249
        if (!empty($message)) {
250
            $message = $this->colorizeTextBox($style, implode(PHP_EOL, $message));
251
        }
252
 
253
        return [$message, $diff];
254
    }
255
 
256
    protected function formatStacktrace(Throwable $t): string
257
    {
258
        $trace = \PHPUnit\Util\Filter::getFilteredStacktrace($t);
259
 
260
        if (!$this->colors) {
261
            return $trace;
262
        }
263
 
264
        $lines    = [];
265
        $prevPath = '';
266
 
267
        foreach (explode(PHP_EOL, $trace) as $line) {
268
            if (preg_match('/^(.*):(\d+)$/', $line, $matches)) {
269
                $lines[] = Color::colorizePath($matches[1], $prevPath) .
270
                    Color::dim(':') .
271
                    Color::colorize('fg-blue', $matches[2]) .
272
                    "\n";
273
                $prevPath = $matches[1];
274
            } else {
275
                $lines[]  = $line;
276
                $prevPath = '';
277
            }
278
        }
279
 
280
        return implode('', $lines);
281
    }
282
 
283
    protected function formatTestResultMessage(Throwable $t, array $result, ?string $prefix = null): string
284
    {
285
        $message = $this->formatThrowable($t, $result['status']);
286
        $diff    = '';
287
 
288
        if (!($this->verbose || $result['verbose'])) {
289
            return '';
290
        }
291
 
292
        if ($message && $this->colors) {
293
            $style            = self::STATUS_STYLES[$result['status']]['message'] ?? '';
294
            [$message, $diff] = $this->colorizeMessageAndDiff($style, $message);
295
        }
296
 
297
        if ($prefix === null || !$this->colors) {
298
            $prefix = self::PREFIX_SIMPLE;
299
        }
300
 
301
        if ($this->colors) {
302
            $color  = self::STATUS_STYLES[$result['status']]['color'] ?? '';
303
            $prefix = array_map(static function ($p) use ($color)
304
            {
305
                return Color::colorize($color, $p);
306
            }, self::PREFIX_DECORATED);
307
        }
308
 
309
        $trace = $this->formatStacktrace($t);
310
        $out   = $this->prefixLines($prefix['start'], PHP_EOL) . PHP_EOL;
311
 
312
        if ($message) {
313
            $out .= $this->prefixLines($prefix['message'], $message . PHP_EOL) . PHP_EOL;
314
        }
315
 
316
        if ($diff) {
317
            $out .= $this->prefixLines($prefix['diff'], $diff . PHP_EOL) . PHP_EOL;
318
        }
319
 
320
        if ($trace) {
321
            if ($message || $diff) {
322
                $out .= $this->prefixLines($prefix['default'], PHP_EOL) . PHP_EOL;
323
            }
324
            $out .= $this->prefixLines($prefix['trace'], $trace . PHP_EOL) . PHP_EOL;
325
        }
326
        $out .= $this->prefixLines($prefix['last'], PHP_EOL) . PHP_EOL;
327
 
328
        return $out;
329
    }
330
 
331
    protected function drawSpinner(): void
332
    {
333
        if ($this->colors) {
334
            $id = $this->spinState % count(self::SPINNER_ICONS);
335
            $this->write(self::SPINNER_ICONS[$id]);
336
        }
337
    }
338
 
339
    protected function undrawSpinner(): void
340
    {
341
        if ($this->colors) {
342
            $id = $this->spinState % count(self::SPINNER_ICONS);
343
            $this->write("\e[1K\e[" . strlen(self::SPINNER_ICONS[$id]) . 'D');
344
        }
345
    }
346
 
347
    private function formatRuntime(float $time, string $color = ''): string
348
    {
349
        if (!$this->colors) {
350
            return sprintf('[%.2f ms]', $time * 1000);
351
        }
352
 
353
        if ($time > 1) {
354
            $color = 'fg-magenta';
355
        }
356
 
357
        return Color::colorize($color, ' ' . (int) ceil($time * 1000) . ' ' . Color::dim('ms'));
358
    }
359
 
360
    private function printNonSuccessfulTestsSummary(int $numberOfExecutedTests): void
361
    {
362
        if (empty($this->nonSuccessfulTestResults)) {
363
            return;
364
        }
365
 
366
        if ((count($this->nonSuccessfulTestResults) / $numberOfExecutedTests) >= 0.7) {
367
            return;
368
        }
369
 
370
        $this->write("Summary of non-successful tests:\n\n");
371
 
372
        $prevResult = $this->getEmptyTestResult();
373
 
374
        foreach ($this->nonSuccessfulTestResults as $testIndex) {
375
            $result = $this->testResults[$testIndex];
376
            $this->writeTestResult($prevResult, $result);
377
            $prevResult = $result;
378
        }
379
    }
380
}