#!/usr/bin/perl
use strict;
use warnings;
use parent qw(Test::Class);
use Test::More;
use Test::Fatal;
use lib 'lib';
use Config;
my $perl = $Config{perlpath};
use t::Utils;
use IO::Handle;
use Ubic::Daemon qw(start_daemon stop_daemon check_daemon);
sub setup :Tests(setup) {
rebuild_tfiles;
}
sub basic :Tests(8) {
start_daemon({
bin => "sleep 10",
pidfile => "tfiles/pid",
stdout => 'tfiles/stdout',
stderr => 'tfiles/stderr',
ubic_log => 'tfiles/ubic.log',
});
ok(check_daemon("tfiles/pid"), 'daemon is running');
ok
exception {
start_daemon({
bin => "sleep 10",
pidfile => "tfiles/pid",
stdout => 'tfiles/stdout',
stderr => 'tfiles/stderr',
ubic_log => 'tfiles/ubic.log',
});
},
'start_daemon fails if daemon is already started';
stop_daemon('tfiles/pid');
ok(!(check_daemon("tfiles/pid")), 'daemon is not running');
start_daemon({
bin => "sleep 2",
pidfile => "tfiles/pid",
stdout => 'tfiles/stdout',
stderr => 'tfiles/stderr',
ubic_log => 'tfiles/ubic.log',
});
ok(check_daemon("tfiles/pid"), 'daemon is running again');
sleep 4;
ok(!(check_daemon("tfiles/pid")), 'daemon stopped after several seconds');
start_daemon({
function => sub { sleep 2 },
name => 'callback-daemon',
pidfile => "tfiles/pid",
stdout => 'tfiles/stdout',
stderr => 'tfiles/stderr',
ubic_log => 'tfiles/ubic.log',
});
ok(check_daemon("tfiles/pid"), 'daemon in callback mode started');
sleep 4;
ok(!(check_daemon("tfiles/pid")), 'callback daemon stopped after several seconds');
my $error = exception {
start_daemon({
function => sub { sleep 2 },
name => 'abc',
stdout => 'tfiles/non-existent/forbidden.log',
pidfile => 'tfiles/pid',
})
};
like $error, qr{\QError: Can't write to 'tfiles/non-existent/forbidden.log'\E}, 'start_daemon reports correct errrors';
}
sub revive_after_kill_9 :Tests(4) {
start_daemon({
bin => [$perl, 't/bin/locking-daemon'],
pidfile => 'tfiles/pid',
stdout => 'tfiles/stdout',
stderr => 'tfiles/stderr',
ubic_log => 'tfiles/ubic.log',
});
ok(check_daemon("tfiles/pid"), 'daemon started');
chomp(my $piddata = slurp('tfiles/pid/pid'));
my ($pid) = $piddata =~ /pid\s+(\d+)/ or die "Unknown pidfile content '$piddata'";
kill -9 => $pid;
sleep 1;
ok(!check_daemon("tfiles/pid", { quiet => 1 }), 'ubic-guardian is dead');
start_daemon({
bin => [$perl, 't/bin/locking-daemon'],
pidfile => 'tfiles/pid',
stdout => 'tfiles/stdout',
stderr => 'tfiles/stderr',
ubic_log => 'tfiles/ubic.log',
});
sleep 1;
ok(check_daemon("tfiles/pid"), 'daemon started again');
stop_daemon('tfiles/pid');
ok(!check_daemon("tfiles/pid"), 'daemon stopped');
}
sub term_timeout :Tests(5) {
start_daemon({
function => sub {
$SIG{TERM} = sub {
print "sigterm caught\n";
STDOUT->flush;
exit;
};
sleep 100;
},
name => 'abc',
stdout => 'tfiles/kill_default.log',
pidfile => 'tfiles/pid',
ubic_log => 'tfiles/ubic.term.log',
});
sleep 1;
stop_daemon('tfiles/pid');
unless( is(slurp('tfiles/kill_default.log'), "sigterm caught\n", 'default kill signal is SIGTERM - log written') ) {
# something is wrong
# this diag can look ugly, but it's the easiest way to figure out what's wrong with some cpantesters
diag(slurp('tfiles/ubic.term.log'));
}
start_daemon({
function => sub {
$SIG{TERM} = sub {
print "sigterm caught\n";
exit;
};
sleep 100;
},
name => 'abc',
stdout => 'tfiles/kill_zero_timeout.log',
pidfile => 'tfiles/pid',
ubic_log => 'tfiles/ubic.term.log',
term_timeout => 0,
});
sleep 1;
stop_daemon('tfiles/pid');
is(slurp('tfiles/kill_zero_timeout.log'), "", 'when term_timeout is 0, SIGKILL is sent immediately');
start_daemon({
function => sub {
$SIG{TERM} = sub {
$|++;
print "sigterm caught\n";
exit;
};
sleep 100;
},
name => 'abc',
stdout => 'tfiles/kill_term.log',
pidfile => 'tfiles/pid',
ubic_log => 'tfiles/ubic.term.log',
term_timeout => 2,
});
sleep 1;
stop_daemon('tfiles/pid');
is(slurp('tfiles/kill_term.log'), "sigterm caught\n", 'process caught SIGTERM and written something in log');
start_daemon({
function => sub {
$SIG{TERM} = sub {
sleep 4;
print "sigterm caught\n";
exit;
};
sleep 100;
},
name => 'abc',
stdout => 'tfiles/kill_4.log',
pidfile => 'tfiles/pid',
ubic_log => 'tfiles/ubic.term.log',
term_timeout => 2,
});
sleep 1;
stop_daemon('tfiles/pid');
is(slurp('tfiles/kill_4.log'), '', 'process caught SIGTERM but was too slow to do anything about it');
my $error = exception {
start_daemon({
function => sub {
$SIG{TERM} = sub {
print "sigterm caught\n";
exit;
};
sleep 100;
},
name => 'abc',
stdout => 'tfiles/kill_segv.log',
pidfile => 'tfiles/pid',
ubic_log => 'tfiles/ubic.term.log',
term_timeout => 'abc',
})
};
like $error, qr/did not pass regex check/, 'term_timeout values are limited to integers';
}
sub stop_daemon_options :Tests(4) {
my $start = sub {
start_daemon({
function => sub {
$SIG{TERM} = 'IGNORE'; # ubic-guardian will send sigterm, and we want it to fail
sleep 100;
},
name => 'abc',
pidfile => 'tfiles/pid',
ubic_log => 'tfiles/ubic.term.log',
term_timeout => 3,
});
sleep 1;
};
$start->();
is(stop_daemon('tfiles/pid'), 'stopped', 'stop with large enough timeout is ok');
$start->();
my $error;
$error = exception {
stop_daemon('tfiles/pid', { timeout => 1 });
};
like $error, qr/failed to stop daemon/, 'stop with small timeout fails';
is
stop_daemon('tfiles/pid', { timeout => 5 }),
'stopped',
'start and stop with large enough timeout is ok';
$error = exception {
stop_daemon('tfiles/pid', { timeout => 'abc' });
};
like $error, qr/did not pass regex check/, 'stop with invalid timeout fails parameters validation';
}
sub stop_daemon_params_validation :Tests(2) {
ok
not(exception {
stop_daemon('aeuklryaweur')
}),
'stop_daemon with non-existing pidfile is ok';
ok
exception {
stop_daemon({ pidfile => 'auerawera' })
},
'calling stop_daemon with invalid parameters is wrong';
}
sub log_sigterm :Test {
start_daemon({
bin => "sleep 10",
pidfile => "tfiles/pid",
ubic_log => 'tfiles/ubic.log',
});
stop_daemon('tfiles/pid');
my $log = slurp('tfiles/ubic.log');
like($log, qr/daemon \d+ exited by sigterm$/m);
}
sub log_code_zero :Test {
start_daemon({
bin => ['perl', '-le', '$SIG{TERM} = sub { print "term"; exit }; sleep 10'],
pidfile => "tfiles/pid",
stdout => 'tfiles/log',
stderr => 'tfiles/err.log',
ubic_log => 'tfiles/ubic.log',
});
sleep 1;
stop_daemon('tfiles/pid');
my $log = slurp('tfiles/ubic.log');
like($log, qr/daemon \d+ exited$/m, 'exit voluntarily');
}
sub log_code_nonzero :Test {
start_daemon({
bin => ['perl', '-e', 'sleep 1; exit 3'],
pidfile => "tfiles/pid",
ubic_log => 'tfiles/ubic.log',
});
sleep 2;
my $log = slurp('tfiles/ubic.log');
like($log, qr/daemon \d+ failed, exit code 3$/m, 'exit with non-zero code');
}
sub log_exit_immediately :Test {
# there are two options:
# 1) daemon exits before ubic-guardian finishes its initialization; in this case, start_daemon will throw an exception
# 2) ubic-guardian finishes its initialization and then daemon exits; in this case, we check for ubic.log
my $error = exception {
start_daemon({
bin => ['perl', '-e', 'exit 3'],
pidfile => "tfiles/pid",
ubic_log => 'tfiles/ubic.log',
});
};
if ($error) {
like($error, qr/daemon exited immediately/, 'daemon exits immediately, before guardian initialization');
}
else {
sleep 1;
my $log = slurp('tfiles/ubic.log');
like($log, qr/daemon \d+ failed, exit code 3$/m, 'daemon exits immediately, after guardian initialization');
}
}
sub log_sigkill :Test {
start_daemon({
bin => ['perl', '-e', '$SIG{TERM} = "IGNORE"; sleep 30'],
pidfile => "tfiles/pid",
ubic_log => 'tfiles/ubic.log',
term_timeout => 1,
});
sleep 1;
stop_daemon('tfiles/pid');
my $log = slurp('tfiles/ubic.log');
like($log, qr/daemon \d+ probably killed by SIGKILL$/m, 'exit via sigkill');
}
sub log_external_signal :Test {
start_daemon({
bin => ['perl', '-e', '$SIG{TERM} = "IGNORE"; sleep 30'],
pidfile => "tfiles/pid",
ubic_log => 'tfiles/ubic.log',
term_timeout => 1,
});
sleep 1;
my $status = check_daemon('tfiles/pid');
kill 9 => $status->pid;
sleep 1;
stop_daemon('tfiles/pid');
my $log = slurp('tfiles/ubic.log');
like($log, qr/daemon \d+ failed with signal KILL \(9\)$/m, 'exit via signal to daemon');
}
sub start_hook :Tests(2) {
start_daemon({
bin => ['perl', '-e', 'use IO::Handle; print "foo=$ENV{FOO}\n"; STDOUT->flush; sleep 3' ],
pidfile => "tfiles/pid",
stdout => 'tfiles/log',
start_hook => sub {
$ENV{FOO} = 5;
},
});
sleep 1;
stop_daemon('tfiles/pid');
like slurp('tfiles/log'), qr/foo=5/;
is $ENV{FOO}, undef, 'start_hook executed after the fork';
}
__PACKAGE__->new->runtests;