X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FFile%2FPath.t;h=3ecd8f646dbe1cd1e8e98c7667f07627b75cc098;hb=e63b33793c3cf76a134a6446d1f83479e030a15f;hp=ee320008c0233e48b84868e7fb5f9c894a72db3f;hpb=538f81fb4338b6cec35928f30f0732f2ae710894;p=p5sagit%2Fp5-mst-13.2.git diff --git a/lib/File/Path.t b/lib/File/Path.t index ee32000..3ecd8f6 100755 --- a/lib/File/Path.t +++ b/lib/File/Path.t @@ -2,16 +2,20 @@ use strict; -use Test::More tests => 84; +use Test::More tests => 120; +use Config; BEGIN { - use_ok('File::Path'); + use_ok('Cwd'); + use_ok('File::Path', qw(rmtree mkpath make_path remove_tree)); use_ok('File::Spec::Functions'); } eval "use Test::Output"; my $has_Test_Output = $@ ? 0 : 1; +my $Is_VMS = $^O eq 'VMS'; + # first check for stupid permissions second for full, so we clean up # behind ourselves for my $perm (0111,0777) { @@ -43,7 +47,7 @@ my @dir = ( ); # create them -my @created = mkpath(@dir); +my @created = mkpath([@dir]); is(scalar(@created), 7, "created list of directories"); @@ -52,24 +56,123 @@ is(scalar(@created), 7, "created list of directories"); is(scalar(@created), 0, "skipped making existing directory") or diag("unexpectedly recreated @created"); +# create a file +my $file_name = catfile( $tmp_base, 'a', 'delete.me' ); +my $file_count = 0; +if (open OUT, "> $file_name") { + print OUT "this file may be deleted\n"; + close OUT; + ++$file_count; +} +else { + diag( "Failed to create file $file_name: $!" ); +} + +SKIP: { + skip "cannot remove a file we failed to create", 1 + unless $file_count == 1; + my $count = rmtree($file_name); + is($count, 1, "rmtree'ed a file"); +} + @created = mkpath(''); is(scalar(@created), 0, "Can't create a directory named ''"); my $dir; my $dir2; +sub gisle { + # background info: @_ = 1; !shift # gives '' not 0 + # Message-Id: <3C820CE6-4400-4E91-AF43-A3D19B356E68@activestate.com> + # http://www.nntp.perl.org/group/perl.perl5.porters/2008/05/msg136625.html + mkpath(shift, !shift, 0755); +} + +sub count { + opendir D, shift or return -1; + my $count = () = readdir D; + closedir D or return -1; + return $count; +} + +{ + mkdir 'solo', 0755; + chdir 'solo'; + open my $f, '>', 'foo.dat'; + close $f; + my $before = count(curdir()); + cmp_ok($before, '>', 0, "baseline $before"); + + gisle('1st', 1); + is(count(curdir()), $before + 1, "first after $before"); + + $before = count(curdir()); + gisle('2nd', 1); + is(count(curdir()), $before + 1, "second after $before"); + + chdir updir(); + rmtree 'solo'; +} + +{ + mkdir 'solo', 0755; + chdir 'solo'; + open my $f, '>', 'foo.dat'; + close $f; + my $before = count(curdir()); + cmp_ok($before, '>', 0, "ARGV $before"); + { + local @ARGV = (1); + mkpath('3rd', !shift, 0755); + } + is(count(curdir()), $before + 1, "third after $before"); + + $before = count(curdir()); + { + local @ARGV = (1); + mkpath('4th', !shift, 0755); + } + is(count(curdir()), $before + 1, "fourth after $before"); + + chdir updir(); + rmtree 'solo'; +} + SKIP: { - $dir = catdir($tmp_base, 'B'); - $dir2 = catdir($dir, updir()); - # IOW: File::Spec->catdir( qw(foo bar), File::Spec->updir ) eq 'foo' - # rather than foo/bar/.. - skip "updir() canonicalises path on this platform", 2 - if $dir2 eq $tmp_base - or $^O eq 'cygwin'; - - @created = mkpath($dir2, {mask => 0700}); - is(scalar(@created), 1, "make directory with trailing parent segment"); - is($created[0], $dir, "made parent"); + # tests for rmtree() of ancestor directory + my $nr_tests = 6; + my $cwd = getcwd() or skip "failed to getcwd: $!", $nr_tests; + my $dir = catdir($cwd, 'remove'); + my $dir2 = catdir($cwd, 'remove', 'this', 'dir'); + + skip "failed to mkpath '$dir2': $!", $nr_tests + unless mkpath($dir2, {verbose => 0}); + skip "failed to chdir dir '$dir2': $!", $nr_tests + unless chdir($dir2); + + rmtree($dir, {error => \$error}); + my $nr_err = @$error; + is($nr_err, 1, "ancestor error"); + + if ($nr_err) { + my ($file, $message) = each %{$error->[0]}; + is($file, $dir, "ancestor named"); + my $ortho_dir = $^O eq 'MSWin32' ? File::Path::_slash_lc($dir2) : $dir2; + $^O eq 'MSWin32' and $message + =~ s/\A(cannot remove path when cwd is )(.*)\Z/$1 . File::Path::_slash_lc($2)/e; + is($message, "cannot remove path when cwd is $ortho_dir", "ancestor reason"); + ok(-d $dir2, "child not removed"); + ok(-d $dir, "ancestor not removed"); + } + else { + fail( "ancestor 1"); + fail( "ancestor 2"); + fail( "ancestor 3"); + fail( "ancestor 4"); + } + chdir $cwd; + rmtree($dir); + ok(!(-d $dir), "ancestor now removed"); }; my $count = rmtree({error => \$error}); @@ -82,8 +185,8 @@ is(scalar(@created), 0, "skipped making existing directories (old style 1)") $dir = catdir($tmp_base,'C'); # mkpath returns unix syntax filespecs on VMS -$dir = VMS::Filespec::unixify($dir) if $^O eq 'VMS'; -@created = mkpath($tmp_base, $dir); +$dir = VMS::Filespec::unixify($dir) if $Is_VMS; +@created = make_path($tmp_base, $dir); is(scalar(@created), 1, "created directory (new style 1)"); is($created[0], $dir, "created directory (new style 1) cross-check"); @@ -93,16 +196,17 @@ is(scalar(@created), 0, "skipped making existing directories (old style 2)") $dir2 = catdir($tmp_base,'D'); # mkpath returns unix syntax filespecs on VMS -$dir2 = VMS::Filespec::unixify($dir2) if $^O eq 'VMS'; -@created = mkpath($tmp_base, $dir, $dir2); +$dir2 = VMS::Filespec::unixify($dir2) if $Is_VMS; +@created = make_path($tmp_base, $dir, $dir2); is(scalar(@created), 1, "created directory (new style 2)"); is($created[0], $dir2, "created directory (new style 2) cross-check"); $count = rmtree($dir, 0); -is($count, 1, "removed directory (old style 1)"); +is($count, 1, "removed directory unsafe mode"); $count = rmtree($dir2, 0, 1); -is($count, 1, "removed directory (old style 2)"); +my $removed = $Is_VMS ? 0 : 1; +is($count, $removed, "removed directory safe mode"); # mkdir foo ./E/../Y # Y should exist @@ -113,7 +217,7 @@ cmp_ok(scalar(@created), '>=', 1, "made one or more dirs because of .."); cmp_ok(scalar(@created), '<=', 2, "made less than two dirs because of .."); ok( -d catdir($tmp_base, 'Y'), "directory after parent" ); -@created = mkpath(catdir(curdir(), $tmp_base)); +@created = make_path(catdir(curdir(), $tmp_base)); is(scalar(@created), 0, "nothing created") or diag(@created); @@ -137,7 +241,7 @@ ok(-d $dir2, "dir z still exists"); $dir = catdir($tmp_base,'F'); # mkpath returns unix syntax filespecs on VMS -$dir = VMS::Filespec::unixify($dir) if $^O eq 'VMS'; +$dir = VMS::Filespec::unixify($dir) if $Is_VMS; @created = mkpath($dir, undef, 0770); is(scalar(@created), 1, "created directory (old style 2 verbose undef)"); @@ -154,6 +258,14 @@ is(scalar(@created), 1, "created directory (old style 3 mode undef)"); is($created[0], $dir, "created directory (old style 3 mode undef) cross-check"); is(rmtree($dir, 0, undef), 1, "removed directory 3 verbose undef"); +$dir = catdir($tmp_base,'G'); +$dir = VMS::Filespec::unixify($dir) if $Is_VMS; + +@created = mkpath($dir, undef, 0200); +is(scalar(@created), 1, "created write-only dir"); +is($created[0], $dir, "created write-only directory cross-check"); +is(rmtree($dir), 1, "removed write-only dir"); + # borderline new-style heuristics if (chdir $tmp_base) { pass("chdir to temp dir"); @@ -165,22 +277,22 @@ else { $dir = catdir('a', 'd1'); $dir2 = catdir('a', 'd2'); -@created = mkpath( $dir, 0, $dir2 ); +@created = make_path( $dir, 0, $dir2 ); is(scalar @created, 3, 'new-style 3 dirs created'); -$count = rmtree( $dir, 0, $dir2, ); +$count = remove_tree( $dir, 0, $dir2, ); is($count, 3, 'new-style 3 dirs removed'); -@created = mkpath( $dir, $dir2, 1 ); +@created = make_path( $dir, $dir2, 1 ); is(scalar @created, 3, 'new-style 3 dirs created (redux)'); -$count = rmtree( $dir, $dir2, 1 ); +$count = remove_tree( $dir, $dir2, 1 ); is($count, 3, 'new-style 3 dirs removed (redux)'); -@created = mkpath( $dir, $dir2 ); +@created = make_path( $dir, $dir2 ); is(scalar @created, 2, 'new-style 2 dirs created'); -$count = rmtree( $dir, $dir2 ); +$count = remove_tree( $dir, $dir2 ); is($count, 2, 'new-style 2 dirs removed'); if (chdir updir()) { @@ -190,6 +302,43 @@ else { fail("chdir parent: $!"); } +SKIP: { + # test bug http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=487319 + skip "Don't need Force_Writeable semantics on $^O", 4 + if grep {$^O eq $_} qw(amigaos dos epoc MSWin32 MacOS os2); + skip "Symlinks not available", 4 unless $Config{'d_symlink'}; + $dir = 'bug487319'; + $dir2 = 'bug487319-symlink'; + @created = make_path($dir, {mask => 0700}); + is(scalar @created, 1, 'bug 487319 setup'); + symlink($dir, $dir2); + ok(-e $dir2, "debian bug 487319 setup symlink") or diag($dir2); + + chmod 0500, $dir; + my $mask_initial = (stat $dir)[2]; + remove_tree($dir2); + + my $mask = (stat $dir)[2]; + is( $mask, $mask_initial, 'mask of symlink target dir unchanged (debian bug 487319)'); + + # now try a file + my $file = catfile($dir, 'file'); + open my $out, '>', $file; + close $out; + + chmod 0500, $file; + $mask_initial = (stat $file)[2]; + + my $file2 = catfile($dir, 'symlink'); + symlink($file, $file2); + remove_tree($file2); + + $mask = (stat $file)[2]; + is( $mask, $mask_initial, 'mask of symlink target file unchanged (debian bug 487319)'); + + remove_tree($dir); +} + # see what happens if a file exists where we want a directory SKIP: { my $entry = catdir($tmp_base, "file"); @@ -213,8 +362,9 @@ SKIP: { my $extra = catdir(curdir(), qw(EXTRA 1 a)); SKIP: { - skip "extra scenarios not set up, see eg/setup-extra-tests", 8 + skip "extra scenarios not set up, see eg/setup-extra-tests", 14 unless -e $extra; + skip "Symlinks not available", 14 unless $Config{'d_symlink'}; my ($list, $err); $dir = catdir( 'EXTRA', '1' ); @@ -229,17 +379,31 @@ SKIP: { $dir = catdir('EXTRA', '3', 'S'); rmtree($dir, {error => \$error}); - is( scalar(@$error), 2, 'two errors for an unreadable dir' ); + is( scalar(@$error), 1, 'one error for an unreadable dir' ); + eval { ($file, $message) = each %{$error->[0]}}; + is( $file, $dir, 'unreadable dir reported in error' ) + or diag($message); $dir = catdir('EXTRA', '3', 'T'); rmtree($dir, {error => \$error}); + is( scalar(@$error), 1, 'one error for an unreadable dir T' ); + eval { ($file, $message) = each %{$error->[0]}}; + is( $file, $dir, 'unreadable dir reported in error T' ); $dir = catdir( 'EXTRA', '4' ); rmtree($dir, {result => \$list, error => \$err} ); - is( @$list, 0, q{don't follow a symlinked dir} ); - is( @$err, 1, q{one error when removing a symlink in r/o dir} ); + is( scalar(@$list), 0, q{don't follow a symlinked dir} ); + is( scalar(@$err), 2, q{two errors when removing a symlink in r/o dir} ); eval { ($file, $message) = each %{$err->[0]} }; is( $file, $dir, 'symlink reported in error' ); + + $dir = catdir('EXTRA', '3', 'U'); + $dir2 = catdir('EXTRA', '3', 'V'); + rmtree($dir, $dir2, {verbose => 0, error => \$err, result => \$list}); + is( scalar(@$list), 1, q{deleted 1 out of 2 directories} ); + is( scalar(@$error), 1, q{left behind 1 out of 2 directories} ); + eval { ($file, $message) = each %{$err->[0]} }; + is( $file, $dir, 'first dir reported in error' ); } { @@ -264,25 +428,33 @@ SKIP: { $dir = catdir('EXTRA', '3', 'U'); stderr_like( sub {rmtree($dir, {verbose => 0})}, - qr{\bCan't read \Q$dir\E: }, - q(rmtree can't read root dir) + qr{\Acannot make child directory read-write-exec for [^:]+: .* at \S+ line \d+}, + q(rmtree can't chdir into root dir) ); $dir = catdir('EXTRA', '3'); stderr_like( sub {rmtree($dir, {})}, - qr{\ACan't remove directory \S+: .*? at \S+ line \d+\n}, + qr{\Acannot make child directory read-write-exec for [^:]+: .* at (\S+) line (\d+) +cannot make child directory read-write-exec for [^:]+: .* at \1 line \2 +cannot make child directory read-write-exec for [^:]+: .* at \1 line \2 +cannot remove directory for [^:]+: .* at \1 line \2}, 'rmtree with file owned by root' ); stderr_like( sub {rmtree('EXTRA', {})}, - qr{\ACan't make directory EXTRA read\+writeable: .*? at \S+ line \d+ -(?:Can't remove directory EXTRA/\d: .*? at \S+ line \d+ -)+Can't unlink file [^:]+: .*? at \S+ line \d+ -Can't remove directory EXTRA: .*? at \S+ line \d+ -and can't restore permissions to \d+ - at \S+ line \d+}, + qr{\Acannot remove directory for [^:]+: .* at (\S+) line (\d+) +cannot remove directory for [^:]+: .* at \1 line \2 +cannot make child directory read-write-exec for [^:]+: .* at \1 line \2 +cannot make child directory read-write-exec for [^:]+: .* at \1 line \2 +cannot make child directory read-write-exec for [^:]+: .* at \1 line \2 +cannot remove directory for [^:]+: .* at \1 line \2 +cannot unlink file for [^:]+: .* at \1 line \2 +cannot restore permissions to \d+ for [^:]+: .* at \1 line \2 +cannot make child directory read-write-exec for [^:]+: .* at \1 line \2 +cannot remove directory for [^:]+: .* at \1 line \2 +cannot restore permissions to \d+ for [^:]+: .* at \1 line \2}, 'rmtree with insufficient privileges' ); } @@ -303,8 +475,8 @@ and can't restore permissions to \d+ "rmtree of empty dir carps sensibly" ); - stderr_is( sub { mkpath() }, '', "mkpath no args does not carp" ); - stderr_is( sub { rmtree() }, '', "rmtree no args does not carp" ); + stderr_is( sub { make_path() }, '', "make_path no args does not carp" ); + stderr_is( sub { remove_tree() }, '', "remove_tree no args does not carp" ); stdout_is( sub {@created = mkpath($dir, 1)}, @@ -353,27 +525,48 @@ and can't restore permissions to \d+ } SKIP: { - skip "extra scenarios not set up, see eg/setup-extra-tests", 6 + skip "extra scenarios not set up, see eg/setup-extra-tests", 11 unless -d catdir(qw(EXTRA 1)); rmtree 'EXTRA', {safe => 0, error => \$error}; - is( scalar(@$error), 7, 'seven deadly sins' ); + is( scalar(@$error), 11, 'seven deadly sins' ); # well there used to be 7 rmtree 'EXTRA', {safe => 1, error => \$error}; - is( scalar(@$error), 4, 'safe is better' ); + is( scalar(@$error), 9, 'safe is better' ); for (@$error) { ($file, $message) = each %$_; if ($file =~ /[123]\z/) { - is(index($message, 'rmdir: '), 0, "failed to remove $file with rmdir") + is(index($message, 'cannot remove directory: '), 0, "failed to remove $file with rmdir") or diag($message); } else { - is(index($message, 'unlink: '), 0, "failed to remove $file with unlink") - or diag($message); + like($message, qr(\Acannot (?:restore permissions to \d+|chdir to child|unlink file): ), "failed to remove $file with unlink") + or diag($message) } } } -rmtree($tmp_base, {result => \$list} ); -is(ref($list), 'ARRAY', "received a final list of results"); -ok( !(-d $tmp_base), "test base directory gone" ); +SKIP: { + my $nr_tests = 6; + my $cwd = getcwd() or skip "failed to getcwd: $!", $nr_tests; + rmtree($tmp_base, {result => \$list} ); + is(ref($list), 'ARRAY', "received a final list of results"); + ok( !(-d $tmp_base), "test base directory gone" ); + + my $p = getcwd(); + my $x = "x$$"; + my $xx = $x . "x"; + + # setup + ok(mkpath($xx)); + ok(chdir($xx)); + END { + ok(chdir($p)); + ok(rmtree($xx)); + } + + # create and delete directory + my $px = catdir($p, $x); + ok(mkpath($px)); + ok(rmtree($px), "rmtree"); # fails in File-Path-2.07 +}