Passed
Pull Request — master (#7)
by Roman
01:21
created

FileHelperTest::checkNoexist()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
c 1
b 0
f 0
nc 3
nop 2
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
        $this->checkExist($structure, $destination);
257
    }
258
259
    public function testCopyDirectoryNotRecursive(): void
260
    {
261
        $source = 'test_src_dir_not_rec';
262
        $structure = [
263
            'directory1' => [
264
                'file1.txt' => 'file 1 content',
265
                'file2.txt' => 'file 2 content',
266
            ],
267
            'directory2' => [
268
                'file3.txt' => 'file 3 content',
269
                'file4.txt' => 'file 4 content',
270
            ],
271
            'file5.txt' => 'file 5 content',
272
        ];
273
274
        $this->createFileStructure([
275
            $source => $structure,
276
        ]);
277
278
        $basePath = $this->testFilePath;
279
        $source = $basePath . '/' . $source;
280
        $destination = $basePath . '/' . 'test_dst_dir';
281
282
        FileHelper::copyDirectory($source, $destination, ['recursive' => false]);
283
284
        $this->assertFileExists($destination, 'Destination directory does not exist!');
285
286
        foreach ($structure as $name => $content) {
287
            $fileName = $destination . '/' . $name;
288
            if (is_array($content)) {
289
                $this->assertFileNotExists($fileName);
290
            } else {
291
                $this->assertFileExists($fileName);
292
                $this->assertStringEqualsFile($fileName, $content, 'Incorrect file content!');
293
            }
294
        }
295
    }
296
297
    public function testCopyDirectoryPermissions(): void
298
    {
299
        if (!$this->isChmodReliable()) {
300
            $this->markTestSkipped('Skipping test since chmod is not reliable in this environment.');
301
        }
302
303
        $isWindows = DIRECTORY_SEPARATOR === '\\';
304
305
        if ($isWindows) {
306
            $this->markTestSkipped('Skipping tests on Windows because fileperms() always return 0777.');
307
        }
308
309
        $source = 'test_src_dir';
310
        $subDirectory = 'test_sub_dir';
311
        $fileName = 'test_file.txt';
312
313
        $this->createFileStructure([
314
            $source => [
315
                $subDirectory => [],
316
                $fileName => 'test file content',
317
            ],
318
        ]);
319
320
        $basePath = $this->testFilePath;
321
        $source = $basePath . '/' . $source;
322
        $destination = $basePath . '/test_dst_dir';
323
        $directoryMode = 0755;
324
        $fileMode = 0755;
325
        $options = [
326
            'dirMode' => $directoryMode,
327
            'fileMode' => $fileMode,
328
        ];
329
330
        FileHelper::copyDirectory($source, $destination, $options);
331
332
        $this->assertFileMode($directoryMode, $destination, 'Destination directory has wrong mode!');
333
        $this->assertFileMode($directoryMode, $destination . '/' . $subDirectory, 'Copied sub directory has wrong mode!');
334
        $this->assertFileMode($fileMode, $destination . '/' . $fileName, 'Copied file has wrong mode!');
335
    }
336
337
    /**
338
     * Copy directory to it self.
339
     *
340
     * @see https://github.com/yiisoft/yii2/issues/10710
341
     *
342
     * @return void
343
     */
344
    public function testCopyDirectoryToItself(): void
345
    {
346
        $directoryName = 'test_dir';
347
348
        $this->createFileStructure([
349
            $directoryName => [],
350
        ]);
351
        $this->expectException(\InvalidArgumentException::class);
352
353
        $directoryName = $this->testFilePath . '/test_dir';
354
355
        FileHelper::copyDirectory($directoryName, $directoryName);
356
    }
357
358
    /**
359
     * Copy directory to sudirectory of it self.
360
     *
361
     * @see https://github.com/yiisoft/yii2/issues/10710
362
     *
363
     * @return void
364
     */
365
    public function testCopyDirToSubdirOfItself(): void
366
    {
367
        $this->createFileStructure([
368
            'data' => [],
369
            'backup' => ['data' => []],
370
        ]);
371
        $this->expectException(\InvalidArgumentException::class);
372
373
        FileHelper::copyDirectory(
374
            $this->testFilePath . '/backup',
375
            $this->testFilePath . '/backup/data'
376
        );
377
    }
378
379
    /**
380
     * Copy directory to another with same name.
381
     *
382
     * @see https://github.com/yiisoft/yii2/issues/10710
383
     *
384
     * @return void
385
     */
386
    public function testCopyDirToAnotherWithSameName(): void
387
    {
388
        $this->createFileStructure([
389
            'data' => [],
390
            'backup' => ['data' => []],
391
        ]);
392
393
        FileHelper::copyDirectory(
394
            $this->testFilePath . '/data',
395
            $this->testFilePath . '/backup/data'
396
        );
397
398
        $this->assertFileExists($this->testFilePath . '/backup/data');
399
    }
