Completed
Pull Request — master (#94)
by Alessandro
08:53
created

LogPrinter::endTestSuite()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 6
ccs 2
cts 2
cp 1
rs 9.4285
cc 1
eloc 4
nc 1
nop 1
crap 1
1
<?php
2
declare(strict_types=1);
3
4
namespace Paraunit\Parser\JSON;
5
6
use Paraunit\Configuration\EnvVariables;
7
use PHPUnit\Framework\AssertionFailedError;
8
use PHPUnit\Framework\Test;
9
use PHPUnit\Framework\TestCase;
10
use PHPUnit\Framework\TestFailure;
11
use PHPUnit\Framework\TestListener;
12
use PHPUnit\Framework\TestSuite;
13
use PHPUnit\Framework\Warning;
14
use PHPUnit\Util;
15
16
/**
17
 * This class comes from Util\Log_JSON.
18
 * It's copied and refactored here because it's deprecated in PHPUnit 5.7 and it will be dropped in PHPUnit 6
19
 *
20
 * Class LogPrinter
21
 * @package Paraunit\Parser\JSON
22
 */
23
class LogPrinter extends Util\Printer implements TestListener
24
{
25
    /** @var string */
26
    private $logDirectory;
27
28
    /** @var int */
29
    private $testSuiteLevel;
30
31
    /** @var string */
32
    private $currentTestSuiteName;
33
34 10
    /** @var string */
35
    private $currentTestName;
36 10
37 10
    /** @var bool */
38 10
    private $currentTestPass;
39
40
    public function __construct()
41
    {
42
        $this->testSuiteLevel = 0;
43
        $this->autoFlush = true;
44
45
        $logFilename = $this->getLogFilename();
46
47 1
        $logDir = $this->getLogDirectory();
48 View Code Duplication
        if (! @mkdir($logDir, 0777, true) && ! is_dir($logDir)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
49 1
            throw new \RuntimeException('Cannot create folder for JSON logs');
50 1
        }
51
52 1
        $this->out = fopen($logDir . $logFilename, 'wt');
53 1
    }
54
55
    /**
56
     * An error occurred.
57 1
     *
58 1
     * @param Test $test
59
     * @param \Exception $e
60
     * @param float $time
61
     */
62 View Code Duplication
    public function addError(Test $test, \Exception $e, $time)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
63
    {
64
        $this->writeCase(
65
            'error',
66
            $time,
67 1
            Util\Filter::getFilteredStacktrace($e, false),
0 ignored issues
show
Compatibility introduced by
$e of type object<Exception> is not a sub-type of object<PHPUnit\Framework\Exception>. It seems like you assume a child class of the class Exception to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Bug introduced by
It seems like \PHPUnit\Util\Filter::ge...edStacktrace($e, false) targeting PHPUnit\Util\Filter::getFilteredStacktrace() can also be of type string; however, Paraunit\Parser\JSON\LogPrinter::writeCase() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
68
            TestFailure::exceptionToString($e),
69 1
            $test
0 ignored issues
show
Documentation introduced by
$test is of type object<PHPUnit\Framework\Test>, but the function expects a object<PHPUnit\Framework\TestCase>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
70 1
        );
71
72 1
        $this->currentTestPass = false;
73 1
    }
74
75
    /**
76
     * A warning occurred.
77 1
     *
78 1
     * @param Test $test
79
     * @param Warning $e
80
     * @param float $time
81
     */
82 View Code Duplication
    public function addWarning(Test $test, Warning $e, $time)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
83
    {
84
        $this->writeCase(
85
            'warning',
86
            $time,
87 1
            Util\Filter::getFilteredStacktrace($e, false),
0 ignored issues
show
Bug introduced by
It seems like \PHPUnit\Util\Filter::ge...edStacktrace($e, false) targeting PHPUnit\Util\Filter::getFilteredStacktrace() can also be of type string; however, Paraunit\Parser\JSON\LogPrinter::writeCase() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
88
            TestFailure::exceptionToString($e),
89 1
            $test
0 ignored issues
show
Documentation introduced by
$test is of type object<PHPUnit\Framework\Test>, but the function expects a object<PHPUnit\Framework\TestCase>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
90 1
        );
91
92 1
        $this->currentTestPass = false;
93 1
    }
94
95
    /**
96
     * A failure occurred.
97 1
     *
98 1
     * @param Test $test
99
     * @param AssertionFailedError $e
100
     * @param float $time
101
     */
102 View Code Duplication
    public function addFailure(Test $test, AssertionFailedError $e, $time)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
103
    {
104
        $this->writeCase(
105
            'fail',
106
            $time,
107 1
            Util\Filter::getFilteredStacktrace($e, false),
0 ignored issues
show
Bug introduced by
It seems like \PHPUnit\Util\Filter::ge...edStacktrace($e, false) targeting PHPUnit\Util\Filter::getFilteredStacktrace() can also be of type string; however, Paraunit\Parser\JSON\LogPrinter::writeCase() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
108
            TestFailure::exceptionToString($e),
109 1
            $test
0 ignored issues
show
Documentation introduced by
$test is of type object<PHPUnit\Framework\Test>, but the function expects a object<PHPUnit\Framework\TestCase>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
110 1
        );
111
112 1
        $this->currentTestPass = false;
113 1
    }
114
115
    /**
116
     * Incomplete test.
117 1
     *
118 1
     * @param Test $test
119
     * @param \Exception $e
120
     * @param float $time
121
     */
122 View Code Duplication
    public function addIncompleteTest(Test $test, \Exception $e, $time)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
123
    {
124
        $this->writeCase(
125
            'error',
126
            $time,
127 1
            Util\Filter::getFilteredStacktrace($e, false),
0 ignored issues
show
Compatibility introduced by
$e of type object<Exception> is not a sub-type of object<PHPUnit\Framework\Exception>. It seems like you assume a child class of the class Exception to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Bug introduced by
It seems like \PHPUnit\Util\Filter::ge...edStacktrace($e, false) targeting PHPUnit\Util\Filter::getFilteredStacktrace() can also be of type string; however, Paraunit\Parser\JSON\LogPrinter::writeCase() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
128
            'Incomplete Test: ' . $e->getMessage(),
129 1
            $test
0 ignored issues
show
Documentation introduced by
$test is of type object<PHPUnit\Framework\Test>, but the function expects a object<PHPUnit\Framework\TestCase>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
130 1
        );
131
132 1
        $this->currentTestPass = false;
133 1
    }
134
135
    /**
136
     * Risky test.
137 1
     *
138 1
     * @param Test $test
139
     * @param \Exception $e
140
     * @param float $time
141
     */
142 View Code Duplication
    public function addRiskyTest(Test $test, \Exception $e, $time)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
143
    {
144
        $this->writeCase(
145
            'error',
146
            $time,
147 1
            Util\Filter::getFilteredStacktrace($e, false),
0 ignored issues
show
Compatibility introduced by
$e of type object<Exception> is not a sub-type of object<PHPUnit\Framework\Exception>. It seems like you assume a child class of the class Exception to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Bug introduced by
It seems like \PHPUnit\Util\Filter::ge...edStacktrace($e, false) targeting PHPUnit\Util\Filter::getFilteredStacktrace() can also be of type string; however, Paraunit\Parser\JSON\LogPrinter::writeCase() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
148
            'Risky Test: ' . $e->getMessage(),
149 1
            $test
0 ignored issues
show
Documentation introduced by
$test is of type object<PHPUnit\Framework\Test>, but the function expects a object<PHPUnit\Framework\TestCase>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
150 1
        );
151
152 1
        $this->currentTestPass = false;
153 1
    }
154
155
    /**
156
     * Skipped test.
157 1
     *
158 1
     * @param Test $test
159
     * @param \Exception $e
160
     * @param float $time
161
     */
162 View Code Duplication
    public function addSkippedTest(Test $test, \Exception $e, $time)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
163
    {
164
        $this->writeCase(
165
            'error',
166 9
            $time,
167
            Util\Filter::getFilteredStacktrace($e, false),
0 ignored issues
show
Compatibility introduced by
$e of type object<Exception> is not a sub-type of object<PHPUnit\Framework\Exception>. It seems like you assume a child class of the class Exception to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Bug introduced by
It seems like \PHPUnit\Util\Filter::ge...edStacktrace($e, false) targeting PHPUnit\Util\Filter::getFilteredStacktrace() can also be of type string; however, Paraunit\Parser\JSON\LogPrinter::writeCase() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
168 9
            'Skipped Test: ' . $e->getMessage(),
169 9
            $test
0 ignored issues
show
Documentation introduced by
$test is of type object<PHPUnit\Framework\Test>, but the function expects a object<PHPUnit\Framework\TestCase>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
170
        );
171 9
172 9
        $this->currentTestPass = false;
173
    }
174
175
    /**
176 9
     * A testsuite started.
177
     *
178
     * @param TestSuite $suite
179 9
     * @throws \RuntimeException
180 9
     */
181 9
    public function startTestSuite(TestSuite $suite)
182
    {
183 9
        $this->testSuiteLevel++;
184
        $this->currentTestSuiteName = $suite->getName();
185 9
        $this->currentTestName = '';
186 9
187 9
        $this->writeArray([
188
            'event' => 'suiteStart',
189
            'suite' => $this->currentTestSuiteName,
190 9
            'tests' => count($suite)
191
        ]);
192
    }
193
194
    public function endTestSuite(TestSuite $suite)
195
    {
196
        $this->testSuiteLevel--;
197 1
        $this->currentTestSuiteName = '';
198
        $this->currentTestName = '';
199 1
    }
200 1
201 1
    public function startTest(Test $test)
202 1
    {
203
        $this->currentTestName = Util\Test::describe($test);
204
        $this->currentTestPass = true;
205
206
        $this->writeArray([
207
            'event' => 'testStart',
208
            'suite' => $this->currentTestSuiteName,
209 7
            'test' => $this->currentTestName
210
        ]);
211 7
    }
212 7
213
    /**
214 7
     * A test ended.
215
     *
216 7
     * @param Test $test
217 7
     * @param float $time
218 7
     */
219
    public function endTest(Test $test, $time)
220
    {
221 7
        if ($this->currentTestPass) {
222
            $this->writeCase('pass', $time, [], '', $test);
0 ignored issues
show
Documentation introduced by
$test is of type object<PHPUnit\Framework\Test>, but the function expects a object<PHPUnit\Framework\TestCase>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
223
        }
224
    }
225
226
    /**
227
     * @param string $status
228
     * @param float $time
229 1
     * @param array $trace
230
     * @param string $message
231 1
     * @param TestCase|null $test
232 1
     */
233
    protected function writeCase($status, $time, array $trace = [], $message = '', $test = null)
234 1
    {
235
        $output = '';
236
        // take care of TestSuite producing error (e.g. by running into exception) as TestSuite doesn't have hasOutput
237
        if ($test !== null && method_exists($test, 'hasOutput') && $test->hasOutput()) {
238
            $output = $test->getActualOutput();
239
        }
240
        $this->writeArray([
241
            'event' => 'test',
242
            'suite' => $this->currentTestSuiteName,
243 7
            'test' => $this->currentTestName,
244
            'status' => $status,
245 7
            'time' => $time,
246
            'trace' => $trace,
247 7
            'message' => $this->convertToUtf8($message),
248
            'output' => $output,
249
        ]);
250 7
    }
251
252 7
    /**
253 7
     * @param array $buffer
254 7
     */
255 7
    public function writeArray($buffer)
256 7
    {
257 7
        array_walk_recursive($buffer, function (&$input) {
258 7
            if (is_string($input)) {
259 7
                $input = $this->convertToUtf8($input);
260
            }
261
        });
262 7
263
        $this->write(json_encode($buffer, JSON_PRETTY_PRINT));
264
    }
265
266
    public function write($buffer)
267
    {
268
        // ignore everything that is not a JSON object
269 9
        if ($buffer != '' && $buffer[0] === '{') {
270 9
            parent::write($buffer);
271 9
        }
272
    }
273 9
274
    /**
275 9
     * @return string
276
     * @throws \InvalidArgumentException
277 9
     */
278 9
    private function getLogFilename(): string
279
    {
280
        return getenv(EnvVariables::PROCESS_UNIQUE_ID) . '.json.log';
281 9
    }
282 9
283
    /**
284
     * @return string
285
     * @throws \InvalidArgumentException
286
     */
287
    private function getLogDirectory(): string
288 10
    {
289
        $this->logDirectory = getenv(EnvVariables::LOG_DIR);
290 10
291
        if ($this->logDirectory === false) {
292 10
            throw new \InvalidArgumentException('Log directory not received: environment variable not set');
293
        }
294
295
        if (substr($this->logDirectory, -1) !== DIRECTORY_SEPARATOR) {
296
            $this->logDirectory .= DIRECTORY_SEPARATOR;
297
        }
298
299 10
        return $this->logDirectory;
300
    }
301 10
302
    private function convertToUtf8($string): string
303 10
    {
304
        if (! $this->isUtf8($string)) {
305
            if (\function_exists('mb_convert_encoding')) {
306
                return \mb_convert_encoding($string, 'UTF-8');
307
            }
308
309 10
            return \utf8_encode($string);
310
        }
311 10
312 10
        return $string;
313 10
    }
314
315
    private function isUtf8(string $string): bool
316 10
    {
317
        $length = \strlen($string);
318
319
        for ($i = 0; $i < $length; $i++) {
320
            if (\ord($string[$i]) < 0x80) {
321
                $n = 0;
322
            } elseif ((\ord($string[$i]) & 0xE0) == 0xC0) {
323
                $n = 1;
324
            } elseif ((\ord($string[$i]) & 0xF0) == 0xE0) {
325
                $n = 2;
326
            } elseif ((\ord($string[$i]) & 0xF0) == 0xF0) {
327
                $n = 3;
328
            } else {
329
                return false;
330
            }
331
332
            for ($j = 0; $j < $n; $j++) {
333
                if ((++$i == $length) || ((\ord($string[$i]) & 0xC0) != 0x80)) {
334
                    return false;
335
                }
336
            }
337
        }
338
339
        return true;
340
    }
341
}
342