From 1e445f3f848d282a9fe63cc03b830bc094ed8a43 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Sun, 22 Dec 2024 00:06:19 +1000 Subject: [PATCH] test: implement support for parametrizing tests litest supports ranged tests but they are not enough, doubly so with tests where we want to parametrize across multiple options. This patch adds support for just that, in clunky C style. The typical invocation for a test is by giving the test parameter a name, a number of values and then the values themselves: struct litest_parameters *params = litest_parameters_new("axis", 's', 2, "ABS_X", "ABS_Y", "enabled", 'b', '2', true, false, "number", 'u', '2', 10, 11, NULL); litest_add_parametrized(sometest, LITEST_ANY, LITEST_ANY, params); litest_parameters_unref(params); Currently supported are u (uint32), i (int32), d (double), b (bool), c (char) and s (string). In the test itself, the `test_env->params` variable is available and retrieval of the parameters works like this: const char *axis; uint32_t number; bool enabled; litest_test_param_fetch(test_env->params, "axis", &axis, "enabled", &enabled, "number", &number, NULL); Note that since this is an effectively internal test-suite only functionality we don't do type-checking here, it's assumed that if you write the code to pass parameters into a test you remember the type of said params when you write the test code. Because we don't have hashmaps or anything useful other than lists the implementation is a bit clunky: we copy the parameter into the test during litest_add_*, permutate it for our test list which gives us yet another linked list C struct, and finally copy the actual value into the test and test environment as it's executed. Not pretty, but it works. A few tests are switched as simple demonstration. The name of the test has the parameters with their names and values appended now, e.g.: "pointer:pointer_scroll_wheel_hires_send_only_lores:ms-surface-cover:axis:ABS_X" "pointer:pointer_motion_relative_min_decel:mouse-roccat:direction:NW" Filtering by parameters can be done via globs of their string representation: libinput-test-suite --filter-params="axis:ABS_*,enabled:true,number:10*" Part-of: --- src/util-multivalue.h | 7 + test/litest-runner.c | 65 +++++ test/litest-runner.h | 32 ++- test/litest-selftest.c | 82 ++++++ test/litest.c | 640 ++++++++++++++++++++++++++++++++++++++++- test/litest.h | 94 +++++- test/test-log.c | 18 +- test/test-pointer.c | 93 +++--- test/test-touch.c | 14 +- test/test-utils.c | 48 ++-- 10 files changed, 1004 insertions(+), 89 deletions(-) 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); } }