Completed
Push — stable ( 8ba0c8...0d8cdb )
by Nuno
17:40 queued 15:16
created

Style::updateFooter()   B

Complexity

Conditions 6
Paths 24

Size

Total Lines 44

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
dl 0
loc 44
ccs 0
cts 28
cp 0
rs 8.5937
c 0
b 0
f 0
cc 6
nc 24
nop 2
crap 42
1
<?php
2
3
/**
4
 * This file is part of Collision.
5
 *
6
 * (c) Nuno Maduro <[email protected]>
7
 *
8
 *  For the full copyright and license information, please view the LICENSE
9
 *  file that was distributed with this source code.
10
 */
11
12
namespace NunoMaduro\Collision\Adapters\Phpunit;
13
14
use NunoMaduro\Collision\Writer;
15
use PHPUnit\Framework\AssertionFailedError;
16
use PHPUnit\Framework\ExceptionWrapper;
17
use PHPUnit\Framework\ExpectationFailedException;
18
use PHPUnit\Framework\TestCase;
19
use Symfony\Component\Console\Output\ConsoleOutput;
20
use Symfony\Component\Console\Output\ConsoleSectionOutput;
21
use Throwable;
22
use Whoops\Exception\Inspector;
23
24
/**
25
 * @internal
26
 */
