Completed
Push — short-traces ( e2d26e...5b20e2 )
by Erin
02:16
created

AbstractBaseReporter::isTtyTerminal()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 4
nop 1
dl 0
loc 8
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($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(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);
0 ignored issues
show
Bug introduced by
The variable $index does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
347
    }
348
}
349