Completed
Pull Request — master (#93)
by Alessandro
02:47
created

LogPrinter::writeArray()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 1
nop 1
crap 2
1
<?php
2
declare(strict_types=1);
3
4
namespace Paraunit\Parser\JSON;
5
6
use Paraunit\Configuration\StaticOutputPath;
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
    /** @var string */
35
    private $currentTestName;
36
37
    /** @var bool */
38
    private $currentTestPass;
39
40 10
    public function __construct()
41
    {
42 10
        $this->testSuiteLevel = 0;
43
    }
44
45
    /**
46
     * An error occurred.
47
     *
48
     * @param Test $test
49
     * @param \Exception $e
50
     * @param float $time
51
     */
52 1 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...
53
    {
54 1
        $this->writeCase(
55 1
            'error',
56 1
            $time,
57 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...
58 1
            TestFailure::exceptionToString($e),
59 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...
60
        );
61
62 1
        $this->currentTestPass = false;
63
    }
64
65
    /**
66
     * A warning occurred.
67
     *
68
     * @param Test $test
69
     * @param Warning $e
70
     * @param float $time
71
     */
72 1 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...
73
    {
74 1
        $this->writeCase(
75 1
            'warning',
76 1
            $time,
77 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...
78 1
            TestFailure::exceptionToString($e),
79 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...
80
        );
81
82 1
        $this->currentTestPass = false;
83
    }
84
85
    /**
86
     * A failure occurred.
87
     *
88
     * @param Test $test
89
     * @param AssertionFailedError $e
90
     * @param float $time
91
     */
92 1 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...
93
    {
94 1
        $this->writeCase(
95 1
            'fail',
96 1
            $time,
97 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...
98 1
            TestFailure::exceptionToString($e),
99 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...
100
        );
101
102 1
        $this->currentTestPass = false;
103
    }
104
105
    /**
106
     * Incomplete test.
107
     *
108
     * @param Test $test
109
     * @param \Exception $e
110
     * @param float $time
111
     */
112 1 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...
113
    {
114 1
        $this->writeCase(
115 1
            'error',
116 1
            $time,
117 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...
118 1
            'Incomplete Test: ' . $e->getMessage(),
119 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...
120
        );
121
122 1
        $this->currentTestPass = false;
123
    }
124
125
    /**
126
     * Risky test.
127
     *
128
     * @param Test $test
129
     * @param \Exception $e
130
     * @param float $time
131
     */
132 1 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...
133
    {
134 1
        $this->writeCase(
135 1
            'error',
136 1
            $time,
137 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...
138 1
            'Risky Test: ' . $e->getMessage(),
139 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...
140
        );
141
142 1
        $this->currentTestPass = false;
143
    }
144
145
    /**
146
     * Skipped test.
147
     *
148
     * @param Test $test
149
     * @param \Exception $e
150
     * @param float $time
151
     */
152 1 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...
153
    {
154 1
        $this->writeCase(
155 1
            'error',
156 1
            $time,
157 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...
158 1
            'Skipped Test: ' . $e->getMessage(),
159 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...
160
        );
161
162 1
        $this->currentTestPass = false;
163
    }
164
165
    /**
166
     * A testsuite started.
167
     *
168
     * @param TestSuite $suite
169
     * @throws \RuntimeException
170
     */
171 9
    public function startTestSuite(TestSuite $suite)
172
    {
173 9
        if ($this->testSuiteLevel === 0) {
174 9
            $logFilename = $this->getLogFilename($suite);
175
176 9
            $logDir = dirname($logFilename);
177 9 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...
178
                throw new \RuntimeException('Cannot create folder for JSON logs');
179
            }
180
181 9
            $this->out = fopen($logFilename, 'wt');
182
        }
183
184 9
        $this->testSuiteLevel++;
185 9
        $this->currentTestSuiteName = $suite->getName();
186 9
        $this->currentTestName = '';
187
188 9
        $this->writeArray([
189 9
            'event' => 'suiteStart',
190 9
            'suite' => $this->currentTestSuiteName,
191 9
            'tests' => count($suite)
192
        ]);
193
    }
194
195 1
    public function endTestSuite(TestSuite $suite)
196
    {
197 1
        $this->testSuiteLevel--;
198 1
        $this->currentTestSuiteName = '';
199 1
        $this->currentTestName = '';
200
    }
