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 00000000..9f1c3bc8 Binary files /dev/null and b/docs/i3-sync-working.dia differ diff --git a/docs/i3-sync-working.png b/docs/i3-sync-working.png new file mode 100644 index 00000000..dce44ac5 Binary files /dev/null and b/docs/i3-sync-working.png differ diff --git a/docs/i3-sync.dia b/docs/i3-sync.dia new file mode 100644 index 00000000..0945ae26 Binary files /dev/null and b/docs/i3-sync.dia differ diff --git a/docs/i3-sync.png b/docs/i3-sync.png new file mode 100644 index 00000000..b64cce25 Binary files /dev/null and b/docs/i3-sync.png differ 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 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; diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 8f740d8c..52038b33 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -27,8 +27,8 @@ use File::Basename qw(basename); use AnyEvent::I3 qw(:all); use Try::Tiny; use Getopt::Long; -use Time::HiRes qw(sleep); -use X11::XCB::Connection; +use Time::HiRes qw(sleep gettimeofday tv_interval); +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'); @@ -133,12 +134,13 @@ 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); my $activate_cv = AnyEvent->condvar; + my $time_before_start = [gettimeofday]; my $start_i3 = sub { # remove the old unix socket unlink("/tmp/nested-$display-activation"); @@ -157,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); @@ -200,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; }); @@ -234,9 +232,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"; diff --git a/testcases/t/02-fullscreen.t b/testcases/t/02-fullscreen.t index 34e5364e..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'; @@ -59,11 +60,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"); @@ -94,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"); @@ -101,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"); @@ -124,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'); @@ -135,12 +137,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 +154,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/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/05-ipc.t b/testcases/t/05-ipc.t index a910c930..982ece7e 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; @@ -17,17 +12,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; +my $window = open_window($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..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 f8143979..b5be284c 100644 --- a/testcases/t/08-focus-stack.t +++ b/testcases/t/08-focus-stack.t @@ -4,45 +4,26 @@ # 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; -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); - -sleep 0.25; +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 = $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_floating_window($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; +wait_for_unmap($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/10-dock.t b/testcases/t/10-dock.t index 3f0a5195..cad54c26 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 @@ -30,17 +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'), -); - -$window->map; - -sleep 0.25; +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'); @@ -67,7 +58,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 +73,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'); @@ -91,16 +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'), -); - -$window->map; - -sleep 0.25; +$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'); @@ -111,7 +97,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'); @@ -120,12 +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'), -); +$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(); @@ -145,24 +131,24 @@ $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'); -$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'), -); +$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(); @@ -182,7 +168,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'); @@ -194,17 +180,16 @@ $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'), -); +$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; -sleep 0.25; +wait_for_map $x; does_i3_live; diff --git a/testcases/t/11-goto.t b/testcases/t/11-goto.t index 542dc828..5ddef776 100644 --- a/testcases/t/11-goto.t +++ b/testcases/t/11-goto.t @@ -2,16 +2,10 @@ # 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; -my $i3 = i3(get_socket_path()); my $tmp = fresh_workspace; cmd 'split h'; @@ -20,16 +14,9 @@ cmd 'split h'; # Create two windows and make sure focus switching works ##################################################################### -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); -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 @@ -39,6 +26,7 @@ sub focus_after { my $msg = shift; cmd $msg; + sync_with_i3($x); return $x->input_focus; } @@ -52,12 +40,12 @@ 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"); -$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 09297df0..ac3387a9 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); @@ -19,31 +16,22 @@ fresh_workspace; # Create a floating window and see if resizing works ##################################################################### -# 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_floating_window($x); # 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 +40,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..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'); @@ -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/14-client-leader.t b/testcases/t/14-client-leader.t index 98978eb3..6f7ffce0 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; @@ -21,74 +20,42 @@ 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', -); - -$left->name('Left'); -$left->map; - -my $right = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [0, 0, 30, 30], - background_color => '#FF0000', -); - -$right->name('Right'); -$right->map; - -sleep 0.25; +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'), -); - -$child->name('Child window'); +my $child = open_floating_window($x, { + dont_map => 1, + 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; 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'), -); - -$child2->name('Child window 2'); +my $child2 = open_floating_window($x, { + dont_map => 1, + 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'); # check wm_transient_for - - -my $fwindow = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#FF0000', -); - +my $fwindow = open_window($x, { dont_map => 1 }); $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)'); @@ -100,16 +67,10 @@ SKIP: { # Create a parent window ##################################################################### -my $window = $x->root->create_child( -class => WINDOW_CLASS_INPUT_OUTPUT, -rect => [ 0, 0, 30, 30 ], -background_color => '#C0C0C0', -); - -$window->name('Parent window'); +my $window = open_window($x, { dont_map => 1, 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 @@ -117,17 +78,11 @@ sleep 0.25; ######################################################################### fresh_workspace; -my $child = $x->root->create_child( -class => WINDOW_CLASS_INPUT_OUTPUT, -rect => [ 0, 0, 30, 30 ], -background_color => '#C0C0C0', -); - -$child->name('Child window'); +my $child = open_window($x, { dont_map => 1, 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 e4fc6ec0..8b9d21d3 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_window($x); my $content = get_ws_content($tmp); ok(@{$content} == 1, 'window mapped'); my $win = $content->[0]; @@ -48,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); @@ -88,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); @@ -114,7 +102,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'); @@ -129,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); @@ -143,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'); @@ -158,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); @@ -172,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/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 05897c88..d2d77e8e 100644 --- a/testcases/t/33-size-hints.t +++ b/testcases/t/33-size-hints.t @@ -5,20 +5,13 @@ # use i3test; -my $i3 = i3(get_socket_path()); - my $x = X11::XCB::Connection->new; 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', -); - +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; @@ -28,11 +21,11 @@ $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; -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..4c5b562f 100644 --- a/testcases/t/35-floating-focus.t +++ b/testcases/t/35-floating-focus.t @@ -13,8 +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); +my $first = open_window($x); +my $second = open_window($x); is($x->input_focus, $second->id, 'second window focused'); @@ -30,9 +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 +$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'); @@ -40,6 +40,8 @@ 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 +49,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'); @@ -59,9 +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 +$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'); @@ -69,6 +72,8 @@ 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 +82,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'); @@ -93,11 +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 +$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'); @@ -105,23 +111,26 @@ 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'); @@ -131,8 +140,10 @@ 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); is($x->input_focus, $second->id, 'second container focused'); @@ -142,31 +153,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'); @@ -176,39 +187,41 @@ 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); 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..a6e0e405 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_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 3ae4b12d..ab1a33d3 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_floating_window($x); 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..b08190a2 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_floating_window($x); 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_window($x); ($nodes, $focus) = get_ws_content($tmp); @@ -68,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'; @@ -78,27 +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 = $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_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 9df220d1..fb77f01e 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'); @@ -25,12 +24,11 @@ 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; +my $left = open_window($x); +my $mid = open_window($x); +my $right = open_window($x); + +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..8691a044 100644 --- a/testcases/t/41-resize.t +++ b/testcases/t/41-resize.t @@ -14,10 +14,10 @@ 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; +my $top = open_window($x); +my $bottom = open_window($x); + +sync_with_i3($x); diag("top = " . $top->id . ", bottom = " . $bottom->id); @@ -54,10 +54,8 @@ $tmp = fresh_workspace; cmd 'split v'; -$top = open_standard_window($x); -sleep 0.25; -$bottom = open_standard_window($x); -sleep 0.25; +$top = open_window($x); +$bottom = open_window($x); cmd 'split h'; cmd 'layout stacked'; @@ -78,8 +76,7 @@ is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%'); $tmp = fresh_workspace; -$top = open_standard_window($x); -sleep 0.25; +$top = open_window($x); cmd 'floating enable'; diff --git a/testcases/t/45-flattening.t b/testcases/t/45-flattening.t index 98b93ef1..904252e7 100644 --- a/testcases/t/45-flattening.t +++ b/testcases/t/45-flattening.t @@ -17,12 +17,9 @@ 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; +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 a4e90d4d..bc1302bb 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 { @@ -13,14 +12,11 @@ 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 $left = open_window($x); +my $mid = open_window($x); cmd 'split v'; -my $bottom = open_standard_window($x); -sleep 0.25; +my $bottom = open_window($x); my ($nodes, $focus) = get_ws_content($tmp); @@ -28,23 +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 = $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_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 6e04916d..771ace32 100644 --- a/testcases/t/47-regress-floatingmove.t +++ b/testcases/t/47-regress-floatingmove.t @@ -16,12 +16,9 @@ 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; +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 0bec5418..6f9dfc40 100644 --- a/testcases/t/48-regress-floatingmovews.t +++ b/testcases/t/48-regress-floatingmovews.t @@ -16,21 +16,21 @@ my $x = X11::XCB::Connection->new; my $tmp = fresh_workspace; # open a tiling window on the first workspace -open_standard_window($x); -sleep 0.25; +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); -sleep 0.25; +open_window($x); +#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/50-regress-dock-restart.t b/testcases/t/50-regress-dock-restart.t index a4f7bebc..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,16 +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'), -); - -$window->map; - -sleep 0.25; +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 @@ -69,7 +62,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'); @@ -78,17 +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'), -); - -$window->map; - -sleep 0.25; +$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 16e62c20..db0b6e9c 100644 --- a/testcases/t/53-floating-originalsize.t +++ b/testcases/t/53-floating-originalsize.t @@ -11,17 +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', -); - -isa_ok($window, 'X11::XCB::Window'); - -$window->map; - -sleep 0.25; +my $window = open_window($x, { rect => [ 0, 0, 400, 150 ] }); my ($absolute, $top) = $window->rect; @@ -30,7 +20,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/54-regress-multiple-dock.t b/testcases/t/54-regress-multiple-dock.t index 36070db1..21cb9696 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; @@ -28,31 +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'), -); - -$first->map; - -sleep 0.25; +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'), -); - -$second->map; - -sleep 0.25; +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 ecffbb12..5de05e8b 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; @@ -20,29 +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', -); - -$first->map; - -sleep 0.25; +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', -); - -$second->map; - -sleep 0.25; +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 ee60dc7a..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,7 +44,15 @@ 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, + }); + +$third->map; + +sync_with_i3 $x; diag("third = " . $third->id); @@ -56,7 +64,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/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..a90ce1c3 100644 --- a/testcases/t/58-wm_take_focus.t +++ b/testcases/t/58-wm_take_focus.t @@ -7,106 +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; -my $i3 = i3(get_socket_path()); 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; }; 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/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/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..e55d8682 100644 --- a/testcases/t/63-wm-state.t +++ b/testcases/t/63-wm-state.t @@ -7,34 +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 = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30 ], - background_color => '#00ff00', - event_mask => [ 'structure_notify' ], -); +my $window = open_window($x); -$window->name('Window 1'); -$window->map; - -diag('window mapped'); - -sleep 0.5; +sync_with_i3($x); is($window->state, ICCCM_WM_STATE_NORMAL, 'WM_STATE normal'); $window->unmap; -sleep 0.5; +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 2e0669ba..ef45a789 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; @@ -15,8 +13,10 @@ 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; 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..36a20ea9 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,17 +175,18 @@ $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'); 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'); @@ -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/67-workspace_layout.t b/testcases/t/67-workspace_layout.t index 2b9f6e56..6ff3f15b 100644 --- a/testcases/t/67-workspace_layout.t +++ b/testcases/t/67-workspace_layout.t @@ -27,8 +27,10 @@ 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); is($x->input_focus, $second->id, 'second window focused'); ok(@{get_ws_content($tmp)} == 2, 'two containers opened'); @@ -54,8 +56,10 @@ $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); is($x->input_focus, $second->id, 'second window focused'); my @content = @{get_ws_content($tmp)}; @@ -68,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 f2dfc18e..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,16 @@ $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); + is($x->input_focus, $third->id, 'third window focused'); cmd 'focus left'; 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/74-border-config.t b/testcases/t/74-border-config.t index c8a4d73d..617e37b9 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; @@ -27,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'); @@ -53,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'); @@ -77,18 +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'), -); - -$first->map; - -sleep 0.25; +$first = open_floating_window($x); my $wscontent = get_ws($tmp); my @floating = @{$wscontent->{floating_nodes}}; @@ -116,18 +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'), -); - -$first->map; - -sleep 0.25; +$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 3d2d85af..c890693c 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); @@ -17,10 +18,33 @@ 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_window + open_floating_window + get_dock_clients + cmd + sync_with_i3 + does_i3_live + exit_gracefully + workspace_exists + focused_ws + get_socket_path + launch_with_config + wait_for_event + wait_for_map + wait_for_unmap +); my $tester = Test::Builder->new(); my $_cached_socket_path = undef; +my $_sync_window = undef; my $tmp_socket_path = undef; BEGIN { @@ -47,32 +71,107 @@ use warnings; goto \&Exporter::import; } -sub open_standard_window { - my ($x, $color, $floating) = @_; +# +# 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) = @_; - $color ||= '#c0c0c0'; + my $cv = AE::cv; - # 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, - ); + my $prep = EV::prepare sub { + $x->flush; + }; - if (defined($floating) && $floating) { - @args = (@args, window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY')); - } + my $check = EV::check sub { + while (defined(my $event = $x->poll_for_event)) { + if ($cb->($event)) { + $cv->send(1); + last; + } + } + }; - my $window = $x->root->create_child(@args); + 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 { 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); +} + +# +# 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 : ()); + + my $dont_map = delete $args{dont_map}; + + $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); + + return $window if $dont_map; - $window->name('Window ' . counter_window()); $window->map; - - sleep(0.25); - + 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) = @_; @@ -197,6 +296,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->get_root_window(); + # 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}}; @@ -220,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 @@ -264,7 +430,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);