Imported Upstream version 2.6.1
[debian/amanda] / installcheck / Amanda_MainLoop.pl
diff --git a/installcheck/Amanda_MainLoop.pl b/installcheck/Amanda_MainLoop.pl
new file mode 100644 (file)
index 0000000..c85e980
--- /dev/null
@@ -0,0 +1,395 @@
+# Copyright (c) 2005-2008 Zmanda Inc.  All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 as published
+# by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# Contact information: Zmanda Inc, 465 S Mathlida Ave, Suite 300
+# Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
+
+use Test::More tests => 17;
+use strict;
+use warnings;
+use POSIX qw(WIFEXITED WEXITSTATUS EINTR);
+
+use lib "@amperldir@";
+use Amanda::MainLoop qw( :GIOCondition );
+
+{
+    my $global = 0;
+
+    my $to = Amanda::MainLoop::timeout_source(200);
+    $to->set_callback(sub { 
+       # ignore $src argument
+       if (++$global >= 3) {
+           $to->remove();
+           Amanda::MainLoop::quit();
+       }
+    });
+
+    Amanda::MainLoop::run();
+    is($global, 3, "Timeout source works, calls back repeatedly (using a closure)");
+}
+
+{
+    my $global = 0;
+
+    my $to = Amanda::MainLoop::timeout_source(200);
+    $to->set_callback(sub { 
+       my ($src) = @_;
+       if (++$global >= 3) {
+           $src->remove();
+           Amanda::MainLoop::quit();
+       }
+    });
+    $to = undef; # remove the lexical reference to the source
+
+    Amanda::MainLoop::run();
+    is($global, 3, "Timeout source works, calls back repeatedly (no external reference to the source)");
+}
+
+{
+    my $global = 0;
+
+    my $id = Amanda::MainLoop::idle_source(5);
+    $id->set_callback(sub { 
+       my ($src) = @_;
+       if (++$global >= 30) {
+           $src->remove();
+           Amanda::MainLoop::quit();
+       }
+    });
+
+    Amanda::MainLoop::run();
+    is($global, 30, "Idle source works, calls back repeatedly");
+    $id->remove();
+}
+
+{
+    my $global = 0;
+
+    # to1 is removed before it runs, so it should never
+    # execute its callback
+    my $to1 = Amanda::MainLoop::timeout_source(10);
+    $to1->set_callback(sub { ++$global; });
+    $to1->remove();
+
+    my $to2 = Amanda::MainLoop::timeout_source(300);
+    $to2->set_callback(sub { Amanda::MainLoop::quit(); });
+
+    Amanda::MainLoop::run();
+    is($global, 0, "A remove()d source doesn't call back");
+
+    $to2->remove();
+}
+
+{
+    my $global = 0;
+
+    my $pid = fork();
+    if ($pid == 0) {
+       ## child
+       sleep(1);
+       exit(9);
+    }
+
+    ## parent
+
+    my $cw = Amanda::MainLoop::child_watch_source($pid);
+    $cw->set_callback(sub {
+       my ($src, $got_pid, $got_status) = @_;
+       Amanda::MainLoop::quit();
+       $src->remove();
+
+       if ($got_pid != $pid) {
+           diag("Got pid $got_pid, but expected $pid");
+           return;
+       }
+       if (!WIFEXITED($got_status)) {
+           diag("Didn't get an 'exited' status");
+           return;
+       }
+       if (WEXITSTATUS($got_status) != 9) {
+           diag("Didn't get exit status 9");
+           return;
+       }
+       $global = 1;
+    });
+
+    my $to = Amanda::MainLoop::timeout_source(3000);
+    $to->set_callback(sub {
+       my ($src) = @_;
+       $global = 7;
+
+       $src->remove();
+       Amanda::MainLoop::quit();
+    });
+
+    Amanda::MainLoop::run();
+    is($global, 1, "Child watch detects a dead child");
+
+    $cw->remove();
+    $to->remove();
+}
+
+{
+    my $global = 0;
+
+    my $pid = fork();
+    if ($pid == 0) {
+       ## child
+       exit(11);
+    }
+
+    ## parent
+
+    sleep(1);
+    my $cw = Amanda::MainLoop::child_watch_source($pid);
+    $cw->set_callback(sub {
+       my ($src, $got_pid, $got_status) = @_;
+       Amanda::MainLoop::quit();
+       $src->remove();
+
+       if ($got_pid != $pid) {
+           diag("Got pid $got_pid, but expected $pid");
+           return;
+       }
+       if (!WIFEXITED($got_status)) {
+           diag("Didn't get an 'exited' status");
+           return;
+       }
+       if (WEXITSTATUS($got_status) != 11) {
+           diag("Didn't get exit status 11");
+           return;
+       }
+       $global = 1;
+    });
+
+    my $to = Amanda::MainLoop::timeout_source(3000);
+    $to->set_callback(sub { $global = 7; Amanda::MainLoop::quit(); });
+
+    Amanda::MainLoop::run();
+    is($global, 1, "Child watch detects a dead child that dies before the callback is set");
+
+    $cw->remove();
+    $to->remove();
+}
+
+{
+    my $global = 0;
+    my ($readinfd, $writeinfd) = POSIX::pipe();
+    my ($readoutfd, $writeoutfd) = POSIX::pipe();
+
+    my $pid = fork();
+    if ($pid == 0) {
+       ## child
+
+       my $data;
+
+       POSIX::close($readinfd);
+       POSIX::close($writeoutfd);
+
+       # the read()s here are to synchronize with our parent; the
+       # results are ignored.
+       POSIX::read($readoutfd, $data, 1024);
+       POSIX::write($writeinfd, "HELLO\n", 6);
+       POSIX::read($readoutfd, $data, 1024);
+       POSIX::write($writeinfd, "WORLD\n", 6);
+       POSIX::read($readoutfd, $data, 1024);
+       exit(33);
+    }
+
+    ## parent
+
+    POSIX::close($writeinfd);
+    POSIX::close($readoutfd);
+
+    my @events;
+
+    my $to = Amanda::MainLoop::timeout_source(200);
+    my $times = 3;
+    $to->set_callback(sub {
+       push @events, "time";
+       POSIX::write($writeoutfd, "A", 1); # wake up the child
+       if (--$times == 0) {
+           $to->remove();
+       }
+    });
+
+    my $cw = Amanda::MainLoop::child_watch_source($pid);
+    $cw->set_callback(sub {
+       my ($src, $got_pid, $got_status) = @_;
+       $cw->remove();
+       Amanda::MainLoop::quit();
+
+       push @events, "died";
+    });
+
+    my $fd = Amanda::MainLoop::fd_source($readinfd, $G_IO_IN | $G_IO_HUP);
+    $fd->set_callback(sub {
+       my $str;
+       if (POSIX::read($readinfd, $str, 1024) == 0) {
+           # EOF
+           POSIX::close($readinfd);
+           POSIX::close($writeoutfd);
+           $fd->remove();
+           return;
+       }
+       chomp $str;
+       push @events, "read $str";
+    });
+
+    Amanda::MainLoop::run();
+    $to->remove();
+    $cw->remove();
+    $fd->remove();
+
+    is_deeply([ @events ],
+       [ "time", "read HELLO", "time", "read WORLD", "time", "died" ],
+       "fd source works for reading from a file descriptor");
+}
+
+# see if a "looping" callback with some closure values works.  This test teased
+# out some memory corruption bugs once upon a time.
+
+{
+    my $completed = 0;
+    sub loop {
+       my ($finished_cb) = @_;
+       my $time = 700;
+       my $to;
+
+       my $cb;
+       $cb = sub {
+           $time -= 300;
+           $to->remove();
+           if ($time <= 0) {
+               $finished_cb->();
+           } else {
+               $to = Amanda::MainLoop::timeout_source($time);
+               $to->set_callback($cb);
+           }
+       };
+       $to = Amanda::MainLoop::timeout_source($time);
+       $to->set_callback($cb);
+    };
+    loop(sub {
+       $completed = 1;
+       Amanda::MainLoop::quit();
+    });
+    Amanda::MainLoop::run();
+    is($completed, 1, "looping construct terminates with a callback");
+}
+
+# Make sure that a die() in a callback correctly kills the process.  Such
+# a die() skips the usual Perl handling, so an eval { } won't do -- we have
+# to fork a child.
+{
+    my $global = 0;
+    my ($readfd, $writefd) = POSIX::pipe();
+
+    my $pid = fork();
+    if ($pid == 0) {
+       ## child
+
+       my $data;
+
+       # fix up the file descriptors to hook fd 2 (stderr) to
+       # the pipe
+       POSIX::close($readfd);
+       POSIX::dup2($writefd, 2);
+       POSIX::close($writefd);
+
+       # and now die in a callback, using an eval {} in case the
+       # exception propagates out of the MainLoop run()
+       my $src = Amanda::MainLoop::timeout_source(10);
+       $src->set_callback(sub { die("Oh, the humanity"); });
+       eval { Amanda::MainLoop::run(); };
+       exit(33);
+    }
+
+    ## parent
+
+    POSIX::close($writefd);
+
+    # read from the child and wait for it to die.  There's no
+    # need to use MainLoop here.
+    my $str;
+    while (!defined(POSIX::read($readfd, $str, 1024))) {
+       # we may be interrupted by a SIGCHLD; keep going
+       next if ($! == EINTR);
+       die ("POSIX::read failed: $!");
+    }
+    POSIX::close($readfd);
+    waitpid($pid, 0);
+
+    ok($? != 33 && $? != 0, "die() in a callback exits with an error condition");
+    like($str, qr/Oh, the humanity/, "..and displays die message on stderr");
+}
+
+# test misc. management of sources.  Ideally it won't crash :)
+
+my $src = Amanda::MainLoop::idle_source(1);
+$src->set_callback(sub { 1; });
+$src->set_callback(sub { 1; });
+$src->set_callback(sub { 1; });
+pass("Can call set_callback a few times on the same source");
+
+$src->remove();
+$src->remove();
+pass("Calling remove twice is ok");
+
+{
+    my ($cb1, $cb2);
+    my $gothere = 0;
+
+    $cb1 = sub {
+       my ($a, $b) = @_;
+       ok(Amanda::MainLoop::is_running(),
+           "call_later waits until mainloop runs");
+       is($a+$b, 10,
+           "call_later passes arguments correctly");
+       Amanda::MainLoop::call_later($cb2);
+       Amanda::MainLoop::quit();
+    };
+
+    $cb2 = sub {
+       $gothere = 1;
+    };
+
+    ok(!Amanda::MainLoop::is_running(), "main loop is correctly recognized as not running");
+    Amanda::MainLoop::call_later($cb1, 7, 3);
+    Amanda::MainLoop::run();
+    ok($gothere, "call_later while already running calls immediately");
+
+    my @actions = ();
+
+    $cb1 = sub {
+        push @actions, "cb1 start";
+       Amanda::MainLoop::call_later($cb2, "hello");
+        push @actions, "cb1 end";
+    };
+
+    $cb2 = sub {
+       my ($greeting) = @_;
+
+        push @actions, "cb2 start $greeting";
+       Amanda::MainLoop::quit();
+        push @actions, "cb2 end";
+    };
+
+    Amanda::MainLoop::call_later($cb1);
+    Amanda::MainLoop::run();
+    is_deeply([ @actions ],
+              [ "cb1 start", "cb1 end", "cb2 start hello", "cb2 end" ],
+              "call_later doesn't call its argument immediately");
+}