testsuite: eliminate sleep, wait until i3 replies via IPC

next
Michael Stapelberg 2011-08-10 15:56:39 +02:00
parent 10f871b57b
commit 5524785877
1 changed files with 144 additions and 52 deletions

View File

@ -29,6 +29,18 @@ use Try::Tiny;
use Getopt::Long;
use Time::HiRes qw(sleep);
use X11::XCB::Connection;
use IO::Socket::UNIX; # core
use POSIX; # core
use AnyEvent::Handle;
# open a file so that we get file descriptor 3. we will later close it in the
# child and dup() the listening socket file descriptor to 3 to pass it to i3
open(my $reserved, '<', '/dev/null');
if (fileno($reserved) != 3) {
warn "Socket file descriptor is not 3.";
warn "Please don't start this script within a subshell of vim or something.";
exit 1;
}
# install a dummy CHLD handler to overwrite the CHLD handler of AnyEvent / EV
# XXX: we could maybe also use a different loop than the default loop in EV?
@ -72,7 +84,6 @@ for my $display (@displays) {
};
}
my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler";
my $config = slurp('i3-test.config');
# 1: get a list of all testcases
@ -116,21 +127,90 @@ take_job($_) for @wdisplays;
#
sub take_job {
my ($display) = @_;
my $test = shift @testfiles;
return unless $test;
my $dont_start = (slurp($test) =~ /# !NO_I3_INSTANCE!/);
my $logpath = "$outdir/i3-log-for-" . basename($test);
my ($fh, $tmpfile) = tempfile();
say $fh $config;
say $fh "ipc-socket /tmp/nested-$display";
close($fh);
my $test = shift @testfiles;
return unless $test;
my $logpath = "$outdir/i3-log-for-" . basename($test);
my $cmd = "export DISPLAY=$display; exec $i3cmd -c $tmpfile >$logpath 2>&1";
my $dont_start = (slurp($test) =~ /# !NO_I3_INSTANCE!/);
my $activate_cv = AnyEvent->condvar;
my $start_i3 = sub {
# remove the old unix socket
unlink("/tmp/nested-$display-activation");
my $process = Proc::Background->new($cmd) unless $dont_start;
say "[$display] Running $test with logfile $logpath";
# pass all file descriptors up to three to the children.
# we need to set this flag before opening the socket.
open(my $fdtest, '<', '/dev/null');
$^F = fileno($fdtest);
close($fdtest);
my $socket = IO::Socket::UNIX->new(
Listen => 1,
Local => "/tmp/nested-$display-activation",
);
my $pid = fork;
if (!defined($pid)) {
die "could not fork()";
}
say "pid = $pid";
if ($pid == 0) {
say "child!";
$ENV{LISTEN_PID} = $$;
$ENV{LISTEN_FDS} = 1;
$ENV{DISPLAY} = $display;
$^F = 3;
say "fileno is " . fileno($socket);
close($reserved);
POSIX::dup2(fileno($socket), 3);
# now execute i3
my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler";
my $cmd = "exec $i3cmd -c $tmpfile >$logpath 2>&1";
exec "/bin/sh", '-c', $cmd;
# if we are still here, i3 could not be found or exec failed. bail out.
exit 1;
}
my $child_watcher;
$child_watcher = AnyEvent->child(pid => $pid, cb => sub {
say "child died. pid = $pid";
undef $child_watcher;
});
# close the socket, the child process should be the only one which keeps a file
# descriptor on the listening socket.
$socket->close;
# We now connect (will succeed immediately) and send a request afterwards.
# As soon as the reply is there, i3 is considered ready.
my $cl = IO::Socket::UNIX->new(Peer => "/tmp/nested-$display-activation");
my $hdl;
$hdl = AnyEvent::Handle->new(fh => $cl, on_error => sub { $activate_cv->send(0) });
# send a get_tree message without payload
$hdl->push_write('i3-ipc' . pack("LL", 0, 4));
# wait for the reply
$hdl->push_read(chunk => 1, => sub {
my ($h, $line) = @_;
say "read something from i3";
$activate_cv->send(1);
undef $hdl;
});
return $pid;
};
my $pid;
$pid = $start_i3->() unless $dont_start;
sleep 0.5;
my $kill_i3 = sub {
# Dont bother killing i3 when we havent started it
return if $dont_start;
@ -148,53 +228,65 @@ sub take_job {
}
say "[$display] killing i3";
kill(9, $process->pid) or die "could not kill i3";
kill(9, $pid) or die "could not kill i3";
};
my $output;
my $parser = TAP::Parser->new({
exec => [ 'sh', '-c', "DISPLAY=$display /usr/bin/perl -It/lib $test" ],
spool => IO::Scalar->new(\$output),
merge => 1,
# This will be called as soon as i3 is running and answered to our
# IPC request
$activate_cv->cb(sub {
say "cb";
my ($status) = $activate_cv->recv;
say "complete-run: status = $status";
say "[$display] Running $test with logfile $logpath";
my $output;
my $parser = TAP::Parser->new({
exec => [ 'sh', '-c', "DISPLAY=$display /usr/bin/perl -It/lib $test" ],
spool => IO::Scalar->new(\$output),
merge => 1,
});
my @watchers;
my ($stdout, $stderr) = $parser->get_select_handles;
for my $handle ($parser->get_select_handles) {
my $w;
$w = AnyEvent->io(
fh => $handle,
poll => 'r',
cb => sub {
# Ignore activity on stderr (unnecessary with merge => 1,
# but lets keep it in here if we want to use merge => 0
# for some reason in the future).
return if defined($stderr) and $handle == $stderr;
my $result = $parser->next;
if (defined($result)) {
# TODO: check if we should bail out
return;
}
# $result is not defined, we are done parsing
say "[$display] $test finished";
close($parser->delete_spool);
$aggregator->add($test, $parser);
push @done, [ $test, $output ];
$kill_i3->();
undef $_ for @watchers;
if (@done == $num) {
$cv->send;
} else {
take_job($display);
}
}
);
push @watchers, $w;
}
});
my @watchers;
my ($stdout, $stderr) = $parser->get_select_handles;
for my $handle ($parser->get_select_handles) {
my $w;
$w = AnyEvent->io(
fh => $handle,
poll => 'r',
cb => sub {
# Ignore activity on stderr (unnecessary with merge => 1,
# but lets keep it in here if we want to use merge => 0
# for some reason in the future).
return if defined($stderr) and $handle == $stderr;
my $result = $parser->next;
if (defined($result)) {
# TODO: check if we should bail out
return;
}
# $result is not defined, we are done parsing
say "[$display] $test finished";
close($parser->delete_spool);
$aggregator->add($test, $parser);
push @done, [ $test, $output ];
$kill_i3->();
undef $_ for @watchers;
if (@done == $num) {
$cv->send;
} else {
take_job($display);
}
}
);
push @watchers, $w;
}
$activate_cv->send(1) if $dont_start;
}
$cv->recv;