3 # This file is part of GNU Stow.
5 # GNU Stow is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # GNU Stow is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see https://www.gnu.org/licenses/.
19 # Test unstowing packages
25 use File
::Spec
qw(make_path);
27 use Test
::More tests
=> 35;
29 use English
qw(-no_match_vars);
32 use Stow
::Util
qw(canon_path);
36 init_test_dirs
($TEST_DIR);
38 our $COMPAT_TEST_DIR = "${TEST_DIR}-compat";
39 our $COMPAT_ABS_TEST_DIR = init_test_dirs
($COMPAT_TEST_DIR);
42 make_path
('stow2'); # make our alternate stow dir a subdir of target
43 make_file
('stow2/.stow');
46 sub create_unowned_files
{
47 # Make things harder for Stow to figure out, by adding
48 # a bunch of alien files unrelated to Stow.
49 my @UNOWNED_DIRS = ('unowned-dir', '.unowned-dir', 'dot-unowned-dir');
50 for my $dir ('.', @UNOWNED_DIRS) {
51 for my $subdir ('.', @UNOWNED_DIRS) {
52 make_path
("$dir/$subdir");
53 make_file
("$dir/$subdir/unowned");
54 make_file
("$dir/$subdir/.unowned");
55 make_file
("$dir/$subdir/dot-unowned");
60 # Run a subtest twice, with compat off then on, in parallel test trees.
62 # Params: $name[, $setup], $test_code
64 # $setup is an optional ref to an options hash to pass into the new
65 # Stow() constructor, or a ref to a sub which performs setup before
66 # the constructor gets called and then returns that options hash.
69 my $setup = @_ == 2 ?
shift : {};
72 $ENV{HOME
} = $ABS_TEST_DIR;
74 cd
("$TEST_DIR/target");
75 create_unowned_files
();
76 # cd first to allow setup to cd somewhere else.
77 my $opts = ref($setup) eq 'HASH' ?
$setup : $setup->($TEST_DIR);
79 make_path
($opts->{dir
}) if $opts->{dir
};
80 my $stow = new_Stow
(%$opts);
81 $code->($stow, $TEST_DIR);
84 $ENV{HOME
} = $COMPAT_ABS_TEST_DIR;
86 cd
("$COMPAT_TEST_DIR/target");
87 create_unowned_files
();
88 # cd first to allow setup to cd somewhere else.
89 $opts = ref $setup eq 'HASH' ?
$setup : $setup->($COMPAT_TEST_DIR);
90 subtest
("$name (compat mode)", sub {
91 make_path
($opts->{dir
}) if $opts->{dir
};
92 my $stow = new_compat_Stow
(%$opts);
93 $code->($stow, $COMPAT_TEST_DIR);
98 my ($stow, $count) = @_;
99 plan tests
=> $stow->{compat
} ?
$count + 2 : $count;
102 subtests
("unstow a simple tree minimally", sub {
106 make_path
('../stow/pkg1/bin1');
107 make_file
('../stow/pkg1/bin1/file1');
108 make_link
('bin1', '../stow/pkg1/bin1');
110 $stow->plan_unstow('pkg1');
111 $stow->process_tasks();
112 is
($stow->get_conflict_count, 0, 'conflict count');
113 ok
(-f
'../stow/pkg1/bin1/file1');
114 ok
(! -e
'bin1' => 'unstow a simple tree');
117 subtests
("unstow a simple tree from an existing directory", sub {
122 make_path
('../stow/pkg2/lib2');
123 make_file
('../stow/pkg2/lib2/file2');
124 make_link
('lib2/file2', '../../stow/pkg2/lib2/file2');
125 $stow->plan_unstow('pkg2');
126 $stow->process_tasks();
127 is
($stow->get_conflict_count, 0, 'conflict count');
128 ok
(-f
'../stow/pkg2/lib2/file2');
130 => 'unstow simple tree from a pre-existing directory'
134 subtests
("fold tree after unstowing", sub {
140 make_path
('../stow/pkg3a/bin3');
141 make_file
('../stow/pkg3a/bin3/file3a');
142 make_link
('bin3/file3a' => '../../stow/pkg3a/bin3/file3a'); # emulate stow
144 make_path
('../stow/pkg3b/bin3');
145 make_file
('../stow/pkg3b/bin3/file3b');
146 make_link
('bin3/file3b' => '../../stow/pkg3b/bin3/file3b'); # emulate stow
147 $stow->plan_unstow('pkg3b');
148 $stow->process_tasks();
149 is
($stow->get_conflict_count, 0, 'conflict count');
151 is
(readlink('bin3'), '../stow/pkg3a/bin3'
152 => 'fold tree after unstowing'
156 subtests
("existing link is owned by stow but is invalid so it gets removed anyway", sub {
161 make_path
('../stow/pkg4/bin4');
162 make_file
('../stow/pkg4/bin4/file4');
163 make_invalid_link
('bin4/file4', '../../stow/pkg4/bin4/does-not-exist');
165 $stow->plan_unstow('pkg4');
166 $stow->process_tasks();
167 is
($stow->get_conflict_count, 0, 'conflict count');
169 => q
(remove invalid
link owned by stow
)
173 subtests
("Existing invalid link is not owned by stow", sub {
177 make_path
('../stow/pkg5/bin5');
178 make_invalid_link
('bin5', '../not-stow');
180 $stow->plan_unstow('pkg5');
181 is
($stow->get_conflict_count, 0, 'conflict count');
182 ok
(-l
'bin5', 'invalid link not removed');
183 is
(readlink('bin5'), '../not-stow' => "invalid link not changed");
186 subtests
("Target already exists, is owned by stow, but points to a different package", sub {
191 make_path
('../stow/pkg6a/bin6');
192 make_file
('../stow/pkg6a/bin6/file6');
193 make_link
('bin6/file6', '../../stow/pkg6a/bin6/file6');
195 make_path
('../stow/pkg6b/bin6');
196 make_file
('../stow/pkg6b/bin6/file6');
198 $stow->plan_unstow('pkg6b');
199 is
($stow->get_conflict_count, 0, 'conflict count');
202 readlink('bin6/file6'),
203 '../../stow/pkg6a/bin6/file6'
204 => q
(ignore existing
link that points to a different
package)
208 subtests
("Don't unlink anything under the stow directory",
211 return { dir
=> 'stow' };
212 # target dir defaults to parent of stow, which is target directory
218 # Emulate stowing into ourself (bizarre corner case or accident):
219 make_path
('stow/pkg7a/stow/pkg7b');
220 make_file
('stow/pkg7a/stow/pkg7b/file7b');
221 # Make a package be a link to a package of the same name inside another package.
222 make_link
('stow/pkg7b', '../stow/pkg7a/stow/pkg7b');
225 sub { $stow->plan_unstow('pkg7b'); },
226 $stow->{compat
} ?
qr/WARNING: skipping target which was current stow directory stow/ : qr//
227 => "warn when unstowing from ourself"
229 is
($stow->get_tasks, 0, 'no tasks to process when unstowing pkg7b');
230 is
($stow->get_conflict_count, 0, 'conflict count');
233 readlink('stow/pkg7b'),
234 '../stow/pkg7a/stow/pkg7b'
235 => q
(don
't unlink any nodes under the stow directory)
239 subtests("Don't
unlink any nodes under another stow directory
",
242 return { dir => 'stow' };
249 # emulate stowing into ourself (bizarre corner case or accident)
250 make_path('stow/pkg8a/stow2/pkg8b');
251 make_file('stow/pkg8a/stow2/pkg8b/file8b');
252 make_link('stow2/pkg8b', '../stow/pkg8a/stow2/pkg8b');
255 sub { $stow->plan_unstow('pkg8a'); },
256 qr/WARNING: skipping marked Stow directory stow2/
257 => "warn when skipping unstowing
"
259 is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg8a');
260 is($stow->get_conflict_count, 0, 'conflict count');
261 ok(-l 'stow2/pkg8b');
263 readlink('stow2/pkg8b'),
264 '../stow/pkg8a/stow2/pkg8b'
265 => q(don't unlink any nodes under another stow directory)
269 # This will be used by subsequent tests
270 sub check_protected_dirs_skipped {
272 for my $dir (qw{stow stow2}) {
274 qr/WARNING: skipping marked Stow directory $dir/
275 => "warn when skipping marked directory
$dir");
279 subtests("overriding already stowed documentation
",
280 {override => ['man9', 'info9']},
283 plan_tests($stow, 2);
285 make_file('stow/.stow');
287 make_path('../stow/pkg9a/man9/man1');
288 make_file('../stow/pkg9a/man9/man1/file9.1');
289 make_path('man9/man1');
290 make_link('man9/man1/file9.1' => '../../../stow/pkg9a/man9/man1/file9.1'); # emulate stow
292 make_path('../stow/pkg9b/man9/man1');
293 make_file('../stow/pkg9b/man9/man1/file9.1');
294 my $stderr = stderr_from { $stow->plan_unstow('pkg9b') };
295 check_protected_dirs_skipped($stderr) if $stow->{compat};
296 $stow->process_tasks();
297 is($stow->get_conflict_count, 0, 'conflict count');
298 ok(!-l 'man9/man1/file9.1'
299 => 'overriding existing documentation files'
303 subtests("deferring to already stowed documentation
",
304 {defer => ['man10', 'info10']},
307 plan_tests($stow, 3);
310 make_path('../stow/pkg10a/man10/man1');
311 make_file('../stow/pkg10a/man10/man1/file10a.1');
312 make_path('man10/man1');
313 make_link('man10/man1/file10a.1' => '../../../stow/pkg10a/man10/man1/file10a.1');
315 # need this to block folding
316 make_path('../stow/pkg10b/man10/man1');
317 make_file('../stow/pkg10b/man10/man1/file10b.1');
318 make_link('man10/man1/file10b.1' => '../../../stow/pkg10b/man10/man1/file10b.1');
320 make_path('../stow/pkg10c/man10/man1');
321 make_file('../stow/pkg10c/man10/man1/file10a.1');
322 my $stderr = stderr_from { $stow->plan_unstow('pkg10c') };
323 check_protected_dirs_skipped($stderr) if $stow->{compat};
324 is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg10c');
325 is($stow->get_conflict_count, 0, 'conflict count');
327 readlink('man10/man1/file10a.1'),
328 '../../../stow/pkg10a/man10/man1/file10a.1'
329 => 'defer to existing documentation files'
333 subtests("Ignore temp files
",
334 {ignore => ['~', '\.#.*']},
337 plan_tests($stow, 2);
340 make_path('../stow/pkg12/man12/man1');
341 make_file('../stow/pkg12/man12/man1/file12.1');
342 make_file('../stow/pkg12/man12/man1/file12.1~');
343 make_file('../stow/pkg12/man12/man1/.#file12.1');
344 make_path('man12/man1');
345 make_link('man12/man1/file12.1' => '../../../stow/pkg12/man12/man1/file12.1');
347 my $stderr = stderr_from { $stow->plan_unstow('pkg12') };
348 check_protected_dirs_skipped($stderr) if $stow->{compat};
349 $stow->process_tasks();
350 is($stow->get_conflict_count, 0, 'conflict count');
351 ok(! -e 'man12/man1/file12.1' => 'man12/man1/file12.1 was unstowed');
354 subtests("Unstow an already unstowed
package", sub {
356 plan_tests($stow, 2);
358 my $stderr = stderr_from { $stow->plan_unstow('pkg12') };
359 check_protected_dirs_skipped($stderr) if $stow->{compat};
360 is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg12');
361 is($stow->get_conflict_count, 0, 'conflict count');
364 subtests("Unstow a never stowed
package", sub {
368 eval { remove_dir($stow->{target}); };
369 mkdir($stow->{target});
371 $stow->plan_unstow('pkg12');
372 is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg12 which was never stowed');
373 is($stow->get_conflict_count, 0, 'conflict count');
376 subtests("Unstowing
when target contains real files shouldn
't be an issue", sub {
380 # Test both a file which do / don't overlap with the
package
381 make_path
('man12/man1');
382 make_file
('man12/man1/alien');
383 make_file
('man12/man1/file12.1');
385 $stow->plan_unstow('pkg12');
386 is
($stow->get_tasks, 0, 'no tasks to process when unstowing pkg12 for third time');
387 is
($stow->get_conflict_count, 0, 'conflict count');
388 ok
(-f
'man12/man1/alien', 'alien untouched');
389 ok
(-f
'man12/man1/file12.1', 'file overlapping with pkg untouched');
392 subtests
("unstow a simple tree minimally when cwd isn't target",
394 my $test_dir = shift;
397 dir
=> "$test_dir/stow",
398 target
=> "$test_dir/target"
402 my ($stow, $test_dir) = @_;
405 make_path
("$test_dir/stow/pkg13/bin13");
406 make_file
("$test_dir/stow/pkg13/bin13/file13");
407 make_link
("$test_dir/target/bin13", '../stow/pkg13/bin13');
409 $stow->plan_unstow('pkg13');
410 $stow->process_tasks();
411 is
($stow->get_conflict_count, 0, 'conflict count');
412 ok
(-f
"$test_dir/stow/pkg13/bin13/file13", 'package file untouched');
413 ok
(! -e
"$test_dir/target/bin13" => 'bin13/ unstowed');
416 subtests
("unstow a simple tree minimally with absolute stow dir when cwd isn't target",
418 my $test_dir = shift;
421 dir
=> canon_path
("$test_dir/stow"),
422 target
=> "$test_dir/target"
427 my ($stow, $test_dir) = @_;
429 make_path
("$test_dir/stow/pkg14/bin14");
430 make_file
("$test_dir/stow/pkg14/bin14/file14");
431 make_link
("$test_dir/target/bin14", '../stow/pkg14/bin14');
433 $stow->plan_unstow('pkg14');
434 $stow->process_tasks();
435 is
($stow->get_conflict_count, 0, 'conflict count');
436 ok
(-f
"$test_dir/stow/pkg14/bin14/file14");
437 ok
(! -e
"$test_dir/target/bin14"
438 => 'unstow a simple tree with absolute stow dir'
442 subtests
("unstow a simple tree minimally with absolute stow AND target dirs when cwd isn't target",
444 my $test_dir = shift;
447 dir
=> canon_path
("$test_dir/stow"),
448 target
=> canon_path
("$test_dir/target")
452 my ($stow, $test_dir) = @_;
455 make_path
("$test_dir/stow/pkg15/bin15");
456 make_file
("$test_dir/stow/pkg15/bin15/file15");
457 make_link
("$test_dir/target/bin15", '../stow/pkg15/bin15');
459 $stow->plan_unstow('pkg15');
460 $stow->process_tasks();
461 is
($stow->get_conflict_count, 0, 'conflict count');
462 ok
(-f
"$test_dir/stow/pkg15/bin15/file15");
463 ok
(! -e
"$test_dir/target/bin15"
464 => 'unstow a simple tree with absolute stow and target dirs'
468 sub create_and_stow_pkg
{
471 my $stow_pkg = "../stow/$id-$pkg";
472 make_path
($stow_pkg);
473 make_file
("$stow_pkg/$id-file-$pkg");
475 # create a shallow hierarchy specific to this package and stow
477 make_path
("$stow_pkg/$id-$pkg-only-folded");
478 make_file
("$stow_pkg/$id-$pkg-only-folded/file-$pkg");
479 make_link
("$id-$pkg-only-folded", "$stow_pkg/$id-$pkg-only-folded");
481 # create a deeper hierarchy specific to this package and stow
483 make_path
("$stow_pkg/$id-$pkg-only-folded2/subdir");
484 make_file
("$stow_pkg/$id-$pkg-only-folded2/subdir/file-$pkg");
485 make_link
("$id-$pkg-only-folded2",
486 "$stow_pkg/$id-$pkg-only-folded2");
488 # create a shallow hierarchy specific to this package and stow
490 make_path
("$stow_pkg/$id-$pkg-only-unfolded");
491 make_file
("$stow_pkg/$id-$pkg-only-unfolded/file-$pkg");
492 make_path
("$id-$pkg-only-unfolded");
493 make_link
("$id-$pkg-only-unfolded/file-$pkg",
494 "../$stow_pkg/$id-$pkg-only-unfolded/file-$pkg");
496 # create a deeper hierarchy specific to this package and stow
498 make_path
("$stow_pkg/$id-$pkg-only-unfolded2/subdir");
499 make_file
("$stow_pkg/$id-$pkg-only-unfolded2/subdir/file-$pkg");
500 make_path
("$id-$pkg-only-unfolded2/subdir");
501 make_link
("$id-$pkg-only-unfolded2/subdir/file-$pkg",
502 "../../$stow_pkg/$id-$pkg-only-unfolded2/subdir/file-$pkg");
504 # create a shallow shared hierarchy which this package uses, and stow
505 # its contents without folding
506 make_path
("$stow_pkg/$id-shared");
507 make_file
("$stow_pkg/$id-shared/file-$pkg");
508 make_path
("$id-shared");
509 make_link
("$id-shared/file-$pkg",
510 "../$stow_pkg/$id-shared/file-$pkg");
512 # create a deeper shared hierarchy which this package uses, and stow
513 # its contents without folding
514 make_path
("$stow_pkg/$id-shared2/subdir");
515 make_file
("$stow_pkg/$id-shared2/file-$pkg");
516 make_file
("$stow_pkg/$id-shared2/subdir/file-$pkg");
517 make_path
("$id-shared2/subdir");
518 make_link
("$id-shared2/file-$pkg",
519 "../$stow_pkg/$id-shared2/file-$pkg");
520 make_link
("$id-shared2/subdir/file-$pkg",
521 "../../$stow_pkg/$id-shared2/subdir/file-$pkg");
524 subtest
("unstow a tree with no-folding enabled - no refolding should take place", sub {
525 cd
("$TEST_DIR/target");
528 foreach my $pkg (qw{a b
}) {
529 create_and_stow_pkg
('no-folding', $pkg);
532 my $stow = new_Stow
('no-folding' => 1);
533 $stow->plan_unstow('no-folding-b');
534 is_deeply
([ $stow->get_conflicts ], [] => 'no conflicts with --no-folding');
536 $stow->process_tasks();
538 is_nonexistent_path
('no-folding-b-only-folded');
539 is_nonexistent_path
('no-folding-b-only-folded2');
540 is_nonexistent_path
('no-folding-b-only-unfolded/file-b');
541 is_nonexistent_path
('no-folding-b-only-unfolded2/subdir/file-b');
542 is_dir_not_symlink
('no-folding-shared');
543 is_dir_not_symlink
('no-folding-shared2');
544 is_dir_not_symlink
('no-folding-shared2/subdir');
547 # subtests("Test cleaning up subdirs with --paranoid option", sub {