From e1c3bdc6f2658e5c0f3bf128000f3dc1dc7f88bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 27 Nov 2021 17:23:33 +0100 Subject: [PATCH 1/6] =?UTF-8?q?test:=20initial=20external=20unit=20test=20?= =?UTF-8?q?suite=20for=20=E2=80=98config.c=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds a separate (external) test binary with unit tests for config.c This is achieved by having the test’s source file include config.c (yes, the C file, not the header file). This allows us to call e.g. parse_section_*() directly, without having to go through config_load() (which requires having a readable file at “path”). --- meson.build | 1 + tests/meson.build | 8 ++++ tests/test-config.c | 93 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 tests/meson.build create mode 100644 tests/test-config.c diff --git a/meson.build b/meson.build index 9faaf44b..39237a33 100644 --- a/meson.build +++ b/meson.build @@ -272,6 +272,7 @@ endif subdir('completions') subdir('icons') +subdir('tests') summary( { diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 00000000..8618bf04 --- /dev/null +++ b/tests/meson.build @@ -0,0 +1,8 @@ +config_test = executable( + 'test-config', + 'test-config.c', '../tokenize.c', + wl_proto_headers, + link_with: [common], + dependencies: [pixman, xkb, fcft]) + +test('config', config_test) diff --git a/tests/test-config.c b/tests/test-config.c new file mode 100644 index 00000000..d8ef32f1 --- /dev/null +++ b/tests/test-config.c @@ -0,0 +1,93 @@ +#if !defined(_DEBUG) + #define _DEBUG +#endif +#undef NDEBUG + +#include "../log.h" + +#include "../config.c" + +#define ALEN(v) (sizeof(v) / sizeof((v)[0])) + +/* + * Stubs + */ + +void +user_notification_add_fmt(user_notifications_t *notifications, + enum user_notification_kind kind, + const char *fmt, ...) +{ +} + +static void +test_boolean(struct context *ctx, bool (*parse_fun)(struct context *ctx), + const char *key, bool *conf_ptr) +{ + ctx->key = key; + + static const struct { + const char *option_string; + bool value; + bool invalid; + } input[] = { + {"1", true}, {"0", false}, + {"on", true}, {"off", false}, + {"true", true}, {"false", false}, + {"unittest-invalid-boolean-value", false, true}, + }; + + for (size_t i = 0; i < ALEN(input); i++) { + ctx->value = input[i].option_string; + + if (input[i].invalid) { + if (parse_fun(ctx)) { + BUG("[%s].%s=%s: did not fail to parse as expected", + ctx->section, ctx->key, ctx->value); + } + } else { + if (!parse_fun(ctx)) { + BUG("[%s].%s=%s: failed to parse", + ctx->section, ctx->key, ctx->value); + } + if (*conf_ptr != input[i].value) + BUG("[%s].%s=%s: set value (%s) not the expected one (%s)", + ctx->section, ctx->key, ctx->value, + *conf_ptr ? "true" : "false", + input[i].value ? "true" : "false"); + } + } +} + +static void +test_section_main(void) +{ +#define CTX(_key, _value) \ + (struct context){ \ + .conf = &conf, .section = "main", .key = _key, .value = _value, .path = "unittest"} + + struct config conf = {0}; + struct context ctx; + + ctx = CTX("shell", "/bin/bash"); + xassert(parse_section_main(&ctx)); + xassert(strcmp(conf.shell, "/bin/bash") == 0); + + ctx = CTX("term", "foot-unittest"); + xassert(parse_section_main(&ctx)); + xassert(strcmp(conf.term, "foot-unittest") == 0); + + test_boolean(&ctx, &parse_section_main, "login-shell", &conf.login_shell); + + config_free(conf); +#undef CTX +} + +int +main(int argc, const char *const *argv) +{ + log_init(LOG_COLORIZE_AUTO, false, 0, LOG_CLASS_ERROR); + test_section_main(); + log_deinit(); + return 0; +} From 7a9f929368a087227e2291acba8cbe369ee04e84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 27 Nov 2021 20:40:45 +0100 Subject: [PATCH 2/6] test: config: cover all basic (primitive) options in [main] --- tests/test-config.c | 207 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 193 insertions(+), 14 deletions(-) diff --git a/tests/test-config.c b/tests/test-config.c index d8ef32f1..d8c38aa7 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -20,9 +20,94 @@ user_notification_add_fmt(user_notifications_t *notifications, { } +static void +test_invalid_key(struct context *ctx, bool (*parse_fun)(struct context *ctx), + const char *key) +{ + ctx->key = key; + ctx->value = "value for invalid key"; + + if (parse_fun(ctx)) { + BUG("[%s].%s: did not fail to parse as expected" + "(key should be invalid)", ctx->section, ctx->key); + } +} + +static void +test_string(struct context *ctx, bool (*parse_fun)(struct context *ctx), + const char *key, char *const *conf_ptr) +{ + ctx->key = key; + + static const struct { + const char *option_string; + const char *value; + bool invalid; + } input[] = { + {"a string", "a string"}, + }; + + for (size_t i = 0; i < ALEN(input); i++) { + ctx->value = input[i].option_string; + + if (input[i].invalid) { + if (parse_fun(ctx)) { + BUG("[%s].%s=%s: did not fail to parse as expected", + ctx->section, ctx->key, ctx->value); + } + } else { + if (!parse_fun(ctx)) { + BUG("[%s].%s=%s: failed to parse", + ctx->section, ctx->key, ctx->value); + } + if (strcmp(*conf_ptr, input[i].value) != 0) { + BUG("[%s].%s=%s: set value (%s) not the expected one (%s)", + ctx->section, ctx->key, ctx->value, + *conf_ptr, input[i].value); + } + } + } +} + +static void +test_wstring(struct context *ctx, bool (*parse_fun)(struct context *ctx), + const char *key, wchar_t *const *conf_ptr) +{ + ctx->key = key; + + static const struct { + const char *option_string; + const wchar_t *value; + bool invalid; + } input[] = { + {"a string", L"a string"}, + }; + + for (size_t i = 0; i < ALEN(input); i++) { + ctx->value = input[i].option_string; + + if (input[i].invalid) { + if (parse_fun(ctx)) { + BUG("[%s].%s=%s: did not fail to parse as expected", + ctx->section, ctx->key, ctx->value); + } + } else { + if (!parse_fun(ctx)) { + BUG("[%s].%s=%s: failed to parse", + ctx->section, ctx->key, ctx->value); + } + if (wcscmp(*conf_ptr, input[i].value) != 0) { + BUG("[%s].%s=%s: set value (%ls) not the expected one (%ls)", + ctx->section, ctx->key, ctx->value, + *conf_ptr, input[i].value); + } + } + } +} + static void test_boolean(struct context *ctx, bool (*parse_fun)(struct context *ctx), - const char *key, bool *conf_ptr) + const char *key, const bool *conf_ptr) { ctx->key = key; @@ -50,11 +135,88 @@ test_boolean(struct context *ctx, bool (*parse_fun)(struct context *ctx), BUG("[%s].%s=%s: failed to parse", ctx->section, ctx->key, ctx->value); } - if (*conf_ptr != input[i].value) + if (*conf_ptr != input[i].value) { BUG("[%s].%s=%s: set value (%s) not the expected one (%s)", ctx->section, ctx->key, ctx->value, *conf_ptr ? "true" : "false", input[i].value ? "true" : "false"); + } + } + } +} + +static void +test_uint16(struct context *ctx, bool (*parse_fun)(struct context *ctx), + const char *key, const uint16_t *conf_ptr) +{ + ctx->key = key; + + static const struct { + const char *option_string; + uint16_t value; + bool invalid; + } input[] = { + {"0", 0}, {"65535", 65535}, {"65536", 0, true}, + {"abc", 0, true}, {"true", 0, true}, + }; + + for (size_t i = 0; i < ALEN(input); i++) { + ctx->value = input[i].option_string; + + if (input[i].invalid) { + if (parse_fun(ctx)) { + BUG("[%s].%s=%s: did not fail to parse as expected", + ctx->section, ctx->key, ctx->value); + } + } else { + if (!parse_fun(ctx)) { + BUG("[%s].%s=%s: failed to parse", + ctx->section, ctx->key, ctx->value); + } + if (*conf_ptr != input[i].value) { + BUG("[%s].%s=%s: set value (%hu) not the expected one (%hu)", + ctx->section, ctx->key, ctx->value, + *conf_ptr, input[i].value); + } + } + } +} + +static void +test_pt_or_px(struct context *ctx, bool (*parse_fun)(struct context *ctx), + const char *key, const struct pt_or_px *conf_ptr) +{ + ctx->key = key; + + static const struct { + const char *option_string; + struct pt_or_px value; + bool invalid; + } input[] = { + {"12", {.pt = 12}}, {"12px", {.px = 12}}, + {"unittest-invalid-pt-or-px-value", {0}, true}, + }; + + for (size_t i = 0; i < ALEN(input); i++) { + ctx->value = input[i].option_string; + + if (input[i].invalid) { + if (parse_fun(ctx)) { + BUG("[%s].%s=%s: did not fail to parse as expected", + ctx->section, ctx->key, ctx->value); + } + } else { + if (!parse_fun(ctx)) { + BUG("[%s].%s=%s: failed to parse", + ctx->section, ctx->key, ctx->value); + } + if (memcmp(conf_ptr, &input[i].value, sizeof(*conf_ptr)) != 0) { + BUG("[%s].%s=%s: " + "set value (pt=%f, px=%d) not the expected one (pt=%f, px=%d)", + ctx->section, ctx->key, ctx->value, + conf_ptr->pt, conf_ptr->px, + input[i].value.pt, input[i].value.px); + } } } } @@ -62,25 +224,42 @@ test_boolean(struct context *ctx, bool (*parse_fun)(struct context *ctx), static void test_section_main(void) { -#define CTX(_key, _value) \ - (struct context){ \ - .conf = &conf, .section = "main", .key = _key, .value = _value, .path = "unittest"} - struct config conf = {0}; - struct context ctx; + struct context ctx = {.conf = &conf, .section = "main", .path = "unittest"}; - ctx = CTX("shell", "/bin/bash"); - xassert(parse_section_main(&ctx)); - xassert(strcmp(conf.shell, "/bin/bash") == 0); + test_invalid_key(&ctx, &parse_section_main, "invalid-key"); - ctx = CTX("term", "foot-unittest"); - xassert(parse_section_main(&ctx)); - xassert(strcmp(conf.term, "foot-unittest") == 0); + test_string(&ctx, &parse_section_main, "shell", &conf.shell); + test_string(&ctx, &parse_section_main, "term", &conf.term); + test_string(&ctx, &parse_section_main, "app-id", &conf.app_id); + + test_wstring(&ctx, &parse_section_main, "word-delimiters", &conf.word_delimiters); test_boolean(&ctx, &parse_section_main, "login-shell", &conf.login_shell); + test_boolean(&ctx, &parse_section_main, "box-drawings-uses-font-glyphs", &conf.box_drawings_uses_font_glyphs); + test_boolean(&ctx, &parse_section_main, "locked-title", &conf.locked_title); + test_boolean(&ctx, &parse_section_main, "notify-focus-inhibit", &conf.notify_focus_inhibit); + + test_pt_or_px(&ctx, &parse_section_main, "line-height", &conf.line_height); + test_pt_or_px(&ctx, &parse_section_main, "letter-spacing", &conf.letter_spacing); + test_pt_or_px(&ctx, &parse_section_main, "horizontal-letter-offset", &conf.horizontal_letter_offset); + test_pt_or_px(&ctx, &parse_section_main, "vertical-letter-offset", &conf.vertical_letter_offset); + + test_uint16(&ctx, &parse_section_main, "resize-delay-ms", &conf.resize_delay_ms); + test_uint16(&ctx, &parse_section_main, "workers", &conf.render_worker_count); + + /* TODO: font (custom) */ + /* TODO: include (custom) */ + /* TODO: dpi-aware (enum/boolean) */ + /* TODO: bold-text-in-bright (enum/boolean) */ + /* TODO: pad (geometry + optional string)*/ + /* TODO: initial-window-size-pixels (geometry) */ + /* TODO: initial-window-size-chars (geometry) */ + /* TODO: notify (spawn template)*/ + /* TODO: selection-target (enum) */ + /* TODO: initial-window-mode (enum) */ config_free(conf); -#undef CTX } int From 4a00d0d0501818e52932147d2aad5aa7beda2201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 27 Nov 2021 20:51:27 +0100 Subject: [PATCH 3/6] ci (gitlab+builds.sr.ht): execute foot{,client} --version This runs our builtin unit tests (debug builds only), and verifies it runs, at all. (and also logs the version in the build logs...) --- .builds/freebsd-x64.yml | 5 +++++ .gitlab-ci.yml | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/.builds/freebsd-x64.yml b/.builds/freebsd-x64.yml index f2c5c3ef..f52ae0c0 100644 --- a/.builds/freebsd-x64.yml +++ b/.builds/freebsd-x64.yml @@ -32,8 +32,13 @@ tasks: meson --buildtype=debug -Dterminfo=disabled -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/debug ninja -C bld/debug -k0 meson test -C bld/debug --print-errorlogs + bld/debug/foot --version + bld/debug/footclient --version + - release: | mkdir -p bld/release meson --buildtype=minsize -Dterminfo=disabled -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release ninja -C bld/release -k0 meson test -C bld/release --print-errorlogs + bld/release/foot --version + bld/release/footclient --version diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 296f6f78..01baedfb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -36,6 +36,8 @@ debug-x64-no-grapheme-clustering: - meson --buildtype=debug -Dgrapheme-clustering=disabled -Dfcft:grapheme-shaping=disabled -Dfcft:run-shaping=disabled -Dfcft:test-text-shaping=false ../../ - ninja -v -k0 - ninja -v test + - ./foot --version + - ./footclient --version artifacts: reports: junit: bld/debug/meson-logs/testlog.junit.xml @@ -49,6 +51,8 @@ release-x64: - meson --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../../ - ninja -v -k0 - ninja -v test + - ./foot --version + - ./footclient --version artifacts: reports: junit: bld/release/meson-logs/testlog.junit.xml @@ -62,6 +66,8 @@ debug-x86: - meson --buildtype=debug -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../../ - ninja -v -k0 - ninja -v test + - ./foot --version + - ./footclient --version artifacts: reports: junit: bld/debug/meson-logs/testlog.junit.xml @@ -75,6 +81,8 @@ release-x86: - meson --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../../ - ninja -v -k0 - ninja -v test + - ./foot --version + - ./footclient --version artifacts: reports: junit: bld/release/meson-logs/testlog.junit.xml From 8cf9f0f0dc4f795d4f32e7c9cbc36205d18234fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 27 Nov 2021 20:59:58 +0100 Subject: [PATCH 4/6] tests: meson: link with tllist --- tests/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/meson.build b/tests/meson.build index 8618bf04..abc7f34a 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -3,6 +3,6 @@ config_test = executable( 'test-config.c', '../tokenize.c', wl_proto_headers, link_with: [common], - dependencies: [pixman, xkb, fcft]) + dependencies: [pixman, xkb, fcft, tllist]) test('config', config_test) From 9c5019ae778b2da8f5f8bb4f6c58014fb2a958d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 11 Dec 2021 18:30:17 +0100 Subject: [PATCH 5/6] test: config: key-bindings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds tests for all [key-bindings] options, as well as an invalid “action”. The tests also verify key combo collisions are detected correctly. --- tests/test-config.c | 218 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) diff --git a/tests/test-config.c b/tests/test-config.c index d8c38aa7..917b9700 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -262,11 +262,229 @@ test_section_main(void) config_free(conf); } +static void +test_uint16(struct context *ctx, bool (*parse_fun)(struct context *ctx), + const char *key, const uint16_t *conf_ptr); + +static void +test_key_binding(struct context *ctx, bool (*parse_fun)(struct context *ctx), + int action, int max_action, const char *const *map, + struct config_key_binding_list *bindings) +{ + xassert(map[action] != NULL); + xassert(bindings->count == 0); + + const char *key = map[action]; + + /* “Randomize” which modifiers to enable */ + const bool ctrl = action % 2; + const bool alt = action % 3; + const bool shift = action % 4; + const bool super = action % 5; + + /* Generate the modifier part of the ‘value’ */ + char modifier_string[32]; + int chars = sprintf(modifier_string, "%s%s%s%s", + ctrl ? XKB_MOD_NAME_CTRL "+" : "", + alt ? XKB_MOD_NAME_ALT "+" : "", + shift ? XKB_MOD_NAME_SHIFT "+" : "", + super ? XKB_MOD_NAME_LOGO "+" : ""); + + /* Use a unique symbol for this action */ + xkb_keysym_t sym = XKB_KEY_a + action; + char sym_name[8]; + xkb_keysym_get_name(sym, sym_name, sizeof(sym_name)); + + /* Finally, generate the ‘value’ (e.g. “Control+shift+x”) */ + char value[chars + strlen(sym_name) + 1]; + sprintf(value, "%s%s", modifier_string, sym_name); + + ctx->key = key; + ctx->value = value; + + if (!parse_fun(ctx)) { + BUG("[%s].%s=%s failed to parse", + ctx->section, ctx->key, ctx->value); + } + + const struct config_key_binding *binding = + &bindings->arr[bindings->count - 1]; + + xassert(binding->pipe.argv.args == NULL); + + if (binding->action != action) { + BUG("[%s].%s=%s: action mismatch: %d != %d", + ctx->section, ctx->key, ctx->value, binding->action, action); + } + + if (binding->sym != sym) { + BUG("[%s].%s=%s: key symbol mismatch: %d != %d", + ctx->section, ctx->key, ctx->value, binding->sym, sym); + } + + if (binding->modifiers.ctrl != ctrl || + binding->modifiers.alt != alt || + binding->modifiers.shift != shift || + binding->modifiers.meta != super) + { + BUG("[%s].%s=%s: modifier mismatch:\n" + " have: ctrl=%d, alt=%d, shift=%d, super=%d\n" + " expected: ctrl=%d, alt=%d, shift=%d, super=%d", + ctx->section, ctx->key, ctx->value, + binding->modifiers.ctrl, binding->modifiers.alt, + binding->modifiers.shift, binding->modifiers.meta, + ctrl, alt, shift, super); + } + + key_binding_list_free(bindings); + bindings->arr = NULL; + bindings->count = 0; + + if (action >= max_action) + return; + + /* + * Test collisions + */ + + /* First, verify we get a collision when trying to assign the same + * key combo to multiple actions */ + bindings->count = 1; + bindings->arr = xmalloc(sizeof(bindings->arr[0])); + bindings->arr[0] = (struct config_key_binding){ + .action = action + 1, + .sym = XKB_KEY_a, + .modifiers = { + .ctrl = true, + }, + }; + + xkb_keysym_get_name(XKB_KEY_a, sym_name, sizeof(sym_name)); + + char collision[128]; + snprintf(collision, sizeof(collision), "%s+%s", XKB_MOD_NAME_CTRL, sym_name); + + ctx->value = collision; + if (parse_fun(ctx)) { + BUG("[%s].%s=%s: key combo collision not detected", + ctx->section, ctx->key, ctx->value); + } + + /* Next, verify we get a collision when trying to assign the same + * key combo to the same action, but with different pipe argvs */ + bindings->arr[0].action = action; + bindings->arr[0].pipe.master_copy = true; + bindings->arr[0].pipe.argv.args = xmalloc(4 * sizeof(bindings->arr[0].pipe.argv.args[0])); + bindings->arr[0].pipe.argv.args[0] = xstrdup("/usr/bin/foobar"); + bindings->arr[0].pipe.argv.args[1] = xstrdup("hello"); + bindings->arr[0].pipe.argv.args[2] = xstrdup("world"); + bindings->arr[0].pipe.argv.args[3] = NULL; + + snprintf(collision, sizeof(collision), + "[/usr/bin/foobar hello] %s+%s", + XKB_MOD_NAME_CTRL, sym_name); + + ctx->value = collision; + if (parse_fun(ctx)) { + BUG("[%s].%s=%s: key combo collision not detected", + ctx->section, ctx->key, ctx->value); + } + + /* Finally, verify we do *not* get a collision when assigning the + * same key combo to the same action, with matching argvs */ + snprintf(collision, sizeof(collision), + "[/usr/bin/foobar hello world] %s+%s", + XKB_MOD_NAME_CTRL, sym_name); + + ctx->value = collision; + if (!parse_fun(ctx)) { + BUG("[%s].%s=%s: invalid key combo collision", + ctx->section, ctx->key, ctx->value); + } + + key_binding_list_free(bindings); + bindings->arr = NULL; + bindings->count = 0; +} + +static void +test_section_key_bindings(void) +{ + struct config conf = {0}; + struct context ctx = { + .conf = &conf, .section = "key-bindings", .path = "unittest"}; + + test_invalid_key(&ctx, &parse_section_key_bindings, "invalid-key"); + + for (int action = 0; action < BIND_ACTION_KEY_COUNT; action++) { + if (binding_action_map[action] == NULL) + continue; + + test_key_binding( + &ctx, &parse_section_key_bindings, + action, BIND_ACTION_KEY_COUNT - 1, + binding_action_map, &conf.bindings.key); + } + + config_free(conf); +} + +#if 0 +static void +test_section_search_bindings(void) +{ + struct config conf = {0}; + struct context ctx = { + .conf = &conf, .section = "search-bindings", .path = "unittest"}; + + test_invalid_key(&ctx, &parse_section_search_bindings, "invalid-key"); + + for (int action = 0; action < BIND_ACTION_SEARCH_COUNT; action++) { + if (search_binding_action_map[action] == NULL) + continue; + + test_key_binding( + &ctx, &parse_section_search_bindings, + action, BIND_ACTION_SEARCH_COUNT - 1, + search_binding_action_map, &conf.bindings.search); + } + + config_free(conf); +} + +static void +test_section_url_bindings(void) +{ + struct config conf = {0}; + struct context ctx = { + .conf = &conf, .section = "rul-bindings", .path = "unittest"}; + + test_invalid_key(&ctx, &parse_section_url_bindings, "invalid-key"); + + for (int action = 0; action < BIND_ACTION_URL_COUNT; action++) { + if (url_binding_action_map[action] == NULL) + continue; + + test_key_binding( + &ctx, &parse_section_url_bindings, + action, BIND_ACTION_URL_COUNT - 1, + url_binding_action_map, &conf.bindings.url); + } + + config_free(conf); +} +#endif + int main(int argc, const char *const *argv) { log_init(LOG_COLORIZE_AUTO, false, 0, LOG_CLASS_ERROR); test_section_main(); + test_section_key_bindings(); +#if 0 + test_section_search_bindings(); + test_section_url_bindings(); +#endif log_deinit(); return 0; } From 614de228a83cc4fed1b53b3aeef9a83720e056e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 11 Dec 2021 18:49:59 +0100 Subject: [PATCH 6/6] test: config: mouse-bindings --- tests/test-config.c | 175 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 171 insertions(+), 4 deletions(-) diff --git a/tests/test-config.c b/tests/test-config.c index 917b9700..befe4864 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -262,10 +262,6 @@ test_section_main(void) config_free(conf); } -static void -test_uint16(struct context *ctx, bool (*parse_fun)(struct context *ctx), - const char *key, const uint16_t *conf_ptr); - static void test_key_binding(struct context *ctx, bool (*parse_fun)(struct context *ctx), int action, int max_action, const char *const *map, @@ -407,6 +403,154 @@ test_key_binding(struct context *ctx, bool (*parse_fun)(struct context *ctx), bindings->count = 0; } +static void +test_mouse_binding(struct context *ctx, bool (*parse_fun)(struct context *ctx), + int action, int max_action, const char *const *map, + struct config_mouse_binding_list *bindings) +{ + xassert(map[action] != NULL); + xassert(bindings->count == 0); + + const char *key = map[action]; + + /* “Randomize” which modifiers to enable */ + const bool ctrl = action % 2; + const bool alt = action % 3; + const bool shift = action % 4; + const bool super = action % 5; + + /* Generate the modifier part of the ‘value’ */ + char modifier_string[32]; + int chars = sprintf(modifier_string, "%s%s%s%s", + ctrl ? XKB_MOD_NAME_CTRL "+" : "", + alt ? XKB_MOD_NAME_ALT "+" : "", + shift ? XKB_MOD_NAME_SHIFT "+" : "", + super ? XKB_MOD_NAME_LOGO "+" : ""); + + const int button_idx = action % ALEN(button_map); + const int button = button_map[button_idx].code; + const char *const button_name = button_map[button_idx].name; + const int click_count = action % 3 + 1; + + xassert(click_count > 0); + + /* Finally, generate the ‘value’ (e.g. “Control+shift+x”) */ + char value[chars + strlen(button_name) + 2 + 1]; + chars = sprintf(value, "%s%s", modifier_string, button_name); + if (click_count > 1) + sprintf(&value[chars], "-%d", click_count); + + ctx->key = key; + ctx->value = value; + + if (!parse_fun(ctx)) { + BUG("[%s].%s=%s failed to parse", + ctx->section, ctx->key, ctx->value); + } + + const struct config_mouse_binding *binding = + &bindings->arr[bindings->count - 1]; + + xassert(binding->pipe.argv.args == NULL); + + if (binding->action != action) { + BUG("[%s].%s=%s: action mismatch: %d != %d", + ctx->section, ctx->key, ctx->value, binding->action, action); + } + + if (binding->button != button) { + BUG("[%s].%s=%s: button mismatch: %d != %d", + ctx->section, ctx->key, ctx->value, binding->button, button); + } + + if (binding->count != click_count) { + BUG("[%s].%s=%s: button click count mismatch: %d != %d", + ctx->section, ctx->key, ctx->value, binding->count, click_count); + } + + if (binding->modifiers.ctrl != ctrl || + binding->modifiers.alt != alt || + binding->modifiers.shift != shift || + binding->modifiers.meta != super) + { + BUG("[%s].%s=%s: modifier mismatch:\n" + " have: ctrl=%d, alt=%d, shift=%d, super=%d\n" + " expected: ctrl=%d, alt=%d, shift=%d, super=%d", + ctx->section, ctx->key, ctx->value, + binding->modifiers.ctrl, binding->modifiers.alt, + binding->modifiers.shift, binding->modifiers.meta, + ctrl, alt, shift, super); + } + + mouse_binding_list_free(bindings); + bindings->arr = NULL; + bindings->count = 0; + + if (action >= max_action) + return; + + /* + * Test collisions + */ + + /* First, verify we get a collision when trying to assign the same + * key combo to multiple actions */ + bindings->count = 1; + bindings->arr = xmalloc(sizeof(bindings->arr[0])); + bindings->arr[0] = (struct config_mouse_binding){ + .action = action + 1, + .button = BTN_LEFT, + .count = 1, + .modifiers = { + .ctrl = true, + }, + }; + + char collision[128]; + snprintf(collision, sizeof(collision), "%s+BTN_LEFT", XKB_MOD_NAME_CTRL); + + ctx->value = collision; + if (parse_fun(ctx)) { + BUG("[%s].%s=%s: mouse combo collision not detected", + ctx->section, ctx->key, ctx->value); + } + + /* Next, verify we get a collision when trying to assign the same + * key combo to the same action, but with different pipe argvs */ + bindings->arr[0].action = action; + bindings->arr[0].pipe.master_copy = true; + bindings->arr[0].pipe.argv.args = xmalloc(4 * sizeof(bindings->arr[0].pipe.argv.args[0])); + bindings->arr[0].pipe.argv.args[0] = xstrdup("/usr/bin/foobar"); + bindings->arr[0].pipe.argv.args[1] = xstrdup("hello"); + bindings->arr[0].pipe.argv.args[2] = xstrdup("world"); + bindings->arr[0].pipe.argv.args[3] = NULL; + + snprintf(collision, sizeof(collision), + "[/usr/bin/foobar hello] %s+BTN_LEFT", XKB_MOD_NAME_CTRL); + + ctx->value = collision; + if (parse_fun(ctx)) { + BUG("[%s].%s=%s: key combo collision not detected", + ctx->section, ctx->key, ctx->value); + } + +#if 0 /* BUG! (should be fixed by https://codeberg.org/dnkl/foot/pulls/832) */ + /* Finally, verify we do *not* get a collision when assigning the + * same key combo to the same action, with matching argvs */ + snprintf(collision, sizeof(collision), + "[/usr/bin/foobar hello world] %s+BTN_LEFT", XKB_MOD_NAME_CTRL); + + ctx->value = collision; + if (!parse_fun(ctx)) { + BUG("[%s].%s=%s: invalid mouse combo collision", + ctx->section, ctx->key, ctx->value); + } +#endif + mouse_binding_list_free(bindings); + bindings->arr = NULL; + bindings->count = 0; +} + static void test_section_key_bindings(void) { @@ -475,6 +619,28 @@ test_section_url_bindings(void) } #endif +static void +test_section_mouse_bindings(void) +{ + struct config conf = {0}; + struct context ctx = { + .conf = &conf, .section = "mouse-bindings", .path = "unittest"}; + + test_invalid_key(&ctx, &parse_section_mouse_bindings, "invalid-key"); + + for (int action = 0; action < BIND_ACTION_COUNT; action++) { + if (binding_action_map[action] == NULL) + continue; + + test_mouse_binding( + &ctx, &parse_section_mouse_bindings, + action, BIND_ACTION_COUNT - 1, + binding_action_map, &conf.bindings.mouse); + } + + config_free(conf); +} + int main(int argc, const char *const *argv) { @@ -485,6 +651,7 @@ main(int argc, const char *const *argv) test_section_search_bindings(); test_section_url_bindings(); #endif + test_section_mouse_bindings(); log_deinit(); return 0; }