400
401
    /**
402
     * Copy directory with same name.
403
     *
404
     * @see https://github.com/yiisoft/yii2/issues/10710
405
     *
406
     * @return void
407
     */
408
    public function testCopyDirWithSameName(): void
409
    {
410
        $this->createFileStructure([
411
            'data' => [],
412
            'data-backup' => [],
413
        ]);
414
415
        FileHelper::copyDirectory(
416
            $this->testFilePath . '/data',
417
            $this->testFilePath . '/data-backup'
418
        );
419
420
        $this->assertTrue(true, 'no error');
421
    }
422
423
    public function testsCopyDirectoryFilterPath(): void
424
    {
425
        $source = 'boostrap4';
426
427
        $structure = [
428
            'css' => [
429
                'bootstrap.css'           => 'file 1 content',
430
                'bootstrap.css.map'       => 'file 2 content',
431
                'bootstrap.min.css'       => 'file 3 content',
432
                'bootstrap.min.css.map'   => 'file 4 content'
433
            ],
434
            'js' => [
435
                'bootstrap.js'            => 'file 5 content',
436
                'bootstrap.bundle.js'     => 'file 6 content',
437
                'bootstrap.bundle.js.map' => 'file 7 content',
438
                'bootstrap.min.js'        => 'file 8 content'
439
            ]
440
        ];
441
442
        $this->createFileStructure([
443
            $source => $structure,
444
        ]);
445
446
        $basePath = $this->testFilePath;
447
        $source = $basePath . '/' . $source;
448
        $destination = $basePath . '/assets';
449
450
        // without filter options return all directory.
451
        $options = [];
452
453
        FileHelper::copyDirectory($source, $destination, $options);
454
455
        $this->assertFileExists($destination, 'Destination directory does not exist!');
456
        $this->checkExist($structure, $destination);
457
    }
458
459
    public function testsCopyDirectoryFilterPathOnly(): void
460
    {
461
        $source = 'boostrap4';
462
463
        $structure = [
464
            'css' => [
465
                'bootstrap.css'           => 'file 1 content',
466
                'bootstrap.css.map'       => 'file 2 content',
467
                'bootstrap.min.css'       => 'file 3 content',
468
                'bootstrap.min.css.map'   => 'file 4 content'
469
            ],
470
            'js' => [
471
                'bootstrap.js'            => 'file 5 content',
472
                'bootstrap.bundle.js'     => 'file 6 content',
473
                'bootstrap.bundle.js.map' => 'file 7 content',
474
                'bootstrap.min.js'        => 'file 8 content'
475
            ]
476
        ];
477
478
        $exist = [
479
            'css' => [
480
                'bootstrap.css'           => 'file 1 content',
481
                'bootstrap.min.css'       => 'file 3 content',
482
            ]
483
        ];
484
485
        $noexist = [
486
            'css' => [
487
                'bootstrap.css.map'       => 'file 2 content',
488
                'bootstrap.min.css.map'   => 'file 4 content'
489
            ],
490
            'js' => [
491
                'bootstrap.js'            => 'file 5 content',
492
                'bootstrap.bundle.js'     => 'file 6 content',
493
                'bootstrap.bundle.js.map' => 'file 7 content',
494
                'bootstrap.min.js'        => 'file 8 content'
495
            ]
496
        ];
497
498
        $this->createFileStructure([
499
            $source => $structure,
500
        ]);
501
502
        $basePath = $this->testFilePath;
503
        $source = $basePath . '/' . $source;
504
        $destination = $basePath . '/assets';
505
506
        // without filter options return all directory.
507
        $options = [
508
            // options default false AssetManager
509
            'copyEmptyDirectories' => false,
510
            'only' => [
511
                'css/*.css',
512
            ]
513
        ];
514
515
        FileHelper::copyDirectory($source, $destination, $options);
516
517
        $this->assertFileExists($destination, 'Destination directory does not exist!');
518
        $this->checkExist($exist, $destination);
519
        $this->checkNoexist($noexist, $destination);
520
    }
521
522
    public function testsCopyDirectoryFilterPathExcept(): void
