summaryrefslogtreecommitdiff
path: root/t/testutil.pm
blob: 32fc2f1da8c3ac3c20ed32846c238805d0b119ad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
#!/usr/bin/perl

#
# Utilities shared by test scripts
#

package testutil;

use strict;
use warnings;

use Carp qw(croak);
use File::Basename;
use File::Path qw(remove_tree);
use File::Spec;
use IO::Scalar;
use Test::More;

use Stow;
use Stow::Util qw(parent canon_path);

use base qw(Exporter);
our @EXPORT = qw(
    $OUT_DIR
    $stderr
    init_test_dirs
    cd
    new_Stow new_compat_Stow
    make_dir make_link make_invalid_link make_file
    remove_dir remove_link
    cat_file
    is_link is_dir_not_symlink is_nonexistent_path
    capture_stderr uncapture_stderr
);

our $OUT_DIR = 'tmp-testing-trees';

our $stderr;
my $tied_err;

sub capture_stderr {
    undef $stderr;
    $tied_err = tie *STDERR, 'IO::Scalar', \$stderr;
}

sub uncapture_stderr {
    undef $tied_err;
    untie *STDERR;
}

sub init_test_dirs {
    for my $dir ("$OUT_DIR/target", "$OUT_DIR/stow") {
        -d $dir and remove_tree($dir);
        make_dir($dir);
    }

    # Don't let user's ~/.stow-global-ignore affect test results
    $ENV{HOME} = '/tmp/fake/home';
}

sub new_Stow {
    my %opts = @_;
    $opts{dir}    ||= '../stow';
    $opts{target} ||= '.';
    $opts{test_mode} = 1;
    return new Stow(%opts);
}

sub new_compat_Stow {
    my %opts = @_;
    $opts{compat} = 1;
    return new_Stow(%opts);
}

#===== SUBROUTINE ===========================================================
# Name      : make_link()
# Purpose   : safely create a link
# Parameters: $target => path to the link
#           : $source => where the new link should point
#           : $invalid => true iff $source refers to non-existent file
# Returns   : n/a
# Throws    : fatal error if the link can not be safely created
# Comments  : checks for existing nodes
#============================================================================
sub make_link {
    my ($target, $source, $invalid) = @_;

    if (-l $target) {
        my $old_source = readlink join('/', parent($target), $source) 
            or die "$target is already a link but could not read link $target/$source";
        if ($old_source ne $source) {
            die "$target already exists but points elsewhere\n";
        }
    }
    die "$target already exists and is not a link\n" if -e $target;
    my $abs_target = File::Spec->rel2abs($target);
    my $target_container = dirname($abs_target);
    my $abs_source = File::Spec->rel2abs($source, $target_container);
    #warn "t $target c $target_container as $abs_source";
    if (-e $abs_source) {
        croak "Won't make invalid link pointing to existing $abs_target"
            if $invalid;
    }
    else {
        croak "Won't make link pointing to non-existent $abs_target"
            unless $invalid;
    }
    symlink $source, $target
        or die "could not create link $target => $source ($!)\n";
}

#===== 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);
}

#===== SUBROUTINE ===========================================================
# Name      : make_dir()
# Purpose   : create a directory and any requisite parents
# Parameters: $dir => path to the new directory
# Returns   : n/a
# Throws    : fatal error if the directory or any of its parents cannot be
#           : created
# Comments  : none
#============================================================================
sub make_dir {
    my ($dir) = @_;

    my @parents = ();
    for my $part (split '/', $dir) {
        my $path = join '/', @parents, $part;
        if (not -d $path and not mkdir $path) {
            die "could not create directory: $path ($!)\n";
        }
        push @parents, $part;
    }
    return;
}

#===== SUBROUTINE ===========================================================
# Name      : create_file()
# Purpose   : create an empty file
# Parameters: $path => proposed path to the file
#           : $contents => (optional) contents to write to file
# Returns   : n/a
# Throws    : fatal error if the file could not be created
# Comments  : detects clash with an existing non-file
#============================================================================
sub make_file {
    my ($path, $contents) = @_;

    if (-e $path and ! -f $path) {
        die "a non-file already exists at $path\n";
    }

    open my $FILE ,'>', $path
        or die "could not create file: $path ($!)\n";
    print $FILE $contents if defined $contents;
    close $FILE;
}

#===== 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) {
        die qq(remove_link() called with a non-link: $path);
    }
    unlink $path or die "could not remove link: $path ($!)\n";
    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) {
        die "file at $path is non-empty\n";
    }
    unlink $path or die "could not remove empty file: $path ($!)\n";
    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) {
        die "$dir is not a directory";
    }

    opendir my $DIR, $dir or die "cannot read directory: $dir ($!)\n";
    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";
        if (-l $path or -z $path or $node eq $Stow::LOCAL_IGNORE_FILE) {
            unlink $path or die "cannot unlink $path ($!)\n";
        }
        elsif (-d "$path") {
            remove_dir($path);
        }
        else {
            die "$path is not a link, directory, or empty file\n";
        }
    }
    rmdir $dir or die "cannot rmdir $dir ($!)\n";

    return;
}

#===== 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) = @_;
    chdir $dir or die "Failed to chdir($dir): $!\n";
}

#===== 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) = @_;
    open F, $file or die "Failed to open($file): $!\n";
    my $contents = join '', <F>;
    close(F);
    return $contents;
}

#===== 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");
}


1;

# Local variables:
# mode: perl
# cperl-indent-level: 4
# end:
# vim: ft=perl