Imported Upstream version 3.3.3
[debian/amanda] / common-src / match-test.c
1 /*
2  * Copyright (c) 2010-2012 Zmanda, Inc.  All Rights Reserved.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
17  *
18  * Contact information: Zmanda Inc, 465 S. Mathilda Ave., Suite 300
19  * Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
20  */
21
22 #include "amanda.h"
23 #include "testutils.h"
24 #include "match.h"
25
26 /*
27  * Tests
28  */
29
30 static gboolean
31 test_validate_regexp(void)
32 {
33     gboolean ok = TRUE;
34     struct {
35         char *regexp;
36         gboolean should_validate;
37     } tests[] = {
38         { ".*", TRUE },
39         { "*", FALSE },
40         { "[abc", FALSE },
41         { "(abc", FALSE },
42         { "{1,}", FALSE },
43         { NULL, FALSE },
44     }, *t;
45
46     for (t = tests; t->regexp; t++) {
47         char *validated_err = validate_regexp(t->regexp);
48         if (!validated_err != !!t->should_validate) {
49             ok = FALSE;
50             if (t->should_validate) {
51                 g_fprintf(stderr, "should have validated regular expr %s: %s\n",
52                         t->regexp, validated_err);
53             } else {
54                 g_fprintf(stderr, "unexpectedly validated regular expr %s\n",
55                         t->regexp);
56             }
57         }
58     }
59
60     return ok;
61 }
62
63 static gboolean
64 test_match(void)
65 {
66     gboolean ok = TRUE;
67     struct {
68         char *expr, *str;
69         gboolean should_match, should_match_no_newline;
70     } tests[] = {
71         /* literal, unanchored matching */
72         { "a", "a", TRUE, TRUE },
73         { "a", "A", FALSE, FALSE },
74         { "a", "ab", TRUE, TRUE },
75         { "a", "ba", TRUE, TRUE },
76         { "a", "bab", TRUE, TRUE },
77
78         /* dot */
79         { ".", "", FALSE, FALSE },
80         { ".", "a", TRUE, TRUE },
81         { "..", "a", FALSE, FALSE },
82         { "..", "bc", TRUE, TRUE },
83
84         /* brackets */
85         { "[abc]", "xbx", TRUE, TRUE },
86         { "[abc]", "xyz", FALSE, FALSE },
87         { "[^abc]", "cba", FALSE, FALSE },
88         { "[^abc]", "xyz", TRUE, TRUE },
89         { "[a-c]", "b", TRUE, TRUE },
90         { "[^a-c]", "-", TRUE, TRUE },
91         { "[1-9-]", "-", TRUE, TRUE },
92         { "[ab\\-cd]", "-", FALSE, FALSE }, /* NOTE! */
93
94         /* anchors */
95         { "^xy", "xyz", TRUE, TRUE },
96         { "^xy", "wxyz", FALSE, FALSE },
97         { "yz$", "xyz", TRUE, TRUE },
98         { "yz$", "yza", FALSE, FALSE },
99         { "^123$", "123", TRUE, TRUE },
100         { "^123$", "0123", FALSE, FALSE },
101         { "^123$", "1234", FALSE, FALSE },
102
103         /* capture groups */
104         { "([a-c])([x-y])", "pqaxyr", TRUE, TRUE },
105         { "([a-c])([x-y])", "paqrxy", FALSE, FALSE },
106         { "([a-c])/\\1", "a/b", FALSE, FALSE },
107         { "([a-c])/\\1", "c/c", TRUE, TRUE },
108
109         /* * */
110         { ">[0-9]*<", "><", TRUE, TRUE },
111         { ">[0-9]*<", ">3<", TRUE, TRUE },
112         { ">[0-9]*<", ">34<", TRUE, TRUE },
113         { ">[0-9]*<", ">345<", TRUE, TRUE },
114         { ">[0-9]*<", ">x<", FALSE, FALSE },
115
116         /* | */
117         { ":(abc|ABC);", ":abc;", TRUE, TRUE },
118         { ":(abc|ABC);", ":ABC;", TRUE, TRUE },
119         { ":(abc|ABC);", ":abcBC;", FALSE, FALSE },
120
121         /* + */
122         { ">[0-9]+<", "><", FALSE, FALSE },
123         { ">[0-9]+<", ">3<", TRUE, TRUE },
124         { ">[0-9]+<", ">34<", TRUE, TRUE },
125         { ">[0-9]+<", ">345<", TRUE, TRUE },
126         { ">[0-9]+<", ">x<", FALSE, FALSE },
127
128         /* { .. } */
129         { ">[0-9]{0,1}<", "><", TRUE, TRUE },
130         { ">[0-9]{0,1}<", ">9<", TRUE, TRUE },
131         { ">[0-9]{0,1}<", ">98<", FALSE, FALSE },
132         { ">[0-9]{2,3}<", "><", FALSE, FALSE },
133         { ">[0-9]{2,3}<", ">5<", FALSE, FALSE },
134         { ">[0-9]{2,3}<", ">55<", TRUE, TRUE },
135         { ">[0-9]{2,3}<", ">555<", TRUE, TRUE },
136         { ">[0-9]{2,3}<", ">5555<", FALSE, FALSE },
137
138         /* quoting metacharacters */
139         { "\\\\", "\\", TRUE, TRUE },
140         { "\\,", ",", TRUE, TRUE },
141         { "\\[", "[", TRUE, TRUE },
142         { "\\*", "*", TRUE, TRUE },
143         { "\\?", "?", TRUE, TRUE },
144         { "\\+", "+", TRUE, TRUE },
145         { "\\.", ".", TRUE, TRUE },
146         { "\\|", "|", TRUE, TRUE },
147         { "\\^", "^", TRUE, TRUE },
148         { "\\$", "$", TRUE, TRUE },
149
150         /* differences between match and match_no_newline */
151         { "x.y", "x\ny", FALSE, TRUE },
152         { "x[^yz]y", "x\ny", FALSE, TRUE },
153         { "^y", "x\ny", TRUE, FALSE },
154         { "x$", "x\ny", TRUE, FALSE },
155
156         { NULL, NULL, FALSE, FALSE },
157     }, *t;
158
159     for (t = tests; t->expr; t++) {
160         gboolean matched = match(t->expr, t->str);
161         if (!!matched != !!t->should_match) {
162             ok = FALSE;
163             if (t->should_match) {
164                 g_fprintf(stderr, "%s should have matched regular expr %s\n",
165                         t->str, t->expr);
166             } else {
167                 g_fprintf(stderr, "%s unexpectedly matched regular expr %s\n",
168                         t->str, t->expr);
169             }
170         }
171
172         matched = match_no_newline(t->expr, t->str);
173         if (!!matched != !!t->should_match_no_newline) {
174             ok = FALSE;
175             if (t->should_match) {
176                 g_fprintf(stderr, "%s should have matched (no_newline) regular expr %s\n",
177                         t->str, t->expr);
178             } else {
179                 g_fprintf(stderr, "%s unexpectedly matched (no_newline) regular expr %s\n",
180                         t->str, t->expr);
181             }
182         }
183     }
184
185     return ok;
186 }
187
188 static gboolean
189 test_validate_glob(void)
190 {
191     gboolean ok = TRUE;
192     struct {
193         char *glob;
194         gboolean should_validate;
195     } tests[] = {
196         { "foo.*", TRUE },
197         { "*.txt", TRUE },
198         { "x[abc]y", TRUE },
199         { "x[!abc]y", TRUE },
200         { "[abc", FALSE },
201         { "[!abc", FALSE },
202         { "??*", TRUE },
203         { "**?", TRUE }, /* legal, but weird */
204         { "foo\\", FALSE }, /* un-escaped \ is illegal */
205         { "foo\\\\", TRUE }, /* but escaped is OK */
206         { "(){}+.^$|", TRUE }, /* funny characters OK */
207         { "/usr/bin/*", TRUE }, /* filename seps are OK */
208         { NULL, FALSE },
209     }, *t;
210
211     for (t = tests; t->glob; t++) {
212         char *validated_err = validate_glob(t->glob);
213         if (!validated_err != !!t->should_validate) {
214             ok = FALSE;
215             if (t->should_validate) {
216                 g_fprintf(stderr, "should have validated glob %s: %s\n",
217                         t->glob, validated_err);
218             } else {
219                 g_fprintf(stderr, "unexpectedly validated glob %s\n",
220                         t->glob);
221             }
222         }
223     }
224
225     return ok;
226 }
227
228 static gboolean
229 test_glob_to_regex(void)
230 {
231     gboolean ok = TRUE;
232     struct { char *glob, *regex; } tests[] = {
233         { "abc", "^abc$" },
234         { "*.txt", "^[^/]*\\.txt$" },
235         { "?.txt", "^[^/]\\.txt$" },
236         { "?*.txt", "^[^/][^/]*\\.txt$" },
237         { "foo.[tT][xX][tT]", "^foo\\.[tT][xX][tT]$" },
238         { "foo.[tT][!yY][tT]", "^foo\\.[tT][^yY][tT]$" },
239         { "foo\\\\", "^foo\\\\$" },
240         { "(){}+.^$|", "^\\(\\)\\{\\}\\+\\.\\^\\$\\|$" },
241         { "/usr/bin/*", "^/usr/bin/[^/]*$" },
242         { NULL, NULL },
243     }, *t;
244
245     for (t = tests; t->glob; t++) {
246         char *regex = glob_to_regex(t->glob);
247         if (0 != strcmp(regex, t->regex)) {
248             ok = FALSE;
249             g_fprintf(stderr, "glob_to_regex(\"%s\") returned \"%s\"; expected \"%s\"\n",
250                     t->glob, regex, t->regex);
251         }
252     }
253
254     return ok;
255 }
256
257 static gboolean
258 test_match_glob(void)
259 {
260     gboolean ok = TRUE;
261     struct {
262         char *expr, *str;
263         gboolean should_match;
264     } tests[] = {
265         /* literal, unanchored matching */
266         { "a", "a", TRUE },
267
268         { "abc", "abc", TRUE },
269         { "abc", "abcd", FALSE },
270         { "abc", "dabc", FALSE },
271         { "abc", "/usr/bin/abc", FALSE },
272
273         { "*.txt", "foo.txt", TRUE },
274         { "*.txt", ".txt", TRUE },
275         { "*.txt", "txt", FALSE },
276
277         { "?.txt", "X.txt", TRUE },
278         { "?.txt", ".txt", FALSE },
279         { "?.txt", "XY.txt", FALSE },
280
281         { "?*.txt", ".txt", FALSE },
282         { "?*.txt", "a.txt", TRUE },
283         { "?*.txt", "aa.txt", TRUE },
284         { "?*.txt", "aaa.txt", TRUE },
285
286         { "foo.[tT][xX][tT]", "foo.txt", TRUE },
287         { "foo.[tT][xX][tT]", "foo.TXt", TRUE },
288         { "foo.[tT][xX][tT]", "foo.TXT", TRUE },
289         { "foo.[tT][xX][tT]", "foo.TaT", FALSE },
290
291         { "foo.[tT][!yY][tT]", "foo.TXt", TRUE },
292         { "foo.[tT][!yY][tT]", "foo.TXT", TRUE },
293         { "foo.[tT][!yY][tT]", "foo.TyT", FALSE },
294
295         { "foo\\\\", "foo", FALSE },
296         { "foo\\\\", "foo\\", TRUE },
297         { "foo\\\\", "foo\\\\", FALSE },
298
299         { "(){}+.^$|", "(){}+.^$|", TRUE },
300
301         { "/usr/bin/*", "/usr/bin/tar", TRUE },
302         { "/usr/bin/*", "/usr/bin/local/tar", FALSE },
303         { "/usr/bin/*", "/usr/sbin/tar", FALSE },
304         { "/usr/bin/*", "/opt/usr/bin/tar", FALSE },
305
306         { "/usr?bin", "/usr/bin", FALSE },
307         { "/usr*bin", "/usr/bin", FALSE },
308
309         { NULL, NULL, FALSE },
310     }, *t;
311
312     for (t = tests; t->expr; t++) {
313         gboolean matched = match_glob(t->expr, t->str);
314         if (!!matched != !!t->should_match) {
315             ok = FALSE;
316             if (t->should_match) {
317                 g_fprintf(stderr, "%s should have matched glob %s\n",
318                         t->str, t->expr);
319             } else {
320                 g_fprintf(stderr, "%s unexpectedly matched glob %s\n",
321                         t->str, t->expr);
322             }
323         }
324     }
325
326     return ok;
327 }
328
329 static gboolean
330 test_match_tar(void)
331 {
332     gboolean ok = TRUE;
333     struct {
334         char *expr, *str;
335         gboolean should_match;
336     } tests[] = {
337         /* literal, unanchored matching */
338         { "a", "a", TRUE },
339
340         { "abc", "abc", TRUE },
341         { "abc", "abcd", FALSE },
342         { "abc", "dabc", FALSE },
343         { "abc", "/usr/bin/abc", TRUE },
344
345         { "*.txt", "foo.txt", TRUE },
346         { "*.txt", ".txt", TRUE },
347         { "*.txt", "txt", FALSE },
348
349         { "?.txt", "X.txt", TRUE },
350         { "?.txt", ".txt", FALSE },
351         { "?.txt", "XY.txt", FALSE },
352
353         { "?*.txt", ".txt", FALSE },
354         { "?*.txt", "a.txt", TRUE },
355         { "?*.txt", "aa.txt", TRUE },
356         { "?*.txt", "aaa.txt", TRUE },
357
358         { "foo.[tT][xX][tT]", "foo.txt", TRUE },
359         { "foo.[tT][xX][tT]", "foo.TXt", TRUE },
360         { "foo.[tT][xX][tT]", "foo.TXT", TRUE },
361         { "foo.[tT][xX][tT]", "foo.TaT", FALSE },
362
363         { "foo.[tT][!yY][tT]", "foo.TXt", TRUE },
364         { "foo.[tT][!yY][tT]", "foo.TXT", TRUE },
365         { "foo.[tT][!yY][tT]", "foo.TyT", FALSE },
366
367         { "foo\\\\", "foo", FALSE },
368         { "foo\\\\", "foo\\", TRUE },
369         { "foo\\\\", "foo\\\\", FALSE },
370
371         { "(){}+.^$|", "(){}+.^$|", TRUE },
372
373         { "/usr/bin/*", "/usr/bin/tar", TRUE },
374         { "/usr/bin/*", "/usr/bin/local/tar", TRUE }, /* different from match_glob */
375         { "/usr/bin/*", "/usr/sbin/tar", FALSE },
376         { "/usr/bin/*", "/opt/usr/bin/tar", FALSE },
377
378         { "/usr?bin", "/usr/bin", FALSE },
379         { "/usr*bin", "/usr/bin", TRUE }, /* different from match_glob */
380
381         /* examples from the amgtar manpage */
382         { "./temp-files", "./temp-files", TRUE },
383         { "./temp-files", "./temp-files/foo", TRUE },
384         { "./temp-files", "./temp-files/foo/bar", TRUE },
385         { "./temp-files", "./temp-files.bak", FALSE },
386         { "./temp-files", "./backup/temp-files", FALSE },
387
388         { "./temp-files/", "./temp-files", FALSE },
389         { "./temp-files/", "./temp-files/", TRUE },
390         { "./temp-files/", "./temp-files/foo", FALSE },
391         { "./temp-files/", "./temp-files/foo/bar", FALSE },
392         { "./temp-files/", "./temp-files.bak", FALSE },
393
394         { "/temp-files/", "./temp-files", FALSE },
395         { "/temp-files/", "./temp-files/", FALSE },
396         { "/temp-files/", "./temp-files/foo", FALSE },
397         { "/temp-files/", "./temp-files/foo/bar", FALSE },
398         { "/temp-files/", "./temp-files.bak", FALSE },
399
400         { "./temp-files/*", "./temp-files", FALSE },
401         { "./temp-files/*", "./temp-files/", TRUE },
402         { "./temp-files/*", "./temp-files/foo", TRUE },
403         { "./temp-files/*", "./temp-files/foo/bar", TRUE },
404
405         { "temp-files", "./my/temp-files", TRUE },
406         { "temp-files", "./my/temp-files/bar", TRUE },
407         { "temp-files", "./temp-files", TRUE },
408         { "temp-files", "./her-temp-files", FALSE },
409         { "temp-files", "./her/old-temp-files", FALSE },
410         { "temp-files", "./her/old-temp-files/bar", FALSE },
411
412         { "generated-*", "./my/generated-xyz", TRUE },
413         { "generated-*", "./my/generated-xyz/bar", TRUE },
414         { "generated-*", "./generated-xyz", TRUE },
415         { "generated-*", "./her-generated-xyz", FALSE },
416         { "generated-*", "./her/old-generated-xyz", FALSE },
417         { "generated-*", "./her/old-generated-xyz/bar", FALSE },
418
419         { "*.iso", "./my/amanda.iso", TRUE },
420         { "*.iso", "./amanda.iso", TRUE },
421
422         { "proxy/local/cache", "./usr/proxy/local/cache", TRUE },
423         { "proxy/local/cache", "./proxy/local/cache", TRUE },
424         { "proxy/local/cache", "./proxy/local/cache/7a", TRUE },
425
426         { NULL, NULL, FALSE },
427     }, *t;
428
429     for (t = tests; t->expr; t++) {
430         gboolean matched = match_tar(t->expr, t->str);
431         if (!!matched != !!t->should_match) {
432             ok = FALSE;
433             if (t->should_match) {
434                 g_fprintf(stderr, "%s should have matched tar %s\n",
435                         t->str, t->expr);
436             } else {
437                 g_fprintf(stderr, "%s unexpectedly matched tar %s\n",
438                         t->str, t->expr);
439             }
440         }
441     }
442
443     return ok;
444 }
445
446 static gboolean
447 test_make_exact_host_expression(void)
448 {
449     gboolean ok = TRUE;
450     guint i, j;
451     const char *test_strs[] = {
452         "host",
453         "host.org",
454         "host.host.org",
455         /* note that these will inter-match: */
456         /*
457         ".host",
458         ".host.org",
459         ".host.host.org",
460         "host.",
461         "host.org.",
462         "host.host.org.",
463         */
464         "org",
465         "^host",
466         "host$",
467         "^host$",
468         "ho[s]t",
469         "ho[!s]t",
470         "ho\\st",
471         "ho/st",
472         "ho?t",
473         "h*t",
474         "h**t",
475     };
476
477     for (i = 0; i < G_N_ELEMENTS(test_strs); i++) {
478         for (j = 0; j < G_N_ELEMENTS(test_strs); j++) {
479             char *expr = make_exact_host_expression(test_strs[i]);
480             gboolean matched = match_host(expr, test_strs[j]);
481             if (!!matched != !!(i == j)) {
482                 ok = FALSE;
483                 if (matched) {
484                     g_fprintf(stderr, "expr %s for str %s unexpectedly matched %s\n",
485                             expr, test_strs[i], test_strs[j]);
486                 } else {
487                     g_fprintf(stderr, "expr %s for str %s should have matched %s\n",
488                             expr, test_strs[i], test_strs[j]);
489                 }
490             }
491         }
492     }
493
494     return ok;
495 }
496
497 static gboolean
498 test_match_host(void)
499 {
500     gboolean ok = TRUE;
501     struct {
502         char *expr, *str;
503         gboolean should_match;
504     } tests[] = {
505         /* from the amanda(8) manpage */
506         { "hosta", "hosta", TRUE },
507         { "hosta", "foo.hosta.org", TRUE },
508         { "hosta", "hoSTA.dOMAIna.ORG", TRUE },
509         { "hosta", "hostb", FALSE },
510         { "hOsta", "hosta", TRUE },
511         { "hOsta", "foo.hosta.org", TRUE },
512         { "hOsta", "hoSTA.dOMAIna.ORG", TRUE },
513         { "hOsta", "hostb", FALSE },
514
515         { "host", "host", TRUE },
516         { "host", "hosta", FALSE },
517
518         { "host?", "hosta", TRUE },
519         { "host?", "hostb", TRUE },
520         { "host?", "host", FALSE },
521         { "host?", "hostabc", FALSE },
522
523         { "ho*na", "hona", TRUE },
524         { "ho*na", "hoina", TRUE },
525         { "ho*na", "hoina.org", TRUE },
526         { "ho*na", "ns.hoina.org", TRUE },
527         { "ho*na", "ho.aina.org", FALSE },
528
529         { "ho**na", "hona", TRUE },
530         { "ho**na", "hoina", TRUE },
531         { "ho**na", "hoina.org", TRUE },
532         { "ho**na", "ns.hoina.org", TRUE },
533         { "ho**na", "ho.aina.org", TRUE },
534
535         { "^hosta", "hosta", TRUE },
536         { "^hosta", "hosta.org", TRUE },
537         { "^hosta", "hostabc", FALSE },
538         { "^hosta", "www.hosta", FALSE },
539         { "^hosta", "www.hosta.org", FALSE },
540
541         { "/opt", "opt", FALSE },
542
543         { ".hosta.", "hosta", TRUE },
544         { ".hosta.", "foo.hosta", TRUE },
545         { ".hosta.", "hosta.org", TRUE },
546         { ".hosta.", "foo.hosta.org", TRUE },
547         { "/hosta", "hosta", FALSE },
548         { "/hosta", "foo.hosta", FALSE },
549         { "/hosta", "hosta.org", FALSE },
550         { "/hosta", "foo.hosta.org", FALSE },
551
552         { ".opt.", "opt", TRUE },
553         { ".opt.", "www.opt", TRUE },
554         { ".opt.", "www.opt.com", TRUE },
555         { ".opt.", "opt.com", TRUE },
556
557         /* other examples */
558         { "^hosta$", "hosta", TRUE },
559         { "^hosta$", "foo.hosta", FALSE },
560         { "^hosta$", "hosta.org", FALSE },
561         { "^hosta$", "foo.hosta.org", FALSE },
562
563         { "^lu.vis.ta$", "lu.vis.ta", TRUE },
564         { "^lu.vis.ta$", "lu-vis.ta", FALSE },
565         { "^lu.vis.ta$", "luvista", FALSE },
566         { "^lu.vis.ta$", "foo.lu.vis.ta", FALSE },
567         { "^lu.vis.ta$", "lu.vis.ta.org", FALSE },
568         { "^lu.vis.ta$", "foo.lu.vis.ta.org", FALSE },
569
570         { "mo[st]a", "mota", TRUE },
571         { "mo[st]a", "mosa", TRUE },
572         { "mo[st]a", "mosta", FALSE },
573         { "mo[!st]a", "mota", FALSE },
574         { "mo[!st]a", "moma", TRUE },
575         { "mo[!st]a", "momma", FALSE },
576
577         { "host[acd]", "hosta", TRUE },
578         { "host[acd]", "hostb", FALSE },
579         { "host[acd]", "hostc", TRUE },
580         { "host[!acd]", "hosta", FALSE },
581         { "host[!acd]", "hostb", TRUE },
582         { "host[!acd]", "hostc", FALSE },
583
584         { "toast", "www.toast.com", TRUE },
585         { ".toast", "www.toast.com", TRUE },
586         { "toast.", "www.toast.com", TRUE },
587         { ".toast.", "www.toast.com", TRUE },
588
589         { NULL, NULL, FALSE },
590     }, *t;
591
592     for (t = tests; t->expr; t++) {
593         gboolean matched = match_host(t->expr, t->str);
594         if (!!matched != !!t->should_match) {
595             ok = FALSE;
596             if (t->should_match) {
597                 g_fprintf(stderr, "%s should have matched host expr %s\n",
598                         t->str, t->expr);
599             } else {
600                 g_fprintf(stderr, "%s unexpectedly matched host expr %s\n",
601                         t->str, t->expr);
602             }
603         }
604     }
605
606     return ok;
607 }
608
609 static gboolean
610 test_make_exact_disk_expression(void)
611 {
612     gboolean ok = TRUE;
613     guint i, j;
614     const char *test_strs[] = {
615         "/disk",
616         "/disk/disk",
617         "d[i]sk",
618         "d**k",
619         "d*k",
620         "d?sk",
621         "d.sk",
622         "d[!pqr]sk",
623         "^disk",
624         "disk$",
625         "^disk$",
626         /* these intermatch due to some special-casing */
627         /*
628         "//windows/share",
629         "\\\\windows\\share",
630         */
631     };
632
633     for (i = 0; i < G_N_ELEMENTS(test_strs); i++) {
634         for (j = 0; j < G_N_ELEMENTS(test_strs); j++) {
635             char *expr = make_exact_disk_expression(test_strs[i]);
636             gboolean matched = match_disk(expr, test_strs[j]);
637             if (!!matched != !!(i == j)) {
638                 ok = FALSE;
639                 if (matched) {
640                     g_fprintf(stderr, "expr %s for str %s unexpectedly matched %s\n",
641                             expr, test_strs[i], test_strs[j]);
642                 } else {
643                     g_fprintf(stderr, "expr %s for str %s should have matched %s\n",
644                             expr, test_strs[i], test_strs[j]);
645                 }
646             }
647         }
648     }
649
650     return ok;
651 }
652
653 static gboolean
654 test_match_disk(void)
655 {
656     gboolean ok = TRUE;
657     struct {
658         char *expr, *str;
659         gboolean should_match;
660     } tests[] = {
661         /* from the amanda(8) manpage */
662         { "sda*", "/dev/sda1", TRUE },
663         { "sda*", "/dev/sda2", TRUE },
664         { "sda*", "/dev/sdb2", FALSE },
665
666         { "opt", "opt", TRUE },
667         { "opt", "/opt", TRUE },
668         { "opt", "/opt/foo", TRUE },
669         { "opt", "opt/foo", TRUE },
670
671         { "/opt", "opt", TRUE },
672         { "/opt", "opt/", TRUE },
673         { "/opt", "/opt", TRUE },
674         { "/opt", "/opt/", TRUE },
675         { "/opt", "/local/opt/", TRUE },
676         { "/opt", "/opt/local/", TRUE },
677
678         { "opt/", "opt", TRUE },
679         { "opt/", "opt/", TRUE },
680         { "opt/", "/opt", TRUE },
681         { "opt/", "/opt/", TRUE },
682         { "opt/", "/local/opt/", TRUE },
683         { "opt/", "/opt/local/", TRUE },
684
685         { "/", "/", TRUE },
686         { "/", "/opt/local/", FALSE },
687
688         { "/usr$", "/", FALSE },
689         { "/usr$", "/usr", TRUE },
690         { "/usr$", "/usr/local", FALSE },
691
692         { "share", "\\\\windows1\\share", TRUE },
693         { "share", "\\\\windows2\\share", TRUE },
694         { "share", "//windows1/share", TRUE },
695         { "share", "//windows2/share", TRUE },
696
697         { "share*", "\\\\windows\\share1", TRUE },
698         { "share*", "\\\\windows\\share2", TRUE },
699         { "share*", "//windows/share3", TRUE },
700         { "share*", "//windows/share4", TRUE },
701
702         { "//windows/share", "//windows/share", TRUE },
703         { "//windows/share", "\\\\windows\\share", TRUE },
704         { "\\\\windows\\share", "//windows/share", FALSE },
705         { "\\\\windows\\share", "\\\\windows\\share", FALSE },
706         { "\\\\\\\\windows\\\\share", "//windows/share", FALSE },
707         { "\\\\\\\\windows\\\\share", "\\\\windows\\share", TRUE },
708
709         /* other expressions */
710         { "^local", "/local", FALSE },
711         { "^/local", "/local", TRUE },
712         { "^local", "/local/vore", FALSE },
713         { "^/local", "/local/vore", TRUE },
714         { "^local", "/usr/local", FALSE },
715
716         { "local/bin", "/local/bin", TRUE },
717         { "local/bin", "/opt/local/bin", TRUE },
718         { "local/bin", "/local/bin/git", TRUE },
719
720         { "//windows/share", "//windows/share/files", TRUE },
721         { "//windows/share", "\\\\windows\\share\\files", TRUE },
722         { "\\\\windows\\share", "//windows/share/files", FALSE },
723         { "\\\\windows\\share", "\\\\windows\\share\\files", FALSE },
724
725         /* longer expressions */
726         { "local/var", "/local/var", TRUE },
727         { "local/var", "/opt/local/var", TRUE },
728         { "local/var", "/local/var/lib", TRUE },
729         { "local/var", "/local/usr/var", FALSE },
730
731         /* trailing slashes */
732         { "/usr/bin", "/usr/bin", TRUE },
733         { "/usr/bin", "/usr/bin/", TRUE },
734         { "/usr/bin/", "/usr/bin", TRUE },
735         { "/usr/bin/", "/usr/bin/", TRUE },
736         { "/usr/bin", "/usr/bin//", TRUE },
737         { "/usr/bin//", "/usr/bin", FALSE },
738         { "/usr/bin//", "/usr/bin//", TRUE },
739
740         /* quoting '/' is weird: it doesn't work on the leading slash.  Note that
741          * the documentation does not specify how to quote metacharacters in a host
742          * or disk expression. */
743         { "/usr\\/bin", "/usr/bin", TRUE },
744         { "^/usr\\/bin$", "/usr/bin", TRUE },
745         { "\\/usr\\/bin", "/usr/bin", FALSE },
746         { "^\\/usr\\/bin$", "/usr/bin", TRUE },
747         { "/usr\\/bin\\/", "/usr/bin/", TRUE },
748         { "^/usr\\/bin\\/$", "/usr/bin/", TRUE },
749         { "\\/usr\\/bin\\/", "/usr/bin/", FALSE },
750         { "^\\/usr\\/bin\\/$", "/usr/bin/", TRUE },
751
752         { "oracle", "oracle", TRUE },
753         { "oracle", "/oracle", TRUE },
754         { "oracle", "oracle/", TRUE },
755         { "oracle", "/oracle/", TRUE },
756         { "/oracle", "oracle", TRUE },
757         { "/oracle", "/oracle", TRUE },
758         { "/oracle", "oracle/", TRUE },
759         { "/oracle", "/oracle/", TRUE },
760         { "^oracle", "oracle", TRUE },
761         { "^oracle", "/oracle", FALSE },
762         { "^oracle", "oracle/", TRUE },
763         { "^oracle", "/oracle/", FALSE },
764         { "^/oracle", "oracle", FALSE },
765         { "^/oracle", "/oracle", TRUE },
766         { "^/oracle", "oracle/", FALSE },
767         { "^/oracle", "/oracle/", TRUE },
768
769         { "oracle", "oracle", TRUE },
770         { "oracle", "/oracle", TRUE },
771         { "oracle", "oracle/", TRUE },
772         { "oracle", "/oracle/", TRUE },
773         { "oracle$", "oracle", TRUE },
774         { "oracle$", "/oracle", TRUE },
775         { "oracle$", "oracle/", FALSE },
776         { "oracle$", "/oracle/", FALSE },
777         { "oracle/$", "oracle", FALSE },
778         { "oracle/$", "/oracle", FALSE },
779         { "oracle/$", "oracle/", TRUE },
780         { "oracle/$", "/oracle/", TRUE },
781
782         { NULL, NULL, FALSE },
783     }, *t;
784
785     for (t = tests; t->expr; t++) {
786         gboolean matched = match_disk(t->expr, t->str);
787         if (!!matched != !!t->should_match) {
788             ok = FALSE;
789             if (t->should_match) {
790                 g_fprintf(stderr, "%s should have matched disk expr %s\n",
791                         t->str, t->expr);
792             } else {
793                 g_fprintf(stderr, "%s unexpectedly matched disk expr %s\n",
794                         t->str, t->expr);
795             }
796         }
797     }
798
799     return ok;
800 }
801
802 static gboolean
803 test_match_datestamp(void)
804 {
805     gboolean ok = TRUE;
806     struct {
807         char *expr, *str;
808         gboolean should_match;
809     } tests[] = {
810         /* from the amanda(8) manpage */
811         { "20001212-14", "20001212", TRUE },
812         { "20001212-14", "20001212010203", TRUE },
813         { "20001212-14", "20001213", TRUE },
814         { "20001212-14", "20001213010203", TRUE },
815         { "20001212-14", "20001214", TRUE },
816         { "20001212-14", "20001215", FALSE },
817
818         { "20001212-4", "20001212", TRUE },
819         { "20001212-4", "20001212010203", TRUE },
820         { "20001212-4", "20001213", TRUE },
821         { "20001212-4", "20001213010203", TRUE },
822         { "20001212-4", "20001214", TRUE },
823         { "20001212-4", "20001215", FALSE },
824
825         { "20001212-214", "20001212", TRUE },
826         { "20001212-214", "20001212010203", TRUE },
827         { "20001212-214", "20001213", TRUE },
828         { "20001212-214", "20001213010203", TRUE },
829         { "20001212-214", "20001214", TRUE },
830         { "20001212-214", "20001215", FALSE },
831
832         { "20001212-24", "20001211", FALSE },
833         { "20001212-24", "20001214010203", TRUE },
834         { "20001212-24", "20001221010203", TRUE },
835         { "20001212-24", "20001224", TRUE },
836         { "20001212-24", "20001225", FALSE },
837
838         { "2000121", "20001209", FALSE },
839         { "2000121", "20001210", TRUE },
840         { "2000121", "20001210012345", TRUE },
841         { "2000121", "20001219", TRUE },
842         { "2000121", "20001219012345", TRUE },
843         { "2000121", "20001220", FALSE },
844
845         { "2", "19991231", FALSE },
846         { "2", "20000101", TRUE },
847         { "2", "20100419", TRUE },
848
849         { "^2", "19991231", FALSE },
850         { "^2", "20000101", TRUE },
851         { "^2", "20100419", TRUE },
852
853         { "2000-2010", "19991231235959", FALSE },
854         { "2000-2010", "20001010", TRUE },
855         { "2000-2010", "20101231", TRUE },
856         { "2000-2010", "20111010", FALSE },
857
858         { "200010$", "200010", TRUE }, /* but it's not a real datestamp */
859         { "200010$", "20001001", FALSE },
860         { "200010$", "20001001061500", FALSE },
861
862         { "20000615$", "20000615", TRUE },
863         { "20000615$", "20000615000000", FALSE },
864         { "20000615$", "20000615010306", FALSE },
865
866         { NULL, NULL, FALSE },
867     }, *t;
868
869     for (t = tests; t->expr; t++) {
870         gboolean matched = match_datestamp(t->expr, t->str);
871         if (!!matched != !!t->should_match) {
872             ok = FALSE;
873             if (t->should_match) {
874                 g_fprintf(stderr, "%s should have matched datestamp expr %s\n",
875                         t->str, t->expr);
876             } else {
877                 g_fprintf(stderr, "%s unexpectedly matched datestamp expr %s\n",
878                         t->str, t->expr);
879             }
880         }
881     }
882
883     return ok;
884 }
885
886 static gboolean
887 test_match_level(void)
888 {
889     gboolean ok = TRUE;
890     struct {
891         char *expr, *str;
892         gboolean should_match;
893     } tests[] = {
894         /* exact matches, optionally ignoring "^" */
895         { "3$", "2", FALSE },
896         { "3$", "3", TRUE },
897         { "3$", "4", FALSE },
898         { "3$", "32", FALSE },
899
900         { "^3$", "2", FALSE },
901         { "^3$", "3", TRUE },
902         { "^3$", "4", FALSE },
903         { "^3$", "32", FALSE },
904
905         /* prefix matches */
906         { "3", "2", FALSE },
907         { "3", "3", TRUE },
908         { "3", "4", FALSE },
909         { "3", "32", TRUE },
910
911         /* ranges */
912         { "2-5", "1", FALSE },
913         { "2-5", "13", FALSE },
914         { "2-5", "23", FALSE },
915         { "2-5", "2", TRUE },
916         { "2-5", "4", TRUE },
917         { "2-5", "5", TRUE },
918         { "2-5", "53", FALSE },
919         { "2-5", "63", FALSE },
920         { "2-5", "6", FALSE },
921
922         { "9-15", "8", FALSE },
923         { "9-15", "19", FALSE },
924         { "9-15", "91", FALSE },
925         { "9-15", "9", TRUE },
926         { "9-15", "14", TRUE },
927         { "9-15", "15", TRUE },
928         { "9-15", "152", FALSE },
929         { "9-15", "16", FALSE },
930
931         { "19-21", "18", FALSE },
932         { "19-21", "19", TRUE },
933         { "19-21", "21", TRUE },
934         { "19-21", "22", FALSE },
935
936         /* single range is the same as an exact match */
937         { "99-99", "98", FALSE },
938         { "99-99", "99", TRUE },
939         { "99-99", "100", FALSE },
940
941         /* reversed range never matches */
942         { "21-19", "18", FALSE },
943         { "21-19", "19", FALSE },
944         { "21-19", "21", FALSE },
945         { "21-19", "22", FALSE },
946
947         { NULL, NULL, FALSE },
948     }, *t;
949
950     for (t = tests; t->expr; t++) {
951         gboolean matched = match_level(t->expr, t->str);
952         if (!!matched != !!t->should_match) {
953             ok = FALSE;
954             if (t->should_match) {
955                 g_fprintf(stderr, "%s should have matched level expr %s\n",
956                         t->str, t->expr);
957             } else {
958                 g_fprintf(stderr, "%s unexpectedly matched level expr %s\n",
959                         t->str, t->expr);
960             }
961         }
962     }
963
964     return ok;
965 }
966
967
968 /*
969  * Main driver
970  */
971
972 int
973 main(int argc, char **argv)
974 {
975     static TestUtilsTest tests[] = {
976         TU_TEST(test_validate_regexp, 90),
977         TU_TEST(test_match, 90),
978         TU_TEST(test_validate_glob, 90),
979         TU_TEST(test_glob_to_regex, 90),
980         TU_TEST(test_match_glob, 90),
981         TU_TEST(test_match_tar, 90),
982         TU_TEST(test_make_exact_host_expression, 90),
983         TU_TEST(test_match_host, 90),
984         TU_TEST(test_make_exact_disk_expression, 90),
985         TU_TEST(test_match_disk, 90),
986         TU_TEST(test_match_datestamp, 90),
987         TU_TEST(test_match_level, 90),
988         TU_END()
989     };
990
991     return testutils_run_tests(argc, argv, tests);
992 }
993