test: add per-device udev rule support

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>
This commit is contained in:
Peter Hutterer 2015-02-02 10:47:52 +10:00
parent 7d7ea4ec2e
commit 4ec2947cc9
6 changed files with 125 additions and 9 deletions

View 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

View 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 = {

View 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 {

View 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,
};

View 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)

View 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);