1# Copyright (c) 2008-2013 Zmanda, Inc.  All Rights Reserved.
2#
3# This program is free software; you can redistribute it and/or
4# modify it under the terms of the GNU General Public License
5# as published by the Free Software Foundation; either version 2
6# of the License, or (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
11# for more details.
12#
13# You should have received a copy of the GNU General Public License along
14# with this program; if not, write to the Free Software Foundation, Inc.,
15# 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16#
17# Contact information: Zmanda Inc, 465 S. Mathilda Ave., Suite 300
18# Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
19
20use Test::More tests => 31;
21use strict;
22use warnings;
23
24# This test only puts the perl wrappers through their paces -- the underlying
25# library is well-covered by amar-test.
26
27use lib "@amperldir@";
28use Installcheck;
29use Amanda::Archive;
30use Amanda::Paths;
31use Amanda::MainLoop;
32use Amanda::Debug;
33use Data::Dumper;
34
35Amanda::Debug::dbopen("installcheck");
36
37my $arch_filename = "$Installcheck::TMP/amanda_archive.bin";
38my $data_filename = "$Installcheck::TMP/some_data.bin";
39my ($fh, $dfh, $ar, $f1, $f2, $a1, $a2, @res, $posn);
40
41# some versions of Test::More will fail tests if the identity
42# relationships of the two objects passed to is_deeply do not
43# match, so we use the same object for $user_data throughout.
44my $user_data = [ "x", "y", "z" ];
45
46# set up a large file full of data
47
48open($dfh, ">", $data_filename);
49my $onek = "abcd" x 256;
50my $onemeg = $onek x 1024;
51for (my $i = 0; $i < 5; $i++) {
52    print $dfh $onemeg;
53}
54$onek = $onemeg = undef;
55close($dfh);
56
57# utility functions for creating a "fake" archive file
58
59sub make_header {
60    my ($fh, $version) = @_;
61    my $hdr = "AMANDA ARCHIVE FORMAT $version";
62    $hdr .= "\0" x (28 - length $hdr);
63    print $fh $hdr;
64}
65
66sub make_record {
67    my ($fh, $filenum, $attrid, $data, $eoa) = @_;
68    my $size = length($data);
69    if ($eoa) {
70	$size |= 0x80000000;
71    }
72    print $fh pack("nnN", $filenum, $attrid, $size);
73    print $fh $data;
74}
75
76####
77## TEST WRITING
78
79open($fh, ">", $arch_filename) or die("opening $arch_filename: $!");
80$ar = Amanda::Archive->new(fileno($fh), ">");
81pass("Create a new archive");
82
83$f1 = $ar->new_file("filename1");
84pass("Start an archive file");
85
86$a1 = $f1->new_attr($Amanda::Archive::AMAR_ATTR_GENERIC_DATA);
87$a1->add_data("foo!", 0);
88$a2 = $f1->new_attr(19);
89$a2->add_data("BAR!", 0);
90$a1->add_data("FOO.", 1);
91$a2->add_data("bar.", 0);
92pass("Write some interleaved data");
93
94$a1->close();
95pass("Close an attribute with the close() method");
96
97$a1 = Amanda::Archive::Attr->new($f1, 99);
98pass("Create an attribute with its constructor");
99
100open($dfh, "<", $data_filename);
101$a1->add_data_fd(fileno($dfh), 1);
102close($dfh);
103pass("Add data from a file descriptor");
104ok($a1->size() == 5242880, "size attribute A is " . $a1->size());
105ok($f1->size() == 5242961, "size file A is " . $f1->size());
106ok($ar->size() == 5242989, "Size A is " . $ar->size);
107
108$a1 = undef;
109pass("Close attribute when its refcount hits zero");
110ok($ar->size() == 5242989, "Size B is " . $ar->size);
111
112$f2 = Amanda::Archive::File->new($ar, "filename2");
113pass("Add a new file (filename2)");
114
115$a1 = $f2->new_attr(82);
116$a1->add_data("word", 1);
117pass("Add data to it");
118ok($a1->size() == 4, "size attribute A1 is " . $a1->size());
119ok($f2->size() == 29, "size file F2 is " . $f2->size());
120ok($ar->size() == 5243018, "Size C is " . $ar->size);
121
122$a2->add_data("barrrrr?", 0);	# note no EOA
123pass("Add more data to first attribute");
124ok($a2->size() == 16, "size attribute A2 is " . $a2->size());
125ok($f1->size() == 5242977, "size file F1 is " . $f1->size());
126ok($ar->size() == 5243034, "Size D is " . $ar->size);
127
128($f1, $posn) = $ar->new_file("posititioned file", 1);
129ok($posn > 0, "new_file returns a positive position");
130
131$ar = undef;
132pass("unref archive early");
133
134($ar, $f1, $f2, $a1, $a2) = ();
135pass("Close remaining objects");
136
137close($fh);
138
139####
140## TEST READING
141
142open($fh, ">", $arch_filename);
143make_header($fh, 1);
144make_record($fh, 16, 0, "/etc/passwd", 1);
145make_record($fh, 16, 20, "root:foo", 1);
146make_record($fh, 16, 21, "boot:foot", 0);
147make_record($fh, 16, 22, "dustin:snazzy", 1);
148make_record($fh, 16, 21, "..more-boot:foot", 1);
149make_record($fh, 16, 1, "", 1);
150close($fh);
151
152open($fh, "<", $arch_filename);
153$ar = Amanda::Archive->new(fileno($fh), "<");
154pass("Create a new archive for reading");
155
156@res = ();
157$ar->read(
158    file_start => sub {
159	push @res, [ "file_start", @_ ];
160	return "cows";
161    },
162    file_finish => sub {
163	push @res, [ "file_finish", @_ ];
164    },
165    0 => sub {
166	push @res, [ "frag", @_ ];
167	return "ants";
168    },
169    user_data => $user_data,
170);
171is_deeply([@res], [
172	[ 'file_start', $user_data, 16, '/etc/passwd' ],
173        [ 'frag', $user_data, 16, "cows", 20, undef, 'root:foo', 1, 0 ],
174        [ 'frag', $user_data, 16, "cows", 21, undef, 'boot:foot', 0, 0 ],
175        [ 'frag', $user_data, 16, "cows", 22, undef, 'dustin:snazzy', 1, 0 ],
176        [ 'frag', $user_data, 16, "cows", 21, "ants", '..more-boot:foot', 1, 0 ],
177        [ 'file_finish', $user_data, "cows", 16, 0 ]
178], "simple read callbacks called in the right order")
179    or diag(Dumper(\@res));
180$ar->close();
181close($fh);
182
183
184open($fh, "<", $arch_filename);
185$ar = Amanda::Archive->new(fileno($fh), "<");
186pass("Create a new archive for reading");
187
188@res = ();
189$ar->read(
190    file_start => sub {
191	push @res, [ "file_start", @_ ];
192	return "IGNORE";
193    },
194    file_finish => sub {
195	push @res, [ "file_finish", @_ ];
196    },
197    0 => sub {
198	push @res, [ "frag", @_ ];
199	return "ants";
200    },
201    user_data => $user_data,
202);
203is_deeply([@res], [
204	[ 'file_start', $user_data, 16, '/etc/passwd' ],
205], "'IGNORE' handled correctly")
206    or diag(Dumper(\@res));
207# TODO: check that file data gets dumped appropriately?
208
209
210open($fh, "<", $arch_filename);
211$ar = Amanda::Archive->new(fileno($fh), "<");
212
213@res = ();
214$ar->read(
215    file_start => sub {
216	push @res, [ "file_start", @_ ];
217	return "dogs";
218    },
219    file_finish => sub {
220	push @res, [ "file_finish", @_ ];
221    },
222    21 => [ 100, sub {
223	push @res, [ "fragbuf", @_ ];
224	return "pants";
225    } ],
226    0 => sub {
227	push @res, [ "frag", @_ ];
228	return "ants";
229    },
230    user_data => $user_data,
231);
232is_deeply([@res], [
233	[ 'file_start', $user_data, 16, '/etc/passwd' ],
234        [ 'frag', $user_data, 16, "dogs", 20, undef, 'root:foo', 1, 0 ],
235        [ 'frag', $user_data, 16, "dogs", 22, undef, 'dustin:snazzy', 1, 0 ],
236        [ 'fragbuf', $user_data, 16, "dogs", 21, undef, 'boot:foot..more-boot:foot', 1, 0 ],
237        [ 'file_finish', $user_data, "dogs", 16, 0 ]
238], "buffering parameters parsed correctly")
239    or diag(Dumper(\@res));
240
241
242open($fh, "<", $arch_filename);
243$ar = Amanda::Archive->new(fileno($fh), "<");
244
245@res = ();
246eval {
247    $ar->read(
248	file_start => sub {
249	    push @res, [ "file_start", @_ ];
250	    die "uh oh";
251	},
252	user_data => $user_data,
253    );
254};
255like($@, qr/uh oh at .*/, "exception propagated correctly");
256is_deeply([@res], [
257	[ 'file_start', $user_data, 16, '/etc/passwd' ],
258], "file_start called before exception was rasied")
259    or diag(Dumper(\@res));
260$ar->close();
261
262unlink($arch_filename);
263
264open($fh, ">", $arch_filename);
265$ar = Amanda::Archive->new(fileno($fh), ">");
266$f1 = $ar->new_file("filename1");
267$a1 = $f1->new_attr($Amanda::Archive::AMAR_ATTR_GENERIC_DATA);
268
269open($dfh, "<", $data_filename);
270$a1->add_data_fd(fileno($dfh), 1);
271close($dfh);
272
273$a1->close();
274$f1->close();
275
276$f1 = $ar->new_file("filename2");
277$a1 = $f1->new_attr($Amanda::Archive::AMAR_ATTR_GENERIC_DATA);
278$a1->add_data("abcdefgh" x 16384);
279$a1->close();
280$f1->close();
281
282$f1 = $ar->new_file("filename3");
283$a1 = $f1->new_attr($Amanda::Archive::AMAR_ATTR_GENERIC_DATA);
284$a1->add_data("abcdefgh" x 16384);
285$a1->close();
286$f1->close();
287
288$ar->close();
289close($fh);
290
291open($fh, "<", $arch_filename);
292$ar = Amanda::Archive->new(fileno($fh), "<");
293@res = ();
294my $fh1;
295open $fh1, ">/dev/null" || die("/dev/null");
296$ar->set_read_cb(
297    file_start => sub {
298	my ($user_data, $filenum, $filename) = @_;
299	push @res, ["file_start", @_ ];
300	if ($filename eq "filename1") {
301	    my $time_str = Amanda::MainLoop::timeout_source(500);
302	    $time_str->set_callback(sub {
303		$ar->read_to($filenum, $Amanda::Archive::AMAR_ATTR_GENERIC_DATA, fileno($fh1));
304		$ar->start_read();
305		$time_str->remove();
306	    });
307	    $ar->stop_read();
308	}
309	return "dog $filenum $filename";
310    },
311    file_finish => sub {
312	my ($user_data, $filenum, $filename) = @_;
313	push @res, [ "file_finish", @_ ];
314    },
315    16 => sub {
316	my ($user_data, $filenum, $file_data, $attrid,
317	    $attr_data, $data, $eoa, $truncated) = @_;
318	push @res, [ "frag", $user_data, $filenum, $file_data, $attrid, $attr_data, $eoa, $truncated ];
319    },
320    0 => sub {
321	my ($user_data, $filenum, $file_data, $attrid,
322	    $attr_data, $data, $eoa, $truncated) = @_;
323	push @res, [ "16", $user_data, $filenum, $file_data, $attrid, $attr_data, $eoa, $truncated ];
324    },
325    user_data => $user_data,
326    done => sub {
327	my ($error) = @_;
328	push @res, [ "done" , @_ ];
329	Amanda::MainLoop::quit();
330    }
331);
332Amanda::MainLoop::run();
333close $fh1;
334$ar->close();
335
336is_deeply([@res], [
337	[ 'file_start', $user_data, 1, 'filename1' ],
338	[ 'file_finish', $user_data, 'dog 1 filename1', 1, 0 ],
339	[ 'file_start', $user_data, 2, 'filename2' ],
340	[ 'frag', $user_data, 2, "dog 2 filename2", 16, undef, 0, 0 ],
341	[ 'frag', $user_data, 2, "dog 2 filename2", 16, 4, 1, 0 ],
342	[ 'file_finish', $user_data, 'dog 2 filename2', 2, 0 ],
343	[ 'file_start', $user_data, 3, 'filename3' ],
344	[ 'frag', $user_data, 3, "dog 3 filename3", 16, undef, 0, 0 ],
345	[ 'frag', $user_data, 3, "dog 3 filename3", 16, 8, 1, 0 ],
346	[ 'file_finish', $user_data, "dog 3 filename3", 3, 0 ],
347	[ 'done' ]
348], "buffering parameters parsed correctly")
349    or diag(Dumper(\@res));
350unlink($data_filename);
351unlink($arch_filename);
352
353
354