]> git.sur5r.net Git - i3/i3/blob - testcases/lib/SocketActivation.pm
complete-run: implement --valgrind
[i3/i3] / testcases / lib / SocketActivation.pm
1 package SocketActivation;
2 # vim:ts=4:sw=4:expandtab
3
4 use IO::Socket::UNIX; # core
5 use Cwd qw(abs_path); # core
6 use POSIX; # core
7 use AnyEvent::Handle; # not core
8 use Exporter 'import';
9 use v5.10;
10
11 our @EXPORT = qw(activate_i3);
12
13 #
14 # Starts i3 using socket activation. Creates a listening socket (with bind +
15 # listen) which is then passed to i3, who in turn calls accept and handles the
16 # requests.
17 #
18 # Since the kernel buffers the connect, the parent process can connect to the
19 # socket immediately after forking. It then sends a request and waits until it
20 # gets an answer. Obviously, i3 has to be initialized to actually answer the
21 # request.
22 #
23 # This way, we can wait *precisely* the amount of time which i3 waits to get
24 # ready, which is a *HUGE* speed gain (and a lot more robust) in comparison to
25 # using sleep() with a fixed amount of time.
26 #
27 # unix_socket_path: Location of the socket to use for the activation
28 # display: X11 $ENV{DISPLAY}
29 # configfile: path to the configuration file to use
30 # logpath: path to the logfile to which i3 will append
31 # cv: an AnyEvent->condvar which will be triggered once i3 is ready
32 #
33 sub activate_i3 {
34     my %args = @_;
35
36     # remove the old unix socket
37     unlink($args{unix_socket_path});
38
39     # pass all file descriptors up to three to the children.
40     # we need to set this flag before opening the socket.
41     open(my $fdtest, '<', '/dev/null');
42     $^F = fileno($fdtest);
43     close($fdtest);
44     my $socket = IO::Socket::UNIX->new(
45         Listen => 1,
46         Local => $args{unix_socket_path},
47     );
48
49     my $pid = fork;
50     if (!defined($pid)) {
51         die "could not fork()";
52     }
53     if ($pid == 0) {
54         $ENV{LISTEN_PID} = $$;
55         $ENV{LISTEN_FDS} = 1;
56         delete $ENV{DESKTOP_STARTUP_ID};
57         $ENV{DISPLAY} = $args{display};
58         $ENV{PATH} = join(':',
59             '../i3-nagbar',
60             '../i3-msg',
61             '../i3-config-wizard',
62             '../i3bar',
63             '..',
64             $ENV{PATH}
65         );
66         # Only pass file descriptors 0 (stdin), 1 (stdout), 2 (stderr) and
67         # 3 (socket) to the child.
68         $^F = 3;
69
70         # If the socket does not use file descriptor 3 by chance already, we
71         # close fd 3 and dup2() the socket to 3.
72         if (fileno($socket) != 3) {
73             POSIX::close(3);
74             POSIX::dup2(fileno($socket), 3);
75         }
76
77         # Construct the command to launch i3. Use maximum debug level, disable
78         # the interactive signalhandler to make it crash immediately instead.
79         my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler";
80
81         if ($args{valgrind}) {
82             $i3cmd =
83                 qq|valgrind -v --log-file="$args{outdir}/valgrind.log" | .
84                 qq|--leak-check=full --track-origins=yes --num-callers=20 | .
85                 qq|--tool=memcheck -- $i3cmd|;
86         }
87
88         # Append to $args{logpath} instead of overwriting because i3 might be
89         # run multiple times in one testcase.
90         my $cmd = "exec $i3cmd -c $args{configfile} >>$args{logpath} 2>&1";
91
92         # We need to use the shell due to using output redirections.
93         exec "/bin/sh", '-c', $cmd;
94
95         # if we are still here, i3 could not be found or exec failed. bail out.
96         exit 1;
97     }
98
99     # close the socket, the child process should be the only one which keeps a file
100     # descriptor on the listening socket.
101     $socket->close;
102
103     # We now connect (will succeed immediately) and send a request afterwards.
104     # As soon as the reply is there, i3 is considered ready.
105     my $cl = IO::Socket::UNIX->new(Peer => $args{unix_socket_path});
106     my $hdl;
107     $hdl = AnyEvent::Handle->new(
108         fh => $cl,
109         on_error => sub {
110             $hdl->destroy;
111             $args{cv}->send(0);
112         });
113
114     # send a get_tree message without payload
115     $hdl->push_write('i3-ipc' . pack("LL", 0, 4));
116
117     # wait for the reply
118     $hdl->push_read(chunk => 1, => sub {
119         my ($h, $line) = @_;
120         $args{cv}->send(1);
121         undef $hdl;
122     });
123
124     return $pid;
125 }
126
127 1