|
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 PHPUnit\Framework\TestCase; |
|
15
|
|
|
use PHPUnit\Framework\TestSuite; |
|
16
|
|
|
use PHPUnit\Framework\Warning; |
|
17
|
|
|
use Symfony\Component\Console\Output\ConsoleOutput; |
|
18
|
|
|
use Symfony\Component\Console\Output\ConsoleSectionOutput; |
|
19
|
|
|
use Throwable; |
|
20
|
|
|
|
|
21
|
|
|
/** |
|
22
|
|
|
* @internal |
|
23
|
|
|
*/ |
|
24
|
|
|
final class Section |
|
25
|
|
|
{ |
|
26
|
|
|
/** |
|
27
|
|
|
* Holds an instance of the section. |
|
28
|
|
|
* |
|
29
|
|
|
* @var ConsoleSectionOutput |
|
30
|
|
|
*/ |
|
31
|
|
|
private $section; |
|
32
|
|
|
|
|
33
|
|
|
/** |
|
34
|
|
|
* Holds an instance of the test suite. |
|
35
|
|
|
* |
|
36
|
|
|
* @var TestSuite |
|
37
|
|
|
*/ |
|
38
|
|
|
private $testSuite; |
|
39
|
|
|
|
|
40
|
|
|
/** |
|
41
|
|
|
* If the current testSuite is dirty |
|
42
|
|
|
* |
|
43
|
|
|
* @var bool |
|
44
|
|
|
*/ |
|
45
|
|
|
private $dirty = false; |
|
46
|
|
|
|
|
47
|
|
|
/** |
|
48
|
|
|
* Holds an instance of the test case. |
|
49
|
|
|
* |
|
50
|
|
|
* @var TestCase |
|
51
|
|
|
*/ |
|
52
|
|
|
private $testCase; |
|
53
|
|
|
|
|
54
|
|
|
/** |
|
55
|
|
|
* If the current testCase should pass. |
|
56
|
|
|
* |
|
57
|
|
|
* @var bool |
|
58
|
|
|
*/ |
|
59
|
|
|
private $shouldPass = true; |
|
60
|
|
|
|
|
61
|
|
|
/** |
|
62
|
|
|
* Holds the content of the section. |
|
63
|
|
|
* |
|
64
|
|
|
* @var array<int, string> |
|
65
|
|
|
*/ |
|
66
|
|
|
private $tests = []; |
|
67
|
|
|
|
|
68
|
|
|
/** |
|
69
|
|
|
* Section constructor. |
|
70
|
|
|
* |
|
71
|
|
|
* @param ConsoleSectionOutput $section |
|
72
|
|
|
* @param TestSuite $testSuite |
|
73
|
|
|
*/ |
|
74
|
3 |
|
public function __construct(ConsoleSectionOutput $section, TestSuite $testSuite) |
|
75
|
|
|
{ |
|
76
|
3 |
|
$this->section = $section; |
|
77
|
3 |
|
$this->testSuite = $testSuite; |
|
78
|
|
|
$this->testCase = new class extends TestCase { |
|
79
|
|
|
}; |
|
80
|
3 |
|
} |
|
81
|
|
|
|
|
82
|
|
|
/** |
|
83
|
|
|
* @param ConsoleOutput $output |
|
84
|
|
|
* @param TestSuite $testSuite |
|
85
|
|
|
* |
|
86
|
|
|
* @return Section |
|
87
|
|
|
*/ |
|
88
|
3 |
|
public static function create(ConsoleOutput $output, TestSuite $testSuite): Section |
|
89
|
|
|
{ |
|
90
|
3 |
|
return new self($output->section(), $testSuite); |
|
91
|
|
|
} |
|
92
|
|
|
|
|
93
|
|
|
/** |
|
94
|
|
|
* Runs the given test. |
|
95
|
|
|
* |
|
96
|
|
|
* @param TestCase $test |
|
97
|
|
|
* |
|
98
|
|
|
* @return void |
|
99
|
|
|
*/ |
|
100
|
|
|
public function runs(TestCase $test): void |
|
101
|
|
|
{ |
|
102
|
|
|
$this->testCase = $test; |
|
103
|
|
|
$this->shouldPass = true; |
|
104
|
|
|
|
|
105
|
|
|
if (count($this->tests) === 0) { |
|
106
|
|
|
$this->title('RUNS', 'yellow'); |
|
107
|
|
|
} |
|
108
|
|
|
|
|
109
|
|
|
$this->updateTest('•', 'yellow', true); |
|
110
|
|
|
} |
|
111
|
|
|
|
|
112
|
|
|
|
|
113
|
|
|
/** |
|
114
|
|
|
* Passes the current test case. |
|
115
|
|
|
* |
|
116
|
|
|
* @return void |
|
117
|
|
|
*/ |
|
118
|
|
|
public function pass(): void |
|
119
|
|
|
{ |
|
120
|
|
|
if ($this->shouldPass) { |
|
121
|
|
|
$this->updateTest('✓', 'green'); |
|
122
|
|
|
} |
|
123
|
|
|
} |
|
124
|
|
|
|
|
125
|
|
|
/** |
|
126
|
|
|
* Marks the current test case as failed. |
|
127
|
|
|
* |
|
128
|
|
|
* @return void |
|
129
|
|
|
*/ |
|
130
|
|
View Code Duplication |
public function fail(): void |
|
|
|
|
|
|
131
|
|
|
{ |
|
132
|
|
|
$this->title('FAIL', 'red'); |
|
133
|
|
|
$this->updateTest('✕', 'red'); |
|
134
|
|
|
|
|
135
|
|
|
$this->dirty = true; |
|
136
|
|
|
$this->shouldPass = false; |
|
137
|
|
|
} |
|
138
|
|
|
|
|
139
|
|
|
/** |
|
140
|
|
|
* Marks the current test case as incomplete. |
|
141
|
|
|
* |
|
142
|
|
|
* @param Throwable $throwable |
|
143
|
|
|
* |
|
144
|
|
|
* @return void |
|
145
|
|
|
*/ |
|
146
|
|
View Code Duplication |
public function incomplete(Throwable $throwable): void |
|
|
|
|
|
|
147
|
|
|
{ |
|
148
|
|
|
$this->updateTest('i', 'yellow', false, $throwable->getMessage()); |
|
149
|
|
|
$this->title('WARN', 'yellow'); |
|
150
|
|
|
|
|
151
|
|
|
$this->dirty = true; |
|
152
|
|
|
$this->shouldPass = false; |
|
153
|
|
|
} |
|
154
|
|
|
|
|
155
|
|
|
/** |
|
156
|
|
|
* Marks the current test case as risky. |
|
157
|
|
|
* |
|
158
|
|
|
* @return void |
|
159
|
|
|
*/ |
|
160
|
|
View Code Duplication |
public function risky(): void |
|
|
|
|
|
|
161
|
|
|
{ |
|
162
|
|
|
$this->updateTest('r', 'yellow'); |
|
163
|
|
|
$this->title('WARN', 'yellow'); |
|
164
|
|
|
|
|
165
|
|
|
$this->dirty = true; |
|
166
|
|
|
$this->shouldPass = false; |
|
167
|
|
|
} |
|
168
|
|
|
|
|
169
|
|
|
/** |
|
170
|
|
|
* Marks the current test case as risky. |
|
171
|
|
|
* |
|
172
|
|
|
* @param Throwable $throwable |
|
173
|
|
|
* |
|
174
|
|
|
* @return void |
|
175
|
|
|
*/ |
|
176
|
|
View Code Duplication |
public function skipped(Throwable $throwable): void |
|
|
|
|
|
|
177
|
|
|
{ |
|
178
|
|
|
$this->updateTest('s', 'yellow', false, $throwable->getMessage()); |
|
179
|
|
|
$this->title('WARN', 'yellow'); |
|
180
|
|
|
|
|
181
|
|
|
$this->dirty = true; |
|
182
|
|
|
$this->shouldPass = false; |
|
183
|
|
|
} |
|
184
|
|
|
|
|
185
|
|
|
/** |
|
186
|
|
|
* Marks the current test case as risky. |
|
187
|
|
|
* |
|
188
|
|
|
* @return void |
|
189
|
|
|
*/ |
|
190
|
|
View Code Duplication |
public function warn(Warning $warning): void |
|
|
|
|
|
|
191
|
|
|
{ |
|
192
|
|
|
$this->updateTest('w', 'yellow', false, $warning->getMessage()); |
|
193
|
|
|
$this->title('WARN', 'yellow'); |
|
194
|
|
|
|
|
195
|
|
|
$this->dirty = true; |
|
196
|
|
|
$this->shouldPass = false; |
|
197
|
|
|
} |
|
198
|
|
|
|
|
199
|
|
|
public function end(): void |
|
200
|
|
|
{ |
|
201
|
|
|
if (! $this->dirty && count($this->tests)) { |
|
202
|
|
|
$this->title('PASS', 'green'); |
|
203
|
|
|
} |
|
204
|
|
|
} |
|
205
|
|
|
|
|
206
|
|
|
/** |
|
207
|
|
|
* Updates the console with the current state. |
|
208
|
|
|
* |
|
209
|
|
|
* @param string $icon |
|
210
|
|
|
* @param string $color |
|
211
|
|
|
* |
|
212
|
|
|
* @param bool $create |
|
213
|
|
|
* |
|
214
|
|
|
* @return void |
|
215
|
|
|
*/ |
|
216
|
|
|
private function updateTest(string $icon, string $color, bool $create = false, string $note = null): void |
|
217
|
|
|
{ |
|
218
|
|
|
$value = sprintf( |
|
219
|
|
|
' <fg=%s;options=bold>%s</><fg=default> %s</>', |
|
220
|
|
|
$color, |
|
221
|
|
|
$icon, |
|
222
|
|
|
$name = $this->getTestCaseDescription() |
|
223
|
|
|
); |
|
224
|
|
|
|
|
225
|
|
|
if ($note) { |
|
|
|
|
|
|
226
|
|
|
$value .= sprintf(" → <fg=%s>%s</>", $color, trim((string) preg_replace("/\r|\n/", ' ', $note))); |
|
227
|
|
|
} |
|
228
|
|
|
|
|
229
|
|
|
if ($create) { |
|
230
|
|
|
$this->tests[] = $value; |
|
231
|
|
|
} else { |
|
232
|
|
|
$this->tests[count($this->tests) - 1] = $value; |
|
233
|
|
|
} |
|
234
|
|
|
|
|
235
|
|
|
$this->update(); |
|
236
|
|
|
} |
|
237
|
|
|
|
|
238
|
|
|
/** |
|
239
|
|
|
* Updates the console with the current state. |
|
240
|
|
|
* |
|
241
|
|
|
* @return void |
|
242
|
|
|
*/ |
|
243
|
|
|
private function update(): void |
|
244
|
|
|
{ |
|
245
|
|
|
$this->section->clear(); |
|
246
|
|
|
$this->section->writeln($this->tests); |
|
|
|
|
|
|
247
|
|
|
} |
|
248
|
|
|
|
|
249
|
|
|
/** |
|
250
|
|
|
* Get the current test case description. |
|
251
|
|
|
* |
|
252
|
|
|
* @return string |
|
253
|
|
|
*/ |
|
254
|
|
|
private function getTestCaseDescription(): string |
|
255
|
|
|
{ |
|
256
|
|
|
$name = $this->testCase->getName(false); |
|
257
|
|
|
|
|
258
|
|
|
// First, lets replace underscore by spaces. |
|
259
|
|
|
$name = str_replace('_', ' ', $name); |
|
260
|
|
|
|
|
261
|
|
|
// Then, replace upper cases by spaces. |
|
262
|
|
|
$name = (string) preg_replace('/([A-Z])/', ' $1', $name); |
|
263
|
|
|
|
|
264
|
|
|
// Finally, if it starts with `test`, we remove it. |
|
265
|
|
|
$name = (string) preg_replace('/^test/', '', $name); |
|
266
|
|
|
|
|
267
|
|
|
// Removes spaces |
|
268
|
|
|
$name = (string) trim($name); |
|
269
|
|
|
|
|
270
|
|
|
// Finally, lower case everything |
|
271
|
|
|
return (string) mb_strtolower($name); |
|
272
|
|
|
} |
|
273
|
|
|
|
|
274
|
|
|
private function title(string $title, string $color): void |
|
275
|
|
|
{ |
|
276
|
|
|
$fg = $title === 'FAIL' ? 'default' : 'black'; |
|
277
|
|
|
|
|
278
|
|
|
$classParts = explode('\\', get_class($this->testCase)); |
|
279
|
|
|
|
|
280
|
|
|
// Removes `Tests` part |
|
281
|
|
|
array_shift($classParts); |
|
282
|
|
|
|
|
283
|
|
|
$highlightedPart = array_pop($classParts); |
|
284
|
|
|
$nonHighlightedPart = implode('\\', $classParts); |
|
285
|
|
|
$class = sprintf("\e[2m%s\e[22m<fg=white;options=bold>%s</>", "$nonHighlightedPart\\", $highlightedPart); |
|
286
|
|
|
|
|
287
|
|
|
$this->tests[0] = sprintf( |
|
288
|
|
|
"\n <fg=%s;bg=%s;options=bold> %s </><fg=default> %s</>", |
|
289
|
|
|
$fg, |
|
290
|
|
|
$color, |
|
291
|
|
|
$title, |
|
292
|
|
|
$class |
|
293
|
|
|
); |
|
294
|
|
|
|
|
295
|
|
|
$this->update(); |
|
296
|
|
|
} |
|
297
|
|
|
} |
|
298
|
|
|
|
|
299
|
|
|
|
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.