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