File Coverage

blib/lib/Brackup/Test.pm
Criterion Covered Total %
statement 163 178 91.5
branch 42 66 63.6
condition 7 8 87.5
subroutine 28 29 96.5
pod 0 9 0.0
total 240 290 82.7


line stmt bran cond sub pod time code
1             # support module for helping test brackup
2             package Brackup::Test;
3             require Exporter;
4 12     12   469615 use strict;
  12         28  
  12         501  
5 12     12   66 use vars qw(@ISA @EXPORT);
  12         21  
  12         1544  
6             @ISA = qw(Exporter);
7             @EXPORT = qw(do_backup do_restore ok_dirs_match ok_files_match);
8              
9 12     12   149 use Test::More;
  12         19  
  12         8189  
10 12     12   24382 use FindBin qw($Bin);
  12         17599  
  12         2188  
11 12     12   8866 use Brackup::Util qw(tempdir tempfile);
  12         45  
  12         1355  
12 12     12   441 use File::Find;
  12         26  
  12         763  
13 12     12   12880 use File::stat ();
  12         113416  
  12         351  
14 12     12   191 use Cwd;
  12         31  
  12         941  
15 12     12   7972 use Brackup::DecryptedFile;
  12         31  
  12         445  
16 12     12   87 use Digest::SHA1 qw/sha1_hex/;
  12         22  
  12         679  
17              
18 12     12   6636 use Brackup;
  12         42  
  12         20153  
19              
20 12     12   5786 my $has_diff = eval "use Text::Diff; 1;";
  0         0  
  0         0  
