Completed
Pull Request — master (#94)
by Alessandro
04:33
created

LogPrinter::write()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3

Importance

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