Completed
Push — master ( 11aa12...25ec1c )
by Erin
01:41
created

AbstractBaseReporter::warnings()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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