Passed
Pull Request — master (#5)
by Alexander
01:17
created

FileHelperTest::setUp()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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