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, ...)
288 name = va_arg(args, const char *);
290 assert(++idx < 20); /* safety guard */
291 if (!find_model(ctx, name))
293 name = va_arg(args, const char *);
302 static struct rxkb_layout *
303 fetch_layout(struct rxkb_context *ctx, const char *layout, const char *variant)
305 struct rxkb_layout *l = rxkb_layout_first(ctx);
307 const char *v = rxkb_layout_get_variant(l);
309 if (streq(rxkb_layout_get_name(l), layout) &&
310 ((v == NULL && variant == NULL) ||
311 (v != NULL && variant != NULL && streq(v, variant))))
312 return rxkb_layout_ref(l);
313 l = rxkb_layout_next(l);
319 find_layout(struct rxkb_context *ctx, const char *layout, const char *variant)
321 struct rxkb_layout *l = fetch_layout(ctx, layout, variant);
322 rxkb_layout_unref(l);
327 find_layouts(struct rxkb_context *ctx, ...)
330 const char *name, *variant;
335 name = va_arg(args, const char *);
336 variant = va_arg(args, const char *);
338 assert(++idx < 20); /* safety guard */
339 if (!find_layout(ctx, name, variant))
341 name = va_arg(args, const char *);
343 variant = va_arg(args, const char *);
352 static struct rxkb_option_group *
353 fetch_option_group(struct rxkb_context *ctx, const char *grp)
355 struct rxkb_option_group *g = rxkb_option_group_first(ctx);
357 if (streq(grp, rxkb_option_group_get_name(g)))
358 return rxkb_option_group_ref(g);
359 g = rxkb_option_group_next(g);
365 find_option_group(struct rxkb_context *ctx, const char *grp)
367 struct rxkb_option_group *g = fetch_option_group(ctx, grp);
368 rxkb_option_group_unref(g);
372 static struct rxkb_option *
373 fetch_option(struct rxkb_context *ctx, const char *grp, const char *opt)
375 struct rxkb_option_group *g = rxkb_option_group_first(ctx);
377 if (streq(grp, rxkb_option_group_get_name(g))) {
378 struct rxkb_option *o = rxkb_option_first(g);
381 if (streq(opt, rxkb_option_get_name(o)))
382 return rxkb_option_ref(o);
383 o = rxkb_option_next(o);
386 g = rxkb_option_group_next(g);
392 find_option(struct rxkb_context *ctx, const char *grp, const char *opt)
394 struct rxkb_option *o = fetch_option(ctx, grp, opt);
395 rxkb_option_unref(o);
400 find_options(struct rxkb_context *ctx, ...)
403 const char *grp, *opt;
408 grp = va_arg(args, const char *);
409 opt = va_arg(args, const char *);
411 assert(++idx < 20); /* safety guard */
412 if (!find_option(ctx, grp, opt))
414 grp = va_arg(args, const char *);
416 opt = va_arg(args, const char *);
426 cmp_models(struct test_model *tm, struct rxkb_model *m)
431 if (!streq(tm->name, rxkb_model_get_name(m)))
434 if (!streq_null(tm->vendor, rxkb_model_get_vendor(m)))
437 if (!streq_null(tm->description, rxkb_model_get_description(m)))
444 cmp_layouts(struct test_layout *tl, struct rxkb_layout *l)
449 if (!streq(tl->name, rxkb_layout_get_name(l)))
452 if (!streq_null(tl->variant, rxkb_layout_get_variant(l)))
455 if (!streq_null(tl->brief, rxkb_layout_get_brief(l)))
458 if (!streq_null(tl->description, rxkb_layout_get_description(l)))
465 cmp_options(struct test_option *to, struct rxkb_option *o)
470 if (!streq(to->name, rxkb_option_get_name(o)))
473 if (!streq_null(to->description, rxkb_option_get_description(o)))
485 cmp_option_groups(struct test_option_group *tg, struct rxkb_option_group *g,
488 struct rxkb_option *o;
489 struct test_option *to;
494 if (!streq(tg->name, rxkb_option_group_get_name(g)))
497 if (!streq_null(tg->description, rxkb_option_group_get_description(g)))
500 if (tg->allow_multiple_selection != rxkb_option_group_allows_multiple(g))
504 o = rxkb_option_first(g);
506 while (o && to->name) {
507 if (!cmp_options(to, o))
510 o = rxkb_option_next(o);
513 if (cmp == CMP_EXACT && (o || to->name))
520 test_load_basic(void)
522 struct test_model system_models[] = {
527 struct test_layout system_layouts[] = {
532 struct test_option_group system_groups[] = {
534 { {"grp1:1"}, {"grp1:2"} } },
535 {"grp2", NULL, false,
536 { {"grp2:1"}, {"grp2:2"} } },
539 struct rxkb_context *ctx;
541 ctx = test_setup_context(system_models, NULL,
542 system_layouts, NULL,
543 system_groups, NULL);
545 assert(find_models(ctx, "m1", "m2", NULL));
546 assert(find_layouts(ctx, "l1", NO_VARIANT,
548 assert(find_options(ctx, "grp1", "grp1:1",
551 "grp2", "grp2:2", NULL));
552 rxkb_context_unref(ctx);
558 struct test_model system_models[] = {
559 {"m1", "vendor1", "desc1"},
560 {"m2", "vendor2", "desc2"},
563 struct test_layout system_layouts[] = {
564 {"l1", NO_VARIANT, "lbrief1", "ldesc1"},
565 {"l1", "v1", "vbrief1", "vdesc1"},
568 struct test_option_group system_groups[] = {
569 {"grp1", "gdesc1", true,
570 { {"grp1:1", "odesc11"}, {"grp1:2", "odesc12"} } },
571 {"grp2", "gdesc2", false,
572 { {"grp2:1", "odesc21"}, {"grp2:2", "odesc22"} } },
575 struct rxkb_context *ctx;
576 struct rxkb_model *m;
577 struct rxkb_layout *l;
578 struct rxkb_option_group *g;
580 ctx = test_setup_context(system_models, NULL,
581 system_layouts, NULL,
582 system_groups, NULL);
584 m = fetch_model(ctx, "m1");
585 assert(cmp_models(&system_models[0], m));
588 m = fetch_model(ctx, "m2");
589 assert(cmp_models(&system_models[1], m));
592 l = fetch_layout(ctx, "l1", NO_VARIANT);
593 assert(cmp_layouts(&system_layouts[0], l));
594 rxkb_layout_unref(l);
596 l = fetch_layout(ctx, "l1", "v1");
597 assert(cmp_layouts(&system_layouts[1], l));
598 rxkb_layout_unref(l);
600 g = fetch_option_group(ctx, "grp1");
601 assert(cmp_option_groups(&system_groups[0], g, CMP_EXACT));
602 rxkb_option_group_unref(g);
604 g = fetch_option_group(ctx, "grp2");
605 assert(cmp_option_groups(&system_groups[1], g, CMP_EXACT));
606 rxkb_option_group_unref(g);
608 rxkb_context_unref(ctx);
612 test_popularity(void)
614 struct test_layout system_layouts[] = {
619 struct rxkb_context *ctx;
620 struct rxkb_layout *l;
621 const char *ruleset = "xkbtests.extras";
624 dir = test_create_rules(ruleset, NULL, system_layouts, NULL);
625 ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES |
626 RXKB_CONTEXT_LOAD_EXOTIC_RULES);
628 assert(rxkb_context_include_path_append(ctx, dir));
629 /* Hack: rulest above generates xkbtests.extras.xml, loading "xkbtests"
630 * means the extras file counts as exotic */
631 assert(rxkb_context_parse(ctx, "xkbtests"));
633 l = fetch_layout(ctx, "l1", NO_VARIANT);
634 assert(rxkb_layout_get_popularity(l) == RXKB_POPULARITY_EXOTIC);
635 rxkb_layout_unref(l);
637 l = fetch_layout(ctx, "l1", "v1");
638 assert(rxkb_layout_get_popularity(l) == RXKB_POPULARITY_EXOTIC);
639 rxkb_layout_unref(l);
641 test_remove_rules(dir, ruleset);
642 rxkb_context_unref(ctx);
647 test_load_merge(void)
649 struct test_model system_models[] = {
650 {"m1", "vendor1", "desc1"},
651 {"m2", "vendor2", "desc2"},
654 struct test_model user_models[] = {
655 {"m3", "vendor3", "desc3"},
656 {"m4", "vendor4", "desc4"},
659 struct test_layout system_layouts[] = {
660 {"l1", NO_VARIANT, "lbrief1", "ldesc1"},
661 {"l1", "v1", "vbrief1", "vdesc1"},
664 struct test_layout user_layouts[] = {
665 {"l2", NO_VARIANT, "lbrief2", "ldesc2"},
666 {"l2", "v2", "vbrief2", "vdesc2"},
669 struct test_option_group system_groups[] = {
671 { {"grp1:1"}, {"grp1:2"} } },
672 {"grp2", NULL, false,
673 { {"grp2:1"}, {"grp2:2"} } },
676 struct test_option_group user_groups[] = {
678 { {"grp3:1"}, {"grp3:2"} } },
679 {"grp4", NULL, false,
680 { {"grp4:1"}, {"grp4:2"} } },
683 struct rxkb_context *ctx;
684 struct rxkb_model *m;
685 struct rxkb_layout *l;
686 struct rxkb_option_group *g;
688 ctx = test_setup_context(system_models, user_models,
689 system_layouts, user_layouts,
690 system_groups, user_groups);
692 assert(find_models(ctx, "m1", "m2", "m3", "m4", NULL));
693 assert(find_layouts(ctx, "l1", NO_VARIANT,
698 m = fetch_model(ctx, "m1");
699 assert(cmp_models(&system_models[0], m));
702 m = fetch_model(ctx, "m2");
703 assert(cmp_models(&system_models[1], m));
706 m = fetch_model(ctx, "m3");
707 assert(cmp_models(&user_models[0], m));
710 m = fetch_model(ctx, "m4");
711 assert(cmp_models(&user_models[1], m));
714 l = fetch_layout(ctx, "l1", NO_VARIANT);
715 assert(cmp_layouts(&system_layouts[0], l));
716 rxkb_layout_unref(l);
718 l = fetch_layout(ctx, "l1", "v1");
719 assert(cmp_layouts(&system_layouts[1], l));
720 rxkb_layout_unref(l);
722 l = fetch_layout(ctx, "l2", NO_VARIANT);
723 assert(cmp_layouts(&user_layouts[0], l));
724 rxkb_layout_unref(l);
726 l = fetch_layout(ctx, "l2", "v2");
727 assert(cmp_layouts(&user_layouts[1], l));
728 rxkb_layout_unref(l);
730 g = fetch_option_group(ctx, "grp1");
731 assert(cmp_option_groups(&system_groups[0], g, CMP_EXACT));
732 rxkb_option_group_unref(g);
734 g = fetch_option_group(ctx, "grp2");
735 assert(cmp_option_groups(&system_groups[1], g, CMP_EXACT));
736 rxkb_option_group_unref(g);
738 g = fetch_option_group(ctx, "grp3");
739 assert(cmp_option_groups(&user_groups[0], g, CMP_EXACT));
740 rxkb_option_group_unref(g);
742 g = fetch_option_group(ctx, "grp4");
743 assert(cmp_option_groups(&user_groups[1], g, CMP_EXACT));
744 rxkb_option_group_unref(g);
746 rxkb_context_unref(ctx);
750 test_load_merge_no_overwrite(void)
752 struct test_model system_models[] = {
753 {"m1", "vendor1", "desc1"},
754 {"m2", "vendor2", "desc2"},
757 struct test_model user_models[] = {
758 {"m1", "vendor3", "desc3"}, /* must not overwrite */
759 {"m4", "vendor4", "desc4"},
762 struct test_layout system_layouts[] = {
763 {"l1", NO_VARIANT, "lbrief1", "ldesc1"},
764 {"l1", "v1", "vbrief1", "vdesc1"},
767 struct test_layout user_layouts[] = {
768 {"l2", NO_VARIANT, "lbrief2", "ldesc2"},
769 {"l2", "v2", "vbrief2", "vdesc2"},
770 {"l1", NO_VARIANT, "lbrief3", "ldesc3"}, /* must not overwrite */
771 {"l1", "v2", "vbrief3", "vdesc3"}, /* must not overwrite */
774 struct test_option_group system_groups[] = {
775 {"grp1", "gdesc1", true,
776 { {"grp1:1", "odesc11"}, {"grp1:2", "odesc12"} } },
777 {"grp2", "gdesc2", false,
778 { {"grp2:1", "odesc21"}, {"grp2:2", "odesc22"} } },
781 struct test_option_group user_groups[] = {
782 {"grp1", "XXXXX", false, /* must not overwrite */
783 { {"grp1:1", "YYYYYYY"}, /* must not overwrite */
784 {"grp1:3", "ZZZZZZ"} } }, /* append */
785 {"grp4", "gdesc4", false,
786 { {"grp4:1", "odesc41"}, {"grp4:2", "odesc42"} } },
789 struct rxkb_context *ctx;
790 struct rxkb_model *m;
791 struct rxkb_layout *l;
792 struct rxkb_option_group *g;
794 ctx = test_setup_context(system_models, user_models,
795 system_layouts, user_layouts,
796 system_groups, user_groups);
798 m = fetch_model(ctx, "m1");
799 assert(cmp_models(&system_models[0], m));
802 l = fetch_layout(ctx, "l1", NO_VARIANT);
803 assert(cmp_layouts(&system_layouts[0], l));
804 rxkb_layout_unref(l);
806 l = fetch_layout(ctx, "l1", "v1");
807 assert(cmp_layouts(&system_layouts[1], l));
808 rxkb_layout_unref(l);
810 assert(find_option(ctx, "grp1", "grp1:3"));
811 g = fetch_option_group(ctx, "grp1");
812 assert(cmp_option_groups(&system_groups[0], g, CMP_MATCHING_ONLY));
813 rxkb_option_group_unref(g);
815 rxkb_context_unref(ctx);
819 test_no_include_paths(void)
821 struct rxkb_context *ctx;
823 ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES);
825 assert(!rxkb_context_parse_default_ruleset(ctx));
827 rxkb_context_unref(ctx);
831 test_invalid_include(void)
833 struct rxkb_context *ctx;
835 ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES);
837 assert(!rxkb_context_include_path_append(ctx, "/foo/bar/baz/bat"));
838 assert(!rxkb_context_parse_default_ruleset(ctx));
840 rxkb_context_unref(ctx);
846 test_no_include_paths();
847 test_invalid_include();
851 test_load_merge_no_overwrite();