523
    {
524
        $source = 'boostrap4';
525
526
        $structure = [
527
            'css' => [
528
                'bootstrap.css'           => 'file 1 content',
529
                'bootstrap.css.map'       => 'file 2 content',
530
                'bootstrap.min.css'       => 'file 3 content',
531
                'bootstrap.min.css.map'   => 'file 4 content'
532
            ],
533
            'js' => [
534
                'bootstrap.js'            => 'file 5 content',
535
                'bootstrap.bundle.js'     => 'file 6 content',
536
                'bootstrap.bundle.js.map' => 'file 7 content',
537
                'bootstrap.min.js'        => 'file 8 content'
538
            ]
539
        ];
540
541
        $exist = [
542
            'css' => [
543
                'bootstrap.css'           => 'file 1 content',
544
            ]
545
        ];
546
547
        $noexist = [
548
            'css' => [
549
                'bootstrap.css.map'       => 'file 2 content',
550
                'bootstrap.min.css'       => 'file 3 content',
551
                'bootstrap.min.css.map'   => 'file 4 content'
552
            ],
553
            'js' => [
554
                'bootstrap.js'            => 'file 5 content',
555
                'bootstrap.bundle.js'     => 'file 6 content',
556
                'bootstrap.bundle.js.map' => 'file 7 content',
557
                'bootstrap.min.js'        => 'file 8 content'
558
            ]
559
        ];
560
561
        $this->createFileStructure([
562
            $source => $structure,
563
        ]);
564
565
        $basePath = $this->testFilePath;
566
        $source = $basePath . '/' . $source;
567
        $destination = $basePath . '/assets';
568
569
        // without filter options return all directory.
570
        $options = [
571
            // options default false AssetManager
572
            'copyEmptyDirectories' => false,
573
            'only' => [
574
                'css/*.css',
575
            ],
576
            'except' => [
577
                'css/bootstrap.min.css'
578
            ]
579
        ];
580
581
        FileHelper::copyDirectory($source, $destination, $options);
582
583
        $this->assertFileExists($destination, 'Destination directory does not exist!');
584
        $this->checkExist($exist, $destination);
585
        $this->checkNoexist($noexist, $destination);
586
    }
587
588
    /**
589
     * Check if exist filename.
590
     *
591
     * @param array $exist
592
     * @param string $dstDirName
593
     *
594
     * @return void
595
     */
596
    private function checkExist(array $exist, string $dstDirName): void
597
    {
598
        foreach ($exist as $name => $content) {
599
            if (is_array($content)) {
600
                $this->checkExist($content, $dstDirName . '/' . $name);
601
            } else {
602
                $fileName = $dstDirName . '/' . $name;
603
                $this->assertFileExists($fileName);
604
                $this->assertStringEqualsFile($fileName, $content, 'Incorrect file content!');
605
            }
606
        }
607
    }
608
609
    /**
610
     * Check if no exist filename.
611
     *
612
     * @param array $noexist
613
     * @param string $dstDirName
614
     *
615
     * @return void
616
     */
617
    private function checkNoexist(array $noexist, string $dstDirName): void
618
    {
619
        foreach ($noexist as $name => $content) {
620
            if (is_array($content)) {
621
                $this->checkNoexist($content, $dstDirName . '/' . $name);
622
            } else {
623
                $fileName = $dstDirName . '/' . $name;
624
                $this->assertFileNotExists($fileName);
625
            }
626
        }
627
    }
628
629
    /**
630
     * Asserts that file has specific permission mode.
631
     *
632
     * @param int $expectedMode expected file permission mode.
633
     * @param string $fileName file name.
634
     * @param string $message error message
635
     *
636
     * @return void
637
     */
638
    private function assertFileMode(int $expectedMode, string $fileName, string $message = ''): void
639
    {
640
        $expectedMode = sprintf('%04o', $expectedMode);
641
        $this->assertEquals($expectedMode, $this->getMode($fileName), $message);
642
    }
643
644
    /**
645
     * Creates test files structure.
646
     *
647
     * @param array $items file system objects to be created in format: objectName => objectContent
648
     *                         Arrays specifies directories, other values - files.
649
     * @param string $basePath structure base file path.
650
     *
651
     * @return void
652
     */
653
    private function createFileStructure(array $items, ?string $basePath = null): void
654
    {
655
        $basePath = $basePath ?? $this->testFilePath;
656
657
        if (empty($basePath)) {
658
            $basePath = $this->testFilePath;
659
        }
660
        foreach ($items as $name => $content) {
661
            $itemName = $basePath . DIRECTORY_SEPARATOR . $name;
662
            if (is_array($content)) {
663
                if (isset($content[0], $content[1]) && $content[0] === 'symlink') {
664
                    symlink($basePath . '/' . $content[1], $itemName);
665
                } else {
666
                    mkdir($itemName, 0777, true);
667
                    $this->createFileStructure($content, $itemName);
668
                }
669
            } else {
670
                file_put_contents($itemName, $content);
671
            }
672
        }
673
    }
674
675
    /**
676
     * Get file permission mode.
677
     *
678
     * @param string $file file name.
679
     *
680
     * @return string permission mode.
681
     */
682
    private function getMode(string $file): string
683
    {
684
        return substr(sprintf('%04o', fileperms($file)), -4);
685
    }
686
687
    /**
688
     * Check if chmod works as expected.
689
     *
690
     * On remote file systems and vagrant mounts chmod returns true but file permissions are not set properly.
691
     */
692
    private function isChmodReliable(): bool
693
    {
694
        $directory = $this->testFilePath . '/test_chmod';
695
        mkdir($directory);
696
        chmod($directory, 0700);
697
        $mode = $this->getMode($directory);
698
        rmdir($directory);
699
700
        return $mode === '0700';
701
    }
702
}
703