27
final class Style
28
{
29
    /**
30
     * @var ConsoleOutput
31
     */
32
    private $output;
33
34
    /**
35
     * @var ConsoleSectionOutput
36
     */
37
    private $footer;
38
39
    /**
40
     * Style constructor.
41
     *
42
     * @param  ConsoleOutput  $output
43
     */
44 3
    public function __construct(ConsoleOutput $output)
45
    {
46 3
        $this->output = $output;
47
48 3
        $this->footer = $output->section();
49 3
    }
50
51
    /**
52
     * Prints the content similar too:.
53
     *
54
     * ```
55
     *    PASS  Unit\ExampleTest
56
     *    ✓ basic test
57
     * ```
58
     *
59
     * @param  State  $state
60
     *
61
     * @return void
62
     */
63
    public function writeCurrentRecap(State $state): void
64
    {
65
        if (! $state->testCaseTestsCount()) {
66
            return;
67
        }
68
69
        $this->footer->clear();
70
71
        $this->output->writeln($this->titleLineFrom(
72
            $state->getTestCaseTitle() === 'FAIL' ? 'white' : 'black',
73
            $state->getTestCaseTitleColor(),
74
            $state->getTestCaseTitle(),
75
            $state->testCaseClass
76
        ));
77
78
        $state->eachTestCaseTests(function (TestResult $testResult) {
79
            $this->output->writeln($this->testLineFrom(
80
                $testResult->color,
81
                $testResult->icon,
82
                $testResult->description,
83
                $testResult->warning
84
            ));
85
        });
86
    }
87
88
    /**
89
     * Prints the content similar too on the footer. Where
90
     * we are updating the current test.
91
     *
92
     * ```
93
     *    Runs  Unit\ExampleTest
94
     *    • basic test
95
     * ```
96
     *
97
     * @param  State  $state
98
     * @param  TestCase|null  $testCase
99
     *
100
     * @return void
101
     */
102
    public function updateFooter(State $state, TestCase $testCase = null): void
103
    {
104
        $runs = [];
105
106
        if ($testCase) {
107
            $runs[] = $this->titleLineFrom(
108
                'black',
109
                'yellow',
110
                'RUNS',
111
                get_class($testCase)
112
            );
113
114
            $testResult = TestResult::fromTestCase($testCase, TestResult::RUNS);
115
            $runs[] = $this->testLineFrom(
116
                $testResult->color,
117
                $testResult->icon,
118
                $testResult->description
119
            );
120
        }
121
122
        $types = [TestResult::FAIL, TestResult::WARN, TestResult::RISKY, TestResult::INCOMPLETE, TestResult::SKIPPED, TestResult::PASS];
123
124
        foreach ($types as $type) {
125
            if ($countTests = $state->countTestsInTestSuiteBy($type)) {
126
                $color = TestResult::makeColor($type);
127
                $tests[] = "<fg=$color;options=bold>$countTests $type</>";
0 ignored issues
show
Coding Style Comprehensibility introduced by
$tests was never initialized. Although not strictly required by PHP, it is generally a good practice to add $tests = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
128
            }
129
        }
130
131
        $pending = $state->suiteTotalTests - $state->testSuiteTestsCount();
132
        if ($pending) {
133
            $tests[] = "$pending pending";
0 ignored issues
show
Bug introduced by
The variable $tests does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
134
        }
135
136
        if (! empty($tests)) {
137
            $this->footer->overwrite(array_merge($runs, [
138
                '',
139
                sprintf(
140
                    '  <fg=white;options=bold>Tests:  </><fg=default>%s</>',
141
                    implode(', ', $tests)
142
                ),
143
            ]));
144
        }
145
    }
146
147
    /**
148
     * Writes the final recap.
149
     *
150
     * @param  Timer  $timer
151
     */
152
    public function writeRecap(Timer $timer): void
153
    {
154
        $timeElapsed = number_format($timer->result(), 2, '.', '');
155
        $this->footer->writeln(
156
            sprintf(
157
                '  <fg=white;options=bold>Time:   </><fg=default>%ss</>',
158
                $timeElapsed
159
            )
160
        );
161
    }
162
163
    /**
164
     * Displays the error using Collision's writer
165
     * and terminates with exit code === 1.
166
     *
167
     * @param  Throwable  $throwable
168
     *
169
     * @return void
170
     */
171
    public function writeError(State $state, Throwable $throwable)
172
    {
173
        $this->writeCurrentRecap($state);
174
175
        $this->updateFooter($state);
176
177
        $writer = (new Writer())->setOutput($this->output);
178
179
        if ($throwable instanceof AssertionFailedError) {
180
            $writer->showTitle(false);
181
            $this->output->write('', true);
182
        }
183
184
        $writer->ignoreFilesIn([
185
            '/vendor\/phpunit\/phpunit\/src/',
186
            '/vendor\/mockery\/mockery/',
187
            '/vendor\/laravel\/framework\/src\/Illuminate\/Testing/',
188
            '/vendor\/laravel\/framework\/src\/Illuminate\/Foundation\/Testing/',
189
        ]);
190
191
        if ($throwable instanceof ExceptionWrapper && $throwable->getOriginalException() !== null) {
192
            $throwable = $throwable->getOriginalException();
193
        }
194
195
        $inspector = new Inspector($throwable);
196
197
        $writer->write($inspector);
198
199
        if ($throwable instanceof ExpectationFailedException && $comparisionFailure = $throwable->getComparisonFailure()) {
200
            $this->output->write($comparisionFailure->getDiff());
201
        }
202
203
        exit(1);
204
    }
205
206
    /**
207
     * Returns the title contents.
208
     *
209
     * @param  string  $fg
210
     * @param  string  $bg
211
     * @param  string  $title
212
     * @param  string  $testCaseClass
213
     *
214
     * @return string
215
     */
216
    private function titleLineFrom(string $fg, string $bg, string $title, string $testCaseClass): string
217
    {
218
        $classParts = explode('\\', $testCaseClass);
219
        // Removes `Tests` part
220
        array_shift($classParts);
221
        $highlightedPart = array_pop($classParts);
222
        $nonHighlightedPart = implode('\\', $classParts);
223
224
        $testCaseClass = sprintf("\e[2m%s\e[22m<fg=white;options=bold>%s</>", "$nonHighlightedPart\\", $highlightedPart);
225
226
        return sprintf(
227
            "\n  <fg=%s;bg=%s;options=bold> %s </><fg=default> %s</>",
228
            $fg,
229
            $bg,
230
            $title,
231
            $testCaseClass
232
        );
233
    }
234
235
    /**
236
     * Returns the test contents.
237
     *
238
     * @param  string  $fg
239
     * @param  string  $icon
240
     * @param  string  $description
241
     *
242
     * @return string
243
     */
244
    private function testLineFrom(string $fg, string $icon, string $description, string $warning = null): string
245
    {
246
        if (! empty($warning)) {
247
            $warning = sprintf(
248
                ' → %s',
249
                $warning
250
            );
251
        }
252
253
        return sprintf(
254
            "  <fg=%s;options=bold>%s</><fg=default> \e[2m%s\e[22m</><fg=yellow>%s</>",
255
            $fg,
256
            $icon,
257
            $description,
258
            $warning
259
        );
260
    }
261
}
262