Passed
Pull Request — master (#5)
by Wilmer
01:25
created

FileHelperTest   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 768
Duplicated Lines 0 %

Importance

Changes 8
Bugs 0 Features 2
Metric Value
eloc 382
c 8
b 0
f 2
dl 0
loc 768
rs 8.64
wmc 47

23 Methods

Rating   Name   Duplication   Size   Complexity  
A isChmodReliable() 0 9 1
A testCreateDirectoryPermissions() 0 11 2
A testCopyDirectory() 0 24 2
A testCopyDirWithSameName() 0 13 1
A testNormalizePath() 0 29 1
B testsCopyDirectoryFilterPathOnly() 0 85 5
A testRemoveDirectorySymlinks1() 0 39 1
A getMode() 0 3 1
A testCopyDirectoryToItself() 0 12 1
A tearDown() 0 3 1
A testRemoveDirectory() 0 24 1
A testCopyDirectoryPermissions() 0 38 3
A testsCopyDirectoryFilterPath() 0 47 3
A createFileStructure() 0 18 6
A testRemoveDirectorySymlinks2() 0 39 1
A testCopyDirectoryRecursive() 0 39 3
A testCopyDirToAnotherWithSameName() 0 13 1
B testsCopyDirectoryFilterPathExcept() 0 88 5
A testCopyDirToSubdirOfItself() 0 11 1
A assertFileMode() 0 4 1
A testCopyDirectoryNotRecursive() 0 34 3
A setUp() 0 8 2
A testCreateDirectory() 0 7 1

How to fix   Complexity   

Complex Class

Complex classes like FileHelperTest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FileHelperTest, and based on these observations, apply Extract Interface, too.

1
<?php
2
declare(strict_types = 1);
3
4
namespace Yiisoft\Files\Tests;
5
6
use PHPUnit\Framework\TestCase;
7
use Yiisoft\Files\FileHelper;
8
9
/**
10
 * File helper tests class.
11
 */
12
final class FileHelperTest extends TestCase
13
{
14
    /**
15
     * @var string test files path.
16
     */
17
    private $testFilePath = '';
18
19
    public function setUp(): void
20
    {
21
        $this->testFilePath = FileHelper::normalizePath(sys_get_temp_dir() . '/' . get_class($this));
22
        
23
        FileHelper::createDirectory($this->testFilePath, 0777);
24
25
        if (!file_exists($this->testFilePath)) {
26
            $this->markTestIncomplete('Unit tests runtime directory should have writable permissions!');
27
        }
28
    }
29
30
    public function tearDown(): void
31
    {
32
        FileHelper::removeDirectory($this->testFilePath);
33
    }
34
35
    /**
36
     * Create directory.
37
     *
38
     * @return void
39
     */
40
    public function testCreateDirectory(): void
41
    {
42
        $basePath = $this->testFilePath;
43
        $directory = $basePath . '/test_dir_level_1/test_dir_level_2';
44
        $this->assertTrue(FileHelper::createDirectory($directory), 'FileHelper::createDirectory should return true if directory was created!');
45
        $this->assertFileExists($directory, 'Unable to create directory recursively!');
46
        $this->assertTrue(FileHelper::createDirectory($directory), 'FileHelper::createDirectory should return true for already existing directories!');
47
    }
48
49
    /**
50
     * Create directory permissions.
51
     *
52
     * @return void
53
     */
54
    public function testCreateDirectoryPermissions(): void
55
    {
56
        if (!$this->isChmodReliable()) {
57
            $this->markTestSkipped('Skipping test since chmod is not reliable in this environment.');
58
        }
59
60
        $basePath = $this->testFilePath;
61
        $dirName = $basePath . '/test_dir_perms';
62
63
        $this->assertTrue(FileHelper::createDirectory($dirName, 0700));
64
        $this->assertFileMode(0700, $dirName);
65
    }
66
67
    /**
68
     * Remove directory.
69
     *
70
     * @return void
71
     */
72
    public function testRemoveDirectory(): void
73
    {
74
        $dirName = 'test_dir_for_remove';
75
76
        $this->createFileStructure([
77
            $dirName => [
78
                'file1.txt' => 'file 1 content',
79
                'file2.txt' => 'file 2 content',
80
                'test_sub_dir' => [
81
                    'sub_dir_file_1.txt' => 'sub dir file 1 content',
82
                    'sub_dir_file_2.txt' => 'sub dir file 2 content',
83
                ],
84
            ],
85
        ]);
86
87
        $basePath = $this->testFilePath;
88
        $dirName = $basePath . '/' . $dirName;
89
90
        FileHelper::removeDirectory($dirName);
91
92
        $this->assertFileNotExists($dirName, 'Unable to remove directory!');
93
94
        // should be silent about non-existing directories
95
        FileHelper::removeDirectory($basePath . '/nonExisting');
96
    }
97
98
    /**
99
     * Remove directory symlinks1.
100
     *
101
     * @return void
102
     */
103
    public function testRemoveDirectorySymlinks1(): void
104
    {
105
        $dirName = 'remove-directory-symlinks-1';
106
107
        $this->createFileStructure([
108
            $dirName => [
109
                'file' => 'Symlinked file.',
110
                'directory' => [
111
                    'standard-file-1' => 'Standard file 1.',
112
                ],
113
                'symlinks' => [
114
                    'standard-file-2' => 'Standard file 2.',
115
                    'symlinked-file' => ['symlink', '../file'],
116
                    'symlinked-directory' => ['symlink', '../directory'],
117
                ],
118
            ],
119
        ]);
120
121
        $basePath = $this->testFilePath . '/' . $dirName . '/';
122
123
        $this->assertFileExists($basePath . 'file');
124
        $this->assertDirectoryExists($basePath . 'directory');
125
        $this->assertFileExists($basePath . 'directory/standard-file-1');
126
        $this->assertDirectoryExists($basePath . 'symlinks');
127
        $this->assertFileExists($basePath . 'symlinks/standard-file-2');
128
        $this->assertFileExists($basePath . 'symlinks/symlinked-file');
129
        $this->assertDirectoryExists($basePath . 'symlinks/symlinked-directory');
130
        $this->assertFileExists($basePath . 'symlinks/symlinked-directory/standard-file-1');
131
132
        FileHelper::removeDirectory($basePath . 'symlinks');
133
134
        $this->assertFileExists($basePath . 'file');
135
        $this->assertDirectoryExists($basePath . 'directory');
136
        $this->assertFileExists($basePath . 'directory/standard-file-1'); // symlinked directory still have it's file
137
        $this->assertDirectoryNotExists($basePath . 'symlinks');
138
        $this->assertFileNotExists($basePath . 'symlinks/standard-file-2');
139
        $this->assertFileNotExists($basePath . 'symlinks/symlinked-file');
140
        $this->assertDirectoryNotExists($basePath . 'symlinks/symlinked-directory');
141
        $this->assertFileNotExists($basePath . 'symlinks/symlinked-directory/standard-file-1');
142
    }
143
144
    /**
145
     * Remove directory symlinks2.
146
     *
147
     * @return void
148
     */
149
    public function testRemoveDirectorySymlinks2(): void
150
    {
151
        $dirName = 'remove-directory-symlinks-2';
152
153
        $this->createFileStructure([
154
            $dirName => [
155
                'file' => 'Symlinked file.',
156
                'directory' => [
157
                    'standard-file-1' => 'Standard file 1.',
158
                ],
159
                'symlinks' => [
160
                    'standard-file-2' => 'Standard file 2.',
161
                    'symlinked-file' => ['symlink', '../file'],
162
                    'symlinked-directory' => ['symlink', '../directory'],
163
                ],
164
            ],
165
        ]);
166
167
        $basePath = $this->testFilePath . '/' . $dirName . '/';
168
169
        $this->assertFileExists($basePath . 'file');
170
        $this->assertDirectoryExists($basePath . 'directory');
171
        $this->assertFileExists($basePath . 'directory/standard-file-1');
172
        $this->assertDirectoryExists($basePath . 'symlinks');
173
        $this->assertFileExists($basePath . 'symlinks/standard-file-2');
174
        $this->assertFileExists($basePath . 'symlinks/symlinked-file');
175
        $this->assertDirectoryExists($basePath . 'symlinks/symlinked-directory');
176
        $this->assertFileExists($basePath . 'symlinks/symlinked-directory/standard-file-1');
177
178
        FileHelper::removeDirectory($basePath . 'symlinks', ['traverseSymlinks' => true]);
179
180
        $this->assertFileExists($basePath . 'file');
181
        $this->assertDirectoryExists($basePath . 'directory');
182
        $this->assertFileNotExists($basePath . 'directory/standard-file-1'); // symlinked directory doesn't have it's file now
183
        $this->assertDirectoryNotExists($basePath . 'symlinks');
184
        $this->assertFileNotExists($basePath . 'symlinks/standard-file-2');
185
        $this->assertFileNotExists($basePath . 'symlinks/symlinked-file');
186
        $this->assertDirectoryNotExists($basePath . 'symlinks/symlinked-directory');
187
        $this->assertFileNotExists($basePath . 'symlinks/symlinked-directory/standard-file-1');
188
    }
189
190
    /**
191
     * Normalize path.
192
     *
193
     * @return void
194
     */
195
    public function testNormalizePath(): void
196
    {
197
        $this->assertEquals('/a/b', FileHelper::normalizePath('//a\\b/'));
198
        $this->assertEquals('/b/c', FileHelper::normalizePath('/a/../b/c'));
199
        $this->assertEquals('/c', FileHelper::normalizePath('/a\\b/../..///c'));
200
        $this->assertEquals('/c', FileHelper::normalizePath('/a/.\\b//../../c'));
201
        $this->assertEquals('c', FileHelper::normalizePath('/a/.\\b/../..//../c'));
202
        $this->assertEquals('../c', FileHelper::normalizePath('//a/.\\b//..//..//../../c'));
203
204
        // relative paths
205
        $this->assertEquals('.', FileHelper::normalizePath('.'));
206
        $this->assertEquals('.', FileHelper::normalizePath('./'));
207
        $this->assertEquals('a', FileHelper::normalizePath('.\\a'));
208
        $this->assertEquals('a/b', FileHelper::normalizePath('./a\\b'));
209
        $this->assertEquals('.', FileHelper::normalizePath('./a\\../'));
210
        $this->assertEquals('../../a', FileHelper::normalizePath('../..\\a'));
211
        $this->assertEquals('../../a', FileHelper::normalizePath('../..\\a/../a'));
212
        $this->assertEquals('../../b', FileHelper::normalizePath('../..\\a/../b'));
213
        $this->assertEquals('../a', FileHelper::normalizePath('./..\\a'));
214
        $this->assertEquals('../a', FileHelper::normalizePath('././..\\a'));
215
        $this->assertEquals('../a', FileHelper::normalizePath('./..\\a/../a'));
216
        $this->assertEquals('../b', FileHelper::normalizePath('./..\\a/../b'));
217
218
        // Windows file system may have paths for network shares that start with two backslashes. These two backslashes
219
        // should not be touched.
220
        // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
221
        // https://github.com/yiisoft/yii2/issues/13034
222
        $this->assertEquals('\\\\server/share/path/file', FileHelper::normalizePath('\\\\server\share\path\file'));
223
        $this->assertEquals('\\\\server/share/path/file', FileHelper::normalizePath('\\\\server\share\path//file'));
224
    }
225
226
    /**
227
     * Copy directory.
228
     *
229
     * @depends testCreateDirectory
230
     *
231
     * @return void
232
     */
233
    public function testCopyDirectory(): void
234
    {
235
        $source = 'test_src_dir';
236
        $files = [
237
            'file1.txt' => 'file 1 content',
238
            'file2.txt' => 'file 2 content',
239
        ];
240
241
        $this->createFileStructure([
242
            $source => $files,
243
        ]);
244
245
        $basePath = $this->testFilePath;
246
        $source = $basePath . '/' . $source;
247
        $destination = $basePath . '/test_dst_dir';
248
249
        FileHelper::copyDirectory($source, $destination);
250
251
        $this->assertFileExists($destination, 'Destination directory does not exist!');
252
253
        foreach ($files as $name => $content) {
254
            $fileName = $destination . '/' . $name;
255
            $this->assertFileExists($fileName);
256
            $this->assertStringEqualsFile($fileName, $content, 'Incorrect file content!');
257
        }
258
    }
259
260
    /**
261
     * Copy directory recursive.
262
     *
263
     * @return void
264
     */
265
    public function testCopyDirectoryRecursive(): void
266
    {
267
        $source = 'test_src_dir_rec';
268
        $structure = [
269
            'directory1' => [
270
                'file1.txt' => 'file 1 content',
271
                'file2.txt' => 'file 2 content',
272
            ],
273
            'directory2' => [
274
                'file3.txt' => 'file 3 content',
275
                'file4.txt' => 'file 4 content',
276
            ],
277
            'file5.txt' => 'file 5 content',
278
        ];
279
280
        $this->createFileStructure([
281
            $source => $structure,
282
        ]);
283
284
        $basePath = $this->testFilePath;
285
        $source = $basePath . '/' . $source;
286
        $destination = $basePath . '/test_dst_dir';
287
288
        FileHelper::copyDirectory($source, $destination);
289
290
        $this->assertFileExists($destination, 'Destination directory does not exist!');
291
292
        $checker = function ($structure, $dstDirName) use (&$checker) {
293
            foreach ($structure as $name => $content) {
294
                if (is_array($content)) {
295
                    $checker($content, $dstDirName . '/' . $name);
296
                } else {
297
                    $fileName = $dstDirName . '/' . $name;
298
                    $this->assertFileExists($fileName);
299
                    $this->assertStringEqualsFile($fileName, $content, 'Incorrect file content!');
300
                }
301
            }
302
        };
303
        $checker($structure, $destination);
304
    }
305
306
    /**
307
     * Copy directory not recursive.
308
     *
309
     * @return void
310
     */
311
    public function testCopyDirectoryNotRecursive(): void
312
    {
313
        $source = 'test_src_dir_not_rec';
314
        $structure = [
315
            'directory1' => [
316
                'file1.txt' => 'file 1 content',
317
                'file2.txt' => 'file 2 content',
318
            ],
319
            'directory2' => [
320
                'file3.txt' => 'file 3 content',
321
                'file4.txt' => 'file 4 content',
322
            ],
323
            'file5.txt' => 'file 5 content',
324
        ];
325
326
        $this->createFileStructure([
327
            $source => $structure,
328
        ]);
329
330
        $basePath = $this->testFilePath;
331
        $source = $basePath . '/' . $source;
332
        $destination = $basePath . '/' . 'test_dst_dir';
333
334
        FileHelper::copyDirectory($source, $destination, ['recursive' => false]);
335
336
        $this->assertFileExists($destination, 'Destination directory does not exist!');
337
338
        foreach ($structure as $name => $content) {
339
            $fileName = $destination . '/' . $name;
340
            if (is_array($content)) {
341
                $this->assertFileNotExists($fileName);
342
            } else {
343
                $this->assertFileExists($fileName);
344
                $this->assertStringEqualsFile($fileName, $content, 'Incorrect file content!');
345
            }
346
        }
347
    }
348
349
    /**
350
     * Copy directory permissions.
351
     *
352
     * @depends testCopyDirectory
353
     *
354
     * @return void
355
     */
356
    public function testCopyDirectoryPermissions(): void
357
    {
358
        if (!$this->isChmodReliable()) {
359
            $this->markTestSkipped('Skipping test since chmod is not reliable in this environment.');
360
        }
361
362
        $isWindows = DIRECTORY_SEPARATOR === '\\';
363
364
        if ($isWindows) {
365
            $this->markTestSkipped('Skipping tests on Windows because fileperms() always return 0777.');
366
        }
367
368
        $source = 'test_src_dir';
369
        $subDirectory = 'test_sub_dir';
370
        $fileName = 'test_file.txt';
371
372
        $this->createFileStructure([
373
            $source => [
374
                $subDirectory => [],
375
                $fileName => 'test file content',
376
            ],
377
        ]);
378
379
        $basePath = $this->testFilePath;
380
        $source = $basePath . '/' . $source;
381
        $destination = $basePath . '/test_dst_dir';
382
        $directoryMode = 0755;
383
        $fileMode = 0755;
384
        $options = [
385
            'dirMode' => $directoryMode,
386
            'fileMode' => $fileMode,
387
        ];
388
389
        FileHelper::copyDirectory($source, $destination, $options);
390
391
        $this->assertFileMode($directoryMode, $destination, 'Destination directory has wrong mode!');
392
        $this->assertFileMode($directoryMode, $destination . '/' . $subDirectory, 'Copied sub directory has wrong mode!');
393
        $this->assertFileMode($fileMode, $destination . '/' . $fileName, 'Copied file has wrong mode!');
394
    }
395
396
    /**
397
     * Copy directory to it self.
398
     *
399
     * @see https://github.com/yiisoft/yii2/issues/10710
400
     *
401
     * @return void
402
     */
403
    public function testCopyDirectoryToItself(): void
404
    {
405
        $directoryName = 'test_dir';
406
407
        $this->createFileStructure([
408
            $directoryName => [],
409
        ]);
410
        $this->expectException(\InvalidArgumentException::class);
411
412
        $directoryName = $this->testFilePath . '/test_dir';
413
414
        FileHelper::copyDirectory($directoryName, $directoryName);
415
    }
416
417
    /**
418
     * Copy directory to sudirectory of it self.
419
     *
420
     * @see https://github.com/yiisoft/yii2/issues/10710
421
     *
422
     * @return void
423
     */
424
    public function testCopyDirToSubdirOfItself(): void
425
    {
426
        $this->createFileStructure([
427
            'data' => [],
428
            'backup' => ['data' => []],
429
        ]);
430
        $this->expectException(\InvalidArgumentException::class);
431
432
        FileHelper::copyDirectory(
433
            $this->testFilePath . '/backup',
434
            $this->testFilePath . '/backup/data'
435
        );
436
    }
437
438
    /**
439
     * Copy directory to another with same name.
440
     *
441
     * @see https://github.com/yiisoft/yii2/issues/10710
442
     *
443
     * @return void
444
     */
445
    public function testCopyDirToAnotherWithSameName(): void
446
    {
447
        $this->createFileStructure([
448
            'data' => [],
449
            'backup' => ['data' => []],
450
        ]);
451
452
        FileHelper::copyDirectory(
453
            $this->testFilePath . '/data',
454
            $this->testFilePath . '/backup/data'
455
        );
456
457
        $this->assertFileExists($this->testFilePath . '/backup/data');
458
    }
459
460
    /**
461
     * Copy directory with same name.
462
     *
463
     * @see https://github.com/yiisoft/yii2/issues/10710
464
     *
465
     * @return void
466
     */
467
    public function testCopyDirWithSameName(): void
468
    {
469
        $this->createFileStructure([
470
            'data' => [],
471
            'data-backup' => [],
472
        ]);
473
474
        FileHelper::copyDirectory(
475
            $this->testFilePath . '/data',
476
            $this->testFilePath . '/data-backup'
477
        );
478
479
        $this->assertTrue(true, 'no error');
480
    }
481
482
    public function testsCopyDirectoryFilterPath()
483
    {
484
        $source = 'boostrap4';
485
486
        $structure = [
487
            'css' => [
488
                'bootstrap.css'           => 'file 1 content',
489
                'bootstrap.css.map'       => 'file 2 content',
490
                'bootstrap.min.css'       => 'file 3 content',
491
                'bootstrap.min.css.map'   => 'file 4 content'
492
            ],
493
            'js' => [
494
                'bootstrap.js'            => 'file 5 content',
495
                'bootstrap.bundle.js'     => 'file 6 content',
496
                'bootstrap.bundle.js.map' => 'file 7 content',
497
                'bootstrap.min.js'        => 'file 8 content'
498
            ]
499
        ];
500
501
        $this->createFileStructure([
502
            $source => $structure,
503
        ]);
504
505
        $basePath = $this->testFilePath;
506
        $source = $basePath . '/' . $source;
507
        $destination = $basePath . '/assets';
508
509
        // without filter options return all directory.
510
        $options = [];
511
512
        FileHelper::copyDirectory($source, $destination, $options);
513
514
        $this->assertFileExists($destination, 'Destination directory does not exist!');
515
516
        $checker = function ($structure, $dstDirName) use (&$checker) {
517
            foreach ($structure as $name => $content) {
518
                if (is_array($content)) {
519
                    $checker($content, $dstDirName . '/' . $name);
520
                } else {
521
                    $fileName = $dstDirName . '/' . $name;
522
                    $this->assertFileExists($fileName);
523
                    $this->assertStringEqualsFile($fileName, $content, 'Incorrect file content!');
524
                }
525
            }
526
        };
527
528
        $checker($structure, $destination);
529
    }
530
531
    public function testsCopyDirectoryFilterPathOnly()
532
    {
533
        $source = 'boostrap4';
534
535
        $structure = [
536
            'css' => [
537
                'bootstrap.css'           => 'file 1 content',
538
                'bootstrap.css.map'       => 'file 2 content',
539
                'bootstrap.min.css'       => 'file 3 content',
540
                'bootstrap.min.css.map'   => 'file 4 content'
541
            ],
542
            'js' => [
543
                'bootstrap.js'            => 'file 5 content',
544
                'bootstrap.bundle.js'     => 'file 6 content',
545
                'bootstrap.bundle.js.map' => 'file 7 content',
546
                'bootstrap.min.js'        => 'file 8 content'
547
            ]
548
        ];
549
550
        $exist = [
0 ignored issues
show
Unused Code introduced by
The assignment to $exist is dead and can be removed.
Loading history...
551
            'css' => [
552
                'bootstrap.css'           => 'file 1 content',
553
                'bootstrap.min.css'       => 'file 3 content',
554
            ]
555
        ];
556
557
        $noexist = [
0 ignored issues
show
Unused Code introduced by
The assignment to $noexist is dead and can be removed.
Loading history...
558
            'css' => [
559
                'bootstrap.css.map'       => 'file 2 content',
560
                'bootstrap.min.css.map'   => 'file 4 content'
561
            ],
562
            'js' => [
563
                'bootstrap.js'            => 'file 5 content',
564
                'bootstrap.bundle.js'     => 'file 6 content',
565
                'bootstrap.bundle.js.map' => 'file 7 content',
566
                'bootstrap.min.js'        => 'file 8 content'
567
            ]
568
        ];
569
570
        $this->createFileStructure([
571
            $source => $structure,
572
        ]);
573
574
        $basePath = $this->testFilePath;
575
        $source = $basePath . '/' . $source;
576
        $destination = $basePath . '/assets';
577
578
        // without filter options return all directory.
579
        $options = [
580
            // options default false AssetManager
581
            'copyEmptyDirectories' => false,
582
            'only' => [
583
                'css/*.css',
584
            ]
585
        ];
586
587
        FileHelper::copyDirectory($source, $destination, $options);
588
589
        $this->assertFileExists($destination, 'Destination directory does not exist!');
590
591
        $exist = function ($exist, $dstDirName) use (&$checker) {
592
            foreach ($exist as $name => $content) {
593
                if (is_array($content)) {
594
                    $checker($content, $dstDirName . '/' . $name);
595
                } else {
596
                    $fileName = $dstDirName . '/' . $name;
597
                    $this->assertFileExists($fileName);
598
                    $this->assertStringEqualsFile($fileName, $content, 'Incorrect file content!');
599
                }
600
            }
601
        };
602
        $exist($exist, $destination);
603
604
        $noexist = function ($noexist, $dstDirName) use (&$checker) {
605
            foreach ($noexist as $name => $content) {
606
                if (is_array($content)) {
607
                    $checker($content, $dstDirName . '/' . $name);
608
                } else {
609
                    $fileName = $dstDirName . '/' . $name;
610
                    $this->assertFileNotExists($fileName);
611
                    $this->assertStringEqualsFile($fileName, $content, 'Incorrect file content!');
612
                }
613
            }
614
        };
615
        $noexist($noexist, $destination);
616
    }
617
618
    public function testsCopyDirectoryFilterPathExcept()
619
    {
620
        $source = 'boostrap4';
621
622
        $structure = [
623
            'css' => [
624
                'bootstrap.css'           => 'file 1 content',
625
                'bootstrap.css.map'       => 'file 2 content',
626
                'bootstrap.min.css'       => 'file 3 content',
627
                'bootstrap.min.css.map'   => 'file 4 content'
628
            ],
629
            'js' => [
630
                'bootstrap.js'            => 'file 5 content',
631
                'bootstrap.bundle.js'     => 'file 6 content',
632
                'bootstrap.bundle.js.map' => 'file 7 content',
633
                'bootstrap.min.js'        => 'file 8 content'
634
            ]
635
        ];
636
637
        $exist = [
0 ignored issues
show
Unused Code introduced by
The assignment to $exist is dead and can be removed.
Loading history...
638
            'css' => [
639
                'bootstrap.css'           => 'file 1 content',
640
            ]
641
        ];
642
643
        $noexist = [
0 ignored issues
show
Unused Code introduced by
The assignment to $noexist is dead and can be removed.
Loading history...
644
            'css' => [
645
                'bootstrap.css.map'       => 'file 2 content',
646
                'bootstrap.min.css'       => 'file 3 content',
647
                'bootstrap.min.css.map'   => 'file 4 content'
648
            ],
649
            'js' => [
650
                'bootstrap.js'            => 'file 5 content',
651
                'bootstrap.bundle.js'     => 'file 6 content',
652
                'bootstrap.bundle.js.map' => 'file 7 content',
653
                'bootstrap.min.js'        => 'file 8 content'
654
            ]
655
        ];
656
657
        $this->createFileStructure([
658
            $source => $structure,
659
        ]);
660
661
        $basePath = $this->testFilePath;
662
        $source = $basePath . '/' . $source;
663
        $destination = $basePath . '/assets';
664
665
        // without filter options return all directory.
666
        $options = [
667
            // options default false AssetManager
668
            'copyEmptyDirectories' => false,
669
            'only' => [
670
                'css/*.css',
671
            ],
672
            'except' => [
673
                'css/bootstrap.min.css'
674
            ]
675
        ];
676
677
        FileHelper::copyDirectory($source, $destination, $options);
678
679
        $this->assertFileExists($destination, 'Destination directory does not exist!');
680
681
        $exist = function ($exist, $dstDirName) use (&$checker) {
682
            foreach ($exist as $name => $content) {
683
                if (is_array($content)) {
684
                    $checker($content, $dstDirName . '/' . $name);
685
                } else {
686
                    $fileName = $dstDirName . '/' . $name;
687
                    $this->assertFileExists($fileName);
688
                    $this->assertStringEqualsFile($fileName, $content, 'Incorrect file content!');
689
                }
690
            }
691
        };
692
        $exist($exist, $destination);
693
694
        $noexist = function ($noexist, $dstDirName) use (&$checker) {
695
            foreach ($noexist as $name => $content) {
696
                if (is_array($content)) {
697
                    $checker($content, $dstDirName . '/' . $name);
698
                } else {
699
                    $fileName = $dstDirName . '/' . $name;
700
                    $this->assertFileNotExists($fileName);
701
                    $this->assertStringEqualsFile($fileName, $content, 'Incorrect file content!');
702
                }
703
            }
704
        };
705
        $noexist($noexist, $destination);
706
    }
707
708
    /**
709
     * Asserts that file has specific permission mode.
710
     *
711
     * @param int $expectedMode expected file permission mode.
712
     * @param string $fileName file name.
713
     * @param string $message error message
714
     *
715
     * @return void
716
     */
717
    private function assertFileMode(int $expectedMode, string $fileName, string $message = ''): void
718
    {
719
        $expectedMode = sprintf('%04o', $expectedMode);
720
        $this->assertEquals($expectedMode, $this->getMode($fileName), $message);
721
    }
722
723
    /**
724
     * Creates test files structure.
725
     *
726
     * @param array $items file system objects to be created in format: objectName => objectContent
727
     *                         Arrays specifies directories, other values - files.
728
     * @param string $basePath structure base file path.
729
     *
730
     * @return void
731
     */
732
    private function createFileStructure(array $items, ?string $basePath = null): void
733
    {
734
        $basePath = $basePath ?? $this->testFilePath;
735
736
        if (empty($basePath)) {
737
            $basePath = $this->testFilePath;
738
        }
739
        foreach ($items as $name => $content) {
740
            $itemName = $basePath . DIRECTORY_SEPARATOR . $name;
741
            if (is_array($content)) {
742
                if (isset($content[0], $content[1]) && $content[0] === 'symlink') {
743
                    symlink($basePath . '/' . $content[1], $itemName);
744
                } else {
745
                    mkdir($itemName, 0777, true);
746
                    $this->createFileStructure($content, $itemName);
747
                }
748
            } else {
749
                file_put_contents($itemName, $content);
750
            }
751
        }
752
    }
753
754
    /**
755
     * Get file permission mode.
756
     *
757
     * @param string $file file name.
758
     *
759
     * @return string permission mode.
760
     */
761
    private function getMode(string $file): string
762
    {
763
        return substr(sprintf('%04o', fileperms($file)), -4);
764
    }
765
766
    /**
767
     * Check if chmod works as expected.
768
     *
769
     * On remote file systems and vagrant mounts chmod returns true but file permissions are not set properly.
770
     */
771
    private function isChmodReliable(): bool
772
    {
773
        $directory = $this->testFilePath . '/test_chmod';
774
        mkdir($directory);
775
        chmod($directory, 0700);
776
        $mode = $this->getMode($directory);
777
        rmdir($directory);
778
779
        return $mode === '0700';
780
    }
781
}
782