201
202 7
    public function startTest(Test $test)
203
    {
204 7
        $this->currentTestName = Util\Test::describe($test);
205 7
        $this->currentTestPass = true;
206
207 7
        $this->writeArray([
208 7
            'event' => 'testStart',
209 7
            'suite' => $this->currentTestSuiteName,
210 7
            'test' => $this->currentTestName
211
        ]);
212
    }
213
214
    /**
215
     * A test ended.
216
     *
217
     * @param Test $test
218
     * @param float $time
219
     */
220 1
    public function endTest(Test $test, $time)
221
    {
222 1
        if ($this->currentTestPass) {
223 1
            $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...
224
        }
225
    }
226
227
    /**
228
     * @param string $status
229
     * @param float $time
230
     * @param array $trace
231
     * @param string $message
232
     * @param TestCase|null $test
233
     */
234 7
    protected function writeCase($status, $time, array $trace = [], $message = '', $test = null)
235
    {
236 7
        $output = '';
237
        // take care of TestSuite producing error (e.g. by running into exception) as TestSuite doesn't have hasOutput
238 7
        if ($test !== null && method_exists($test, 'hasOutput') && $test->hasOutput()) {
239
            $output = $test->getActualOutput();
240
        }
241 7
        $this->writeArray([
242 7
            'event' => 'test',
243 7
            'suite' => $this->currentTestSuiteName,
244 7
            'test' => $this->currentTestName,
245 7
            'status' => $status,
246 7
            'time' => $time,
247 7
            'trace' => $trace,
248 7
            'message' => $this->convertToUtf8($message),
249 7
            'output' => $output,
250
        ]);
251
    }
252
253
    /**
254
     * @param array $buffer
255
     */
256
    public function writeArray($buffer)
257
    {
258 9
        array_walk_recursive($buffer, function (&$input) {
259 9
            if (is_string($input)) {
260 9
                $input = $this->convertToUtf8($input);
261
            }
262 9
        });
263
264 9
        $this->write(json_encode($buffer, JSON_PRETTY_PRINT));
265
    }
266
267 10
    private function getLogFilename(TestSuite $suite): string
268
    {
269 10
        $testFilename = $this->getTestFilename($suite);
270
271 10
        return $this->getLogDirectory() . md5($testFilename) . '.json.log';
272
    }
273
274 10
    private function getTestFilename(TestSuite $suite): string
275
    {
276 10
        $reflection = new \ReflectionClass($suite->getName());
277
278 10
        return $reflection->getFileName();
279
    }
280
281
    /**
282
     * @return string
283
     * @throws \RuntimeException
284
     */
285 10
    private function getLogDirectory(): string
286
    {
287 10
        $this->logDirectory = StaticOutputPath::getPath();
288 10
        if (substr($this->logDirectory, -1) !== DIRECTORY_SEPARATOR) {
289 10
            $this->logDirectory .= DIRECTORY_SEPARATOR;
290
        }
291
292 10
        return $this->logDirectory;
293
    }
294
295 9
    private function convertToUtf8($string): string
296
    {
297 9
        if (! $this->isUtf8($string)) {
298
            if (\function_exists('mb_convert_encoding')) {
299
                return \mb_convert_encoding($string, 'UTF-8');
300
            }
301
302
            return \utf8_encode($string);
303
        }
304
305 9
        return $string;
306
    }
307
308 9
    private function isUtf8(string $string): bool
309
    {
310 9
        $length = \strlen($string);
311
312 9
        for ($i = 0; $i < $length; $i++) {
313 9
            if (\ord($string[$i]) < 0x80) {
314 9
                $n = 0;
315
            } elseif ((\ord($string[$i]) & 0xE0) == 0xC0) {
316
                $n = 1;
317
            } elseif ((\ord($string[$i]) & 0xF0) == 0xE0) {
318
                $n = 2;
319
            } elseif ((\ord($string[$i]) & 0xF0) == 0xF0) {
320
                $n = 3;
321
            } else {
322
                return false;
323
            }
324
325 9
            for ($j = 0; $j < $n; $j++) {
326
                if ((++$i == $length) || ((\ord($string[$i]) & 0xC0) != 0x80)) {
327
                    return false;
328
                }
329
            }
330
        }
331
332 9
        return true;
333
    }
334
}
335