From 83560c85d8e972e6915cdc42d3f9b8ac8a315351 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 22 Sep 2011 20:30:24 +0100 Subject: [PATCH 01/15] =?UTF-8?q?lib/i3test.pm:=20Don=E2=80=99t=20sleep(0.?= =?UTF-8?q?25),=20but=20wait=20until=20the=20window=20was=20mapped?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes it faster and less racey --- testcases/t/lib/i3test.pm | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 3d2d85af..f8923667 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -7,6 +7,7 @@ use X11::XCB::Rect; use X11::XCB::Window; use X11::XCB qw(:all); use AnyEvent::I3; +use EV; use List::Util qw(first); use List::MoreUtils qw(lastval); use Time::HiRes qw(sleep); @@ -57,6 +58,7 @@ sub open_standard_window { class => WINDOW_CLASS_INPUT_OUTPUT, rect => X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30 ), background_color => $color, + event_mask => [ 'structure_notify' ], ); if (defined($floating) && $floating) { @@ -68,7 +70,29 @@ sub open_standard_window { $window->name('Window ' . counter_window()); $window->map; - sleep(0.25); + # wait for the mapped event with a timeout of 0.25s + my $cv = AE::cv; + + my $prep = EV::prepare sub { + $x->flush; + }; + + my $check = EV::check sub { + while (defined(my $event = $x->poll_for_event)) { + if ($event->{response_type} == MAP_NOTIFY) { + $cv->send(0) + } + } + }; + + my $watcher = EV::io $x->get_file_descriptor, EV::READ, sub { + # do nothing, we only need this watcher so that EV picks up the events + }; + + # Trigger timeout after 0.25s + my $timeout = AE::timer 0.5, 0, sub { say STDERR "timeout"; $cv->send(1) }; + + my $result = $cv->recv; return $window; } From 1481cd95c941e87fff8713a514ddc26700ce0b7d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 22 Sep 2011 22:08:42 +0100 Subject: [PATCH 02/15] Implement the I3_SYNC client protocol This is mainly useful for the testsuite. The tests can wait until i3 processed all X11 events and then continue. This eliminates sleep() calls which leads to a more robust and faster testsuite. --- include/atoms.xmacro | 1 + src/handlers.c | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/include/atoms.xmacro b/include/atoms.xmacro index 7f83a7ee..309a3325 100644 --- a/include/atoms.xmacro +++ b/include/atoms.xmacro @@ -24,3 +24,4 @@ xmacro(WM_TAKE_FOCUS) xmacro(WM_WINDOW_ROLE) xmacro(I3_SOCKET_PATH) xmacro(I3_CONFIG_PATH) +xmacro(I3_SYNC) diff --git a/src/handlers.c b/src/handlers.c index 267159ec..b3cb1df7 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -648,6 +648,25 @@ static int handle_client_message(xcb_client_message_event_t *event) { tree_render(); x_push_changes(croot); + } else if (event->type == A_I3_SYNC) { + DLOG("i3 sync, yay\n"); + xcb_window_t window = event->data.data32[0]; + uint32_t rnd = event->data.data32[1]; + DLOG("Sending random value %d back to X11 window 0x%08x\n", rnd, window); + + void *reply = scalloc(32); + xcb_client_message_event_t *ev = reply; + + ev->response_type = XCB_CLIENT_MESSAGE; + ev->window = window; + ev->type = A_I3_SYNC; + ev->format = 32; + ev->data.data32[0] = window; + ev->data.data32[1] = rnd; + + xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char*)ev); + xcb_flush(conn); + free(reply); } else { ELOG("unhandled clientmessage\n"); return 0; From 3167e9ad2d3c4b226f01ab2c65d0f8a85c1edaec Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 22 Sep 2011 22:11:11 +0100 Subject: [PATCH 03/15] lib/i3test.pm: reformat exports list --- testcases/t/lib/i3test.pm | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index f8923667..9ded4942 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -18,7 +18,25 @@ use Proc::Background; use v5.10; use Exporter (); -our @EXPORT = qw(get_workspace_names get_unused_workspace fresh_workspace get_ws_content get_ws get_focused open_empty_con open_standard_window get_dock_clients cmd does_i3_live exit_gracefully workspace_exists focused_ws get_socket_path launch_with_config); +our @EXPORT = qw( + get_workspace_names + get_unused_workspace + fresh_workspace + get_ws_content + get_ws + get_focused + open_empty_con + open_standard_window + get_dock_clients + cmd + sync_with_i3 + does_i3_live + exit_gracefully + workspace_exists + focused_ws + get_socket_path + launch_with_config +); my $tester = Test::Builder->new(); my $_cached_socket_path = undef; From 38a9eabff134b23bb0883561034dd2578e1231b1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 23 Sep 2011 20:37:45 +0100 Subject: [PATCH 04/15] tests: implement sync_with_i3 and use it instead of sleep() Also use open_standard_window() in a few more places where appropriate --- testcases/t/02-fullscreen.t | 10 +- testcases/t/05-ipc.t | 11 +-- testcases/t/06-focus.t | 2 +- testcases/t/08-focus-stack.t | 24 +---- testcases/t/09-stacking.t | 2 - testcases/t/11-goto.t | 4 +- testcases/t/12-floating-resize.t | 24 ++--- testcases/t/13-urgent.t | 6 +- testcases/t/19-match.t | 17 +--- testcases/t/33-size-hints.t | 2 +- testcases/t/35-floating-focus.t | 59 +++++++---- testcases/t/36-floating-ws-empty.t | 15 +-- testcases/t/37-floating-unmap.t | 17 +--- testcases/t/38-floating-attach.t | 43 +------- testcases/t/40-focus-lost.t | 6 +- testcases/t/41-resize.t | 7 +- testcases/t/45-flattening.t | 3 - testcases/t/46-floating-reinsert.t | 19 +--- testcases/t/47-regress-floatingmove.t | 3 - testcases/t/48-regress-floatingmovews.t | 8 +- testcases/t/53-floating-originalsize.t | 2 +- testcases/t/56-fullscreen-focus.t | 2 +- testcases/t/62-regress-dock-urgent.t | 2 +- testcases/t/63-wm-state.t | 15 +-- testcases/t/67-workspace_layout.t | 4 + testcases/t/70-force_focus_wrapping.t | 3 + testcases/t/lib/i3test.pm | 126 +++++++++++++++++++----- 27 files changed, 198 insertions(+), 238 deletions(-) diff --git a/testcases/t/02-fullscreen.t b/testcases/t/02-fullscreen.t index 34e5364e..e83e45ac 100644 --- a/testcases/t/02-fullscreen.t +++ b/testcases/t/02-fullscreen.t @@ -59,11 +59,9 @@ my $new_rect = $window->rect; ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned"); $original_rect = $new_rect; -sleep 0.25; - $window->fullscreen(1); -sleep 0.25; +sync_with_i3($x); $new_rect = $window->rect; ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen"); @@ -135,12 +133,12 @@ $new_rect = $swindow->rect; ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned"); $swindow->fullscreen(1); -sleep 0.25; +sync_with_i3($x); is(fullscreen_windows(), 1, 'amount of fullscreen windows'); $window->fullscreen(0); -sleep 0.25; +sync_with_i3($x); is(fullscreen_windows(), 0, 'amount of fullscreen windows'); ok($swindow->mapped, 'window mapped after other fullscreen ended'); @@ -152,7 +150,7 @@ ok($swindow->mapped, 'window mapped after other fullscreen ended'); ########################################################################### $swindow->fullscreen(0); -sleep 0.25; +sync_with_i3($x); is(fullscreen_windows(), 0, 'amount of fullscreen windows after disabling'); diff --git a/testcases/t/05-ipc.t b/testcases/t/05-ipc.t index a910c930..0d18040e 100644 --- a/testcases/t/05-ipc.t +++ b/testcases/t/05-ipc.t @@ -2,11 +2,6 @@ # vim:ts=4:sw=4:expandtab use i3test; -use X11::XCB qw(:all); - -BEGIN { - use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); -} my $x = X11::XCB::Connection->new; @@ -18,16 +13,14 @@ fresh_workspace; # Create a window so we can get a focus different from NULL my $window = open_standard_window($x); -diag("window->id = " . $window->id); - -sleep 0.25; +sync_with_i3($x); my $focus = $x->input_focus; -diag("old focus = $focus"); # Switch to another workspace fresh_workspace; +sync_with_i3($x); my $new_focus = $x->input_focus; isnt($focus, $new_focus, "Focus changed"); diff --git a/testcases/t/06-focus.t b/testcases/t/06-focus.t index d357c8a9..b3add322 100644 --- a/testcases/t/06-focus.t +++ b/testcases/t/06-focus.t @@ -24,7 +24,7 @@ cmd 'split v'; my $top = open_standard_window($x); my $mid = open_standard_window($x); my $bottom = open_standard_window($x); -sleep 0.25; +##sleep 0.25; diag("top id = " . $top->id); diag("mid id = " . $mid->id); diff --git a/testcases/t/08-focus-stack.t b/testcases/t/08-focus-stack.t index f8143979..33a5884a 100644 --- a/testcases/t/08-focus-stack.t +++ b/testcases/t/08-focus-stack.t @@ -4,11 +4,6 @@ # over an unfocused tiling client and destroying the floating one again. use i3test; -use X11::XCB qw(:all); - -BEGIN { - use_ok('X11::XCB::Window') or BAIL_OUT('Could not load X11::XCB::Window'); -} my $x = X11::XCB::Connection->new; @@ -19,30 +14,21 @@ cmd 'split h'; my $tiled_left = open_standard_window($x); my $tiled_right = open_standard_window($x); -sleep 0.25; +sync_with_i3($x); # Get input focus before creating the floating window my $focus = $x->input_focus; # Create a floating window which is smaller than the minimum enforced size of i3 -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 1, 1, 30, 30], - background_color => '#C0C0C0', - type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), -); +my $window = open_standard_window($x, undef, 1); +sync_with_i3($x); -isa_ok($window, 'X11::XCB::Window'); - -$window->map; - -sleep 1; -sleep 0.25; is($x->input_focus, $window->id, 'floating window focused'); $window->unmap; -sleep 0.25; +# TODO: wait for unmap +sync_with_i3($x); is($x->input_focus, $focus, 'Focus correctly restored'); diff --git a/testcases/t/09-stacking.t b/testcases/t/09-stacking.t index 1cb205ed..cc285f32 100644 --- a/testcases/t/09-stacking.t +++ b/testcases/t/09-stacking.t @@ -27,9 +27,7 @@ $i3->command('9')->recv; ##################################################################### my $top = i3test::open_standard_window($x); -sleep(0.25); my $mid = i3test::open_standard_window($x); -sleep(0.25); my $bottom = i3test::open_standard_window($x); sleep(0.25); diff --git a/testcases/t/11-goto.t b/testcases/t/11-goto.t index 542dc828..44cf55ab 100644 --- a/testcases/t/11-goto.t +++ b/testcases/t/11-goto.t @@ -21,11 +21,8 @@ cmd 'split h'; ##################################################################### my $top = open_standard_window($x); -sleep 0.25; my $mid = open_standard_window($x); -sleep 0.25; my $bottom = open_standard_window($x); -sleep 0.25; diag("top id = " . $top->id); diag("mid id = " . $mid->id); @@ -39,6 +36,7 @@ sub focus_after { my $msg = shift; cmd $msg; + sync_with_i3($x); return $x->input_focus; } diff --git a/testcases/t/12-floating-resize.t b/testcases/t/12-floating-resize.t index 09297df0..1aec9573 100644 --- a/testcases/t/12-floating-resize.t +++ b/testcases/t/12-floating-resize.t @@ -1,8 +1,5 @@ #!perl # vim:ts=4:sw=4:expandtab -# Beware that this test uses workspace 9 to perform some tests (it expects -# the workspace to be empty). -# TODO: skip it by default? use i3test; use X11::XCB qw(:all); @@ -20,30 +17,22 @@ fresh_workspace; ##################################################################### # Create a floating window -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#C0C0C0', - # replace the type with 'utility' as soon as the coercion works again in X11::XCB - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), -); - -isa_ok($window, 'X11::XCB::Window'); - -$window->map; -sleep 0.25; +my $window = open_standard_window($x, undef, 1); # See if configurerequests cause window movements (they should not) my ($a, $t) = $window->rect; $window->rect(X11::XCB::Rect->new(x => $a->x, y => $a->y, width => $a->width, height => $a->height)); -sleep 0.25; +sync_with_i3($x); + my ($na, $nt) = $window->rect; is_deeply($na, $a, 'Rects are equal after configurerequest'); sub test_resize { $window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 100, height => 100)); + sync_with_i3($x); + my ($absolute, $top) = $window->rect; # Make sure the width/height are different from what we’re gonna test, so @@ -52,7 +41,8 @@ sub test_resize { isnt($absolute->height, 500, 'height != 500'); $window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 300, height => 500)); - sleep 0.25; + + sync_with_i3($x); ($absolute, $top) = $window->rect; diff --git a/testcases/t/13-urgent.t b/testcases/t/13-urgent.t index f40b72fb..83c36a98 100644 --- a/testcases/t/13-urgent.t +++ b/testcases/t/13-urgent.t @@ -31,7 +31,7 @@ is(@urgent, 0, 'no window got the urgent flag'); # Add the urgency hint, switch to a different workspace and back again ##################################################################### $top->add_hint('urgency'); -sleep 0.5; +sync_with_i3($x); @content = @{get_ws_content($tmp)}; @urgent = grep { $_->{urgent} } @content; @@ -48,7 +48,7 @@ cmd '[id="' . $top->id . '"] focus'; is(@urgent, 0, 'no window got the urgent flag after focusing'); $top->add_hint('urgency'); -sleep 0.5; +sync_with_i3($x); @urgent = grep { $_->{urgent} } @{get_ws_content($tmp)}; is(@urgent, 0, 'no window got the urgent flag after re-setting urgency hint'); @@ -62,7 +62,7 @@ ok(!$ws->{urgent}, 'urgent flag not set on workspace'); my $otmp = fresh_workspace; $top->add_hint('urgency'); -sleep 0.5; +sync_with_i3($x); $ws = get_ws($tmp); ok($ws->{urgent}, 'urgent flag set on workspace'); diff --git a/testcases/t/19-match.t b/testcases/t/19-match.t index e4fc6ec0..93822f1f 100644 --- a/testcases/t/19-match.t +++ b/testcases/t/19-match.t @@ -12,20 +12,7 @@ ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); # Open a new window my $x = X11::XCB::Connection->new; -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30 ], - background_color => '#C0C0C0', -); - -$window->map; -# give it some time to be picked up by the window manager -# TODO: better check for $window->mapped or something like that? -# maybe we can even wait for getting mapped? -my $c = 0; -while (@{get_ws_content($tmp)} == 0 and $c++ < 5) { - sleep 0.25; -} +my $window = open_standard_window($x); my $content = get_ws_content($tmp); ok(@{$content} == 1, 'window mapped'); my $win = $content->[0]; @@ -114,7 +101,7 @@ ok(@{$content} == 2, 'two windows opened'); cmd '[class="special" title="left"] kill'; -sleep 0.25; +sync_with_i3($x); $content = get_ws_content($tmp); is(@{$content}, 1, 'one window still there'); diff --git a/testcases/t/33-size-hints.t b/testcases/t/33-size-hints.t index 05897c88..e212dc72 100644 --- a/testcases/t/33-size-hints.t +++ b/testcases/t/33-size-hints.t @@ -32,7 +32,7 @@ sleep 0.25; $win->hints->aspect($aspect); $x->flush; -sleep 0.25; +sync_with_i3($x); my $rect = $win->rect; my $ar = $rect->width / $rect->height; diff --git a/testcases/t/35-floating-focus.t b/testcases/t/35-floating-focus.t index 6adad246..fc94c440 100644 --- a/testcases/t/35-floating-focus.t +++ b/testcases/t/35-floating-focus.t @@ -16,6 +16,8 @@ my $tmp = fresh_workspace; my $first = open_standard_window($x); my $second = open_standard_window($x); +sync_with_i3($x); + is($x->input_focus, $second->id, 'second window focused'); cmd 'floating enable'; @@ -34,12 +36,16 @@ $first = open_standard_window($x); # window 2 $second = open_standard_window($x); # window 3 my $third = open_standard_window($x); # window 4 +sync_with_i3($x); + is($x->input_focus, $third->id, 'last container focused'); cmd 'floating enable'; cmd '[id="' . $second->id . '"] focus'; +sync_with_i3($x); + is($x->input_focus, $second->id, 'second con focused'); cmd 'floating enable'; @@ -47,7 +53,8 @@ cmd 'floating enable'; # now kill the third one (it's floating). focus should stay unchanged cmd '[id="' . $third->id . '"] kill'; -sleep 0.25; +# TODO: wait for unmapnotify +sync_with_i3($x); is($x->input_focus, $second->id, 'second con still focused after killing third'); @@ -63,12 +70,16 @@ $first = open_standard_window($x, '#ff0000'); # window 5 $second = open_standard_window($x, '#00ff00'); # window 6 my $third = open_standard_window($x, '#0000ff'); # window 7 +sync_with_i3($x); + is($x->input_focus, $third->id, 'last container focused'); cmd 'floating enable'; cmd '[id="' . $second->id . '"] focus'; +sync_with_i3($x); + is($x->input_focus, $second->id, 'second con focused'); cmd 'floating enable'; @@ -77,13 +88,14 @@ cmd 'floating enable'; # also floating cmd 'kill'; -sleep 0.25; +# TODO: wait for unmapnotify +sync_with_i3($x); is($x->input_focus, $third->id, 'third con focused'); cmd 'kill'; - -sleep 0.25; +# TODO: wait for unmapnotify +sync_with_i3($x); is($x->input_focus, $first->id, 'first con focused after killing all floating cons'); @@ -99,29 +111,34 @@ cmd 'layout stacked'; $second = open_standard_window($x, '#00ff00'); # window 6 $third = open_standard_window($x, '#0000ff'); # window 7 +sync_with_i3($x); + is($x->input_focus, $third->id, 'last container focused'); cmd 'floating enable'; cmd '[id="' . $second->id . '"] focus'; +sync_with_i3($x); + is($x->input_focus, $second->id, 'second con focused'); cmd 'floating enable'; -sleep 0.5; +sync_with_i3($x); # now kill the second one. focus should fall back to the third one, which is # also floating cmd 'kill'; -sleep 0.25; +# TODO: wait for unmapnotify +sync_with_i3($x); -is($x->input_focus, $third->id, 'second con focused'); +is($x->input_focus, $third->id, 'third con focused'); cmd 'kill'; - -sleep 0.25; +# TODO: wait for unmapnotify +sync_with_i3($x); is($x->input_focus, $first->id, 'first con focused after killing all floating cons'); @@ -134,6 +151,8 @@ $tmp = fresh_workspace; $first = open_standard_window($x, '#ff0000'); # window 8 $second = open_standard_window($x, '#00ff00'); # window 9 +sync_with_i3($x); + is($x->input_focus, $second->id, 'second container focused'); cmd 'floating enable'; @@ -142,31 +161,31 @@ is($x->input_focus, $second->id, 'second container focused'); cmd 'focus tiling'; -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $first->id, 'first (tiling) container focused'); cmd 'focus floating'; -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $second->id, 'second (floating) container focused'); cmd 'focus floating'; -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $second->id, 'second (floating) container still focused'); cmd 'focus mode_toggle'; -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $first->id, 'first (tiling) container focused'); cmd 'focus mode_toggle'; -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $second->id, 'second (floating) container focused'); @@ -180,35 +199,37 @@ $first = open_standard_window($x, '#ff0000', 1); # window 10 $second = open_standard_window($x, '#00ff00', 1); # window 11 $third = open_standard_window($x, '#0000ff', 1); # window 12 +sync_with_i3($x); + is($x->input_focus, $third->id, 'third container focused'); cmd 'focus left'; -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $second->id, 'second container focused'); cmd 'focus left'; -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $first->id, 'first container focused'); cmd 'focus left'; -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $third->id, 'focus wrapped to third container'); cmd 'focus right'; -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $first->id, 'focus wrapped to first container'); cmd 'focus right'; -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $second->id, 'focus on second container'); diff --git a/testcases/t/36-floating-ws-empty.t b/testcases/t/36-floating-ws-empty.t index f33d04db..91467ed3 100644 --- a/testcases/t/36-floating-ws-empty.t +++ b/testcases/t/36-floating-ws-empty.t @@ -22,20 +22,7 @@ ok(workspace_exists($tmp), "workspace $tmp exists"); my $x = X11::XCB::Connection->new; # Create a floating window which is smaller than the minimum enforced size of i3 -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#C0C0C0', - # replace the type with 'utility' as soon as the coercion works again in X11::XCB - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), -); - -isa_ok($window, 'X11::XCB::Window'); - -$window->map; - -sleep 0.25; - +my $window = open_standard_window($x, undef, 1); ok($window->mapped, 'Window is mapped'); # switch to a different workspace, see if the window is still mapped? diff --git a/testcases/t/37-floating-unmap.t b/testcases/t/37-floating-unmap.t index 3ae4b12d..b3be1348 100644 --- a/testcases/t/37-floating-unmap.t +++ b/testcases/t/37-floating-unmap.t @@ -21,27 +21,14 @@ my $tmp = fresh_workspace; my $x = X11::XCB::Connection->new; # Create a floating window which is smaller than the minimum enforced size of i3 -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#C0C0C0', - # replace the type with 'utility' as soon as the coercion works again in X11::XCB - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), -); - -isa_ok($window, 'X11::XCB::Window'); - -$window->map; - -sleep 0.25; - +my $window = open_standard_window($x, undef, 1); ok($window->mapped, 'Window is mapped'); # switch to a different workspace, see if the window is still mapped? my $otmp = fresh_workspace; -sleep 0.25; +sync_with_i3($x); ok(!$window->mapped, 'Window is not mapped after switching ws'); diff --git a/testcases/t/38-floating-attach.t b/testcases/t/38-floating-attach.t index 31bddafd..411ffa8e 100644 --- a/testcases/t/38-floating-attach.t +++ b/testcases/t/38-floating-attach.t @@ -5,7 +5,6 @@ use i3test; use X11::XCB qw(:all); -use Time::HiRes qw(sleep); BEGIN { use_ok('X11::XCB::Window'); @@ -22,20 +21,7 @@ my $tmp = fresh_workspace; my $x = X11::XCB::Connection->new; # Create a floating window -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#C0C0C0', - # replace the type with 'utility' as soon as the coercion works again in X11::XCB - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), -); - -isa_ok($window, 'X11::XCB::Window'); - -$window->map; - -sleep 0.25; - +my $window = open_standard_window($x, undef, 1); ok($window->mapped, 'Window is mapped'); my $ws = get_ws($tmp); @@ -45,17 +31,7 @@ is(@{$ws->{floating_nodes}}, 1, 'one floating node'); is(@{$nodes}, 0, 'no tiling nodes'); # Create a tiling window -my $twindow = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#C0C0C0', -); - -isa_ok($twindow, 'X11::XCB::Window'); - -$twindow->map; - -sleep 0.25; +my $twindow = open_standard_window($x); ($nodes, $focus) = get_ws_content($tmp); @@ -78,20 +54,7 @@ is(@{$ws->{floating_nodes}}, 0, 'no floating nodes so far'); is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)'); # Create a floating window -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#C0C0C0', - # replace the type with 'utility' as soon as the coercion works again in X11::XCB - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), -); - -isa_ok($window, 'X11::XCB::Window'); - -$window->map; - -sleep 0.25; - +my $window = open_standard_window($x, undef, 1); ok($window->mapped, 'Window is mapped'); $ws = get_ws($tmp); diff --git a/testcases/t/40-focus-lost.t b/testcases/t/40-focus-lost.t index 9df220d1..1121f124 100644 --- a/testcases/t/40-focus-lost.t +++ b/testcases/t/40-focus-lost.t @@ -4,7 +4,6 @@ # bug introduced by 77d0d42ed2d7ac8cafe267c92b35a81c1b9491eb use i3test; use X11::XCB qw(:all); -use Time::HiRes qw(sleep); BEGIN { use_ok('X11::XCB::Window'); @@ -26,11 +25,10 @@ sub check_order { my $tmp = fresh_workspace; my $left = open_standard_window($x); -sleep 0.25; my $mid = open_standard_window($x); -sleep 0.25; my $right = open_standard_window($x); -sleep 0.25; + +sync_with_i3($x); diag("left = " . $left->id . ", mid = " . $mid->id . ", right = " . $right->id); diff --git a/testcases/t/41-resize.t b/testcases/t/41-resize.t index 1d1b1206..2a3846d8 100644 --- a/testcases/t/41-resize.t +++ b/testcases/t/41-resize.t @@ -15,9 +15,9 @@ my $tmp = fresh_workspace; cmd 'split v'; my $top = open_standard_window($x); -sleep 0.25; my $bottom = open_standard_window($x); -sleep 0.25; + +sync_with_i3($x); diag("top = " . $top->id . ", bottom = " . $bottom->id); @@ -55,9 +55,7 @@ $tmp = fresh_workspace; cmd 'split v'; $top = open_standard_window($x); -sleep 0.25; $bottom = open_standard_window($x); -sleep 0.25; cmd 'split h'; cmd 'layout stacked'; @@ -79,7 +77,6 @@ is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%'); $tmp = fresh_workspace; $top = open_standard_window($x); -sleep 0.25; cmd 'floating enable'; diff --git a/testcases/t/45-flattening.t b/testcases/t/45-flattening.t index 98b93ef1..4a57f211 100644 --- a/testcases/t/45-flattening.t +++ b/testcases/t/45-flattening.t @@ -18,11 +18,8 @@ my $x = X11::XCB::Connection->new; my $tmp = fresh_workspace; my $left = open_standard_window($x); -sleep 0.25; my $mid = open_standard_window($x); -sleep 0.25; my $right = open_standard_window($x); -sleep 0.25; cmd 'move before v'; cmd 'move after h'; diff --git a/testcases/t/46-floating-reinsert.t b/testcases/t/46-floating-reinsert.t index a4e90d4d..07f78956 100644 --- a/testcases/t/46-floating-reinsert.t +++ b/testcases/t/46-floating-reinsert.t @@ -2,7 +2,6 @@ # vim:ts=4:sw=4:expandtab # use X11::XCB qw(:all); -use Time::HiRes qw(sleep); use i3test; BEGIN { @@ -14,13 +13,10 @@ my $x = X11::XCB::Connection->new; my $tmp = fresh_workspace; my $left = open_standard_window($x); -sleep 0.25; my $mid = open_standard_window($x); -sleep 0.25; cmd 'split v'; my $bottom = open_standard_window($x); -sleep 0.25; my ($nodes, $focus) = get_ws_content($tmp); @@ -31,20 +27,7 @@ my ($nodes, $focus) = get_ws_content($tmp); my $x = X11::XCB::Connection->new; # Create a floating window -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#C0C0C0', - # replace the type with 'utility' as soon as the coercion works again in X11::XCB - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), -); - -isa_ok($window, 'X11::XCB::Window'); - -$window->map; - -sleep 0.25; - +my $window = open_standard_window($x, undef, 1); ok($window->mapped, 'Window is mapped'); ($nodes, $focus) = get_ws_content($tmp); diff --git a/testcases/t/47-regress-floatingmove.t b/testcases/t/47-regress-floatingmove.t index 6e04916d..8425f6d9 100644 --- a/testcases/t/47-regress-floatingmove.t +++ b/testcases/t/47-regress-floatingmove.t @@ -17,11 +17,8 @@ my $x = X11::XCB::Connection->new; my $tmp = fresh_workspace; my $left = open_standard_window($x); -sleep 0.25; my $mid = open_standard_window($x); -sleep 0.25; my $right = open_standard_window($x); -sleep 0.25; # go to workspace level cmd 'level up'; diff --git a/testcases/t/48-regress-floatingmovews.t b/testcases/t/48-regress-floatingmovews.t index 0bec5418..355e697a 100644 --- a/testcases/t/48-regress-floatingmovews.t +++ b/testcases/t/48-regress-floatingmovews.t @@ -17,20 +17,20 @@ my $tmp = fresh_workspace; # open a tiling window on the first workspace open_standard_window($x); -sleep 0.25; +#sleep 0.25; my $first = get_focused($tmp); # on a different ws, open a floating window my $otmp = fresh_workspace; open_standard_window($x); -sleep 0.25; +#sleep 0.25; my $float = get_focused($otmp); cmd 'mode toggle'; -sleep 0.25; +#sleep 0.25; # move the floating con to first workspace cmd "move workspace $tmp"; -sleep 0.25; +#sleep 0.25; # switch to the first ws and check focus is(get_focused($tmp), $float, 'floating client correctly focused'); diff --git a/testcases/t/53-floating-originalsize.t b/testcases/t/53-floating-originalsize.t index 16e62c20..1daa2709 100644 --- a/testcases/t/53-floating-originalsize.t +++ b/testcases/t/53-floating-originalsize.t @@ -30,7 +30,7 @@ cmp_ok($absolute->{width}, '>', 400, 'i3 raised the width'); cmp_ok($absolute->{height}, '>', 150, 'i3 raised the height'); cmd 'floating toggle'; -sleep 0.25; +sync_with_i3($x); ($absolute, $top) = $window->rect; diff --git a/testcases/t/56-fullscreen-focus.t b/testcases/t/56-fullscreen-focus.t index ee60dc7a..89af6219 100644 --- a/testcases/t/56-fullscreen-focus.t +++ b/testcases/t/56-fullscreen-focus.t @@ -56,7 +56,7 @@ cmd "move workspace $tmp2"; # verify that the third window has the focus -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $third->id, 'third window focused'); diff --git a/testcases/t/62-regress-dock-urgent.t b/testcases/t/62-regress-dock-urgent.t index 8d188738..5fb88129 100644 --- a/testcases/t/62-regress-dock-urgent.t +++ b/testcases/t/62-regress-dock-urgent.t @@ -52,7 +52,7 @@ is($docknode->{rect}->{height}, 30, 'dock node has unchanged height'); $window->add_hint('urgency'); -sleep 0.25; +sync_with_i3($x); does_i3_live; diff --git a/testcases/t/63-wm-state.t b/testcases/t/63-wm-state.t index 7e983289..b9161400 100644 --- a/testcases/t/63-wm-state.t +++ b/testcases/t/63-wm-state.t @@ -16,25 +16,18 @@ BEGIN { my $x = X11::XCB::Connection->new; -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30 ], - background_color => '#00ff00', - event_mask => [ 'structure_notify' ], -); +my $window = open_standard_window($x); -$window->name('Window 1'); -$window->map; +sync_with_i3($x); diag('window mapped'); -sleep 0.5; - is($window->state, ICCCM_WM_STATE_NORMAL, 'WM_STATE normal'); $window->unmap; -sleep 0.5; +# TODO: wait for unmapnotify +sync_with_i3($x); is($window->state, ICCCM_WM_STATE_WITHDRAWN, 'WM_STATE withdrawn'); diff --git a/testcases/t/67-workspace_layout.t b/testcases/t/67-workspace_layout.t index 2b9f6e56..0e07ebf7 100644 --- a/testcases/t/67-workspace_layout.t +++ b/testcases/t/67-workspace_layout.t @@ -30,6 +30,8 @@ ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); my $first = open_standard_window($x); my $second = open_standard_window($x); +sync_with_i3($x); + is($x->input_focus, $second->id, 'second window focused'); ok(@{get_ws_content($tmp)} == 2, 'two containers opened'); isnt($content[0]->{layout}, 'stacked', 'layout not stacked'); @@ -57,6 +59,8 @@ ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); $first = open_standard_window($x); $second = open_standard_window($x); +sync_with_i3($x); + is($x->input_focus, $second->id, 'second window focused'); my @content = @{get_ws_content($tmp)}; ok(@content == 1, 'one con at workspace level'); diff --git a/testcases/t/70-force_focus_wrapping.t b/testcases/t/70-force_focus_wrapping.t index f2dfc18e..cf1c3216 100644 --- a/testcases/t/70-force_focus_wrapping.t +++ b/testcases/t/70-force_focus_wrapping.t @@ -73,6 +73,9 @@ cmd 'layout tabbed'; cmd 'focus parent'; $third = open_standard_window($x); + +sync_with_i3($x); + is($x->input_focus, $third->id, 'third window focused'); cmd 'focus left'; diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 9ded4942..2a51dad6 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -40,6 +40,7 @@ our @EXPORT = qw( my $tester = Test::Builder->new(); my $_cached_socket_path = undef; +my $_sync_window = undef; my $tmp_socket_path = undef; BEGIN { @@ -66,6 +67,44 @@ use warnings; goto \&Exporter::import; } +# +# Waits for the next event and calls the given callback for every event to +# determine if this is the event we are waiting for. +# +# Can be used to wait until a window is mapped, until a ClientMessage is +# received, etc. +# +# wait_for_event $x, 0.25, sub { $_[0]->{response_type} == MAP_NOTIFY }; +# +sub wait_for_event { + my ($x, $timeout, $cb) = @_; + + my $cv = AE::cv; + + my $prep = EV::prepare sub { + $x->flush; + }; + + my $check = EV::check sub { + while (defined(my $event = $x->poll_for_event)) { + if ($cb->($event)) { + $cv->send(1); + last; + } + } + }; + + my $watcher = EV::io $x->get_file_descriptor, EV::READ, sub { + # do nothing, we only need this watcher so that EV picks up the events + }; + + # Trigger timeout after $timeout seconds (can be fractional) + my $timeout = AE::timer $timeout, 0, sub { say STDERR "timeout"; $cv->send(0) }; + + my $result = $cv->recv; + return $result; +} + sub open_standard_window { my ($x, $color, $floating) = @_; @@ -88,29 +127,7 @@ sub open_standard_window { $window->name('Window ' . counter_window()); $window->map; - # wait for the mapped event with a timeout of 0.25s - my $cv = AE::cv; - - my $prep = EV::prepare sub { - $x->flush; - }; - - my $check = EV::check sub { - while (defined(my $event = $x->poll_for_event)) { - if ($event->{response_type} == MAP_NOTIFY) { - $cv->send(0) - } - } - }; - - my $watcher = EV::io $x->get_file_descriptor, EV::READ, sub { - # do nothing, we only need this watcher so that EV picks up the events - }; - - # Trigger timeout after 0.25s - my $timeout = AE::timer 0.5, 0, sub { say STDERR "timeout"; $cv->send(1) }; - - my $result = $cv->recv; + wait_for_event $x, 0.5, sub { $_[0]->{response_type} == MAP_NOTIFY }; return $window; } @@ -239,6 +256,69 @@ sub focused_ws { } } +# +# Sends an I3_SYNC ClientMessage with a random value to the root window. +# i3 will reply with the same value, but, due to the order of events it +# processes, only after all other events are done. +# +# This can be used to ensure the results of a cmd 'focus left' are pushed to +# X11 and that $x->input_focus returns the correct value afterwards. +# +# See also docs/testsuite for a long explanation +# +sub sync_with_i3 { + my ($x) = @_; + + # Since we need a (mapped) window for receiving a ClientMessage, we create + # one on the first call of sync_with_i3. It will be re-used in all + # subsequent calls. + if (!defined($_sync_window)) { + $_sync_window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => X11::XCB::Rect->new(x => -15, y => -15, width => 10, height => 10 ), + override_redirect => 1, + background_color => '#ff0000', + event_mask => [ 'structure_notify' ], + ); + + $_sync_window->map; + + wait_for_event $x, 0.5, sub { $_[0]->{response_type} == MAP_NOTIFY }; + } + + my $root = $x->root->id; + # Generate a random number to identify this particular ClientMessage. + my $myrnd = int(rand(255)) + 1; + + # Generate a ClientMessage, see xcb_client_message_t + my $msg = pack "CCSLLLLLLL", + CLIENT_MESSAGE, # response_type + 32, # format + 0, # sequence + $root, # destination window + $x->atom(name => 'I3_SYNC')->id, + + $_sync_window->id, # data[0]: our own window id + $myrnd, # data[1]: a random value to identify the request + 0, + 0, + 0; + + # Send it to the root window -- since i3 uses the SubstructureRedirect + # event mask, it will get the ClientMessage. + $x->send_event(0, $root, EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg); + + # now wait until the reply is here + return wait_for_event $x, 1, sub { + my ($event) = @_; + # TODO: const + return 0 unless $event->{response_type} == 161; + + my ($win, $rnd) = unpack "LL", $event->{data}; + return ($rnd == $myrnd); + }; +} + sub does_i3_live { my $tree = i3(get_socket_path())->get_tree->recv; my @nodes = @{$tree->{nodes}}; From 4821b13cae173c7ee598811f82c6502841a73a55 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 13:07:05 +0100 Subject: [PATCH 05/15] tests: lib/i3test: provide wait_for_map and wait_for_unmap These functions should be used instead of calling wait_for_event directly when waiting for MAP_NOTIFY or UNMAP_NOTIFY --- testcases/t/lib/i3test.pm | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 2a51dad6..40e84044 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -36,6 +36,9 @@ our @EXPORT = qw( focused_ws get_socket_path launch_with_config + wait_for_event + wait_for_map + wait_for_unmap ); my $tester = Test::Builder->new(); @@ -99,12 +102,28 @@ sub wait_for_event { }; # Trigger timeout after $timeout seconds (can be fractional) - my $timeout = AE::timer $timeout, 0, sub { say STDERR "timeout"; $cv->send(0) }; + my $timeout = AE::timer $timeout, 0, sub { warn "timeout"; $cv->send(0) }; my $result = $cv->recv; return $result; } +# thin wrapper around wait_for_event which waits for MAP_NOTIFY +# make sure to include 'structure_notify' in the window’s event_mask attribute +sub wait_for_map { + my ($x) = @_; + wait_for_event $x, 1, sub { $_[0]->{response_type} == MAP_NOTIFY }; +} + +# Wrapper around wait_for_event which waits for UNMAP_NOTIFY. Also calls +# sync_with_i3 to make sure i3 also picked up and processed the UnmapNotify +# event. +sub wait_for_unmap { + my ($x) = @_; + wait_for_event $x, 1, sub { $_[0]->{response_type} == UNMAP_NOTIFY }; + sync_with_i3($x); +} + sub open_standard_window { my ($x, $color, $floating) = @_; @@ -286,7 +305,7 @@ sub sync_with_i3 { wait_for_event $x, 0.5, sub { $_[0]->{response_type} == MAP_NOTIFY }; } - my $root = $x->root->id; + my $root = $x->get_root_window(); # Generate a random number to identify this particular ClientMessage. my $myrnd = int(rand(255)) + 1; @@ -386,7 +405,7 @@ sub launch_with_config { # one test case. my $i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >>$ENV{LOGPATH} 2>&1"; my $process = Proc::Background->new($i3cmd); - sleep 1; + sleep 1.25; # force update of the cached socket path in lib/i3test get_socket_path(0); From 4da5b7e784acb0a0daa5a24116c940510a81294c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 13:08:02 +0100 Subject: [PATCH 06/15] tests: use wait_for_{map,unmap} to eliminate more sleep()s --- testcases/t/02-fullscreen.t | 10 ++++-- testcases/t/04-floating.t | 9 +++-- testcases/t/10-dock.t | 24 +++++++------ testcases/t/14-client-leader.t | 33 +++++++++++------- testcases/t/19-match.t | 19 +++++----- testcases/t/33-size-hints.t | 5 ++- testcases/t/50-regress-dock-restart.t | 8 +++-- testcases/t/53-floating-originalsize.t | 3 +- testcases/t/54-regress-multiple-dock.t | 7 ++-- testcases/t/55-floating-split-size.t | 7 ++-- testcases/t/56-fullscreen-focus.t | 14 +++++++- testcases/t/64-kill-win-vs-client.t | 4 +-- testcases/t/65-for_window.t | 48 ++++++++++++++++---------- testcases/t/66-assign.t | 24 ++++++++----- testcases/t/74-border-config.t | 8 ++--- 15 files changed, 139 insertions(+), 84 deletions(-) diff --git a/testcases/t/02-fullscreen.t b/testcases/t/02-fullscreen.t index e83e45ac..ae8c63f6 100644 --- a/testcases/t/02-fullscreen.t +++ b/testcases/t/02-fullscreen.t @@ -42,6 +42,7 @@ my $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => $original_rect, background_color => '#C0C0C0', + event_mask => [ 'structure_notify' ], ); isa_ok($window, 'X11::XCB::Window'); @@ -50,7 +51,7 @@ is_deeply($window->rect, $original_rect, "rect unmodified before mapping"); $window->map; -sleep 0.25; +wait_for_map $x; # open another container to make the window get only half of the screen cmd 'open'; @@ -92,6 +93,7 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => $original_rect, background_color => 61440, + event_mask => [ 'structure_notify' ], ); is_deeply($window->rect, $original_rect, "rect unmodified before mapping"); @@ -99,7 +101,7 @@ is_deeply($window->rect, $original_rect, "rect unmodified before mapping"); $window->fullscreen(1); $window->map; -sleep(0.25); +wait_for_map $x; $new_rect = $window->rect; ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen"); @@ -122,10 +124,12 @@ my $swindow = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => $original_rect, background_color => '#C0C0C0', + event_mask => [ 'structure_notify' ], ); $swindow->map; -sleep 0.25; + +sync_with_i3($x); ok(!$swindow->mapped, 'window not mapped while fullscreen window active'); diff --git a/testcases/t/04-floating.t b/testcases/t/04-floating.t index fcf73f08..d605328d 100644 --- a/testcases/t/04-floating.t +++ b/testcases/t/04-floating.t @@ -17,13 +17,14 @@ my $window = $x->root->create_child( background_color => '#C0C0C0', # replace the type with 'utility' as soon as the coercion works again in X11::XCB window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), + event_mask => [ 'structure_notify' ], ); isa_ok($window, 'X11::XCB::Window'); $window->map; -sleep 0.25; +wait_for_map $x; my ($absolute, $top) = $window->rect; @@ -40,13 +41,14 @@ $window = $x->root->create_child( rect => [ 1, 1, 80, 90], background_color => '#C0C0C0', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), + event_mask => [ 'structure_notify' ], ); isa_ok($window, 'X11::XCB::Window'); $window->map; -sleep 0.25; +wait_for_map $x; ($absolute, $top) = $window->rect; @@ -70,13 +72,14 @@ $window = $x->root->create_child( rect => [ 1, 1, 80, 90], background_color => '#C0C0C0', #window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), + event_mask => [ 'structure_notify' ], ); isa_ok($window, 'X11::XCB::Window'); $window->map; -sleep 0.25; +wait_for_map $x; cmd 'floating enable'; diff --git a/testcases/t/10-dock.t b/testcases/t/10-dock.t index 3f0a5195..988d92db 100644 --- a/testcases/t/10-dock.t +++ b/testcases/t/10-dock.t @@ -10,7 +10,6 @@ BEGIN { } my $x = X11::XCB::Connection->new; -my $i3 = i3(get_socket_path()); ##################################################################### # verify that there is no dock window yet @@ -36,11 +35,12 @@ my $window = $x->root->create_child( rect => [ 0, 0, 30, 30], background_color => '#FF0000', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + event_mask => [ 'structure_notify' ], ); $window->map; -sleep 0.25; +wait_for_map $x; my $rect = $window->rect; is($rect->width, $primary->rect->width, 'dock client is as wide as the screen'); @@ -67,7 +67,7 @@ is($docknode->{rect}->{height}, 30, 'dock node has unchanged height'); $window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 50, height => 40)); -sleep 0.25; +sync_with_i3 $x; @docked = get_dock_clients('top'); is(@docked, 1, 'one dock client found'); @@ -82,7 +82,7 @@ is($docknode->{rect}->{height}, 40, 'dock height changed'); $window->destroy; -sleep 0.25; +wait_for_unmap $x; @docked = get_dock_clients(); is(@docked, 0, 'no more dock clients'); @@ -96,11 +96,12 @@ $window = $x->root->create_child( rect => [ 0, 1000, 30, 30], background_color => '#FF0000', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + event_mask => [ 'structure_notify' ], ); $window->map; -sleep 0.25; +wait_for_map $x; my $rect = $window->rect; is($rect->width, $primary->rect->width, 'dock client is as wide as the screen'); @@ -111,7 +112,7 @@ is(@docked, 1, 'dock client on bottom'); $window->destroy; -sleep 0.25; +wait_for_unmap $x; @docked = get_dock_clients(); is(@docked, 0, 'no more dock clients'); @@ -125,6 +126,7 @@ $window = $x->root->create_child( rect => [ 0, 1000, 30, 30], background_color => '#FF0000', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + event_mask => [ 'structure_notify' ], ); $window->_create(); @@ -145,14 +147,14 @@ $x->change_property( $window->map; -sleep 0.25; +wait_for_map $x; @docked = get_dock_clients('top'); is(@docked, 1, 'dock client on top'); $window->destroy; -sleep 0.25; +wait_for_unmap $x; @docked = get_dock_clients(); is(@docked, 0, 'no more dock clients'); @@ -162,6 +164,7 @@ $window = $x->root->create_child( rect => [ 0, 1000, 30, 30], background_color => '#FF0000', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + event_mask => [ 'structure_notify' ], ); $window->_create(); @@ -182,7 +185,7 @@ $x->change_property( $window->map; -sleep 0.25; +wait_for_map $x; @docked = get_dock_clients('bottom'); is(@docked, 1, 'dock client on bottom'); @@ -199,12 +202,13 @@ my $fwindow = $x->root->create_child( rect => [ 0, 0, 30, 30], background_color => '#FF0000', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + event_mask => [ 'structure_notify' ], ); $fwindow->transient_for($window); $fwindow->map; -sleep 0.25; +wait_for_map $x; does_i3_live; diff --git a/testcases/t/14-client-leader.t b/testcases/t/14-client-leader.t index 98978eb3..08deabf8 100644 --- a/testcases/t/14-client-leader.t +++ b/testcases/t/14-client-leader.t @@ -9,7 +9,6 @@ BEGIN { } my $x = X11::XCB::Connection->new; -my $i3 = i3(get_socket_path()); my $tmp = fresh_workspace; @@ -25,6 +24,7 @@ my $left = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [0, 0, 30, 30], background_color => '#FF0000', + event_mask => [ 'structure_notify' ], ); $left->name('Left'); @@ -34,12 +34,14 @@ my $right = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [0, 0, 30, 30], background_color => '#FF0000', + event_mask => [ 'structure_notify' ], ); $right->name('Right'); $right->map; -sleep 0.25; +ok(wait_for_map($x), 'left window mapped'); +ok(wait_for_map($x), 'right window mapped'); my ($abs, $rgeom) = $right->rect; @@ -48,13 +50,14 @@ my $child = $x->root->create_child( rect => [ 0, 0, 30, 30 ], background_color => '#C0C0C0', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), + event_mask => [ 'structure_notify' ], ); $child->name('Child window'); $child->client_leader($right); $child->map; -sleep 0.25; +ok(wait_for_map($x), 'child window mapped'); my $cgeom; ($abs, $cgeom) = $child->rect; @@ -65,13 +68,14 @@ my $child2 = $x->root->create_child( rect => [ 0, 0, 30, 30 ], background_color => '#C0C0C0', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), + event_mask => [ 'structure_notify' ], ); $child2->name('Child window 2'); $child2->client_leader($left); $child2->map; -sleep 0.25; +ok(wait_for_map($x), 'second child window mapped'); ($abs, $cgeom) = $child2->rect; cmp_ok(($cgeom->x + $cgeom->width), '<', $rgeom->x, 'child above left window'); @@ -83,12 +87,13 @@ my $fwindow = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30], background_color => '#FF0000', + event_mask => [ 'structure_notify' ], ); $fwindow->transient_for($right); $fwindow->map; -sleep 0.25; +ok(wait_for_map($x), 'transient window mapped'); my ($absolute, $top) = $fwindow->rect; ok($absolute->{x} != 0 && $absolute->{y} != 0, 'i3 did not map it to (0x0)'); @@ -101,15 +106,16 @@ SKIP: { ##################################################################### my $window = $x->root->create_child( -class => WINDOW_CLASS_INPUT_OUTPUT, -rect => [ 0, 0, 30, 30 ], -background_color => '#C0C0C0', + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#C0C0C0', + event_mask => [ 'structure_notify' ], ); $window->name('Parent window'); $window->map; -sleep 0.25; +ok(wait_for_map($x), 'parent window mapped'); ######################################################################### # Switch to a different workspace and open a child window. It should be opened @@ -118,16 +124,17 @@ sleep 0.25; fresh_workspace; my $child = $x->root->create_child( -class => WINDOW_CLASS_INPUT_OUTPUT, -rect => [ 0, 0, 30, 30 ], -background_color => '#C0C0C0', + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#C0C0C0', + event_mask => [ 'structure_notify' ], ); $child->name('Child window'); $child->client_leader($window); $child->map; -sleep 0.25; +ok(wait_for_map($x), 'child window mapped'); isnt($x->input_focus, $child->id, "Child window focused"); diff --git a/testcases/t/19-match.t b/testcases/t/19-match.t index 93822f1f..aab54456 100644 --- a/testcases/t/19-match.t +++ b/testcases/t/19-match.t @@ -35,8 +35,7 @@ cmd 'nop now killing the window'; my $id = $win->{id}; cmd qq|[con_id="$id"] kill|; -# give i3 some time to pick up the UnmapNotify event -sleep 0.25; +wait_for_unmap $x; cmd 'nop checking if its gone'; $content = get_ws_content($tmp); @@ -75,25 +74,27 @@ my $left = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#0000ff', + event_mask => [ 'structure_notify' ], ); $left->_create; set_wm_class($left->id, 'special', 'special'); $left->name('left'); $left->map; -sleep 0.25; +ok(wait_for_map($x), 'left window mapped'); my $right = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#0000ff', + event_mask => [ 'structure_notify' ], ); $right->_create; set_wm_class($right->id, 'special', 'special'); $right->name('right'); $right->map; -sleep 0.25; +ok(wait_for_map($x), 'right window mapped'); # two windows should be here $content = get_ws_content($tmp); @@ -116,13 +117,14 @@ $left = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#0000ff', + event_mask => [ 'structure_notify' ], ); $left->_create; set_wm_class($left->id, 'special7', 'special7'); $left->name('left'); $left->map; -sleep 0.25; +ok(wait_for_map($x), 'left window mapped'); # two windows should be here $content = get_ws_content($tmp); @@ -130,7 +132,7 @@ ok(@{$content} == 1, 'window opened'); cmd '[class="^special[0-9]$"] kill'; -sleep 0.25; +wait_for_unmap $x; $content = get_ws_content($tmp); is(@{$content}, 0, 'window killed'); @@ -145,13 +147,14 @@ $left = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#0000ff', + event_mask => [ 'structure_notify' ], ); $left->_create; set_wm_class($left->id, 'special7', 'special7'); $left->name('ä 3'); $left->map; -sleep 0.25; +ok(wait_for_map($x), 'left window mapped'); # two windows should be here $content = get_ws_content($tmp); @@ -159,7 +162,7 @@ ok(@{$content} == 1, 'window opened'); cmd '[title="^\w [3]$"] kill'; -sleep 0.25; +wait_for_unmap $x; $content = get_ws_content($tmp); is(@{$content}, 0, 'window killed'); diff --git a/testcases/t/33-size-hints.t b/testcases/t/33-size-hints.t index e212dc72..0d607dbc 100644 --- a/testcases/t/33-size-hints.t +++ b/testcases/t/33-size-hints.t @@ -5,8 +5,6 @@ # use i3test; -my $i3 = i3(get_socket_path()); - my $x = X11::XCB::Connection->new; my $tmp = fresh_workspace; @@ -17,6 +15,7 @@ my $win = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30), background_color => '#C0C0C0', + event_mask => [ 'structure_notify' ], ); # XXX: we should check screen size. in screens with an AR of 2.0, @@ -28,7 +27,7 @@ $aspect->max_num(600); $aspect->max_den(300); $win->_create; $win->map; -sleep 0.25; +wait_for_map $x; $win->hints->aspect($aspect); $x->flush; diff --git a/testcases/t/50-regress-dock-restart.t b/testcases/t/50-regress-dock-restart.t index a4f7bebc..caadb9e3 100644 --- a/testcases/t/50-regress-dock-restart.t +++ b/testcases/t/50-regress-dock-restart.t @@ -31,11 +31,12 @@ my $window = $x->root->create_child( rect => [ 0, 0, 30, 30], background_color => '#FF0000', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + event_mask => [ 'structure_notify' ], ); $window->map; -sleep 0.25; +wait_for_map $x; ##################################################################### # check that we can find it in the layout tree at the expected position @@ -69,7 +70,7 @@ is($docknode->{rect}->{height}, 30, 'dock node has unchanged height after restar $window->destroy; -sleep 0.25; +wait_for_unmap $x; @docked = get_dock_clients; is(@docked, 0, 'no dock clients found'); @@ -84,11 +85,12 @@ $window = $x->root->create_child( rect => [ 0, 0, 30, 20], background_color => '#00FF00', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + event_mask => [ 'structure_notify' ], ); $window->map; -sleep 0.25; +wait_for_map $x; @docked = get_dock_clients; is(@docked, 1, 'one dock client found'); diff --git a/testcases/t/53-floating-originalsize.t b/testcases/t/53-floating-originalsize.t index 1daa2709..b1dc6199 100644 --- a/testcases/t/53-floating-originalsize.t +++ b/testcases/t/53-floating-originalsize.t @@ -15,13 +15,14 @@ my $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 400, 150], background_color => '#C0C0C0', + event_mask => [ 'structure_notify' ], ); isa_ok($window, 'X11::XCB::Window'); $window->map; -sleep 0.25; +wait_for_map $x; my ($absolute, $top) = $window->rect; diff --git a/testcases/t/54-regress-multiple-dock.t b/testcases/t/54-regress-multiple-dock.t index 36070db1..b1809493 100644 --- a/testcases/t/54-regress-multiple-dock.t +++ b/testcases/t/54-regress-multiple-dock.t @@ -11,7 +11,6 @@ BEGIN { } my $x = X11::XCB::Connection->new; -my $i3 = i3(get_socket_path()); my $tmp = fresh_workspace; @@ -33,11 +32,12 @@ my $first = $x->root->create_child( rect => [ 0, 0, 30, 30], background_color => '#FF0000', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + event_mask => [ 'structure_notify' ], ); $first->map; -sleep 0.25; +wait_for_map $x; ##################################################################### # Open a second dock client @@ -48,11 +48,12 @@ my $second = $x->root->create_child( rect => [ 0, 0, 30, 30], background_color => '#FF0000', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + event_mask => [ 'structure_notify' ], ); $second->map; -sleep 0.25; +wait_for_map $x; ##################################################################### # Kill the second dock client diff --git a/testcases/t/55-floating-split-size.t b/testcases/t/55-floating-split-size.t index ecffbb12..0ba5cdc4 100644 --- a/testcases/t/55-floating-split-size.t +++ b/testcases/t/55-floating-split-size.t @@ -12,7 +12,6 @@ BEGIN { } my $x = X11::XCB::Connection->new; -my $i3 = i3(get_socket_path()); my $tmp = fresh_workspace; @@ -24,11 +23,12 @@ my $first = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 200, 80], background_color => '#FF0000', + event_mask => [ 'structure_notify' ], ); $first->map; -sleep 0.25; +wait_for_map $x; ##################################################################### # Open a second window with 300x90 @@ -38,11 +38,12 @@ my $second = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 300, 90], background_color => '#00FF00', + event_mask => [ 'structure_notify' ], ); $second->map; -sleep 0.25; +wait_for_map $x; ##################################################################### # Set the parent to floating diff --git a/testcases/t/56-fullscreen-focus.t b/testcases/t/56-fullscreen-focus.t index 89af6219..2eb35a6b 100644 --- a/testcases/t/56-fullscreen-focus.t +++ b/testcases/t/56-fullscreen-focus.t @@ -44,7 +44,19 @@ cmd 'fullscreen'; # Open a third window ##################################################################### -my $third = open_standard_window($x, '#0000ff'); +#my $third = open_standard_window($x, '#0000ff'); + +my $third = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30 ), + background_color => '#0000ff', + event_mask => [ 'structure_notify' ], +); + +$third->name('Third window'); +$third->map; + +sync_with_i3 $x; diag("third = " . $third->id); diff --git a/testcases/t/64-kill-win-vs-client.t b/testcases/t/64-kill-win-vs-client.t index 2e0669ba..89077500 100644 --- a/testcases/t/64-kill-win-vs-client.t +++ b/testcases/t/64-kill-win-vs-client.t @@ -4,8 +4,6 @@ # Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when # unmapped. # -use X11::XCB qw(:all); -use X11::XCB::Connection; use i3test; my $x = X11::XCB::Connection->new; @@ -18,6 +16,8 @@ sub two_windows { my $first = open_standard_window($x); my $second = open_standard_window($x); + sync_with_i3 $x; + is($x->input_focus, $second->id, 'second window focused'); ok(@{get_ws_content($tmp)} == 2, 'two containers opened'); diff --git a/testcases/t/65-for_window.t b/testcases/t/65-for_window.t index 1746d117..0dbe99c3 100644 --- a/testcases/t/65-for_window.t +++ b/testcases/t/65-for_window.t @@ -32,18 +32,19 @@ my $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#00ff00', + event_mask => [ 'structure_notify' ], ); $window->name('Border window'); $window->map; -sleep 0.25; +wait_for_map $x; my @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'normal', 'normal border'); $window->unmap; -sleep 0.25; +wait_for_unmap $x; my @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 0, 'no more nodes'); @@ -53,6 +54,7 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#00ff00', + event_mask => [ 'structure_notify' ], ); $window->_create; @@ -79,14 +81,14 @@ sub set_wm_class { set_wm_class($window->id, 'borderless', 'borderless'); $window->name('Borderless window'); $window->map; -sleep 0.25; +wait_for_map $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'none', 'no border'); $window->unmap; -sleep 0.25; +wait_for_unmap $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 0, 'no more nodes'); @@ -113,24 +115,25 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#00ff00', + event_mask => [ 'structure_notify' ], ); $window->name('special title'); $window->map; -sleep 0.25; +wait_for_map $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'normal', 'normal border'); $window->name('special borderless title'); -sleep 0.25; +sync_with_i3 $x; @content = @{get_ws_content($tmp)}; is($content[0]->{border}, 'none', 'no border'); $window->name('special title'); -sleep 0.25; +sync_with_i3 $x; cmd 'border normal'; @@ -138,13 +141,13 @@ cmd 'border normal'; is($content[0]->{border}, 'normal', 'border reset to normal'); $window->name('special borderless title'); -sleep 0.25; +sync_with_i3 $x; @content = @{get_ws_content($tmp)}; is($content[0]->{border}, 'normal', 'still normal border'); $window->unmap; -sleep 0.25; +wait_for_unmap $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 0, 'no more nodes'); @@ -172,11 +175,12 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#00ff00', + event_mask => [ 'structure_notify' ], ); $window->name('special mark title'); $window->map; -sleep 0.25; +wait_for_map $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); @@ -215,6 +219,7 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#00ff00', + event_mask => [ 'structure_notify' ], ); $window->_create; @@ -222,14 +227,14 @@ $window->_create; set_wm_class($window->id, 'borderless', 'borderless'); $window->name('usethis'); $window->map; -sleep 0.25; +wait_for_map $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'none', 'no border'); $window->unmap; -sleep 0.25; +wait_for_unmap $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 0, 'no nodes on this workspace now'); @@ -237,7 +242,7 @@ cmp_ok(@content, '==', 0, 'no nodes on this workspace now'); set_wm_class($window->id, 'borderless', 'borderless'); $window->name('notthis'); $window->map; -sleep 0.25; +wait_for_map $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); @@ -264,6 +269,7 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#00ff00', + event_mask => [ 'structure_notify' ], ); $window->_create; @@ -271,7 +277,7 @@ $window->_create; set_wm_class($window->id, 'bar', 'foo'); $window->name('usethis'); $window->map; -sleep 0.25; +wait_for_map $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); @@ -298,6 +304,7 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#00ff00', + event_mask => [ 'structure_notify' ], ); $window->_create; @@ -305,7 +312,7 @@ $window->_create; set_wm_class($window->id, 'bar', 'foo'); $window->name('usethis'); $window->map; -sleep 0.25; +wait_for_map $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); @@ -334,6 +341,7 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#00ff00', + event_mask => [ 'structure_notify' ], ); $window->_create; @@ -341,7 +349,7 @@ $window->_create; set_wm_class($window->id, 'bar', 'foo'); $window->name('usethis'); $window->map; -sleep 0.25; +wait_for_map $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); @@ -370,6 +378,7 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#00ff00', + event_mask => [ 'structure_notify' ], ); $window->_create; @@ -388,7 +397,7 @@ $x->change_property( $window->name('usethis'); $window->map; -sleep 0.25; +wait_for_map $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); @@ -418,13 +427,14 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#00ff00', + event_mask => [ 'structure_notify' ], ); $window->_create; $window->name('usethis'); $window->map; -sleep 0.25; +wait_for_map $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); @@ -444,7 +454,7 @@ $x->change_property( $x->flush; -sleep 0.25; +sync_with_i3 $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); diff --git a/testcases/t/66-assign.t b/testcases/t/66-assign.t index b8366917..cc41b7af 100644 --- a/testcases/t/66-assign.t +++ b/testcases/t/66-assign.t @@ -50,13 +50,14 @@ my $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#0000ff', + event_mask => [ 'structure_notify' ], ); $window->_create; set_wm_class($window->id, 'special', 'special'); $window->name('special window'); $window->map; -sleep 0.25; +wait_for_map $x; ok(@{get_ws_content($tmp)} == 1, 'special window got managed to current (random) workspace'); @@ -64,8 +65,6 @@ exit_gracefully($process->pid); $window->destroy; -sleep 0.25; - ##################################################################### # start a window and see that it gets assigned to a formerly unused # workspace @@ -89,13 +88,14 @@ my $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#0000ff', + event_mask => [ 'structure_notify' ], ); $window->_create; set_wm_class($window->id, 'special', 'special'); $window->name('special window'); $window->map; -sleep 0.25; +wait_for_map $x; ok(@{get_ws_content($tmp)} == 0, 'still no containers'); ok("targetws" ~~ @{get_workspace_names()}, 'targetws exists'); @@ -128,13 +128,18 @@ my $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#0000ff', + event_mask => [ 'structure_notify' ], ); $window->_create; set_wm_class($window->id, 'special', 'special'); $window->name('special window'); $window->map; -sleep 0.25; + +# We use sync_with_i3 instead of wait_for_map here because i3 will not actually +# map the window -- it will be assigned to a different workspace and will only +# be mapped once you switch to that workspace +sync_with_i3 $x; ok(@{get_ws_content($tmp)} == 0, 'still no containers'); ok(@{get_ws_content('targetws')} == 2, 'two containers on targetws'); @@ -164,13 +169,14 @@ my $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#0000ff', + event_mask => [ 'structure_notify' ], ); $window->_create; set_wm_class($window->id, 'special', 'special'); $window->name('special window'); $window->map; -sleep 0.25; +wait_for_map $x; my $content = get_ws($tmp); ok(@{$content->{nodes}} == 0, 'no tiling cons'); @@ -204,13 +210,14 @@ my $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#0000ff', + event_mask => [ 'structure_notify' ], ); $window->_create; set_wm_class($window->id, 'SPEcial', 'SPEcial'); $window->name('special window'); $window->map; -sleep 0.25; +wait_for_map $x; my $content = get_ws($tmp); ok(@{$content->{nodes}} == 0, 'no tiling cons'); @@ -249,13 +256,14 @@ my $window = $x->root->create_child( rect => [ 0, 0, 30, 30 ], background_color => '#0000ff', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + event_mask => [ 'structure_notify' ], ); $window->_create; set_wm_class($window->id, 'special', 'special'); $window->name('special window'); $window->map; -sleep 0.25; +wait_for_map $x; my $content = get_ws($tmp); ok(@{$content->{nodes}} == 0, 'no tiling cons'); diff --git a/testcases/t/74-border-config.t b/testcases/t/74-border-config.t index c8a4d73d..d66ef477 100644 --- a/testcases/t/74-border-config.t +++ b/testcases/t/74-border-config.t @@ -6,8 +6,6 @@ # use i3test; -use X11::XCB qw(:all); -use X11::XCB::Connection; my $x = X11::XCB::Connection->new; @@ -84,11 +82,12 @@ $first = $x->root->create_child( background_color => '#C0C0C0', # replace the type with 'utility' as soon as the coercion works again in X11::XCB window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), + event_mask => [ 'structure_notify' ], ); $first->map; -sleep 0.25; +wait_for_map $x; my $wscontent = get_ws($tmp); my @floating = @{$wscontent->{floating_nodes}}; @@ -123,11 +122,12 @@ $first = $x->root->create_child( background_color => '#C0C0C0', # replace the type with 'utility' as soon as the coercion works again in X11::XCB window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), + event_mask => [ 'structure_notify' ], ); $first->map; -sleep 0.25; +wait_for_map $x; $wscontent = get_ws($tmp); @floating = @{$wscontent->{floating_nodes}}; From e244a758015b8385f497701dfa94a91b0740fe99 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 13:09:20 +0100 Subject: [PATCH 07/15] tests: complete_run: directly use X11::XCB instead of ::Connection This saves about 0.5s wallclock time due to not starting up Moose/Mouse. This is worthwhile when you develop a new feature and you are often invoking complete_run for one specific test. --- testcases/complete-run.pl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 8f740d8c..74bc507f 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -28,7 +28,7 @@ use AnyEvent::I3 qw(:all); use Try::Tiny; use Getopt::Long; use Time::HiRes qw(sleep); -use X11::XCB::Connection; +use X11::XCB; use IO::Socket::UNIX; # core use POSIX; # core use AnyEvent::Handle; @@ -75,13 +75,14 @@ my $result = GetOptions( my @conns; my @wdisplays; for my $display (@displays) { - try { - my $x = X11::XCB::Connection->new(display => $display); + my $screen; + my $x = X11::XCB->new($display, $screen); + if ($x->has_error) { + say STDERR "WARNING: Not using X11 display $display, could not connect"; + } else { push @conns, $x; push @wdisplays, $display; - } catch { - say STDERR "WARNING: Not using X11 display $display, could not connect"; - }; + } } my $config = slurp('i3-test.config'); From 1a438f12ede41fdfc3406e42c360b425a3fabf7b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 13:13:09 +0100 Subject: [PATCH 08/15] tests: complete-run: display time i3 took for starting up --- testcases/complete-run.pl | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 74bc507f..b356e0d1 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -27,7 +27,7 @@ use File::Basename qw(basename); use AnyEvent::I3 qw(:all); use Try::Tiny; use Getopt::Long; -use Time::HiRes qw(sleep); +use Time::HiRes qw(sleep gettimeofday tv_interval); use X11::XCB; use IO::Socket::UNIX; # core use POSIX; # core @@ -140,6 +140,7 @@ sub take_job { close($fh); my $activate_cv = AnyEvent->condvar; + my $time_before_start = [gettimeofday]; my $start_i3 = sub { # remove the old unix socket unlink("/tmp/nested-$display-activation"); @@ -235,9 +236,14 @@ sub take_job { # This will be called as soon as i3 is running and answered to our # IPC request $activate_cv->cb(sub { - say "cb"; + my $time_activating = [gettimeofday]; + my $start_duration = tv_interval($time_before_start, $time_activating); my ($status) = $activate_cv->recv; - say "complete-run: status = $status"; + if ($dont_start) { + say "[$display] Not starting i3, testcase does that"; + } else { + say "[$display] i3 startup: took " . sprintf("%.2f", $start_duration) . "s, status = $status"; + } say "[$display] Running $test with logfile $logpath"; From c3eb9f6c45b92bf4755ac1696fdf44212e3fc0f4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 13:13:26 +0100 Subject: [PATCH 09/15] tests: complete-run: remove debugging messages --- testcases/complete-run.pl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index b356e0d1..3566d78b 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -159,15 +159,12 @@ sub take_job { 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); @@ -202,7 +199,6 @@ sub take_job { # wait for the reply $hdl->push_read(chunk => 1, => sub { my ($h, $line) = @_; - say "read something from i3"; $activate_cv->send(1); undef $hdl; }); From de5286da594cf2dd7677ee7b0e1da3a2a4991013 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 15:11:37 +0100 Subject: [PATCH 10/15] tests: lib/i3test: Remove open_standard_window, introduce open_window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit open_window has a better API than open_standard_window. It uses named parameters and supplies default values for everything you don’t specify. This way, you can use every feature which X11::XCB::Window supports. --- testcases/t/05-ipc.t | 3 +- testcases/t/06-focus.t | 20 ++--- testcases/t/08-focus-stack.t | 13 +--- testcases/t/10-dock.t | 69 +++++++---------- testcases/t/11-goto.t | 13 +--- testcases/t/12-floating-resize.t | 3 +- testcases/t/13-urgent.t | 4 +- testcases/t/14-client-leader.t | 78 ++++---------------- testcases/t/19-match.t | 2 +- testcases/t/29-focus-after-close.t | 2 +- testcases/t/33-size-hints.t | 8 +- testcases/t/35-floating-focus.t | 40 ++++------ testcases/t/36-floating-ws-empty.t | 2 +- testcases/t/37-floating-unmap.t | 2 +- testcases/t/38-floating-attach.t | 12 +-- testcases/t/39-ws-numbers.t | 12 +-- testcases/t/40-focus-lost.t | 6 +- testcases/t/41-resize.t | 10 +-- testcases/t/45-flattening.t | 6 +- testcases/t/46-floating-reinsert.t | 10 +-- testcases/t/47-regress-floatingmove.t | 6 +- testcases/t/48-regress-floatingmovews.t | 4 +- testcases/t/50-regress-dock-restart.t | 34 +++------ testcases/t/53-floating-originalsize.t | 13 +--- testcases/t/54-regress-multiple-dock.t | 30 ++------ testcases/t/55-floating-split-size.t | 28 ++----- testcases/t/56-fullscreen-focus.t | 18 ++--- testcases/t/57-regress-fullscreen-level-up.t | 3 +- testcases/t/58-wm_take_focus.t | 1 - testcases/t/61-regress-borders-restart.t | 2 +- testcases/t/63-wm-state.t | 14 +--- testcases/t/64-kill-win-vs-client.t | 4 +- testcases/t/65-for_window.t | 2 +- testcases/t/67-workspace_layout.t | 12 +-- testcases/t/68-regress-fullscreen-restart.t | 4 +- testcases/t/70-force_focus_wrapping.t | 12 +-- testcases/t/74-border-config.t | 32 +------- testcases/t/lib/i3test.pm | 59 ++++++++++----- 38 files changed, 207 insertions(+), 386 deletions(-) diff --git a/testcases/t/05-ipc.t b/testcases/t/05-ipc.t index 0d18040e..982ece7e 100644 --- a/testcases/t/05-ipc.t +++ b/testcases/t/05-ipc.t @@ -12,8 +12,7 @@ fresh_workspace; ##################################################################### # Create a window so we can get a focus different from NULL -my $window = open_standard_window($x); -sync_with_i3($x); +my $window = open_window($x); my $focus = $x->input_focus; diff --git a/testcases/t/06-focus.t b/testcases/t/06-focus.t index b3add322..5ded494f 100644 --- a/testcases/t/06-focus.t +++ b/testcases/t/06-focus.t @@ -2,15 +2,9 @@ # vim:ts=4:sw=4:expandtab use i3test; -use X11::XCB qw(:all); - -BEGIN { - use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); -} my $x = X11::XCB::Connection->new; -my $i3 = i3(get_socket_path()); my $tmp = fresh_workspace; ##################################################################### @@ -21,14 +15,9 @@ my $tmp = fresh_workspace; cmd 'layout default'; cmd 'split v'; -my $top = open_standard_window($x); -my $mid = open_standard_window($x); -my $bottom = open_standard_window($x); -##sleep 0.25; - -diag("top id = " . $top->id); -diag("mid id = " . $mid->id); -diag("bottom id = " . $bottom->id); +my $top = open_window($x); +my $mid = open_window($x); +my $bottom = open_window($x); # # Returns the input focus after sending the given command to i3 via IPC @@ -37,7 +26,8 @@ diag("bottom id = " . $bottom->id); sub focus_after { my $msg = shift; - $i3->command($msg)->recv; + cmd $msg; + sync_with_i3 $x; return $x->input_focus; } diff --git a/testcases/t/08-focus-stack.t b/testcases/t/08-focus-stack.t index 33a5884a..b5be284c 100644 --- a/testcases/t/08-focus-stack.t +++ b/testcases/t/08-focus-stack.t @@ -7,28 +7,23 @@ use i3test; my $x = X11::XCB::Connection->new; -my $i3 = i3(get_socket_path()); fresh_workspace; cmd 'split h'; -my $tiled_left = open_standard_window($x); -my $tiled_right = open_standard_window($x); - -sync_with_i3($x); +my $tiled_left = open_window($x); +my $tiled_right = open_window($x); # Get input focus before creating the floating window my $focus = $x->input_focus; # Create a floating window which is smaller than the minimum enforced size of i3 -my $window = open_standard_window($x, undef, 1); -sync_with_i3($x); +my $window = open_floating_window($x); is($x->input_focus, $window->id, 'floating window focused'); $window->unmap; -# TODO: wait for unmap -sync_with_i3($x); +wait_for_unmap($x); is($x->input_focus, $focus, 'Focus correctly restored'); diff --git a/testcases/t/10-dock.t b/testcases/t/10-dock.t index 988d92db..cad54c26 100644 --- a/testcases/t/10-dock.t +++ b/testcases/t/10-dock.t @@ -29,18 +29,9 @@ my $screens = $x->screens; my $primary = first { $_->primary } @{$screens}; # TODO: focus the primary screen before - -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#FF0000', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), - event_mask => [ 'structure_notify' ], -); - -$window->map; - -wait_for_map $x; +my $window = open_window($x, { + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + }); my $rect = $window->rect; is($rect->width, $primary->rect->width, 'dock client is as wide as the screen'); @@ -91,17 +82,11 @@ is(@docked, 0, 'no more dock clients'); # check if it gets placed on bottom (by coordinates) ##################################################################### -$window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 1000, 30, 30], - background_color => '#FF0000', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), - event_mask => [ 'structure_notify' ], -); - -$window->map; - -wait_for_map $x; +$window = open_window($x, { + rect => [ 0, 1000, 30, 30 ], + background_color => '#FF0000', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + }); my $rect = $window->rect; is($rect->width, $primary->rect->width, 'dock client is as wide as the screen'); @@ -121,13 +106,12 @@ is(@docked, 0, 'no more dock clients'); # check if it gets placed on bottom (by hint) ##################################################################### -$window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 1000, 30, 30], - background_color => '#FF0000', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), - event_mask => [ 'structure_notify' ], -); +$window = open_window($x, { + dont_map => 1, + rect => [ 0, 1000, 30, 30 ], + background_color => '#FF0000', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + }); $window->_create(); @@ -159,13 +143,12 @@ wait_for_unmap $x; @docked = get_dock_clients(); is(@docked, 0, 'no more dock clients'); -$window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 1000, 30, 30], - background_color => '#FF0000', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), - event_mask => [ 'structure_notify' ], -); +$window = open_window($x, { + dont_map => 1, + rect => [ 0, 1000, 30, 30 ], + background_color => '#FF0000', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + }); $window->_create(); @@ -197,13 +180,11 @@ $window->destroy; # regression test: transient dock client ##################################################################### -my $fwindow = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#FF0000', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), - event_mask => [ 'structure_notify' ], -); +$fwindow = open_window($x, { + dont_map => 1, + background_color => '#FF0000', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + }); $fwindow->transient_for($window); $fwindow->map; diff --git a/testcases/t/11-goto.t b/testcases/t/11-goto.t index 44cf55ab..5bec5892 100644 --- a/testcases/t/11-goto.t +++ b/testcases/t/11-goto.t @@ -11,7 +11,6 @@ BEGIN { my $x = X11::XCB::Connection->new; -my $i3 = i3(get_socket_path()); my $tmp = fresh_workspace; cmd 'split h'; @@ -20,13 +19,9 @@ cmd 'split h'; # Create two windows and make sure focus switching works ##################################################################### -my $top = open_standard_window($x); -my $mid = open_standard_window($x); -my $bottom = open_standard_window($x); - -diag("top id = " . $top->id); -diag("mid id = " . $mid->id); -diag("bottom id = " . $bottom->id); +my $top = open_window($x); +my $mid = open_window($x); +my $bottom = open_window($x); # # Returns the input focus after sending the given command to i3 via IPC @@ -55,7 +50,7 @@ my $random_mark = sha1_base64(rand()); $focus = focus_after(qq|[con_mark="$random_mark"] focus|); is($focus, $mid->id, "focus unchanged"); -$i3->command("mark $random_mark")->recv; +cmd "mark $random_mark"; $focus = focus_after('focus left'); is($focus, $top->id, "Top window focused"); diff --git a/testcases/t/12-floating-resize.t b/testcases/t/12-floating-resize.t index 1aec9573..ac3387a9 100644 --- a/testcases/t/12-floating-resize.t +++ b/testcases/t/12-floating-resize.t @@ -16,8 +16,7 @@ fresh_workspace; # Create a floating window and see if resizing works ##################################################################### -# Create a floating window -my $window = open_standard_window($x, undef, 1); +my $window = open_floating_window($x); # See if configurerequests cause window movements (they should not) my ($a, $t) = $window->rect; diff --git a/testcases/t/13-urgent.t b/testcases/t/13-urgent.t index 83c36a98..7954408f 100644 --- a/testcases/t/13-urgent.t +++ b/testcases/t/13-urgent.t @@ -19,8 +19,8 @@ my $tmp = fresh_workspace; cmd 'split v'; -my $top = open_standard_window($x); -my $bottom = open_standard_window($x); +my $top = open_window($x); +my $bottom = open_window($x); my @urgent = grep { $_->{urgent} } @{get_ws_content($tmp)}; is(@urgent, 0, 'no window got the urgent flag'); diff --git a/testcases/t/14-client-leader.t b/testcases/t/14-client-leader.t index 08deabf8..6f7ffce0 100644 --- a/testcases/t/14-client-leader.t +++ b/testcases/t/14-client-leader.t @@ -20,40 +20,15 @@ my $tmp = fresh_workspace; # one of both (depending on your screen resolution) will be positioned wrong. #################################################################################### -my $left = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [0, 0, 30, 30], - background_color => '#FF0000', - event_mask => [ 'structure_notify' ], -); - -$left->name('Left'); -$left->map; - -my $right = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [0, 0, 30, 30], - background_color => '#FF0000', - event_mask => [ 'structure_notify' ], -); - -$right->name('Right'); -$right->map; - -ok(wait_for_map($x), 'left window mapped'); -ok(wait_for_map($x), 'right window mapped'); +my $left = open_window($x, { name => 'Left' }); +my $right = open_window($x, { name => 'Right' }); my ($abs, $rgeom) = $right->rect; -my $child = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30 ], - background_color => '#C0C0C0', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), - event_mask => [ 'structure_notify' ], -); - -$child->name('Child window'); +my $child = open_floating_window($x, { + dont_map => 1, + name => 'Child window', + }); $child->client_leader($right); $child->map; @@ -63,15 +38,10 @@ my $cgeom; ($abs, $cgeom) = $child->rect; cmp_ok($cgeom->x, '>=', $rgeom->x, 'Child X >= right container X'); -my $child2 = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30 ], - background_color => '#C0C0C0', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), - event_mask => [ 'structure_notify' ], -); - -$child2->name('Child window 2'); +my $child2 = open_floating_window($x, { + dont_map => 1, + name => 'Child window 2', + }); $child2->client_leader($left); $child2->map; @@ -81,15 +51,7 @@ ok(wait_for_map($x), 'second child window mapped'); cmp_ok(($cgeom->x + $cgeom->width), '<', $rgeom->x, 'child above left window'); # check wm_transient_for - - -my $fwindow = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#FF0000', - event_mask => [ 'structure_notify' ], -); - +my $fwindow = open_window($x, { dont_map => 1 }); $fwindow->transient_for($right); $fwindow->map; @@ -105,14 +67,7 @@ SKIP: { # Create a parent window ##################################################################### -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30 ], - background_color => '#C0C0C0', - event_mask => [ 'structure_notify' ], -); - -$window->name('Parent window'); +my $window = open_window($x, { dont_map => 1, name => 'Parent window' }); $window->map; ok(wait_for_map($x), 'parent window mapped'); @@ -123,14 +78,7 @@ ok(wait_for_map($x), 'parent window mapped'); ######################################################################### fresh_workspace; -my $child = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30 ], - background_color => '#C0C0C0', - event_mask => [ 'structure_notify' ], -); - -$child->name('Child window'); +my $child = open_window($x, { dont_map => 1, name => 'Child window' }); $child->client_leader($window); $child->map; diff --git a/testcases/t/19-match.t b/testcases/t/19-match.t index aab54456..8b9d21d3 100644 --- a/testcases/t/19-match.t +++ b/testcases/t/19-match.t @@ -12,7 +12,7 @@ ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); # Open a new window my $x = X11::XCB::Connection->new; -my $window = open_standard_window($x); +my $window = open_window($x); my $content = get_ws_content($tmp); ok(@{$content} == 1, 'window mapped'); my $win = $content->[0]; diff --git a/testcases/t/29-focus-after-close.t b/testcases/t/29-focus-after-close.t index ac029eb1..8d225613 100644 --- a/testcases/t/29-focus-after-close.t +++ b/testcases/t/29-focus-after-close.t @@ -102,7 +102,7 @@ $first = open_empty_con($i3); $middle = open_empty_con($i3); # XXX: the $right empty con will be filled with the x11 window we are creating afterwards $right = open_empty_con($i3); -my $win = open_standard_window($x, '#00ff00'); +my $win = open_window($x, { background_color => '#00ff00' }); cmd qq|[con_id="$middle"] focus|; $win->destroy; diff --git a/testcases/t/33-size-hints.t b/testcases/t/33-size-hints.t index 0d607dbc..d2d77e8e 100644 --- a/testcases/t/33-size-hints.t +++ b/testcases/t/33-size-hints.t @@ -11,13 +11,7 @@ my $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -my $win = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30), - background_color => '#C0C0C0', - event_mask => [ 'structure_notify' ], -); - +my $win = open_window($x, { dont_map => 1 }); # XXX: we should check screen size. in screens with an AR of 2.0, # this is not a good idea. my $aspect = X11::XCB::Sizehints::Aspect->new; diff --git a/testcases/t/35-floating-focus.t b/testcases/t/35-floating-focus.t index fc94c440..4c5b562f 100644 --- a/testcases/t/35-floating-focus.t +++ b/testcases/t/35-floating-focus.t @@ -13,10 +13,8 @@ my $tmp = fresh_workspace; # 1: see if focus stays the same when toggling tiling/floating mode ############################################################################# -my $first = open_standard_window($x); -my $second = open_standard_window($x); - -sync_with_i3($x); +my $first = open_window($x); +my $second = open_window($x); is($x->input_focus, $second->id, 'second window focused'); @@ -32,11 +30,9 @@ is($x->input_focus, $second->id, 'second window still focused after mode toggle' $tmp = fresh_workspace; -$first = open_standard_window($x); # window 2 -$second = open_standard_window($x); # window 3 -my $third = open_standard_window($x); # window 4 - -sync_with_i3($x); +$first = open_window($x); # window 2 +$second = open_window($x); # window 3 +my $third = open_window($x); # window 4 is($x->input_focus, $third->id, 'last container focused'); @@ -66,11 +62,9 @@ is($x->input_focus, $second->id, 'second con still focused after killing third') $tmp = fresh_workspace; -$first = open_standard_window($x, '#ff0000'); # window 5 -$second = open_standard_window($x, '#00ff00'); # window 6 -my $third = open_standard_window($x, '#0000ff'); # window 7 - -sync_with_i3($x); +$first = open_window($x, '#ff0000'); # window 5 +$second = open_window($x, '#00ff00'); # window 6 +my $third = open_window($x, '#0000ff'); # window 7 is($x->input_focus, $third->id, 'last container focused'); @@ -105,13 +99,11 @@ is($x->input_focus, $first->id, 'first con focused after killing all floating co $tmp = fresh_workspace; -$first = open_standard_window($x, '#ff0000'); # window 5 +$first = open_window($x, { background_color => '#ff0000' }); # window 5 cmd 'split v'; cmd 'layout stacked'; -$second = open_standard_window($x, '#00ff00'); # window 6 -$third = open_standard_window($x, '#0000ff'); # window 7 - -sync_with_i3($x); +$second = open_window($x, { background_color => '#00ff00' }); # window 6 +$third = open_window($x, { background_color => '#0000ff' }); # window 7 is($x->input_focus, $third->id, 'last container focused'); @@ -148,8 +140,8 @@ is($x->input_focus, $first->id, 'first con focused after killing all floating co $tmp = fresh_workspace; -$first = open_standard_window($x, '#ff0000'); # window 8 -$second = open_standard_window($x, '#00ff00'); # window 9 +$first = open_window($x, { background_color => '#ff0000' }); # window 8 +$second = open_window($x, { background_color => '#00ff00' }); # window 9 sync_with_i3($x); @@ -195,9 +187,9 @@ is($x->input_focus, $second->id, 'second (floating) container focused'); $tmp = fresh_workspace; -$first = open_standard_window($x, '#ff0000', 1); # window 10 -$second = open_standard_window($x, '#00ff00', 1); # window 11 -$third = open_standard_window($x, '#0000ff', 1); # window 12 +$first = open_floating_window($x, { background_color => '#ff0000' });# window 10 +$second = open_floating_window($x, { background_color => '#00ff00' }); # window 11 +$third = open_floating_window($x, { background_color => '#0000ff' }); # window 12 sync_with_i3($x); diff --git a/testcases/t/36-floating-ws-empty.t b/testcases/t/36-floating-ws-empty.t index 91467ed3..a6e0e405 100644 --- a/testcases/t/36-floating-ws-empty.t +++ b/testcases/t/36-floating-ws-empty.t @@ -22,7 +22,7 @@ ok(workspace_exists($tmp), "workspace $tmp exists"); my $x = X11::XCB::Connection->new; # Create a floating window which is smaller than the minimum enforced size of i3 -my $window = open_standard_window($x, undef, 1); +my $window = open_floating_window($x); ok($window->mapped, 'Window is mapped'); # switch to a different workspace, see if the window is still mapped? diff --git a/testcases/t/37-floating-unmap.t b/testcases/t/37-floating-unmap.t index b3be1348..ab1a33d3 100644 --- a/testcases/t/37-floating-unmap.t +++ b/testcases/t/37-floating-unmap.t @@ -21,7 +21,7 @@ my $tmp = fresh_workspace; my $x = X11::XCB::Connection->new; # Create a floating window which is smaller than the minimum enforced size of i3 -my $window = open_standard_window($x, undef, 1); +my $window = open_floating_window($x); ok($window->mapped, 'Window is mapped'); # switch to a different workspace, see if the window is still mapped? diff --git a/testcases/t/38-floating-attach.t b/testcases/t/38-floating-attach.t index 411ffa8e..b08190a2 100644 --- a/testcases/t/38-floating-attach.t +++ b/testcases/t/38-floating-attach.t @@ -21,7 +21,7 @@ my $tmp = fresh_workspace; my $x = X11::XCB::Connection->new; # Create a floating window -my $window = open_standard_window($x, undef, 1); +my $window = open_floating_window($x); ok($window->mapped, 'Window is mapped'); my $ws = get_ws($tmp); @@ -31,7 +31,7 @@ is(@{$ws->{floating_nodes}}, 1, 'one floating node'); is(@{$nodes}, 0, 'no tiling nodes'); # Create a tiling window -my $twindow = open_standard_window($x); +my $twindow = open_window($x); ($nodes, $focus) = get_ws_content($tmp); @@ -44,8 +44,8 @@ is(@{$nodes}, 1, 'one tiling node'); $tmp = fresh_workspace; -my $first = open_standard_window($x); -my $second = open_standard_window($x); +my $first = open_window($x); +my $second = open_window($x); cmd 'layout stacked'; @@ -54,14 +54,14 @@ is(@{$ws->{floating_nodes}}, 0, 'no floating nodes so far'); is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)'); # Create a floating window -my $window = open_standard_window($x, undef, 1); +my $window = open_floating_window($x); ok($window->mapped, 'Window is mapped'); $ws = get_ws($tmp); is(@{$ws->{floating_nodes}}, 1, 'one floating nodes'); is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)'); -my $third = open_standard_window($x); +my $third = open_window($x); $ws = get_ws($tmp); diff --git a/testcases/t/39-ws-numbers.t b/testcases/t/39-ws-numbers.t index 3afd281b..31f013ef 100644 --- a/testcases/t/39-ws-numbers.t +++ b/testcases/t/39-ws-numbers.t @@ -30,7 +30,7 @@ check_order('workspace order alright before testing'); cmd "workspace 93"; -open_standard_window($x); +open_window($x); my @ws = @{$i3->get_workspaces->recv}; my @f = grep { defined($_->{num}) && $_->{num} == 93 } @ws; @@ -38,23 +38,23 @@ is(@f, 1, 'ws 93 found by num'); check_order('workspace order alright after opening 93'); cmd "workspace 92"; -open_standard_window($x); +open_window($x); check_order('workspace order alright after opening 92'); cmd "workspace 94"; -open_standard_window($x); +open_window($x); check_order('workspace order alright after opening 94'); cmd "workspace 96"; -open_standard_window($x); +open_window($x); check_order('workspace order alright after opening 96'); cmd "workspace foo"; -open_standard_window($x); +open_window($x); check_order('workspace order alright after opening foo'); cmd "workspace 91"; -open_standard_window($x); +open_window($x); check_order('workspace order alright after opening 91'); done_testing; diff --git a/testcases/t/40-focus-lost.t b/testcases/t/40-focus-lost.t index 1121f124..fb77f01e 100644 --- a/testcases/t/40-focus-lost.t +++ b/testcases/t/40-focus-lost.t @@ -24,9 +24,9 @@ sub check_order { my $tmp = fresh_workspace; -my $left = open_standard_window($x); -my $mid = open_standard_window($x); -my $right = open_standard_window($x); +my $left = open_window($x); +my $mid = open_window($x); +my $right = open_window($x); sync_with_i3($x); diff --git a/testcases/t/41-resize.t b/testcases/t/41-resize.t index 2a3846d8..8691a044 100644 --- a/testcases/t/41-resize.t +++ b/testcases/t/41-resize.t @@ -14,8 +14,8 @@ my $tmp = fresh_workspace; cmd 'split v'; -my $top = open_standard_window($x); -my $bottom = open_standard_window($x); +my $top = open_window($x); +my $bottom = open_window($x); sync_with_i3($x); @@ -54,8 +54,8 @@ $tmp = fresh_workspace; cmd 'split v'; -$top = open_standard_window($x); -$bottom = open_standard_window($x); +$top = open_window($x); +$bottom = open_window($x); cmd 'split h'; cmd 'layout stacked'; @@ -76,7 +76,7 @@ is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%'); $tmp = fresh_workspace; -$top = open_standard_window($x); +$top = open_window($x); cmd 'floating enable'; diff --git a/testcases/t/45-flattening.t b/testcases/t/45-flattening.t index 4a57f211..904252e7 100644 --- a/testcases/t/45-flattening.t +++ b/testcases/t/45-flattening.t @@ -17,9 +17,9 @@ my $x = X11::XCB::Connection->new; my $tmp = fresh_workspace; -my $left = open_standard_window($x); -my $mid = open_standard_window($x); -my $right = open_standard_window($x); +my $left = open_window($x); +my $mid = open_window($x); +my $right = open_window($x); cmd 'move before v'; cmd 'move after h'; diff --git a/testcases/t/46-floating-reinsert.t b/testcases/t/46-floating-reinsert.t index 07f78956..bc1302bb 100644 --- a/testcases/t/46-floating-reinsert.t +++ b/testcases/t/46-floating-reinsert.t @@ -12,11 +12,11 @@ my $x = X11::XCB::Connection->new; my $tmp = fresh_workspace; -my $left = open_standard_window($x); -my $mid = open_standard_window($x); +my $left = open_window($x); +my $mid = open_window($x); cmd 'split v'; -my $bottom = open_standard_window($x); +my $bottom = open_window($x); my ($nodes, $focus) = get_ws_content($tmp); @@ -24,10 +24,8 @@ my ($nodes, $focus) = get_ws_content($tmp); # 1: open a floating window, get it mapped ############################################################################# -my $x = X11::XCB::Connection->new; - # Create a floating window -my $window = open_standard_window($x, undef, 1); +my $window = open_floating_window($x); ok($window->mapped, 'Window is mapped'); ($nodes, $focus) = get_ws_content($tmp); diff --git a/testcases/t/47-regress-floatingmove.t b/testcases/t/47-regress-floatingmove.t index 8425f6d9..771ace32 100644 --- a/testcases/t/47-regress-floatingmove.t +++ b/testcases/t/47-regress-floatingmove.t @@ -16,9 +16,9 @@ my $x = X11::XCB::Connection->new; my $tmp = fresh_workspace; -my $left = open_standard_window($x); -my $mid = open_standard_window($x); -my $right = open_standard_window($x); +my $left = open_window($x); +my $mid = open_window($x); +my $right = open_window($x); # go to workspace level cmd 'level up'; diff --git a/testcases/t/48-regress-floatingmovews.t b/testcases/t/48-regress-floatingmovews.t index 355e697a..6f9dfc40 100644 --- a/testcases/t/48-regress-floatingmovews.t +++ b/testcases/t/48-regress-floatingmovews.t @@ -16,13 +16,13 @@ my $x = X11::XCB::Connection->new; my $tmp = fresh_workspace; # open a tiling window on the first workspace -open_standard_window($x); +open_window($x); #sleep 0.25; my $first = get_focused($tmp); # on a different ws, open a floating window my $otmp = fresh_workspace; -open_standard_window($x); +open_window($x); #sleep 0.25; my $float = get_focused($otmp); cmd 'mode toggle'; diff --git a/testcases/t/50-regress-dock-restart.t b/testcases/t/50-regress-dock-restart.t index caadb9e3..e294e6bd 100644 --- a/testcases/t/50-regress-dock-restart.t +++ b/testcases/t/50-regress-dock-restart.t @@ -11,7 +11,6 @@ BEGIN { } my $x = X11::XCB::Connection->new; -my $i3 = i3(get_socket_path()); my $tmp = fresh_workspace; @@ -26,17 +25,10 @@ is(@docked, 0, 'no dock clients yet'); # open a dock client -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#FF0000', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), - event_mask => [ 'structure_notify' ], -); - -$window->map; - -wait_for_map $x; +my $window = open_window($x, { + background_color => '#FF0000', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + }); ##################################################################### # check that we can find it in the layout tree at the expected position @@ -79,18 +71,12 @@ is(@docked, 0, 'no dock clients found'); # create a dock client with a 1px border ##################################################################### -$window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - border => 1, - rect => [ 0, 0, 30, 20], - background_color => '#00FF00', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), - event_mask => [ 'structure_notify' ], -); - -$window->map; - -wait_for_map $x; +$window = open_window($x, { + border => 1, + rect => [ 0, 0, 30, 20 ], + background_color => '#00FF00', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + }); @docked = get_dock_clients; is(@docked, 1, 'one dock client found'); diff --git a/testcases/t/53-floating-originalsize.t b/testcases/t/53-floating-originalsize.t index b1dc6199..db0b6e9c 100644 --- a/testcases/t/53-floating-originalsize.t +++ b/testcases/t/53-floating-originalsize.t @@ -11,18 +11,7 @@ my $tmp = fresh_workspace; my $x = X11::XCB::Connection->new; # Create a floating window which is smaller than the minimum enforced size of i3 -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 400, 150], - background_color => '#C0C0C0', - event_mask => [ 'structure_notify' ], -); - -isa_ok($window, 'X11::XCB::Window'); - -$window->map; - -wait_for_map $x; +my $window = open_window($x, { rect => [ 0, 0, 400, 150 ] }); my ($absolute, $top) = $window->rect; diff --git a/testcases/t/54-regress-multiple-dock.t b/testcases/t/54-regress-multiple-dock.t index b1809493..21cb9696 100644 --- a/testcases/t/54-regress-multiple-dock.t +++ b/testcases/t/54-regress-multiple-dock.t @@ -27,33 +27,19 @@ is(@docked, 0, 'no dock clients yet'); # open a dock client ##################################################################### -my $first = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#FF0000', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), - event_mask => [ 'structure_notify' ], -); - -$first->map; - -wait_for_map $x; +my $first = open_window($x, { + background_color => '#FF0000', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + }); ##################################################################### # Open a second dock client ##################################################################### -my $second = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#FF0000', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), - event_mask => [ 'structure_notify' ], -); - -$second->map; - -wait_for_map $x; +my $second = open_window($x, { + background_color => '#FF0000', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + }); ##################################################################### # Kill the second dock client diff --git a/testcases/t/55-floating-split-size.t b/testcases/t/55-floating-split-size.t index 0ba5cdc4..5de05e8b 100644 --- a/testcases/t/55-floating-split-size.t +++ b/testcases/t/55-floating-split-size.t @@ -19,31 +19,19 @@ my $tmp = fresh_workspace; # open a window with 200x80 ##################################################################### -my $first = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 200, 80], - background_color => '#FF0000', - event_mask => [ 'structure_notify' ], -); - -$first->map; - -wait_for_map $x; +my $first = open_window($x, { + rect => [ 0, 0, 200, 80], + background_color => '#FF0000', + }); ##################################################################### # Open a second window with 300x90 ##################################################################### -my $second = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 300, 90], - background_color => '#00FF00', - event_mask => [ 'structure_notify' ], -); - -$second->map; - -wait_for_map $x; +my $second = open_window($x, { + rect => [ 0, 0, 300, 90], + background_color => '#00FF00', + }); ##################################################################### # Set the parent to floating diff --git a/testcases/t/56-fullscreen-focus.t b/testcases/t/56-fullscreen-focus.t index 2eb35a6b..a559b5a5 100644 --- a/testcases/t/56-fullscreen-focus.t +++ b/testcases/t/56-fullscreen-focus.t @@ -20,7 +20,7 @@ my $tmp = fresh_workspace; # open the left window ##################################################################### -my $left = open_standard_window($x, '#ff0000'); +my $left = open_window($x, { background_color => '#ff0000' }); is($x->input_focus, $left->id, 'left window focused'); @@ -30,7 +30,7 @@ diag("left = " . $left->id); # Open the right window ##################################################################### -my $right = open_standard_window($x, '#00ff00'); +my $right = open_window($x, { background_color => '#00ff00' }); diag("right = " . $right->id); @@ -44,16 +44,12 @@ cmd 'fullscreen'; # Open a third window ##################################################################### -#my $third = open_standard_window($x, '#0000ff'); +my $third = open_window($x, { + background_color => '#0000ff', + name => 'Third window', + dont_map => 1, + }); -my $third = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30 ), - background_color => '#0000ff', - event_mask => [ 'structure_notify' ], -); - -$third->name('Third window'); $third->map; sync_with_i3 $x; diff --git a/testcases/t/57-regress-fullscreen-level-up.t b/testcases/t/57-regress-fullscreen-level-up.t index 3e0b2fe1..7a101dbc 100644 --- a/testcases/t/57-regress-fullscreen-level-up.t +++ b/testcases/t/57-regress-fullscreen-level-up.t @@ -11,7 +11,6 @@ BEGIN { } my $x = X11::XCB::Connection->new; -my $i3 = i3(get_socket_path()); my $tmp = fresh_workspace; @@ -19,7 +18,7 @@ my $tmp = fresh_workspace; # open a window, verify it’s not in fullscreen mode ##################################################################### -my $win = open_standard_window($x); +my $win = open_window($x); my $nodes = get_ws_content $tmp; is(@$nodes, 1, 'exactly one client'); diff --git a/testcases/t/58-wm_take_focus.t b/testcases/t/58-wm_take_focus.t index 04c785ae..372f41cd 100644 --- a/testcases/t/58-wm_take_focus.t +++ b/testcases/t/58-wm_take_focus.t @@ -17,7 +17,6 @@ BEGIN { } my $x = X11::XCB::Connection->new; -my $i3 = i3(get_socket_path()); subtest 'Window without WM_TAKE_FOCUS', sub { diff --git a/testcases/t/61-regress-borders-restart.t b/testcases/t/61-regress-borders-restart.t index 1acf6c66..c5e3ef80 100644 --- a/testcases/t/61-regress-borders-restart.t +++ b/testcases/t/61-regress-borders-restart.t @@ -12,7 +12,7 @@ use i3test; my $x = X11::XCB::Connection->new; my $i3 = i3(get_socket_path()); my $tmp = fresh_workspace; -my $window = open_standard_window($x); +my $window = open_window($x); sub get_border_style { my @content = @{get_ws_content($tmp)}; diff --git a/testcases/t/63-wm-state.t b/testcases/t/63-wm-state.t index b9161400..e55d8682 100644 --- a/testcases/t/63-wm-state.t +++ b/testcases/t/63-wm-state.t @@ -7,27 +7,17 @@ use X11::XCB qw(:all); use i3test; -BEGIN { - use_ok('X11::XCB::Window'); - use_ok('X11::XCB::Event::Generic'); - use_ok('X11::XCB::Event::MapNotify'); - use_ok('X11::XCB::Event::ClientMessage'); -} - my $x = X11::XCB::Connection->new; -my $window = open_standard_window($x); +my $window = open_window($x); sync_with_i3($x); -diag('window mapped'); - is($window->state, ICCCM_WM_STATE_NORMAL, 'WM_STATE normal'); $window->unmap; -# TODO: wait for unmapnotify -sync_with_i3($x); +wait_for_unmap $x; is($window->state, ICCCM_WM_STATE_WITHDRAWN, 'WM_STATE withdrawn'); diff --git a/testcases/t/64-kill-win-vs-client.t b/testcases/t/64-kill-win-vs-client.t index 89077500..ef45a789 100644 --- a/testcases/t/64-kill-win-vs-client.t +++ b/testcases/t/64-kill-win-vs-client.t @@ -13,8 +13,8 @@ sub two_windows { ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); - my $first = open_standard_window($x); - my $second = open_standard_window($x); + my $first = open_window($x); + my $second = open_window($x); sync_with_i3 $x; diff --git a/testcases/t/65-for_window.t b/testcases/t/65-for_window.t index 0dbe99c3..36a20ea9 100644 --- a/testcases/t/65-for_window.t +++ b/testcases/t/65-for_window.t @@ -186,7 +186,7 @@ wait_for_map $x; cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'none', 'no border'); -my $other = open_standard_window($x); +my $other = open_window($x); @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 2, 'two nodes'); diff --git a/testcases/t/67-workspace_layout.t b/testcases/t/67-workspace_layout.t index 0e07ebf7..6ff3f15b 100644 --- a/testcases/t/67-workspace_layout.t +++ b/testcases/t/67-workspace_layout.t @@ -27,8 +27,8 @@ my $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -my $first = open_standard_window($x); -my $second = open_standard_window($x); +my $first = open_window($x); +my $second = open_window($x); sync_with_i3($x); @@ -56,8 +56,8 @@ $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -$first = open_standard_window($x); -$second = open_standard_window($x); +$first = open_window($x); +$second = open_window($x); sync_with_i3($x); @@ -72,8 +72,8 @@ is($content[0]->{layout}, 'stacked', 'layout stacked'); ##################################################################### cmd 'focus parent'; -my $right_top = open_standard_window($x); -my $right_bot = open_standard_window($x); +my $right_top = open_window($x); +my $right_bot = open_window($x); @content = @{get_ws_content($tmp)}; is(@content, 2, 'two cons at workspace level after focus parent'); diff --git a/testcases/t/68-regress-fullscreen-restart.t b/testcases/t/68-regress-fullscreen-restart.t index fcc9ac7e..1418b402 100644 --- a/testcases/t/68-regress-fullscreen-restart.t +++ b/testcases/t/68-regress-fullscreen-restart.t @@ -11,8 +11,8 @@ my $x = X11::XCB::Connection->new; fresh_workspace; -open_standard_window($x); -open_standard_window($x); +open_window($x); +open_window($x); cmd 'layout stacking'; sleep 1; diff --git a/testcases/t/70-force_focus_wrapping.t b/testcases/t/70-force_focus_wrapping.t index cf1c3216..2aa5407d 100644 --- a/testcases/t/70-force_focus_wrapping.t +++ b/testcases/t/70-force_focus_wrapping.t @@ -25,13 +25,13 @@ my $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -my $first = open_standard_window($x); -my $second = open_standard_window($x); +my $first = open_window($x); +my $second = open_window($x); cmd 'layout tabbed'; cmd 'focus parent'; -my $third = open_standard_window($x); +my $third = open_window($x); is($x->input_focus, $third->id, 'third window focused'); cmd 'focus left'; @@ -66,13 +66,13 @@ $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -$first = open_standard_window($x); -$second = open_standard_window($x); +$first = open_window($x); +$second = open_window($x); cmd 'layout tabbed'; cmd 'focus parent'; -$third = open_standard_window($x); +$third = open_window($x); sync_with_i3($x); diff --git a/testcases/t/74-border-config.t b/testcases/t/74-border-config.t index d66ef477..617e37b9 100644 --- a/testcases/t/74-border-config.t +++ b/testcases/t/74-border-config.t @@ -25,7 +25,7 @@ my $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -my $first = open_standard_window($x); +my $first = open_window($x); my @content = @{get_ws_content($tmp)}; ok(@content == 1, 'one container opened'); @@ -51,7 +51,7 @@ $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -$first = open_standard_window($x); +$first = open_window($x); @content = @{get_ws_content($tmp)}; ok(@content == 1, 'one container opened'); @@ -75,19 +75,7 @@ $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -# Create a floating window which is smaller than the minimum enforced size of i3 -$first = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#C0C0C0', - # replace the type with 'utility' as soon as the coercion works again in X11::XCB - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), - event_mask => [ 'structure_notify' ], -); - -$first->map; - -wait_for_map $x; +$first = open_floating_window($x); my $wscontent = get_ws($tmp); my @floating = @{$wscontent->{floating_nodes}}; @@ -115,19 +103,7 @@ $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -# Create a floating window which is smaller than the minimum enforced size of i3 -$first = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#C0C0C0', - # replace the type with 'utility' as soon as the coercion works again in X11::XCB - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), - event_mask => [ 'structure_notify' ], -); - -$first->map; - -wait_for_map $x; +$first = open_floating_window($x); $wscontent = get_ws($tmp); @floating = @{$wscontent->{floating_nodes}}; diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 40e84044..87cb769d 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -26,7 +26,8 @@ our @EXPORT = qw( get_ws get_focused open_empty_con - open_standard_window + open_window + open_floating_window get_dock_clients cmd sync_with_i3 @@ -124,33 +125,53 @@ sub wait_for_unmap { sync_with_i3($x); } -sub open_standard_window { - my ($x, $color, $floating) = @_; +# +# Opens a new window (see X11::XCB::Window), maps it, waits until it got mapped +# and synchronizes with i3. +# +# set dont_map to a true value to avoid mapping +# +# default values: +# class => WINDOW_CLASS_INPUT_OUTPUT +# rect => [ 0, 0, 30, 30 ] +# background_color => '#c0c0c0' +# event_mask => [ 'structure_notify' ] +# name => 'Window ' +# +sub open_window { + my ($x, $args) = @_; + my %args = ($args ? %$args : ()); - $color ||= '#c0c0c0'; + my $dont_map = delete $args{dont_map}; - # We cannot use a hashref here because create_child expands the arguments into an array - my @args = ( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30 ), - background_color => $color, - event_mask => [ 'structure_notify' ], - ); + $args{class} = WINDOW_CLASS_INPUT_OUTPUT unless exists $args{class}; + $args{rect} = [ 0, 0, 30, 30 ] unless exists $args{rect}; + $args{background_color} = '#c0c0c0' unless exists $args{background_color}; + $args{event_mask} = [ 'structure_notify' ] unless exists $args{event_mask}; + $args{name} = 'Window ' . counter_window() unless exists $args{name}; - if (defined($floating) && $floating) { - @args = (@args, window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY')); - } + my $window = $x->root->create_child(%args); - my $window = $x->root->create_child(@args); + return $window if $dont_map; - $window->name('Window ' . counter_window()); $window->map; - - wait_for_event $x, 0.5, sub { $_[0]->{response_type} == MAP_NOTIFY }; - + wait_for_map($x); + # We sync with i3 here to make sure $x->input_focus is updated. + sync_with_i3($x); return $window; } +# Thin wrapper around open_window which sets window_type to +# _NET_WM_WINDOW_TYPE_UTILITY to make the window floating. +sub open_floating_window { + my ($x, $args) = @_; + my %args = ($args ? %$args : ()); + + $args{window_type} = $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'); + + return open_window($x, \%args); +} + sub open_empty_con { my ($i3) = @_; From 378611c11c2ab113b2bbd61f6b69a1dd7f610002 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 15:38:31 +0100 Subject: [PATCH 11/15] tests: refactor t/58-wm_take_focus to use wait_for_event --- testcases/t/58-wm_take_focus.t | 98 +++++----------------------------- 1 file changed, 14 insertions(+), 84 deletions(-) diff --git a/testcases/t/58-wm_take_focus.t b/testcases/t/58-wm_take_focus.t index 372f41cd..a90ce1c3 100644 --- a/testcases/t/58-wm_take_focus.t +++ b/testcases/t/58-wm_take_focus.t @@ -7,105 +7,35 @@ use X11::XCB qw(:all); use i3test; use v5.10; -BEGIN { - use_ok('EV'); - use_ok('AnyEvent'); - use_ok('X11::XCB::Window'); - use_ok('X11::XCB::Event::Generic'); - use_ok('X11::XCB::Event::MapNotify'); - use_ok('X11::XCB::Event::ClientMessage'); -} - my $x = X11::XCB::Connection->new; subtest 'Window without WM_TAKE_FOCUS', sub { + fresh_workspace; - my $tmp = fresh_workspace; + my $window = open_window($x); - my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30 ], - background_color => '#00ff00', - event_mask => [ 'structure_notify' ], - ); - - $window->name('Window 1'); - $window->map; - - my $cv = AE::cv; - - my $prep = EV::prepare sub { - $x->flush; - }; - - my $check = EV::check sub { - while (defined(my $event = $x->poll_for_event)) { - if ($event->response_type == 161) { - # clientmessage - $cv->send(0); - } - } - }; - - my $w = EV::io $x->get_file_descriptor, EV::READ, sub { - # do nothing, we only need this watcher so that EV picks up the events - }; - - # Trigger timeout after 1 second - my $t = AE::timer 1, 0, sub { - $cv->send(1); - }; - - my $result = $cv->recv; - ok($result, 'cv result'); + ok(!wait_for_event($x, 1, sub { $_[0]->{response_type} == 161 }), 'did not receive ClientMessage'); done_testing; }; subtest 'Window with WM_TAKE_FOCUS', sub { + fresh_workspace; - my $tmp = fresh_workspace; + my $take_focus = $x->atom(name => 'WM_TAKE_FOCUS'); - my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30 ], - background_color => '#00ff00', - event_mask => [ 'structure_notify' ], - protocols => [ $x->atom(name => 'WM_TAKE_FOCUS') ], - ); + my $window = open_window($x, { + dont_map => 1, + protocols => [ $take_focus ], + }); - $window->name('Window 1'); $window->map; - my $cv = AE::cv; - - my $prep = EV::prepare sub { - $x->flush; - }; - - my $check = EV::check sub { - while (defined(my $event = $x->poll_for_event)) { - if ($event->response_type == 161) { - $cv->send($event->data); - } - } - }; - - my $w = EV::io $x->get_file_descriptor, EV::READ, sub { - # do nothing, we only need this watcher so that EV picks up the events - }; - - my $t = AE::timer 1, 0, sub { - say "timer!"; - $cv->send(undef); - }; - - my $result = $cv->recv; - ok(defined($result), 'got a ClientMessage'); - if (defined($result)) { - my ($data, $time) = unpack("L2", $result); - is($data, $x->atom(name => 'WM_TAKE_FOCUS')->id, 'first uint32_t contains WM_TAKE_FOCUS atom'); - } + ok(wait_for_event($x, 1, sub { + return 0 unless $_[0]->{response_type} == 161; + my ($data, $time) = unpack("L2", $_[0]->{data}); + return ($data == $take_focus->id); + }), 'got ClientMessage with WM_TAKE_FOCUS atom'); done_testing; }; From 761dac55143a469022f0556017126f96f2bd8c9b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 15:44:42 +0100 Subject: [PATCH 12/15] tests: lib/i3test: Use //= instead of unless exists $args{key} (Thanks mxf) --- testcases/t/lib/i3test.pm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 87cb769d..2685d695 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -144,11 +144,11 @@ sub open_window { my $dont_map = delete $args{dont_map}; - $args{class} = WINDOW_CLASS_INPUT_OUTPUT unless exists $args{class}; - $args{rect} = [ 0, 0, 30, 30 ] unless exists $args{rect}; - $args{background_color} = '#c0c0c0' unless exists $args{background_color}; - $args{event_mask} = [ 'structure_notify' ] unless exists $args{event_mask}; - $args{name} = 'Window ' . counter_window() unless exists $args{name}; + $args{class} //= WINDOW_CLASS_INPUT_OUTPUT; + $args{rect} //= [ 0, 0, 30, 30 ]; + $args{background_color} //= '#c0c0c0'; + $args{event_mask} //= [ 'structure_notify' ]; + $args{name} //= 'Window ' . counter_window(); my $window = $x->root->create_child(%args); From 10a9d2a439f681288d26f13f13ffe1bec2ead409 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 15:56:43 +0100 Subject: [PATCH 13/15] tests: Bugfix: 11-goto.t: use mktemp for generating a random mark, not base64 The base64 string could contain / and + which is treated specially since we implemented PCRE support :) --- testcases/t/11-goto.t | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/testcases/t/11-goto.t b/testcases/t/11-goto.t index 5bec5892..5ddef776 100644 --- a/testcases/t/11-goto.t +++ b/testcases/t/11-goto.t @@ -2,12 +2,7 @@ # vim:ts=4:sw=4:expandtab use i3test; -use X11::XCB qw(:all); -use Digest::SHA1 qw(sha1_base64); - -BEGIN { - use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); -} +use File::Temp; my $x = X11::XCB::Connection->new; @@ -45,7 +40,7 @@ is($focus, $mid->id, "Middle window focused"); # Now goto a mark which does not exist ##################################################################### -my $random_mark = sha1_base64(rand()); +my $random_mark = mktemp('mark.XXXXXX'); $focus = focus_after(qq|[con_mark="$random_mark"] focus|); is($focus, $mid->id, "focus unchanged"); From 1eb011aae12d60c07117a7a6a9b95a35f42eec5b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 16:28:21 +0100 Subject: [PATCH 14/15] tests: make sure to leave no tempfiles behind --- testcases/complete-run.pl | 2 +- testcases/t/59-socketpaths.t | 4 ++-- testcases/t/71-config-migrate.t | 2 +- testcases/t/lib/i3test.pm | 4 ++++ 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 3566d78b..52038b33 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -134,7 +134,7 @@ sub take_job { my $dont_start = (slurp($test) =~ /# !NO_I3_INSTANCE!/); my $logpath = "$outdir/i3-log-for-" . basename($test); - my ($fh, $tmpfile) = tempfile(); + my ($fh, $tmpfile) = tempfile('i3-run-cfg.XXXXXX', UNLINK => 1); say $fh $config; say $fh "ipc-socket /tmp/nested-$display"; close($fh); diff --git a/testcases/t/59-socketpaths.t b/testcases/t/59-socketpaths.t index 33350927..36c99087 100644 --- a/testcases/t/59-socketpaths.t +++ b/testcases/t/59-socketpaths.t @@ -18,7 +18,7 @@ my $i3_path = abs_path("../i3"); # default case: socket will be created in /tmp/i3-/ipc-socket. ##################################################################### -my ($fh, $tmpfile) = tempfile(); +my ($fh, $tmpfile) = tempfile('/tmp/i3-test-config.XXXXXX', UNLINK => 1); say $fh "# i3 config file (v4)"; say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; close($fh); @@ -67,7 +67,7 @@ my $tmpdir = tempdir(CLEANUP => 1); $socketpath = $tmpdir . "/config.sock"; ok(! -e $socketpath, "$socketpath does not exist yet"); -($fh, $tmpfile) = tempfile(); +($fh, $tmpfile) = tempfile('/tmp/i3-test-config.XXXXXX', UNLINK => 1); say $fh "# i3 config file (v4)"; say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; say $fh "ipc-socket $socketpath"; diff --git a/testcases/t/71-config-migrate.t b/testcases/t/71-config-migrate.t index 6b41f2c1..561538e5 100644 --- a/testcases/t/71-config-migrate.t +++ b/testcases/t/71-config-migrate.t @@ -23,7 +23,7 @@ sub slurp { sub migrate_config { my ($config) = @_; - my ($fh, $tmpfile) = tempfile(); + my ($fh, $tmpfile) = tempfile('/tmp/i3-migrate-cfg.XXXXXX', UNLINK => 1); print $fh $config; close($fh); diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 2685d695..c890693c 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -382,6 +382,10 @@ sub exit_gracefully { if (!$exited) { kill(9, $pid) or die "could not kill i3"; } + + if ($socketpath =~ m,^/tmp/i3-test-socket-,) { + unlink($socketpath); + } } # Gets the socket path from the I3_SOCKET_PATH atom stored on the X11 root window From d174ff16edc462a7fc44cb1372789bb06ddaa543 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 17:20:36 +0100 Subject: [PATCH 15/15] Add docs/testsuite --- docs/Makefile | 5 +- docs/i3-sync-working.dia | Bin 0 -> 2762 bytes docs/i3-sync-working.png | Bin 0 -> 25072 bytes docs/i3-sync.dia | Bin 0 -> 2208 bytes docs/i3-sync.png | Bin 0 -> 17308 bytes docs/testsuite | 446 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 450 insertions(+), 1 deletion(-) create mode 100644 docs/i3-sync-working.dia create mode 100644 docs/i3-sync-working.png create mode 100644 docs/i3-sync.dia create mode 100644 docs/i3-sync.png create mode 100644 docs/testsuite diff --git a/docs/Makefile b/docs/Makefile index 9d70243d..990bba87 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,5 +1,5 @@ -all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html wsbar.html refcard.pdf +all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html wsbar.html refcard.pdf testsuite.html hacking-howto.html: hacking-howto asciidoc -a toc -n $< @@ -10,6 +10,9 @@ debugging.html: debugging userguide.html: userguide asciidoc -a toc -n $< +testsuite.html: testsuite + asciidoc -a toc -n $< + ipc.html: ipc asciidoc -a toc -n $< diff --git a/docs/i3-sync-working.dia b/docs/i3-sync-working.dia new file mode 100644 index 0000000000000000000000000000000000000000..9f1c3bc8cae13895ca87b89239036f7f14c4718b GIT binary patch literal 2762 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 QXOB1k2e*BCJ%PCZ0H|wW!2kdN literal 0 HcmV?d00001 diff --git a/docs/i3-sync-working.png b/docs/i3-sync-working.png new file mode 100644 index 0000000000000000000000000000000000000000..dce44ac5ac4d2efe4d1d1751b499586613eca33f GIT binary patch literal 25072 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 literal 0 HcmV?d00001 diff --git a/docs/i3-sync.png b/docs/i3-sync.png new file mode 100644 index 0000000000000000000000000000000000000000..b64cce25959b1620084fc2214e35c2b7f9fb8a10 GIT binary patch literal 17308 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 literal 0 HcmV?d00001 diff --git a/docs/testsuite b/docs/testsuite new file mode 100644 index 00000000..7611499d --- /dev/null +++ b/docs/testsuite @@ -0,0 +1,446 @@ +i3 testsuite +============ +Michael Stapelberg +September 2011 + +This document explains how the i3 testsuite works, how to use it and how to +extend it. It is targeted at developers who not necessarily have been doing +testing before or have not been testing in Perl before. In general, the +testsuite is not of interest for end users. + + +== Introduction + +The i3 testsuite is a collection of files which contain testcases for various +i3 features. Some of them test if a certain workflow works correctly (moving +windows, focus behaviour, …). Others are regression tests and contain code +which previously made i3 crash or lead to unexpected behaviour. They then check +if i3 still runs (meaning it did not crash) and if it handled everything +correctly. + +The goal of having these tests is to automatically find problems and to +automatically get a feel for whether a change in the source code breaks any +existing feature. After every modification of the i3 sourcecode, the developer +should run the full testsuite. If one of the tests does not pass (but fails), +the corresponding problem should be fixed (or, in some cases, the testcase has +to be modified). For every bugreport, a testcase should be written to test the +correct behaviour. Initially, it will fail, but after fixing the bug, it will +pass. This ensures (or increases the chance) that bugs which have been fixed +once will never be found again. + +Also, when implementing a new feature, a testcase might be a good way to be +able to easily test if the feature is working correctly. Many developers will +test manually if everything works. Having a testcase not only helps you with +that, but it will also be useful for every future change. + +== Implementation + +For several reasons, the i3 testsuite has been implemented in Perl: + +1. Perl has a long tradition of testing. Every popular/bigger Perl module which + you can find on CPAN will not only come with documentation, but also with + tests. Therefore, the available infrastructure for tests is comprehensive. + See for example the excellent http://search.cpan.org/perldoc?Test::More + and the referenced http://search.cpan.org/perldoc?Test::Tutorial. + +2. Perl is widely available and has a well-working package infrastructure. +3. The author is familiar with Perl :). + +Please do not start programming language flamewars at this point. + +=== Mechanisms + +==== Script: complete-run + +The testcases are run by a script called +complete-run.pl+. It runs all +testcases by default, but you can be more specific and let it only run one or +more testcases. Also, it takes care of starting up a separate instance of i3 +with an appropriate configuration file and creates a folder for each run +containing the appropriate i3 logfile for each testcase. The latest folder can +always be found under the symlink +latest/+. It is recommended that you run the +tests on one or more separate X server instances (you can only start one window +manager per X session), for example using the provided Xdummy script. ++complete-run.pl+ takes one or more X11 display specifications and parallelizes +the testcases appropriately: + +.Example invocation of complete-run.pl+ +--------------------------------------- +$ cd ~/i3/testcases + +# start two dummy X11 instances in the background +$ ./Xdummy :1 & +$ ./Xdummy :2 & + +$ ./complete-run.pl -d :1,:2 +# output omitted because it is very long +All tests successful. +Files=78, Tests=734, 27 wallclock secs ( 0.38 usr 0.48 sys + 17.65 cusr 3.21 csys = 21.72 CPU) +Result: PASS + +$ ./complete-run.pl -d :1 t/04-floating.t +[:3] i3 startup: took 0.07s, status = 1 +[:3] Running t/04-floating.t with logfile testsuite-2011-09-24-16-06-04-4.0.2-226-g1eb011a/i3-log-for-04-floating.t +[:3] t/04-floating.t finished +[:3] killing i3 +output for t/04-floating.t: +ok 1 - use X11::XCB::Window; +ok 2 - The object isa X11::XCB::Window +ok 3 - Window is mapped +ok 4 - i3 raised the width to 75 +ok 5 - i3 raised the height to 50 +ok 6 - i3 did not map it to (0x0) +ok 7 - The object isa X11::XCB::Window +ok 8 - i3 let the width at 80 +ok 9 - i3 let the height at 90 +ok 10 - i3 mapped it to x=1 +ok 11 - i3 mapped it to y=18 +ok 12 - The object isa X11::XCB::Window +ok 13 - i3 let the width at 80 +ok 14 - i3 let the height at 90 +1..14 + +All tests successful. +Files=1, Tests=14, 0 wallclock secs ( 0.01 usr 0.00 sys + 0.19 cusr 0.03 csys = 0.23 CPU) +Result: PASS + +$ less latest/i3-log-for-04-floating.t +---------------------------------------- + +==== IPC interface + +The testsuite makes extensive use of the IPC (Inter-Process Communication) +interface which i3 provides. It is used for the startup process of i3, for +terminating it cleanly and (most importantly) for modifying and getting the +current state (layout tree). + +See [http://i3wm.org/docs/ipc.html] for documentation on the IPC interface. + +==== X11::XCB + +In order to open new windows, change attributes, get events, etc., the +testsuite uses X11::XCB, a new (and quite specific to i3 at the moment) Perl +module which uses the XCB protocol description to generate Perl bindings to +X11. They work in a very similar way to libxcb (which i3 uses) and provide +relatively high-level interfaces (objects such as +X11::XCB::Window+) aswell as +access to the low-level interface, which is very useful when testing a window +manager. + +=== Filesystem structure + +In the git root of i3, the testcases live in the folder +testcases+. This +folder contains the +complete-run.pl+ and +Xdummy+ scripts and a base +configuration file which will be used for the tests. The different testcases +themselve can be found in the conventionally named subfolder +t+: + +.Filesystem structure +-------------------------------------------- +├── testcases +│   ├── complete-run.pl +│   ├── i3-test.config +│   ├── t +│   │   ├── 00-load.t +│   │   ├── 01-tile.t +│   │   ├── 02-fullscreen.t +│   │   ├── ... +│   │   ├── omitted for brevity +│   │   ├── ... +│   │   ├── 74-regress-focus-toggle.t +│   │   └── lib +│   │   └── i3test.pm +│   └── Xdummy +-------------------------------------------- + +== Anatomy of a testcase + +Learning by example is definitely a good strategy when you are wondering how to +write a testcase. Let's take +t/11-goto.t+ as an easy example and go through it +step by step: + +.t/11-goto.t: Boilerplate +---------------------- +#!perl +# vim:ts=4:sw=4:expandtab + +use i3test; +use File::Temp; + +my $x = X11::XCB::Connection->new; +----------------------- + +This is what we call boilerplate. It exists at the top of every test file (to +some extent). The first line is the shebang, which specifies that this file is +a Perl script. The second line contains VIM specific settings on how to +edit/format this file (use spaces instead of tabs, indent using 4 spaces). +Afterwards, the +i3test+ module is used. This module contains i3 testsuite +specific functions which you are strongly encouraged to use. They make writing +testcases a lot easier and will make it easier for other people to read your +tests. + +The next line uses the +File::Temp+ module. This is specific to this testcase, +because it needs to generate a temporary name during the test. Many testcases +use only the +i3test+ module. + +The last line opens a connection to X11. You might or might not need this in +your testcase, depending on whether you are going to open windows (etc.) or +only use i3 commands. + +.t/11-goto.t: Setup +---------------------- +my $tmp = fresh_workspace; + +cmd 'split h'; +---------------------- + +The first line calls i3test's +fresh_workspace+ function which looks for a +currently unused workspace, switches to it, and returns its name. The variable ++$tmp+ will end up having a value such as +"/tmp/87kBVcHbA9"+. Note that this +is not (necessarily) a valid path, it's just a random workspace name. + +So, now that we are on a new workspace, we ensure that the workspace uses +horizontal orientation by issuing the +split h+ command (see the i3 User's +Guide for a list of commands). This is not strictly necessary, but good style. +In general, the +cmd+ function executes the specified i3 command by using the +IPC interface and returns once i3 acknowledged the command. + +.t/11-goto.t: Setup +---------------------- +##################################################################### +# Create two windows and make sure focus switching works +##################################################################### + +my $top = open_window($x); +my $mid = open_window($x); +my $bottom = open_window($x); +---------------------- + +In every major section of a testcase, you should put a comment like the one +above. This makes it immediately clear how the file is structured. + +The +open_window+ function opens a standard window, which will then be put into +tiling mode by i3. If you want a floating window, use the ++open_floating_window+ function. These functions accept the same parameters as ++X11::XCB::Window->new+, see the i3test documentation at TODO. + +.t/11-goto.t: Helper function +---------------------- +# +# Returns the input focus after sending the given command to i3 via IPC +# end sleeping for half a second to make sure i3 reacted +# +sub focus_after { + my $msg = shift; + + cmd $msg; + sync_with_i3 $x; + return $x->input_focus; +} +---------------------- + +This section defines a helper function which will be used over and over in this +testcase. If you have code which gets executed more than once or twice +(depending on the length of your test, use your best judgement), please put it +in a function. Tests should be short, concise and clear. + +The +focus_after+ function executes a command and returns the X11 focus after +the command was executed. The +sync_with_i3+ command makes sure that i3 could +push its state to X11. See <> to learn how this works exactly. + +.t/11-goto.t: Test assumptions +---------------------- +$focus = $x->input_focus; +is($focus, $bottom->id, "Latest window focused"); + +$focus = focus_after('focus left'); +is($focus, $mid->id, "Middle window focused"); +---------------------- + +Now, we run the first two real tests. They use +Test::More+'s +is+ function, +which compares two values and prints the differences if they are not the same. +After the arguments, we supply a short comment to indicate what we are testing +here. This makes it vastly more easy for the developer to spot which testcase +is the problem in case one fails. + +The first test checks that the most recently opened window is focused. +Afterwards, the command +focus left+ is issued and it is verified that the +middle window now has focus. + +Note that this is not a comprehensive test of the +focus+ command -- we would +have to test wrapping, focus when using a more complex layout, focusing the +parent/child containers, etc. But that is not the point of this testcase. +Instead, we just want to know if +$x->input_focus+ corresponds with what we are +expecting. If not, something is completely wrong with the test environment and +this trivial test will fail. + +.t/11-goto.t: Test that the feature does not work (yet) +---------------------- +##################################################################### +# Now goto a mark which does not exist +##################################################################### + +my $random_mark = mktemp('mark.XXXXXX'); + +$focus = focus_after(qq|[con_mark="$random_mark"] focus|); +is($focus, $mid->id, "focus unchanged"); +---------------------- + +In this new major section, a random mark (mark is an identifier for a window, +see "VIM-like marks" in the i3 User’s Guide) will be generated. Afterwards, we +test that trying to focus that mark will not do anything. This is important: Do +not only test that using a feature has the expected outcome, but also test that +using it without properly initializing it does no harm. This command could for +example have changed focus anyways (a bug) or crash i3 (obviously a bug). + +.t/11-goto.t: Test that the feature does work +---------------------- +cmd "mark $random_mark"; + +$focus = focus_after('focus left'); +is($focus, $top->id, "Top window focused"); + +$focus = focus_after(qq|[con_mark="$random_mark"] focus|); +is($focus, $mid->id, "goto worked"); +---------------------- + +Remember: Focus was on the middle window (we verified that earlier in "Test +assumptions"). We now mark the middle window with our randomly generated mark. +Afterwards, we switch focus away from the middle window to be able to tell if +focusing it via its mark will work. If it does work (next test), the goto +command works. + +.t/11-goto.t: Test corner case +---------------------- +# check that we can specify multiple criteria + +$focus = focus_after('focus left'); +is($focus, $top->id, "Top window focused"); + +$focus = focus_after(qq|[con_mark="$random_mark" con_mark="$random_mark"] focus|); +is($focus, $mid->id, "goto worked"); +---------------------- + +Now we test the same feature, but specifying the mark twice in the command. +This should have no effect, but let’s be sure: test it and see if things go +wrong. + +.t/11-goto.t: Test second code path +---------------------- +##################################################################### +# Check whether the focus command will switch to a different +# workspace if necessary +##################################################################### + +my $tmp2 = fresh_workspace; + +is(focused_ws(), $tmp2, 'tmp2 now focused'); + +cmd qq|[con_mark="$random_mark"] focus|; + +is(focused_ws(), $tmp, 'tmp now focused'); +---------------------- + +This part of the test checks that focusing windows by mark works across +workspaces. It uses i3test's +focused_ws+ function to get the current +workspace. + +.t/11-goto.t: Test second code path +---------------------- +done_testing; +---------------------- + +The end of every testcase has to contain the +done_testing+ line. This tells ++complete-run.pl+ that the test was finished successfully. If it does not +occur, the test might have crashed during execution -- some of the reasons why +that could happen are bugs in the used modules, bugs in the testcase itself or +an i3 crash resulting in the testcase being unable to communicate with i3 via +IPC anymore. + +[[i3_sync]] +== Appendix A: The i3 sync protocol + +Consider the following situation: You open two windows in your testcase, then +you use +focus left+ and want to verify that the X11 focus has been updated +properly. Sounds simple, right? Let’s assume you use this straight-forward +implementation: + +.Racey focus testcase +----------- +my $left = open_window($x); +my $right = open_window($x); +cmd 'focus left'; +is($x->input_focus, $left->id, 'left window focused'); +---------- + +However, the test fails. Sometimes. Apparantly, there is a race condition in +your test. If you think about it, this is because you are using two different +pieces of software: You tell i3 to update focus, i3 confirms that, and then you +ask X11 to give you the current focus. There is a certain time i3 needs to +update the X11 state. If the testcase gets CPU time before X11 processed i3's +requests, the test will fail. + +image::i3-sync.png["Diagram of the race condition", title="Diagram of the race condition"] + +One way to "solve" this would be to add +sleep 0.5;+ after the +cmd+ call. +After 0.5 seconds it should be safe to assume that focus has been updated, +right? + +In practice, this usually works. However, it has several problems: + +1. This is obviously not a clean solution, but a workaround. Ugly. +2. On very slow machines, this might not work. Unlikely, but in different + situations (a delay to wait for i3 to startup) the necessary time is much + harder to guess, even for fast machines. +3. This *wastes a lot of time*. Usually, your computer is much faster than 0.5s + to update the status. However, sometimes, it might take 0.4s, so we can’t + make it +sleep 0.1+. + +To illustrate how grave the problem with wasting time actually is: Before +removing all sleeps from the testsuite, a typical run using 4 separate X +servers took around 50 seconds on my machine. After removing all the sleeps, +we achieved times of about 25 seconds. This is very significant and influences +the way you think about tests -- the faster they are, the more likely you are +to check whether everything still works quite often (which you should). + +What I am trying to say is: This adds up quickly and makes the test suite less +robust. + +The real solution for this problem is a mechanism which I call "the i3 sync +protocol". The idea is to send a request (which does not modify state) via X11 +to i3 which will then be answered. Due to the request's position in the event +queue (*after* all previous events), you can be sure that by the time you +receive the reply, all other events have been dealt with by i3 (and, more +importantly, X11). + +image::i3-sync-working.png["Diagram of the i3 sync solution", title="Diagram of the i3 sync solution"] + +=== Implementation details + +The client which wants to sync with i3 initiates the protocol by sending a +ClientMessage to the X11 root window: + +.Send ClientMessage +------------------- +# Generate a ClientMessage, see xcb_client_message_t +my $msg = pack "CCSLLLLLLL", + CLIENT_MESSAGE, # response_type + 32, # format + 0, # sequence + $root, # destination window + $x->atom(name => 'I3_SYNC')->id, + + $_sync_window->id, # data[0]: our own window id + $myrnd, # data[1]: a random value to identify the request + 0, + 0, + 0; + +# Send it to the root window -- since i3 uses the SubstructureRedirect +# event mask, it will get the ClientMessage. +$x->send_event(0, $root, EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg); +------------------- + +i3 will then reply with the same ClientMessage, sent to the window specified in ++data[0]+. In the reply, +data[0]+ and +data[1]+ are exactly the same as in the +request. You should use a random value in +data[1]+ and check that you received +the same one when getting the reply. + +== Appendix B: Socket activation