AbstractBaseReporter::footer()   A
last analyzed

Complexity

Conditions 4
Paths 8

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 8
nop 0
dl 0
loc 18
rs 9.6666
c 0
b 0
f 0
1
<?php
2
namespace Peridot\Reporter;
3
4
use Evenement\EventEmitterInterface;
5
use Peridot\Configuration;
6
use Peridot\Core\HasEventEmitterTrait;
7
use Peridot\Core\Test;
8
use Peridot\Core\TestInterface;
9
use Peridot\Core\TestResult;
10
use SebastianBergmann\Timer\Timer;
11
use Symfony\Component\Console\Output\OutputInterface;
12
use Symfony\Component\Console\Output\StreamOutput;
13
14
/**
15
 * The base class for all Peridot reporters. Sits on top of an OutputInterface
16
 * and an EventEmitter in order to report Peridot results.
17
 *
18
 * @package Peridot\Reporter
19
 */
20
abstract class AbstractBaseReporter implements ReporterInterface
21
{
22
    use HasEventEmitterTrait;
23
24
    /**
25
     * @var \Peridot\Configuration
26
     */
27
    protected $configuration;
28
29
    /**
30
     * @var \Symfony\Component\Console\Output\OutputInterface
31
     */
32
    protected $output;
33
34
    /**
35
     * @var array
36
     */
37
    protected $errors = [];
38
39
    /**
40
     * @var int
41
     */
42
    protected $passing = 0;
43
44
    /**
45
     * @var int
46
     */
47
    protected $pending = 0;
48
49
    /**
50
     * @var double|integer
51
     */
52
    protected $time;
53
54
    /**
55
     * Maps color names to left and right color sequences.
56
     *
57
     * @var array
58
     */
59
    protected $colors = array(
60
        'white' => ['left' => "\033[37m", 'right' => "\033[39m"],
61
        'success' => ['left' => "\033[32m", 'right' => "\033[39m"],
62
        'error' => ['left' => "\033[31m", 'right' => "\033[39m"],
63
        'warning' => ['left' => "\033[33m", 'right' => "\033[39m"],
64
        'muted' => ['left' => "\033[90m", 'right' => "\033[0m"],
65
        'pending' => ['left' => "\033[36m", 'right' => "\033[39m"],
66
    );
67
68
    /**
69
     * Maps symbol names to symbols
70
     *
71
     * @var array
72
     */
73
    protected $symbols = array(
74
        'check' => '✓'
75
    );
76
77
    /**
78
     * @param Configuration $configuration
79
     * @param OutputInterface $output
80
     * @param EventEmitterInterface $eventEmitter
81
     */
82
    public function __construct(
83
        Configuration $configuration,
84
        OutputInterface $output,
85
        EventEmitterInterface $eventEmitter
86
    ) {
87
        $this->configuration = $configuration;
88
        $this->output = $output;
89
        $this->eventEmitter = $eventEmitter;
90
91
        $this->registerSymbols();
92
93
        $this->registerEvents();
94
95
        $this->init();
96
    }
97
98
    /**
99
     * Given a color name, colorize the provided text in that
100
     * color
101
     *
102
     * @param $key
103
     * @param $text
104
     * @return string
105
     */
106
    public function color($key, $text)
107
    {
108
        $colorsEnabled = $this->configuration->areColorsEnabled() && $this->hasColorSupport();
109
        $colorsEnabledExplicit = $this->configuration->areColorsEnabledExplicit();
110
111
        if (!$colorsEnabled && !$colorsEnabledExplicit) {
112
            return $text;
113
        }
114
115
        $color = $this->colors[$key];
116
117
        return sprintf("%s%s%s", $color['left'], $text, $color['right']);
118
    }
119
120
    /**
121
     * Fetch a symbol by name
122
     *
123
     * @param $name
124
     * @return string
125
     */
126
    public function symbol($name)
127
    {
128
        return $this->symbols[$name];
129
    }
130
131
    /**
132
     * Return the OutputInterface associated with the Reporter
133
     *
134
     * @return \Symfony\Component\Console\Output\OutputInterface
135
     */
136
    public function getOutput()
137
    {
138
        return $this->output;
139
    }
140
141
    /**
142
     * Return the Configuration associated with the Reporter
143
     *
144
     * @return \Peridot\Configuration
145
     */
146
    public function getConfiguration()
147
    {
148
        return $this->configuration;
149
    }
150
151
    /**
152
     * Set the run time to report.
153
     *
154
     * @param float $time
155
     */
156
    public function setTime($time)
157
    {
158
        $this->time = $time;
159
    }
160
161
    /**
162
     * Get the run time to report.
163
     *
164
     * @return float
165
     */
166
    public function getTime()
167
    {
168
        return (float)$this->time;
169
    }
170
171
    /**
172
     * Output result footer
173
     */
174
    public function footer()
175
    {
176
        $this->output->write($this->color('success', sprintf("\n  %d passing", $this->passing)));
177
        $this->output->writeln(sprintf($this->color('muted', " (%s)"), Timer::secondsToTimeString($this->getTime())));
178
        if (! empty($this->errors)) {
179
            $this->output->writeln($this->color('error', sprintf("  %d failing", count($this->errors))));
180
        }
181
        if ($this->pending) {
182
            $this->output->writeln($this->color('pending', sprintf("  %d pending", $this->pending)));
183
        }
184
        $this->output->writeln("");
185
        $errorCount = count($this->errors);
186
        for ($i = 0; $i < $errorCount; $i++) {
187
            list($test, $error) = $this->errors[$i];
188
            $this->outputError($i + 1, $test, $error);
189
            $this->output->writeln('');
190
        }
191
    }
192
193
    /**
194
     * Output result warnings
195
     *
196
     * @param TestResult $result
197
     */
198
    public function warnings(TestResult $result)
199
    {
200
        if ($result->isFocusedByDsl()) {
201
            $this->output->writeln($this->color('warning', 'WARNING: Tests have been focused programmatically.'));
202
        }
203
    }
204
205
    /**
206
     * Output a test failure.
207
     *
208
     * @param int $errorNumber
209
     * @param TestInterface $test
210
     * @param $exception - an exception like interface with ->getMessage(), ->getTrace()
211
     */
212
    protected function outputError($errorNumber, TestInterface $test, $exception)
213
    {
214
        $this->output->writeln(sprintf("  %d)%s:", $errorNumber, $test->getTitle()));
215
216
        $message = sprintf("     %s", str_replace(PHP_EOL, PHP_EOL . "     ", $exception->getMessage()));
217
        $this->output->writeln($this->color('error', $message));
218
219
        $location = sprintf('     at %s:%d', $exception->getFile(), $exception->getLine());
220
        $this->output->writeln($location);
221
222
        $this->outputTrace($exception->getTrace());
223
    }
224
225
    /**
226
     * Output a stack trace.
227
     *
228
     * @param array $trace
229
     */
230
    protected function outputTrace(array $trace)
231
    {
232
        foreach ($trace as $index => $entry) {
233
            if (isset($entry['class'])) {
234
                $function = $entry['class'] . $entry['type'] . $entry['function'];
235
            } else {
236
                $function = $entry['function'];
237
            }
238
239
            if (strncmp($function, 'Peridot\\', 8) === 0) {
240
                break;
241
            }
242
243
            $this->output->writeln($this->color('muted', $this->renderTraceEntry($index, $entry, $function)));
244
        }
245
    }
246
247
    /**
248
     * Determine if colorized output is supported by the reporters output.
249
     * Taken from Symfony's console output with some slight modifications
250
     * to use the reporter's output stream
251
     *
252
     * @return bool
253
     */
254
    protected function hasColorSupport()
255
    {
256
        if ($this->isOnWindows()) {
257
            return $this->hasAnsiSupport();
258
        }
259
260
        return $this->hasTty();
261
    }
262
263
    /**
264
     * Register reporter symbols, additionally checking OS compatibility.
265
     */
266
    protected function registerSymbols()
267
    {
268
        //update symbols for windows
269
        if ($this->isOnWindows()) {
270
            $this->symbols['check'] = chr(251);
271
        }
272
    }
273
274
    /**
275
     * Return true if reporter is being used on windows
276
     *
277
     * @return bool
278
     */
279
    protected function isOnWindows()
280
    {
281
        return DIRECTORY_SEPARATOR == '\\';
282
    }
283
284
    /**
285
     * Register events tracking state relevant to all reporters.
286
     */
287
    private function registerEvents()
288
    {
289
        $this->eventEmitter->on('runner.end', [$this, 'setTime']);
290
291
        $this->eventEmitter->on('test.failed', function (Test $test, $e) {
292
            $this->errors[] = [$test, $e];
293
        });
294
295
        $this->eventEmitter->on('test.passed', function () {
296
            $this->passing++;
297
        });
298
299
        $this->eventEmitter->on('test.pending', function () {
300
            $this->pending++;
301
        });
302
    }
303
304
    /**
305
     * Determine if the terminal has ansicon support
306
     *
307
     * @return bool
308
     */
309
    private function hasAnsiSupport()
310
    {
311
        return false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI');
312
    }
313
314
    /**
315
     * Determine if reporter is reporting to a tty terminal.
316
     *
317
     * @return bool
318
     */
319
    private function hasTty()
320
    {
321
        if (! $this->output instanceof StreamOutput) {
322
            return false;
323
        }
324
325
        if (getenv("PERIDOT_TTY")) {
326
            return true;
327
        }
328
329
        return $this->isTtyTerminal($this->output);
330
    }
331
332
    /**
333
     * See if stream output is a tty terminal.
334
     *
335
     * @return bool
336
     */
337
    private function isTtyTerminal(StreamOutput $output)
338
    {
339
        $tty = function_exists('posix_isatty') && @posix_isatty($output->getStream());
340
        if ($tty) {
341
            putenv("PERIDOT_TTY=1");
342
        }
343
        return $tty;
344
    }
345
346
    /**
347
     * Initialize reporter. Setup and listen for events
348
     *
349
     * @return void
350
     */
351
    abstract public function init();
352
353
    private function renderTraceEntry($index, array $entry, $function)
354
    {
355
        if (isset($entry['file'])) {
356
            $location = sprintf(' (%s:%d)', $entry['file'], $entry['line']);
357
        } else {
358
            $location = '';
359
        }
360
361
        return sprintf('       #%d %s%s', $index, $function, $location);
362
    }
363
}
364