stow.t 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. #!/usr/bin/perl
  2. #
  3. # This file is part of GNU Stow.
  4. #
  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.
  9. #
  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.
  14. #
  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/.
  17. #
  18. # Test stowing packages.
  19. #
  20. use strict;
  21. use warnings;
  22. use Test::More tests => 118;
  23. use Test::Output;
  24. use English qw(-no_match_vars);
  25. use Stow::Util qw(canon_path set_debug_level);
  26. use testutil;
  27. init_test_dirs();
  28. cd("$TEST_DIR/target");
  29. my $stow;
  30. my %conflicts;
  31. # Note that each of the following tests use a distinct set of files
  32. #
  33. # stow a simple tree minimally
  34. #
  35. $stow = new_Stow(dir => '../stow');
  36. make_path('../stow/pkg1/bin1');
  37. make_file('../stow/pkg1/bin1/file1');
  38. $stow->plan_stow('pkg1');
  39. $stow->process_tasks();
  40. is_deeply([ $stow->get_conflicts ], [], 'no conflicts with minimal stow');
  41. is(
  42. readlink('bin1'),
  43. '../stow/pkg1/bin1',
  44. => 'minimal stow of a simple tree'
  45. );
  46. #
  47. # stow a simple tree into an existing directory
  48. #
  49. $stow = new_Stow();
  50. make_path('../stow/pkg2/lib2');
  51. make_file('../stow/pkg2/lib2/file2');
  52. make_path('lib2');
  53. $stow->plan_stow('pkg2');
  54. $stow->process_tasks();
  55. is(
  56. readlink('lib2/file2'),
  57. '../../stow/pkg2/lib2/file2',
  58. => 'stow simple tree to existing directory'
  59. );
  60. #
  61. # unfold existing tree
  62. #
  63. $stow = new_Stow();
  64. make_path('../stow/pkg3a/bin3');
  65. make_file('../stow/pkg3a/bin3/file3a');
  66. make_link('bin3' => '../stow/pkg3a/bin3'); # emulate stow
  67. make_path('../stow/pkg3b/bin3');
  68. make_file('../stow/pkg3b/bin3/file3b');
  69. $stow->plan_stow('pkg3b');
  70. $stow->process_tasks();
  71. ok(
  72. -d 'bin3' &&
  73. readlink('bin3/file3a') eq '../../stow/pkg3a/bin3/file3a' &&
  74. readlink('bin3/file3b') eq '../../stow/pkg3b/bin3/file3b'
  75. => 'target already has 1 stowed package'
  76. );
  77. #
  78. # Link to a new dir 'bin4' conflicts with existing non-dir so can't
  79. # unfold
  80. #
  81. $stow = new_Stow();
  82. make_file('bin4'); # this is a file but named like a directory
  83. make_path('../stow/pkg4/bin4');
  84. make_file('../stow/pkg4/bin4/file4');
  85. $stow->plan_stow('pkg4');
  86. %conflicts = $stow->get_conflicts();
  87. ok(
  88. $stow->get_conflict_count == 1 &&
  89. $conflicts{stow}{pkg4}[0] =~
  90. qr/existing target is neither a link nor a directory/
  91. => 'link to new dir bin4 conflicts with existing non-directory'
  92. );
  93. #
  94. # Link to a new dir 'bin4a' conflicts with existing non-dir so can't
  95. # unfold even with --adopt
  96. #
  97. #$stow = new_Stow(adopt => 1);
  98. $stow = new_Stow();
  99. make_file('bin4a'); # this is a file but named like a directory
  100. make_path('../stow/pkg4a/bin4a');
  101. make_file('../stow/pkg4a/bin4a/file4a');
  102. $stow->plan_stow('pkg4a');
  103. %conflicts = $stow->get_conflicts();
  104. ok(
  105. $stow->get_conflict_count == 1 &&
  106. $conflicts{stow}{pkg4a}[0] =~
  107. qr/existing target is neither a link nor a directory/
  108. => 'link to new dir bin4a conflicts with existing non-directory'
  109. );
  110. #
  111. # Link to files 'file4b' and 'bin4b' conflict with existing files
  112. # without --adopt
  113. #
  114. $stow = new_Stow();
  115. # Populate target
  116. make_file('file4b', 'file4b - version originally in target');
  117. make_path ('bin4b');
  118. make_file('bin4b/file4b', 'bin4b/file4b - version originally in target');
  119. # Populate
  120. make_path ('../stow/pkg4b/bin4b');
  121. make_file('../stow/pkg4b/file4b', 'file4b - version originally in stow package');
  122. make_file('../stow/pkg4b/bin4b/file4b', 'bin4b/file4b - version originally in stow package');
  123. $stow->plan_stow('pkg4b');
  124. %conflicts = $stow->get_conflicts();
  125. is($stow->get_conflict_count, 2 => 'conflict per file');
  126. for my $i (0, 1) {
  127. like(
  128. $conflicts{stow}{pkg4b}[$i],
  129. qr/existing target is neither a link nor a directory/
  130. => 'link to file4b conflicts with existing non-directory'
  131. );
  132. }
  133. #
  134. # Link to files 'file4b' and 'bin4b' do not conflict with existing
  135. # files when --adopt is given
  136. #
  137. $stow = new_Stow(adopt => 1);
  138. # Populate target
  139. make_file('file4c', "file4c - version originally in target\n");
  140. make_path ('bin4c');
  141. make_file('bin4c/file4c', "bin4c/file4c - version originally in target\n");
  142. # Populate
  143. make_path ('../stow/pkg4c/bin4c');
  144. make_file('../stow/pkg4c/file4c', "file4c - version originally in stow package\n");
  145. make_file('../stow/pkg4c/bin4c/file4c', "bin4c/file4c - version originally in stow package\n");
  146. $stow->plan_stow('pkg4c');
  147. is($stow->get_conflict_count, 0 => 'no conflicts with --adopt');
  148. is($stow->get_tasks, 4 => 'two tasks per file');
  149. $stow->process_tasks();
  150. for my $file ('file4c', 'bin4c/file4c') {
  151. ok(-l $file, "$file turned into a symlink");
  152. is(
  153. readlink $file,
  154. (index($file, '/') == -1 ? '' : '../' )
  155. . "../stow/pkg4c/$file" => "$file points to right place"
  156. );
  157. is(cat_file($file), "$file - version originally in target\n" => "$file has right contents");
  158. }
  159. #
  160. # Target already exists but is not owned by stow
  161. #
  162. $stow = new_Stow();
  163. make_path('bin5');
  164. make_invalid_link('bin5/file5','../../empty');
  165. make_path('../stow/pkg5/bin5/file5');
  166. $stow->plan_stow('pkg5');
  167. %conflicts = $stow->get_conflicts();
  168. like(
  169. $conflicts{stow}{pkg5}[-1],
  170. qr/not owned by stow/
  171. => 'target already exists but is not owned by stow'
  172. );
  173. #
  174. # Replace existing but invalid target
  175. #
  176. $stow = new_Stow();
  177. make_invalid_link('file6','../stow/path-does-not-exist');
  178. make_path('../stow/pkg6');
  179. make_file('../stow/pkg6/file6');
  180. $stow->plan_stow('pkg6');
  181. $stow->process_tasks();
  182. is(
  183. readlink('file6'),
  184. '../stow/pkg6/file6'
  185. => 'replace existing but invalid target'
  186. );
  187. #
  188. # Target already exists, is owned by stow, but points to a non-directory
  189. # (can't unfold)
  190. #
  191. $stow = new_Stow();
  192. #set_debug_level(4);
  193. make_path('bin7');
  194. make_path('../stow/pkg7a/bin7');
  195. make_file('../stow/pkg7a/bin7/node7');
  196. make_link('bin7/node7','../../stow/pkg7a/bin7/node7');
  197. make_path('../stow/pkg7b/bin7/node7');
  198. make_file('../stow/pkg7b/bin7/node7/file7');
  199. $stow->plan_stow('pkg7b');
  200. %conflicts = $stow->get_conflicts();
  201. like(
  202. $conflicts{stow}{pkg7b}[-1],
  203. qr/existing target is stowed to a different package/
  204. => 'link to new dir conflicts with existing stowed non-directory'
  205. );
  206. #
  207. # stowing directories named 0
  208. #
  209. $stow = new_Stow();
  210. make_path('../stow/pkg8a/0');
  211. make_file('../stow/pkg8a/0/file8a');
  212. make_link('0' => '../stow/pkg8a/0'); # emulate stow
  213. make_path('../stow/pkg8b/0');
  214. make_file('../stow/pkg8b/0/file8b');
  215. $stow->plan_stow('pkg8b');
  216. $stow->process_tasks();
  217. ok(
  218. $stow->get_conflict_count == 0 &&
  219. -d '0' &&
  220. readlink('0/file8a') eq '../../stow/pkg8a/0/file8a' &&
  221. readlink('0/file8b') eq '../../stow/pkg8b/0/file8b'
  222. => 'stowing directories named 0'
  223. );
  224. #
  225. # overriding already stowed documentation
  226. #
  227. $stow = new_Stow(override => ['man9', 'info9']);
  228. make_path('../stow/pkg9a/man9/man1');
  229. make_file('../stow/pkg9a/man9/man1/file9.1');
  230. make_path('man9/man1');
  231. make_link('man9/man1/file9.1' => '../../../stow/pkg9a/man9/man1/file9.1'); # emulate stow
  232. make_path('../stow/pkg9b/man9/man1');
  233. make_file('../stow/pkg9b/man9/man1/file9.1');
  234. $stow->plan_stow('pkg9b');
  235. $stow->process_tasks();
  236. ok(
  237. $stow->get_conflict_count == 0 &&
  238. readlink('man9/man1/file9.1') eq '../../../stow/pkg9b/man9/man1/file9.1'
  239. => 'overriding existing documentation files'
  240. );
  241. #
  242. # deferring to already stowed documentation
  243. #
  244. $stow = new_Stow(defer => ['man10', 'info10']);
  245. make_path('../stow/pkg10a/man10/man1');
  246. make_file('../stow/pkg10a/man10/man1/file10.1');
  247. make_path('man10/man1');
  248. make_link('man10/man1/file10.1' => '../../../stow/pkg10a/man10/man1/file10.1'); # emulate stow
  249. make_path('../stow/pkg10b/man10/man1');
  250. make_file('../stow/pkg10b/man10/man1/file10.1');
  251. $stow->plan_stow('pkg10b');
  252. is($stow->get_tasks, 0, 'no tasks to process');
  253. ok(
  254. $stow->get_conflict_count == 0 &&
  255. readlink('man10/man1/file10.1') eq '../../../stow/pkg10a/man10/man1/file10.1'
  256. => 'defer to existing documentation files'
  257. );
  258. #
  259. # Ignore temp files
  260. #
  261. $stow = new_Stow(ignore => ['~', '\.#.*']);
  262. make_path('../stow/pkg11/man11/man1');
  263. make_file('../stow/pkg11/man11/man1/file11.1');
  264. make_file('../stow/pkg11/man11/man1/file11.1~');
  265. make_file('../stow/pkg11/man11/man1/.#file11.1');
  266. make_path('man11/man1');
  267. $stow->plan_stow('pkg11');
  268. $stow->process_tasks();
  269. ok(
  270. $stow->get_conflict_count == 0 &&
  271. readlink('man11/man1/file11.1') eq '../../../stow/pkg11/man11/man1/file11.1' &&
  272. !-e 'man11/man1/file11.1~' &&
  273. !-e 'man11/man1/.#file11.1'
  274. => 'ignore temp files'
  275. );
  276. #
  277. # stowing links library files
  278. #
  279. $stow = new_Stow();
  280. make_path('../stow/pkg12/lib12/');
  281. make_file('../stow/pkg12/lib12/lib.so.1');
  282. make_link('../stow/pkg12/lib12/lib.so', 'lib.so.1');
  283. make_path('lib12/');
  284. $stow->plan_stow('pkg12');
  285. $stow->process_tasks();
  286. ok(
  287. $stow->get_conflict_count == 0 &&
  288. readlink('lib12/lib.so.1') eq '../../stow/pkg12/lib12/lib.so.1' &&
  289. readlink('lib12/lib.so' ) eq '../../stow/pkg12/lib12/lib.so'
  290. => 'stow links to libraries'
  291. );
  292. #
  293. # unfolding to stow links to library files
  294. #
  295. $stow = new_Stow();
  296. make_path('../stow/pkg13a/lib13/');
  297. make_file('../stow/pkg13a/lib13/liba.so.1');
  298. make_link('../stow/pkg13a/lib13/liba.so', 'liba.so.1');
  299. make_link('lib13','../stow/pkg13a/lib13');
  300. make_path('../stow/pkg13b/lib13/');
  301. make_file('../stow/pkg13b/lib13/libb.so.1');
  302. make_link('../stow/pkg13b/lib13/libb.so', 'libb.so.1');
  303. $stow->plan_stow('pkg13b');
  304. $stow->process_tasks();
  305. ok(
  306. $stow->get_conflict_count == 0 &&
  307. readlink('lib13/liba.so.1') eq '../../stow/pkg13a/lib13/liba.so.1' &&
  308. readlink('lib13/liba.so' ) eq '../../stow/pkg13a/lib13/liba.so' &&
  309. readlink('lib13/libb.so.1') eq '../../stow/pkg13b/lib13/libb.so.1' &&
  310. readlink('lib13/libb.so' ) eq '../../stow/pkg13b/lib13/libb.so'
  311. => 'unfolding to stow links to libraries'
  312. );
  313. #
  314. # stowing to stow dir should fail
  315. #
  316. make_path('stow');
  317. $stow = new_Stow(dir => 'stow');
  318. make_path('stow/pkg14/stow/pkg15');
  319. make_file('stow/pkg14/stow/pkg15/node15');
  320. capture_stderr();
  321. $stow->plan_stow('pkg14');
  322. is($stow->get_tasks, 0, 'no tasks to process');
  323. ok(
  324. $stow->get_conflict_count == 0 &&
  325. ! -l 'stow/pkg15'
  326. => "stowing to stow dir should fail"
  327. );
  328. like($stderr,
  329. qr/WARNING: skipping target which was current stow directory stow/
  330. => "stowing to stow dir should give warning");
  331. uncapture_stderr();
  332. #
  333. # stow a simple tree minimally when cwd isn't target
  334. #
  335. cd('../..');
  336. $stow = new_Stow(dir => "$TEST_DIR/stow", target => "$TEST_DIR/target");
  337. make_path("$TEST_DIR/stow/pkg16/bin16");
  338. make_file("$TEST_DIR/stow/pkg16/bin16/file16");
  339. $stow->plan_stow('pkg16');
  340. $stow->process_tasks();
  341. is_deeply([ $stow->get_conflicts ], [], 'no conflicts with minimal stow');
  342. is(
  343. readlink("$TEST_DIR/target/bin16"),
  344. '../stow/pkg16/bin16',
  345. => "minimal stow of a simple tree when cwd isn't target"
  346. );
  347. #
  348. # stow a simple tree minimally to absolute stow dir when cwd isn't
  349. # target
  350. #
  351. $stow = new_Stow(dir => canon_path("$TEST_DIR/stow"),
  352. target => "$TEST_DIR/target");
  353. make_path("$TEST_DIR/stow/pkg17/bin17");
  354. make_file("$TEST_DIR/stow/pkg17/bin17/file17");
  355. $stow->plan_stow('pkg17');
  356. $stow->process_tasks();
  357. is_deeply([ $stow->get_conflicts ], [], 'no conflicts with minimal stow');
  358. is(
  359. readlink("$TEST_DIR/target/bin17"),
  360. '../stow/pkg17/bin17',
  361. => "minimal stow of a simple tree with absolute stow dir"
  362. );
  363. #
  364. # stow a simple tree minimally with absolute stow AND target dirs when
  365. # cwd isn't target
  366. #
  367. $stow = new_Stow(dir => canon_path("$TEST_DIR/stow"),
  368. target => canon_path("$TEST_DIR/target"));
  369. make_path("$TEST_DIR/stow/pkg18/bin18");
  370. make_file("$TEST_DIR/stow/pkg18/bin18/file18");
  371. $stow->plan_stow('pkg18');
  372. $stow->process_tasks();
  373. is_deeply([ $stow->get_conflicts ], [], 'no conflicts with minimal stow');
  374. is(
  375. readlink("$TEST_DIR/target/bin18"),
  376. '../stow/pkg18/bin18',
  377. => "minimal stow of a simple tree with absolute stow and target dirs"
  378. );
  379. #
  380. # stow a tree with no-folding enabled -
  381. # no new folded directories should be created, and existing
  382. # folded directories should be split open (unfolded) where
  383. # (and only where) necessary
  384. #
  385. cd("$TEST_DIR/target");
  386. sub create_pkg {
  387. my ($id, $pkg) = @_;
  388. my $stow_pkg = "../stow/$id-$pkg";
  389. make_path ($stow_pkg);
  390. make_file("$stow_pkg/$id-file-$pkg");
  391. # create a shallow hierarchy specific to this package which isn't
  392. # yet stowed
  393. make_path ("$stow_pkg/$id-$pkg-only-new");
  394. make_file("$stow_pkg/$id-$pkg-only-new/$id-file-$pkg");
  395. # create a deeper hierarchy specific to this package which isn't
  396. # yet stowed
  397. make_path ("$stow_pkg/$id-$pkg-only-new2/subdir");
  398. make_file("$stow_pkg/$id-$pkg-only-new2/subdir/$id-file-$pkg");
  399. make_link("$stow_pkg/$id-$pkg-only-new2/current", "subdir");
  400. # create a hierarchy specific to this package which is already
  401. # stowed via a folded tree
  402. make_path ("$stow_pkg/$id-$pkg-only-old");
  403. make_link("$id-$pkg-only-old", "$stow_pkg/$id-$pkg-only-old");
  404. make_file("$stow_pkg/$id-$pkg-only-old/$id-file-$pkg");
  405. # create a shared hierarchy which this package uses
  406. make_path ("$stow_pkg/$id-shared");
  407. make_file("$stow_pkg/$id-shared/$id-file-$pkg");
  408. # create a partially shared hierarchy which this package uses
  409. make_path ("$stow_pkg/$id-shared2/subdir-$pkg");
  410. make_file("$stow_pkg/$id-shared2/$id-file-$pkg");
  411. make_file("$stow_pkg/$id-shared2/subdir-$pkg/$id-file-$pkg");
  412. }
  413. foreach my $pkg (qw{a b}) {
  414. create_pkg('no-folding', $pkg);
  415. }
  416. $stow = new_Stow('no-folding' => 1);
  417. $stow->plan_stow('no-folding-a');
  418. is_deeply([ $stow->get_conflicts ], [] => 'no conflicts with --no-folding');
  419. my @tasks = $stow->get_tasks;
  420. use Data::Dumper;
  421. is(scalar(@tasks), 13 => "6 dirs, 7 links") || warn Dumper(\@tasks);
  422. $stow->process_tasks();
  423. sub check_no_folding {
  424. my ($pkg) = @_;
  425. my $stow_pkg = "../stow/no-folding-$pkg";
  426. is_link("no-folding-file-$pkg", "$stow_pkg/no-folding-file-$pkg");
  427. # check existing folded tree is untouched
  428. is_link("no-folding-$pkg-only-old", "$stow_pkg/no-folding-$pkg-only-old");
  429. # check newly stowed shallow tree is not folded
  430. is_dir_not_symlink("no-folding-$pkg-only-new");
  431. is_link("no-folding-$pkg-only-new/no-folding-file-$pkg",
  432. "../$stow_pkg/no-folding-$pkg-only-new/no-folding-file-$pkg");
  433. # check newly stowed deeper tree is not folded
  434. is_dir_not_symlink("no-folding-$pkg-only-new2");
  435. is_dir_not_symlink("no-folding-$pkg-only-new2/subdir");
  436. is_link("no-folding-$pkg-only-new2/subdir/no-folding-file-$pkg",
  437. "../../$stow_pkg/no-folding-$pkg-only-new2/subdir/no-folding-file-$pkg");
  438. is_link("no-folding-$pkg-only-new2/current",
  439. "../$stow_pkg/no-folding-$pkg-only-new2/current");
  440. # check shared tree is not folded. first time round this will be
  441. # newly stowed.
  442. is_dir_not_symlink('no-folding-shared');
  443. is_link("no-folding-shared/no-folding-file-$pkg",
  444. "../$stow_pkg/no-folding-shared/no-folding-file-$pkg");
  445. # check partially shared tree is not folded. first time round this
  446. # will be newly stowed.
  447. is_dir_not_symlink('no-folding-shared2');
  448. is_link("no-folding-shared2/no-folding-file-$pkg",
  449. "../$stow_pkg/no-folding-shared2/no-folding-file-$pkg");
  450. is_link("no-folding-shared2/no-folding-file-$pkg",
  451. "../$stow_pkg/no-folding-shared2/no-folding-file-$pkg");
  452. }
  453. check_no_folding('a');
  454. $stow = new_Stow('no-folding' => 1);
  455. $stow->plan_stow('no-folding-b');
  456. is_deeply([ $stow->get_conflicts ], [] => 'no conflicts with --no-folding');
  457. @tasks = $stow->get_tasks;
  458. is(scalar(@tasks), 11 => '4 dirs, 7 links') || warn Dumper(\@tasks);
  459. $stow->process_tasks();
  460. check_no_folding('a');
  461. check_no_folding('b');