]> git.sur5r.net Git - i3/i3/blob - testcases/lib/i3test.pm.in
tests: use i3_config arg instead of precisely one launch_with_config
[i3/i3] / testcases / lib / i3test.pm.in
1 package i3test;
2 # vim:ts=4:sw=4:expandtab
3 use strict; use warnings;
4
5 use File::Temp qw(tmpnam tempfile tempdir);
6 use Test::Builder;
7 use X11::XCB::Rect;
8 use X11::XCB::Window;
9 use X11::XCB qw(:all);
10 use lib qw(@abs_top_srcdir@/AnyEvent-I3/blib/lib);
11 use AnyEvent::I3;
12 use List::Util qw(first);
13 use Time::HiRes qw(sleep);
14 use Cwd qw(abs_path);
15 use Scalar::Util qw(blessed);
16 use SocketActivation;
17 use i3test::Util qw(slurp);
18
19 use v5.10;
20
21 # preload
22 use Test::More ();
23 use Data::Dumper ();
24
25 use Exporter ();
26 our @EXPORT = qw(
27     get_workspace_names
28     get_unused_workspace
29     fresh_workspace
30     get_ws_content
31     get_ws
32     get_focused
33     open_empty_con
34     open_window
35     open_floating_window
36     get_dock_clients
37     cmd
38     sync_with_i3
39     exit_gracefully
40     workspace_exists
41     focused_ws
42     get_socket_path
43     launch_with_config
44     get_i3_log
45     wait_for_event
46     wait_for_map
47     wait_for_unmap
48     $x
49     kill_all_windows
50 );
51
52 =head1 NAME
53
54 i3test - Testcase setup module
55
56 =encoding utf-8
57
58 =head1 SYNOPSIS
59
60   use i3test;
61
62   my $ws = fresh_workspace;
63   is_num_children($ws, 0, 'no containers on this workspace yet');
64   cmd 'open';
65   is_num_children($ws, 1, 'one container after "open"');
66
67   done_testing;
68
69 =head1 DESCRIPTION
70
71 This module is used in every i3 testcase and takes care of automatically
72 starting i3 before any test instructions run. It also saves you typing of lots
73 of boilerplate in every test file.
74
75
76 i3test automatically "use"s C<Test::More>, C<Data::Dumper>, C<AnyEvent::I3>,
77 C<Time::HiRes>’s C<sleep> and C<i3test::Test> so that all of them are available
78 to you in your testcase.
79
80 See also C<i3test::Test> (L<http://build.i3wm.org/docs/lib-i3test-test.html>)
81 which provides additional test instructions (like C<ok> or C<is>).
82
83 =cut
84
85 my $tester = Test::Builder->new();
86 my $_cached_socket_path = undef;
87 my $_sync_window = undef;
88 my $tmp_socket_path = undef;
89
90 our $x;
91
92 BEGIN {
93     my $window_count = 0;
94     sub counter_window {
95         return $window_count++;
96     }
97 }
98
99 my $i3_pid;
100 my $i3_autostart;
101
102 END {
103
104     # testcases which start i3 manually should always call exit_gracefully
105     # on their own. Let’s see, whether they really did.
106     if (! $i3_autostart) {
107         return unless $i3_pid;
108
109         $tester->ok(undef, 'testcase called exit_gracefully()');
110     }
111
112     # don't trigger SIGCHLD handler
113     local $SIG{CHLD};
114
115     # From perldoc -v '$?':
116     # Inside an "END" subroutine $? contains the value
117     # that is going to be given to "exit()".
118     #
119     # Since waitpid sets $?, we need to localize it,
120     # otherwise TAP would be misinterpreted our return status
121     local $?;
122
123     # When measuring code coverage, try to exit i3 cleanly (otherwise, .gcda
124     # files are not written)
125     if ($ENV{COVERAGE} || $ENV{VALGRIND}) {
126         exit_gracefully($i3_pid, "/tmp/nested-$ENV{DISPLAY}");
127
128     } else {
129         kill(-9, $i3_pid)
130             or $tester->BAIL_OUT("could not kill i3");
131
132         waitpid $i3_pid, 0;
133     }
134 }
135
136 sub import {
137     my ($class, %args) = @_;
138     my $pkg = caller;
139
140     $i3_autostart = delete($args{i3_autostart}) // 1;
141     my $i3_config = delete($args{i3_config}) // '-default';
142
143     my $cv = launch_with_config($i3_config, dont_block => 1)
144         if $i3_autostart;
145
146     my $test_more_args = '';
147     $test_more_args = join(' ', 'qw(', %args, ')') if keys %args;
148     local $@;
149     eval << "__";
150 package $pkg;
151 use Test::More $test_more_args;
152 use Data::Dumper;
153 use AnyEvent::I3;
154 use Time::HiRes qw(sleep);
155 use i3test::Test;
156 __
157     $tester->BAIL_OUT("$@") if $@;
158     feature->import(":5.10");
159     strict->import;
160     warnings->import;
161
162     $x ||= i3test::X11->new;
163     # set the pointer to a predictable position in case a previous test has
164     # disturbed it
165     $x->root->warp_pointer(0, 0);
166     $cv->recv if $i3_autostart;
167
168     @_ = ($class);
169     goto \&Exporter::import;
170 }
171
172 =head1 EXPORT
173
174 =head2 wait_for_event($timeout, $callback)
175
176 Waits for the next event and calls the given callback for every event to
177 determine if this is the event we are waiting for.
178
179 Can be used to wait until a window is mapped, until a ClientMessage is
180 received, etc.
181
182   wait_for_event 0.25, sub { $_[0]->{response_type} == MAP_NOTIFY };
183
184 =cut
185 sub wait_for_event {
186     my ($timeout, $cb) = @_;
187
188     my $cv = AE::cv;
189
190     $x->flush;
191
192     # unfortunately, there is no constant for this
193     my $ae_read = 0;
194
195     my $guard = AE::io $x->get_file_descriptor, $ae_read, sub {
196         while (defined(my $event = $x->poll_for_event)) {
197             if ($cb->($event)) {
198                 $cv->send(1);
199                 last;
200             }
201         }
202     };
203
204     # Trigger timeout after $timeout seconds (can be fractional)
205     my $t = AE::timer $timeout, 0, sub { warn "timeout ($timeout secs)"; $cv->send(0) };
206
207     my $result = $cv->recv;
208     undef $t;
209     undef $guard;
210     return $result;
211 }
212
213 =head2 wait_for_map($window)
214
215 Thin wrapper around wait_for_event which waits for MAP_NOTIFY.
216 Make sure to include 'structure_notify' in the window’s event_mask attribute.
217
218 This function is called by C<open_window>, so in most cases, you don’t need to
219 call it on your own. If you need special setup of the window before mapping,
220 you might have to map it on your own and use this function:
221
222   my $window = open_window(dont_map => 1);
223   # Do something special with the window first
224   # …
225
226   # Now map it and wait until it’s been mapped
227   $window->map;
228   wait_for_map($window);
229
230 =cut
231 sub wait_for_map {
232     my ($win) = @_;
233     my $id = (blessed($win) && $win->isa('X11::XCB::Window')) ? $win->id : $win;
234     wait_for_event 4, sub {
235         $_[0]->{response_type} == MAP_NOTIFY and $_[0]->{window} == $id
236     };
237 }
238
239 =head2 wait_for_unmap($window)
240
241 Wrapper around C<wait_for_event> which waits for UNMAP_NOTIFY. Also calls
242 C<sync_with_i3> to make sure i3 also picked up and processed the UnmapNotify
243 event.
244
245   my $ws = fresh_workspace;
246   my $window = open_window;
247   is_num_children($ws, 1, 'one window on workspace');
248   $window->unmap;
249   wait_for_unmap;
250   is_num_children($ws, 0, 'no more windows on this workspace');
251
252 =cut
253 sub wait_for_unmap {
254     my ($win) = @_;
255     # my $id = (blessed($win) && $win->isa('X11::XCB::Window')) ? $win->id : $win;
256     wait_for_event 4, sub {
257         $_[0]->{response_type} == UNMAP_NOTIFY # and $_[0]->{window} == $id
258     };
259     sync_with_i3();
260 }
261
262 =head2 open_window([ $args ])
263
264 Opens a new window (see C<X11::XCB::Window>), maps it, waits until it got mapped
265 and synchronizes with i3.
266
267 The following arguments can be passed:
268
269 =over 4
270
271 =item class
272
273 The X11 window class (e.g. WINDOW_CLASS_INPUT_OUTPUT), not to be confused with
274 the WM_CLASS!
275
276 =item rect
277
278 An arrayref with 4 members specifying the initial geometry (position and size)
279 of the window, e.g. C<< [ 0, 100, 70, 50 ] >> for a window appearing at x=0, y=100
280 with width=70 and height=50.
281
282 Note that this is entirely irrelevant for tiling windows.
283
284 =item background_color
285
286 The background pixel color of the window, formatted as "#rrggbb", like HTML
287 color codes (e.g. #c0c0c0). This is useful to tell windows apart when actually
288 watching the testcases.
289
290 =item event_mask
291
292 An arrayref containing strings which describe the X11 event mask we use for that
293 window. The default is C<< [ 'structure_notify' ] >>.
294
295 =item name
296
297 The window’s C<_NET_WM_NAME> (UTF-8 window title). By default, this is "Window
298 n" with n being replaced by a counter to keep windows apart.
299
300 =item dont_map
301
302 Set to a true value to avoid mapping the window (making it visible).
303
304 =item before_map
305
306 A coderef which is called before the window is mapped (unless C<dont_map> is
307 true). The freshly created C<$window> is passed as C<$_> and as the first
308 argument.
309
310 =back
311
312 The default values are equivalent to this call:
313
314   open_window(
315     class => WINDOW_CLASS_INPUT_OUTPUT
316     rect => [ 0, 0, 30, 30 ]
317     background_color => '#c0c0c0'
318     event_mask => [ 'structure_notify' ]
319     name => 'Window <n>'
320   );
321
322 Usually, though, calls are simpler:
323
324   my $top_window = open_window;
325
326 To identify the resulting window object in i3 commands, use the id property:
327
328   my $top_window = open_window;
329   cmd '[id="' . $top_window->id . '"] kill';
330
331 =cut
332 sub open_window {
333     my %args = @_ == 1 ? %{$_[0]} : @_;
334
335     my $dont_map = delete $args{dont_map};
336     my $before_map = delete $args{before_map};
337
338     $args{class} //= WINDOW_CLASS_INPUT_OUTPUT;
339     $args{rect} //= [ 0, 0, 30, 30 ];
340     $args{background_color} //= '#c0c0c0';
341     $args{event_mask} //= [ 'structure_notify' ];
342     $args{name} //= 'Window ' . counter_window();
343
344     my $window = $x->root->create_child(%args);
345     $window->add_hint('input');
346
347     if ($before_map) {
348         # TODO: investigate why _create is not needed
349         $window->_create;
350         $before_map->($window) for $window;
351     }
352
353     return $window if $dont_map;
354
355     $window->map;
356     wait_for_map($window);
357     return $window;
358 }
359
360 =head2 open_floating_window([ $args ])
361
362 Thin wrapper around open_window which sets window_type to
363 C<_NET_WM_WINDOW_TYPE_UTILITY> to make the window floating.
364
365 The arguments are the same as those of C<open_window>.
366
367 =cut
368 sub open_floating_window {
369     my %args = @_ == 1 ? %{$_[0]} : @_;
370
371     $args{window_type} = $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY');
372
373     return open_window(\%args);
374 }
375
376 sub open_empty_con {
377     my ($i3) = @_;
378
379     my $reply = $i3->command('open')->recv;
380     return $reply->[0]->{id};
381 }
382
383 =head2 get_workspace_names()
384
385 Returns an arrayref containing the name of every workspace (regardless of its
386 output) which currently exists.
387
388   my $workspace_names = get_workspace_names;
389   is(scalar @$workspace_names, 3, 'three workspaces exist currently');
390
391 =cut
392 sub get_workspace_names {
393     my $i3 = i3(get_socket_path());
394     my $tree = $i3->get_tree->recv;
395     my @outputs = @{$tree->{nodes}};
396     my @cons;
397     for my $output (@outputs) {
398         next if $output->{name} eq '__i3';
399         # get the first CT_CON of each output
400         my $content = first { $_->{type} eq 'con' } @{$output->{nodes}};
401         @cons = (@cons, @{$content->{nodes}});
402     }
403     [ map { $_->{name} } @cons ]
404 }
405
406 =head2 get_unused_workspace
407
408 Returns a workspace name which has not yet been used. See also
409 C<fresh_workspace> which directly switches to an unused workspace.
410
411   my $ws = get_unused_workspace;
412   cmd "workspace $ws";
413
414 =cut
415 sub get_unused_workspace {
416     my @names = get_workspace_names();
417     my $tmp;
418     do { $tmp = tmpnam() } while ((scalar grep { $_ eq $tmp } @names) > 0);
419     $tmp
420 }
421
422 =head2 fresh_workspace([ $args ])
423
424 Switches to an unused workspace and returns the name of that workspace.
425
426 Optionally switches to the specified output first.
427
428     my $ws = fresh_workspace;
429
430     # Get a fresh workspace on the second output.
431     my $ws = fresh_workspace(output => 1);
432
433 =cut
434 sub fresh_workspace {
435     my %args = @_;
436     if (exists($args{output})) {
437         my $i3 = i3(get_socket_path());
438         my $tree = $i3->get_tree->recv;
439         my $output = first { $_->{name} eq "fake-$args{output}" }
440                         @{$tree->{nodes}};
441         die "BUG: Could not find output $args{output}" unless defined($output);
442         # Get the focused workspace on that output and switch to it.
443         my $content = first { $_->{type} eq 'con' } @{$output->{nodes}};
444         my $focused = $content->{focus}->[0];
445         my $workspace = first { $_->{id} == $focused } @{$content->{nodes}};
446         $workspace = $workspace->{name};
447         cmd("workspace $workspace");
448     }
449
450     my $unused = get_unused_workspace;
451     cmd("workspace $unused");
452     $unused
453 }
454
455 =head2 get_ws($workspace)
456
457 Returns the container (from the i3 layout tree) which represents C<$workspace>.
458
459   my $ws = fresh_workspace;
460   my $ws_con = get_ws($ws);
461   ok(!$ws_con->{urgent}, 'fresh workspace not marked urgent');
462
463 Here is an example which counts the number of urgent containers recursively,
464 starting from the workspace container:
465
466   sub count_urgent {
467       my ($con) = @_;
468
469       my @children = (@{$con->{nodes}}, @{$con->{floating_nodes}});
470       my $urgent = grep { $_->{urgent} } @children;
471       $urgent += count_urgent($_) for @children;
472       return $urgent;
473   }
474   my $urgent = count_urgent(get_ws($ws));
475   is($urgent, 3, "three urgent windows on workspace $ws");
476
477
478 =cut
479 sub get_ws {
480     my ($name) = @_;
481     my $i3 = i3(get_socket_path());
482     my $tree = $i3->get_tree->recv;
483
484     my @outputs = @{$tree->{nodes}};
485     my @workspaces;
486     for my $output (@outputs) {
487         # get the first CT_CON of each output
488         my $content = first { $_->{type} eq 'con' } @{$output->{nodes}};
489         @workspaces = (@workspaces, @{$content->{nodes}});
490     }
491
492     # as there can only be one workspace with this name, we can safely
493     # return the first entry
494     return first { $_->{name} eq $name } @workspaces;
495 }
496
497 =head2 get_ws_content($workspace)
498
499 Returns the content (== tree, starting from the node of a workspace)
500 of a workspace. If called in array context, also includes the focus
501 stack of the workspace.
502
503   my $nodes = get_ws_content($ws);
504   is(scalar @$nodes, 4, 'there are four containers at workspace-level');
505
506 Or, in array context:
507
508   my $window = open_window;
509   my ($nodes, $focus) = get_ws_content($ws);
510   is($focus->[0], $window->id, 'newly opened window focused');
511
512 Note that this function does not do recursion for you! It only returns the
513 containers B<on workspace level>. If you want to work with all containers (even
514 nested ones) on a workspace, you have to use recursion:
515
516   # NB: This function does not count floating windows
517   sub count_urgent {
518       my ($nodes) = @_;
519
520       my $urgent = 0;
521       for my $con (@$nodes) {
522           $urgent++ if $con->{urgent};
523           $urgent += count_urgent($con->{nodes});
524       }
525
526       return $urgent;
527   }
528   my $nodes = get_ws_content($ws);
529   my $urgent = count_urgent($nodes);
530   is($urgent, 3, "three urgent windows on workspace $ws");
531
532 If you also want to deal with floating windows, you have to use C<get_ws>
533 instead and access C<< ->{nodes} >> and C<< ->{floating_nodes} >> on your own.
534
535 =cut
536 sub get_ws_content {
537     my ($name) = @_;
538     my $con = get_ws($name);
539     return wantarray ? ($con->{nodes}, $con->{focus}) : $con->{nodes};
540 }
541
542 =head2 get_focused($workspace)
543
544 Returns the container ID of the currently focused container on C<$workspace>.
545
546 Note that the container ID is B<not> the X11 window ID, so comparing the result
547 of C<get_focused> with a window's C<< ->{id} >> property does B<not> work.
548
549   my $ws = fresh_workspace;
550   my $first_window = open_window;
551   my $first_id = get_focused();
552
553   my $second_window = open_window;
554   my $second_id = get_focused();
555
556   cmd 'focus left';
557
558   is(get_focused($ws), $first_id, 'second window focused');
559
560 =cut
561 sub get_focused {
562     my ($ws) = @_;
563     my $con = get_ws($ws);
564
565     my @focused = @{$con->{focus}};
566     my $lf;
567     while (@focused > 0) {
568         $lf = $focused[0];
569         last unless defined($con->{focus});
570         @focused = @{$con->{focus}};
571         my @cons = grep { $_->{id} == $lf } (@{$con->{nodes}}, @{$con->{'floating_nodes'}});
572         $con = $cons[0];
573     }
574
575     return $lf;
576 }
577
578 =head2 get_dock_clients([ $dockarea ])
579
580 Returns an array of all dock containers in C<$dockarea> (one of "top" or
581 "bottom"). If C<$dockarea> is not specified, returns an array of all dock
582 containers in any dockarea.
583
584   my @docked = get_dock_clients;
585   is(scalar @docked, 0, 'no dock clients yet');
586
587 =cut
588 sub get_dock_clients {
589     my $which = shift;
590
591     my $tree = i3(get_socket_path())->get_tree->recv;
592     my @outputs = @{$tree->{nodes}};
593     # Children of all dockareas
594     my @docked;
595     for my $output (@outputs) {
596         if (!defined($which)) {
597             @docked = (@docked, map { @{$_->{nodes}} }
598                                 grep { $_->{type} eq 'dockarea' }
599                                 @{$output->{nodes}});
600         } elsif ($which eq 'top') {
601             my $first = first { $_->{type} eq 'dockarea' } @{$output->{nodes}};
602             @docked = (@docked, @{$first->{nodes}}) if defined($first);
603         } elsif ($which eq 'bottom') {
604             my @matching = grep { $_->{type} eq 'dockarea' } @{$output->{nodes}};
605             my $last = $matching[-1];
606             @docked = (@docked, @{$last->{nodes}}) if defined($last);
607         }
608     }
609     return @docked;
610 }
611
612 =head2 cmd($command)
613
614 Sends the specified command to i3 and returns the output.
615
616   my $ws = unused_workspace;
617   cmd "workspace $ws";
618   cmd 'focus right';
619
620 =cut
621 sub cmd {
622     i3(get_socket_path())->command(@_)->recv
623 }
624
625 =head2 workspace_exists($workspace)
626
627 Returns true if C<$workspace> is the name of an existing workspace.
628
629   my $old_ws = focused_ws;
630   # switch away from where we currently are
631   fresh_workspace;
632
633   ok(workspace_exists($old_ws), 'old workspace still exists');
634
635 =cut
636 sub workspace_exists {
637     my ($name) = @_;
638     (scalar grep { $_ eq $name } @{get_workspace_names()}) > 0;
639 }
640
641 =head2 focused_ws
642
643 Returns the name of the currently focused workspace.
644
645   my $ws = focused_ws;
646   is($ws, '1', 'i3 starts on workspace 1');
647
648 =cut
649 sub focused_ws {
650     my $i3 = i3(get_socket_path());
651     my $tree = $i3->get_tree->recv;
652     my $focused = $tree->{focus}->[0];
653     my $output = first { $_->{id} == $focused } @{$tree->{nodes}};
654     my $content = first { $_->{type} eq 'con' } @{$output->{nodes}};
655     my $first = first { $_->{fullscreen_mode} == 1 } @{$content->{nodes}};
656     return $first->{name}
657 }
658
659 =head2 sync_with_i3([ $args ])
660
661 Sends an I3_SYNC ClientMessage with a random value to the root window.
662 i3 will reply with the same value, but, due to the order of events it
663 processes, only after all other events are done.
664
665 This can be used to ensure the results of a cmd 'focus left' are pushed to
666 X11 and that C<< $x->input_focus >> returns the correct value afterwards.
667
668 See also L<http://build.i3wm.org/docs/testsuite.html> for a longer explanation.
669
670   my $window = open_window;
671   $window->add_hint('urgency');
672   # Ensure i3 picked up the change
673   sync_with_i3;
674
675 The only time when you need to use the C<no_cache> argument is when you just
676 killed your own X11 connection:
677
678   cmd 'kill client';
679   # We need to re-establish the X11 connection which we just killed :).
680   $x = i3test::X11->new;
681   sync_with_i3(no_cache => 1);
682
683 =cut
684 sub sync_with_i3 {
685     my %args = @_ == 1 ? %{$_[0]} : @_;
686
687     # Since we need a (mapped) window for receiving a ClientMessage, we create
688     # one on the first call of sync_with_i3. It will be re-used in all
689     # subsequent calls.
690     if (!exists($args{window_id}) &&
691         (!defined($_sync_window) || exists($args{no_cache}))) {
692         $_sync_window = open_window(
693             rect => [ -15, -15, 10, 10 ],
694             override_redirect => 1,
695         );
696     }
697
698     my $window_id = delete $args{window_id};
699     $window_id //= $_sync_window->id;
700
701     my $root = $x->get_root_window();
702     # Generate a random number to identify this particular ClientMessage.
703     my $myrnd = int(rand(255)) + 1;
704
705     # Generate a ClientMessage, see xcb_client_message_t
706     my $msg = pack "CCSLLLLLLL",
707          CLIENT_MESSAGE, # response_type
708          32,     # format
709          0,      # sequence
710          $root,  # destination window
711          $x->atom(name => 'I3_SYNC')->id,
712
713          $window_id,    # data[0]: our own window id
714          $myrnd, # data[1]: a random value to identify the request
715          0,
716          0,
717          0;
718
719     # Send it to the root window -- since i3 uses the SubstructureRedirect
720     # event mask, it will get the ClientMessage.
721     $x->send_event(0, $root, EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg);
722
723     return $myrnd if $args{dont_wait_for_event};
724
725     # now wait until the reply is here
726     return wait_for_event 4, sub {
727         my ($event) = @_;
728         # TODO: const
729         return 0 unless $event->{response_type} == 161;
730
731         my ($win, $rnd) = unpack "LL", $event->{data};
732         return ($rnd == $myrnd);
733     };
734 }
735
736 =head2 exit_gracefully($pid, [ $socketpath ])
737
738 Tries to exit i3 gracefully (with the 'exit' cmd) or kills the PID if that fails.
739
740 If C<$socketpath> is not specified, C<get_socket_path()> will be called.
741
742 You only need to use this function if you have launched i3 on your own with
743 C<launch_with_config>. Otherwise, it will be automatically called when the
744 testcase ends.
745
746   use i3test i3_autostart => 0;
747   my $pid = launch_with_config($config);
748   # …
749   exit_gracefully($pid);
750
751 =cut
752 sub exit_gracefully {
753     my ($pid, $socketpath) = @_;
754     $socketpath ||= get_socket_path();
755
756     my $exited = 0;
757     eval {
758         say "Exiting i3 cleanly...";
759         i3($socketpath)->command('exit')->recv;
760         $exited = 1;
761     };
762
763     if (!$exited) {
764         kill(9, $pid)
765             or $tester->BAIL_OUT("could not kill i3");
766     }
767
768     if ($socketpath =~ m,^/tmp/i3-test-socket-,) {
769         unlink($socketpath);
770     }
771
772     waitpid $pid, 0;
773     undef $i3_pid;
774 }
775
776 =head2 get_socket_path([ $cache ])
777
778 Gets the socket path from the C<I3_SOCKET_PATH> atom stored on the X11 root
779 window. After the first call, this function will return a cached version of the
780 socket path unless you specify a false value for C<$cache>.
781
782   my $i3 = i3(get_socket_path());
783   $i3->command('nop test example')->recv;
784
785 To avoid caching:
786
787   my $i3 = i3(get_socket_path(0));
788
789 =cut
790 sub get_socket_path {
791     my ($cache) = @_;
792     $cache //= 1;
793
794     if ($cache && defined($_cached_socket_path)) {
795         return $_cached_socket_path;
796     }
797     my $socketpath = i3test::Util::get_socket_path($x);
798     $_cached_socket_path = $socketpath;
799     return $socketpath;
800 }
801
802 =head2 launch_with_config($config, [ $args ])
803
804 Launches a new i3 process with C<$config> as configuration file. Useful for
805 tests which test specific config file directives.
806
807   use i3test i3_autostart => 0;
808
809   my $config = <<EOT;
810   # i3 config file (v4)
811   for_window [class="borderless"] border none
812   for_window [title="special borderless title"] border none
813   EOT
814
815   my $pid = launch_with_config($config);
816
817   # …
818
819   exit_gracefully($pid);
820
821 =cut
822 sub launch_with_config {
823     my ($config, %args) = @_;
824
825     $tmp_socket_path = "/tmp/nested-$ENV{DISPLAY}";
826
827     $args{dont_create_temp_dir} //= 0;
828     $args{validate_config} //= 0;
829
830     my ($fh, $tmpfile) = tempfile("i3-cfg-for-$ENV{TESTNAME}-XXXXX", UNLINK => 1);
831
832     say $fh "ipc-socket $tmp_socket_path"
833         unless $args{dont_add_socket_path};
834
835     if ($config ne '-default') {
836         print $fh $config;
837     } else {
838         open(my $conf_fh, '<', '@abs_top_srcdir@/testcases/i3-test.config')
839             or $tester->BAIL_OUT("could not open default config: $!");
840         local $/;
841         say $fh scalar <$conf_fh>;
842     }
843
844     close($fh);
845
846     my $cv = AnyEvent->condvar;
847     $i3_pid = activate_i3(
848         unix_socket_path => "$tmp_socket_path-activation",
849         display => $ENV{DISPLAY},
850         configfile => $tmpfile,
851         outdir => $ENV{OUTDIR},
852         testname => $ENV{TESTNAME},
853         valgrind => $ENV{VALGRIND},
854         strace => $ENV{STRACE},
855         xtrace => $ENV{XTRACE},
856         restart => $ENV{RESTART},
857         cv => $cv,
858         dont_create_temp_dir => $args{dont_create_temp_dir},
859         validate_config => $args{validate_config},
860         inject_randr15 => $args{inject_randr15},
861         inject_randr15_outputinfo => $args{inject_randr15_outputinfo},
862     );
863
864     # If we called i3 with -C, we wait for it to exit and then return as
865     # there's nothing else we need to do.
866     if ($args{validate_config}) {
867         $cv->recv;
868         waitpid $i3_pid, 0;
869
870         # We need this since exit_gracefully will not be called in this case.
871         undef $i3_pid;
872
873         return ${^CHILD_ERROR_NATIVE};
874     }
875
876     # force update of the cached socket path in lib/i3test
877     # as soon as i3 has started
878     $cv->cb(sub { get_socket_path(0) });
879
880     return $cv if $args{dont_block};
881
882     # blockingly wait until i3 is ready
883     $cv->recv;
884
885     return $i3_pid;
886 }
887
888 =head2 get_i3_log
889
890 Returns the content of the log file for the current test.
891
892 =cut
893 sub get_i3_log {
894     my $logfile = "$ENV{OUTDIR}/i3-log-for-$ENV{TESTNAME}";
895     return slurp($logfile);
896 }
897
898 =head2 kill_all_windows
899
900 Kills all windows to clean up between tests.
901
902 =cut
903 sub kill_all_windows {
904     # Sync in case not all windows are managed by i3 just yet.
905     sync_with_i3;
906     cmd '[title=".*"] kill';
907 }
908
909 =head1 AUTHOR
910
911 Michael Stapelberg <michael@i3wm.org>
912
913 =cut
914
915 package i3test::X11;
916 use parent 'X11::XCB::Connection';
917
918 sub input_focus {
919     my $self = shift;
920     i3test::sync_with_i3();
921
922     return $self->SUPER::input_focus(@_);
923 }
924
925 1