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?
};
}
-my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler";
my $config = slurp('i3-test.config');
# 1: get a list of all testcases
#
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");
+
+ # 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 $process = Proc::Background->new($cmd) unless $dont_start;
- say "[$display] Running $test with logfile $logpath";
+ 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;
}
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,
- });
-
- 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;
+ # 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;
+ }
+ });
- # $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;