[PATCH v5 2/5] Add XInput 2.x integration test framework

Peter Hutterer peter.hutterer at who-t.net
Mon Jun 4 21:41:45 PDT 2012


On Fri, May 25, 2012 at 09:06:09AM -0700, Chase Douglas wrote:
> Signed-off-by: Chase Douglas <chase.douglas at canonical.com>
> Reviewed-by: Peter Hutterer <peter.hutterer at who-t.net>
> ---
>  configure.ac                 |   14 +++
>  test/integration/.gitignore  |    1 +
>  test/integration/Makefile.am |   24 ++++++
>  test/integration/xi2.cpp     |  194 ++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 233 insertions(+)
>  create mode 100644 test/integration/.gitignore
>  create mode 100644 test/integration/xi2.cpp
> 
> diff --git a/configure.ac b/configure.ac
> index ba10b2e..0c511b8 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -2161,6 +2161,20 @@ if [test "x$XORG" = xyes && test "x$enable_integration_tests" != xno]; then
>      fi
>  fi
>  
> +PKG_CHECK_MODULES(TEST_X11, x11, have_test_x11=yes, have_test_x11=no)
> +PKG_CHECK_MODULES(TEST_XINPUT, [xi >= 1.6], have_test_xinput=yes, have_test_xinput=no)
> +
> +if [test "x$have_test_x11" != xyes]; then
> +    AC_MSG_NOTICE([libX11 not available, skipping input tests])
> +elif [test "x$have_test_xinput" != xyes]; then
> +    AC_MSG_NOTICE([libXi 1.6 not available, skipping input tests])
> +elif [test "x$have_xorg_gtest_evemu" != xyes]; then
> +    AC_MSG_NOTICE([uTouch-Evemu not available, skipping input tests])
> +else
> +    enable_input_tests=yes
> +fi
> +AM_CONDITIONAL(ENABLE_XORG_GTEST_INPUT_TESTS, [test "x$enable_input_tests" = xyes])
> +
>  dnl and the rest of these are generic, so they're in config.h
>  dnl 
>  dnl though, thanks to the passing of some significant amount of time, the
> diff --git a/test/integration/.gitignore b/test/integration/.gitignore
> new file mode 100644
> index 0000000..ab86ebf
> --- /dev/null
> +++ b/test/integration/.gitignore
> @@ -0,0 +1 @@
> +gtest-tests
> diff --git a/test/integration/Makefile.am b/test/integration/Makefile.am
> index 2028d35..ff9789e 100644
> --- a/test/integration/Makefile.am
> +++ b/test/integration/Makefile.am
> @@ -1,4 +1,28 @@
> +TESTS =
> +
>  if ENABLE_XORG_GTEST_TESTS
>  include $(top_srcdir)/test/integration/Makefile-xorg-gtest.am
>  noinst_LIBRARIES = $(XORG_GTEST_BUILD_LIBS)
> +
> +if ENABLE_XORG_GTEST_INPUT_TESTS
> +TESTS += gtest-tests
>  endif
> +endif
> +
> +noinst_PROGRAMS = $(TESTS)
> +
> +AM_CPPFLAGS = \
> +	-DDEFAULT_XORG_SERVER=\"$(abs_top_builddir)/hw/xfree86/Xorg\" \
> +	-DTEST_ROOT_DIR=\"$(abs_top_srcdir)/test/integration/\" \
> +	$(GTEST_CPPFLAGS) \
> +	$(XORG_GTEST_CPPFLAGS)
> +
> +AM_CXXFLAGS = $(GTEST_CXXFLAGS) $(XORG_GTEST_CXXFLAGS)
> +
> +gtest_tests_SOURCES = xi2.cpp
> +gtest_tests_LDADD = \
> +	$(TEST_XINPUT_LIBS) \
> +	$(TEST_X11_LIBS) \
> +	$(XORG_GTEST_LIBS) \
> +	$(EVEMU_LIBS) \
> +	$(XORG_GTEST_MAIN_LIBS)
> diff --git a/test/integration/xi2.cpp b/test/integration/xi2.cpp
> new file mode 100644
> index 0000000..d3c3a30
> --- /dev/null
> +++ b/test/integration/xi2.cpp
> @@ -0,0 +1,194 @@
> +#include <stdexcept>
> +
> +#include <xorg/gtest/xorg-gtest.h>
> +
> +#include <X11/extensions/XInput2.h>
> +
> +namespace {
> +
> +/**
> + * Wait for an event on the X connection.
> + *
> + * @param [in] display The X display connection
> + * @param [in] timeout The timeout in milliseconds
> + *
> + * @return Whether an event is available
> + */
> +bool WaitForEvent(::Display *display, time_t timeout = 1000)
> +{
> +    fd_set fds;
> +    FD_ZERO(&fds);
> +
> +    int display_fd = ConnectionNumber(display);
> +
> +    XSync(display, False);
> +
> +    if (XPending(display))
> +        return true;
> +    else {
> +        FD_SET(display_fd, &fds);
> +
> +        struct timeval timeval = {
> +            static_cast<time_t>(timeout / 1000),
> +            static_cast<time_t>(timeout % 1000),
> +        };
> +
> +        int ret;
> +        if (timeout)
> +            ret = select(display_fd + 1, &fds, NULL, NULL, &timeval);
> +        else
> +            ret = select(display_fd + 1, &fds, NULL, NULL, NULL);
> +
> +        if (ret < 0)
> +            throw std::runtime_error("Failed to select on X fd");
> +
> +        if (ret == 0)
> +            return false;
> +
> +        return XPending(display);
> +    }
> +}
> +
> +/**
> + * Wait for an event of a specific type on the X connection.
> + *
> + * All events preceding the matching event are discarded. If no event was found
> + * before the timeout expires, all events in the queue will have been discarded.
> + *
> + * @param [in] display   The X display connection
> + * @param [in] type      The X core protocol event type
> + * @param [in] extension The X extension opcode of a generic event, or -1 for
> + *                       any generic event
> + * @param [in] evtype    The X extension event type of a generic event, or -1
> + *                       for any event of the given extension
> + * @param [in] timeout   The timeout in milliseconds
> + *
> + * @return Whether an event is available
> + */
> +bool WaitForEventOfType(::Display *display, int type, int extension, int evtype,
> +                        time_t timeout = 1000)
> +{
> +    while (WaitForEvent(display)) {
> +        XEvent event;
> +        if (!XPeekEvent(display, &event))
> +            throw std::runtime_error("Failed to peek X event");
> +
> +        if (event.type != type) {
> +            if (XNextEvent(display, &event) != Success)
> +                throw std::runtime_error("Failed to remove X event");
> +            continue;
> +        }
> +
> +        if (event.type != GenericEvent || extension == -1)
> +            return true;
> +
> +        XGenericEvent *generic_event = reinterpret_cast<XGenericEvent*>(&event);
> +
> +        if (generic_event->extension != extension) {
> +            if (XNextEvent(display, &event) != Success)
> +                throw std::runtime_error("Failed to remove X event");
> +            continue;
> +        }
> +
> +        if (evtype == -1 || generic_event->evtype == evtype)
> +            return true;
> +
> +        if (XNextEvent(display, &event) != Success)
> +            throw std::runtime_error("Failed to remove X event");
> +    }
> +}
> +
> +/**
> + * Wait for a specific device to be added to the server.
> + *
> + * @param [in] display The X display connection
> + * @param [in] name    The name of the device to wait for
> + * @param [in] timeout The timeout in milliseconds
> + *
> + * @return Whether the device was added
> + */
> +bool WaitForDevice(::Display *display, const std::string &name,
> +                   time_t timeout = 1000)
> +{
> +    int opcode;
> +    int event_start;
> +    int error_start;
> +
> +    if (!XQueryExtension(display, "XInputExtension", &opcode, &event_start,
> +                         &error_start))
> +        throw std::runtime_error("Failed to query XInput extension");
> +
> +    while (WaitForEventOfType(display, GenericEvent, opcode,
> +                              XI_HierarchyChanged)) {
> +        XEvent event;
> +        if (XNextEvent(display, &event) != Success)
> +            throw std::runtime_error("Failed to get X event");
> +
> +        XGenericEventCookie *xcookie =
> +            reinterpret_cast<XGenericEventCookie*>(&event.xcookie);
> +        if (!XGetEventData(display, xcookie))
> +            throw std::runtime_error("Failed to get X event data");
> +
> +        XIHierarchyEvent *hierarchy_event =
> +            reinterpret_cast<XIHierarchyEvent*>(xcookie->data);
> +
> +        if (!(hierarchy_event->flags & XISlaveAdded)) {
> +            XFreeEventData(display, xcookie);
> +            continue;
> +        }

I didn't actually hit this but while debugging something else here I
realised there's a race condition here. the way devices are enabled, you'll
see the XISlaveAdded before the device is enabled. In the unlikely case that
this test is too fast, you may be sending events before the server will take
them, causing them to be discarded. 
this will result in heisenbugs as some tests may arbitrarily fail.

simply changing this to XIDeviceEnabled should do the job, though you'll
need some more exception handling in case you get the enabled for other
devices, I suspect.

follow-up patch for this is fine with me.

Cheers,
  Peter

> +
> +        bool device_found = false;
> +        for (int i = 0; i < hierarchy_event->num_info; i++) {
> +            if (!(hierarchy_event->info[i].flags & XISlaveAdded))
> +                continue;
> +
> +            int num_devices;
> +            XIDeviceInfo *device_info =
> +                XIQueryDevice(display, hierarchy_event->info[i].deviceid,
> +                              &num_devices);
> +            if (num_devices != 1 || !device_info)
> +                throw std::runtime_error("Failed to query device");
> +
> +            if (name.compare(device_info[0].name) == 0) {
> +                device_found = true;
> +                break;
> +            }
> +        }
> +
> +        XFreeEventData(display, xcookie);
> +
> +        if (device_found)
> +            return true;
> +    }
> +
> +    return false;
> +}
> +
> +}
> +
> +/**
> + * A test fixture for testing the XInput 2.x extension.
> + *
> + * @tparam The XInput 2.x minor version
> + */
> +class XInput2Test : public xorg::testing::Test,
> +                    public ::testing::WithParamInterface<int> {
> +protected:
> +    virtual void SetUp()
> +    {
> +        ASSERT_NO_FATAL_FAILURE(xorg::testing::Test::SetUp());
> +
> +        int event_start;
> +        int error_start;
> +
> +        ASSERT_TRUE(XQueryExtension(Display(), "XInputExtension", &xi2_opcode_,
> +                                    &event_start, &error_start));
> +
> +        int major = 2;
> +        int minor = GetParam();
> +
> +        ASSERT_EQ(Success, XIQueryVersion(Display(), &major, &minor));
> +    }
> +
> +    int xi2_opcode_;
> +};
> -- 
> 1.7.9.5
> 


More information about the xorg-devel mailing list