21              
22             my @to_unlink;
23             my $par_pid = $$;
24             END {
25 12 50   12   17026 if ($$ == $par_pid) {
26 12 50       1653 my $rv = unlink @to_unlink unless $ENV{BRACKUP_TEST_NOCLEANUP};
27             }
28             }
29              
30             # Set the gpg directory, so we don't rely on users having a ~/.gnupg
31             $ENV{GNUPGHOME} = tempdir( CLEANUP => 1 );
32              
33             sub do_backup {
34 8     8 0 18404 my %opts = @_;
35 8   50 0   135 my $with_confsec = delete $opts{'with_confsec'} || sub {};
  0         0  
36 8   100 6   520 my $with_targetsec = delete $opts{'with_targetsec'} || sub {};
  6         12  
37 8   100 5   110 my $with_root = delete $opts{'with_root'} || sub {};
  5         12  
38 8         43 my $target = delete $opts{'with_target'};
39 8 50       47 die if %opts;
40              
41 8         180 my $initer = shift;
42              
43 8         990 my $conf = Brackup::Config->new;
44 8         73 my $confsec;
45              
46 8         112 $confsec = Brackup::ConfigSection->new("SOURCE:test_root");
47 8         53 $with_confsec->($confsec);
48 8         48 $conf->add_section($confsec);
49              
50 8         78 my $root = $conf->load_root("test_root");
51 8         64 ok($root, "have a source root");
52 8         11804 $with_root->($root);
53              
54 8 100       251 unless ($target) {
55 7 50       599 my $backup_dir = tempdir( CLEANUP => $ENV{BRACKUP_TEST_NOCLEANUP} ? 0 : 1 );
56 7         7825 ok_dir_empty($backup_dir);
57              
58 7         14090 my ($inv_fh, $inv_filename) = tempfile();
59 7         103 close($inv_fh);
60 7         33 push @to_unlink, $inv_filename;
61              
62              
63 7         78 $confsec = Brackup::ConfigSection->new("TARGET:test_restore");
64 7         35 $with_targetsec->($confsec);
65 7 100       53 $confsec->add("type" => "Filesystem") unless exists $confsec->{type};
66 7         34 $confsec->add("inventorydb_file" => $inv_filename);
67 7         27 $confsec->add("path" => $backup_dir);
68 7         125 $conf->add_section($confsec);
69 7         66 $target = $conf->load_target("test_restore", testmode => 1);
70             }
71              
72 8         93 ok($target, "have a target ($target)");
73              
74 8         12842 my $backup = Brackup::Backup->new(
75             root => $root,
76             target => $target,
77             savefiles => 1,
78             );
79 8         36 ok($backup, "have a backup object");
80              
81 8         5049 my ($meta_fh, $meta_filename) = tempfile();
82 8         178 ok(-e $meta_filename, "metafile exists");
83 8         5101 push @to_unlink, $meta_filename;
84              
85 8         65 ok(eval { $backup->backup($meta_filename) }, "backup succeeded");
  8         70  
86 8 50       17034 if ($@) {
87 0         0 warn "Died running backup: $@\n";
88             }
89 8         247 ok(-s $meta_filename, "backup file has size");
90              
91 8         9583 check_inventory_db($target, [$root->gpg_args]);
92              
93 8 100       10805 return wantarray ? ($meta_filename, $backup, $target) : $meta_filename;
94             }
95              
96             sub check_inventory_db {
97 9     9 0 1527 my ($target, $gpg_args) = @_;
98              
99 9         18 my $inv_db_file;
100 9         21 eval {
101 9 50       94 my $inv_db = $target->inventory_db or die 'cannot open inventory db';
102 9 50       93 $inv_db_file = $inv_db->backing_file ? (' ' . $inv_db->backing_file) : '';
103              
104 9         57 while (my ($key, $value) = $inv_db->each) {
105 107         503 my ($raw_dig, $gpg_rcpt) = split /;/, $key;
106 107         906 my ($enc_dig, $enc_size, $range) = split /\s+/, $value;
107              
108             # check the stored data
109 107 50       870 my $dataref = $target->load_chunk($enc_dig) or die "cannot load chunk $enc_dig";
110 107 50       687 length $$dataref == $enc_size or die "chunk $enc_dig has wrong size, not $enc_size";
111 107 50       3616 $enc_dig eq "sha1:".sha1_hex($$dataref) or die "chunk $enc_dig has wrong digest";
112            
113             # if we are in a composite chunk, keep only the part we want
114 107 100       438 if($range) {
115 11         72 my ($from, $to) = split '-', $range;
116 11         58 my $part = substr $$dataref, $from, $to-$from;
117 11         43 $dataref = \$part;
118             }
119              
120             # decrypt if encrypted
121 107         183 my $dec_ref;
122 107 100       963 if($gpg_rcpt =~ /^to=(.*)$/) {
123 33         345 my $meta = { 'GPG-Recipient' => $1 };
124 33         263 local @Brackup::GPG_ARGS = @$gpg_args;
125              
126 33         296 $dec_ref = Brackup::Decrypt::decrypt_data($dataref, meta => $meta);
127             } else {
128 74         109 $dec_ref = $dataref;
129             }
130              
131             # check the raw data
132 107 50       4346 $raw_dig eq "sha1:".sha1_hex($$dec_ref) or die "chunk $enc_dig has wrong raw digest";
133             }
134             };
135 9 50       132 ok(!$@, "inventory db$inv_db_file is good") or diag($@);
136             }
137              
138             sub do_restore {
139 11     11 0 21604 my ($backup_file, %opts) = @_;
140 11   100     153 my $prefix = delete $opts{'prefix'} || ""; # default is restore everything
141 11         41 my $restore_should_die = delete $opts{'restore_should_die'};
142 11 50       275 die if %opts;
143 11 50       135 my $restore_dir = tempdir( CLEANUP => $ENV{BRACKUP_TEST_NOCLEANUP} ? 0 : 1 );
144 11         9390 ok_dir_empty($restore_dir);
145              
146 11         11870 my $restore = Brackup::Restore->new(
147             to => $restore_dir,
148             prefix => $prefix,
149             file => $backup_file,
150             );
151 11         71 ok($restore, "have restore object");
152 11         7944 my $rv = eval { $restore->restore; };
  11         103  
153 11 100       3214 if ($restore_should_die) {
154 1 50       14 ok(! defined $rv, "restore died: $@")
155             or die "restore unexpectedly succeeded";
156 1         1071 return;
157             }
158             else {
159 10 50       119 ok($rv, "did the restore")
160             or die "restore failed: $@";
161 10         8507 return $restore_dir;
162             }
163             }
164              
165             sub ok_dirs_match {
166 8     8 0 100 my ($after, $before) = @_;
167              
168 8         111 my $pre_ls = dir_structure($before);
169 8         36 my $post_ls = dir_structure($after);
170              
171 8 50       184 if ($has_diff) {
172 12     12   21785 use Data::Dumper;
  12         133740  
  12         2913  
173 0         0 my $pre_dump = Dumper($pre_ls);
174 0         0 my $post_dump = Dumper($post_ls);
175 0         0 my $diff = Text::Diff::diff(\$pre_dump, \$post_dump);
176 0         0 is($diff, "", "dirs match");
177             } else {
178 8         221 is_deeply($post_ls, $pre_ls, "dirs match");
179             }
180             }
181              
182             sub ok_files_match {
183 2     2 0 23 my ($after, $before) = @_;
184              
185 2         11 my $pre_ls = file_meta($before);
186 2         7 my $post_ls = file_meta($after);
187              
188 2 50       7 if ($has_diff) {
189 12     12   118 use Data::Dumper;
  12         1259  
  12         15650  
190 0         0 my $pre_dump = Dumper($pre_ls);
191 0         0 my $post_dump = Dumper($post_ls);
192 0         0 my $diff = Text::Diff::diff(\$pre_dump, \$post_dump);
193 0         0 is($diff, "", "files match");
194             } else {
195 2         12 is_deeply($post_ls, $pre_ls, "files match");
196             }
197             }
198              
199             sub ok_dir_empty {
200 18     18 0 87 my $dir = shift;
201 18 50       682 unless (-d $dir) { ok(0, "not a dir"); return; }
  0         0  
  0         0  
202 18 50       2053 opendir(my $dh, $dir) or die "failed to opendir: $!";
203 18         1137 is_deeply([ sort readdir($dh) ], ['.', '..'], "dir is empty: $dir");
204             }
205              
206             sub file_meta {
207 202     202 0 262 my $path = shift;
208 202         624 my $st = File::stat::lstat($path);
209              
210 202         28517 my $meta = {};
211 202 100       7274 $meta->{size} = $st->size unless -d $path;
212 202 100       3907 $meta->{is_file} = 1 if -f $path;
213 202 50       2125 $meta->{is_link} = 1 if -l $path;
214 202 50       502 if ($meta->{is_link}) {
215 0         0 $meta->{link} = readlink $path;
216             } else {
217             # we ignore these for links, since Linux doesn't let us restore anyway,
218             # as Linux as no lutimes(2) syscall, as of Linux 2.6.16 at least
219 202         209 $meta->{atime} = $st->atime if 0; # TODO: make tests work with atimes
220 202         5150 $meta->{mtime} = $st->mtime;
221 202         5679 $meta->{mode} = sprintf('%#o', $st->mode & 0777);
222             }
223              
224             # the gpg tests open/close the rings in the root, so
225             # mtimes get bumped around or something. the proper fix
226             # is too ugly for what it's worth, so let's just ignore
227             # the mtime of top-level
228 202 100       2490 delete $meta->{mtime} if $path eq ".";
229              
230 202         6317 return $meta;
231             }
232              
233             # given a directory, returns a hashref of its contentn
234             sub dir_structure {
235 16     16 0 47 my $dir = shift;
236 16         39 my %files; # "filename" -> {metadata => ...}
237 16         160 my $cwd = getcwd;
238 16 50       554 chdir($dir) or die "Failed to chdir to $dir";
239              
240             find({
241             no_chdir => 1,
242 38     38   1421 preprocess => sub { return sort @_ },
243             wanted => sub {
244 198     198   313 my $path = $_;
245 198         502 $files{$path} = file_meta($path);
246             },
247 16         1654 }, ".");
248              
249 16 50       560 chdir($cwd) or die "Failed to chdir back to $cwd";
250 16         316 return \%files;
251             }
252              
253             # add a random number of orphan chunks to $target
254             sub add_orphan_chunks {
255 1     1 0 599 my ($root, $target, $orphan_chunks_count) = @_;
256              
257 1         5 for (1..$orphan_chunks_count) {
258             # HACK: to avoid worse hacks, we need a pchunk to store an orphan chunk.
259             # We use small segments of 'pubring-test.gpg' so that they are different
260             # than all other chunks
261 7         253 my $pchunk = Brackup::PositionedChunk->new(
262             file => Brackup::File->new(root => $root,
263             path => 'pubring-test.gpg'),
264             offset => $_ * 10,
265             length => 10,
266             );
267              
268             # no encryption, copy raw data and store schunk
269 7         46 my $schunk = Brackup::StoredChunk->new($pchunk);
270             # $schunk->copy_raw_data;
271 7         31 $target->store_chunk($schunk);
272             }
273             }
274              
275              
276             1;
277              
278             # vim:et:sw=4