]> git.sur5r.net Git - i3/i3/commitdiff
Add i3test::XTEST, add 2 test cases for key bindings
authorMichael Stapelberg <michael@stapelberg.de>
Mon, 16 Nov 2015 08:25:53 +0000 (09:25 +0100)
committerMichael Stapelberg <michael@stapelberg.de>
Mon, 16 Nov 2015 08:37:54 +0000 (09:37 +0100)
.travis.yml
testcases/lib/i3test/XTEST.pm [new file with mode: 0644]
testcases/t/257-keypress-group1-fallback.t [new file with mode: 0644]
testcases/t/258-keypress-release.t [new file with mode: 0644]

index dd1fb156142fb237313a7fa9a09f4c2e87c91e34..c4d221ae057fe421bc05c6b37c213bc85e3e834a 100644 (file)
@@ -44,7 +44,7 @@ before_install:
 install:
   - sudo mk-build-deps --install --remove --tool 'apt-get --no-install-recommends' debian/control
   # Install as many dependencies as possible via apt because cpanm is not very reliable/easy to debug.
-  - sudo apt-get install --no-install-recommends libanyevent-perl libanyevent-i3-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl-modules libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libtest-use-ok-perl libipc-run-perl
+  - sudo apt-get install --no-install-recommends libanyevent-perl libanyevent-i3-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl-modules libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libtest-use-ok-perl libipc-run-perl libxcb-xtest0-dev
   - sudo /bin/sh -c 'cpanm -n -v X11::XCB || true'
   - sudo /bin/sh -c 'cpanm -n -v AnyEvent::I3 || true'
 script:
