test: add per-device udev rule support
authorPeter Hutterer <peter.hutterer@who-t.net>
Mon, 2 Feb 2015 00:47:52 +0000 (10:47 +1000)
committerPeter Hutterer <peter.hutterer@who-t.net>
Tue, 3 Feb 2015 00:34:26 +0000 (10:34 +1000)
Don't rely on a magic version tag, instead let a device define a udev rule and
drop that into the udev runtime directory before the device is created.

There are a couple of caveats with this approach: first, since this changes
system-wide state it may cause issues on the device the test suite is run on.
This can be avoided if the udev rules have filter patterns that ensure only
test devices are affected.

Second, the check test suite aborts but it doesn't run the teardown() function
if a test fails. So far this wasn't a problem since uinput devices disappear
whenever we exit. The rules files will hang around though, so an unchecked
fixture was added to delete all litest-foo.rules files before and after a test
case starts. Unchecked fixtures are run regardless of the exit status of the
test but run in the same address space - i.e. no ck_assert() usage.

Also unchecked fixtures are only run once per test-case, not once per test
function. For us, that means they're only run once per device (we use the
devices as test case), i.e. if a test fails and the udev rule isn't tidied up,
the next test may be unpredictable. This shouldn't matter too much though.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
configure.ac
src/evdev-mt-touchpad.c
test/litest-int.h
test/litest-synaptics-x1-carbon-3rd.c
test/litest.c
test/litest.h

index 39e42d0fda2a55744dbac00139dc02daedf21d77..803d5733208a1c0e68aedb6ddf89216a8d07b81b 100644 (file)
@@ -34,6 +34,7 @@ LIBINPUT_LT_VERSION=8:0:1
 AC_SUBST(LIBINPUT_LT_VERSION)
 
 AM_SILENT_RULES([yes])
+AC_USE_SYSTEM_EXTENSIONS
 
 # Check for programs
 AC_PROG_CC_C99
