Passed
Push — master ( ee71f3...4995b0 )
by Alexander
07:48
created

FileHelperTest::testCopyDirectoryPermissions()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 38
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

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