Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / vendor / symfony / filesystem / Tests / FilesystemTest.php
1 <?php
2
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Symfony\Component\Filesystem\Tests;
13
14 /**
15  * Test class for Filesystem.
16  */
17 class FilesystemTest extends FilesystemTestCase
18 {
19     public function testCopyCreatesNewFile()
20     {
21         $sourceFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_source_file';
22         $targetFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_target_file';
23
24         file_put_contents($sourceFilePath, 'SOURCE FILE');
25
26         $this->filesystem->copy($sourceFilePath, $targetFilePath);
27
28         $this->assertFileExists($targetFilePath);
29         $this->assertStringEqualsFile($targetFilePath, 'SOURCE FILE');
30     }
31
32     /**
33      * @expectedException \Symfony\Component\Filesystem\Exception\IOException
34      */
35     public function testCopyFails()
36     {
37         $sourceFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_source_file';
38         $targetFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_target_file';
39
40         $this->filesystem->copy($sourceFilePath, $targetFilePath);
41     }
42
43     /**
44      * @expectedException \Symfony\Component\Filesystem\Exception\IOException
45      */
46     public function testCopyUnreadableFileFails()
47     {
48         // skip test on Windows; PHP can't easily set file as unreadable on Windows
49         if ('\\' === \DIRECTORY_SEPARATOR) {
50             $this->markTestSkipped('This test cannot run on Windows.');
51         }
52
53         if (!getenv('USER') || 'root' === getenv('USER')) {
54             $this->markTestSkipped('This test will fail if run under superuser');
55         }
56
57         $sourceFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_source_file';
58         $targetFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_target_file';
59
60         file_put_contents($sourceFilePath, 'SOURCE FILE');
61
62         // make sure target cannot be read
63         $this->filesystem->chmod($sourceFilePath, 0222);
64
65         $this->filesystem->copy($sourceFilePath, $targetFilePath);
66     }
67
68     public function testCopyOverridesExistingFileIfModified()
69     {
70         $sourceFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_source_file';
71         $targetFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_target_file';
72
73         file_put_contents($sourceFilePath, 'SOURCE FILE');
74         file_put_contents($targetFilePath, 'TARGET FILE');
75         touch($targetFilePath, time() - 1000);
76
77         $this->filesystem->copy($sourceFilePath, $targetFilePath);
78
79         $this->assertFileExists($targetFilePath);
80         $this->assertStringEqualsFile($targetFilePath, 'SOURCE FILE');
81     }
82
83     public function testCopyDoesNotOverrideExistingFileByDefault()
84     {
85         $sourceFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_source_file';
86         $targetFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_target_file';
87
88         file_put_contents($sourceFilePath, 'SOURCE FILE');
89         file_put_contents($targetFilePath, 'TARGET FILE');
90
91         // make sure both files have the same modification time
92         $modificationTime = time() - 1000;
93         touch($sourceFilePath, $modificationTime);
94         touch($targetFilePath, $modificationTime);
95
96         $this->filesystem->copy($sourceFilePath, $targetFilePath);
97
98         $this->assertFileExists($targetFilePath);
99         $this->assertStringEqualsFile($targetFilePath, 'TARGET FILE');
100     }
101
102     public function testCopyOverridesExistingFileIfForced()
103     {
104         $sourceFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_source_file';
105         $targetFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_target_file';
106
107         file_put_contents($sourceFilePath, 'SOURCE FILE');
108         file_put_contents($targetFilePath, 'TARGET FILE');
109
110         // make sure both files have the same modification time
111         $modificationTime = time() - 1000;
112         touch($sourceFilePath, $modificationTime);
113         touch($targetFilePath, $modificationTime);
114
115         $this->filesystem->copy($sourceFilePath, $targetFilePath, true);
116
117         $this->assertFileExists($targetFilePath);
118         $this->assertStringEqualsFile($targetFilePath, 'SOURCE FILE');
119     }
120
121     /**
122      * @expectedException \Symfony\Component\Filesystem\Exception\IOException
123      */
124     public function testCopyWithOverrideWithReadOnlyTargetFails()
125     {
126         // skip test on Windows; PHP can't easily set file as unwritable on Windows
127         if ('\\' === \DIRECTORY_SEPARATOR) {
128             $this->markTestSkipped('This test cannot run on Windows.');
129         }
130
131         if (!getenv('USER') || 'root' === getenv('USER')) {
132             $this->markTestSkipped('This test will fail if run under superuser');
133         }
134
135         $sourceFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_source_file';
136         $targetFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_target_file';
137
138         file_put_contents($sourceFilePath, 'SOURCE FILE');
139         file_put_contents($targetFilePath, 'TARGET FILE');
140
141         // make sure both files have the same modification time
142         $modificationTime = time() - 1000;
143         touch($sourceFilePath, $modificationTime);
144         touch($targetFilePath, $modificationTime);
145
146         // make sure target is read-only
147         $this->filesystem->chmod($targetFilePath, 0444);
148
149         $this->filesystem->copy($sourceFilePath, $targetFilePath, true);
150     }
151
152     public function testCopyCreatesTargetDirectoryIfItDoesNotExist()
153     {
154         $sourceFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_source_file';
155         $targetFileDirectory = $this->workspace.\DIRECTORY_SEPARATOR.'directory';
156         $targetFilePath = $targetFileDirectory.\DIRECTORY_SEPARATOR.'copy_target_file';
157
158         file_put_contents($sourceFilePath, 'SOURCE FILE');
159
160         $this->filesystem->copy($sourceFilePath, $targetFilePath);
161
162         $this->assertTrue(is_dir($targetFileDirectory));
163         $this->assertFileExists($targetFilePath);
164         $this->assertStringEqualsFile($targetFilePath, 'SOURCE FILE');
165     }
166
167     /**
168      * @group network
169      */
170     public function testCopyForOriginUrlsAndExistingLocalFileDefaultsToCopy()
171     {
172         if (!\in_array('https', stream_get_wrappers())) {
173             $this->markTestSkipped('"https" stream wrapper is not enabled.');
174         }
175         $sourceFilePath = 'https://symfony.com/images/common/logo/logo_symfony_header.png';
176         $targetFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_target_file';
177
178         file_put_contents($targetFilePath, 'TARGET FILE');
179
180         $this->filesystem->copy($sourceFilePath, $targetFilePath, false);
181
182         $this->assertFileExists($targetFilePath);
183         $this->assertEquals(file_get_contents($sourceFilePath), file_get_contents($targetFilePath));
184     }
185
186     public function testMkdirCreatesDirectoriesRecursively()
187     {
188         $directory = $this->workspace
189             .\DIRECTORY_SEPARATOR.'directory'
190             .\DIRECTORY_SEPARATOR.'sub_directory';
191
192         $this->filesystem->mkdir($directory);
193
194         $this->assertTrue(is_dir($directory));
195     }
196
197     public function testMkdirCreatesDirectoriesFromArray()
198     {
199         $basePath = $this->workspace.\DIRECTORY_SEPARATOR;
200         $directories = array(
201             $basePath.'1', $basePath.'2', $basePath.'3',
202         );
203
204         $this->filesystem->mkdir($directories);
205
206         $this->assertTrue(is_dir($basePath.'1'));
207         $this->assertTrue(is_dir($basePath.'2'));
208         $this->assertTrue(is_dir($basePath.'3'));
209     }
210
211     public function testMkdirCreatesDirectoriesFromTraversableObject()
212     {
213         $basePath = $this->workspace.\DIRECTORY_SEPARATOR;
214         $directories = new \ArrayObject(array(
215             $basePath.'1', $basePath.'2', $basePath.'3',
216         ));
217
218         $this->filesystem->mkdir($directories);
219
220         $this->assertTrue(is_dir($basePath.'1'));
221         $this->assertTrue(is_dir($basePath.'2'));
222         $this->assertTrue(is_dir($basePath.'3'));
223     }
224
225     /**
226      * @expectedException \Symfony\Component\Filesystem\Exception\IOException
227      */
228     public function testMkdirCreatesDirectoriesFails()
229     {
230         $basePath = $this->workspace.\DIRECTORY_SEPARATOR;
231         $dir = $basePath.'2';
232
233         file_put_contents($dir, '');
234
235         $this->filesystem->mkdir($dir);
236     }
237
238     public function testTouchCreatesEmptyFile()
239     {
240         $file = $this->workspace.\DIRECTORY_SEPARATOR.'1';
241
242         $this->filesystem->touch($file);
243
244         $this->assertFileExists($file);
245     }
246
247     /**
248      * @expectedException \Symfony\Component\Filesystem\Exception\IOException
249      */
250     public function testTouchFails()
251     {
252         $file = $this->workspace.\DIRECTORY_SEPARATOR.'1'.\DIRECTORY_SEPARATOR.'2';
253
254         $this->filesystem->touch($file);
255     }
256
257     public function testTouchCreatesEmptyFilesFromArray()
258     {
259         $basePath = $this->workspace.\DIRECTORY_SEPARATOR;
260         $files = array(
261             $basePath.'1', $basePath.'2', $basePath.'3',
262         );
263
264         $this->filesystem->touch($files);
265
266         $this->assertFileExists($basePath.'1');
267         $this->assertFileExists($basePath.'2');
268         $this->assertFileExists($basePath.'3');
269     }
270
271     public function testTouchCreatesEmptyFilesFromTraversableObject()
272     {
273         $basePath = $this->workspace.\DIRECTORY_SEPARATOR;
274         $files = new \ArrayObject(array(
275             $basePath.'1', $basePath.'2', $basePath.'3',
276         ));
277
278         $this->filesystem->touch($files);
279
280         $this->assertFileExists($basePath.'1');
281         $this->assertFileExists($basePath.'2');
282         $this->assertFileExists($basePath.'3');
283     }
284
285     public function testRemoveCleansFilesAndDirectoriesIteratively()
286     {
287         $basePath = $this->workspace.\DIRECTORY_SEPARATOR.'directory'.\DIRECTORY_SEPARATOR;
288
289         mkdir($basePath);
290         mkdir($basePath.'dir');
291         touch($basePath.'file');
292
293         $this->filesystem->remove($basePath);
294
295         $this->assertFileNotExists($basePath);
296     }
297
298     public function testRemoveCleansArrayOfFilesAndDirectories()
299     {
300         $basePath = $this->workspace.\DIRECTORY_SEPARATOR;
301
302         mkdir($basePath.'dir');
303         touch($basePath.'file');
304
305         $files = array(
306             $basePath.'dir', $basePath.'file',
307         );
308
309         $this->filesystem->remove($files);
310
311         $this->assertFileNotExists($basePath.'dir');
312         $this->assertFileNotExists($basePath.'file');
313     }
314
315     public function testRemoveCleansTraversableObjectOfFilesAndDirectories()
316     {
317         $basePath = $this->workspace.\DIRECTORY_SEPARATOR;
318
319         mkdir($basePath.'dir');
320         touch($basePath.'file');
321
322         $files = new \ArrayObject(array(
323             $basePath.'dir', $basePath.'file',
324         ));
325
326         $this->filesystem->remove($files);
327
328         $this->assertFileNotExists($basePath.'dir');
329         $this->assertFileNotExists($basePath.'file');
330     }
331
332     public function testRemoveIgnoresNonExistingFiles()
333     {
334         $basePath = $this->workspace.\DIRECTORY_SEPARATOR;
335
336         mkdir($basePath.'dir');
337
338         $files = array(
339             $basePath.'dir', $basePath.'file',
340         );
341
342         $this->filesystem->remove($files);
343
344         $this->assertFileNotExists($basePath.'dir');
345     }
346
347     public function testRemoveCleansInvalidLinks()
348     {
349         $this->markAsSkippedIfSymlinkIsMissing();
350
351         $basePath = $this->workspace.\DIRECTORY_SEPARATOR.'directory'.\DIRECTORY_SEPARATOR;
352
353         mkdir($basePath);
354         mkdir($basePath.'dir');
355         // create symlink to nonexistent file
356         @symlink($basePath.'file', $basePath.'file-link');
357
358         // create symlink to dir using trailing forward slash
359         $this->filesystem->symlink($basePath.'dir/', $basePath.'dir-link');
360         $this->assertTrue(is_dir($basePath.'dir-link'));
361
362         // create symlink to nonexistent dir
363         rmdir($basePath.'dir');
364         $this->assertFalse('\\' === \DIRECTORY_SEPARATOR ? @readlink($basePath.'dir-link') : is_dir($basePath.'dir-link'));
365
366         $this->filesystem->remove($basePath);
367
368         $this->assertFileNotExists($basePath);
369     }
370
371     public function testFilesExists()
372     {
373         $basePath = $this->workspace.\DIRECTORY_SEPARATOR.'directory'.\DIRECTORY_SEPARATOR;
374
375         mkdir($basePath);
376         touch($basePath.'file1');
377         mkdir($basePath.'folder');
378
379         $this->assertTrue($this->filesystem->exists($basePath.'file1'));
380         $this->assertTrue($this->filesystem->exists($basePath.'folder'));
381     }
382
383     /**
384      * @expectedException \Symfony\Component\Filesystem\Exception\IOException
385      */
386     public function testFilesExistsFails()
387     {
388         if ('\\' !== \DIRECTORY_SEPARATOR) {
389             $this->markTestSkipped('Long file names are an issue on Windows');
390         }
391         $basePath = $this->workspace.'\\directory\\';
392         $maxPathLength = PHP_MAXPATHLEN - 2;
393
394         $oldPath = getcwd();
395         mkdir($basePath);
396         chdir($basePath);
397         $file = str_repeat('T', $maxPathLength - \strlen($basePath) + 1);
398         $path = $basePath.$file;
399         exec('TYPE NUL >>'.$file); // equivalent of touch, we can not use the php touch() here because it suffers from the same limitation
400         $this->longPathNamesWindows[] = $path; // save this so we can clean up later
401         chdir($oldPath);
402         $this->filesystem->exists($path);
403     }
404
405     public function testFilesExistsTraversableObjectOfFilesAndDirectories()
406     {
407         $basePath = $this->workspace.\DIRECTORY_SEPARATOR;
408
409         mkdir($basePath.'dir');
410         touch($basePath.'file');
411
412         $files = new \ArrayObject(array(
413             $basePath.'dir', $basePath.'file',
414         ));
415
416         $this->assertTrue($this->filesystem->exists($files));
417     }
418
419     public function testFilesNotExistsTraversableObjectOfFilesAndDirectories()
420     {
421         $basePath = $this->workspace.\DIRECTORY_SEPARATOR;
422
423         mkdir($basePath.'dir');
424         touch($basePath.'file');
425         touch($basePath.'file2');
426
427         $files = new \ArrayObject(array(
428             $basePath.'dir', $basePath.'file', $basePath.'file2',
429         ));
430
431         unlink($basePath.'file');
432
433         $this->assertFalse($this->filesystem->exists($files));
434     }
435
436     public function testInvalidFileNotExists()
437     {
438         $basePath = $this->workspace.\DIRECTORY_SEPARATOR.'directory'.\DIRECTORY_SEPARATOR;
439
440         $this->assertFalse($this->filesystem->exists($basePath.time()));
441     }
442
443     public function testChmodChangesFileMode()
444     {
445         $this->markAsSkippedIfChmodIsMissing();
446
447         $dir = $this->workspace.\DIRECTORY_SEPARATOR.'dir';
448         mkdir($dir);
449         $file = $dir.\DIRECTORY_SEPARATOR.'file';
450         touch($file);
451
452         $this->filesystem->chmod($file, 0400);
453         $this->filesystem->chmod($dir, 0753);
454
455         $this->assertFilePermissions(753, $dir);
456         $this->assertFilePermissions(400, $file);
457     }
458
459     public function testChmodWithWrongModLeavesPreviousPermissionsUntouched()
460     {
461         $this->markAsSkippedIfChmodIsMissing();
462
463         if (\defined('HHVM_VERSION')) {
464             $this->markTestSkipped('chmod() changes permissions even when passing invalid modes on HHVM');
465         }
466
467         $dir = $this->workspace.\DIRECTORY_SEPARATOR.'file';
468         touch($dir);
469
470         $permissions = fileperms($dir);
471
472         $this->filesystem->chmod($dir, 'Wrongmode');
473
474         $this->assertSame($permissions, fileperms($dir));
475     }
476
477     public function testChmodRecursive()
478     {
479         $this->markAsSkippedIfChmodIsMissing();
480
481         $dir = $this->workspace.\DIRECTORY_SEPARATOR.'dir';
482         mkdir($dir);
483         $file = $dir.\DIRECTORY_SEPARATOR.'file';
484         touch($file);
485
486         $this->filesystem->chmod($file, 0400, 0000, true);
487         $this->filesystem->chmod($dir, 0753, 0000, true);
488
489         $this->assertFilePermissions(753, $dir);
490         $this->assertFilePermissions(753, $file);
491     }
492
493     public function testChmodAppliesUmask()
494     {
495         $this->markAsSkippedIfChmodIsMissing();
496
497         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
498         touch($file);
499
500         $this->filesystem->chmod($file, 0770, 0022);
501         $this->assertFilePermissions(750, $file);
502     }
503
504     public function testChmodChangesModeOfArrayOfFiles()
505     {
506         $this->markAsSkippedIfChmodIsMissing();
507
508         $directory = $this->workspace.\DIRECTORY_SEPARATOR.'directory';
509         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
510         $files = array($directory, $file);
511
512         mkdir($directory);
513         touch($file);
514
515         $this->filesystem->chmod($files, 0753);
516
517         $this->assertFilePermissions(753, $file);
518         $this->assertFilePermissions(753, $directory);
519     }
520
521     public function testChmodChangesModeOfTraversableFileObject()
522     {
523         $this->markAsSkippedIfChmodIsMissing();
524
525         $directory = $this->workspace.\DIRECTORY_SEPARATOR.'directory';
526         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
527         $files = new \ArrayObject(array($directory, $file));
528
529         mkdir($directory);
530         touch($file);
531
532         $this->filesystem->chmod($files, 0753);
533
534         $this->assertFilePermissions(753, $file);
535         $this->assertFilePermissions(753, $directory);
536     }
537
538     public function testChmodChangesZeroModeOnSubdirectoriesOnRecursive()
539     {
540         $this->markAsSkippedIfChmodIsMissing();
541
542         $directory = $this->workspace.\DIRECTORY_SEPARATOR.'directory';
543         $subdirectory = $directory.\DIRECTORY_SEPARATOR.'subdirectory';
544
545         mkdir($directory);
546         mkdir($subdirectory);
547         chmod($subdirectory, 0000);
548
549         $this->filesystem->chmod($directory, 0753, 0000, true);
550
551         $this->assertFilePermissions(753, $subdirectory);
552     }
553
554     public function testChown()
555     {
556         $this->markAsSkippedIfPosixIsMissing();
557
558         $dir = $this->workspace.\DIRECTORY_SEPARATOR.'dir';
559         mkdir($dir);
560
561         $owner = $this->getFileOwner($dir);
562         $this->filesystem->chown($dir, $owner);
563
564         $this->assertSame($owner, $this->getFileOwner($dir));
565     }
566
567     public function testChownRecursive()
568     {
569         $this->markAsSkippedIfPosixIsMissing();
570
571         $dir = $this->workspace.\DIRECTORY_SEPARATOR.'dir';
572         mkdir($dir);
573         $file = $dir.\DIRECTORY_SEPARATOR.'file';
574         touch($file);
575
576         $owner = $this->getFileOwner($dir);
577         $this->filesystem->chown($dir, $owner, true);
578
579         $this->assertSame($owner, $this->getFileOwner($file));
580     }
581
582     public function testChownSymlink()
583     {
584         $this->markAsSkippedIfSymlinkIsMissing();
585
586         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
587         $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
588
589         touch($file);
590
591         $this->filesystem->symlink($file, $link);
592
593         $owner = $this->getFileOwner($link);
594         $this->filesystem->chown($link, $owner);
595
596         $this->assertSame($owner, $this->getFileOwner($link));
597     }
598
599     public function testChownLink()
600     {
601         $this->markAsSkippedIfLinkIsMissing();
602
603         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
604         $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
605
606         touch($file);
607
608         $this->filesystem->hardlink($file, $link);
609
610         $owner = $this->getFileOwner($link);
611         $this->filesystem->chown($link, $owner);
612
613         $this->assertSame($owner, $this->getFileOwner($link));
614     }
615
616     /**
617      * @expectedException \Symfony\Component\Filesystem\Exception\IOException
618      */
619     public function testChownSymlinkFails()
620     {
621         $this->markAsSkippedIfSymlinkIsMissing();
622
623         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
624         $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
625
626         touch($file);
627
628         $this->filesystem->symlink($file, $link);
629
630         $this->filesystem->chown($link, 'user'.time().mt_rand(1000, 9999));
631     }
632
633     /**
634      * @expectedException \Symfony\Component\Filesystem\Exception\IOException
635      */
636     public function testChownLinkFails()
637     {
638         $this->markAsSkippedIfLinkIsMissing();
639
640         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
641         $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
642
643         touch($file);
644
645         $this->filesystem->hardlink($file, $link);
646
647         $this->filesystem->chown($link, 'user'.time().mt_rand(1000, 9999));
648     }
649
650     /**
651      * @expectedException \Symfony\Component\Filesystem\Exception\IOException
652      */
653     public function testChownFail()
654     {
655         $this->markAsSkippedIfPosixIsMissing();
656
657         $dir = $this->workspace.\DIRECTORY_SEPARATOR.'dir';
658         mkdir($dir);
659
660         $this->filesystem->chown($dir, 'user'.time().mt_rand(1000, 9999));
661     }
662
663     public function testChgrp()
664     {
665         $this->markAsSkippedIfPosixIsMissing();
666
667         $dir = $this->workspace.\DIRECTORY_SEPARATOR.'dir';
668         mkdir($dir);
669
670         $group = $this->getFileGroup($dir);
671         $this->filesystem->chgrp($dir, $group);
672
673         $this->assertSame($group, $this->getFileGroup($dir));
674     }
675
676     public function testChgrpRecursive()
677     {
678         $this->markAsSkippedIfPosixIsMissing();
679
680         $dir = $this->workspace.\DIRECTORY_SEPARATOR.'dir';
681         mkdir($dir);
682         $file = $dir.\DIRECTORY_SEPARATOR.'file';
683         touch($file);
684
685         $group = $this->getFileGroup($dir);
686         $this->filesystem->chgrp($dir, $group, true);
687
688         $this->assertSame($group, $this->getFileGroup($file));
689     }
690
691     public function testChgrpSymlink()
692     {
693         $this->markAsSkippedIfSymlinkIsMissing();
694
695         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
696         $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
697
698         touch($file);
699
700         $this->filesystem->symlink($file, $link);
701
702         $group = $this->getFileGroup($link);
703         $this->filesystem->chgrp($link, $group);
704
705         $this->assertSame($group, $this->getFileGroup($link));
706     }
707
708     public function testChgrpLink()
709     {
710         $this->markAsSkippedIfLinkIsMissing();
711
712         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
713         $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
714
715         touch($file);
716
717         $this->filesystem->hardlink($file, $link);
718
719         $group = $this->getFileGroup($link);
720         $this->filesystem->chgrp($link, $group);
721
722         $this->assertSame($group, $this->getFileGroup($link));
723     }
724
725     /**
726      * @expectedException \Symfony\Component\Filesystem\Exception\IOException
727      */
728     public function testChgrpSymlinkFails()
729     {
730         $this->markAsSkippedIfSymlinkIsMissing();
731
732         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
733         $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
734
735         touch($file);
736
737         $this->filesystem->symlink($file, $link);
738
739         $this->filesystem->chgrp($link, 'user'.time().mt_rand(1000, 9999));
740     }
741
742     /**
743      * @expectedException \Symfony\Component\Filesystem\Exception\IOException
744      */
745     public function testChgrpLinkFails()
746     {
747         $this->markAsSkippedIfLinkIsMissing();
748
749         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
750         $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
751
752         touch($file);
753
754         $this->filesystem->hardlink($file, $link);
755
756         $this->filesystem->chgrp($link, 'user'.time().mt_rand(1000, 9999));
757     }
758
759     /**
760      * @expectedException \Symfony\Component\Filesystem\Exception\IOException
761      */
762     public function testChgrpFail()
763     {
764         $this->markAsSkippedIfPosixIsMissing();
765
766         $dir = $this->workspace.\DIRECTORY_SEPARATOR.'dir';
767         mkdir($dir);
768
769         $this->filesystem->chgrp($dir, 'user'.time().mt_rand(1000, 9999));
770     }
771
772     public function testRename()
773     {
774         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
775         $newPath = $this->workspace.\DIRECTORY_SEPARATOR.'new_file';
776         touch($file);
777
778         $this->filesystem->rename($file, $newPath);
779
780         $this->assertFileNotExists($file);
781         $this->assertFileExists($newPath);
782     }
783
784     /**
785      * @expectedException \Symfony\Component\Filesystem\Exception\IOException
786      */
787     public function testRenameThrowsExceptionIfTargetAlreadyExists()
788     {
789         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
790         $newPath = $this->workspace.\DIRECTORY_SEPARATOR.'new_file';
791
792         touch($file);
793         touch($newPath);
794
795         $this->filesystem->rename($file, $newPath);
796     }
797
798     public function testRenameOverwritesTheTargetIfItAlreadyExists()
799     {
800         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
801         $newPath = $this->workspace.\DIRECTORY_SEPARATOR.'new_file';
802
803         touch($file);
804         touch($newPath);
805
806         $this->filesystem->rename($file, $newPath, true);
807
808         $this->assertFileNotExists($file);
809         $this->assertFileExists($newPath);
810     }
811
812     /**
813      * @expectedException \Symfony\Component\Filesystem\Exception\IOException
814      */
815     public function testRenameThrowsExceptionOnError()
816     {
817         $file = $this->workspace.\DIRECTORY_SEPARATOR.uniqid('fs_test_', true);
818         $newPath = $this->workspace.\DIRECTORY_SEPARATOR.'new_file';
819
820         $this->filesystem->rename($file, $newPath);
821     }
822
823     public function testSymlink()
824     {
825         if ('\\' === \DIRECTORY_SEPARATOR) {
826             $this->markTestSkipped('Windows does not support creating "broken" symlinks');
827         }
828
829         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
830         $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
831
832         // $file does not exists right now: creating "broken" links is a wanted feature
833         $this->filesystem->symlink($file, $link);
834
835         $this->assertTrue(is_link($link));
836
837         // Create the linked file AFTER creating the link
838         touch($file);
839
840         $this->assertEquals($file, readlink($link));
841     }
842
843     /**
844      * @depends testSymlink
845      */
846     public function testRemoveSymlink()
847     {
848         $this->markAsSkippedIfSymlinkIsMissing();
849
850         $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
851
852         $this->filesystem->remove($link);
853
854         $this->assertFalse(is_link($link));
855         $this->assertFalse(is_file($link));
856         $this->assertFalse(is_dir($link));
857     }
858
859     public function testSymlinkIsOverwrittenIfPointsToDifferentTarget()
860     {
861         $this->markAsSkippedIfSymlinkIsMissing();
862
863         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
864         $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
865
866         touch($file);
867         symlink($this->workspace, $link);
868
869         $this->filesystem->symlink($file, $link);
870
871         $this->assertTrue(is_link($link));
872         $this->assertEquals($file, readlink($link));
873     }
874
875     public function testSymlinkIsNotOverwrittenIfAlreadyCreated()
876     {
877         $this->markAsSkippedIfSymlinkIsMissing();
878
879         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
880         $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
881
882         touch($file);
883         symlink($file, $link);
884
885         $this->filesystem->symlink($file, $link);
886
887         $this->assertTrue(is_link($link));
888         $this->assertEquals($file, readlink($link));
889     }
890
891     public function testSymlinkCreatesTargetDirectoryIfItDoesNotExist()
892     {
893         $this->markAsSkippedIfSymlinkIsMissing();
894
895         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
896         $link1 = $this->workspace.\DIRECTORY_SEPARATOR.'dir'.\DIRECTORY_SEPARATOR.'link';
897         $link2 = $this->workspace.\DIRECTORY_SEPARATOR.'dir'.\DIRECTORY_SEPARATOR.'subdir'.\DIRECTORY_SEPARATOR.'link';
898
899         touch($file);
900
901         $this->filesystem->symlink($file, $link1);
902         $this->filesystem->symlink($file, $link2);
903
904         $this->assertTrue(is_link($link1));
905         $this->assertEquals($file, readlink($link1));
906         $this->assertTrue(is_link($link2));
907         $this->assertEquals($file, readlink($link2));
908     }
909
910     public function testLink()
911     {
912         $this->markAsSkippedIfLinkIsMissing();
913
914         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
915         $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
916
917         touch($file);
918         $this->filesystem->hardlink($file, $link);
919
920         $this->assertTrue(is_file($link));
921         $this->assertEquals(fileinode($file), fileinode($link));
922     }
923
924     /**
925      * @depends testLink
926      */
927     public function testRemoveLink()
928     {
929         $this->markAsSkippedIfLinkIsMissing();
930
931         $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
932
933         $this->filesystem->remove($link);
934
935         $this->assertTrue(!is_file($link));
936     }
937
938     public function testLinkIsOverwrittenIfPointsToDifferentTarget()
939     {
940         $this->markAsSkippedIfLinkIsMissing();
941
942         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
943         $file2 = $this->workspace.\DIRECTORY_SEPARATOR.'file2';
944         $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
945
946         touch($file);
947         touch($file2);
948         link($file2, $link);
949
950         $this->filesystem->hardlink($file, $link);
951
952         $this->assertTrue(is_file($link));
953         $this->assertEquals(fileinode($file), fileinode($link));
954     }
955
956     public function testLinkIsNotOverwrittenIfAlreadyCreated()
957     {
958         $this->markAsSkippedIfLinkIsMissing();
959
960         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
961         $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
962
963         touch($file);
964         link($file, $link);
965
966         $this->filesystem->hardlink($file, $link);
967
968         $this->assertTrue(is_file($link));
969         $this->assertEquals(fileinode($file), fileinode($link));
970     }
971
972     public function testLinkWithSeveralTargets()
973     {
974         $this->markAsSkippedIfLinkIsMissing();
975
976         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
977         $link1 = $this->workspace.\DIRECTORY_SEPARATOR.'link';
978         $link2 = $this->workspace.\DIRECTORY_SEPARATOR.'link2';
979
980         touch($file);
981
982         $this->filesystem->hardlink($file, array($link1, $link2));
983
984         $this->assertTrue(is_file($link1));
985         $this->assertEquals(fileinode($file), fileinode($link1));
986         $this->assertTrue(is_file($link2));
987         $this->assertEquals(fileinode($file), fileinode($link2));
988     }
989
990     public function testLinkWithSameTarget()
991     {
992         $this->markAsSkippedIfLinkIsMissing();
993
994         $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
995         $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
996
997         touch($file);
998
999         // practically same as testLinkIsNotOverwrittenIfAlreadyCreated
1000         $this->filesystem->hardlink($file, array($link, $link));
1001
1002         $this->assertTrue(is_file($link));
1003         $this->assertEquals(fileinode($file), fileinode($link));
1004     }
1005
1006     public function testReadRelativeLink()
1007     {
1008         $this->markAsSkippedIfSymlinkIsMissing();
1009
1010         if ('\\' === \DIRECTORY_SEPARATOR) {
1011             $this->markTestSkipped('Relative symbolic links are not supported on Windows');
1012         }
1013
1014         $file = $this->workspace.'/file';
1015         $link1 = $this->workspace.'/dir/link';
1016         $link2 = $this->workspace.'/dir/link2';
1017         touch($file);
1018
1019         $this->filesystem->symlink('../file', $link1);
1020         $this->filesystem->symlink('link', $link2);
1021
1022         $this->assertEquals($this->normalize('../file'), $this->filesystem->readlink($link1));
1023         $this->assertEquals('link', $this->filesystem->readlink($link2));
1024         $this->assertEquals($file, $this->filesystem->readlink($link1, true));
1025         $this->assertEquals($file, $this->filesystem->readlink($link2, true));
1026         $this->assertEquals($file, $this->filesystem->readlink($file, true));
1027     }
1028
1029     public function testReadAbsoluteLink()
1030     {
1031         $this->markAsSkippedIfSymlinkIsMissing();
1032
1033         $file = $this->normalize($this->workspace.'/file');
1034         $link1 = $this->normalize($this->workspace.'/dir/link');
1035         $link2 = $this->normalize($this->workspace.'/dir/link2');
1036         touch($file);
1037
1038         $this->filesystem->symlink($file, $link1);
1039         $this->filesystem->symlink($link1, $link2);
1040
1041         $this->assertEquals($file, $this->filesystem->readlink($link1));
1042         $this->assertEquals($link1, $this->filesystem->readlink($link2));
1043         $this->assertEquals($file, $this->filesystem->readlink($link1, true));
1044         $this->assertEquals($file, $this->filesystem->readlink($link2, true));
1045         $this->assertEquals($file, $this->filesystem->readlink($file, true));
1046     }
1047
1048     public function testReadBrokenLink()
1049     {
1050         $this->markAsSkippedIfSymlinkIsMissing();
1051
1052         if ('\\' === \DIRECTORY_SEPARATOR) {
1053             $this->markTestSkipped('Windows does not support creating "broken" symlinks');
1054         }
1055
1056         $file = $this->workspace.'/file';
1057         $link = $this->workspace.'/link';
1058
1059         $this->filesystem->symlink($file, $link);
1060
1061         $this->assertEquals($file, $this->filesystem->readlink($link));
1062         $this->assertNull($this->filesystem->readlink($link, true));
1063
1064         touch($file);
1065         $this->assertEquals($file, $this->filesystem->readlink($link, true));
1066     }
1067
1068     public function testReadLinkDefaultPathDoesNotExist()
1069     {
1070         $this->assertNull($this->filesystem->readlink($this->normalize($this->workspace.'/invalid')));
1071     }
1072
1073     public function testReadLinkDefaultPathNotLink()
1074     {
1075         $file = $this->normalize($this->workspace.'/file');
1076         touch($file);
1077
1078         $this->assertNull($this->filesystem->readlink($file));
1079     }
1080
1081     public function testReadLinkCanonicalizePath()
1082     {
1083         $this->markAsSkippedIfSymlinkIsMissing();
1084
1085         $file = $this->normalize($this->workspace.'/file');
1086         mkdir($this->normalize($this->workspace.'/dir'));
1087         touch($file);
1088
1089         $this->assertEquals($file, $this->filesystem->readlink($this->normalize($this->workspace.'/dir/../file'), true));
1090     }
1091
1092     public function testReadLinkCanonicalizedPathDoesNotExist()
1093     {
1094         $this->assertNull($this->filesystem->readlink($this->normalize($this->workspace.'invalid'), true));
1095     }
1096
1097     /**
1098      * @dataProvider providePathsForMakePathRelative
1099      */
1100     public function testMakePathRelative($endPath, $startPath, $expectedPath)
1101     {
1102         $path = $this->filesystem->makePathRelative($endPath, $startPath);
1103
1104         $this->assertEquals($expectedPath, $path);
1105     }
1106
1107     public function providePathsForMakePathRelative()
1108     {
1109         $paths = array(
1110             array('/var/lib/symfony/src/Symfony/', '/var/lib/symfony/src/Symfony/Component', '../'),
1111             array('/var/lib/symfony/src/Symfony/', '/var/lib/symfony/src/Symfony/Component/', '../'),
1112             array('/var/lib/symfony/src/Symfony', '/var/lib/symfony/src/Symfony/Component', '../'),
1113             array('/var/lib/symfony/src/Symfony', '/var/lib/symfony/src/Symfony/Component/', '../'),
1114             array('/usr/lib/symfony/', '/var/lib/symfony/src/Symfony/Component', '../../../../../../usr/lib/symfony/'),
1115             array('/var/lib/symfony/src/Symfony/', '/var/lib/symfony/', 'src/Symfony/'),
1116             array('/aa/bb', '/aa/bb', './'),
1117             array('/aa/bb', '/aa/bb/', './'),
1118             array('/aa/bb/', '/aa/bb', './'),
1119             array('/aa/bb/', '/aa/bb/', './'),
1120             array('/aa/bb/cc', '/aa/bb/cc/dd', '../'),
1121             array('/aa/bb/cc', '/aa/bb/cc/dd/', '../'),
1122             array('/aa/bb/cc/', '/aa/bb/cc/dd', '../'),
1123             array('/aa/bb/cc/', '/aa/bb/cc/dd/', '../'),
1124             array('/aa/bb/cc', '/aa', 'bb/cc/'),
1125             array('/aa/bb/cc', '/aa/', 'bb/cc/'),
1126             array('/aa/bb/cc/', '/aa', 'bb/cc/'),
1127             array('/aa/bb/cc/', '/aa/', 'bb/cc/'),
1128             array('/a/aab/bb', '/a/aa', '../aab/bb/'),
1129             array('/a/aab/bb', '/a/aa/', '../aab/bb/'),
1130             array('/a/aab/bb/', '/a/aa', '../aab/bb/'),
1131             array('/a/aab/bb/', '/a/aa/', '../aab/bb/'),
1132             array('/a/aab/bb/', '/', 'a/aab/bb/'),
1133             array('/a/aab/bb/', '/b/aab', '../../a/aab/bb/'),
1134             array('/aab/bb', '/aa', '../aab/bb/'),
1135             array('/aab', '/aa', '../aab/'),
1136             array('/aa/bb/cc', '/aa/dd/..', 'bb/cc/'),
1137             array('/aa/../bb/cc', '/aa/dd/..', '../bb/cc/'),
1138             array('/aa/bb/../../cc', '/aa/../dd/..', 'cc/'),
1139             array('/../aa/bb/cc', '/aa/dd/..', 'bb/cc/'),
1140             array('/../../aa/../bb/cc', '/aa/dd/..', '../bb/cc/'),
1141             array('C:/aa/bb/cc', 'C:/aa/dd/..', 'bb/cc/'),
1142             array('c:/aa/../bb/cc', 'c:/aa/dd/..', '../bb/cc/'),
1143             array('C:/aa/bb/../../cc', 'C:/aa/../dd/..', 'cc/'),
1144             array('C:/../aa/bb/cc', 'C:/aa/dd/..', 'bb/cc/'),
1145             array('C:/../../aa/../bb/cc', 'C:/aa/dd/..', '../bb/cc/'),
1146         );
1147
1148         if ('\\' === \DIRECTORY_SEPARATOR) {
1149             $paths[] = array('c:\var\lib/symfony/src/Symfony/', 'c:/var/lib/symfony/', 'src/Symfony/');
1150         }
1151
1152         return $paths;
1153     }
1154
1155     /**
1156      * @group legacy
1157      * @dataProvider provideLegacyPathsForMakePathRelativeWithRelativePaths
1158      * @expectedDeprecation Support for passing relative paths to Symfony\Component\Filesystem\Filesystem::makePathRelative() is deprecated since Symfony 3.4 and will be removed in 4.0.
1159      */
1160     public function testMakePathRelativeWithRelativePaths($endPath, $startPath, $expectedPath)
1161     {
1162         $path = $this->filesystem->makePathRelative($endPath, $startPath);
1163
1164         $this->assertEquals($expectedPath, $path);
1165     }
1166
1167     public function provideLegacyPathsForMakePathRelativeWithRelativePaths()
1168     {
1169         return array(
1170             array('usr/lib/symfony/', 'var/lib/symfony/src/Symfony/Component', '../../../../../../usr/lib/symfony/'),
1171             array('aa/bb', 'aa/cc', '../bb/'),
1172             array('aa/cc', 'bb/cc', '../../aa/cc/'),
1173             array('aa/bb', 'aa/./cc', '../bb/'),
1174             array('aa/./bb', 'aa/cc', '../bb/'),
1175             array('aa/./bb', 'aa/./cc', '../bb/'),
1176             array('../../', '../../', './'),
1177             array('../aa/bb/', 'aa/bb/', '../../../aa/bb/'),
1178             array('../../../', '../../', '../'),
1179             array('', '', './'),
1180             array('', 'aa/', '../'),
1181             array('aa/', '', 'aa/'),
1182         );
1183     }
1184
1185     public function testMirrorCopiesFilesAndDirectoriesRecursively()
1186     {
1187         $sourcePath = $this->workspace.\DIRECTORY_SEPARATOR.'source'.\DIRECTORY_SEPARATOR;
1188         $directory = $sourcePath.'directory'.\DIRECTORY_SEPARATOR;
1189         $file1 = $directory.'file1';
1190         $file2 = $sourcePath.'file2';
1191
1192         mkdir($sourcePath);
1193         mkdir($directory);
1194         file_put_contents($file1, 'FILE1');
1195         file_put_contents($file2, 'FILE2');
1196
1197         $targetPath = $this->workspace.\DIRECTORY_SEPARATOR.'target'.\DIRECTORY_SEPARATOR;
1198
1199         $this->filesystem->mirror($sourcePath, $targetPath);
1200
1201         $this->assertTrue(is_dir($targetPath));
1202         $this->assertTrue(is_dir($targetPath.'directory'));
1203         $this->assertFileEquals($file1, $targetPath.'directory'.\DIRECTORY_SEPARATOR.'file1');
1204         $this->assertFileEquals($file2, $targetPath.'file2');
1205
1206         $this->filesystem->remove($file1);
1207
1208         $this->filesystem->mirror($sourcePath, $targetPath, null, array('delete' => false));
1209         $this->assertTrue($this->filesystem->exists($targetPath.'directory'.\DIRECTORY_SEPARATOR.'file1'));
1210
1211         $this->filesystem->mirror($sourcePath, $targetPath, null, array('delete' => true));
1212         $this->assertFalse($this->filesystem->exists($targetPath.'directory'.\DIRECTORY_SEPARATOR.'file1'));
1213
1214         file_put_contents($file1, 'FILE1');
1215
1216         $this->filesystem->mirror($sourcePath, $targetPath, null, array('delete' => true));
1217         $this->assertTrue($this->filesystem->exists($targetPath.'directory'.\DIRECTORY_SEPARATOR.'file1'));
1218
1219         $this->filesystem->remove($directory);
1220         $this->filesystem->mirror($sourcePath, $targetPath, null, array('delete' => true));
1221         $this->assertFalse($this->filesystem->exists($targetPath.'directory'));
1222         $this->assertFalse($this->filesystem->exists($targetPath.'directory'.\DIRECTORY_SEPARATOR.'file1'));
1223     }
1224
1225     public function testMirrorCreatesEmptyDirectory()
1226     {
1227         $sourcePath = $this->workspace.\DIRECTORY_SEPARATOR.'source'.\DIRECTORY_SEPARATOR;
1228
1229         mkdir($sourcePath);
1230
1231         $targetPath = $this->workspace.\DIRECTORY_SEPARATOR.'target'.\DIRECTORY_SEPARATOR;
1232
1233         $this->filesystem->mirror($sourcePath, $targetPath);
1234
1235         $this->assertTrue(is_dir($targetPath));
1236
1237         $this->filesystem->remove($sourcePath);
1238     }
1239
1240     public function testMirrorCopiesLinks()
1241     {
1242         $this->markAsSkippedIfSymlinkIsMissing();
1243
1244         $sourcePath = $this->workspace.\DIRECTORY_SEPARATOR.'source'.\DIRECTORY_SEPARATOR;
1245
1246         mkdir($sourcePath);
1247         file_put_contents($sourcePath.'file1', 'FILE1');
1248         symlink($sourcePath.'file1', $sourcePath.'link1');
1249
1250         $targetPath = $this->workspace.\DIRECTORY_SEPARATOR.'target'.\DIRECTORY_SEPARATOR;
1251
1252         $this->filesystem->mirror($sourcePath, $targetPath);
1253
1254         $this->assertTrue(is_dir($targetPath));
1255         $this->assertFileEquals($sourcePath.'file1', $targetPath.'link1');
1256         $this->assertTrue(is_link($targetPath.\DIRECTORY_SEPARATOR.'link1'));
1257     }
1258
1259     public function testMirrorCopiesLinkedDirectoryContents()
1260     {
1261         $this->markAsSkippedIfSymlinkIsMissing(true);
1262
1263         $sourcePath = $this->workspace.\DIRECTORY_SEPARATOR.'source'.\DIRECTORY_SEPARATOR;
1264
1265         mkdir($sourcePath.'nested/', 0777, true);
1266         file_put_contents($sourcePath.'/nested/file1.txt', 'FILE1');
1267         // Note: We symlink directory, not file
1268         symlink($sourcePath.'nested', $sourcePath.'link1');
1269
1270         $targetPath = $this->workspace.\DIRECTORY_SEPARATOR.'target'.\DIRECTORY_SEPARATOR;
1271
1272         $this->filesystem->mirror($sourcePath, $targetPath);
1273
1274         $this->assertTrue(is_dir($targetPath));
1275         $this->assertFileEquals($sourcePath.'/nested/file1.txt', $targetPath.'link1/file1.txt');
1276         $this->assertTrue(is_link($targetPath.\DIRECTORY_SEPARATOR.'link1'));
1277     }
1278
1279     public function testMirrorCopiesRelativeLinkedContents()
1280     {
1281         $this->markAsSkippedIfSymlinkIsMissing(true);
1282
1283         $sourcePath = $this->workspace.\DIRECTORY_SEPARATOR.'source'.\DIRECTORY_SEPARATOR;
1284         $oldPath = getcwd();
1285
1286         mkdir($sourcePath.'nested/', 0777, true);
1287         file_put_contents($sourcePath.'/nested/file1.txt', 'FILE1');
1288         // Note: Create relative symlink
1289         chdir($sourcePath);
1290         symlink('nested', 'link1');
1291
1292         chdir($oldPath);
1293
1294         $targetPath = $this->workspace.\DIRECTORY_SEPARATOR.'target'.\DIRECTORY_SEPARATOR;
1295
1296         $this->filesystem->mirror($sourcePath, $targetPath);
1297
1298         $this->assertTrue(is_dir($targetPath));
1299         $this->assertFileEquals($sourcePath.'/nested/file1.txt', $targetPath.'link1/file1.txt');
1300         $this->assertTrue(is_link($targetPath.\DIRECTORY_SEPARATOR.'link1'));
1301         $this->assertEquals('\\' === \DIRECTORY_SEPARATOR ? realpath($sourcePath.'\nested') : 'nested', readlink($targetPath.\DIRECTORY_SEPARATOR.'link1'));
1302     }
1303
1304     public function testMirrorContentsWithSameNameAsSourceOrTargetWithoutDeleteOption()
1305     {
1306         $sourcePath = $this->workspace.\DIRECTORY_SEPARATOR.'source'.\DIRECTORY_SEPARATOR;
1307
1308         mkdir($sourcePath);
1309         touch($sourcePath.'source');
1310         touch($sourcePath.'target');
1311
1312         $targetPath = $this->workspace.\DIRECTORY_SEPARATOR.'target'.\DIRECTORY_SEPARATOR;
1313
1314         $oldPath = getcwd();
1315         chdir($this->workspace);
1316
1317         $this->filesystem->mirror('source', $targetPath);
1318
1319         chdir($oldPath);
1320
1321         $this->assertTrue(is_dir($targetPath));
1322         $this->assertFileExists($targetPath.'source');
1323         $this->assertFileExists($targetPath.'target');
1324     }
1325
1326     public function testMirrorContentsWithSameNameAsSourceOrTargetWithDeleteOption()
1327     {
1328         $sourcePath = $this->workspace.\DIRECTORY_SEPARATOR.'source'.\DIRECTORY_SEPARATOR;
1329
1330         mkdir($sourcePath);
1331         touch($sourcePath.'source');
1332
1333         $targetPath = $this->workspace.\DIRECTORY_SEPARATOR.'target'.\DIRECTORY_SEPARATOR;
1334
1335         mkdir($targetPath);
1336         touch($targetPath.'source');
1337         touch($targetPath.'target');
1338
1339         $oldPath = getcwd();
1340         chdir($this->workspace);
1341
1342         $this->filesystem->mirror('source', 'target', null, array('delete' => true));
1343
1344         chdir($oldPath);
1345
1346         $this->assertTrue(is_dir($targetPath));
1347         $this->assertFileExists($targetPath.'source');
1348         $this->assertFileNotExists($targetPath.'target');
1349     }
1350
1351     /**
1352      * @dataProvider providePathsForIsAbsolutePath
1353      */
1354     public function testIsAbsolutePath($path, $expectedResult)
1355     {
1356         $result = $this->filesystem->isAbsolutePath($path);
1357
1358         $this->assertEquals($expectedResult, $result);
1359     }
1360
1361     public function providePathsForIsAbsolutePath()
1362     {
1363         return array(
1364             array('/var/lib', true),
1365             array('c:\\\\var\\lib', true),
1366             array('\\var\\lib', true),
1367             array('var/lib', false),
1368             array('../var/lib', false),
1369             array('', false),
1370             array(null, false),
1371         );
1372     }
1373
1374     public function testTempnam()
1375     {
1376         $dirname = $this->workspace;
1377
1378         $filename = $this->filesystem->tempnam($dirname, 'foo');
1379
1380         $this->assertFileExists($filename);
1381     }
1382
1383     public function testTempnamWithFileScheme()
1384     {
1385         $scheme = 'file://';
1386         $dirname = $scheme.$this->workspace;
1387
1388         $filename = $this->filesystem->tempnam($dirname, 'foo');
1389
1390         $this->assertStringStartsWith($scheme, $filename);
1391         $this->assertFileExists($filename);
1392     }
1393
1394     public function testTempnamWithMockScheme()
1395     {
1396         stream_wrapper_register('mock', 'Symfony\Component\Filesystem\Tests\Fixtures\MockStream\MockStream');
1397
1398         $scheme = 'mock://';
1399         $dirname = $scheme.$this->workspace;
1400
1401         $filename = $this->filesystem->tempnam($dirname, 'foo');
1402
1403         $this->assertStringStartsWith($scheme, $filename);
1404         $this->assertFileExists($filename);
1405     }
1406
1407     /**
1408      * @expectedException \Symfony\Component\Filesystem\Exception\IOException
1409      */
1410     public function testTempnamWithZlibSchemeFails()
1411     {
1412         $scheme = 'compress.zlib://';
1413         $dirname = $scheme.$this->workspace;
1414
1415         // The compress.zlib:// stream does not support mode x: creates the file, errors "failed to open stream: operation failed" and returns false
1416         $this->filesystem->tempnam($dirname, 'bar');
1417     }
1418
1419     public function testTempnamWithPHPTempSchemeFails()
1420     {
1421         $scheme = 'php://temp';
1422         $dirname = $scheme;
1423
1424         $filename = $this->filesystem->tempnam($dirname, 'bar');
1425
1426         $this->assertStringStartsWith($scheme, $filename);
1427
1428         // The php://temp stream deletes the file after close
1429         $this->assertFileNotExists($filename);
1430     }
1431
1432     /**
1433      * @expectedException \Symfony\Component\Filesystem\Exception\IOException
1434      */
1435     public function testTempnamWithPharSchemeFails()
1436     {
1437         // Skip test if Phar disabled phar.readonly must be 0 in php.ini
1438         if (!\Phar::canWrite()) {
1439             $this->markTestSkipped('This test cannot run when phar.readonly is 1.');
1440         }
1441
1442         $scheme = 'phar://';
1443         $dirname = $scheme.$this->workspace;
1444         $pharname = 'foo.phar';
1445
1446         new \Phar($this->workspace.'/'.$pharname, 0, $pharname);
1447         // The phar:// stream does not support mode x: fails to create file, errors "failed to open stream: phar error: "$filename" is not a file in phar "$pharname"" and returns false
1448         $this->filesystem->tempnam($dirname, $pharname.'/bar');
1449     }
1450
1451     /**
1452      * @expectedException \Symfony\Component\Filesystem\Exception\IOException
1453      */
1454     public function testTempnamWithHTTPSchemeFails()
1455     {
1456         $scheme = 'http://';
1457         $dirname = $scheme.$this->workspace;
1458
1459         // The http:// scheme is read-only
1460         $this->filesystem->tempnam($dirname, 'bar');
1461     }
1462
1463     public function testTempnamOnUnwritableFallsBackToSysTmp()
1464     {
1465         $scheme = 'file://';
1466         $dirname = $scheme.$this->workspace.\DIRECTORY_SEPARATOR.'does_not_exist';
1467
1468         $filename = $this->filesystem->tempnam($dirname, 'bar');
1469         $realTempDir = realpath(sys_get_temp_dir());
1470         $this->assertStringStartsWith(rtrim($scheme.$realTempDir, \DIRECTORY_SEPARATOR), $filename);
1471         $this->assertFileExists($filename);
1472
1473         // Tear down
1474         @unlink($filename);
1475     }
1476
1477     public function testDumpFile()
1478     {
1479         $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'baz.txt';
1480
1481         // skip mode check on Windows
1482         if ('\\' !== \DIRECTORY_SEPARATOR) {
1483             $oldMask = umask(0002);
1484         }
1485
1486         $this->filesystem->dumpFile($filename, 'bar');
1487         $this->assertFileExists($filename);
1488         $this->assertStringEqualsFile($filename, 'bar');
1489
1490         // skip mode check on Windows
1491         if ('\\' !== \DIRECTORY_SEPARATOR) {
1492             $this->assertFilePermissions(664, $filename);
1493             umask($oldMask);
1494         }
1495     }
1496
1497     public function testDumpFileWithArray()
1498     {
1499         $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'baz.txt';
1500
1501         $this->filesystem->dumpFile($filename, array('bar'));
1502
1503         $this->assertFileExists($filename);
1504         $this->assertStringEqualsFile($filename, 'bar');
1505     }
1506
1507     public function testDumpFileWithResource()
1508     {
1509         $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'baz.txt';
1510
1511         $resource = fopen('php://memory', 'rw');
1512         fwrite($resource, 'bar');
1513         fseek($resource, 0);
1514
1515         $this->filesystem->dumpFile($filename, $resource);
1516
1517         fclose($resource);
1518         $this->assertFileExists($filename);
1519         $this->assertStringEqualsFile($filename, 'bar');
1520     }
1521
1522     public function testDumpFileOverwritesAnExistingFile()
1523     {
1524         $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo.txt';
1525         file_put_contents($filename, 'FOO BAR');
1526
1527         $this->filesystem->dumpFile($filename, 'bar');
1528
1529         $this->assertFileExists($filename);
1530         $this->assertStringEqualsFile($filename, 'bar');
1531     }
1532
1533     public function testDumpFileWithFileScheme()
1534     {
1535         if (\defined('HHVM_VERSION')) {
1536             $this->markTestSkipped('HHVM does not handle the file:// scheme correctly');
1537         }
1538
1539         $scheme = 'file://';
1540         $filename = $scheme.$this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'baz.txt';
1541
1542         $this->filesystem->dumpFile($filename, 'bar');
1543
1544         $this->assertFileExists($filename);
1545         $this->assertStringEqualsFile($filename, 'bar');
1546     }
1547
1548     public function testDumpFileWithZlibScheme()
1549     {
1550         $scheme = 'compress.zlib://';
1551         $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'baz.txt';
1552
1553         $this->filesystem->dumpFile($filename, 'bar');
1554
1555         // Zlib stat uses file:// wrapper so remove scheme
1556         $this->assertFileExists(str_replace($scheme, '', $filename));
1557         $this->assertStringEqualsFile($filename, 'bar');
1558     }
1559
1560     public function testAppendToFile()
1561     {
1562         $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'bar.txt';
1563
1564         // skip mode check on Windows
1565         if ('\\' !== \DIRECTORY_SEPARATOR) {
1566             $oldMask = umask(0002);
1567         }
1568
1569         $this->filesystem->dumpFile($filename, 'foo');
1570
1571         $this->filesystem->appendToFile($filename, 'bar');
1572
1573         $this->assertFileExists($filename);
1574         $this->assertStringEqualsFile($filename, 'foobar');
1575
1576         // skip mode check on Windows
1577         if ('\\' !== \DIRECTORY_SEPARATOR) {
1578             $this->assertFilePermissions(664, $filename);
1579             umask($oldMask);
1580         }
1581     }
1582
1583     public function testAppendToFileWithScheme()
1584     {
1585         if (\defined('HHVM_VERSION')) {
1586             $this->markTestSkipped('HHVM does not handle the file:// scheme correctly');
1587         }
1588
1589         $scheme = 'file://';
1590         $filename = $scheme.$this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'baz.txt';
1591         $this->filesystem->dumpFile($filename, 'foo');
1592
1593         $this->filesystem->appendToFile($filename, 'bar');
1594
1595         $this->assertFileExists($filename);
1596         $this->assertStringEqualsFile($filename, 'foobar');
1597     }
1598
1599     public function testAppendToFileWithZlibScheme()
1600     {
1601         $scheme = 'compress.zlib://';
1602         $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'baz.txt';
1603         $this->filesystem->dumpFile($filename, 'foo');
1604
1605         // Zlib stat uses file:// wrapper so remove it
1606         $this->assertStringEqualsFile(str_replace($scheme, '', $filename), 'foo');
1607
1608         $this->filesystem->appendToFile($filename, 'bar');
1609
1610         $this->assertFileExists($filename);
1611         $this->assertStringEqualsFile($filename, 'foobar');
1612     }
1613
1614     public function testAppendToFileCreateTheFileIfNotExists()
1615     {
1616         $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'bar.txt';
1617
1618         // skip mode check on Windows
1619         if ('\\' !== \DIRECTORY_SEPARATOR) {
1620             $oldMask = umask(0002);
1621         }
1622
1623         $this->filesystem->appendToFile($filename, 'bar');
1624
1625         // skip mode check on Windows
1626         if ('\\' !== \DIRECTORY_SEPARATOR) {
1627             $this->assertFilePermissions(664, $filename);
1628             umask($oldMask);
1629         }
1630
1631         $this->assertFileExists($filename);
1632         $this->assertStringEqualsFile($filename, 'bar');
1633     }
1634
1635     public function testDumpKeepsExistingPermissionsWhenOverwritingAnExistingFile()
1636     {
1637         $this->markAsSkippedIfChmodIsMissing();
1638
1639         $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo.txt';
1640         file_put_contents($filename, 'FOO BAR');
1641         chmod($filename, 0745);
1642
1643         $this->filesystem->dumpFile($filename, 'bar', null);
1644
1645         $this->assertFilePermissions(745, $filename);
1646     }
1647
1648     public function testCopyShouldKeepExecutionPermission()
1649     {
1650         $this->markAsSkippedIfChmodIsMissing();
1651
1652         $sourceFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_source_file';
1653         $targetFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_target_file';
1654
1655         file_put_contents($sourceFilePath, 'SOURCE FILE');
1656         chmod($sourceFilePath, 0745);
1657
1658         $this->filesystem->copy($sourceFilePath, $targetFilePath);
1659
1660         $this->assertFilePermissions(767, $targetFilePath);
1661     }
1662
1663     /**
1664      * Normalize the given path (transform each blackslash into a real directory separator).
1665      *
1666      * @param string $path
1667      *
1668      * @return string
1669      */
1670     private function normalize($path)
1671     {
1672         return str_replace('/', \DIRECTORY_SEPARATOR, $path);
1673     }
1674 }