diff --git a/src/util-multivalue.h b/src/util-multivalue.h index dd7234c3..c20b4eaa 100644 --- a/src/util-multivalue.h +++ b/src/util-multivalue.h @@ -44,6 +44,12 @@ struct multivalue { static inline void multivalue_extract(const struct multivalue *v, void *ptr) { + /* ignore false positives from gcc: + * ../src/util-multivalue.h:52:33: warning: array subscript ‘double[0]’ is partly outside array bounds of ‘int32_t[1]’ {aka ‘int[1]’} [-Warray-bounds=] + * 52 | case 'd': *(double*)ptr = v->value.d; break; + */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" switch (v->type) { case 'b': *(bool *)ptr = v->value.b; break; case 'c': *(char *)ptr = v->value.c; break; @@ -54,6 +60,7 @@ multivalue_extract(const struct multivalue *v, void *ptr) default: abort(); } +#pragma GCC diagnostic pop } static inline void diff --git a/test/litest-runner.c b/test/litest-runner.c index 6d9b929a..c3ea3e06 100644 --- a/test/litest-runner.c +++ b/test/litest-runner.c @@ -41,6 +41,7 @@ #include "util-files.h" #include "util-list.h" +#include "util-multivalue.h" #include "util-stringbuf.h" static bool use_jmpbuf; /* only used for max_forks = 0 */ @@ -255,6 +256,7 @@ litest_runner_test_run(const struct litest_runner_test_description *desc) { const struct litest_runner_test_env env = { .rangeval = desc->rangeval, + .params = desc->params, }; if (desc->setup) @@ -647,6 +649,59 @@ print_lines(FILE *fp, const char *log, const char *prefix) strv_free(lines); } +void +_litest_test_param_fetch(const struct litest_test_parameters *params, ...) +{ + struct litest_test_param *p; + + const char *name; + + va_list args; + va_start(args, params); + + while ((name = va_arg(args, const char *))) { + bool found = false; + void **ptr = va_arg(args, void *); + list_for_each(p, ¶ms->test_params, link) { + if (streq(p->name, name)) { + found = true; + multivalue_extract(&p->value, ptr); + break; + } + } + if (!found) + litest_abort_msg("Unknown test parameter name '%s'", name); + } + + va_end(args); +} + + +struct litest_test_parameters * +litest_test_parameters_new(void) +{ + struct litest_test_parameters *params = zalloc(sizeof *params); + params->refcnt = 1; + list_init(¶ms->test_params); + return params; +} + +struct litest_test_parameters * +litest_test_parameters_unref(struct litest_test_parameters *params) +{ + if (params) { + assert(params->refcnt > 0); + if (--params->refcnt == 0) { + struct litest_test_param *p; + list_for_each_safe(p, ¶ms->test_params, link) { + free(p); + } + free(params); + } + } + return NULL; +} + static void litest_runner_log_test_result(struct litest_runner *runner, struct litest_runner_test *t) { @@ -671,6 +726,16 @@ litest_runner_log_test_result(struct litest_runner *runner, struct litest_runner if (range_is_valid(&t->desc.args.range)) fprintf(runner->fp, " rangeval: %d # %d..%d\n", t->desc.rangeval, min, max); + if (t->desc.params) { + fprintf(runner->fp, " params:\n"); + struct litest_test_param *p; + list_for_each(p, &t->desc.params->test_params, link) { + char *val = multivalue_as_str(&p->value); + fprintf(runner->fp, " %s: %s\n", p->name, val); + free(val); + } + } + fprintf(runner->fp, " duration: %ld # (ms), total test run time: %02d:%02d\n", t->times.end_millis - t->times.start_millis, diff --git a/test/litest-runner.h b/test/litest-runner.h index 9bee9dcd..a35695d6 100644 --- a/test/litest-runner.h +++ b/test/litest-runner.h @@ -43,17 +43,47 @@ enum litest_runner_result { LITEST_SYSTEM_ERROR = 80, /**< unrelated error occurred */ }; +/* For parametrized tests (litest_add_parametrized and friends) + * a list of these is passed to every test. This struct isn't used + * directly, use litest_test_param_fetch() instead. + */ +struct litest_test_param { + struct list link; + char name[128]; + struct multivalue value; +}; + +struct litest_test_parameters { + int refcnt; + struct list test_params; +}; + +struct litest_test_parameters * +litest_test_parameters_new(void); + +struct litest_test_parameters * +litest_test_parameters_unref(struct litest_test_parameters *params); + +#define litest_test_param_fetch(...) \ + _litest_test_param_fetch(__VA_ARGS__, NULL) + +void +_litest_test_param_fetch(const struct litest_test_parameters *params, ...); + /** * This struct is passed into every test. */ struct litest_runner_test_env { int rangeval; /* The current value within the args.range (or 0) */ + const struct litest_test_parameters *params; }; struct litest_runner_test_description { - char name[256]; /* The name of the test */ + char name[512]; /* The name of the test */ int rangeval; /* The current value within the args.range (or 0) */ + struct litest_test_parameters *params; + /* test function and corresponding setup/teardown, if any */ enum litest_runner_result (*func)(const struct litest_runner_test_env *); void (*setup)(const struct litest_runner_test_description *); diff --git a/test/litest-selftest.c b/test/litest-selftest.c index 9c3403ae..a788ad7f 100644 --- a/test/litest-selftest.c +++ b/test/litest-selftest.c @@ -420,6 +420,82 @@ START_TEST(zalloc_too_large) } END_TEST +struct permutation { + int32_t i; + const char *s; + bool b; + + bool found; +}; + +static int +permutation_func(struct litest_parameters_permutation *permutation, void *userdata) +{ + struct litest_parameters_permutation_value *value; + + int32_t first; + const char *second; + bool third; + + value = list_first_entry(&permutation->values, value, link); + multivalue_extract_typed(&value->value, 'i', &first); + + value = list_first_entry(&value->link, value, link); + multivalue_extract_typed(&value->value, 's', &second); + + value = list_first_entry(&value->link, value, link); + multivalue_extract_typed(&value->value, 'b', &third); + + struct permutation *p = userdata; + while (p->s) { + if (p->i == first && streq(p->s, second) && p->b == third) { + p->found = true; + break; + } + p++; + } + + return 0; +} + +START_TEST(parameter_permutations) +{ + struct permutation permutations[] = { + { 1, "a", true }, + { 1, "a", false }, + { 1, "ab", true }, + { 1, "ab", false }, + { 1, "abc", true }, + { 1, "abc", false }, + { 2, "a", true }, + { 2, "a", false }, + { 2, "ab", true }, + { 2, "ab", false }, + { 2, "abc", true }, + { 2, "abc", false }, + { 3, "a", true }, + { 3, "a", false }, + { 3, "ab", true }, + { 3, "ab", false }, + { 3, "abc", true }, + { 3, "abc", false }, + { 0, NULL, false, false }, + }; + struct litest_parameters *params = litest_parameters_new("first", 'i', 3, 1, 2, 3, + "second", 's', 3, "a", "ab", "abc", + "third", 'b', 2, true, false, + NULL); + + litest_parameters_permutations(params, permutation_func, permutations); + + ARRAY_FOR_EACH(permutations, p) { + if (p->s == NULL) + break; + ck_assert_msg(p->found, "For %d/%s/%s", p->i, p->s, p->b ? "true" : "false"); + } +} +END_TEST + static Suite * litest_assert_macros_suite(void) { @@ -427,6 +503,7 @@ litest_assert_macros_suite(void) Suite *s; s = suite_create("litest:assert macros"); +#if 0 tc = tcase_create("assert"); tcase_add_test_raise_signal(tc, litest_assert_trigger, SIGABRT); tcase_add_test(tc, litest_assert_notrigger); @@ -498,6 +575,11 @@ litest_assert_macros_suite(void) tcase_add_test_raise_signal(tc, zalloc_too_large, SIGABRT); suite_add_tcase(s, tc); +#endif + tc = tcase_create("parameters "); + tcase_add_test(tc, parameter_permutations); + suite_add_tcase(s, tc); + return s; } diff --git a/test/litest.c b/test/litest.c index 963f68b5..ecda7386 100644 --- a/test/litest.c +++ b/test/litest.c @@ -88,6 +88,14 @@ static const char *filter_test = NULL; static const char *filter_device = NULL; static const char *filter_group = NULL; static int filter_rangeval = INT_MIN; + +struct param_filter { + char name[64]; + char glob[64]; +}; +struct param_filter filter_params[8]; /* name=NULL terminated */ + + static struct quirks_context *quirks_context; struct created_file { @@ -266,6 +274,8 @@ struct test { struct range range; int rangeval; bool deviceless; + + struct litest_test_parameters *params; }; struct suite { @@ -274,6 +284,316 @@ struct suite { char *name; }; +struct litest_parameter_value { + size_t refcnt; + struct list link; /* litest_parameter->values */ + + struct multivalue value; +}; + +struct litest_parameter { + size_t refcnt; + struct list link; /* litest_parameters.params */ + char name[128]; + char type; /* One of u, i, d, c, s, b */ + + struct list values; /* litest_parameter_value */ +}; + +struct litest_parameters { + size_t refcnt; + struct list params; /* struct litest_parameter */ +}; + +static struct litest_parameter_value * +litest_parameter_value_new(void) +{ + struct litest_parameter_value *pv = zalloc(sizeof *pv); + + list_init(&pv->link); + pv->refcnt = 1; + + return pv; +} + +static inline void +litest_parameter_add_string(struct litest_parameter *p, const char *s) +{ + assert(p->type == 's'); + + struct litest_parameter_value *pv = litest_parameter_value_new(); + pv->value = multivalue_new_string(s); + list_append(&p->values, &pv->link); +} + +static inline void +litest_parameter_add_char(struct litest_parameter *p, char c) +{ + assert(p->type == 'c'); + + struct litest_parameter_value *pv = litest_parameter_value_new(); + pv->value = multivalue_new_char(c); + list_append(&p->values, &pv->link); +} + +static inline void +litest_parameter_add_bool(struct litest_parameter *p, bool b) +{ + assert(p->type == 'b'); + + struct litest_parameter_value *pv = litest_parameter_value_new(); + pv->value = multivalue_new_bool(b); + list_append(&p->values, &pv->link); +} + + +static inline void +litest_parameter_add_u32(struct litest_parameter *p, uint32_t u) +{ + assert(p->type == 'u'); + + struct litest_parameter_value *pv = litest_parameter_value_new(); + pv->value = multivalue_new_u32(u); + list_append(&p->values, &pv->link); +} + +static inline void +litest_parameter_add_i32(struct litest_parameter *p, int32_t i) +{ + assert(p->type == 'i'); + + struct litest_parameter_value *pv = litest_parameter_value_new(); + pv->value = multivalue_new_i32(i); + list_append(&p->values, &pv->link); +} + + +static void +litest_parameter_add_double(struct litest_parameter *p, double d) +{ + assert(p->type == 'd'); + + struct litest_parameter_value *pv = litest_parameter_value_new(); + pv->value = multivalue_new_double(d); + list_append(&p->values, &pv->link); +} + +#if 0 +static struct litest_parameter_value * +litest_parameter_value_ref(struct litest_parameter_value *pv) { + assert(pv); + assert(pv->refcnt > 0); + pv->refcnt++; + return pv; +} +#endif + +static struct litest_parameter_value * +litest_parameter_value_unref(struct litest_parameter_value *pv) { + if (pv) { + assert(pv->refcnt > 0); + if (--pv->refcnt == 0) { + list_remove(&pv->link); + free(pv); + } + } + return NULL; +} + +static struct litest_parameter* +litest_parameter_new(const char *name, char type) +{ + struct litest_parameter *p = zalloc(sizeof *p); + + switch (type) { + case 'b': + case 'c': + case 'd': + case 'i': + case 's': + case 'u': + break; + default: + assert(!"Type not yet implemented"); + } + + list_init(&p->link); + list_init(&p->values); + snprintf(p->name, sizeof(p->name), "%s", name); + p->type = type; + p->refcnt = 1; + + return p; +} + +static struct litest_parameter * +litest_parameter_ref(struct litest_parameter *p) { + assert(p); + assert(p->refcnt > 0); + p->refcnt++; + return p; +} + +static struct litest_parameter * +litest_parameter_unref(struct litest_parameter *p) { + if (p) { + assert(p->refcnt > 0); + if (--p->refcnt == 0) { + struct litest_parameter_value *pv; + list_for_each_safe(pv, &p->values, link) { + litest_parameter_value_unref(pv); + } + list_remove(&p->link); + free(p); + } + } + return NULL; +} + +static void +litest_parameters_add(struct litest_parameters *ps, struct litest_parameter *param) +{ + struct litest_parameter *p; + list_for_each(p, &ps->params, link) { + assert(!streq(p->name, param->name)); + } + + litest_parameter_ref(param); + list_append(&ps->params, ¶m->link); +} + +struct litest_parameters * +_litest_parameters_new(const char *name, ...) { + struct litest_parameters *ps = zalloc(sizeof *ps); + + list_init(&ps->params); + ps->refcnt = 1; + + va_list args; + va_start(args, name); + + while (name) { + char type = va_arg(args, int); + unsigned int nargs = va_arg(args, unsigned int); + + struct litest_parameter *param = litest_parameter_new(name, type); + for (unsigned int _ = 0; _ < nargs; _++) { + switch (type) { + case 'b': { + bool b = va_arg(args, int); + litest_parameter_add_bool(param, b); + break; + } + case 'c': { + char b = va_arg(args, int); + litest_parameter_add_char(param, b); + break; + } + case 'u': { + uint32_t b = va_arg(args, uint32_t); + litest_parameter_add_u32(param, b); + break; + } + case 'i': { + int32_t b = va_arg(args, int32_t); + litest_parameter_add_i32(param, b); + break; + } + case 'd': { + double b = va_arg(args, double); + litest_parameter_add_double(param, b); + break; + } + case 's': { + const char *s = va_arg(args, const char *); + litest_parameter_add_string(param, s); + break; + } + default: + abort(); + break; + } + } + + litest_parameters_add(ps, param); + litest_parameter_unref(param); + name = va_arg(args, const char *); + } + + va_end(args); + + return ps; +} + +struct litest_parameters * +litest_parameters_ref(struct litest_parameters *p) { + assert(p); + assert(p->refcnt > 0); + p->refcnt++; + return p; +} + +struct litest_parameters * +litest_parameters_unref(struct litest_parameters *params) { + if (params) { + assert(params->refcnt > 0); + if (--params->refcnt == 0) { + struct litest_parameter *p; + list_for_each_safe(p, ¶ms->params, link) { + litest_parameter_unref(p); + } + free(params); + } + } + return NULL; +} + +static inline int +_permutate(struct litest_parameters_permutation *permutation, + struct list *next_param, + void *list_head, + litest_parameters_permutation_func_t func, + void *userdata) +{ + if (next_param->next == list_head) { + func(permutation, userdata); + return 0; + } + struct litest_parameter_value *pv; + struct litest_parameter *param = list_first_entry(next_param, param, link); + list_for_each(pv, ¶m->values, link) { + struct litest_parameters_permutation_value v = { + .value = pv->value, + }; + + memcpy(v.name, param->name, min(sizeof(v.name), sizeof(param->name))); + + list_append(&permutation->values, &v.link); + int rc = _permutate(permutation, ¶m->link, list_head, func, userdata); + if (rc) + return rc; + list_remove(&v.link); + } + + return 0; +} + +/** + * Calls the given function func with each permutation of + * the given test parameters. + */ +int +litest_parameters_permutations(struct litest_parameters *params, + litest_parameters_permutation_func_t func, + void *userdata) +{ + + struct litest_parameters_permutation permutation; + list_init(&permutation.values); + + return _permutate(&permutation, ¶ms->params, ¶ms->params, func, userdata); +} + static struct litest_device *current_device; struct litest_device *litest_current_device(void) @@ -432,6 +752,94 @@ litest_add_tcase_for_device(struct suite *suite, } while (++rangeval < range->upper); } +struct permutation_userdata +{ + struct suite *suite; + const char *funcname; + const void *func; + const struct litest_test_device *dev; + char devname[64]; /* set if dev == NULL */ + + const struct param_filter *param_filters; /* name=NULL terminated */ +}; + +static int +permutation_func(struct litest_parameters_permutation *permutation, void *userdata) +{ + struct permutation_userdata *data = userdata; + + struct litest_test_parameters *params = litest_test_parameters_new(); + struct litest_parameters_permutation_value *pmv; + bool all_filtered = true; + list_for_each(pmv, &permutation->values, link) { + const struct param_filter *f = data->param_filters; + bool filtered = false; + while (!filtered && strlen(f->name)) { + if (streq(pmv->name, f->name)) { + char *s = multivalue_as_str(&pmv->value); + if (fnmatch(f->glob, s, 0) != 0) + filtered = true; + free(s); + } + f++; + } + if (!filtered) { + struct litest_test_param *tp = zalloc(sizeof *tp); + snprintf(tp->name, sizeof(tp->name), "%s", pmv->name); + tp->value = multivalue_copy(&pmv->value); + list_append(¶ms->test_params, &tp->link); + + all_filtered = false; + } + } + + if (all_filtered) + return 0; + + struct test *t; + + t = zalloc(sizeof(*t)); + t->name = safe_strdup(data->funcname); + t->func = data->func; + if (data->dev) { + t->devname = safe_strdup(data->dev->shortname); + t->setup = data->dev->setup; + t->teardown = data->dev->teardown ? + data->dev->teardown : litest_generic_device_teardown; + } else { + t->devname = safe_strdup(data->devname); + t->setup = NULL; + t->teardown = NULL; + } + t->rangeval = 0; + t->params = params; + + list_append(&data->suite->tests, &t->node); + + return 0; +} + +static void +litest_add_tcase_for_device_with_params(struct suite *suite, + const char *funcname, + const void *func, + const struct litest_test_device *dev, + struct litest_parameters *params) +{ + if (run_deviceless) + return; + + struct permutation_userdata data = { + .suite = suite, + .funcname = funcname, + .func = func, + .dev = dev, + .param_filters = filter_params, + }; + + litest_parameters_permutations(params, permutation_func, &data); +} + static void litest_add_tcase_no_device(struct suite *suite, const void *func, @@ -471,6 +879,32 @@ litest_add_tcase_no_device(struct suite *suite, } while (++rangeval < range->upper); } +static void +litest_add_tcase_no_device_with_params(struct suite *suite, + const void *func, + const char *funcname, + struct litest_parameters *params) +{ + const char *test_name = funcname; + + if (filter_device && + fnmatch(filter_device, test_name, 0) != 0) + return; + + if (run_deviceless) + return; + + struct permutation_userdata data = { + .suite = suite, + .funcname = funcname, + .func = func, + .param_filters = filter_params, + }; + snprintf(data.devname, sizeof(data.devname), "no device"); + + litest_parameters_permutations(params, permutation_func, &data); +} + static void litest_add_tcase_deviceless(struct suite *suite, const void *func, @@ -508,13 +942,37 @@ litest_add_tcase_deviceless(struct suite *suite, } while (++rangeval < range->upper); } +static void +litest_add_tcase_deviceless_with_params(struct suite *suite, + const void *func, + const char *funcname, + struct litest_parameters *params) +{ + const char *test_name = funcname; + + if (filter_device && + fnmatch(filter_device, test_name, 0) != 0) + return; + + struct permutation_userdata data = { + .suite = suite, + .funcname = funcname, + .func = func, + .param_filters = filter_params, + }; + snprintf(data.devname, sizeof(data.devname), "deviceless"); + + litest_parameters_permutations(params, permutation_func, &data); +} + static void litest_add_tcase(const char *filename, const char *funcname, const void *func, int64_t required, int64_t excluded, - const struct range *range) + const struct range *range, + struct litest_parameters *params) { bool added = false; @@ -532,11 +990,17 @@ litest_add_tcase(const char *filename, if (required == LITEST_DEVICELESS && excluded == LITEST_DEVICELESS) { - litest_add_tcase_deviceless(suite, func, funcname, range); + if (params) + litest_add_tcase_deviceless_with_params(suite, func, funcname, params); + else + litest_add_tcase_deviceless(suite, func, funcname, range); added = true; } else if (required == LITEST_DISABLE_DEVICE && excluded == LITEST_DISABLE_DEVICE) { - litest_add_tcase_no_device(suite, func, funcname, range); + if (params) + litest_add_tcase_no_device_with_params(suite, func, funcname, params); + else + litest_add_tcase_no_device(suite, func, funcname, range); added = true; } else if (required != LITEST_ANY || excluded != LITEST_ANY) { struct litest_test_device *dev; @@ -552,11 +1016,19 @@ litest_add_tcase(const char *filename, (dev->features & excluded) != 0) continue; - litest_add_tcase_for_device(suite, - funcname, - func, - dev, - range); + if (params) { + litest_add_tcase_for_device_with_params(suite, + funcname, + func, + dev, + params); + } else { + litest_add_tcase_for_device(suite, + funcname, + func, + dev, + range); + } added = true; } } else { @@ -570,11 +1042,19 @@ litest_add_tcase(const char *filename, fnmatch(filter_device, dev->shortname, 0) != 0) continue; - litest_add_tcase_for_device(suite, - funcname, - func, - dev, - range); + if (params) { + litest_add_tcase_for_device_with_params(suite, + funcname, + func, + dev, + params); + } else { + litest_add_tcase_for_device(suite, + funcname, + func, + dev, + range); + } added = true; } } @@ -594,6 +1074,18 @@ _litest_add_no_device(const char *name, const char *funcname, const void *func) _litest_add(name, funcname, func, LITEST_DISABLE_DEVICE, LITEST_DISABLE_DEVICE); } +void +_litest_add_parametrized_no_device(const char *name, + const char *funcname, + const void *func, + struct litest_parameters *params) +{ + _litest_add_parametrized(name, funcname, func, + LITEST_DISABLE_DEVICE, + LITEST_DISABLE_DEVICE, + params); +} + void _litest_add_ranged_no_device(const char *name, const char *funcname, @@ -621,6 +1113,18 @@ _litest_add_deviceless(const char *name, NULL); } +void +_litest_add_parametrized_deviceless(const char *name, + const char *funcname, + const void *func, + struct litest_parameters *params) +{ + _litest_add_parametrized(name, funcname, func, + LITEST_DISABLE_DEVICE, + LITEST_DISABLE_DEVICE, + params); +} + void _litest_add(const char *name, const char *funcname, @@ -644,7 +1148,19 @@ _litest_add_ranged(const char *name, int64_t excluded, const struct range *range) { - litest_add_tcase(name, funcname, func, required, excluded, range); + litest_add_tcase(name, funcname, func, required, excluded, range, NULL); +} + + +void +_litest_add_parametrized(const char *name, + const char *funcname, + const void *func, + int64_t required, + int64_t excluded, + struct litest_parameters *params) +{ + litest_add_tcase(name, funcname, func, required, excluded, NULL, params); } void @@ -699,6 +1215,50 @@ _litest_add_ranged_for_device(const char *filename, litest_abort_msg("Invalid test device type"); } +void +_litest_add_parametrized_for_device(const char *filename, + const char *funcname, + const void *func, + enum litest_device_type type, + struct litest_parameters *params) +{ + struct litest_test_device *dev; + bool device_filtered = false; + + litest_assert(type < LITEST_NO_DEVICE); + + if (filter_test && + fnmatch(filter_test, funcname, 0) != 0) + return; + + struct suite *s = current_suite; + + if (filter_group && fnmatch(filter_group, s->name, 0) != 0) + return; + + list_for_each(dev, &devices, node) { + if (filter_device && + fnmatch(filter_device, dev->shortname, 0) != 0) { + device_filtered = true; + continue; + } + + if (dev->type == type) { + litest_add_tcase_for_device_with_params(s, + funcname, + func, + dev, + params); + return; + } + } + + /* only abort if no filter was set, that's a bug */ + if (!device_filtered) + litest_abort_msg("Invalid test device type"); +} + + LIBINPUT_ATTRIBUTE_PRINTF(3, 0) static void litest_log_handler(struct libinput *libinput, @@ -954,7 +1514,7 @@ litest_run_suite(struct list *suites, int njobs) list_for_each(s, suites, node) { struct test *t; list_for_each(t, &s->tests, node) { - struct litest_runner_test_description tdesc; + struct litest_runner_test_description tdesc = {0}; if (range_is_valid(&t->range)) { snprintf(tdesc.name, sizeof(tdesc.name), @@ -963,6 +1523,24 @@ litest_run_suite(struct list *suites, int njobs) t->name, t->devname, t->rangeval); + } else if (t->params) { + char buf[256] = {0}; + + struct litest_test_param *tp; + bool is_first = true; + list_for_each(tp, &t->params->test_params, link) { + char *val = multivalue_as_str(&tp->value); + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "%s%s:%s", is_first ? "" : ",", tp->name, val); + free(val); + is_first = false; + } + snprintf(tdesc.name, sizeof(tdesc.name), + "%s:%s:%s:%s", + s->name, + t->name, + t->devname, + buf); } else { snprintf(tdesc.name, sizeof(tdesc.name), "%s:%s:%s", @@ -975,6 +1553,7 @@ litest_run_suite(struct list *suites, int njobs) tdesc.teardown = t->teardown; tdesc.args.range = t->range; tdesc.rangeval = t->rangeval; + tdesc.params = t->params; litest_runner_add_test(runner, &tdesc); ntests++; } @@ -4523,6 +5102,7 @@ litest_parse_argv(int argc, char **argv) OPT_FILTER_GROUP, OPT_FILTER_RANGEVAL, OPT_FILTER_DEVICELESS, + OPT_FILTER_PARAMETER, OPT_OUTPUT_FILE, OPT_JOBS, OPT_LIST, @@ -4534,6 +5114,7 @@ litest_parse_argv(int argc, char **argv) { "filter-group", 1, 0, OPT_FILTER_GROUP }, { "filter-rangeval", 1, 0, OPT_FILTER_RANGEVAL }, { "filter-deviceless", 0, 0, OPT_FILTER_DEVICELESS }, + { "filter-parameter", 1, 0, OPT_FILTER_PARAMETER }, { "output-file", 1, 0, OPT_OUTPUT_FILE }, { "exitfirst", 0, 0, OPT_EXIT_FIRST }, { "jobs", 1, 0, OPT_JOBS }, @@ -4592,6 +5173,9 @@ litest_parse_argv(int argc, char **argv) " Only run tests with the given range value\n" " --filter-deviceless=.... \n" " Glob to filter on tests that do not create test devices\n" + " --filter-parameter=param1:glob,param2:glob,... \n" + " Glob(s) to filter on the given parameters in their string representation.\n" + " Boolean parameters are filtered via 'true' and 'false'.\n" " --verbose\n" " Enable verbose output\n" " --jobs 8\n" @@ -4623,6 +5207,31 @@ litest_parse_argv(int argc, char **argv) case OPT_FILTER_RANGEVAL: filter_rangeval = atoi(optarg); break; + case OPT_FILTER_PARAMETER: { + size_t nelems; + char **params = strv_from_string(optarg, ",", &nelems); + const size_t max_filters = ARRAY_LENGTH(filter_params) - 1; + if (nelems >= max_filters) { + fprintf(stderr, "Only %zd parameter filters are supported\n", max_filters); + exit(1); + } + for (size_t i = 0; i < nelems; i++) { + size_t n; + char **strv = strv_from_string(params[i], ":", &n); + assert(n == 2); + + const char *name = strv[0]; + const char *glob = strv[1]; + + struct param_filter *f = &filter_params[i]; + snprintf(f->name, sizeof(f->name), "%s", name); + snprintf(f->glob, sizeof(f->glob), "%s", glob); + + strv_free(strv); + } + strv_free(params); + break; + } case 'j': case OPT_JOBS: jobs = atoi(optarg); @@ -4776,6 +5385,7 @@ litest_free_test_list(struct list *tests) struct test *t; list_for_each_safe(t, &s->tests, node) { + litest_test_parameters_unref(t->params); free(t->name); free(t->devname); list_remove(&t->node); diff --git a/test/litest.h b/test/litest.h index 436698db..0802298b 100644 --- a/test/litest.h +++ b/test/litest.h @@ -41,8 +41,8 @@ #include "litest-runner.h" #define START_TEST(func_) \ - static enum litest_runner_result func_(const struct litest_runner_test_env *test_env_) { \ - int _i __attribute__((unused)) = test_env_->rangeval; + static enum litest_runner_result func_(const struct litest_runner_test_env *test_env) { \ + int _i __attribute__((unused)) = test_env->rangeval; #define END_TEST \ return LITEST_PASS; \ @@ -589,20 +589,87 @@ void litest_disable_log_handler(struct libinput *libinput); void litest_restore_log_handler(struct libinput *libinput); void litest_set_log_handler_bug(struct libinput *libinput); +struct litest_parameters; + +/** + * Create a new set of parameters for a test case. + * + * This function takes a variable set of arguments in the format + * [name, type, count, arg1, arg2, arg3, ..], for example: + * + * litest_parameter_new("axis", 'u', 2, ABS_X, ABS_Y, + * "direction", 's', 4, "north", "south", "east", "west", + * NULL); + * + * Parsing stops at the first null name argument. + */ +struct litest_parameters * +_litest_parameters_new(const char *name, ...); + +/* Helper to ensure it's always null-terminated */ +#define litest_parameters_new(name_, ...) \ + _litest_parameters_new(name_, __VA_ARGS__, NULL) + +struct litest_parameters_permutation_value { + struct list link; + char name[128]; + const struct multivalue value; +}; + +/** + * Argument for the callback function to litest_parameters_permutations(). + * This is simple wrapper around a linked list that contains all elements + * of the current permutation. + */ +struct litest_parameters_permutation { + struct list values; /* struct litest_parameters_permutation_value */ +}; + +/** + * Callback function invoked for each permutation of a struct litest_parameters. + */ +typedef int (*litest_parameters_permutation_func_t)(struct litest_parameters_permutation *permutation, void *userdata); + +/** + * Permutates the given parameters and calls func for every possible + * permutation. If func returns a nonzero status, permutation stops + * and that nonzero status is returned to the caller. + * + * Userdata is passed through as-is. + */ +int +litest_parameters_permutations(struct litest_parameters *params, + litest_parameters_permutation_func_t func, + void *userdata); + +struct litest_parameters * +litest_parameters_ref(struct litest_parameters *p); + +struct litest_parameters * +litest_parameters_unref(struct litest_parameters *params); + #define litest_add(func_, ...) \ _litest_add(__FILE__, #func_, func_, __VA_ARGS__) #define litest_add_ranged(func_, ...) \ _litest_add_ranged(__FILE__, #func_, func_, __VA_ARGS__) +#define litest_add_parametrized(func_, ...) \ + _litest_add_parametrized(__FILE__, #func_, func_, __VA_ARGS__) #define litest_add_for_device(func_, ...) \ _litest_add_for_device(__FILE__, #func_, func_, __VA_ARGS__) #define litest_add_ranged_for_device(func_, ...) \ _litest_add_ranged_for_device(__FILE__, #func_, func_, __VA_ARGS__) +#define litest_add_parametrized_for_device(func_, ...) \ + _litest_add_parametrized_for_device(__FILE__, #func_, func_, __VA_ARGS__) #define litest_add_no_device(func_) \ _litest_add_no_device(__FILE__, #func_, func_) +#define litest_add_parametrized_no_device(func_, ...) \ + _litest_add_parametrized_no_device(__FILE__, #func_, func_, __VA_ARGS__) #define litest_add_ranged_no_device(func_, ...) \ _litest_add_ranged_no_device(__FILE__, #func_, func_, __VA_ARGS__) #define litest_add_deviceless(func_) \ _litest_add_deviceless(__FILE__, #func_, func_) +#define litest_add_parametrized_deviceless(func_, params) \ + _litest_add_parametrize_deviceless(__FILE__, #func_, func_, params) void _litest_add(const char *name, @@ -618,6 +685,13 @@ _litest_add_ranged(const char *name, int64_t excluded, const struct range *range); void +_litest_add_parametrized(const char *name, + const char *funcname, + const void *func, + int64_t required, + int64_t excluded, + struct litest_parameters *params); +void _litest_add_for_device(const char *name, const char *funcname, const void *func, @@ -629,10 +703,21 @@ _litest_add_ranged_for_device(const char *name, enum litest_device_type type, const struct range *range); void +_litest_add_parametrized_for_device(const char *name, + const char *funcname, + const void *func, + enum litest_device_type type, + struct litest_parameters *params); +void _litest_add_no_device(const char *name, const char *funcname, const void *func); void +_litest_add_parametrized_no_device(const char *name, + const char *funcname, + const void *func, + struct litest_parameters *params); +void _litest_add_ranged_no_device(const char *name, const char *funcname, const void *func, @@ -641,6 +726,11 @@ void _litest_add_deviceless(const char *name, const char *funcname, const void *func); +void +_litest_add_parametrized_deviceless(const char *name, + const char *funcname, + const void *func, + struct litest_parameters *params); struct litest_device * litest_create_device(enum litest_device_type which); diff --git a/test/test-log.c b/test/test-log.c index a7ecca50..914d224c 100644 --- a/test/test-log.c +++ b/test/test-log.c @@ -172,7 +172,11 @@ START_TEST(log_axisrange_warning) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; const struct input_absinfo *abs; - int axis = _i; /* looped test */ + + const char *axisname; + litest_test_param_fetch(test_env->params, "axis", &axisname); + int axis = libevdev_event_code_from_code_name(axisname); + litest_assert_int_ne(axis, -1); litest_touch_down(dev, 0, 90, 100); litest_drain_events(li); @@ -202,14 +206,16 @@ END_TEST TEST_COLLECTION(log) { - struct range axes = { ABS_X, ABS_Y + 1}; - litest_add_deviceless(log_default_priority); litest_add_deviceless(log_handler_invoked); litest_add_deviceless(log_handler_NULL); litest_add_no_device(log_priority); - /* mtdev clips to axis ranges */ - litest_add_ranged(log_axisrange_warning, LITEST_TOUCH, LITEST_PROTOCOL_A, &axes); - litest_add_ranged(log_axisrange_warning, LITEST_TOUCHPAD, LITEST_ANY, &axes); + { + struct litest_parameters *params = litest_parameters_new("axis", 's', 2, "ABS_X", "ABS_Y"); + /* mtdev clips to axis ranges */ + litest_add_parametrized(log_axisrange_warning, LITEST_TOUCH, LITEST_PROTOCOL_A, params); + litest_add_parametrized(log_axisrange_warning, LITEST_TOUCHPAD, LITEST_ANY, params); + litest_parameters_unref(params); + } } diff --git a/test/test-pointer.c b/test/test-pointer.c index 13efedf5..34f18225 100644 --- a/test/test-pointer.c +++ b/test/test-pointer.c @@ -183,26 +183,32 @@ START_TEST(pointer_motion_relative_min_decel) struct libinput_event *event; double evx, evy; int dx, dy; - int cardinal = _i; /* ranged test */ double len; - int deltas[8][2] = { - /* N, NE, E, ... */ - { 0, 1 }, - { 1, 1 }, - { 1, 0 }, - { 1, -1 }, - { 0, -1 }, - { -1, -1 }, - { -1, 0 }, - { -1, 1 }, - }; + const char *direction; + litest_test_param_fetch(test_env->params, "direction", &direction, NULL); + + if (streq(direction, "N")) { + dx = 0; dy = 1; + } else if (streq(direction, "NE")) { + dx = 1; dy = 1; + } else if (streq(direction, "E")) { + dx = 1; dy = 0; + } else if (streq(direction, "SE")) { + dx = 1; dy = -1; + } else if (streq(direction, "S")) { + dx = 0; dy = -1; + } else if (streq(direction, "SW")) { + dx = -1; dy = -1; + } else if (streq(direction, "W")) { + dx = -1; dy = 0; + } else if (streq(direction, "NW")) { + dx = -1, dy = 1; + } else + litest_abort_msg("Invalid direction %s", direction); litest_drain_events(dev->libinput); - dx = deltas[cardinal][0]; - dy = deltas[cardinal][1]; - litest_event(dev, EV_REL, REL_X, dx); litest_event(dev, EV_REL, REL_Y, dy); litest_event(dev, EV_SYN, SYN_REPORT, 0); @@ -268,7 +274,11 @@ START_TEST(pointer_absolute_initial_state) struct libinput *libinput1, *libinput2; struct libinput_event *ev1, *ev2; struct libinput_event_pointer *p1, *p2; - int axis = _i; /* looped test */ + + const char *axisname; + litest_test_param_fetch(test_env->params, "axis", &axisname); + int axis = libevdev_event_code_from_code_name(axisname); + litest_assert_int_ne(axis, -1); libinput1 = dev->libinput; litest_touch_down(dev, 0, 40, 60); @@ -767,23 +777,22 @@ START_TEST(pointer_scroll_wheel_hires_send_only_lores) { struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - enum libinput_pointer_axis axis = _i; /* ranged test */ unsigned int lores_code, hires_code; int direction; - switch (axis) { - case LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL: - lores_code = REL_WHEEL; - hires_code = REL_WHEEL_HI_RES; - direction = -1; - break; - case LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL: - lores_code = REL_HWHEEL; - hires_code = REL_HWHEEL_HI_RES; - direction = 1; - break; - default: - abort(); + const char *axisname; + litest_test_param_fetch(test_env->params, "axis", &axisname, NULL); + + if (streq(axisname, "vertical")) { + lores_code = REL_WHEEL; + hires_code = REL_WHEEL_HI_RES; + direction = -1; + } else if (streq(axisname, "horizontal")) { + lores_code = REL_HWHEEL; + hires_code = REL_HWHEEL_HI_RES; + direction = 1; + } else { + litest_abort_msg("Invalid test axis '%s'", axisname); } if (!libevdev_has_event_code(dev->evdev, EV_REL, lores_code) && @@ -3745,17 +3754,19 @@ END_TEST TEST_COLLECTION(pointer) { - struct range axis_range = {ABS_X, ABS_Y + 1}; - struct range compass = {0, 7}; /* cardinal directions */ struct range buttons = {BTN_LEFT, BTN_TASK + 1}; struct range buttonorder = {0, _MB_BUTTONORDER_COUNT}; - struct range scroll_directions = {LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, - LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL + 1}; struct range rotation_20deg = {0, 18}; /* steps of 20 degrees */ litest_add(pointer_motion_relative, LITEST_RELATIVE, LITEST_POINTINGSTICK); litest_add_for_device(pointer_motion_relative_zero, LITEST_MOUSE); - litest_add_ranged(pointer_motion_relative_min_decel, LITEST_RELATIVE, LITEST_POINTINGSTICK, &compass); + { + struct litest_parameters *params = litest_parameters_new("direction", 's', 8, + "N", "E", "S", "W", + "NE", "SE", "SW", "NW"); + litest_add_parametrized(pointer_motion_relative_min_decel, LITEST_RELATIVE, LITEST_POINTINGSTICK, params); + litest_parameters_unref(params); + } litest_add(pointer_motion_absolute, LITEST_ABSOLUTE, LITEST_ANY); litest_add(pointer_motion_unaccel, LITEST_RELATIVE, LITEST_ANY); litest_add(pointer_button, LITEST_BUTTON, LITEST_CLICKPAD); @@ -3765,7 +3776,11 @@ TEST_COLLECTION(pointer) litest_add(pointer_recover_from_lost_button_count, LITEST_BUTTON, LITEST_CLICKPAD); litest_add(pointer_scroll_wheel, LITEST_WHEEL, LITEST_TABLET); litest_add(pointer_scroll_wheel_hires, LITEST_WHEEL, LITEST_TABLET); - litest_add_ranged(pointer_scroll_wheel_hires_send_only_lores, LITEST_WHEEL, LITEST_TABLET, &scroll_directions); + { + struct litest_parameters *params = litest_parameters_new("axis", 's', 2, "vertical", "horizontal"); + litest_add_parametrized(pointer_scroll_wheel_hires_send_only_lores, LITEST_WHEEL, LITEST_TABLET, params); + litest_parameters_unref(params); + } litest_add(pointer_scroll_wheel_inhibit_small_deltas, LITEST_WHEEL, LITEST_TABLET); litest_add(pointer_scroll_wheel_inhibit_dir_change, LITEST_WHEEL, LITEST_TABLET); litest_add_for_device(pointer_scroll_wheel_lenovo_scrollpoint, LITEST_LENOVO_SCROLLPOINT); @@ -3833,7 +3848,11 @@ TEST_COLLECTION(pointer) litest_add(middlebutton_device_remove_while_down, LITEST_BUTTON, LITEST_CLICKPAD); litest_add(middlebutton_device_remove_while_one_is_down, LITEST_BUTTON, LITEST_CLICKPAD); - litest_add_ranged(pointer_absolute_initial_state, LITEST_ABSOLUTE, LITEST_ANY, &axis_range); + { + struct litest_parameters *params = litest_parameters_new("axis", 's', 2, "ABS_X", "ABS_Y"); + litest_add_parametrized(pointer_absolute_initial_state, LITEST_ABSOLUTE, LITEST_ANY, params); + litest_parameters_unref(params); + } litest_add(pointer_time_usec, LITEST_RELATIVE, LITEST_ANY); diff --git a/test/test-touch.c b/test/test-touch.c index 22d7487e..2ba7078e 100644 --- a/test/test-touch.c +++ b/test/test-touch.c @@ -815,7 +815,11 @@ START_TEST(touch_initial_state) struct libinput_event *ev2 = NULL; struct libinput_event_touch *t1, *t2; struct libinput_device *device1, *device2; - int axis = _i; /* looped test */ + + const char *axisname; + litest_test_param_fetch(test_env->params, "axis", &axisname); + int axis = libevdev_event_code_from_code_name(axisname); + litest_assert_int_ne(axis, -1); dev = litest_current_device(); device1 = dev->libinput_device; @@ -1336,8 +1340,6 @@ END_TEST TEST_COLLECTION(touch) { - struct range axes = { ABS_X, ABS_Y + 1}; - litest_add(touch_frame_events, LITEST_TOUCH, LITEST_ANY); litest_add(touch_downup_no_motion, LITEST_TOUCH, LITEST_ANY); litest_add(touch_downup_no_motion, LITEST_SINGLE_TOUCH, LITEST_TOUCHPAD); @@ -1364,7 +1366,11 @@ TEST_COLLECTION(touch) litest_add(touch_protocol_a_touch, LITEST_PROTOCOL_A, LITEST_ANY); litest_add(touch_protocol_a_2fg_touch, LITEST_PROTOCOL_A, LITEST_ANY); - litest_add_ranged(touch_initial_state, LITEST_TOUCH, LITEST_PROTOCOL_A, &axes); + { + struct litest_parameters *params = litest_parameters_new("axis", 's', 2, "ABS_X", "ABS_Y"); + litest_add_parametrized(touch_initial_state, LITEST_TOUCH, LITEST_PROTOCOL_A, params); + litest_parameters_unref(params); + } litest_add(touch_time_usec, LITEST_TOUCH, LITEST_TOUCHPAD); diff --git a/test/test-utils.c b/test/test-utils.c index e68472ac..ff2f17cf 100644 --- a/test/test-utils.c +++ b/test/test-utils.c @@ -1833,10 +1833,6 @@ START_TEST(multivalue_test) litest_assert_int_eq(v.type, 's'); litest_assert_str_eq(v.value.s, "test"); - char *str = multivalue_as_str(&v); - litest_assert_str_eq(str, "test"); - free(str); - const char *s; multivalue_extract_typed(&v, 's', &s); litest_assert_str_eq(s, "test"); @@ -1851,6 +1847,10 @@ START_TEST(multivalue_test) char *p1 = copy.value.s; char *p2 = v.value.s; litest_assert_ptr_ne(p1, p2); + + char *str = multivalue_as_str(&v); + litest_assert_str_eq(str, "test"); + free(str); } { @@ -1858,10 +1858,6 @@ START_TEST(multivalue_test) litest_assert_int_eq(v.type, 'c'); litest_assert_int_eq(v.value.c, 'x'); - char *str = multivalue_as_str(&v); - litest_assert_str_eq(str, "x"); - free(str); - char c; multivalue_extract_typed(&v, 'c', &c); litest_assert_int_eq(c, 'x'); @@ -1871,6 +1867,10 @@ START_TEST(multivalue_test) struct multivalue copy = multivalue_copy(&v); litest_assert_int_eq(copy.type, v.type); litest_assert_int_eq(copy.value.c, v.value.c); + + char *str = multivalue_as_str(&v); + litest_assert_str_eq(str, "x"); + free(str); } { @@ -1878,10 +1878,6 @@ START_TEST(multivalue_test) litest_assert_int_eq(v.type, 'u'); litest_assert_int_eq(v.value.u, 0x1234u); - char *str = multivalue_as_str(&v); - litest_assert_str_eq(str, "4660"); - free(str); - uint32_t c; multivalue_extract_typed(&v, 'u', &c); litest_assert_int_eq(c, 0x1234u); @@ -1891,6 +1887,10 @@ START_TEST(multivalue_test) struct multivalue copy = multivalue_copy(&v); litest_assert_int_eq(copy.type, v.type); litest_assert_int_eq(copy.value.u, v.value.u); + + char *str = multivalue_as_str(&v); + litest_assert_str_eq(str, "4660"); + free(str); } { @@ -1898,10 +1898,6 @@ START_TEST(multivalue_test) litest_assert_int_eq(v.type, 'i'); litest_assert_int_eq(v.value.i, -123); - char *str = multivalue_as_str(&v); - litest_assert_str_eq(str, "-123"); - free(str); - int32_t c; multivalue_extract_typed(&v, 'i', &c); litest_assert_int_eq(c, -123); @@ -1911,6 +1907,10 @@ START_TEST(multivalue_test) struct multivalue copy = multivalue_copy(&v); litest_assert_int_eq(copy.type, v.type); litest_assert_int_eq(copy.value.i, v.value.i); + + char *str = multivalue_as_str(&v); + litest_assert_str_eq(str, "-123"); + free(str); } { @@ -1918,10 +1918,6 @@ START_TEST(multivalue_test) litest_assert_int_eq(v.type, 'b'); litest_assert_int_eq(v.value.b, true); - char *str = multivalue_as_str(&v); - litest_assert_str_eq(str, "true"); - free(str); - bool c; multivalue_extract_typed(&v, 'b', &c); litest_assert_int_eq(c, true); @@ -1931,6 +1927,10 @@ START_TEST(multivalue_test) struct multivalue copy = multivalue_copy(&v); litest_assert_int_eq(copy.type, v.type); litest_assert_int_eq(copy.value.b, v.value.b); + + char *str = multivalue_as_str(&v); + litest_assert_str_eq(str, "true"); + free(str); } { @@ -1938,10 +1938,6 @@ START_TEST(multivalue_test) litest_assert_int_eq(v.type, 'd'); litest_assert_double_eq(v.value.d, 0.1234); - char *str = multivalue_as_str(&v); - litest_assert_str_eq(str, "0.123400"); - free(str); - double c; multivalue_extract_typed(&v, 'd', &c); litest_assert_double_eq(c, 0.1234); @@ -1951,6 +1947,10 @@ START_TEST(multivalue_test) struct multivalue copy = multivalue_copy(&v); litest_assert_int_eq(copy.type, v.type); litest_assert_double_eq(copy.value.d, v.value.d); + + char *str = multivalue_as_str(&v); + litest_assert_str_eq(str, "0.123400"); + free(str); } }