/* * Copyright © 2013 Red Hat, Inc. * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that copyright * notice and this permission notice appear in supporting documentation, and * that the name of the copyright holders not be used in advertising or * publicity pertaining to distribution of the software without specific, * written prior permission. The copyright holders make no representations * about the suitability of this software for any purpose. It is provided "as * is" without express or implied warranty. * * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE * OF THIS SOFTWARE. */ #if HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "litest.h" #include "litest-int.h" #include "libinput-util.h" static int in_debugger = -1; struct test { struct list node; char *name; TCase *tc; enum litest_device_type devices; }; struct suite { struct list node; struct list tests; char *name; Suite *suite; }; static struct litest_device *current_device; struct litest_device *litest_current_device(void) { return current_device; } void litest_set_current_device(struct litest_device *device) { current_device = device; } void litest_generic_device_teardown(void) { litest_delete_device(current_device); current_device = NULL; } extern struct litest_test_device litest_keyboard_device; extern struct litest_test_device litest_synaptics_clickpad_device; extern struct litest_test_device litest_synaptics_touchpad_device; extern struct litest_test_device litest_trackpoint_device; extern struct litest_test_device litest_bcm5974_device; extern struct litest_test_device litest_mouse_device; extern struct litest_test_device litest_wacom_touch_device; struct litest_test_device* devices[] = { &litest_synaptics_clickpad_device, &litest_synaptics_touchpad_device, &litest_keyboard_device, &litest_trackpoint_device, &litest_bcm5974_device, &litest_mouse_device, &litest_wacom_touch_device, NULL, }; static struct list all_tests; static void litest_add_tcase_for_device(struct suite *suite, void *func, const struct litest_test_device *dev) { struct test *t; const char *test_name = dev->shortname; list_for_each(t, &suite->tests, node) { if (strcmp(t->name, test_name) != 0) continue; tcase_add_test(t->tc, func); return; } t = zalloc(sizeof(*t)); t->name = strdup(test_name); t->tc = tcase_create(test_name); list_insert(&suite->tests, &t->node); tcase_add_checked_fixture(t->tc, dev->setup, dev->teardown ? dev->teardown : litest_generic_device_teardown); tcase_add_test(t->tc, func); suite_add_tcase(suite->suite, t->tc); } static void litest_add_tcase_no_device(struct suite *suite, void *func) { struct test *t; const char *test_name = "no device"; list_for_each(t, &suite->tests, node) { if (strcmp(t->name, test_name) != 0) continue; tcase_add_test(t->tc, func); return; } t = zalloc(sizeof(*t)); t->name = strdup(test_name); t->tc = tcase_create(test_name); list_insert(&suite->tests, &t->node); tcase_add_test(t->tc, func); suite_add_tcase(suite->suite, t->tc); } static void litest_add_tcase(struct suite *suite, void *func, enum litest_device_feature required, enum litest_device_feature excluded) { struct litest_test_device **dev = devices; if (required == LITEST_DISABLE_DEVICE && excluded == LITEST_DISABLE_DEVICE) { litest_add_tcase_no_device(suite, func); } else if (required != LITEST_ANY || excluded != LITEST_ANY) { while (*dev) { if (((*dev)->features & required) == required && ((*dev)->features & excluded) == 0) litest_add_tcase_for_device(suite, func, *dev); dev++; } } else { while (*dev) { litest_add_tcase_for_device(suite, func, *dev); dev++; } } } void litest_add_no_device(const char *name, void *func) { litest_add(name, func, LITEST_DISABLE_DEVICE, LITEST_DISABLE_DEVICE); } void litest_add(const char *name, void *func, enum litest_device_feature required, enum litest_device_feature excluded) { struct suite *s; if (all_tests.next == NULL && all_tests.prev == NULL) list_init(&all_tests); list_for_each(s, &all_tests, node) { if (strcmp(s->name, name) == 0) { litest_add_tcase(s, func, required, excluded); return; } } s = zalloc(sizeof(*s)); s->name = strdup(name); s->suite = suite_create(s->name); list_init(&s->tests); list_insert(&all_tests, &s->node); litest_add_tcase(s, func, required, excluded); } int is_debugger_attached() { int status; int rc; int pid = fork(); if (pid == -1) return 0; if (pid == 0) { int ppid = getppid(); if (ptrace(PTRACE_ATTACH, ppid, NULL, NULL) == 0) { waitpid(ppid, NULL, 0); ptrace(PTRACE_CONT, NULL, NULL); ptrace(PTRACE_DETACH, ppid, NULL, NULL); rc = 0; } else { rc = 1; } _exit(rc); } else { waitpid(pid, &status, 0); rc = WEXITSTATUS(status); } return rc; } static void litest_list_tests(struct list *tests) { struct suite *s; list_for_each(s, tests, node) { struct test *t; printf("%s:\n", s->name); list_for_each(t, &s->tests, node) { printf(" %s\n", t->name); } } } static const struct option opts[] = { { "list", 0, 0, 'l' }, { 0, 0, 0, 0} }; int litest_run(int argc, char **argv) { struct suite *s, *snext; int failed; SRunner *sr = NULL; if (in_debugger == -1) { in_debugger = is_debugger_attached(); if (in_debugger) setenv("CK_FORK", "no", 0); } list_for_each(s, &all_tests, node) { if (!sr) sr = srunner_create(s->suite); else srunner_add_suite(sr, s->suite); } while(1) { int c; int option_index = 0; c = getopt_long(argc, argv, "", opts, &option_index); if (c == -1) break; switch(c) { case 'l': litest_list_tests(&all_tests); return 0; default: fprintf(stderr, "usage: %s [--list]\n", argv[0]); return 1; } } srunner_run_all(sr, CK_NORMAL); failed = srunner_ntests_failed(sr); srunner_free(sr); list_for_each_safe(s, snext, &all_tests, node) { struct test *t, *tnext; list_for_each_safe(t, tnext, &s->tests, node) { free(t->name); list_remove(&t->node); free(t); } list_remove(&s->node); free(s->name); free(s); } return failed; } static int open_restricted(const char *path, int flags, void *userdata) { return open(path, flags); } static void close_restricted(int fd, void *userdata) { close(fd); } const struct libinput_interface interface = { .open_restricted = open_restricted, .close_restricted = close_restricted, }; static struct input_absinfo * merge_absinfo(const struct input_absinfo *orig, const struct input_absinfo *override) { struct input_absinfo *abs; int nelem, i; size_t sz = ABS_MAX + 1; if (!orig) return NULL; abs = calloc(sz, sizeof(*abs)); ck_assert(abs != NULL); nelem = 0; while (orig[nelem].value != -1) { abs[nelem] = orig[nelem]; nelem++; ck_assert_int_lt(nelem, sz); } /* just append, if the same axis is present twice, libevdev will only use the last value anyway */ i = 0; while (override && override[i].value != -1) { abs[nelem++] = override[i++]; ck_assert_int_lt(nelem, sz); } ck_assert_int_lt(nelem, sz); abs[nelem].value = -1; return abs; } static int* merge_events(const int *orig, const int *override) { int *events; int nelem, i; size_t sz = KEY_MAX * 3; if (!orig) return NULL; events = calloc(sz, sizeof(int)); ck_assert(events != NULL); nelem = 0; while (orig[nelem] != -1) { events[nelem] = orig[nelem]; nelem++; ck_assert_int_lt(nelem, sz); } /* just append, if the same axis is present twice, libevdev will * ignore the double definition anyway */ i = 0; while (override && override[i] != -1) { events[nelem++] = override[i++]; ck_assert_int_le(nelem, sz); } ck_assert_int_lt(nelem, sz); events[nelem] = -1; return events; } static struct litest_device * litest_create(enum litest_device_type which, const char *name_override, struct input_id *id_override, const struct input_absinfo *abs_override, const int *events_override) { struct litest_device *d = NULL; struct litest_test_device **dev; const char *name; const struct input_id *id; struct input_absinfo *abs; int *events; dev = devices; while (*dev) { if ((*dev)->type == which) break; dev++; } if (!dev) ck_abort_msg("Invalid device type %d\n", which); d = zalloc(sizeof(*d)); ck_assert(d != NULL); /* device has custom create method */ if ((*dev)->create) { (*dev)->create(d); if (abs_override || events_override) ck_abort_msg("Custom create cannot" "be overridden"); return d; } abs = merge_absinfo((*dev)->absinfo, abs_override); events = merge_events((*dev)->events, events_override); name = name_override ? name_override : (*dev)->name; id = id_override ? id_override : (*dev)->id; d->uinput = litest_create_uinput_device_from_description(name, id, abs, events); d->interface = (*dev)->interface; free(abs); free(events); return d; } struct litest_device * litest_create_device_with_overrides(enum litest_device_type which, const char *name_override, struct input_id *id_override, const struct input_absinfo *abs_override, const int *events_override) { struct litest_device *d; int fd; int rc; const char *path; d = litest_create(which, name_override, id_override, abs_override, events_override); path = libevdev_uinput_get_devnode(d->uinput); ck_assert(path != NULL); fd = open(path, O_RDWR|O_NONBLOCK); ck_assert_int_ne(fd, -1); rc = libevdev_new_from_fd(fd, &d->evdev); ck_assert_int_eq(rc, 0); d->libinput = libinput_path_create_context(&interface, NULL); ck_assert(d->libinput != NULL); d->libinput_device = libinput_path_add_device(d->libinput, path); ck_assert(d->libinput_device != NULL); libinput_device_ref(d->libinput_device); if (d->interface) { d->interface->min[ABS_X] = libevdev_get_abs_minimum(d->evdev, ABS_X); d->interface->max[ABS_X] = libevdev_get_abs_maximum(d->evdev, ABS_X); d->interface->min[ABS_Y] = libevdev_get_abs_minimum(d->evdev, ABS_Y); d->interface->max[ABS_Y] = libevdev_get_abs_maximum(d->evdev, ABS_Y); } return d; } struct litest_device * litest_create_device(enum litest_device_type which) { return litest_create_device_with_overrides(which, NULL, NULL, NULL, NULL); } int litest_handle_events(struct litest_device *d) { struct pollfd fd; fd.fd = libinput_get_fd(d->libinput); fd.events = POLLIN; while (poll(&fd, 1, 1)) libinput_dispatch(d->libinput); return 0; } void litest_delete_device(struct litest_device *d) { if (!d) return; libinput_device_unref(d->libinput_device); libinput_destroy(d->libinput); libevdev_free(d->evdev); libevdev_uinput_destroy(d->uinput); memset(d,0, sizeof(*d)); free(d); } void litest_event(struct litest_device *d, unsigned int type, unsigned int code, int value) { libevdev_uinput_write_event(d->uinput, type, code, value); } static int auto_assign_value(struct litest_device *d, const struct input_event *ev, int slot, int x, int y) { static int tracking_id; int value = ev->value; if (value != LITEST_AUTO_ASSIGN || ev->type != EV_ABS) return value; switch (ev->code) { case ABS_X: case ABS_MT_POSITION_X: value = litest_scale(d, ABS_X, x); break; case ABS_Y: case ABS_MT_POSITION_Y: value = litest_scale(d, ABS_Y, y); break; case ABS_MT_TRACKING_ID: value = ++tracking_id; break; case ABS_MT_SLOT: value = slot; break; } return value; } void litest_touch_down(struct litest_device *d, unsigned int slot, int x, int y) { struct input_event *ev; if (d->interface->touch_down) { d->interface->touch_down(d, slot, x, y); return; } ev = d->interface->touch_down_events; while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) { int value = auto_assign_value(d, ev, slot, x, y); litest_event(d, ev->type, ev->code, value); ev++; } } void litest_touch_up(struct litest_device *d, unsigned int slot) { struct input_event *ev; struct input_event up[] = { { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN }, { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = -1 }, { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, { .type = -1, .code = -1 } }; if (d->interface->touch_up) { d->interface->touch_up(d, slot); return; } else if (d->interface->touch_up_events) { ev = d->interface->touch_up_events; } else ev = up; while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) { int value = auto_assign_value(d, ev, slot, 0, 0); litest_event(d, ev->type, ev->code, value); ev++; } } void litest_touch_move(struct litest_device *d, unsigned int slot, int x, int y) { struct input_event *ev; if (d->interface->touch_move) { d->interface->touch_move(d, slot, x, y); return; } ev = d->interface->touch_move_events; while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) { int value = auto_assign_value(d, ev, slot, x, y); litest_event(d, ev->type, ev->code, value); ev++; } } void litest_touch_move_to(struct litest_device *d, unsigned int slot, int x_from, int y_from, int x_to, int y_to, int steps) { for (int i = 0; i < steps - 1; i++) litest_touch_move(d, slot, x_from + (x_to - x_from)/steps * i, y_from + (y_to - y_from)/steps * i); litest_touch_move(d, slot, x_to, y_to); } void litest_button_click(struct litest_device *d, unsigned int button, bool is_press) { struct input_event *ev; struct input_event click[] = { { .type = EV_KEY, .code = button, .value = is_press ? 1 : 0 }, { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, }; ARRAY_FOR_EACH(click, ev) litest_event(d, ev->type, ev->code, ev->value); } int litest_scale(const struct litest_device *d, unsigned int axis, int val) { int min, max; ck_assert_int_ge(val, 0); ck_assert_int_le(val, 100); ck_assert_int_le(axis, ABS_Y); min = d->interface->min[axis]; max = d->interface->max[axis]; return (max - min) * val/100.0 + min; } void litest_drain_events(struct libinput *li) { struct libinput_event *event; libinput_dispatch(li); while ((event = libinput_get_event(li))) { libinput_event_destroy(event); libinput_dispatch(li); } } struct libevdev_uinput * litest_create_uinput_device_from_description(const char *name, const struct input_id *id, const struct input_absinfo *abs, const int *events) { struct libevdev_uinput *uinput; struct libevdev *dev; int type, code; int rc; const struct input_absinfo default_abs = { .value = 0, .minimum = 0, .maximum = 0xffff, .fuzz = 0, .flat = 0, .resolution = 100 }; dev = libevdev_new(); ck_assert(dev != NULL); libevdev_set_name(dev, name); if (id) { libevdev_set_id_bustype(dev, id->bustype); libevdev_set_id_vendor(dev, id->vendor); libevdev_set_id_product(dev, id->product); } while (abs && abs->value != -1) { rc = libevdev_enable_event_code(dev, EV_ABS, abs->value, abs); ck_assert_int_eq(rc, 0); abs++; } while (events && (type = *events++) != -1 && (code = *events++) != -1) { if (type == INPUT_PROP_MAX) { rc = libevdev_enable_property(dev, code); } else { if (type != EV_SYN) ck_assert(!libevdev_has_event_code(dev, type, code)); rc = libevdev_enable_event_code(dev, type, code, type == EV_ABS ? &default_abs : NULL); } ck_assert_int_eq(rc, 0); } rc = libevdev_uinput_create_from_device(dev, LIBEVDEV_UINPUT_OPEN_MANAGED, &uinput); ck_assert_int_eq(rc, 0); libevdev_free(dev); return uinput; } static struct libevdev_uinput * litest_create_uinput_abs_device_v(const char *name, struct input_id *id, const struct input_absinfo *abs, va_list args) { int events[KEY_MAX * 2 + 2]; /* increase this if not sufficient */ int *event = events; int type, code; while ((type = va_arg(args, int)) != -1 && (code = va_arg(args, int)) != -1) { *event++ = type; *event++ = code; ck_assert(event < &events[ARRAY_LENGTH(events) - 2]); } *event++ = -1; *event++ = -1; return litest_create_uinput_device_from_description(name, id, abs, events); } struct libevdev_uinput * litest_create_uinput_abs_device(const char *name, struct input_id *id, const struct input_absinfo *abs, ...) { struct libevdev_uinput *uinput; va_list args; va_start(args, abs); uinput = litest_create_uinput_abs_device_v(name, id, abs, args); va_end(args); return uinput; } struct libevdev_uinput * litest_create_uinput_device(const char *name, struct input_id *id, ...) { struct libevdev_uinput *uinput; va_list args; va_start(args, id); uinput = litest_create_uinput_abs_device_v(name, id, NULL, args); va_end(args); return uinput; }