The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
#! /usr/bin/env perl

#
# Author tests
#
# Run 'setup_tests.pl' as root to setup the directory scenario.
#   setup_tests.pl -i
#
# Run 'setup_tests.pl' as root to clean the directory.
#   setup_tests.pl -r
#

# The directory tree root is 'xt/extra'.


use strict;
use File::Path;
use File::Basename;

use Config;
use File::Spec::Functions;
use Fcntl ':mode';
use Test::More;
use lib 't/';
use FilePathTest;

plan skip_all => 'Symlinks not supported or not setup - see setup_tests.pl'
  if not $Config{d_symlink} or
     not -d dirname(__FILE__) . '/extra';

plan tests => 28;

my $d = dirname(__FILE__) . '/extra';

my ($file, $error, $message, $rv);
my $dir;
my $dir2;



my $extra =  catdir(curdir(), qw($d 1 a));

my ($list, $err);

$dir = catdir( $d, '1' );
rmtree( $dir, { result => \$list,
                error  => \$err } );
is(scalar(@$list), 2, "extra dir $dir and two files removed");
is(scalar(@$err), 1, "no errors encountered");

# root can delete symlink to directory
$dir = catdir( $d, '3', 'N' );
rmtree( $dir, {result => \$list, error => \$err} );
is( @$list, 1, q{remove a symlinked dir} );
is( @$err,  0, q{with no errors} );

$dir = catdir($d, '3', 'S');
rmtree($dir, {error => \$error});
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($d, '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( $d, '4' );
rmtree($dir,  {result => \$list, error => \$err} );
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($d, '3', 'U');
$dir2 = catdir($d, '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' );



$dir = catdir($d, '3');

$dir = catdir($d, '3', 'U');
$rv = _run_for_warning( sub { rmtree($dir, {verbose => 0}) } );
like( $rv,
      qr{\Acannot make child directory read-write-exec for [^:]+: .* at \S+ line \d+\.?},
      q(rmtree can't chdir into root dir)
);

$dir = catdir($d, '3');
$rv = _run_for_warning( sub { rmtree( $dir, { } ) } );
like( $rv,
      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'
);

$rv = _run_for_warning( sub { rmtree( $d, { } ) } );
like( $rv,
      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},
    'rmtree with insufficient privileges'
);

rmtree $d, {safe => 0, error => \$error};
is( scalar(@$error), 10, 'seven deadly sins' ); # well there used to be 7

rmtree $d, {safe => 1, error => \$error};
is( scalar(@$error), 9, 'safe is better' );
for (@$error) {
    ($file, $message) = each %$_;
    if ($file =~  /[123]\z/) {
        is(index($message, 'cannot remove directory: '), 0, "failed to remove $file with rmdir")
            or diag($message);
    }
    else {
        like($message, qr(\Acannot (?:restore permissions to \d+|chdir to child|unlink file): ), "failed to remove $file with unlink")
            or diag($message)
    }
}