2 * Copyright © 2020 Red Hat, Inc.
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
33 #include <sys/types.h>
35 #include "xkbcommon/xkbregistry.h"
39 #define NO_VARIANT NULL
49 const char *name; /* required */
51 const char *description;
55 const char *name; /* required */
58 const char *description;
63 const char *description;
66 struct test_option_group {
68 const char *description;
69 bool allow_multiple_selection;
71 struct test_option options[10];
75 fprint_config_item(FILE *fp,
79 const char *description)
81 fprintf(fp, " <configItem>\n"
82 " <name>%s</name>\n", name);
84 fprintf(fp, " <shortDescription>%s</shortDescription>\n", brief);
86 fprintf(fp, " <description>%s</description>\n", description);
88 fprintf(fp, " <vendor>%s</vendor>\n", vendor);
89 fprintf(fp, " </configItem>\n");
93 * Create a directory populated with a rules/<ruleset>.xml that contains the
96 * @return the XKB base directory
99 test_create_rules(const char *ruleset,
100 const struct test_model *test_models,
101 const struct test_layout *test_layouts,
102 const struct test_option_group *test_groups)
104 static int iteration;
110 tmpdir = asprintf_safe("/tmp/%s.%d.XXXXXX", ruleset, iteration++);
112 assert(mkdtemp(tmpdir) == tmpdir);
114 rc = snprintf_safe(buf, sizeof(buf), "%s/rules", tmpdir);
116 rc = mkdir(buf, 0777);
118 rc = snprintf_safe(buf, sizeof(buf), "%s/rules/%s.xml", tmpdir, ruleset);
121 fp = fopen(buf, "w");
125 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
126 "<!DOCTYPE xkbConfigRegistry SYSTEM \"xkb.dtd\">\n"
127 "<xkbConfigRegistry version=\"1.1\">\n");
130 fprintf(fp, "<modelList>\n");
132 for (const struct test_model *m = test_models; m->name; m++) {
133 fprintf(fp, "<model>\n");
134 fprint_config_item(fp, m->name, m->vendor, NULL, m->description);
135 fprintf(fp, "</model>\n");
137 fprintf(fp, "</modelList>\n");
141 const struct test_layout *l, *next;
143 fprintf(fp, "<layoutList>\n");
148 assert(l->variant == NULL);
151 fprintf(fp, "<layout>\n");
152 fprint_config_item(fp, l->name, NULL, l->brief, l->description);
154 if (next->name && streq(next->name, l->name)) {
155 fprintf(fp, "<variantList>\n");
157 fprintf(fp, "<variant>\n");
158 fprint_config_item(fp, next->variant, NULL, next->brief,
160 fprintf(fp, "</variant>\n");
163 } while (next->name && streq(next->name, l->name));
164 fprintf(fp, "</variantList>\n");
166 fprintf(fp, "</layout>\n");
169 fprintf(fp, "</layoutList>\n");
173 fprintf(fp, "<optionList>\n");
175 for (const struct test_option_group *g = test_groups; g->name; g++) {
176 fprintf(fp, "<group allowMultipleSelection=\"%s\">\n",
177 g->allow_multiple_selection ? "true" : "false");
178 fprint_config_item(fp, g->name, NULL, NULL, g->description);
179 for (const struct test_option *o = g->options; o->name; o++) {
180 fprintf(fp, " <option>\n");
181 fprint_config_item(fp, o->name, NULL, NULL, o->description);
182 fprintf(fp, "</option>\n");
184 fprintf(fp, "</group>\n");
186 fprintf(fp, "</optionList>\n");
189 fprintf(fp, "</xkbConfigRegistry>\n");
196 test_remove_rules(char *basedir, const char *ruleset)
201 rc = snprintf_safe(path, sizeof(path), "%s/rules/%s.xml", basedir,
205 rc = snprintf_safe(path, sizeof(path), "%s/xkb/rules", basedir);
212 static struct rxkb_context *
213 test_setup_context_for(const char *ruleset,
214 struct test_model *system_models,
215 struct test_model *user_models,
216 struct test_layout *system_layouts,
217 struct test_layout *user_layouts,
218 struct test_option_group *system_groups,
219 struct test_option_group *user_groups)
221 char *sysdir = NULL, *userdir = NULL;
222 struct rxkb_context *ctx;
224 sysdir = test_create_rules(ruleset, system_models, system_layouts,
226 if (user_models || user_layouts || user_groups)
227 userdir = test_create_rules(ruleset, user_models, user_layouts,
230 ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES);
233 assert(rxkb_context_include_path_append(ctx, userdir));
234 assert(rxkb_context_include_path_append(ctx, sysdir));
235 assert(rxkb_context_parse(ctx, ruleset));
237 test_remove_rules(sysdir, ruleset);
239 test_remove_rules(userdir, ruleset);
244 static struct rxkb_context *
245 test_setup_context(struct test_model *system_models,
246 struct test_model *user_models,
247 struct test_layout *system_layouts,
248 struct test_layout *user_layouts,
249 struct test_option_group *system_groups,
250 struct test_option_group *user_groups)
252 const char *ruleset = "xkbtests";
253 return test_setup_context_for(ruleset, system_models,
254 user_models, system_layouts,
255 user_layouts, system_groups,
259 static struct rxkb_model *
260 fetch_model(struct rxkb_context *ctx, const char *model)
262 struct rxkb_model *m = rxkb_model_first(ctx);
264 if (streq(rxkb_model_get_name(m), model))
265 return rxkb_model_ref(m);
266 m = rxkb_model_next(m);
272 find_model(struct rxkb_context *ctx, const char *model)
274 struct rxkb_model *m = fetch_model(ctx, model);
280 find_models(struct rxkb_context *ctx, ...)
287 name = va_arg(args, const char *);
289 assert(++idx < 20); /* safety guard */
290 if (!find_model(ctx, name))
292 name = va_arg(args, const char *);
299 static struct rxkb_layout *
300 fetch_layout(struct rxkb_context *ctx, const char *layout, const char *variant)
302 struct rxkb_layout *l = rxkb_layout_first(ctx);
304 const char *v = rxkb_layout_get_variant(l);
306 if (streq(rxkb_layout_get_name(l), layout) &&
307 ((v == NULL && variant == NULL) ||
308 (v != NULL && variant != NULL && streq(v, variant))))
309 return rxkb_layout_ref(l);
310 l = rxkb_layout_next(l);
316 find_layout(struct rxkb_context *ctx, const char *layout, const char *variant)
318 struct rxkb_layout *l = fetch_layout(ctx, layout, variant);
319 rxkb_layout_unref(l);
324 find_layouts(struct rxkb_context *ctx, ...)
327 const char *name, *variant;
331 name = va_arg(args, const char *);
332 variant = va_arg(args, const char *);
334 assert(++idx < 20); /* safety guard */
335 if (!find_layout(ctx, name, variant))
337 name = va_arg(args, const char *);
339 variant = va_arg(args, const char *);
346 static struct rxkb_option_group *
347 fetch_option_group(struct rxkb_context *ctx, const char *grp)
349 struct rxkb_option_group *g = rxkb_option_group_first(ctx);
351 if (streq(grp, rxkb_option_group_get_name(g)))
352 return rxkb_option_group_ref(g);
353 g = rxkb_option_group_next(g);
359 find_option_group(struct rxkb_context *ctx, const char *grp)
361 struct rxkb_option_group *g = fetch_option_group(ctx, grp);
362 rxkb_option_group_unref(g);
366 static struct rxkb_option *
367 fetch_option(struct rxkb_context *ctx, const char *grp, const char *opt)
369 struct rxkb_option_group *g = rxkb_option_group_first(ctx);
371 if (streq(grp, rxkb_option_group_get_name(g))) {
372 struct rxkb_option *o = rxkb_option_first(g);
375 if (streq(opt, rxkb_option_get_name(o)))
376 return rxkb_option_ref(o);
377 o = rxkb_option_next(o);
380 g = rxkb_option_group_next(g);
386 find_option(struct rxkb_context *ctx, const char *grp, const char *opt)
388 struct rxkb_option *o = fetch_option(ctx, grp, opt);
389 rxkb_option_unref(o);
394 find_options(struct rxkb_context *ctx, ...)
397 const char *grp, *opt;
401 grp = va_arg(args, const char *);
402 opt = va_arg(args, const char *);
404 assert(++idx < 20); /* safety guard */
405 if (!find_option(ctx, grp, opt))
407 grp = va_arg(args, const char *);
409 opt = va_arg(args, const char *);
417 cmp_models(struct test_model *tm, struct rxkb_model *m)
422 if (!streq(tm->name, rxkb_model_get_name(m)))
425 if (!streq_null(tm->vendor, rxkb_model_get_vendor(m)))
428 if (!streq_null(tm->description, rxkb_model_get_description(m)))
435 cmp_layouts(struct test_layout *tl, struct rxkb_layout *l)
440 if (!streq(tl->name, rxkb_layout_get_name(l)))
443 if (!streq_null(tl->variant, rxkb_layout_get_variant(l)))
446 if (!streq_null(tl->brief, rxkb_layout_get_brief(l)))
449 if (!streq_null(tl->description, rxkb_layout_get_description(l)))
456 cmp_options(struct test_option *to, struct rxkb_option *o)
461 if (!streq(to->name, rxkb_option_get_name(o)))
464 if (!streq_null(to->description, rxkb_option_get_description(o)))
476 cmp_option_groups(struct test_option_group *tg, struct rxkb_option_group *g,
479 struct rxkb_option *o;
480 struct test_option *to;
485 if (!streq(tg->name, rxkb_option_group_get_name(g)))
488 if (!streq_null(tg->description, rxkb_option_group_get_description(g)))
491 if (tg->allow_multiple_selection != rxkb_option_group_allows_multiple(g))
495 o = rxkb_option_first(g);
497 while (o && to->name) {
498 if (!cmp_options(to, o))
501 o = rxkb_option_next(o);
504 if (cmp == CMP_EXACT && (o || to->name))
511 test_load_basic(void)
513 struct test_model system_models[] = {
518 struct test_layout system_layouts[] = {
523 struct test_option_group system_groups[] = {
525 { {"grp1:1"}, {"grp1:2"} } },
526 {"grp2", NULL, false,
527 { {"grp2:1"}, {"grp2:2"} } },
530 struct rxkb_context *ctx;
532 ctx = test_setup_context(system_models, NULL,
533 system_layouts, NULL,
534 system_groups, NULL);
536 assert(find_models(ctx, "m1", "m2", NULL));
537 assert(find_layouts(ctx, "l1", NO_VARIANT,
539 assert(find_options(ctx, "grp1", "grp1:1",
542 "grp2", "grp2:2", NULL));
543 rxkb_context_unref(ctx);
549 struct test_model system_models[] = {
550 {"m1", "vendor1", "desc1"},
551 {"m2", "vendor2", "desc2"},
554 struct test_layout system_layouts[] = {
555 {"l1", NO_VARIANT, "lbrief1", "ldesc1"},
556 {"l1", "v1", "vbrief1", "vdesc1"},
559 struct test_option_group system_groups[] = {
560 {"grp1", "gdesc1", true,
561 { {"grp1:1", "odesc11"}, {"grp1:2", "odesc12"} } },
562 {"grp2", "gdesc2", false,
563 { {"grp2:1", "odesc21"}, {"grp2:2", "odesc22"} } },
566 struct rxkb_context *ctx;
567 struct rxkb_model *m;
568 struct rxkb_layout *l;
569 struct rxkb_option_group *g;
571 ctx = test_setup_context(system_models, NULL,
572 system_layouts, NULL,
573 system_groups, NULL);
575 m = fetch_model(ctx, "m1");
576 assert(cmp_models(&system_models[0], m));
579 m = fetch_model(ctx, "m2");
580 assert(cmp_models(&system_models[1], m));
583 l = fetch_layout(ctx, "l1", NO_VARIANT);
584 assert(cmp_layouts(&system_layouts[0], l));
585 rxkb_layout_unref(l);
587 l = fetch_layout(ctx, "l1", "v1");
588 assert(cmp_layouts(&system_layouts[1], l));
589 rxkb_layout_unref(l);
591 g = fetch_option_group(ctx, "grp1");
592 assert(cmp_option_groups(&system_groups[0], g, CMP_EXACT));
593 rxkb_option_group_unref(g);
595 g = fetch_option_group(ctx, "grp2");
596 assert(cmp_option_groups(&system_groups[1], g, CMP_EXACT));
597 rxkb_option_group_unref(g);
599 rxkb_context_unref(ctx);
603 test_popularity(void)
605 struct test_layout system_layouts[] = {
610 struct rxkb_context *ctx;
611 struct rxkb_layout *l;
612 const char *ruleset = "xkbtests.extras";
615 dir = test_create_rules(ruleset, NULL, system_layouts, NULL);
616 ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES |
617 RXKB_CONTEXT_LOAD_EXOTIC_RULES);
619 assert(rxkb_context_include_path_append(ctx, dir));
620 /* Hack: rulest above generates xkbtests.extras.xml, loading "xkbtests"
621 * means the extras file counts as exotic */
622 assert(rxkb_context_parse(ctx, "xkbtests"));
624 l = fetch_layout(ctx, "l1", NO_VARIANT);
625 assert(rxkb_layout_get_popularity(l) == RXKB_POPULARITY_EXOTIC);
626 rxkb_layout_unref(l);
628 l = fetch_layout(ctx, "l1", "v1");
629 assert(rxkb_layout_get_popularity(l) == RXKB_POPULARITY_EXOTIC);
630 rxkb_layout_unref(l);
632 test_remove_rules(dir, ruleset);
633 rxkb_context_unref(ctx);
638 test_load_merge(void)
640 struct test_model system_models[] = {
641 {"m1", "vendor1", "desc1"},
642 {"m2", "vendor2", "desc2"},
645 struct test_model user_models[] = {
646 {"m3", "vendor3", "desc3"},
647 {"m4", "vendor4", "desc4"},
650 struct test_layout system_layouts[] = {
651 {"l1", NO_VARIANT, "lbrief1", "ldesc1"},
652 {"l1", "v1", "vbrief1", "vdesc1"},
655 struct test_layout user_layouts[] = {
656 {"l2", NO_VARIANT, "lbrief2", "ldesc2"},
657 {"l2", "v2", "vbrief2", "vdesc2"},
660 struct test_option_group system_groups[] = {
662 { {"grp1:1"}, {"grp1:2"} } },
663 {"grp2", NULL, false,
664 { {"grp2:1"}, {"grp2:2"} } },
667 struct test_option_group user_groups[] = {
669 { {"grp3:1"}, {"grp3:2"} } },
670 {"grp4", NULL, false,
671 { {"grp4:1"}, {"grp4:2"} } },
674 struct rxkb_context *ctx;
675 struct rxkb_model *m;
676 struct rxkb_layout *l;
677 struct rxkb_option_group *g;
679 ctx = test_setup_context(system_models, user_models,
680 system_layouts, user_layouts,
681 system_groups, user_groups);
683 assert(find_models(ctx, "m1", "m2", "m3", "m4", NULL));
684 assert(find_layouts(ctx, "l1", NO_VARIANT,
689 m = fetch_model(ctx, "m1");
690 assert(cmp_models(&system_models[0], m));
693 m = fetch_model(ctx, "m2");
694 assert(cmp_models(&system_models[1], m));
697 m = fetch_model(ctx, "m3");
698 assert(cmp_models(&user_models[0], m));
701 m = fetch_model(ctx, "m4");
702 assert(cmp_models(&user_models[1], m));
705 l = fetch_layout(ctx, "l1", NO_VARIANT);
706 assert(cmp_layouts(&system_layouts[0], l));
707 rxkb_layout_unref(l);
709 l = fetch_layout(ctx, "l1", "v1");
710 assert(cmp_layouts(&system_layouts[1], l));
711 rxkb_layout_unref(l);
713 l = fetch_layout(ctx, "l2", NO_VARIANT);
714 assert(cmp_layouts(&user_layouts[0], l));
715 rxkb_layout_unref(l);
717 l = fetch_layout(ctx, "l2", "v2");
718 assert(cmp_layouts(&user_layouts[1], l));
719 rxkb_layout_unref(l);
721 g = fetch_option_group(ctx, "grp1");
722 assert(cmp_option_groups(&system_groups[0], g, CMP_EXACT));
723 rxkb_option_group_unref(g);
725 g = fetch_option_group(ctx, "grp2");
726 assert(cmp_option_groups(&system_groups[1], g, CMP_EXACT));
727 rxkb_option_group_unref(g);
729 g = fetch_option_group(ctx, "grp3");
730 assert(cmp_option_groups(&user_groups[0], g, CMP_EXACT));
731 rxkb_option_group_unref(g);
733 g = fetch_option_group(ctx, "grp4");
734 assert(cmp_option_groups(&user_groups[1], g, CMP_EXACT));
735 rxkb_option_group_unref(g);
737 rxkb_context_unref(ctx);
741 test_load_merge_no_overwrite(void)
743 struct test_model system_models[] = {
744 {"m1", "vendor1", "desc1"},
745 {"m2", "vendor2", "desc2"},
748 struct test_model user_models[] = {
749 {"m1", "vendor3", "desc3"}, /* must not overwrite */
750 {"m4", "vendor4", "desc4"},
753 struct test_layout system_layouts[] = {
754 {"l1", NO_VARIANT, "lbrief1", "ldesc1"},
755 {"l1", "v1", "vbrief1", "vdesc1"},
758 struct test_layout user_layouts[] = {
759 {"l2", NO_VARIANT, "lbrief2", "ldesc2"},
760 {"l2", "v2", "vbrief2", "vdesc2"},
761 {"l1", NO_VARIANT, "lbrief3", "ldesc3"}, /* must not overwrite */
762 {"l1", "v2", "vbrief3", "vdesc3"}, /* must not overwrite */
765 struct test_option_group system_groups[] = {
766 {"grp1", "gdesc1", true,
767 { {"grp1:1", "odesc11"}, {"grp1:2", "odesc12"} } },
768 {"grp2", "gdesc2", false,
769 { {"grp2:1", "odesc21"}, {"grp2:2", "odesc22"} } },
772 struct test_option_group user_groups[] = {
773 {"grp1", "XXXXX", false, /* must not overwrite */
774 { {"grp1:1", "YYYYYYY"}, /* must not overwrite */
775 {"grp1:3", "ZZZZZZ"} } }, /* append */
776 {"grp4", "gdesc4", false,
777 { {"grp4:1", "odesc41"}, {"grp4:2", "odesc42"} } },
780 struct rxkb_context *ctx;
781 struct rxkb_model *m;
782 struct rxkb_layout *l;
783 struct rxkb_option_group *g;
785 ctx = test_setup_context(system_models, user_models,
786 system_layouts, user_layouts,
787 system_groups, user_groups);
789 m = fetch_model(ctx, "m1");
790 assert(cmp_models(&system_models[0], m));
793 l = fetch_layout(ctx, "l1", NO_VARIANT);
794 assert(cmp_layouts(&system_layouts[0], l));
795 rxkb_layout_unref(l);
797 l = fetch_layout(ctx, "l1", "v1");
798 assert(cmp_layouts(&system_layouts[1], l));
799 rxkb_layout_unref(l);
801 assert(find_option(ctx, "grp1", "grp1:3"));
802 g = fetch_option_group(ctx, "grp1");
803 assert(cmp_option_groups(&system_groups[0], g, CMP_MATCHING_ONLY));
804 rxkb_option_group_unref(g);
806 rxkb_context_unref(ctx);
810 test_no_include_paths(void)
812 struct rxkb_context *ctx;
814 ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES);
816 assert(!rxkb_context_parse_default_ruleset(ctx));
818 rxkb_context_unref(ctx);
822 test_invalid_include(void)
824 struct rxkb_context *ctx;
826 ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES);
828 assert(!rxkb_context_include_path_append(ctx, "/foo/bar/baz/bat"));
829 assert(!rxkb_context_parse_default_ruleset(ctx));
831 rxkb_context_unref(ctx);
837 test_no_include_paths();
838 test_invalid_include();
842 test_load_merge_no_overwrite();