From 5524785877354ac4f170b8eb903bdc75e9af6599 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 10 Aug 2011 15:56:39 +0200 Subject: [PATCH] testsuite: eliminate sleep, wait until i3 replies via IPC --- testcases/complete-run.pl | 196 ++++++++++++++++++++++++++++---------- 1 file changed, 144 insertions(+), 52 deletions(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index c05d6e5b..6f00877c 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -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 { # Don’t bother killing i3 when we haven’t 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 let’s 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 let’s 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;