-config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF') or die("Could not load test config");
-
-# some variables we'll need
-my ($error, $slot, $device);
-
-# OK, let's get started with some simple stuff
-setup_changer <<'EOC';
-case "${1}" in
- -slot)
- case "${2}" in
- 1) echo "1 fake:1"; exit 0;;
- 2) echo "<ignored> slot 2 is empty"; exit 1;;
- 3) echo "<error> oh noes!"; exit 2;;
- 4) echo "1"; exit 0;; # test missing 'device' portion
- esac;;
- -reset) echo "reset ignored";;
- -eject) echo "eject ignored";;
- -clean) echo "clean ignored";;
- -label)
- case "${2}" in
- foo?bar) echo "1 ok"; exit 0;;
- *) echo "<error> bad label"; exit 1;;
- esac;;
- -info) echo "7 10 1 1"; exit 0;;
- -search)
- case "${2}" in
- TAPE?01) echo "5 fakedev"; exit 0;;
- *) echo "<error> not found"; exit 2;;
- esac;;
-esac
-EOC
-
-is_deeply([ Amanda::Changer::loadslot(1) ], [0, "1", "fake:1"],
- "A successful loadslot() returns the right stuff");
-
-($error, $slot, $device) = Amanda::Changer::loadslot(2);
-is($error, "slot 2 is empty", "A loadslot() with a benign error returns the right stuff");
-
-eval { Amanda::Changer::loadslot(3); };
-like($@, qr/.*oh noes!.*/, "A loadslot() with a serious error croaks");
-
-is_deeply([ Amanda::Changer::loadslot(4) ], [0, "1", undef],
- "a response without a device string returns undef");
-
-is_deeply([ Amanda::Changer::reset() ], [ 0, "reset" ],
- "reset() calls tapechanger -reset");
-is_deeply([ Amanda::Changer::eject() ], [ 0, "eject" ],
- "eject() calls tapechanger -eject");
-is_deeply([ Amanda::Changer::clean() ], [ 0, "clean" ],
- "clean() calls tapechanger -clean");
-
-is_deeply([ Amanda::Changer::label("foo bar") ], [ 0 ],
- "label('foo bar') calls tapechanger -label 'foo bar' (note spaces)");
-
-is_deeply([ Amanda::Changer::query() ], [ 0, 7, 10, 1, 1 ],
- "query() returns the correct values for a 4-value changer script");
-
-is_deeply([ Amanda::Changer::find("TAPE 01") ], [ 0, "5", "fakedev" ],
- "find on a searchable changer invokes -search");
-
-eval { Amanda::Changer::find("TAPE 02") };
-ok($@, "A searchable changer croaks when the label can't be found");
-
-# Now a simple changer that returns three values for -info
-setup_changer <<'EOC';
-case "${1}" in
- -info) echo "11 13 0"; exit 0;;
-esac
-EOC
-
-is_deeply([ Amanda::Changer::query() ], [ 0, 11, 13, 0, 0 ],
- "query() returns the correct values for a 4-value changer script");
-
-# set up 5 vtapes
-for (my $i = 0; $i < 5; $i++) {
- my $vtapedir = "$AMANDA_TMPDIR/chg-test-tapes/$i/data";
- if (-e $vtapedir) {
- rmtree($vtapedir)
- or die("Could not remove '$vtapedir'");
+
+my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
+if ($cfg_result != $CFGERR_OK) {
+ my ($level, @errors) = Amanda::Config::config_errors();
+ die(join "\n", @errors);
+}
+
+# check out the relevant changer properties
+my $chg = Amanda::Changer->new("mychanger");
+is($chg->{'config'}->get_property("testprop"), "testval",
+ "changer properties are correctly represented");
+
+# test loading by label
+{
+ my @labels;
+ my @reservations;
+ my ($getres, $rq_reserved, $relres);
+
+ $getres = make_cb('getres' => sub {
+ if (!@labels) {
+ return $rq_reserved->();
+ }
+
+ my $label = pop @labels;
+
+ $chg->load(label => $label,
+ set_current => ($label eq "TAPE-02"),
+ res_cb => sub {
+ my ($err, $res) = @_;
+ ok(!$err, "no error loading $label")
+ or diag($err);
+
+ # keep this reservation
+ push @reservations, $res if $res;
+
+ # and start on the next
+ $getres->();
+ });
+ });
+
+ $rq_reserved = make_cb(rq_reserved => sub {
+ # try to load an already-reserved volume
+ $chg->load(label => 'TAPE-00',
+ res_cb => sub {
+ my ($err, $res) = @_;
+ ok($err, "error when requesting already-reserved volume");
+ push @reservations, $res if $res;
+
+ $relres->();
+ });
+ });
+
+ $relres = make_cb('relres' => sub {
+ if (!@reservations) {
+ return Amanda::MainLoop::quit();
+ }
+
+ my $res = pop @reservations;
+ $res->release(finished_cb => sub {
+ my ($err) = @_;
+ die $err if $err;
+
+ $relres->();
+ });
+ });
+
+ # start the loop
+ @labels = ( 'TAPE-02', 'TAPE-00', 'TAPE-03' );
+ $getres->();
+ Amanda::MainLoop::run();
+
+ $relres->();
+ Amanda::MainLoop::run();
+
+ @labels = ( 'TAPE-00', 'TAPE-01' );
+ $getres->();
+ Amanda::MainLoop::run();
+
+ # explicitly release the reservations (without using the callback)
+ for my $res (@reservations) {
+ $res->release();
+ }
+}
+
+# test loading by slot
+{
+ my ($start, $first_cb, $released, $second_cb, $quit);
+ my $slot;
+
+ # reserves the current slot
+ $start = make_cb('start' => sub {
+ $chg->load(res_cb => $first_cb, relative_slot => "current");
+ });
+
+ # gets a reservation for the "current" slot
+ $first_cb = make_cb('first_cb' => sub {
+ my ($err, $res) = @_;
+ die $err if $err;
+
+ is($res->{'this_slot'}, 2,
+ "'current' slot loads slot 2");
+ is($res->{'device'}->device_name, "null:slot-2",
+ "..device is correct");
+
+ $slot = $res->{'this_slot'};
+ $res->release(finished_cb => $released);
+ });
+
+ $released = make_cb(released => sub {
+ my ($err) = @_;
+
+ $chg->load(res_cb => $second_cb, relative_slot => 'next',
+ slot => $slot, set_current => 1);
+ });
+
+ # gets a reservation for the "next" slot
+ $second_cb = make_cb('second_cb' => sub {
+ my ($err, $res) = @_;
+ die $err if $err;
+
+ is($res->{'this_slot'}, 3,
+ "next slot loads slot 3");
+ is($chg->{'curslot'}, 3,
+ "..which is also now the current slot");
+
+ $res->release(finished_cb => $quit);
+ });
+
+ $quit = make_cb(quit => sub {
+ my ($err) = @_;
+ die $err if $err;
+
+ Amanda::MainLoop::quit();
+ });
+
+ $start->();
+ Amanda::MainLoop::run();
+}
+
+# test set_label
+{
+ my ($start, $load1_cb, $set_cb, $released, $load2_cb, $released2, $load3_cb);
+ my $res;
+
+ # load TAPE-00
+ $start = make_cb('start' => sub {
+ $chg->load(res_cb => $load1_cb, label => "TAPE-00");
+ });
+
+ # rename it to TAPE-99
+ $load1_cb = make_cb('load1_cb' => sub {
+ (my $err, $res) = @_;
+ die $err if $err;
+
+ pass("loaded TAPE-00");
+ $res->set_label(label => "TAPE-99", finished_cb => $set_cb);
+ });
+
+ $set_cb = make_cb('set_cb' => sub {
+ my ($err) = @_;
+
+ $res->release(finished_cb => $released);
+ });
+
+ # try to load TAPE-00
+ $released = make_cb('released' => sub {
+ my ($err) = @_;
+ die $err if $err;
+
+ pass("relabeled TAPE-00 to TAPE-99");
+ $chg->load(res_cb => $load2_cb, label => "TAPE-00");
+ });
+
+ # try to load TAPE-99
+ $load2_cb = make_cb('load2_cb' => sub {
+ (my $err, $res) = @_;
+ ok($err, "loading TAPE-00 is now an error");
+
+ $chg->load(res_cb => $load3_cb, label => "TAPE-99");
+ });
+
+ # check result
+ $load3_cb = make_cb('load3_cb' => sub {
+ (my $err, $res) = @_;
+ die $err if $err;
+
+ pass("but loading TAPE-99 is ok");
+
+ $res->release(finished_cb => $released2);
+ });
+
+ $released2 = make_cb(released2 => sub {
+ my ($err) = @_;
+ die $err if $err;
+
+ Amanda::MainLoop::quit();
+ });
+
+ $start->();
+ Amanda::MainLoop::run();
+}
+
+# test reset and clean and inventory
+sub test_simple {
+ my ($finished_cb) = @_;
+
+ my $steps = define_steps
+ cb_ref => \$finished_cb;
+
+ step do_reset => sub {
+ $chg->reset(finished_cb => sub {
+ is($chg->{'curslot'}, 0,
+ "reset() resets to slot 0");
+ $steps->{'do_clean'}->();
+ });
+ };
+
+ step do_clean => sub {
+ $chg->clean(finished_cb => sub {
+ ok($chg->{'clean'}, "clean 'cleaned' the changer");
+ $steps->{'do_inventory'}->();
+ });
+ };
+
+ step do_inventory => sub {
+ $chg->inventory(inventory_cb => sub {
+ is_deeply($_[1], [ {
+ slot => 1,
+ empty => 0,
+ label => 'TAPE-99',
+ barcode => '09385A',
+ reserved => 0,
+ import_export => 0,
+ loaded_in => undef,
+ }], "inventory returns an inventory");
+ $finished_cb->();
+ });
+ };
+}
+test_simple(\&Amanda::MainLoop::quit);
+Amanda::MainLoop::run();
+
+# test info
+{
+ my ($do_info, $check_info, $do_info_err, $check_info_err);
+
+ $do_info = make_cb('do_info' => sub {
+ $chg->info(info_cb => $check_info,
+ info => [ 'num_slots' ]);
+ });
+
+ $check_info = make_cb('check_info' => sub {
+ my ($err, %results) = @_;
+ die($err) if $err;
+ is_deeply(\%results, { 'num_slots' => 13 },
+ "info() works");
+ $do_info_err->();
+ });
+
+ $do_info_err = make_cb('do_info_err' => sub {
+ $chg->info(info_cb => $check_info_err,
+ info => [ 'mkerror1', 'mkerror2' ]);
+ });
+
+ $check_info_err = make_cb('check_info_err' => sub {
+ my ($err, %results) = @_;
+ is($err,
+ "While getting info key 'mkerror1': err1; While getting info key 'mkerror2': err2",
+ "info errors are handled correctly");
+ is($err->{'type'}, 'failed', "error has type 'failed'");
+ ok($err->failed, "\$err->failed is true");
+ ok(!$err->fatal, "\$err->fatal is false");
+ is($err->{'reason'}, 'unknown', "\$err->{'reason'} is 'unknown'");
+ ok($err->unknown, "\$err->unknown is true");
+ ok(!$err->notimpl, "\$err->notimpl is false");
+ Amanda::MainLoop::quit();
+ });
+
+ $do_info->();
+ Amanda::MainLoop::run();
+}
+
+# Test the various permutations of configuration setup, with a patched
+# _new_from_uri so we can monitor the result
+sub my_new_from_uri {
+ my ($uri, $cc, $name) = @_;
+ return $uri if (ref $uri and $uri->isa("Amanda::Changer::Error"));
+ return [ $uri, $cc? "cc" : undef ];
+}
+*saved_new_from_uri = *Amanda::Changer::_new_from_uri;
+*Amanda::Changer::_new_from_uri = *my_new_from_uri;
+
+sub loadconfig {
+ my ($global_tapedev, $global_tpchanger, $defn_tpchanger, $custom_defn) = @_;
+
+ $testconf = Installcheck::Config->new();
+
+ if (defined($global_tapedev)) {
+ $testconf->add_param('tapedev', "\"$global_tapedev\"")
+ }
+
+ if (defined($global_tpchanger)) {
+ $testconf->add_param('tpchanger', "\"$global_tpchanger\"")
+ }
+
+ if (defined($defn_tpchanger)) {
+ $testconf->add_changer("mychanger", [
+ 'tpchanger' => "\"$defn_tpchanger\"",
+ ]);
+ }
+
+ if (defined($custom_defn)) {
+ $testconf->add_changer("customchanger", $custom_defn);
+ $testconf->add_param('tpchanger', '"customchanger"');
+ }
+
+ $testconf->write();
+
+ my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
+ if ($cfg_result != $CFGERR_OK) {
+ my ($level, @errors) = Amanda::Config::config_errors();
+ die(join "\n", @errors);