index 94cbc1365c69fa5575b95adb7d186d1b348b4467..ae37ab17d81ce631f4433e18a9cfae8126df50f5 100644 (file)
@@ -1107,12 +1107,6 @@ tp_tag_device(struct evdev_device *device,
        if (udev_device_get_property_value(udev_device,
                                           "TOUCHPAD_HAS_TRACKPOINT_BUTTONS"))
                device->tags |= EVDEV_TAG_TOUCHPAD_TRACKPOINT;
-
-       /* Magic version tag: used by the litest device. Should never be set
-        * in real life but allows us to test for these features without
-        * requiring custom udev rules during make check */
-       if (libevdev_get_id_version(device->evdev) == 0xfffa)
-               device->tags |= EVDEV_TAG_TOUCHPAD_TRACKPOINT;
 }
 
 static struct evdev_dispatch_interface tp_interface = {
index 95bc2483978f0b89c49f551431a2a1b18271d67f..12746f1bd6e34b7f695b46ed2fd5db53b6eabd60 100644 (file)
@@ -69,6 +69,8 @@ struct litest_test_device {
        */
        struct input_absinfo *absinfo;
        struct litest_device_interface *interface;
+
+       const char *udev_rule;
 };
 
 struct litest_device_interface {
index 8be29e03d3ec604060cc3f976034f5054f71bdfd..67d6f46713ce2324da88ca8ab1178d5205da87bb 100644 (file)
@@ -65,7 +65,6 @@ static struct input_id input_id = {
        .bustype = 0x11,
        .vendor = 0x2,
        .product = 0x7,
-       .version = 0xfffa, /* Magic value, used to detect this test device */
 };
 
 static int events[] = {
@@ -97,6 +96,16 @@ static struct input_absinfo absinfo[] = {
        { .value = -1 }
 };
 
+static const char udev_rule[] =
+"ACTION==\"remove\", GOTO=\"touchpad_end\"\n"
+"KERNEL!=\"event*\", GOTO=\"touchpad_end\"\n"
+"ENV{ID_INPUT_TOUCHPAD}==\"\", GOTO=\"touchpad_end\"\n"
+"\n"
+"ATTRS{name}==\"litest*X1C3rd*\",\\\n"
+"    ENV{TOUCHPAD_HAS_TRACKPOINT_BUTTONS}=\"1\"\n"
+"\n"
+"LABEL=\"touchpad_end\"";
+
 struct litest_test_device litest_synaptics_carbon3rd_device = {
        .type = LITEST_SYNAPTICS_TRACKPOINT_BUTTONS,
        .features = LITEST_TOUCHPAD | LITEST_CLICKPAD | LITEST_BUTTON,
@@ -104,8 +113,9 @@ struct litest_test_device litest_synaptics_carbon3rd_device = {
        .setup = litest_synaptics_carbon3rd_setup,
        .interface = &interface,
 
-       .name = "SynPS/2 Synaptics TouchPad",
+       .name = "SynPS/2 Synaptics TouchPad X1C3rd",
        .id = &input_id,
        .events = events,
        .absinfo = absinfo,
+       .udev_rule = udev_rule,
 };
index 2a336e9aa1a3b0a91a563d2bc67574834b9b92ca..b220b2fe06879cb53b241600144b609becff47d5 100644 (file)
@@ -26,6 +26,7 @@
 
 #include <assert.h>
 #include <check.h>
+#include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <getopt.h>
@@ -44,6 +45,9 @@
 #include "litest-int.h"
 #include "libinput-util.h"
 
+#define UDEV_RULES_D "/run/udev/rules.d"
+#define UDEV_RULE_PREFIX "99-litest-"
+
 static int in_debugger = -1;
 static int verbose = 0;
 
@@ -114,6 +118,55 @@ struct litest_test_device* devices[] = {
 
 static struct list all_tests;
 
+static void
+litest_reload_udev_rules(void)
+{
+       system("udevadm control --reload-rules");
+}
+
+static int
+litest_udev_rule_filter(const struct dirent *entry)
+{
+       return strncmp(entry->d_name,
+                      UDEV_RULE_PREFIX,
+                      strlen(UDEV_RULE_PREFIX)) == 0;
+}
+
+static void
+litest_drop_udev_rules(void)
+{
+       int n;
+       int rc;
+       struct dirent **entries;
+       char path[PATH_MAX];
+
+       n = scandir(UDEV_RULES_D,
+                   &entries,
+                   litest_udev_rule_filter,
+                   alphasort);
+       if (n < 0)
+               return;
+
+       while (n--) {
+               rc = snprintf(path, sizeof(path),
+                             "%s/%s",
+                             UDEV_RULES_D,
+                             entries[n]->d_name);
+               if (rc > 0 &&
+                   (size_t)rc == strlen(UDEV_RULES_D) +
+                           strlen(entries[n]->d_name) + 1)
+                       unlink(path);
+               else
+                       fprintf(stderr,
+                               "Failed to delete %s. Remaining tests are unreliable\n",
+                               entries[n]->d_name);
+               free(entries[n]);
+       }
+       free(entries);
+
+       litest_reload_udev_rules();
+}
+
 static void
 litest_add_tcase_for_device(struct suite *suite,
                            void *func,
@@ -134,6 +187,13 @@ litest_add_tcase_for_device(struct suite *suite,
        t->name = strdup(test_name);
        t->tc = tcase_create(test_name);
        list_insert(&suite->tests, &t->node);
+       /* we can't guarantee that we clean up properly if a test fails, the
+          udev rules used for a previous test may still be in place. Add an
+          unchecked fixture to always clean up all rules before/after a
+          test case completes */
+       tcase_add_unchecked_fixture(t->tc,
+                                   litest_drop_udev_rules,
+                                   litest_drop_udev_rules);
        tcase_add_checked_fixture(t->tc, dev->setup,
                                  dev->teardown ? dev->teardown : litest_generic_device_teardown);
        tcase_add_test(t->tc, func);
@@ -464,6 +524,40 @@ merge_events(const int *orig, const int *override)
        return events;
 }
 
+static char *
+litest_init_udev_rules(struct litest_test_device *dev)
+{
+       int rc;
+       FILE *f;
+       char *path;
+
+       if (!dev->udev_rule)
+               return NULL;
+
+       rc = mkdir(UDEV_RULES_D, 0755);
+       if (rc == -1 && errno != EEXIST)
+               ck_abort_msg("Failed to create udev rules directory (%s)\n",
+                            strerror(errno));
+
+       rc = asprintf(&path,
+                     "%s/%s%s.rules",
+                     UDEV_RULES_D,
+                     UDEV_RULE_PREFIX,
+                     dev->shortname);
+       ck_assert_int_eq(rc,
+                        strlen(UDEV_RULES_D) +
+                        strlen(UDEV_RULE_PREFIX) +
+                        strlen(dev->shortname) + 7);
+       f = fopen(path, "w");
+       ck_assert_notnull(f);
+       ck_assert_int_ge(fputs(dev->udev_rule, f), 0);
+       fclose(f);
+
+       litest_reload_udev_rules();
+
+       return path;
+}
+
 static struct litest_device *
 litest_create(enum litest_device_type which,
              const char *name_override,
@@ -477,6 +571,7 @@ litest_create(enum litest_device_type which,
        const struct input_id *id;
        struct input_absinfo *abs;
        int *events;
+       char *udev_file;
 
        dev = devices;
        while (*dev) {
@@ -491,12 +586,17 @@ litest_create(enum litest_device_type which,
        d = zalloc(sizeof(*d));
        ck_assert(d != NULL);
 
+       udev_file = litest_init_udev_rules(*dev);
+
        /* device has custom create method */
        if ((*dev)->create) {
                (*dev)->create(d);
-               if (abs_override || events_override)
+               if (abs_override || events_override) {
+                       if (udev_file)
+                               unlink(udev_file);
                        ck_abort_msg("Custom create cannot"
                                     "be overridden");
+               }
 
                return d;
        }
@@ -511,6 +611,7 @@ litest_create(enum litest_device_type which,
                                                                 abs,
                                                                 events);
        d->interface = (*dev)->interface;
+       d->udev_rule_file = udev_file;
        free(abs);
        free(events);
 
@@ -629,6 +730,12 @@ litest_delete_device(struct litest_device *d)
        if (!d)
                return;
 
+       if (d->udev_rule_file) {
+               unlink(d->udev_rule_file);
+               free(d->udev_rule_file);
+               d->udev_rule_file = NULL;
+       }
+
        libinput_device_unref(d->libinput_device);
        libinput_path_remove_device(d->libinput_device);
        if (d->owns_context)
index 0625a71ec48bb8396ed4b4a515444d3fa1fc85e6..60ec4587976e9f5a815d3022a46a7e339b154470 100644 (file)
@@ -84,6 +84,8 @@ struct litest_device {
        bool skip_ev_syn;
 
        void *private; /* device-specific data */
+
+       char *udev_rule_file;
 };
 
 struct libinput *litest_create_context(void);