2011-11-16 09:46:31 -05:00
|
|
|
#!/usr/bin/perl
|
2019-06-27 09:02:19 -04:00
|
|
|
#
|
|
|
|
# This file is part of GNU Stow.
|
|
|
|
#
|
|
|
|
# GNU Stow is free software: you can redistribute it and/or modify it
|
|
|
|
# under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# GNU Stow is distributed in the hope that it will be useful, but
|
|
|
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
# General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program. If not, see https://www.gnu.org/licenses/.
|
2011-11-16 09:46:31 -05:00
|
|
|
|
2011-11-16 09:04:03 -05:00
|
|
|
#
|
|
|
|
# Utilities shared by test scripts
|
|
|
|
#
|
|
|
|
|
2011-11-24 11:32:01 -05:00
|
|
|
package testutil;
|
|
|
|
|
2011-11-16 09:04:03 -05:00
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
|
2024-04-01 19:36:51 -04:00
|
|
|
use Carp qw(confess croak);
|
2012-02-18 15:19:05 -05:00
|
|
|
use File::Basename;
|
2019-06-27 20:02:48 -04:00
|
|
|
use File::Path qw(make_path remove_tree);
|
2012-02-18 15:19:05 -05:00
|
|
|
use File::Spec;
|
2012-02-18 15:13:32 -05:00
|
|
|
use Test::More;
|
2012-01-09 16:25:35 -05:00
|
|
|
|
2011-11-24 11:28:09 -05:00
|
|
|
use Stow;
|
2011-11-24 11:32:01 -05:00
|
|
|
use Stow::Util qw(parent canon_path);
|
2011-11-17 13:46:13 -05:00
|
|
|
|
2011-11-24 11:32:01 -05:00
|
|
|
use base qw(Exporter);
|
|
|
|
our @EXPORT = qw(
|
2019-06-28 04:56:46 -04:00
|
|
|
$ABS_TEST_DIR
|
2019-06-27 19:53:12 -04:00
|
|
|
$TEST_DIR
|
2011-11-24 11:32:01 -05:00
|
|
|
init_test_dirs
|
|
|
|
cd
|
|
|
|
new_Stow new_compat_Stow
|
2019-06-27 20:02:48 -04:00
|
|
|
make_path make_link make_invalid_link make_file
|
2019-06-28 04:56:46 -04:00
|
|
|
remove_dir remove_file remove_link
|
2012-01-09 16:25:35 -05:00
|
|
|
cat_file
|
2012-02-18 15:13:32 -05:00
|
|
|
is_link is_dir_not_symlink is_nonexistent_path
|
2011-11-24 11:32:01 -05:00
|
|
|
);
|
2011-11-24 11:28:09 -05:00
|
|
|
|
2019-06-27 19:53:12 -04:00
|
|
|
our $TEST_DIR = 'tmp-testing-trees';
|
2019-06-28 04:56:46 -04:00
|
|
|
our $ABS_TEST_DIR = File::Spec->rel2abs('tmp-testing-trees');
|
2011-11-24 11:32:01 -05:00
|
|
|
|
|
|
|
sub init_test_dirs {
|
2024-04-01 19:36:51 -04:00
|
|
|
my $test_dir = shift || $TEST_DIR;
|
|
|
|
my $abs_test_dir = File::Spec->rel2abs($test_dir);
|
|
|
|
|
2019-06-28 04:56:46 -04:00
|
|
|
# Create a run_from/ subdirectory for tests which want to run
|
|
|
|
# from a separate directory outside the Stow directory or
|
|
|
|
# target directory.
|
|
|
|
for my $dir ("target", "stow", "run_from") {
|
2024-04-01 19:36:51 -04:00
|
|
|
my $path = "$test_dir/$dir";
|
2019-06-28 04:56:46 -04:00
|
|
|
-d $path and remove_tree($path);
|
|
|
|
make_path($path);
|
2011-11-24 11:28:09 -05:00
|
|
|
}
|
2011-11-23 18:45:48 -05:00
|
|
|
|
|
|
|
# Don't let user's ~/.stow-global-ignore affect test results
|
2024-04-01 19:36:51 -04:00
|
|
|
$ENV{HOME} = $abs_test_dir;
|
|
|
|
return $abs_test_dir;
|
2011-11-24 11:28:09 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
sub new_Stow {
|
|
|
|
my %opts = @_;
|
2021-04-04 18:31:35 -04:00
|
|
|
# These default paths assume that execution will be triggered from
|
|
|
|
# within the target directory.
|
2011-11-24 11:28:09 -05:00
|
|
|
$opts{dir} ||= '../stow';
|
|
|
|
$opts{target} ||= '.';
|
|
|
|
$opts{test_mode} = 1;
|
2024-04-01 19:36:51 -04:00
|
|
|
my $stow = eval { new Stow(%opts) };
|
|
|
|
if ($@) {
|
|
|
|
confess "Error while trying to instantiate new Stow(%opts): $@";
|
|
|
|
}
|
|
|
|
return $stow;
|
2011-11-24 11:28:09 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
sub new_compat_Stow {
|
|
|
|
my %opts = @_;
|
|
|
|
$opts{compat} = 1;
|
|
|
|
return new_Stow(%opts);
|
2011-11-17 13:46:13 -05:00
|
|
|
}
|
|
|
|
|
2011-11-16 09:04:03 -05:00
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : make_link()
|
|
|
|
# Purpose : safely create a link
|
2024-04-03 19:36:37 -04:00
|
|
|
# Parameters: $link_src => path to the link
|
|
|
|
# : $link_dest => where the new link should point
|
|
|
|
# : $invalid => true iff $link_dest refers to non-existent file
|
2011-11-16 09:04:03 -05:00
|
|
|
# Returns : n/a
|
|
|
|
# Throws : fatal error if the link can not be safely created
|
|
|
|
# Comments : checks for existing nodes
|
|
|
|
#============================================================================
|
|
|
|
sub make_link {
|
2024-04-03 19:36:37 -04:00
|
|
|
my ($link_src, $link_dest, $invalid) = @_;
|
2011-11-16 09:04:03 -05:00
|
|
|
|
2024-04-03 19:36:37 -04:00
|
|
|
if (-l $link_src) {
|
|
|
|
my $old_source = readlink join('/', parent($link_src), $link_dest)
|
|
|
|
or croak "$link_src is already a link but could not read link $link_src/$link_dest";
|
|
|
|
if ($old_source ne $link_dest) {
|
|
|
|
croak "$link_src already exists but points elsewhere\n";
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
}
|
2024-04-03 19:36:37 -04:00
|
|
|
croak "$link_src already exists and is not a link\n" if -e $link_src;
|
|
|
|
my $abs_target = File::Spec->rel2abs($link_src);
|
|
|
|
my $link_src_container = dirname($abs_target);
|
|
|
|
my $abs_source = File::Spec->rel2abs($link_dest, $link_src_container);
|
|
|
|
#warn "t $link_src c $link_src_container as $abs_source";
|
2012-02-18 15:19:05 -05:00
|
|
|
if (-e $abs_source) {
|
|
|
|
croak "Won't make invalid link pointing to existing $abs_target"
|
|
|
|
if $invalid;
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
else {
|
2012-02-18 15:19:05 -05:00
|
|
|
croak "Won't make link pointing to non-existent $abs_target"
|
|
|
|
unless $invalid;
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
2024-04-03 19:36:37 -04:00
|
|
|
symlink $link_dest, $link_src
|
|
|
|
or croak "could not create link $link_src => $link_dest ($!)\n";
|
2012-02-18 15:19:05 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : make_invalid_link()
|
|
|
|
# Purpose : safely create an invalid link
|
|
|
|
# Parameters: $target => path to the link
|
|
|
|
# : $source => the non-existent source where the new link should point
|
|
|
|
# Returns : n/a
|
|
|
|
# Throws : fatal error if the link can not be safely created
|
|
|
|
# Comments : checks for existing nodes
|
|
|
|
#============================================================================
|
|
|
|
sub make_invalid_link {
|
|
|
|
my ($target, $source, $allow_invalid) = @_;
|
|
|
|
make_link($target, $source, 1);
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : create_file()
|
|
|
|
# Purpose : create an empty file
|
|
|
|
# Parameters: $path => proposed path to the file
|
2011-11-23 18:45:48 -05:00
|
|
|
# : $contents => (optional) contents to write to file
|
2011-11-16 09:04:03 -05:00
|
|
|
# Returns : n/a
|
|
|
|
# Throws : fatal error if the file could not be created
|
|
|
|
# Comments : detects clash with an existing non-file
|
|
|
|
#============================================================================
|
|
|
|
sub make_file {
|
2012-01-09 16:25:35 -05:00
|
|
|
my ($path, $contents) = @_;
|
2011-11-16 09:04:03 -05:00
|
|
|
|
2011-11-23 18:45:48 -05:00
|
|
|
if (-e $path and ! -f $path) {
|
2024-04-01 18:31:36 -04:00
|
|
|
croak "a non-file already exists at $path\n";
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
2011-11-23 18:45:48 -05:00
|
|
|
|
|
|
|
open my $FILE ,'>', $path
|
2024-04-01 18:31:36 -04:00
|
|
|
or croak "could not create file: $path ($!)\n";
|
2011-11-23 18:45:48 -05:00
|
|
|
print $FILE $contents if defined $contents;
|
|
|
|
close $FILE;
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : remove_link()
|
|
|
|
# Purpose : remove an esiting symbolic link
|
|
|
|
# Parameters: $path => path to the symbolic link
|
|
|
|
# Returns : n/a
|
|
|
|
# Throws : fatal error if the operation fails or if passed the path to a
|
|
|
|
# : non-link
|
|
|
|
# Comments : none
|
|
|
|
#============================================================================
|
|
|
|
sub remove_link {
|
|
|
|
my ($path) = @_;
|
|
|
|
if (not -l $path) {
|
2024-04-01 18:31:36 -04:00
|
|
|
croak qq(remove_link() called with a non-link: $path);
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
2024-04-01 18:31:36 -04:00
|
|
|
unlink $path or croak "could not remove link: $path ($!)\n";
|
2011-11-16 09:04:03 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : remove_file()
|
|
|
|
# Purpose : remove an existing empty file
|
|
|
|
# Parameters: $path => the path to the empty file
|
|
|
|
# Returns : n/a
|
|
|
|
# Throws : fatal error if given file is non-empty or the operation fails
|
|
|
|
# Comments : none
|
|
|
|
#============================================================================
|
|
|
|
sub remove_file {
|
|
|
|
my ($path) = @_;
|
|
|
|
if (-z $path) {
|
2024-04-01 18:31:36 -04:00
|
|
|
croak "file at $path is non-empty\n";
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
2024-04-01 18:31:36 -04:00
|
|
|
unlink $path or croak "could not remove empty file: $path ($!)\n";
|
2011-11-16 09:04:03 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : remove_dir()
|
|
|
|
# Purpose : safely remove a tree of test files
|
|
|
|
# Parameters: $dir => path to the top of the tree
|
|
|
|
# Returns : n/a
|
|
|
|
# Throws : fatal error if the tree contains a non-link or non-empty file
|
|
|
|
# Comments : recursively removes directories containing softlinks empty files
|
|
|
|
#============================================================================
|
|
|
|
sub remove_dir {
|
|
|
|
my ($dir) = @_;
|
|
|
|
|
|
|
|
if (not -d $dir) {
|
2024-04-01 18:31:36 -04:00
|
|
|
croak "$dir is not a directory";
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
|
2024-04-01 18:31:36 -04:00
|
|
|
opendir my $DIR, $dir or croak "cannot read directory: $dir ($!)\n";
|
2011-11-16 09:04:03 -05:00
|
|
|
my @listing = readdir $DIR;
|
|
|
|
closedir $DIR;
|
|
|
|
|
|
|
|
NODE:
|
|
|
|
for my $node (@listing) {
|
|
|
|
next NODE if $node eq '.';
|
|
|
|
next NODE if $node eq '..';
|
|
|
|
|
|
|
|
my $path = "$dir/$node";
|
2016-02-27 12:19:57 -05:00
|
|
|
if (-l $path or (-f $path and -z $path) or $node eq $Stow::LOCAL_IGNORE_FILE) {
|
2024-04-01 18:31:36 -04:00
|
|
|
unlink $path or croak "cannot unlink $path ($!)\n";
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
elsif (-d "$path") {
|
|
|
|
remove_dir($path);
|
|
|
|
}
|
|
|
|
else {
|
2024-04-01 18:31:36 -04:00
|
|
|
croak "$path is not a link, directory, or empty file\n";
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
}
|
2024-04-01 18:31:36 -04:00
|
|
|
rmdir $dir or croak "cannot rmdir $dir ($!)\n";
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-11-24 11:28:09 -05:00
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : cd()
|
|
|
|
# Purpose : wrapper around chdir
|
|
|
|
# Parameters: $dir => path to chdir to
|
|
|
|
# Returns : n/a
|
|
|
|
# Throws : fatal error if the chdir fails
|
|
|
|
# Comments : none
|
|
|
|
#============================================================================
|
|
|
|
sub cd {
|
|
|
|
my ($dir) = @_;
|
2024-04-01 18:31:36 -04:00
|
|
|
chdir $dir or croak "Failed to chdir($dir): $!\n";
|
2011-11-24 11:28:09 -05:00
|
|
|
}
|
|
|
|
|
2012-01-09 16:25:35 -05:00
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : cat_file()
|
|
|
|
# Purpose : return file contents
|
|
|
|
# Parameters: $file => file to read
|
|
|
|
# Returns : n/a
|
|
|
|
# Throws : fatal error if the open fails
|
|
|
|
# Comments : none
|
|
|
|
#============================================================================
|
|
|
|
sub cat_file {
|
|
|
|
my ($file) = @_;
|
2024-04-01 18:31:36 -04:00
|
|
|
open F, $file or croak "Failed to open($file): $!\n";
|
2012-01-09 16:25:35 -05:00
|
|
|
my $contents = join '', <F>;
|
|
|
|
close(F);
|
|
|
|
return $contents;
|
|
|
|
}
|
|
|
|
|
2012-02-18 15:13:32 -05:00
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : is_link()
|
|
|
|
# Purpose : assert path is a symlink
|
|
|
|
# Parameters: $path => path to check
|
|
|
|
# : $dest => target symlink should point to
|
|
|
|
#============================================================================
|
|
|
|
sub is_link {
|
|
|
|
my ($path, $dest) = @_;
|
|
|
|
ok(-l $path => "$path should be symlink");
|
|
|
|
is(readlink $path, $dest => "$path symlinks to $dest");
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : is_dir_not_symlink()
|
|
|
|
# Purpose : assert path is a directory not a symlink
|
|
|
|
# Parameters: $path => path to check
|
|
|
|
#============================================================================
|
|
|
|
sub is_dir_not_symlink {
|
|
|
|
my ($path) = @_;
|
|
|
|
ok(! -l $path => "$path should not be symlink");
|
|
|
|
ok(-d _ => "$path should be a directory");
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : is_nonexistent_path()
|
|
|
|
# Purpose : assert path does not exist
|
|
|
|
# Parameters: $path => path to check
|
|
|
|
#============================================================================
|
|
|
|
sub is_nonexistent_path {
|
|
|
|
my ($path) = @_;
|
|
|
|
ok(! -l $path => "$path should not be symlink");
|
|
|
|
ok(! -e _ => "$path should not exist");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-11-16 09:04:03 -05:00
|
|
|
1;
|
2011-11-24 11:28:09 -05:00
|
|
|
|
|
|
|
# Local variables:
|
|
|
|
# mode: perl
|
|
|
|
# end:
|
|
|
|
# vim: ft=perl
|