2 # Copyright (c) 2005-2008 Zmanda Inc. All Rights Reserved.
4 # This program is free software; you can redistribute it and/or modify it
5 # under the terms of the GNU General Public License version 2 as published
6 # by the Free Software Foundation.
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
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
17 # Contact information: Zmanda Inc, 465 S Mathlida Ave, Suite 300
18 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
20 package Installcheck::Run;
24 Installcheck::Run - utilities to set up and run amanda dumps and restores
28 use Installcheck::Run;
30 my $testconf = Installcheck::Run::setup();
31 # make any modifications you'd like to the configuration
34 ok(Installcheck::Run::run('amdump', 'TESTCONF'), "amdump completes successfully");
36 # It's generally polite to clean up your mess, although the test
37 # framework will clean up if your tests crash
38 Installcheck::Run::cleanup();
41 skip "Expect.pm not installed", 7
42 unless $Installcheck::Run::have_expect;
44 my $exp = Installcheck::Run::run_expect('amflush', 'TESTCONF');
51 High-level tests generally depend on a full-scale run of Amanda --
52 a fairly messy project. This module simplifies that process by
53 abstracting away the mess. It takes care of:
56 =item Setting up a holding disk;
57 =item Setting up several vtapes; and
58 =item Setting up a DLE pointing to a reasonably-sized subdirectory of the build directory.
61 Most of this magic is in C<setup()>, which returns a configuration
62 object from C<Installcheck::Config>, allowing the test to
63 modify that configuration before writing it out. The hostname
64 for the DLE is "localhost", and the disk name is available in
65 C<Installcheck::Run::diskname>.
67 This module also provides a convenient Perlish interface for running
68 Amanda commands: C<run($app, $args, ...)>. This function uses the
69 appropriate path to get to $app, and returns true if the application
70 exited with a status of zero. The stdout and stderr of the application
71 are left in C<Installcheck::Run::stdout> and C<stderr>, respectively.
73 To check that a run is successful, and return its stdout (chomped), use
74 C<run_get($app, $args, ...)>. This function returns C<''> if the application
75 returns a nonzero exit status. Similarly, C<run_err> checks that a run returns
76 a nonzero exit status, and then returns its stderr, chomped. If you need a
77 different output file, use a bare C<run> followed by C<get_stderr> or
78 C<get_stdout> as needed.
80 C<run> and friends can be used whether or not this module's C<setup>
83 Finally, C<cleanup()> cleans up from a run, deleting all backed-up
84 data, holding disks, and configuration. It's just good-neighborly
85 to call this before your test script exits.
89 This module sets up a configuration with three 10M vtapes, replete with
90 the proper vtape directories. These are controlled by C<chg-disk>.
91 The tapes are not labeled, and C<label_new_tapes> is not set by
92 default, although C<labelstr> is set to C<TESTCONF[0-9][0-9]>.
94 The vtapes are created in a subdirectory of C<AMANDA_TMPDIR> for ease of later
95 deletion. The subdirectory is available from C<vtape_dir($slot)>.
96 C<load_vtape($slot)> will "load" the indicated slot just like chg-disk would,
97 and return the resulting path.
101 The holding disk is also stored under C<AMANDA_TMPDIR>. It is a 15M
102 holding disk, with a chunksize of 1M (to help exercise the chunker).
106 The disklist is empty by default. Use something like the following
109 $testconf->add_dle("localhost $diskname installcheck-test");
111 The C<installcheck-test> dumptype specifies
116 but of course, it can be modified by the test module.
118 =head2 INTERACTIVE APPLICATIONS
120 This package provides a rudimentary wrapper around C<Expect.pm>, which is not
121 typically included in a perl installation. Consult C<$have_expect> to see if
122 this module is installed, and skip any Expect-based tests if it is not.
124 Otherwise, C<run_expect> takes arguments just like C<run>, but returns an Expect
125 object which you can use as you would like.
129 If your test runs 'amdump', a nonzero exit status may not be very helpful. The
130 function C<amdump_diag> will attempt to figure out what went wrong and display
131 useful information for the user via diag(). If it is given an argument, then
132 it will C<BAIL_OUT> with that message, causing L<Test::Harness> to stop running
133 tests. Otherwise, it will simply die(), which will only terminate this
134 particular test script.
138 use Installcheck::Config;
142 use Cwd qw(abs_path getcwd);
145 use Amanda::Config qw( :init );
150 @EXPORT_OK = qw(setup
153 $diskname $stdout $stderr
155 @EXPORT = qw(exp_continue exp_continue_timeout);
168 *exp_continue = *ignore;
169 *exp_continue_timeout = *ignore;
175 # diskname is device-src, which, when full of object files, is about 4M in
176 # my environment. Consider creating a directory full of a configurable amount
177 # of junk and pointing to that, to eliminate a potential point of variation in
179 our $diskname = abs_path(getcwd() . "/../device-src");
182 my $taperoot = "$AMANDA_TMPDIR/installcheck-vtapes";
183 my $holdingdir ="$AMANDA_TMPDIR/installcheck-holding";
186 my $testconf = Installcheck::Config->new();
188 setup_vtapes($testconf, 3);
189 setup_holding($testconf, 25);
190 setup_disklist($testconf);
196 my ($testconf, $ntapes) = @_;
201 # make each of the tape directories
202 for (my $i = 1; $i < $ntapes+1; $i++) {
203 my $tapepath = "$taperoot/slot$i";
209 # set up the appropriate configuration
210 $testconf->add_param("tapedev", "\"file:$taperoot\"");
211 $testconf->add_param("tpchanger", "\"chg-disk\"");
212 $testconf->add_param("changerfile", "\"$CONFIG_DIR/TESTCONF/ignored-filename\"");
213 $testconf->add_param("labelstr", "\"TESTCONF[0-9][0-9]\"");
215 # this overwrites the existing TEST-TAPE tapetype
216 $testconf->add_tapetype('TEST-TAPE', [
217 'length' => '20 mbytes',
218 'filemark' => '4 kbytes',
223 my ($testconf, $mbytes) = @_;
225 if (-d $holdingdir) {
230 $testconf->add_holdingdisk("hd1", [
231 'directory' => "\"$holdingdir\"",
232 'use' => "$mbytes mbytes",
233 'chunksize' => "1 mbyte",
240 $testconf->add_dumptype("installcheck-test", [
242 'compress' => 'none',
243 'program' => '"GNUTAR"',
249 my $tapepath = "$taperoot/slot$slot";
255 # make the data/ symlink from our taperoot
256 unlink("$taperoot/data");
257 symlink(vtape_dir($slot), "$taperoot/data")
258 or die("Could not create 'data' symlink: $!");
266 my $errtempfile = "$AMANDA_TMPDIR/stderr$$.out";
268 # use a temporary file for error output -- this eliminates synchronization
269 # problems between reading stderr and stdout
270 local (*INFH, *OUTFH, *ERRFH);
271 open(ERRFH, ">", $errtempfile);
273 my $pid = IPC::Open3::open3("INFH", "OUTFH", ">&ERRFH",
274 "$sbindir/$app", @args);
276 # immediately close the child's stdin
279 # read from stdout until it's closed
280 $stdout = do { local $/; <OUTFH> };
283 # and wait for the kid to die
284 waitpid $pid, 0 or croak("Error waiting for child process to die: $@");
288 # fetch stderr from the temporary file
289 open(ERRFH, "<", "$errtempfile") or croak("Could not open '$errtempfile'");
290 $stderr = do { local $/; <ERRFH> };
292 unlink($errtempfile);
294 # and return true if the exit status was zero
295 return ($status >> 8) == 0;
300 Test::More::diag("run unexpectedly failed; no output to compare");
310 Test::More::diag("run unexpectedly succeeded; no output to compare");
332 if (-d $holdingdir) {
341 die "Expect.pm not found" unless $have_expect;
343 my $exp = Expect->new("$sbindir/$app", @args);
351 # try running amreport
352 my $report = "failure-report.txt";
354 my @logfiles = <$CONFIG_DIR/TESTCONF/log/log.*>;
356 run('amreport', 'TESTCONF', '-f', $report, '-l', $logfiles[$#logfiles]);
358 open(my $fh, "<", $report) or return;
359 for my $line (<$fh>) {
360 Test::More::diag($line);
367 # maybe there was a config error
368 config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF");
369 my ($cfgerr_level, @cfgerr_errors) = config_errors();
370 if ($cfgerr_level >= $CFGERR_WARNINGS) {
371 foreach (@cfgerr_errors) {
372 Test::More::diag($_);
378 Test::More::diag("no amreport available, and no config errors");
382 Test::More::BAIL_OUT($msg);
384 die("amdump failed; cannot continue");