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.
30 #include <sys/types.h>
32 #include "xkbcommon/xkbregistry.h"
36 #define NO_VARIANT NULL
46 const char *name; /* required */
48 const char *description;
52 const char *name; /* required */
55 const char *description;
60 const char *description;
63 struct test_option_group {
65 const char *description;
66 bool allow_multiple_selection;
68 struct test_option options[10];
72 fprint_config_item(FILE *fp,
76 const char *description)
78 fprintf(fp, " <configItem>\n"
79 " <name>%s</name>\n", name);
81 fprintf(fp, " <shortDescription>%s</shortDescription>\n", brief);
83 fprintf(fp, " <description>%s</description>\n", description);
85 fprintf(fp, " <vendor>%s</vendor>\n", vendor);
86 fprintf(fp, " </configItem>\n");
90 * Create a directory populated with a rules/<ruleset>.xml that contains the
93 * @return the XKB base directory
96 test_create_rules(const char *ruleset,
97 const struct test_model *test_models,
98 const struct test_layout *test_layouts,
99 const struct test_option_group *test_groups)
101 static int iteration;
107 tmpdir = asprintf_safe("/tmp/%s.%d.XXXXXX", ruleset, iteration++);
109 assert(mkdtemp(tmpdir) == tmpdir);
111 rc = snprintf_safe(buf, sizeof(buf), "%s/rules", tmpdir);
113 rc = mkdir(buf, 0777);
115 rc = snprintf_safe(buf, sizeof(buf), "%s/rules/%s.xml", tmpdir, ruleset);
118 fp = fopen(buf, "w");
122 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
123 "<!DOCTYPE xkbConfigRegistry SYSTEM \"xkb.dtd\">\n"
124 "<xkbConfigRegistry version=\"1.1\">\n");
127 fprintf(fp, "<modelList>\n");
129 for (const struct test_model *m = test_models; m->name; m++) {
130 fprintf(fp, "<model>\n");
131 fprint_config_item(fp, m->name, m->vendor, NULL, m->description);
132 fprintf(fp, "</model>\n");
134 fprintf(fp, "</modelList>\n");
138 const struct test_layout *l, *next;
140 fprintf(fp, "<layoutList>\n");
145 assert(l->variant == NULL);
148 fprintf(fp, "<layout>\n");
149 fprint_config_item(fp, l->name, NULL, l->brief, l->description);
151 if (next->name && streq(next->name, l->name)) {
152 fprintf(fp, "<variantList>\n");
154 fprintf(fp, "<variant>\n");
155 fprint_config_item(fp, next->variant, NULL, next->brief,
157 fprintf(fp, "</variant>\n");
160 } while (next->name && streq(next->name, l->name));
161 fprintf(fp, "</variantList>\n");
163 fprintf(fp, "</layout>\n");
166 fprintf(fp, "</layoutList>\n");
170 fprintf(fp, "<optionList>\n");
172 for (const struct test_option_group *g = test_groups; g->name; g++) {
173 fprintf(fp, "<group allowMultipleSelection=\"%s\">\n",
174 g->allow_multiple_selection ? "true" : "false");
175 fprint_config_item(fp, g->name, NULL, NULL, g->description);
176 for (const struct test_option *o = g->options; o->name; o++) {
177 fprintf(fp, " <option>\n");
178 fprint_config_item(fp, o->name, NULL, NULL, o->description);
179 fprintf(fp, "</option>\n");
181 fprintf(fp, "</group>\n");
183 fprintf(fp, "</optionList>\n");
186 fprintf(fp, "</xkbConfigRegistry>\n");
193 test_remove_rules(char *basedir, const char *ruleset)
198 rc = snprintf_safe(path, sizeof(path), "%s/rules/%s.xml", basedir,
202 rc = snprintf_safe(path, sizeof(path), "%s/xkb/rules", basedir);
209 static struct rxkb_context *
210 test_setup_context_for(const char *ruleset,
211 struct test_model *system_models,
212 struct test_model *user_models,
213 struct test_layout *system_layouts,
214 struct test_layout *user_layouts,
215 struct test_option_group *system_groups,
216 struct test_option_group *user_groups)
218 char *sysdir = NULL, *userdir = NULL;
219 struct rxkb_context *ctx;
221 sysdir = test_create_rules(ruleset, system_models, system_layouts,
223 if (user_models || user_layouts || user_groups)
224 userdir = test_create_rules(ruleset, user_models, user_layouts,
227 ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES);
230 assert(rxkb_context_include_path_append(ctx, userdir));
231 assert(rxkb_context_include_path_append(ctx, sysdir));
232 assert(rxkb_context_parse(ctx, ruleset));
234 test_remove_rules(sysdir, ruleset);
236 test_remove_rules(userdir, ruleset);
241 static struct rxkb_context *
242 test_setup_context(struct test_model *system_models,
243 struct test_model *user_models,
244 struct test_layout *system_layouts,
245 struct test_layout *user_layouts,
246 struct test_option_group *system_groups,
247 struct test_option_group *user_groups)
249 const char *ruleset = "xkbtests";
250 return test_setup_context_for(ruleset, system_models,
251 user_models, system_layouts,
252 user_layouts, system_groups,
256 static struct rxkb_model *
257 fetch_model(struct rxkb_context *ctx, const char *model)
259 struct rxkb_model *m = rxkb_model_first(ctx);
261 if (streq(rxkb_model_get_name(m), model))
262 return rxkb_model_ref(m);
263 m = rxkb_model_next(m);
269 find_model(struct rxkb_context *ctx, const char *model)
271 struct rxkb_model *m = fetch_model(ctx, model);
277 find_models(struct rxkb_context *ctx, ...)
284 name = va_arg(args, const char *);
286 assert(++idx < 20); /* safety guard */
287 if (!find_model(ctx, name))
289 name = va_arg(args, const char *);
296 static struct rxkb_layout *
297 fetch_layout(struct rxkb_context *ctx, const char *layout, const char *variant)
299 struct rxkb_layout *l = rxkb_layout_first(ctx);
301 const char *v = rxkb_layout_get_variant(l);
303 if (streq(rxkb_layout_get_name(l), layout) &&
304 ((v == NULL && variant == NULL) ||
305 (v != NULL && variant != NULL && streq(v, variant))))
306 return rxkb_layout_ref(l);
307 l = rxkb_layout_next(l);
313 find_layout(struct rxkb_context *ctx, const char *layout, const char *variant)
315 struct rxkb_layout *l = fetch_layout(ctx, layout, variant);
316 rxkb_layout_unref(l);
321 find_layouts(struct rxkb_context *ctx, ...)
324 const char *name, *variant;
328 name = va_arg(args, const char *);
329 variant = va_arg(args, const char *);
331 assert(++idx < 20); /* safety guard */
332 if (!find_layout(ctx, name, variant))
334 name = va_arg(args, const char *);
336 variant = va_arg(args, const char *);
343 static struct rxkb_option_group *
344 fetch_option_group(struct rxkb_context *ctx, const char *grp)
346 struct rxkb_option_group *g = rxkb_option_group_first(ctx);
348 if (streq(grp, rxkb_option_group_get_name(g)))
349 return rxkb_option_group_ref(g);
350 g = rxkb_option_group_next(g);
356 find_option_group(struct rxkb_context *ctx, const char *grp)
358 struct rxkb_option_group *g = fetch_option_group(ctx, grp);
359 rxkb_option_group_unref(g);
363 static struct rxkb_option *
364 fetch_option(struct rxkb_context *ctx, const char *grp, const char *opt)
366 struct rxkb_option_group *g = rxkb_option_group_first(ctx);
368 if (streq(grp, rxkb_option_group_get_name(g))) {
369 struct rxkb_option *o = rxkb_option_first(g);
372 if (streq(opt, rxkb_option_get_name(o)))
373 return rxkb_option_ref(o);
374 o = rxkb_option_next(o);
377 g = rxkb_option_group_next(g);
383 find_option(struct rxkb_context *ctx, const char *grp, const char *opt)
385 struct rxkb_option *o = fetch_option(ctx, grp, opt);
386 rxkb_option_unref(o);
391 find_options(struct rxkb_context *ctx, ...)
394 const char *grp, *opt;
398 grp = va_arg(args, const char *);
399 opt = va_arg(args, const char *);
401 assert(++idx < 20); /* safety guard */
402 if (!find_option(ctx, grp, opt))
404 grp = va_arg(args, const char *);
406 opt = va_arg(args, const char *);
414 cmp_models(struct test_model *tm, struct rxkb_model *m)
419 if (!streq(tm->name, rxkb_model_get_name(m)))
422 if (!streq_null(tm->vendor, rxkb_model_get_vendor(m)))
425 if (!streq_null(tm->description, rxkb_model_get_description(m)))
432 cmp_layouts(struct test_layout *tl, struct rxkb_layout *l)
437 if (!streq(tl->name, rxkb_layout_get_name(l)))
440 if (!streq_null(tl->variant, rxkb_layout_get_variant(l)))
443 if (!streq_null(tl->brief, rxkb_layout_get_brief(l)))
446 if (!streq_null(tl->description, rxkb_layout_get_description(l)))
453 cmp_options(struct test_option *to, struct rxkb_option *o)
458 if (!streq(to->name, rxkb_option_get_name(o)))
461 if (!streq_null(to->description, rxkb_option_get_description(o)))
473 cmp_option_groups(struct test_option_group *tg, struct rxkb_option_group *g,
476 struct rxkb_option *o;
477 struct test_option *to;
482 if (!streq(tg->name, rxkb_option_group_get_name(g)))
485 if (!streq_null(tg->description, rxkb_option_group_get_description(g)))
488 if (tg->allow_multiple_selection != rxkb_option_group_allows_multiple(g))
492 o = rxkb_option_first(g);
494 while (o && to->name) {
495 if (!cmp_options(to, o))
498 o = rxkb_option_next(o);
501 if (cmp == CMP_EXACT && (o || to->name))
508 test_load_basic(void)
510 struct test_model system_models[] = {
515 struct test_layout system_layouts[] = {
520 struct test_option_group system_groups[] = {
522 { {"grp1:1"}, {"grp1:2"} } },
523 {"grp2", NULL, false,
524 { {"grp2:1"}, {"grp2:2"} } },
527 struct rxkb_context *ctx;
529 ctx = test_setup_context(system_models, NULL,
530 system_layouts, NULL,
531 system_groups, NULL);
533 assert(find_models(ctx, "m1", "m2", NULL));
534 assert(find_layouts(ctx, "l1", NO_VARIANT,
536 assert(find_options(ctx, "grp1", "grp1:1",
539 "grp2", "grp2:2", NULL));
540 rxkb_context_unref(ctx);
546 struct test_model system_models[] = {
547 {"m1", "vendor1", "desc1"},
548 {"m2", "vendor2", "desc2"},
551 struct test_layout system_layouts[] = {
552 {"l1", NO_VARIANT, "lbrief1", "ldesc1"},
553 {"l1", "v1", "vbrief1", "vdesc1"},
556 struct test_option_group system_groups[] = {
557 {"grp1", "gdesc1", true,
558 { {"grp1:1", "odesc11"}, {"grp1:2", "odesc12"} } },
559 {"grp2", "gdesc2", false,
560 { {"grp2:1", "odesc21"}, {"grp2:2", "odesc22"} } },
563 struct rxkb_context *ctx;
564 struct rxkb_model *m;
565 struct rxkb_layout *l;
566 struct rxkb_option_group *g;
568 ctx = test_setup_context(system_models, NULL,
569 system_layouts, NULL,
570 system_groups, NULL);
572 m = fetch_model(ctx, "m1");
573 assert(cmp_models(&system_models[0], m));
576 m = fetch_model(ctx, "m2");
577 assert(cmp_models(&system_models[1], m));
580 l = fetch_layout(ctx, "l1", NO_VARIANT);
581 assert(cmp_layouts(&system_layouts[0], l));
582 rxkb_layout_unref(l);
584 l = fetch_layout(ctx, "l1", "v1");
585 assert(cmp_layouts(&system_layouts[1], l));
586 rxkb_layout_unref(l);
588 g = fetch_option_group(ctx, "grp1");
589 assert(cmp_option_groups(&system_groups[0], g, CMP_EXACT));
590 rxkb_option_group_unref(g);
592 g = fetch_option_group(ctx, "grp2");
593 assert(cmp_option_groups(&system_groups[1], g, CMP_EXACT));
594 rxkb_option_group_unref(g);
596 rxkb_context_unref(ctx);
600 test_popularity(void)
602 struct test_layout system_layouts[] = {
607 struct rxkb_context *ctx;
608 struct rxkb_layout *l;
609 const char *ruleset = "xkbtests.extras";
612 dir = test_create_rules(ruleset, NULL, system_layouts, NULL);
613 ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES |
614 RXKB_CONTEXT_LOAD_EXOTIC_RULES);
616 assert(rxkb_context_include_path_append(ctx, dir));
617 /* Hack: rulest above generates xkbtests.extras.xml, loading "xkbtests"
618 * means the extras file counts as exotic */
619 assert(rxkb_context_parse(ctx, "xkbtests"));
621 l = fetch_layout(ctx, "l1", NO_VARIANT);
622 assert(rxkb_layout_get_popularity(l) == RXKB_POPULARITY_EXOTIC);
623 rxkb_layout_unref(l);
625 l = fetch_layout(ctx, "l1", "v1");
626 assert(rxkb_layout_get_popularity(l) == RXKB_POPULARITY_EXOTIC);
627 rxkb_layout_unref(l);
629 test_remove_rules(dir, ruleset);
630 rxkb_context_unref(ctx);
635 test_load_merge(void)
637 struct test_model system_models[] = {
638 {"m1", "vendor1", "desc1"},
639 {"m2", "vendor2", "desc2"},
642 struct test_model user_models[] = {
643 {"m3", "vendor3", "desc3"},
644 {"m4", "vendor4", "desc4"},
647 struct test_layout system_layouts[] = {
648 {"l1", NO_VARIANT, "lbrief1", "ldesc1"},
649 {"l1", "v1", "vbrief1", "vdesc1"},
652 struct test_layout user_layouts[] = {
653 {"l2", NO_VARIANT, "lbrief2", "ldesc2"},
654 {"l2", "v2", "vbrief2", "vdesc2"},
657 struct test_option_group system_groups[] = {
659 { {"grp1:1"}, {"grp1:2"} } },
660 {"grp2", NULL, false,
661 { {"grp2:1"}, {"grp2:2"} } },
664 struct test_option_group user_groups[] = {
666 { {"grp3:1"}, {"grp3:2"} } },
667 {"grp4", NULL, false,
668 { {"grp4:1"}, {"grp4:2"} } },
671 struct rxkb_context *ctx;
672 struct rxkb_model *m;
673 struct rxkb_layout *l;
674 struct rxkb_option_group *g;
676 ctx = test_setup_context(system_models, user_models,
677 system_layouts, user_layouts,
678 system_groups, user_groups);
680 assert(find_models(ctx, "m1", "m2", "m3", "m4", NULL));
681 assert(find_layouts(ctx, "l1", NO_VARIANT,
686 m = fetch_model(ctx, "m1");
687 assert(cmp_models(&system_models[0], m));
690 m = fetch_model(ctx, "m2");
691 assert(cmp_models(&system_models[1], m));
694 m = fetch_model(ctx, "m3");
695 assert(cmp_models(&user_models[0], m));
698 m = fetch_model(ctx, "m4");
699 assert(cmp_models(&user_models[1], m));
702 l = fetch_layout(ctx, "l1", NO_VARIANT);
703 assert(cmp_layouts(&system_layouts[0], l));
704 rxkb_layout_unref(l);
706 l = fetch_layout(ctx, "l1", "v1");
707 assert(cmp_layouts(&system_layouts[1], l));
708 rxkb_layout_unref(l);
710 l = fetch_layout(ctx, "l2", NO_VARIANT);
711 assert(cmp_layouts(&user_layouts[0], l));
712 rxkb_layout_unref(l);
714 l = fetch_layout(ctx, "l2", "v2");
715 assert(cmp_layouts(&user_layouts[1], l));
716 rxkb_layout_unref(l);
718 g = fetch_option_group(ctx, "grp1");
719 assert(cmp_option_groups(&system_groups[0], g, CMP_EXACT));
720 rxkb_option_group_unref(g);
722 g = fetch_option_group(ctx, "grp2");
723 assert(cmp_option_groups(&system_groups[1], g, CMP_EXACT));
724 rxkb_option_group_unref(g);
726 g = fetch_option_group(ctx, "grp3");
727 assert(cmp_option_groups(&user_groups[0], g, CMP_EXACT));
728 rxkb_option_group_unref(g);
730 g = fetch_option_group(ctx, "grp4");
731 assert(cmp_option_groups(&user_groups[1], g, CMP_EXACT));
732 rxkb_option_group_unref(g);
734 rxkb_context_unref(ctx);
738 test_load_merge_no_overwrite(void)
740 struct test_model system_models[] = {
741 {"m1", "vendor1", "desc1"},
742 {"m2", "vendor2", "desc2"},
745 struct test_model user_models[] = {
746 {"m1", "vendor3", "desc3"}, /* must not overwrite */
747 {"m4", "vendor4", "desc4"},
750 struct test_layout system_layouts[] = {
751 {"l1", NO_VARIANT, "lbrief1", "ldesc1"},
752 {"l1", "v1", "vbrief1", "vdesc1"},
755 struct test_layout user_layouts[] = {
756 {"l2", NO_VARIANT, "lbrief2", "ldesc2"},
757 {"l2", "v2", "vbrief2", "vdesc2"},
758 {"l1", NO_VARIANT, "lbrief3", "ldesc3"}, /* must not overwrite */
759 {"l1", "v2", "vbrief3", "vdesc3"}, /* must not overwrite */
762 struct test_option_group system_groups[] = {
763 {"grp1", "gdesc1", true,
764 { {"grp1:1", "odesc11"}, {"grp1:2", "odesc12"} } },
765 {"grp2", "gdesc2", false,
766 { {"grp2:1", "odesc21"}, {"grp2:2", "odesc22"} } },
769 struct test_option_group user_groups[] = {
770 {"grp1", "XXXXX", false, /* must not overwrite */
771 { {"grp1:1", "YYYYYYY"}, /* must not overwrite */
772 {"grp1:3", "ZZZZZZ"} } }, /* append */
773 {"grp4", "gdesc4", false,
774 { {"grp4:1", "odesc41"}, {"grp4:2", "odesc42"} } },
777 struct rxkb_context *ctx;
778 struct rxkb_model *m;
779 struct rxkb_layout *l;
780 struct rxkb_option_group *g;
782 ctx = test_setup_context(system_models, user_models,
783 system_layouts, user_layouts,
784 system_groups, user_groups);
786 m = fetch_model(ctx, "m1");
787 assert(cmp_models(&system_models[0], m));
790 l = fetch_layout(ctx, "l1", NO_VARIANT);
791 assert(cmp_layouts(&system_layouts[0], l));
792 rxkb_layout_unref(l);
794 l = fetch_layout(ctx, "l1", "v1");
795 assert(cmp_layouts(&system_layouts[1], l));
796 rxkb_layout_unref(l);
798 assert(find_option(ctx, "grp1", "grp1:3"));
799 g = fetch_option_group(ctx, "grp1");
800 assert(cmp_option_groups(&system_groups[0], g, CMP_MATCHING_ONLY));
801 rxkb_option_group_unref(g);
803 rxkb_context_unref(ctx);
807 test_no_include_paths(void)
809 struct rxkb_context *ctx;
811 ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES);
813 assert(!rxkb_context_parse_default_ruleset(ctx));
815 rxkb_context_unref(ctx);
819 test_invalid_include(void)
821 struct rxkb_context *ctx;
823 ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES);
825 assert(!rxkb_context_include_path_append(ctx, "/foo/bar/baz/bat"));
826 assert(!rxkb_context_parse_default_ruleset(ctx));
828 rxkb_context_unref(ctx);
834 test_no_include_paths();
835 test_invalid_include();
839 test_load_merge_no_overwrite();