These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | namespace Robo; |
||
3 | |||
4 | use \CliGuy; |
||
5 | |||
6 | use Robo\Collection\Temporary; |
||
7 | use Robo\Exception\AbortTasksException; |
||
8 | |||
9 | class CollectionCest |
||
10 | { |
||
11 | public function _before(CliGuy $I) |
||
12 | { |
||
13 | $I->amInPath(codecept_data_dir().'sandbox'); |
||
14 | } |
||
15 | |||
16 | public function toRunMultipleTasksViaACollectionBuilder(CliGuy $I) |
||
17 | { |
||
18 | // This tests creating multiple tasks in a single builder, |
||
19 | // which implicitly adds them to a collection. To keep things |
||
20 | // simple, we are only going to use taskFilesystemStack. It |
||
21 | // would be possible, of course, to do these operations with |
||
22 | // a single FilesystemStack, but our goal is to test creating |
||
23 | // multiple tasks with a builder, and ensure that a propper |
||
24 | // collection is built. |
||
25 | $collection = $I->collectionBuilder(); |
||
26 | $result = $collection->taskFilesystemStack() |
||
27 | ->mkdir('a') |
||
28 | ->touch('a/a.txt') |
||
29 | ->rollback( |
||
30 | $I->taskDeleteDir('a') |
||
31 | ) |
||
32 | ->taskFilesystemStack() |
||
33 | ->mkdir('a/b') |
||
34 | ->touch('a/b/b.txt') |
||
35 | ->taskFilesystemStack() |
||
36 | ->mkdir('a/c') |
||
37 | ->touch('a/c/c.txt') |
||
38 | ->run(); |
||
39 | |||
40 | $I->assertEquals(0, $result->getExitCode(), $result->getMessage()); |
||
41 | |||
42 | // All of the tasks created by the builder should be added |
||
43 | // to a collection, and `run()` should run them all. |
||
44 | $I->seeDirFound('a'); |
||
45 | $I->seeFileFound('a/a.txt'); |
||
46 | $I->seeDirFound('a/b'); |
||
47 | $I->seeFileFound('a/b/b.txt'); |
||
48 | $I->seeDirFound('a/c'); |
||
49 | $I->seeFileFound('a/c/c.txt'); |
||
50 | } |
||
51 | |||
52 | public function toUseAWorkingDirWithACollectionBuilder(CliGuy $I) |
||
53 | { |
||
54 | // Run the same test with a working directory. The working |
||
55 | // directory path will point to a temporary directory which |
||
56 | // will be moved into place once the tasks complete. |
||
57 | $collection = $I->collectionBuilder(); |
||
58 | $workDirPath = $collection->workDir("build"); |
||
59 | $I->assertNotEquals("build", basename($workDirPath)); |
||
60 | $result = $collection->taskFilesystemStack() |
||
61 | ->mkdir("{$workDirPath}/a") |
||
62 | ->touch("{$workDirPath}/a/a.txt") |
||
63 | ->taskFilesystemStack() |
||
64 | ->mkdir("{$workDirPath}/a/b") |
||
65 | ->touch("{$workDirPath}/a/b/b.txt") |
||
66 | ->taskFilesystemStack() |
||
67 | ->mkdir("{$workDirPath}/a/c") |
||
68 | ->touch("{$workDirPath}/a/c/c.txt") |
||
69 | ->run(); |
||
70 | |||
71 | $I->assertEquals(0, $result->getExitCode(), $result->getMessage()); |
||
72 | |||
73 | // All of the tasks created by the builder should be added |
||
74 | // to a collection, and `run()` should run them all. |
||
75 | $I->seeDirFound('build/a'); |
||
76 | $I->seeFileFound('build/a/a.txt'); |
||
77 | $I->seeDirFound('build/a/b'); |
||
78 | $I->seeFileFound('build/a/b/b.txt'); |
||
79 | $I->seeDirFound('build/a/c'); |
||
80 | $I->seeFileFound('build/a/c/c.txt'); |
||
81 | } |
||
82 | |||
83 | View Code Duplication | public function toRollbackAfterFailureViaACollectionBuilder(CliGuy $I) |
|
84 | { |
||
85 | // This is like the previous test, toRunMultipleTasksViaACollectionBuilder, |
||
86 | // except we force an error at the end, and confirm that the |
||
87 | // rollback function is called. |
||
88 | $collection = $I->collectionBuilder(); |
||
89 | $result = $collection->taskFilesystemStack() |
||
90 | ->mkdir('j') |
||
91 | ->touch('j/j.txt') |
||
92 | ->rollback( |
||
93 | $I->taskDeleteDir('j') |
||
94 | ) |
||
95 | ->taskFilesystemStack() |
||
96 | ->mkdir('j/k') |
||
97 | ->touch('j/k/k.txt') |
||
98 | ->taskFilesystemStack() |
||
99 | ->mkdir('j/k/m') |
||
100 | ->touch('j/k/m/m.txt') |
||
101 | ->taskCopyDir(['doesNotExist' => 'copied']) |
||
102 | ->run(); |
||
103 | |||
104 | $I->assertEquals(1, $result->getExitCode(), $result->getMessage()); |
||
105 | |||
106 | // All of the tasks created by the builder should be added |
||
107 | // to a collection, and `run()` should run them all. |
||
108 | $I->dontSeeFileFound('q/q.txt'); |
||
109 | $I->dontSeeFileFound('j/j.txt'); |
||
110 | $I->dontSeeFileFound('j/k/k.txt'); |
||
111 | $I->dontSeeFileFound('j/k/m/m.txt'); |
||
112 | } |
||
113 | |||
114 | View Code Duplication | public function toAbortRollbackOrCompletion(CliGuy $I) |
|
0 ignored issues
–
show
|
|||
115 | { |
||
116 | // This is like the previous test, except we throw a ForcedException() |
||
117 | // inside the rollback to abort the rollback. |
||
118 | $collection = $I->collectionBuilder(); |
||
119 | $result = $collection->taskFilesystemStack() |
||
120 | ->mkdir('j') |
||
121 | ->touch('j/j.txt') |
||
122 | ->rollback( |
||
123 | $I->taskDeleteDir('j') |
||
124 | ) |
||
125 | ->rollbackCode(function () { |
||
126 | throw new AbortTasksException('Aborting rollback.'); |
||
127 | }) |
||
128 | ->taskFilesystemStack() |
||
129 | ->mkdir('j/k') |
||
130 | ->touch('j/k/k.txt') |
||
131 | ->taskFilesystemStack() |
||
132 | ->mkdir('j/k/m') |
||
133 | ->touch('j/k/m/m.txt') |
||
134 | ->taskCopyDir(['doesNotExist' => 'copied']) |
||
135 | ->run(); |
||
136 | |||
137 | $I->assertEquals(1, $result->getExitCode(), $result->getMessage()); |
||
138 | |||
139 | // All of the tasks created by the builder should be added |
||
140 | // to a collection, and `run()` should run them all. |
||
141 | $I->seeFileFound('j/j.txt'); |
||
142 | $I->seeFileFound('j/k/k.txt'); |
||
143 | $I->seeFileFound('j/k/m/m.txt'); |
||
144 | } |
||
145 | |||
146 | public function toRollbackAWorkingDir(CliGuy $I) |
||
147 | { |
||
148 | // Run the same test with a working directory. The working |
||
149 | // directory path will point to a temporary directory which |
||
150 | // will be moved into place once the tasks complete. |
||
151 | $collection = $I->collectionBuilder(); |
||
152 | $workDirPath = $collection->workDir("build"); |
||
153 | $I->assertNotEquals("build", basename($workDirPath)); |
||
154 | $result = $collection->taskFilesystemStack() |
||
155 | ->mkdir("{$workDirPath}/a") |
||
156 | ->touch("{$workDirPath}/a/a.txt") |
||
157 | ->taskFilesystemStack() |
||
158 | ->mkdir("{$workDirPath}/a/b") |
||
159 | ->touch("{$workDirPath}/a/b/b.txt") |
||
160 | ->taskFilesystemStack() |
||
161 | ->mkdir("{$workDirPath}/a/c") |
||
162 | ->touch("{$workDirPath}/a/c/c.txt") |
||
163 | ->taskCopyDir(['doesNotExist' => 'copied']) |
||
164 | ->run(); |
||
165 | |||
166 | $I->assertEquals(1, $result->getExitCode(), $result->getMessage()); |
||
167 | |||
168 | // All of the tasks created by the builder should be added |
||
169 | // to a collection, and `run()` should run them all. |
||
170 | $I->dontSeeFileFound('build/a'); |
||
171 | $I->dontSeeFileFound($workDirPath); |
||
172 | } |
||
173 | |||
174 | public function toBuildFilesViaAddIterable(CliGuy $I) |
||
175 | { |
||
176 | $processList = ['cats', 'dogs', 'sheep', 'fish', 'horses', 'cows']; |
||
177 | |||
178 | $collection = $I->collectionBuilder(); |
||
179 | $result = $collection |
||
180 | ->taskFilesystemStack() |
||
181 | ->mkdir('stuff') |
||
182 | ->taskForEach($processList) |
||
183 | ->withBuilder( |
||
184 | function ($builder, $key, $value) { |
||
185 | return $builder |
||
186 | ->taskFilesystemStack() |
||
187 | ->touch("stuff/{$value}.txt"); |
||
188 | } |
||
189 | ) |
||
190 | ->run(); |
||
191 | |||
192 | $I->assertEquals(0, $result->getExitCode(), $result->getMessage()); |
||
193 | |||
194 | $I->seeFileFound('stuff/cats.txt'); |
||
195 | $I->seeFileFound('stuff/dogs.txt'); |
||
196 | $I->seeFileFound('stuff/sheep.txt'); |
||
197 | $I->seeFileFound('stuff/fish.txt'); |
||
198 | $I->seeFileFound('stuff/horses.txt'); |
||
199 | $I->seeFileFound('stuff/cows.txt'); |
||
200 | } |
||
201 | |||
202 | public function toRollbackANestedCollection(CliGuy $I) |
||
203 | { |
||
204 | // This is like the previous test, toRunMultipleTasksViaACollectionBuilder, |
||
205 | // except we force an error at the end, and confirm that the |
||
206 | // rollback function is called. |
||
207 | $collection = $I->collectionBuilder(); |
||
208 | $collection->taskFilesystemStack() |
||
209 | ->mkdir('j') |
||
210 | ->touch('j/j.txt') |
||
211 | ->rollback( |
||
212 | $I->taskDeleteDir('j') |
||
213 | ) |
||
214 | ->taskFilesystemStack() |
||
215 | ->mkdir('j/k') |
||
216 | ->touch('j/k/k.txt') |
||
217 | ->taskFilesystemStack() |
||
218 | ->mkdir('j/k/m') |
||
219 | ->touch('j/k/m/m.txt'); |
||
220 | |||
221 | $result = $I->collectionBuilder() |
||
222 | ->taskFilesystemStack() |
||
223 | ->mkdir('q') |
||
224 | ->touch('q/q.txt') |
||
225 | ->addTask($collection) |
||
226 | ->taskCopyDir(['doesNotExist' => 'copied']) |
||
227 | ->run(); |
||
228 | |||
229 | $I->assertEquals(1, $result->getExitCode(), $result->getMessage()); |
||
230 | |||
231 | // All of the tasks created by the builder should be added |
||
232 | // to a collection, and `run()` should run them all. |
||
233 | $I->seeFileFound('q/q.txt'); |
||
234 | $I->dontSeeFileFound('j/j.txt'); |
||
235 | $I->dontSeeFileFound('j/k/k.txt'); |
||
236 | $I->dontSeeFileFound('j/k/m/m.txt'); |
||
237 | } |
||
238 | |||
239 | public function toRollbackInCorrectOrder(CliGuy $I) |
||
240 | { |
||
241 | $expected_order = [6,5,4,3,2,1]; |
||
242 | $actual_order = []; |
||
243 | $collection = $I->collectionBuilder(); |
||
244 | $collection->rollbackCode(function () use (&$actual_order) { |
||
245 | $actual_order[] = 1; |
||
246 | }); |
||
247 | $collection->rollbackCode(function () use (&$actual_order) { |
||
248 | $actual_order[] = 2; |
||
249 | }); |
||
250 | $collection->rollbackCode(function () use (&$actual_order) { |
||
251 | $actual_order[] = 3; |
||
252 | }); |
||
253 | // Add a nested collection with rollbacks. |
||
254 | $nested_collection = $I->collectionBuilder(); |
||
255 | $nested_collection->rollbackCode(function () use (&$actual_order) { |
||
256 | $actual_order[] = 4; |
||
257 | }); |
||
258 | $nested_collection->rollbackCode(function () use (&$actual_order) { |
||
259 | $actual_order[] = 5; |
||
260 | }); |
||
261 | $collection->addTask($nested_collection); |
||
262 | |||
263 | $collection->rollbackCode(function () use (&$actual_order) { |
||
264 | $actual_order[] = 6; |
||
265 | }); |
||
266 | $collection->addCode(function () { |
||
267 | return Result::EXITCODE_ERROR; |
||
268 | }); |
||
269 | $result = $collection->run(); |
||
0 ignored issues
–
show
$result is not used, you could remove the assignment.
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently. $myVar = 'Value';
$higher = false;
if (rand(1, 6) > 3) {
$higher = true;
} else {
$higher = false;
}
Both the
Loading history...
|
|||
270 | $I->assertEquals($expected_order, $actual_order); |
||
271 | } |
||
272 | |||
273 | View Code Duplication | public function toCreateDirViaCollection(CliGuy $I) |
|
274 | { |
||
275 | // Set up a collection to add tasks to |
||
276 | $collection = $I->collectionBuilder(); |
||
277 | |||
278 | // Set up a filesystem stack |
||
279 | $collection->taskFilesystemStack() |
||
280 | ->mkdir('log') |
||
281 | ->touch('log/error.txt'); |
||
282 | |||
283 | // FilesystemStack has not run yet, so file should not be found. |
||
284 | $I->dontSeeFileFound('log/error.txt'); |
||
285 | |||
286 | // Run the task collection; now the files should be present |
||
287 | $collection->run(); |
||
288 | $I->seeFileFound('log/error.txt'); |
||
289 | $I->seeDirFound('log'); |
||
290 | } |
||
291 | |||
292 | public function toUseATmpDirAndConfirmItIsDeleted(CliGuy $I) |
||
293 | { |
||
294 | // Set up a collection to add tasks to |
||
295 | $collection = $I->collectionBuilder(); |
||
296 | |||
297 | // Get a temporary directory to work in. Note that we get a |
||
298 | // name back, but the directory is not created until the task |
||
299 | // runs. This technically is not thread-safe, but we create |
||
300 | // a random name, so it is unlikely to conflict. |
||
301 | $tmpPath = $collection->tmpDir(); |
||
302 | |||
303 | // Set up a filesystem stack, but use a collection to defer execution |
||
304 | $collection->taskFilesystemStack() |
||
305 | ->mkdir("$tmpPath/tmp") |
||
306 | ->touch("$tmpPath/tmp/error.txt") |
||
307 | ->rename("$tmpPath/tmp", "$tmpPath/log"); |
||
308 | |||
309 | // Copy our tmp directory to a location that is not transient |
||
310 | $collection->taskCopyDir([$tmpPath => 'copied']); |
||
311 | |||
312 | // FilesystemStack has not run yet, so no files should be found. |
||
313 | $I->dontSeeFileFound("$tmpPath/tmp/error.txt"); |
||
314 | $I->dontSeeFileFound("$tmpPath/log/error.txt"); |
||
315 | $I->dontSeeFileFound('copied/log/error.txt'); |
||
316 | |||
317 | // Run the task collection |
||
318 | $result = $collection->run(); |
||
319 | $I->assertEquals(0, $result->getExitCode(), $result->getMessage()); |
||
320 | $I->assertEquals($result['path'], $tmpPath, "Tmp dir result matches accessor."); |
||
321 | |||
322 | // The file 'error.txt' should have been copied into the "copied" dir. |
||
323 | // This also proves that the tmp directory was created. |
||
324 | $I->seeFileFound('copied/log/error.txt'); |
||
325 | // $tmpPath should be deleted after $collection->run() completes. |
||
326 | $I->dontSeeFileFound("$tmpPath/tmp/error.txt"); |
||
327 | $I->dontSeeFileFound("$tmpPath/log/error.txt"); |
||
328 | $I->dontSeeFileFound("$tmpPath"); |
||
329 | } |
||
330 | |||
331 | public function toUseATmpDirAndChangeWorkingDirectory(CliGuy $I) |
||
332 | { |
||
333 | // Set up a collection to add tasks to |
||
334 | $collection = $I->collectionBuilder(); |
||
335 | |||
336 | $cwd = getcwd(); |
||
337 | |||
338 | $tmpPath = $collection->taskTmpDir() |
||
339 | ->cwd() |
||
340 | ->getPath(); |
||
341 | |||
342 | // Set up a filesystem stack, but use a collection to defer execution. |
||
343 | // Note that since we used 'cwd()' above, the relative file paths |
||
344 | // used below will be inside the temporary directory. |
||
345 | $collection->taskFilesystemStack() |
||
346 | ->mkdir("log") |
||
347 | ->touch("log/error.txt"); |
||
348 | |||
349 | // Copy our tmp directory to a location that is not transient |
||
350 | $collection->taskCopyDir(['log' => "$cwd/copied2"]); |
||
351 | |||
352 | // FilesystemStack has not run yet, so no files should be found. |
||
353 | $I->dontSeeFileFound("$tmpPath/log/error.txt"); |
||
354 | $I->dontSeeFileFound('$cwd/copied2/log/error.txt'); |
||
355 | |||
356 | // Run the task collection |
||
357 | $result = $collection->run(); |
||
358 | $I->assertEquals(0, $result->getExitCode(), $result->getMessage()); |
||
359 | |||
360 | // The file 'error.txt' should have been copied into the "copied" dir |
||
361 | $I->seeFileFound("$cwd/copied2/error.txt"); |
||
362 | // $tmpPath should be deleted after $collection->run() completes. |
||
363 | $I->dontSeeFileFound("$tmpPath/log/error.txt"); |
||
364 | // Make sure that 'log' was created in the temporary directory, not |
||
365 | // at the current working directory. |
||
366 | $I->dontSeeFileFound("$cwd/log/error.txt"); |
||
367 | |||
368 | // Make sure that our working directory was restored. |
||
369 | $finalWorkingDir = getcwd(); |
||
370 | $I->assertEquals($cwd, $finalWorkingDir); |
||
371 | } |
||
372 | |||
373 | public function toCreateATmpFileAndConfirmItIsDeleted(CliGuy $I) |
||
374 | { |
||
375 | // Set up a collection to add tasks to |
||
376 | $collection = $I->collectionBuilder(); |
||
377 | |||
378 | // Write to a temporary file. Note that we can get the path |
||
379 | // to the tempoary file that will be created, even though the |
||
380 | // the file is not created until the task collecction runs. |
||
381 | $tmpPath = $collection->taskTmpFile('tmp', '.txt') |
||
382 | ->line("This is a test file") |
||
383 | ->getPath(); |
||
384 | |||
385 | // Copy our tmp directory to a location that is not transient |
||
386 | $collection->taskFilesystemStack() |
||
387 | ->copy($tmpPath, 'copied.txt'); |
||
388 | |||
389 | // FilesystemStack has not run yet, so no files should be found. |
||
390 | $I->dontSeeFileFound("$tmpPath"); |
||
391 | $I->dontSeeFileFound('copied.txt'); |
||
392 | |||
393 | // Run the task collection |
||
394 | $result = $collection->run(); |
||
395 | $I->assertEquals(0, $result->getExitCode(), $result->getMessage()); |
||
396 | |||
397 | // The file 'copied.txt' should have been copied from the tmp file |
||
398 | $I->seeFileFound('copied.txt'); |
||
399 | // $tmpPath should be deleted after $collection->run() completes. |
||
400 | $I->dontSeeFileFound("$tmpPath"); |
||
401 | } |
||
402 | |||
403 | public function toUseATmpDirWithAlternateSyntax(CliGuy $I) |
||
404 | { |
||
405 | $collection = $I->collectionBuilder(); |
||
406 | |||
407 | // This test is equivalent to toUseATmpDirAndConfirmItIsDeleted, |
||
408 | // but uses a different technique to create a collection of tasks. |
||
409 | $tmpPath = $collection->tmpDir(); |
||
410 | |||
411 | // Now, rather than creating the tasks with a collection builder, |
||
412 | // which automatically adds the tasks to the collection as they are |
||
413 | // created, we will instead create them individually and then add |
||
414 | // them to the collection via the addTaskList() method. |
||
415 | $result = $collection->addTaskList( |
||
416 | [ |
||
417 | $I->taskFilesystemStack()->mkdir("$tmpPath/log")->touch("$tmpPath/log/error.txt"), |
||
418 | $I->taskCopyDir([$tmpPath => 'copied3']), |
||
419 | ] |
||
420 | )->run(); |
||
421 | |||
422 | // The results of this operation should be the same. |
||
423 | $I->assertEquals(0, $result->getExitCode(), $result->getMessage()); |
||
424 | $I->seeFileFound('copied3/log/error.txt'); |
||
425 | $I->dontSeeFileFound("$tmpPath/log/error.txt"); |
||
426 | } |
||
427 | |||
428 | public function toCreateATmpDirWithoutACollection(CliGuy $I) |
||
429 | { |
||
430 | // Create a temporary directory, using our function name as |
||
431 | // the prefix for the directory name. |
||
432 | $tmpDirTask = $I->taskTmpDir(__FUNCTION__); |
||
433 | $tmpPath = $tmpDirTask->getPath(); |
||
434 | $I->dontSeeFileFound($tmpPath); |
||
435 | $tmpDirTask->run(); |
||
436 | $I->seeDirFound($tmpPath); |
||
437 | // Creating a temporary directory without a task collection will |
||
438 | // cause the temporary directory to be deleted when the program |
||
439 | // terminates. We can force it to clean up sooner by calling |
||
440 | // TransientManager::complete(); note that this deletes ALL global tmp |
||
441 | // directories, so this is not thread-safe! Useful in tests, though. |
||
442 | Temporary::complete(); |
||
443 | $I->dontSeeFileFound($tmpPath); |
||
444 | } |
||
445 | |||
446 | public function toCreateATmpDirUsingShortcut(CliGuy $I) |
||
447 | { |
||
448 | // Create a temporary directory, using our function name as |
||
449 | // the prefix for the directory name. |
||
450 | $tmpPath = $I->shortcutTmpDir(__FUNCTION__); |
||
451 | $I->seeDirFound($tmpPath); |
||
452 | // Creating a temporary directory without a task collection will |
||
453 | // cause the temporary directory to be deleted when the program |
||
454 | // terminates. We can force it to clean up sooner by calling |
||
455 | // TransientManager::complete(); note that this deletes ALL global tmp |
||
456 | // directories, so this is not thread-safe! Useful in tests, though. |
||
457 | Temporary::complete(); |
||
458 | $I->dontSeeFileFound($tmpPath); |
||
459 | } |
||
460 | |||
461 | public function toThrowAnExceptionAndConfirmItIsCaught(CliGuy $I) |
||
462 | { |
||
463 | $collection = $I->getContainer()->get('collection'); |
||
464 | |||
465 | $collection->addCode( |
||
466 | function () { |
||
467 | throw new \RuntimeException('Error'); |
||
468 | } |
||
469 | ); |
||
470 | $result = $collection->run(); |
||
471 | $I->assertEquals('Error', $result->getMessage()); |
||
472 | $I->assertEquals(1, $result->getExitCode()); |
||
473 | } |
||
474 | |||
475 | public function toChainData(CliGuy $I) |
||
476 | { |
||
477 | $collection = $I->collectionBuilder(); |
||
478 | |||
479 | $result = $collection |
||
480 | ->taskValueProvider() |
||
481 | ->provideMessage('1st') // Sets Result's message to '1st' |
||
482 | ->storeState('one') // Copy Result's message to $state['one'] |
||
483 | ->taskValueProvider() |
||
484 | ->provideMessage('2nd') |
||
485 | ->storeState('two') |
||
486 | ->taskValueProvider() |
||
487 | ->deferTaskConfiguration('provideItem', 'one') // Same as ->proivdeItem($state['one']), but runs immediately before this task's run() method. |
||
488 | ->deferTaskConfiguration('provideMessage', 'two') |
||
489 | ->storeState('final') |
||
490 | ->run(); |
||
491 | |||
492 | $state = $collection->getState(); |
||
493 | $I->assertEquals('1st', $state['one']); |
||
494 | $I->assertEquals('1st', $state['item']); |
||
495 | $I->assertEquals('2nd', $state['final']); |
||
496 | } |
||
497 | } |
||
498 |
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.