diff --git a/testcases/lib/i3test/XTEST.pm b/testcases/lib/i3test/XTEST.pm
new file mode 100644 (file)
index 0000000..065c8a3
--- /dev/null
@@ -0,0 +1,258 @@
+package i3test::XTEST;
+# vim:ts=4:sw=4:expandtab
+
+use strict;
+use warnings;
+use v5.10;
+
+use i3test i3_autostart => 0;
+use AnyEvent::I3;
+use ExtUtils::PkgConfig;
+
+use Exporter ();
+our @EXPORT = qw(
+    inlinec_connect
+    set_xkb_group
+    xtest_key_press
+    xtest_key_release
+    listen_for_binding
+    start_binding_capture
+    binding_events
+);
+
+=encoding utf-8
+
+=head1 NAME
+
+i3test::XTEST - Inline::C wrappers for xcb-xtest and xcb-xkb
+
+=cut
+
+# We need to use libxcb-xkb because xdotool cannot trigger ISO_Next_Group
+# anymore: it contains code to set the XKB group to 1 and then restore the
+# previous group, effectively rendering any keys that switch groups
+# ineffective.
+my %sn_config;
+BEGIN {
+    %sn_config = ExtUtils::PkgConfig->find('xcb-xkb xcb-xtest');
+}
+
+use Inline C => Config => LIBS => $sn_config{libs}, CCFLAGS => $sn_config{cflags};
+use Inline C => <<'END_OF_C_CODE';
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <xcb/xcb.h>
+#include <xcb/xkb.h>
+#include <xcb/xtest.h>
+
+static xcb_connection_t *conn = NULL;
+
+bool inlinec_connect() {
+    int screen;
+
+    if ((conn = xcb_connect(NULL, &screen)) == NULL ||
+        xcb_connection_has_error(conn)) {
+        fprintf(stderr, "Could not connect to X11\n");
+        return false;
+    }
+
+    if (!xcb_get_extension_data(conn, &xcb_xkb_id)->present) {
+        fprintf(stderr, "XKB not present\n");
+        return false;
+    }
+
+    if (!xcb_get_extension_data(conn, &xcb_test_id)->present) {
+        fprintf(stderr, "XTEST not present\n");
+        return false;
+    }
+
+    xcb_generic_error_t *err = NULL;
+    xcb_xkb_use_extension_reply_t *usereply;
+    usereply = xcb_xkb_use_extension_reply(
+        conn, xcb_xkb_use_extension(conn, XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION), &err);
+    if (err != NULL || usereply == NULL) {
+        fprintf(stderr, "xcb_xkb_use_extension() failed\n");
+        return false;
+    }
+    free(usereply);
+
+    return true;
+}
+
+// NOTE: while |group| should be a uint8_t, Inline::C will not define the
+// function unless we use an int.
+bool set_xkb_group(int group) {
+    xcb_generic_error_t *err = NULL;
+    // Needs libxcb ≥ 1.11 so that we have the following bug fix:
+    // http://cgit.freedesktop.org/xcb/proto/commit/src/xkb.xml?id=8d7ee5b6ba4cf343f7df70372a3e1f85b82aeed7
+    xcb_void_cookie_t cookie = xcb_xkb_latch_lock_state_checked(
+        conn,
+        XCB_XKB_ID_USE_CORE_KBD, /* deviceSpec */
+        0,                       /* affectModLocks */
+        0,                       /* modLocks */
+        1,                       /* lockGroup */
+        group,                   /* groupLock */
+        0,                       /* affectModLatches */
+        0,                       /* latchGroup */
+        0);                      /* groupLatch */
+    if ((err = xcb_request_check(conn, cookie)) != NULL) {
+        fprintf(stderr, "X error code %d\n", err->error_code);
+        return false;
+    }
+    return true;
+}
+
+bool xtest_key(int type, int detail) {
+    xcb_generic_error_t *err;
+    xcb_void_cookie_t cookie;
+
+    cookie = xcb_test_fake_input_checked(
+        conn,
+        type,             /* type */
+        detail,           /* detail */
+        XCB_CURRENT_TIME, /* time */
+        XCB_NONE,         /* root */
+        0,                /* rootX */
+        0,                /* rootY */
+        XCB_NONE);        /* deviceid */
+    if ((err = xcb_request_check(conn, cookie)) != NULL) {
+        fprintf(stderr, "X error code %d\n", err->error_code);
+        return false;
+    }
+
+    return true;
+}
+
+bool xtest_key_press(int detail) {
+    return xtest_key(XCB_KEY_PRESS, detail);
+}
+
+bool xtest_key_release(int detail) {
+    return xtest_key(XCB_KEY_RELEASE, detail);
+}
+
+END_OF_C_CODE
+
+sub import {
+    my ($class, %args) = @_;
+    ok(inlinec_connect(), 'Connect to X11, verify XKB and XTEST are present (via Inline::C)');
+    goto \&Exporter::import;
+}
+
+=head1 EXPORT
+
+=cut
+
+my $i3;
+our @binding_events;
+
+=head2 start_binding_capture()
+
+Captures all binding events sent by i3 in the C<@binding_events> symbol, so
+that you can verify the correct number of binding events was generated.
+
+  my $pid = launch_with_config($config);
+  start_binding_capture;
+  # …
+  sync_with_i3;
+  is(scalar @i3test::XTEST::binding_events, 2, 'Received exactly 2 binding events');
+
+=cut
+
+sub start_binding_capture {
+    # Store a copy of each binding event so that we can count the expected
+    # events in test cases.
+    $i3 = i3(get_socket_path());
+    $i3->connect()->recv;
+    $i3->subscribe({
+        binding => sub {
+            my ($event) = @_;
+            @binding_events = (@binding_events, $event);
+        },
+    })->recv;
+}
+
+=head2 listen_for_binding($cb)
+
+Helper function to evaluate whether sending KeyPress/KeyRelease events via
+XTEST triggers an i3 key binding or not (with a timeout of 0.5s). Expects key
+bindings to be configured in the form “bindsym <binding> nop <binding>”, e.g.
+“bindsym Mod4+Return nop Mod4+Return”.
+
+  is(listen_for_binding(
+      sub {
+          xtest_key_press(133); # Super_L
+          xtest_key_press(36); # Return
+          xtest_key_release(36); # Return
+          xtest_key_release(133); # Super_L
+      },
+      ),
+     'Mod4+Return',
+     'triggered the "Mod4+Return" keybinding');
+
+=cut
+
+sub listen_for_binding {
+    my ($cb) = @_;
+    my $triggered = AnyEvent->condvar;
+    my $i3 = i3(get_socket_path());
+    $i3->connect()->recv;
+    $i3->subscribe({
+        binding => sub {
+            my ($event) = @_;
+            return unless $event->{change} eq 'run';
+            # We look at the command (which is “nop <binding>”) because that is
+            # easier than re-assembling the string representation of
+            # $event->{binding}.
+            $triggered->send($event->{binding}->{command});
+        },
+    })->recv;
+
+    my $t;
+    $t = AnyEvent->timer(
+        after => 0.5,
+        cb => sub {
+            $triggered->send('timeout');
+        }
+    );
+
+    $cb->();
+
+    my $recv = $triggered->recv;
+    $recv =~ s/^nop //g;
+    return $recv;
+}
+
+=head2 set_xkb_group($group)
+
+Changes the current XKB group from the default of 1 to C<$group>, which must be
+one of 1, 2, 3, 4.
+
+Returns false when there was an X11 error changing the group, true otherwise.
+
+=head2 xtest_key_press($detail)
+
+Sends a KeyPress event via XTEST, with the specified C<$detail>, i.e. key code.
+Use C<xev(1)> to find key codes.
+
+Returns false when there was an X11 error changing the group, true otherwise.
+
+=head2 xtest_key_release($detail)
+
+Sends a KeyRelease event via XTEST, with the specified C<$detail>, i.e. key code.
+Use C<xev(1)> to find key codes.
+
+Returns false when there was an X11 error changing the group, true otherwise.
+
+=head1 AUTHOR
+
+Michael Stapelberg <michael@i3wm.org>
+
+=cut
+
+1
diff --git a/testcases/t/257-keypress-group1-fallback.t b/testcases/t/257-keypress-group1-fallback.t
new file mode 100644 (file)
index 0000000..212dfd1
--- /dev/null
@@ -0,0 +1,96 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Verifies that when using multiple keyboard layouts at the same time, bindings
+# without a specified XKB group will work in all XKB groups.
+# Ticket: #2062
+# Bug still in: 4.11-103-gc8d51b4
+# Bug introduced with commit 0e5180cae9e9295678e3f053042b559e82cb8c98
+use i3test i3_autostart => 0;
+use i3test::XTEST;
+use ExtUtils::PkgConfig;
+
+SKIP: {
+    skip "libxcb-xkb too old (need >= 1.11)", 1 unless
+        ExtUtils::PkgConfig->atleast_version('xcb-xkb', '1.11');
+    skip "setxkbmap not found", 1 if
+        system(q|setxkbmap -print >/dev/null|) != 0;
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+bindsym Print nop Print
+bindsym Mod4+Return nop Mod4+Return
+EOT
+
+my $pid = launch_with_config($config);
+
+start_binding_capture;
+
+system(q|setxkbmap us,ru -option grp:alt_shift_toggle|);
+
+is(listen_for_binding(
+    sub {
+        xtest_key_press(107);
+        xtest_key_release(107);
+    },
+    ),
+   'Print',
+   'triggered the "Print" keybinding');
+
+is(listen_for_binding(
+    sub {
+        xtest_key_press(133); # Super_L
+        xtest_key_press(36); # Return
+        xtest_key_release(36); # Return
+        xtest_key_release(133); # Super_L
+    },
+    ),
+   'Mod4+Return',
+   'triggered the "Mod4+Return" keybinding');
+
+# Switch keyboard group to russian.
+set_xkb_group(1);
+
+is(listen_for_binding(
+    sub {
+        xtest_key_press(107);
+        xtest_key_release(107);
+    },
+    ),
+   'Print',
+   'triggered the "Print" keybinding');
+
+is(listen_for_binding(
+    sub {
+        xtest_key_press(133); # Super_L
+        xtest_key_press(36); # Return
+        xtest_key_release(36); # Return
+        xtest_key_release(133); # Super_L
+    },
+    ),
+   'Mod4+Return',
+   'triggered the "Mod4+Return" keybinding');
+
+sync_with_i3;
+is(scalar @i3test::XTEST::binding_events, 4, 'Received exactly 4 binding events');
+
+exit_gracefully($pid);
+
+}
+
+done_testing;
diff --git a/testcases/t/258-keypress-release.t b/testcases/t/258-keypress-release.t
new file mode 100644 (file)
index 0000000..4c2775b
--- /dev/null
@@ -0,0 +1,69 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Verifies that --release key bindings are not shadowed by non-release key
+# bindings for the same key.
+# Ticket: #2002
+# Bug still in: 4.11-103-gc8d51b4
+# Bug introduced with commit bf3cd41b5ddf1e757515ab5fbf811be56e5f69cc
+use i3test i3_autostart => 0;
+use i3test::XTEST;
+use ExtUtils::PkgConfig;
+
+SKIP: {
+    skip "libxcb-xkb too old (need >= 1.11)", 1 unless
+        ExtUtils::PkgConfig->atleast_version('xcb-xkb', '1.11');
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+bindsym Print nop Print
+bindsym --release Control+Print nop Control+Print
+EOT
+
+my $pid = launch_with_config($config);
+
+start_binding_capture;
+
+is(listen_for_binding(
+    sub {
+        xtest_key_press(107); # Print
+        xtest_key_release(107); # Print
+    },
+    ),
+    'Print',
+    'triggered the "Print" keybinding');
+
+is(listen_for_binding(
+    sub {
+        xtest_key_press(37); # Control_L
+        xtest_key_press(107); # Print
+        xtest_key_release(107); # Print
+        xtest_key_release(37); # Control_L
+    },
+    ),
+    'Control+Print',
+    'triggered the "Control+Print" keybinding');
+
+sync_with_i3;
+is(scalar @i3test::XTEST::binding_events, 2, 'Received exactly 2 binding events');
+
+exit_gracefully($pid);
+
+}
+
+done_testing;