testutil.pm 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  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. # Utilities shared by test scripts
  19. #
  20. package testutil;
  21. use strict;
  22. use warnings;
  23. use Carp qw(croak);
  24. use File::Basename;
  25. use File::Path qw(make_path remove_tree);
  26. use File::Spec;
  27. use IO::Scalar;
  28. use Test::More;
  29. use Stow;
  30. use Stow::Util qw(parent canon_path);
  31. use base qw(Exporter);
  32. our @EXPORT = qw(
  33. $ABS_TEST_DIR
  34. $TEST_DIR
  35. $stderr
  36. init_test_dirs
  37. cd
  38. new_Stow new_compat_Stow
  39. make_path make_link make_invalid_link make_file
  40. remove_dir remove_file remove_link
  41. cat_file
  42. is_link is_dir_not_symlink is_nonexistent_path
  43. capture_stderr uncapture_stderr
  44. );
  45. our $TEST_DIR = 'tmp-testing-trees';
  46. our $ABS_TEST_DIR = File::Spec->rel2abs('tmp-testing-trees');
  47. our $stderr;
  48. my $tied_err;
  49. sub capture_stderr {
  50. undef $stderr;
  51. $tied_err = tie *STDERR, 'IO::Scalar', \$stderr;
  52. }
  53. sub uncapture_stderr {
  54. undef $tied_err;
  55. untie *STDERR;
  56. }
  57. sub init_test_dirs {
  58. # Create a run_from/ subdirectory for tests which want to run
  59. # from a separate directory outside the Stow directory or
  60. # target directory.
  61. for my $dir ("target", "stow", "run_from") {
  62. my $path = "$TEST_DIR/$dir";
  63. -d $path and remove_tree($path);
  64. make_path($path);
  65. }
  66. # Don't let user's ~/.stow-global-ignore affect test results
  67. $ENV{HOME} = $ABS_TEST_DIR;
  68. }
  69. sub new_Stow {
  70. my %opts = @_;
  71. $opts{dir} ||= '../stow';
  72. $opts{target} ||= '.';
  73. $opts{test_mode} = 1;
  74. return new Stow(%opts);
  75. }
  76. sub new_compat_Stow {
  77. my %opts = @_;
  78. $opts{compat} = 1;
  79. return new_Stow(%opts);
  80. }
  81. #===== SUBROUTINE ===========================================================
  82. # Name : make_link()
  83. # Purpose : safely create a link
  84. # Parameters: $target => path to the link
  85. # : $source => where the new link should point
  86. # : $invalid => true iff $source refers to non-existent file
  87. # Returns : n/a
  88. # Throws : fatal error if the link can not be safely created
  89. # Comments : checks for existing nodes
  90. #============================================================================
  91. sub make_link {
  92. my ($target, $source, $invalid) = @_;
  93. if (-l $target) {
  94. my $old_source = readlink join('/', parent($target), $source)
  95. or die "$target is already a link but could not read link $target/$source";
  96. if ($old_source ne $source) {
  97. die "$target already exists but points elsewhere\n";
  98. }
  99. }
  100. die "$target already exists and is not a link\n" if -e $target;
  101. my $abs_target = File::Spec->rel2abs($target);
  102. my $target_container = dirname($abs_target);
  103. my $abs_source = File::Spec->rel2abs($source, $target_container);
  104. #warn "t $target c $target_container as $abs_source";
  105. if (-e $abs_source) {
  106. croak "Won't make invalid link pointing to existing $abs_target"
  107. if $invalid;
  108. }
  109. else {
  110. croak "Won't make link pointing to non-existent $abs_target"
  111. unless $invalid;
  112. }
  113. symlink $source, $target
  114. or die "could not create link $target => $source ($!)\n";
  115. }
  116. #===== SUBROUTINE ===========================================================
  117. # Name : make_invalid_link()
  118. # Purpose : safely create an invalid link
  119. # Parameters: $target => path to the link
  120. # : $source => the non-existent source where the new link should point
  121. # Returns : n/a
  122. # Throws : fatal error if the link can not be safely created
  123. # Comments : checks for existing nodes
  124. #============================================================================
  125. sub make_invalid_link {
  126. my ($target, $source, $allow_invalid) = @_;
  127. make_link($target, $source, 1);
  128. }
  129. #===== SUBROUTINE ===========================================================
  130. # Name : create_file()
  131. # Purpose : create an empty file
  132. # Parameters: $path => proposed path to the file
  133. # : $contents => (optional) contents to write to file
  134. # Returns : n/a
  135. # Throws : fatal error if the file could not be created
  136. # Comments : detects clash with an existing non-file
  137. #============================================================================
  138. sub make_file {
  139. my ($path, $contents) = @_;
  140. if (-e $path and ! -f $path) {
  141. die "a non-file already exists at $path\n";
  142. }
  143. open my $FILE ,'>', $path
  144. or die "could not create file: $path ($!)\n";
  145. print $FILE $contents if defined $contents;
  146. close $FILE;
  147. }
  148. #===== SUBROUTINE ===========================================================
  149. # Name : remove_link()
  150. # Purpose : remove an esiting symbolic link
  151. # Parameters: $path => path to the symbolic link
  152. # Returns : n/a
  153. # Throws : fatal error if the operation fails or if passed the path to a
  154. # : non-link
  155. # Comments : none
  156. #============================================================================
  157. sub remove_link {
  158. my ($path) = @_;
  159. if (not -l $path) {
  160. die qq(remove_link() called with a non-link: $path);
  161. }
  162. unlink $path or die "could not remove link: $path ($!)\n";
  163. return;
  164. }
  165. #===== SUBROUTINE ===========================================================
  166. # Name : remove_file()
  167. # Purpose : remove an existing empty file
  168. # Parameters: $path => the path to the empty file
  169. # Returns : n/a
  170. # Throws : fatal error if given file is non-empty or the operation fails
  171. # Comments : none
  172. #============================================================================
  173. sub remove_file {
  174. my ($path) = @_;
  175. if (-z $path) {
  176. die "file at $path is non-empty\n";
  177. }
  178. unlink $path or die "could not remove empty file: $path ($!)\n";
  179. return;
  180. }
  181. #===== SUBROUTINE ===========================================================
  182. # Name : remove_dir()
  183. # Purpose : safely remove a tree of test files
  184. # Parameters: $dir => path to the top of the tree
  185. # Returns : n/a
  186. # Throws : fatal error if the tree contains a non-link or non-empty file
  187. # Comments : recursively removes directories containing softlinks empty files
  188. #============================================================================
  189. sub remove_dir {
  190. my ($dir) = @_;
  191. if (not -d $dir) {
  192. die "$dir is not a directory";
  193. }
  194. opendir my $DIR, $dir or die "cannot read directory: $dir ($!)\n";
  195. my @listing = readdir $DIR;
  196. closedir $DIR;
  197. NODE:
  198. for my $node (@listing) {
  199. next NODE if $node eq '.';
  200. next NODE if $node eq '..';
  201. my $path = "$dir/$node";
  202. if (-l $path or (-f $path and -z $path) or $node eq $Stow::LOCAL_IGNORE_FILE) {
  203. unlink $path or die "cannot unlink $path ($!)\n";
  204. }
  205. elsif (-d "$path") {
  206. remove_dir($path);
  207. }
  208. else {
  209. die "$path is not a link, directory, or empty file\n";
  210. }
  211. }
  212. rmdir $dir or die "cannot rmdir $dir ($!)\n";
  213. return;
  214. }
  215. #===== SUBROUTINE ===========================================================
  216. # Name : cd()
  217. # Purpose : wrapper around chdir
  218. # Parameters: $dir => path to chdir to
  219. # Returns : n/a
  220. # Throws : fatal error if the chdir fails
  221. # Comments : none
  222. #============================================================================
  223. sub cd {
  224. my ($dir) = @_;
  225. chdir $dir or die "Failed to chdir($dir): $!\n";
  226. }
  227. #===== SUBROUTINE ===========================================================
  228. # Name : cat_file()
  229. # Purpose : return file contents
  230. # Parameters: $file => file to read
  231. # Returns : n/a
  232. # Throws : fatal error if the open fails
  233. # Comments : none
  234. #============================================================================
  235. sub cat_file {
  236. my ($file) = @_;
  237. open F, $file or die "Failed to open($file): $!\n";
  238. my $contents = join '', <F>;
  239. close(F);
  240. return $contents;
  241. }
  242. #===== SUBROUTINE ===========================================================
  243. # Name : is_link()
  244. # Purpose : assert path is a symlink
  245. # Parameters: $path => path to check
  246. # : $dest => target symlink should point to
  247. #============================================================================
  248. sub is_link {
  249. my ($path, $dest) = @_;
  250. ok(-l $path => "$path should be symlink");
  251. is(readlink $path, $dest => "$path symlinks to $dest");
  252. }
  253. #===== SUBROUTINE ===========================================================
  254. # Name : is_dir_not_symlink()
  255. # Purpose : assert path is a directory not a symlink
  256. # Parameters: $path => path to check
  257. #============================================================================
  258. sub is_dir_not_symlink {
  259. my ($path) = @_;
  260. ok(! -l $path => "$path should not be symlink");
  261. ok(-d _ => "$path should be a directory");
  262. }
  263. #===== SUBROUTINE ===========================================================
  264. # Name : is_nonexistent_path()
  265. # Purpose : assert path does not exist
  266. # Parameters: $path => path to check
  267. #============================================================================
  268. sub is_nonexistent_path {
  269. my ($path) = @_;
  270. ok(! -l $path => "$path should not be symlink");
  271. ok(! -e _ => "$path should not exist");
  272. }
  273. 1;
  274. # Local variables:
  275. # mode: perl
  276. # cperl-indent-level: 4
  277. # end:
  278. # vim: ft=perl