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