1 /*
2  * Copyright © 2020 Red Hat, Inc.
3  *
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:
10  *
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
13  * Software.
14  *
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.
22  */
23 
24 #include "config.h"
25 
26 #include <assert.h>
27 #if HAVE_UNISTD_H
28 #include <unistd.h>
29 #endif
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <sys/stat.h>
33 #include <sys/types.h>
34 
35 #include "xkbcommon/xkbregistry.h"
36 
37 #include "utils.h"
38 
39 #define NO_VARIANT NULL
40 
41 enum {
42     MODEL = 78,
43     LAYOUT,
44     VARIANT,
45     OPTION,
46 };
47 
48 struct test_model {
49     const char *name; /* required */
50     const char *vendor;
51     const char *description;
52 };
53 
54 struct test_layout {
55     const char *name; /* required */
56     const char *variant;
57     const char *brief;
58     const char *description;
59 };
60 
61 struct test_option {
62     const char *name;
63     const char *description;
64 };
65 
66 struct test_option_group {
67     const char *name;
68     const char *description;
69     bool allow_multiple_selection;
70 
71     struct test_option options[10];
72 };
73 
74 static void
fprint_config_item(FILE * fp,const char * name,const char * vendor,const char * brief,const char * description)75 fprint_config_item(FILE *fp,
76                    const char *name,
77                    const char *vendor,
78                    const char *brief,
79                    const char *description)
80 {
81     fprintf(fp, "  <configItem>\n"
82                 "    <name>%s</name>\n", name);
83     if (brief)
84         fprintf(fp, "    <shortDescription>%s</shortDescription>\n", brief);
85     if (description)
86         fprintf(fp, "    <description>%s</description>\n", description);
87     if (vendor)
88         fprintf(fp, "    <vendor>%s</vendor>\n", vendor);
89     fprintf(fp, "  </configItem>\n");
90 }
91 
92 /**
93  * Create a directory populated with a rules/<ruleset>.xml that contains the
94  * given items.
95  *
96  * @return the XKB base directory
97  */
98 static char *
test_create_rules(const char * ruleset,const struct test_model * test_models,const struct test_layout * test_layouts,const struct test_option_group * test_groups)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)
103 {
104     static int iteration;
105     char *tmpdir;
106     char buf[PATH_MAX];
107     int rc;
108     FILE *fp;
109 
110     tmpdir = asprintf_safe("/tmp/%s.%d.XXXXXX", ruleset, iteration++);
111     assert(tmpdir);
112     assert(mkdtemp(tmpdir) == tmpdir);
113 
114     rc = snprintf_safe(buf, sizeof(buf), "%s/rules", tmpdir);
115     assert(rc);
116     rc = mkdir(buf, 0777);
117     assert(rc == 0);
118     rc = snprintf_safe(buf, sizeof(buf), "%s/rules/%s.xml", tmpdir, ruleset);
119     assert(rc);
120 
121     fp = fopen(buf, "w");
122     assert(fp);
123 
124     fprintf(fp,
125             "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
126             "<!DOCTYPE xkbConfigRegistry SYSTEM \"xkb.dtd\">\n"
127             "<xkbConfigRegistry version=\"1.1\">\n");
128 
129     if (test_models) {
130         fprintf(fp, "<modelList>\n");
131 
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");
136         }
137         fprintf(fp, "</modelList>\n");
138     }
139 
140     if (test_layouts) {
141         const struct test_layout *l, *next;
142 
143         fprintf(fp, "<layoutList>\n");
144 
145         l = test_layouts;
146         next = l + 1;
147 
148         assert(l->variant == NULL);
149 
150         while (l->name) {
151             fprintf(fp, "<layout>\n");
152             fprint_config_item(fp, l->name, NULL, l->brief, l->description);
153 
154             if (next->name && streq(next->name, l->name)) {
155                 fprintf(fp, "<variantList>\n");
156                 do {
157                     fprintf(fp, "<variant>\n");
158                     fprint_config_item(fp, next->variant, NULL, next->brief,
159                                        next->description);
160                     fprintf(fp, "</variant>\n");
161                     l = next;
162                     next++;
163                 } while (next->name && streq(next->name, l->name));
164                 fprintf(fp, "</variantList>\n");
165             }
166             fprintf(fp, "</layout>\n");
167             l++;
168         }
169         fprintf(fp, "</layoutList>\n");
170     }
171 
172     if (test_groups) {
173         fprintf(fp, "<optionList>\n");
174 
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");
183             }
184             fprintf(fp, "</group>\n");
185         }
186         fprintf(fp, "</optionList>\n");
187     }
188 
189     fprintf(fp, "</xkbConfigRegistry>\n");
190     fclose(fp);
191 
192     return tmpdir;
193 }
194 
195 static void
test_remove_rules(char * basedir,const char * ruleset)196 test_remove_rules(char *basedir, const char *ruleset)
197 {
198     char path[PATH_MAX];
199     int rc;
200 
201     rc = snprintf_safe(path, sizeof(path), "%s/rules/%s.xml", basedir,
202                        ruleset);
203     assert(rc);
204     unlink(path);
205     rc = snprintf_safe(path, sizeof(path), "%s/xkb/rules", basedir);
206     assert(rc);
207     rmdir(path);
208     rmdir(basedir);
209     free(basedir);
210 }
211 
212 static struct rxkb_context *
test_setup_context_for(const char * ruleset,struct test_model * system_models,struct test_model * user_models,struct test_layout * system_layouts,struct test_layout * user_layouts,struct test_option_group * system_groups,struct test_option_group * user_groups)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)
220 {
221     char *sysdir = NULL, *userdir = NULL;
222     struct rxkb_context *ctx;
223 
224     sysdir = test_create_rules(ruleset, system_models, system_layouts,
225                                system_groups);
226     if (user_models || user_layouts || user_groups)
227         userdir = test_create_rules(ruleset, user_models, user_layouts,
228                                     user_groups);
229 
230     ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES);
231     assert(ctx);
232     if (userdir)
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));
236 
237     test_remove_rules(sysdir, ruleset);
238     if (userdir)
239         test_remove_rules(userdir, ruleset);
240 
241     return ctx;
242 }
243 
244 static struct rxkb_context *
test_setup_context(struct test_model * system_models,struct test_model * user_models,struct test_layout * system_layouts,struct test_layout * user_layouts,struct test_option_group * system_groups,struct test_option_group * user_groups)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)
251 {
252     const char *ruleset = "xkbtests";
253     return test_setup_context_for(ruleset, system_models,
254                                   user_models, system_layouts,
255                                   user_layouts, system_groups,
256                                   user_groups);
257 }
258 
259 static struct rxkb_model *
fetch_model(struct rxkb_context * ctx,const char * model)260 fetch_model(struct rxkb_context *ctx, const char *model)
261 {
262     struct rxkb_model *m = rxkb_model_first(ctx);
263     while (m) {
264         if (streq(rxkb_model_get_name(m), model))
265             return rxkb_model_ref(m);
266         m = rxkb_model_next(m);
267     }
268     return NULL;
269 }
270 
271 static bool
find_model(struct rxkb_context * ctx,const char * model)272 find_model(struct rxkb_context *ctx, const char *model)
273 {
274     struct rxkb_model *m = fetch_model(ctx, model);
275     rxkb_model_unref(m);
276     return m != NULL;
277 }
278 
279 static bool
find_models(struct rxkb_context * ctx,...)280 find_models(struct rxkb_context *ctx, ...)
281 {
282     va_list args;
283     const char *name;
284     int idx = 0;
285 
286     va_start(args, ctx);
287     name = va_arg(args, const char *);
288     while(name) {
289         assert(++idx < 20); /* safety guard */
290         if (!find_model(ctx, name))
291             return false;
292         name = va_arg(args, const char *);
293     };
294 
295     va_end(args);
296     return true;
297 }
298 
299 static struct rxkb_layout *
fetch_layout(struct rxkb_context * ctx,const char * layout,const char * variant)300 fetch_layout(struct rxkb_context *ctx, const char *layout, const char *variant)
301 {
302     struct rxkb_layout *l = rxkb_layout_first(ctx);
303     while (l) {
304         const char *v = rxkb_layout_get_variant(l);
305 
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);
311     }
312     return NULL;
313 }
314 
315 static bool
find_layout(struct rxkb_context * ctx,const char * layout,const char * variant)316 find_layout(struct rxkb_context *ctx, const char *layout, const char *variant)
317 {
318     struct rxkb_layout *l = fetch_layout(ctx, layout, variant);
319     rxkb_layout_unref(l);
320     return l != NULL;
321 }
322 
323 static bool
find_layouts(struct rxkb_context * ctx,...)324 find_layouts(struct rxkb_context *ctx, ...)
325 {
326     va_list args;
327     const char *name, *variant;
328     int idx = 0;
329 
330     va_start(args, ctx);
331     name = va_arg(args, const char *);
332     variant = va_arg(args, const char *);
333     while(name) {
334         assert(++idx < 20); /* safety guard */
335         if (!find_layout(ctx, name, variant))
336             return false;
337         name = va_arg(args, const char *);
338         if (name)
339             variant = va_arg(args, const char *);
340     };
341 
342     va_end(args);
343     return true;
344 }
345 
346 static struct rxkb_option_group *
fetch_option_group(struct rxkb_context * ctx,const char * grp)347 fetch_option_group(struct rxkb_context *ctx, const char *grp)
348 {
349     struct rxkb_option_group *g = rxkb_option_group_first(ctx);
350     while (g) {
351         if (streq(grp, rxkb_option_group_get_name(g)))
352             return rxkb_option_group_ref(g);
353         g = rxkb_option_group_next(g);
354     }
355     return NULL;
356 }
357 
358 static inline bool
find_option_group(struct rxkb_context * ctx,const char * grp)359 find_option_group(struct rxkb_context *ctx, const char *grp)
360 {
361     struct rxkb_option_group *g = fetch_option_group(ctx, grp);
362     rxkb_option_group_unref(g);
363     return g != NULL;
364 }
365 
366 static struct rxkb_option *
fetch_option(struct rxkb_context * ctx,const char * grp,const char * opt)367 fetch_option(struct rxkb_context *ctx, const char *grp, const char *opt)
368 {
369     struct rxkb_option_group *g = rxkb_option_group_first(ctx);
370     while (g) {
371         if (streq(grp, rxkb_option_group_get_name(g))) {
372             struct rxkb_option *o = rxkb_option_first(g);
373 
374             while (o) {
375                 if (streq(opt, rxkb_option_get_name(o)))
376                     return rxkb_option_ref(o);
377                 o = rxkb_option_next(o);
378             }
379         }
380         g = rxkb_option_group_next(g);
381     }
382     return NULL;
383 }
384 
385 static bool
find_option(struct rxkb_context * ctx,const char * grp,const char * opt)386 find_option(struct rxkb_context *ctx, const char *grp, const char *opt)
387 {
388     struct rxkb_option *o = fetch_option(ctx, grp, opt);
389     rxkb_option_unref(o);
390     return o != NULL;
391 }
392 
393 static bool
find_options(struct rxkb_context * ctx,...)394 find_options(struct rxkb_context *ctx, ...)
395 {
396     va_list args;
397     const char *grp, *opt;
398     int idx = 0;
399 
400     va_start(args, ctx);
401     grp = va_arg(args, const char *);
402     opt = va_arg(args, const char *);
403     while(grp) {
404         assert(++idx < 20); /* safety guard */
405         if (!find_option(ctx, grp, opt))
406             return false;
407         grp = va_arg(args, const char *);
408         if (grp)
409             opt = va_arg(args, const char *);
410     };
411 
412     va_end(args);
413     return true;
414 }
415 
416 static bool
cmp_models(struct test_model * tm,struct rxkb_model * m)417 cmp_models(struct test_model *tm, struct rxkb_model *m)
418 {
419     if (!tm || !m)
420         return false;
421 
422     if (!streq(tm->name, rxkb_model_get_name(m)))
423         return false;
424 
425     if (!streq_null(tm->vendor, rxkb_model_get_vendor(m)))
426         return false;
427 
428     if (!streq_null(tm->description, rxkb_model_get_description(m)))
429         return false;
430 
431     return true;
432 }
433 
434 static bool
cmp_layouts(struct test_layout * tl,struct rxkb_layout * l)435 cmp_layouts(struct test_layout *tl, struct rxkb_layout *l)
436 {
437     if (!tl || !l)
438         return false;
439 
440     if (!streq(tl->name, rxkb_layout_get_name(l)))
441         return false;
442 
443     if (!streq_null(tl->variant, rxkb_layout_get_variant(l)))
444         return false;
445 
446     if (!streq_null(tl->brief, rxkb_layout_get_brief(l)))
447         return false;
448 
449     if (!streq_null(tl->description, rxkb_layout_get_description(l)))
450         return false;
451 
452     return true;
453 }
454 
455 static bool
cmp_options(struct test_option * to,struct rxkb_option * o)456 cmp_options(struct test_option *to, struct rxkb_option *o)
457 {
458     if (!to || !o)
459         return false;
460 
461     if (!streq(to->name, rxkb_option_get_name(o)))
462         return false;
463 
464     if (!streq_null(to->description, rxkb_option_get_description(o)))
465         return false;
466 
467     return true;
468 }
469 
470 enum cmp_type {
471     CMP_EXACT,
472     CMP_MATCHING_ONLY,
473 };
474 
475 static bool
cmp_option_groups(struct test_option_group * tg,struct rxkb_option_group * g,enum cmp_type cmp)476 cmp_option_groups(struct test_option_group *tg, struct rxkb_option_group *g,
477                   enum cmp_type cmp)
478 {
479     struct rxkb_option *o;
480     struct test_option *to;
481 
482     if (!tg || !g)
483         return false;
484 
485     if (!streq(tg->name, rxkb_option_group_get_name(g)))
486         return false;
487 
488     if (!streq_null(tg->description, rxkb_option_group_get_description(g)))
489         return false;
490 
491     if (tg->allow_multiple_selection != rxkb_option_group_allows_multiple(g))
492         return false;
493 
494     to = tg->options;
495     o = rxkb_option_first(g);
496 
497     while (o && to->name) {
498         if (!cmp_options(to, o))
499             return false;
500         to++;
501         o = rxkb_option_next(o);
502     }
503 
504     if (cmp == CMP_EXACT && (o || to->name))
505         return false;
506 
507     return true;
508 }
509 
510 static void
test_load_basic(void)511 test_load_basic(void)
512 {
513     struct test_model system_models[] =  {
514         {"m1"},
515         {"m2"},
516         {NULL},
517     };
518     struct test_layout system_layouts[] =  {
519         {"l1"},
520         {"l1", "v1"},
521         {NULL},
522     };
523     struct test_option_group system_groups[] = {
524         {"grp1", NULL, true,
525           { {"grp1:1"}, {"grp1:2"} } },
526         {"grp2", NULL, false,
527           { {"grp2:1"}, {"grp2:2"} } },
528         { NULL },
529     };
530     struct rxkb_context *ctx;
531 
532     ctx = test_setup_context(system_models, NULL,
533                              system_layouts, NULL,
534                              system_groups, NULL);
535 
536     assert(find_models(ctx, "m1", "m2", NULL));
537     assert(find_layouts(ctx, "l1", NO_VARIANT,
538                              "l1", "v1", NULL));
539     assert(find_options(ctx, "grp1", "grp1:1",
540                              "grp1", "grp1:2",
541                              "grp2", "grp2:1",
542                              "grp2", "grp2:2", NULL));
543     rxkb_context_unref(ctx);
544 }
545 
546 static void
test_load_full(void)547 test_load_full(void)
548 {
549     struct test_model system_models[] =  {
550         {"m1", "vendor1", "desc1"},
551         {"m2", "vendor2", "desc2"},
552         {NULL},
553     };
554     struct test_layout system_layouts[] =  {
555         {"l1", NO_VARIANT, "lbrief1", "ldesc1"},
556         {"l1", "v1", "vbrief1", "vdesc1"},
557         {NULL},
558     };
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"} } },
564         { NULL },
565     };
566     struct rxkb_context *ctx;
567     struct rxkb_model *m;
568     struct rxkb_layout *l;
569     struct rxkb_option_group *g;
570 
571     ctx = test_setup_context(system_models, NULL,
572                              system_layouts, NULL,
573                              system_groups, NULL);
574 
575     m = fetch_model(ctx, "m1");
576     assert(cmp_models(&system_models[0], m));
577     rxkb_model_unref(m);
578 
579     m = fetch_model(ctx, "m2");
580     assert(cmp_models(&system_models[1], m));
581     rxkb_model_unref(m);
582 
583     l = fetch_layout(ctx, "l1", NO_VARIANT);
584     assert(cmp_layouts(&system_layouts[0], l));
585     rxkb_layout_unref(l);
586 
587     l = fetch_layout(ctx, "l1", "v1");
588     assert(cmp_layouts(&system_layouts[1], l));
589     rxkb_layout_unref(l);
590 
591     g = fetch_option_group(ctx, "grp1");
592     assert(cmp_option_groups(&system_groups[0], g, CMP_EXACT));
593     rxkb_option_group_unref(g);
594 
595     g = fetch_option_group(ctx, "grp2");
596     assert(cmp_option_groups(&system_groups[1], g, CMP_EXACT));
597     rxkb_option_group_unref(g);
598 
599     rxkb_context_unref(ctx);
600 }
601 
602 static void
test_popularity(void)603 test_popularity(void)
604 {
605     struct test_layout system_layouts[] =  {
606         {"l1", NO_VARIANT },
607         {"l1", "v1" },
608         {NULL},
609     };
610     struct rxkb_context *ctx;
611     struct rxkb_layout *l;
612     const char *ruleset = "xkbtests.extras";
613     char *dir = NULL;
614 
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);
618     assert(ctx);
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"));
623 
624     l = fetch_layout(ctx, "l1", NO_VARIANT);
625     assert(rxkb_layout_get_popularity(l) == RXKB_POPULARITY_EXOTIC);
626     rxkb_layout_unref(l);
627 
628     l = fetch_layout(ctx, "l1", "v1");
629     assert(rxkb_layout_get_popularity(l) == RXKB_POPULARITY_EXOTIC);
630     rxkb_layout_unref(l);
631 
632     test_remove_rules(dir, ruleset);
633     rxkb_context_unref(ctx);
634 }
635 
636 
637 static void
test_load_merge(void)638 test_load_merge(void)
639 {
640     struct test_model system_models[] =  {
641         {"m1", "vendor1", "desc1"},
642         {"m2", "vendor2", "desc2"},
643         {NULL},
644     };
645     struct test_model user_models[] =  {
646         {"m3", "vendor3", "desc3"},
647         {"m4", "vendor4", "desc4"},
648         {NULL},
649     };
650     struct test_layout system_layouts[] =  {
651         {"l1", NO_VARIANT, "lbrief1", "ldesc1"},
652         {"l1", "v1", "vbrief1", "vdesc1"},
653         {NULL},
654     };
655     struct test_layout user_layouts[] =  {
656         {"l2", NO_VARIANT, "lbrief2", "ldesc2"},
657         {"l2", "v2", "vbrief2", "vdesc2"},
658         {NULL},
659     };
660     struct test_option_group system_groups[] = {
661         {"grp1", NULL, true,
662           { {"grp1:1"}, {"grp1:2"} } },
663         {"grp2", NULL, false,
664           { {"grp2:1"}, {"grp2:2"} } },
665         { NULL },
666     };
667     struct test_option_group user_groups[] = {
668         {"grp3", NULL, true,
669           { {"grp3:1"}, {"grp3:2"} } },
670         {"grp4", NULL, false,
671           { {"grp4:1"}, {"grp4:2"} } },
672         { NULL },
673     };
674     struct rxkb_context *ctx;
675     struct rxkb_model *m;
676     struct rxkb_layout *l;
677     struct rxkb_option_group *g;
678 
679     ctx = test_setup_context(system_models, user_models,
680                              system_layouts, user_layouts,
681                              system_groups, user_groups);
682 
683     assert(find_models(ctx, "m1", "m2", "m3", "m4", NULL));
684     assert(find_layouts(ctx, "l1", NO_VARIANT,
685                              "l1", "v1",
686                              "l2", NO_VARIANT,
687                              "l2", "v2", NULL));
688 
689     m = fetch_model(ctx, "m1");
690     assert(cmp_models(&system_models[0], m));
691     rxkb_model_unref(m);
692 
693     m = fetch_model(ctx, "m2");
694     assert(cmp_models(&system_models[1], m));
695     rxkb_model_unref(m);
696 
697     m = fetch_model(ctx, "m3");
698     assert(cmp_models(&user_models[0], m));
699     rxkb_model_unref(m);
700 
701     m = fetch_model(ctx, "m4");
702     assert(cmp_models(&user_models[1], m));
703     rxkb_model_unref(m);
704 
705     l = fetch_layout(ctx, "l1", NO_VARIANT);
706     assert(cmp_layouts(&system_layouts[0], l));
707     rxkb_layout_unref(l);
708 
709     l = fetch_layout(ctx, "l1", "v1");
710     assert(cmp_layouts(&system_layouts[1], l));
711     rxkb_layout_unref(l);
712 
713     l = fetch_layout(ctx, "l2", NO_VARIANT);
714     assert(cmp_layouts(&user_layouts[0], l));
715     rxkb_layout_unref(l);
716 
717     l = fetch_layout(ctx, "l2", "v2");
718     assert(cmp_layouts(&user_layouts[1], l));
719     rxkb_layout_unref(l);
720 
721     g = fetch_option_group(ctx, "grp1");
722     assert(cmp_option_groups(&system_groups[0], g, CMP_EXACT));
723     rxkb_option_group_unref(g);
724 
725     g = fetch_option_group(ctx, "grp2");
726     assert(cmp_option_groups(&system_groups[1], g, CMP_EXACT));
727     rxkb_option_group_unref(g);
728 
729     g = fetch_option_group(ctx, "grp3");
730     assert(cmp_option_groups(&user_groups[0], g, CMP_EXACT));
731     rxkb_option_group_unref(g);
732 
733     g = fetch_option_group(ctx, "grp4");
734     assert(cmp_option_groups(&user_groups[1], g, CMP_EXACT));
735     rxkb_option_group_unref(g);
736 
737     rxkb_context_unref(ctx);
738 }
739 
740 static void
test_load_merge_no_overwrite(void)741 test_load_merge_no_overwrite(void)
742 {
743     struct test_model system_models[] =  {
744         {"m1", "vendor1", "desc1"},
745         {"m2", "vendor2", "desc2"},
746         {NULL},
747     };
748     struct test_model user_models[] =  {
749         {"m1", "vendor3", "desc3"}, /* must not overwrite */
750         {"m4", "vendor4", "desc4"},
751         {NULL},
752     };
753     struct test_layout system_layouts[] =  {
754         {"l1", NO_VARIANT, "lbrief1", "ldesc1"},
755         {"l1", "v1", "vbrief1", "vdesc1"},
756         {NULL},
757     };
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 */
763         {NULL},
764     };
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"} } },
770         { NULL },
771     };
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"} } },
778         { NULL },
779     };
780     struct rxkb_context *ctx;
781     struct rxkb_model *m;
782     struct rxkb_layout *l;
783     struct rxkb_option_group *g;
784 
785     ctx = test_setup_context(system_models, user_models,
786                              system_layouts, user_layouts,
787                              system_groups, user_groups);
788 
789     m = fetch_model(ctx, "m1");
790     assert(cmp_models(&system_models[0], m));
791     rxkb_model_unref(m);
792 
793     l = fetch_layout(ctx, "l1", NO_VARIANT);
794     assert(cmp_layouts(&system_layouts[0], l));
795     rxkb_layout_unref(l);
796 
797     l = fetch_layout(ctx, "l1", "v1");
798     assert(cmp_layouts(&system_layouts[1], l));
799     rxkb_layout_unref(l);
800 
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);
805 
806     rxkb_context_unref(ctx);
807 }
808 
809 static void
test_no_include_paths(void)810 test_no_include_paths(void)
811 {
812     struct rxkb_context *ctx;
813 
814     ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES);
815     assert(ctx);
816     assert(!rxkb_context_parse_default_ruleset(ctx));
817 
818     rxkb_context_unref(ctx);
819 }
820 
821 static void
test_invalid_include(void)822 test_invalid_include(void)
823 {
824     struct rxkb_context *ctx;
825 
826     ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES);
827     assert(ctx);
828     assert(!rxkb_context_include_path_append(ctx, "/foo/bar/baz/bat"));
829     assert(!rxkb_context_parse_default_ruleset(ctx));
830 
831     rxkb_context_unref(ctx);
832 }
833 
834 int
main(void)835 main(void)
836 {
837     test_no_include_paths();
838     test_invalid_include();
839     test_load_basic();
840     test_load_full();
841     test_load_merge();
842     test_load_merge_no_overwrite();
843     test_popularity();
844 
845     return 0;
846 }
847