|
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)) { |
|
|
|
|
|
|
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) |
|
|
|
|
|
|
63
|
|
|
{ |
|
64
|
1 |
|
$this->writeCase( |
|
65
|
1 |
|
'error', |
|
66
|
1 |
|
$time, |
|
67
|
1 |
|
Util\Filter::getFilteredStacktrace($e, false), |
|
|
|
|
|
|
68
|
1 |
|
TestFailure::exceptionToString($e), |
|
69
|
1 |
|
$test |
|
|
|
|
|
|
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) |
|
|
|
|
|
|
83
|
|
|
{ |
|
84
|
1 |
|
$this->writeCase( |
|
85
|
1 |
|
'warning', |
|
86
|
1 |
|
$time, |
|
87
|
1 |
|
Util\Filter::getFilteredStacktrace($e, false), |
|
|
|
|
|
|
88
|
1 |
|
TestFailure::exceptionToString($e), |
|
89
|
1 |
|
$test |
|
|
|
|
|
|
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) |
|
|
|
|
|
|
103
|
|
|
{ |
|
104
|
1 |
|
$this->writeCase( |
|
105
|
1 |
|
'fail', |
|
106
|
1 |
|
$time, |
|
107
|
1 |
|
Util\Filter::getFilteredStacktrace($e, false), |
|
|
|
|
|
|
108
|
1 |
|
TestFailure::exceptionToString($e), |
|
109
|
1 |
|
$test |
|
|
|
|
|
|
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) |
|
|
|
|
|
|
123
|
|
|
{ |
|
124
|
1 |
|
$this->writeCase( |
|
125
|
1 |
|
'error', |
|
126
|
1 |
|
$time, |
|
127
|
1 |
|
Util\Filter::getFilteredStacktrace($e, false), |
|
|
|
|
|
|
128
|
1 |
|
'Incomplete Test: ' . $e->getMessage(), |
|
129
|
1 |
|
$test |
|
|
|
|
|
|
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) |
|
|
|
|
|
|
143
|
|
|
{ |
|
144
|
1 |
|
$this->writeCase( |
|
145
|
1 |
|
'error', |
|
146
|
1 |
|
$time, |
|
147
|
1 |
|
Util\Filter::getFilteredStacktrace($e, false), |
|
|
|
|
|
|
148
|
1 |
|
'Risky Test: ' . $e->getMessage(), |
|
149
|
1 |
|
$test |
|
|
|
|
|
|
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) |
|
|
|
|
|
|
163
|
|
|
{ |
|
164
|
1 |
|
$this->writeCase( |
|
165
|
1 |
|
'error', |
|
166
|
1 |
|
$time, |
|
167
|
1 |
|
Util\Filter::getFilteredStacktrace($e, false), |
|
|
|
|
|
|
168
|
1 |
|
'Skipped Test: ' . $e->getMessage(), |
|
169
|
1 |
|
$test |
|
|
|
|
|
|
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); |
|
|
|
|
|
|
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
|
|
|
|
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.