Completed
Push — master ( 19c73b...a73acd )
by Erin
01:51
created

AbstractBaseReporter::renderTraceEntry()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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