Imported Upstream version 3.2.0
[debian/amanda] / common-src / match-test.c
index 44c8d1e83011d6353cbf467905fa935e7d50dab7..63717385b68aef34bb19ec6b0931c6f9c5b3d325 100644 (file)
 #include "testutils.h"
 #include "match.h"
 
-/* NOTE: this is an incomplete set of tests for match.c */
-
 /*
  * Tests
  */
 
-/****
- * Test some host expressions
- */
 static int
-test_host_match(void)
+test_validate_regexp(void)
+{
+    gboolean ok = TRUE;
+    struct {
+       char *regexp;
+       gboolean should_validate;
+    } tests[] = {
+       { ".*", TRUE },
+       { "*", FALSE },
+       { "[abc", FALSE },
+       { "(abc", FALSE },
+       { "{1,}", FALSE },
+       { NULL, FALSE },
+    }, *t;
+
+    for (t = tests; t->regexp; t++) {
+       char *validated_err = validate_regexp(t->regexp);
+       if (!validated_err != !!t->should_validate) {
+           ok = FALSE;
+           if (t->should_validate) {
+               g_fprintf(stderr, "should have validated regular expr %s: %s\n",
+                       t->regexp, validated_err);
+           } else {
+               g_fprintf(stderr, "unexpectedly validated regular expr %s\n",
+                       t->regexp);
+           }
+       }
+    }
+
+    return ok;
+}
+
+static int
+test_match(void)
+{
+    gboolean ok = TRUE;
+    struct {
+       char *expr, *str;
+       gboolean should_match, should_match_no_newline;
+    } tests[] = {
+       /* literal, unanchored matching */
+       { "a", "a", TRUE, TRUE },
+       { "a", "A", FALSE, FALSE },
+       { "a", "ab", TRUE, TRUE },
+       { "a", "ba", TRUE, TRUE },
+       { "a", "bab", TRUE, TRUE },
+
+       /* dot */
+       { ".", "", FALSE, FALSE },
+       { ".", "a", TRUE, TRUE },
+       { "..", "a", FALSE, FALSE },
+       { "..", "bc", TRUE, TRUE },
+
+       /* brackets */
+       { "[abc]", "xbx", TRUE, TRUE },
+       { "[abc]", "xyz", FALSE, FALSE },
+       { "[^abc]", "cba", FALSE, FALSE },
+       { "[^abc]", "xyz", TRUE, TRUE },
+       { "[a-c]", "b", TRUE, TRUE },
+       { "[^a-c]", "-", TRUE, TRUE },
+       { "[1-9-]", "-", TRUE, TRUE },
+       { "[ab\\-cd]", "-", FALSE, FALSE }, /* NOTE! */
+
+       /* anchors */
+       { "^xy", "xyz", TRUE, TRUE },
+       { "^xy", "wxyz", FALSE, FALSE },
+       { "yz$", "xyz", TRUE, TRUE },
+       { "yz$", "yza", FALSE, FALSE },
+       { "^123$", "123", TRUE, TRUE },
+       { "^123$", "0123", FALSE, FALSE },
+       { "^123$", "1234", FALSE, FALSE },
+
+       /* capture groups */
+       { "([a-c])([x-y])", "pqaxyr", TRUE, TRUE },
+       { "([a-c])([x-y])", "paqrxy", FALSE, FALSE },
+       { "([a-c])/\\1", "a/b", FALSE, FALSE },
+       { "([a-c])/\\1", "c/c", TRUE, TRUE },
+
+       /* * */
+       { ">[0-9]*<", "><", TRUE, TRUE },
+       { ">[0-9]*<", ">3<", TRUE, TRUE },
+       { ">[0-9]*<", ">34<", TRUE, TRUE },
+       { ">[0-9]*<", ">345<", TRUE, TRUE },
+       { ">[0-9]*<", ">x<", FALSE, FALSE },
+
+       /* | */
+       { ":(abc|ABC);", ":abc;", TRUE, TRUE },
+       { ":(abc|ABC);", ":ABC;", TRUE, TRUE },
+       { ":(abc|ABC);", ":abcBC;", FALSE, FALSE },
+
+       /* + */
+       { ">[0-9]+<", "><", FALSE, FALSE },
+       { ">[0-9]+<", ">3<", TRUE, TRUE },
+       { ">[0-9]+<", ">34<", TRUE, TRUE },
+       { ">[0-9]+<", ">345<", TRUE, TRUE },
+       { ">[0-9]+<", ">x<", FALSE, FALSE },
+
+       /* { .. } */
+       { ">[0-9]{0,1}<", "><", TRUE, TRUE },
+       { ">[0-9]{0,1}<", ">9<", TRUE, TRUE },
+       { ">[0-9]{0,1}<", ">98<", FALSE, FALSE },
+       { ">[0-9]{2,3}<", "><", FALSE, FALSE },
+       { ">[0-9]{2,3}<", ">5<", FALSE, FALSE },
+       { ">[0-9]{2,3}<", ">55<", TRUE, TRUE },
+       { ">[0-9]{2,3}<", ">555<", TRUE, TRUE },
+       { ">[0-9]{2,3}<", ">5555<", FALSE, FALSE },
+
+       /* quoting metacharacters */
+       { "\\\\", "\\", TRUE, TRUE },
+       { "\\,", ",", TRUE, TRUE },
+       { "\\[", "[", TRUE, TRUE },
+       { "\\*", "*", TRUE, TRUE },
+       { "\\?", "?", TRUE, TRUE },
+       { "\\+", "+", TRUE, TRUE },
+       { "\\.", ".", TRUE, TRUE },
+       { "\\|", "|", TRUE, TRUE },
+       { "\\^", "^", TRUE, TRUE },
+       { "\\$", "$", TRUE, TRUE },
+
+       /* differences between match and match_no_newline */
+       { "x.y", "x\ny", FALSE, TRUE },
+       { "x[^yz]y", "x\ny", FALSE, TRUE },
+       { "^y", "x\ny", TRUE, FALSE },
+       { "x$", "x\ny", TRUE, FALSE },
+
+       { NULL, NULL, FALSE, FALSE },
+    }, *t;
+
+    for (t = tests; t->expr; t++) {
+       gboolean matched = match(t->expr, t->str);
+       if (!!matched != !!t->should_match) {
+           ok = FALSE;
+           if (t->should_match) {
+               g_fprintf(stderr, "%s should have matched regular expr %s\n",
+                       t->str, t->expr);
+           } else {
+               g_fprintf(stderr, "%s unexpectedly matched regular expr %s\n",
+                       t->str, t->expr);
+           }
+       }
+
+       matched = match_no_newline(t->expr, t->str);
+       if (!!matched != !!t->should_match_no_newline) {
+           ok = FALSE;
+           if (t->should_match) {
+               g_fprintf(stderr, "%s should have matched (no_newline) regular expr %s\n",
+                       t->str, t->expr);
+           } else {
+               g_fprintf(stderr, "%s unexpectedly matched (no_newline) regular expr %s\n",
+                       t->str, t->expr);
+           }
+       }
+    }
+
+    return ok;
+}
+
+static int
+test_validate_glob(void)
+{
+    gboolean ok = TRUE;
+    struct {
+       char *glob;
+       gboolean should_validate;
+    } tests[] = {
+       { "foo.*", TRUE },
+       { "*.txt", TRUE },
+       { "x[abc]y", TRUE },
+       { "x[!abc]y", TRUE },
+       { "[abc", FALSE },
+       { "[!abc", FALSE },
+       { "??*", TRUE },
+       { "**?", TRUE }, /* legal, but weird */
+       { "foo\\", FALSE }, /* un-escaped \ is illegal */
+       { "foo\\\\", TRUE }, /* but escaped is OK */
+       { "(){}+.^$|", TRUE }, /* funny characters OK */
+       { "/usr/bin/*", TRUE }, /* filename seps are OK */
+       { NULL, FALSE },
+    }, *t;
+
+    for (t = tests; t->glob; t++) {
+       char *validated_err = validate_glob(t->glob);
+       if (!validated_err != !!t->should_validate) {
+           ok = FALSE;
+           if (t->should_validate) {
+               g_fprintf(stderr, "should have validated glob %s: %s\n",
+                       t->glob, validated_err);
+           } else {
+               g_fprintf(stderr, "unexpectedly validated glob %s\n",
+                       t->glob);
+           }
+       }
+    }
+
+    return ok;
+}
+
+static int
+test_glob_to_regex(void)
+{
+    gboolean ok = TRUE;
+    struct { char *glob, *regex; } tests[] = {
+       { "abc", "^abc$" },
+       { "*.txt", "^[^/]*\\.txt$" },
+       { "?.txt", "^[^/]\\.txt$" },
+       { "?*.txt", "^[^/][^/]*\\.txt$" },
+       { "foo.[tT][xX][tT]", "^foo\\.[tT][xX][tT]$" },
+       { "foo.[tT][!yY][tT]", "^foo\\.[tT][^yY][tT]$" },
+       { "foo\\\\", "^foo\\\\$" },
+       { "(){}+.^$|", "^\\(\\)\\{\\}\\+\\.\\^\\$\\|$" },
+       { "/usr/bin/*", "^/usr/bin/[^/]*$" },
+       { NULL, NULL },
+    }, *t;
+
+    for (t = tests; t->glob; t++) {
+       char *regex = glob_to_regex(t->glob);
+       if (0 != strcmp(regex, t->regex)) {
+           ok = FALSE;
+           g_fprintf(stderr, "glob_to_regex(\"%s\") returned \"%s\"; expected \"%s\"\n",
+                   t->glob, regex, t->regex);
+       }
+    }
+
+    return ok;
+}
+
+static int
+test_match_glob(void)
+{
+    gboolean ok = TRUE;
+    struct {
+       char *expr, *str;
+       gboolean should_match;
+    } tests[] = {
+       /* literal, unanchored matching */
+       { "a", "a", TRUE },
+
+       { "abc", "abc", TRUE },
+       { "abc", "abcd", FALSE },
+       { "abc", "dabc", FALSE },
+       { "abc", "/usr/bin/abc", FALSE },
+
+       { "*.txt", "foo.txt", TRUE },
+       { "*.txt", ".txt", TRUE },
+       { "*.txt", "txt", FALSE },
+
+       { "?.txt", "X.txt", TRUE },
+       { "?.txt", ".txt", FALSE },
+       { "?.txt", "XY.txt", FALSE },
+
+       { "?*.txt", ".txt", FALSE },
+       { "?*.txt", "a.txt", TRUE },
+       { "?*.txt", "aa.txt", TRUE },
+       { "?*.txt", "aaa.txt", TRUE },
+
+       { "foo.[tT][xX][tT]", "foo.txt", TRUE },
+       { "foo.[tT][xX][tT]", "foo.TXt", TRUE },
+       { "foo.[tT][xX][tT]", "foo.TXT", TRUE },
+       { "foo.[tT][xX][tT]", "foo.TaT", FALSE },
+
+       { "foo.[tT][!yY][tT]", "foo.TXt", TRUE },
+       { "foo.[tT][!yY][tT]", "foo.TXT", TRUE },
+       { "foo.[tT][!yY][tT]", "foo.TyT", FALSE },
+
+       { "foo\\\\", "foo", FALSE },
+       { "foo\\\\", "foo\\", TRUE },
+       { "foo\\\\", "foo\\\\", FALSE },
+
+       { "(){}+.^$|", "(){}+.^$|", TRUE },
+
+       { "/usr/bin/*", "/usr/bin/tar", TRUE },
+       { "/usr/bin/*", "/usr/bin/local/tar", FALSE },
+       { "/usr/bin/*", "/usr/sbin/tar", FALSE },
+       { "/usr/bin/*", "/opt/usr/bin/tar", FALSE },
+
+       { "/usr?bin", "/usr/bin", FALSE },
+       { "/usr*bin", "/usr/bin", FALSE },
+
+       { NULL, NULL, FALSE },
+    }, *t;
+
+    for (t = tests; t->expr; t++) {
+       gboolean matched = match_glob(t->expr, t->str);
+       if (!!matched != !!t->should_match) {
+           ok = FALSE;
+           if (t->should_match) {
+               g_fprintf(stderr, "%s should have matched glob %s\n",
+                       t->str, t->expr);
+           } else {
+               g_fprintf(stderr, "%s unexpectedly matched glob %s\n",
+                       t->str, t->expr);
+           }
+       }
+    }
+
+    return ok;
+}
+
+static int
+test_match_tar(void)
+{
+    gboolean ok = TRUE;
+    struct {
+       char *expr, *str;
+       gboolean should_match;
+    } tests[] = {
+       /* literal, unanchored matching */
+       { "a", "a", TRUE },
+
+       { "abc", "abc", TRUE },
+       { "abc", "abcd", FALSE },
+       { "abc", "dabc", FALSE },
+       { "abc", "/usr/bin/abc", TRUE },
+
+       { "*.txt", "foo.txt", TRUE },
+       { "*.txt", ".txt", TRUE },
+       { "*.txt", "txt", FALSE },
+
+       { "?.txt", "X.txt", TRUE },
+       { "?.txt", ".txt", FALSE },
+       { "?.txt", "XY.txt", FALSE },
+
+       { "?*.txt", ".txt", FALSE },
+       { "?*.txt", "a.txt", TRUE },
+       { "?*.txt", "aa.txt", TRUE },
+       { "?*.txt", "aaa.txt", TRUE },
+
+       { "foo.[tT][xX][tT]", "foo.txt", TRUE },
+       { "foo.[tT][xX][tT]", "foo.TXt", TRUE },
+       { "foo.[tT][xX][tT]", "foo.TXT", TRUE },
+       { "foo.[tT][xX][tT]", "foo.TaT", FALSE },
+
+       { "foo.[tT][!yY][tT]", "foo.TXt", TRUE },
+       { "foo.[tT][!yY][tT]", "foo.TXT", TRUE },
+       { "foo.[tT][!yY][tT]", "foo.TyT", FALSE },
+
+       { "foo\\\\", "foo", FALSE },
+       { "foo\\\\", "foo\\", TRUE },
+       { "foo\\\\", "foo\\\\", FALSE },
+
+       { "(){}+.^$|", "(){}+.^$|", TRUE },
+
+       { "/usr/bin/*", "/usr/bin/tar", TRUE },
+       { "/usr/bin/*", "/usr/bin/local/tar", TRUE }, /* different from match_glob */
+       { "/usr/bin/*", "/usr/sbin/tar", FALSE },
+       { "/usr/bin/*", "/opt/usr/bin/tar", FALSE },
+
+       { "/usr?bin", "/usr/bin", FALSE },
+       { "/usr*bin", "/usr/bin", TRUE }, /* different from match_glob */
+
+       /* examples from the amgtar manpage */
+       { "./temp-files", "./temp-files", TRUE },
+       { "./temp-files", "./temp-files/foo", TRUE },
+       { "./temp-files", "./temp-files/foo/bar", TRUE },
+       { "./temp-files", "./temp-files.bak", FALSE },
+       { "./temp-files", "./backup/temp-files", FALSE },
+
+       { "./temp-files/", "./temp-files", FALSE },
+       { "./temp-files/", "./temp-files/", TRUE },
+       { "./temp-files/", "./temp-files/foo", FALSE },
+       { "./temp-files/", "./temp-files/foo/bar", FALSE },
+       { "./temp-files/", "./temp-files.bak", FALSE },
+
+       { "/temp-files/", "./temp-files", FALSE },
+       { "/temp-files/", "./temp-files/", FALSE },
+       { "/temp-files/", "./temp-files/foo", FALSE },
+       { "/temp-files/", "./temp-files/foo/bar", FALSE },
+       { "/temp-files/", "./temp-files.bak", FALSE },
+
+       { "./temp-files/*", "./temp-files", FALSE },
+       { "./temp-files/*", "./temp-files/", TRUE },
+       { "./temp-files/*", "./temp-files/foo", TRUE },
+       { "./temp-files/*", "./temp-files/foo/bar", TRUE },
+
+       { "temp-files", "./my/temp-files", TRUE },
+       { "temp-files", "./my/temp-files/bar", TRUE },
+       { "temp-files", "./temp-files", TRUE },
+       { "temp-files", "./her-temp-files", FALSE },
+       { "temp-files", "./her/old-temp-files", FALSE },
+       { "temp-files", "./her/old-temp-files/bar", FALSE },
+
+       { "generated-*", "./my/generated-xyz", TRUE },
+       { "generated-*", "./my/generated-xyz/bar", TRUE },
+       { "generated-*", "./generated-xyz", TRUE },
+       { "generated-*", "./her-generated-xyz", FALSE },
+       { "generated-*", "./her/old-generated-xyz", FALSE },
+       { "generated-*", "./her/old-generated-xyz/bar", FALSE },
+
+       { "*.iso", "./my/amanda.iso", TRUE },
+       { "*.iso", "./amanda.iso", TRUE },
+
+       { "proxy/local/cache", "./usr/proxy/local/cache", TRUE },
+       { "proxy/local/cache", "./proxy/local/cache", TRUE },
+       { "proxy/local/cache", "./proxy/local/cache/7a", TRUE },
+
+       { NULL, NULL, FALSE },
+    }, *t;
+
+    for (t = tests; t->expr; t++) {
+       gboolean matched = match_tar(t->expr, t->str);
+       if (!!matched != !!t->should_match) {
+           ok = FALSE;
+           if (t->should_match) {
+               g_fprintf(stderr, "%s should have matched tar %s\n",
+                       t->str, t->expr);
+           } else {
+               g_fprintf(stderr, "%s unexpectedly matched tar %s\n",
+                       t->str, t->expr);
+           }
+       }
+    }
+
+    return ok;
+}
+
+static int
+test_make_exact_host_expression(void)
 {
     gboolean ok = TRUE;
-    struct { char *expr, *str; gboolean should_match; } tests[] = {
-       /* examples from amanda(8) */
+    guint i, j;
+    const char *test_strs[] = {
+       "host",
+       "host.org",
+       "host.host.org",
+       /* note that these will inter-match: */
+       /*
+       ".host",
+       ".host.org",
+       ".host.host.org",
+       "host.",
+       "host.org.",
+       "host.host.org.",
+       */
+       "org",
+       "^host",
+       "host$",
+       "^host$",
+       "ho[s]t",
+       "ho[!s]t",
+       "ho\\st",
+       "ho/st",
+       "ho?t",
+       "h*t",
+       "h**t",
+    };
+
+    for (i = 0; i < G_N_ELEMENTS(test_strs); i++) {
+       for (j = 0; j < G_N_ELEMENTS(test_strs); j++) {
+           char *expr = make_exact_host_expression(test_strs[i]);
+           gboolean matched = match_host(expr, test_strs[j]);
+           if (!!matched != !!(i == j)) {
+               ok = FALSE;
+               if (matched) {
+                   g_fprintf(stderr, "expr %s for str %s unexpectedly matched %s\n",
+                           expr, test_strs[i], test_strs[j]);
+               } else {
+                   g_fprintf(stderr, "expr %s for str %s should have matched %s\n",
+                           expr, test_strs[i], test_strs[j]);
+               }
+           }
+       }
+    }
+
+    return ok;
+}
+
+static int
+test_match_host(void)
+{
+    gboolean ok = TRUE;
+    struct {
+       char *expr, *str;
+       gboolean should_match;
+    } tests[] = {
+       /* from the amanda(8) manpage */
        { "hosta", "hosta", TRUE },
        { "hosta", "foo.hosta.org", TRUE },
-       { "hosta", "hOsTA.domain.org", TRUE },
+       { "hosta", "hoSTA.dOMAIna.ORG", TRUE },
        { "hosta", "hostb", FALSE },
+       { "hOsta", "hosta", TRUE },
+       { "hOsta", "foo.hosta.org", TRUE },
+       { "hOsta", "hoSTA.dOMAIna.ORG", TRUE },
+       { "hOsta", "hostb", FALSE },
+
        { "host", "host", TRUE },
        { "host", "hosta", FALSE },
+
        { "host?", "hosta", TRUE },
+       { "host?", "hostb", TRUE },
        { "host?", "host", FALSE },
+       { "host?", "hostabc", FALSE },
+
+       { "ho*na", "hona", TRUE },
        { "ho*na", "hoina", TRUE },
+       { "ho*na", "hoina.org", TRUE },
+       { "ho*na", "ns.hoina.org", TRUE },
        { "ho*na", "ho.aina.org", FALSE },
+
+       { "ho**na", "hona", TRUE },
        { "ho**na", "hoina", TRUE },
+       { "ho**na", "hoina.org", TRUE },
+       { "ho**na", "ns.hoina.org", TRUE },
        { "ho**na", "ho.aina.org", TRUE },
+
        { "^hosta", "hosta", TRUE },
-       { "^hosta", "hosta.foo.org", TRUE },
-       { "^hosta", "foo.hosta.org", FALSE },
+       { "^hosta", "hosta.org", TRUE },
+       { "^hosta", "hostabc", FALSE },
+       { "^hosta", "www.hosta", FALSE },
+       { "^hosta", "www.hosta.org", FALSE },
+
+       { "/opt", "opt", FALSE },
 
        { ".hosta.", "hosta", TRUE },
        { ".hosta.", "foo.hosta", TRUE },
@@ -62,7 +548,12 @@ test_host_match(void)
        { "/hosta", "hosta.org", FALSE },
        { "/hosta", "foo.hosta.org", FALSE },
 
-       /* additional checks */
+       { ".opt.", "opt", TRUE },
+       { ".opt.", "www.opt", TRUE },
+       { ".opt.", "www.opt.com", TRUE },
+       { ".opt.", "opt.com", TRUE },
+
+       /* other examples */
        { "^hosta$", "hosta", TRUE },
        { "^hosta$", "foo.hosta", FALSE },
        { "^hosta$", "hosta.org", FALSE },
@@ -82,6 +573,18 @@ test_host_match(void)
        { "mo[!st]a", "moma", TRUE },
        { "mo[!st]a", "momma", FALSE },
 
+       { "host[acd]", "hosta", TRUE },
+       { "host[acd]", "hostb", FALSE },
+       { "host[acd]", "hostc", TRUE },
+       { "host[!acd]", "hosta", FALSE },
+       { "host[!acd]", "hostb", TRUE },
+       { "host[!acd]", "hostc", FALSE },
+
+       { "toast", "www.toast.com", TRUE },
+       { ".toast", "www.toast.com", TRUE },
+       { "toast.", "www.toast.com", TRUE },
+       { ".toast.", "www.toast.com", TRUE },
+
        { NULL, NULL, FALSE },
     }, *t;
 
@@ -102,47 +605,120 @@ test_host_match(void)
     return ok;
 }
 
-/****
- * Test some disk expressions
- */
 static int
-test_disk_match(void)
+test_make_exact_disk_expression(void)
+{
+    gboolean ok = TRUE;
+    guint i, j;
+    const char *test_strs[] = {
+       "/disk",
+       "/disk/disk",
+       "d[i]sk",
+       "d**k",
+       "d*k",
+       "d?sk",
+       "d.sk",
+       "d[!pqr]sk",
+       "^disk",
+       "disk$",
+       "^disk$",
+       /* these intermatch due to some special-casing */
+       /*
+       "//windows/share",
+       "\\\\windows\\share",
+       */
+    };
+
+    for (i = 0; i < G_N_ELEMENTS(test_strs); i++) {
+       for (j = 0; j < G_N_ELEMENTS(test_strs); j++) {
+           char *expr = make_exact_disk_expression(test_strs[i]);
+           gboolean matched = match_disk(expr, test_strs[j]);
+           if (!!matched != !!(i == j)) {
+               ok = FALSE;
+               if (matched) {
+                   g_fprintf(stderr, "expr %s for str %s unexpectedly matched %s\n",
+                           expr, test_strs[i], test_strs[j]);
+               } else {
+                   g_fprintf(stderr, "expr %s for str %s should have matched %s\n",
+                           expr, test_strs[i], test_strs[j]);
+               }
+           }
+       }
+    }
+
+    return ok;
+}
+
+static int
+test_match_disk(void)
 {
     gboolean ok = TRUE;
-    struct { char *expr, *str; gboolean should_match; } tests[] = {
-       /* examples from amanda(8) */
+    struct {
+       char *expr, *str;
+       gboolean should_match;
+    } tests[] = {
+       /* from the amanda(8) manpage */
        { "sda*", "/dev/sda1", TRUE },
-       { "sda*", "/dev/sda12", TRUE },
+       { "sda*", "/dev/sda2", TRUE },
+       { "sda*", "/dev/sdb2", FALSE },
+
        { "opt", "opt", TRUE },
        { "opt", "/opt", TRUE },
        { "opt", "/opt/foo", TRUE },
        { "opt", "opt/foo", TRUE },
+
+       { "/opt", "opt", TRUE },
+       { "/opt", "opt/", TRUE },
+       { "/opt", "/opt", TRUE },
+       { "/opt", "/opt/", TRUE },
+       { "/opt", "/local/opt/", TRUE },
+       { "/opt", "/opt/local/", TRUE },
+
+       { "opt/", "opt", TRUE },
+       { "opt/", "opt/", TRUE },
+       { "opt/", "/opt", TRUE },
+       { "opt/", "/opt/", TRUE },
+       { "opt/", "/local/opt/", TRUE },
+       { "opt/", "/opt/local/", TRUE },
+
        { "/", "/", TRUE },
-       { "/", "/opt", FALSE },
-       { "/", "/opt/var", FALSE },
-       { "/usr", "/", FALSE },
-       { "/usr", "/usr", TRUE },
-       { "/usr", "/usr/local", TRUE },
+       { "/", "/opt/local/", FALSE },
+
        { "/usr$", "/", FALSE },
        { "/usr$", "/usr", TRUE },
        { "/usr$", "/usr/local", FALSE },
-       { "share", "//windows1/share", TRUE },
-       { "share", "//windows2/share", TRUE },
+
        { "share", "\\\\windows1\\share", TRUE },
        { "share", "\\\\windows2\\share", TRUE },
-       { "share*", "//windows/share1", TRUE },
-       { "share*", "//windows/share2", TRUE },
+       { "share", "//windows1/share", TRUE },
+       { "share", "//windows2/share", TRUE },
+
        { "share*", "\\\\windows\\share1", TRUE },
        { "share*", "\\\\windows\\share2", TRUE },
+       { "share*", "//windows/share3", TRUE },
+       { "share*", "//windows/share4", TRUE },
+
        { "//windows/share", "//windows/share", TRUE },
        { "//windows/share", "\\\\windows\\share", TRUE },
-
-       /* and now things get murky */
        { "\\\\windows\\share", "//windows/share", FALSE },
        { "\\\\windows\\share", "\\\\windows\\share", FALSE },
        { "\\\\\\\\windows\\\\share", "//windows/share", FALSE },
        { "\\\\\\\\windows\\\\share", "\\\\windows\\share", TRUE },
 
+       /* other expressions */
+       { "^local", "/local", TRUE },
+       { "^local", "/local/vore", TRUE },
+       { "^local", "/usr/local", FALSE },
+
+       { "local/bin", "/local/bin", TRUE },
+       { "local/bin", "/opt/local/bin", TRUE },
+       { "local/bin", "/local/bin/git", TRUE },
+
+       { "//windows/share", "//windows/share/files", TRUE },
+       { "//windows/share", "\\\\windows\\share\\files", TRUE },
+       { "\\\\windows\\share", "//windows/share/files", FALSE },
+       { "\\\\windows\\share", "\\\\windows\\share\\files", FALSE },
+
        /* longer expressions */
        { "local/var", "/local/var", TRUE },
        { "local/var", "/opt/local/var", TRUE },
@@ -186,53 +762,83 @@ test_disk_match(void)
     return ok;
 }
 
-/****
- * Test make_exact_host_expression
- */
 static int
-test_make_exact_host_expression(void)
+test_match_datestamp(void)
 {
     gboolean ok = TRUE;
-    guint i, j;
-    const char *test_strs[] = {
-       "host",
-       "host.org",
-       "host.host.org",
-       /* note that these will inter-match: */
-       /*
-       ".host",
-       ".host.org",
-       ".host.host.org",
-       "host.",
-       "host.org.",
-       "host.host.org.",
-       */
-       "org",
-       "^host",
-       "host$",
-       "^host$",
-       "ho[s]t",
-       "ho[!s]t",
-       "ho\\st",
-       "ho/st",
-       "ho?t",
-       "h*t",
-       "h**t",
-    };
+    struct {
+       char *expr, *str;
+       gboolean should_match;
+    } tests[] = {
+       /* from the amanda(8) manpage */
+       { "20001212-14", "20001212", TRUE },
+       { "20001212-14", "20001212010203", TRUE },
+       { "20001212-14", "20001213", TRUE },
+       { "20001212-14", "20001213010203", TRUE },
+       { "20001212-14", "20001214", TRUE },
+       { "20001212-14", "20001215", FALSE },
 
-    for (i = 0; i < G_N_ELEMENTS(test_strs); i++) {
-       for (j = 0; j < G_N_ELEMENTS(test_strs); j++) {
-           char *expr = make_exact_host_expression(test_strs[i]);
-           gboolean matched = match_host(expr, test_strs[j]);
-           if (!!matched != !!(i == j)) {
-               ok = FALSE;
-               if (matched) {
-                   g_fprintf(stderr, "expr %s for str %s unexpectedly matched %s\n",
-                           expr, test_strs[i], test_strs[j]);
-               } else {
-                   g_fprintf(stderr, "expr %s for str %s should have matched %s\n",
-                           expr, test_strs[i], test_strs[j]);
-               }
+       { "20001212-4", "20001212", TRUE },
+       { "20001212-4", "20001212010203", TRUE },
+       { "20001212-4", "20001213", TRUE },
+       { "20001212-4", "20001213010203", TRUE },
+       { "20001212-4", "20001214", TRUE },
+       { "20001212-4", "20001215", FALSE },
+
+       { "20001212-214", "20001212", TRUE },
+       { "20001212-214", "20001212010203", TRUE },
+       { "20001212-214", "20001213", TRUE },
+       { "20001212-214", "20001213010203", TRUE },
+       { "20001212-214", "20001214", TRUE },
+       { "20001212-214", "20001215", FALSE },
+
+       { "20001212-24", "20001211", FALSE },
+       { "20001212-24", "20001214010203", TRUE },
+       { "20001212-24", "20001221010203", TRUE },
+       { "20001212-24", "20001224", TRUE },
+       { "20001212-24", "20001225", FALSE },
+
+       { "2000121", "20001209", FALSE },
+       { "2000121", "20001210", TRUE },
+       { "2000121", "20001210012345", TRUE },
+       { "2000121", "20001219", TRUE },
+       { "2000121", "20001219012345", TRUE },
+       { "2000121", "20001220", FALSE },
+
+       { "2", "19991231", FALSE },
+       { "2", "20000101", TRUE },
+       { "2", "20100419", TRUE },
+
+       { "^2", "19991231", FALSE },
+       { "^2", "20000101", TRUE },
+       { "^2", "20100419", TRUE },
+
+       { "2000-2010", "19991231235959", FALSE },
+       { "2000-2010", "20001010", TRUE },
+       { "2000-2010", "20101231", TRUE },
+       { "2000-2010", "20111010", FALSE },
+
+       { "200010$", "200010", TRUE }, /* but it's not a real datestamp */
+       { "200010$", "20001001", FALSE },
+       { "200010$", "20001001061500", FALSE },
+
+       { "20000615$", "20000615", TRUE },
+       { "20000615$", "20000615000000", FALSE },
+       { "20000615$", "20000615010306", FALSE },
+
+       { NULL, NULL, FALSE },
+    }, *t;
+
+    for (t = tests; t->expr; t++) {
+       gboolean matched = match_datestamp(t->expr, t->str);
+       if (!!matched != !!t->should_match) {
+           ok = FALSE;
+           if (t->should_match) {
+               g_fprintf(stderr, "%s should have matched datestamp expr %s\n",
+                       t->str, t->expr);
+           } else {
+               g_fprintf(stderr, "%s unexpectedly matched datestamp expr %s\n",
+                       t->str, t->expr);
            }
        }
     }
@@ -240,46 +846,80 @@ test_make_exact_host_expression(void)
     return ok;
 }
 
-/****
- * Test make_exact_disk_expression
- */
 static int
-test_make_exact_disk_expression(void)
+test_match_level(void)
 {
     gboolean ok = TRUE;
-    guint i, j;
-    const char *test_strs[] = {
-       "/disk",
-       "/disk/disk",
-       "d[i]sk",
-       "d**k",
-       "d*k",
-       "d?sk",
-       "d.sk",
-       "d[!pqr]sk",
-       "^disk",
-       "disk$",
-       "^disk$",
-       /* these intermatch due to some special-casing */
-       /*
-       "//windows/share",
-       "\\\\windows\\share",
-       */
-    };
+    struct {
+       char *expr, *str;
+       gboolean should_match;
+    } tests[] = {
+       /* exact matches, optionally ignoring "^" */
+       { "3$", "2", FALSE },
+       { "3$", "3", TRUE },
+       { "3$", "4", FALSE },
+       { "3$", "32", FALSE },
 
-    for (i = 0; i < G_N_ELEMENTS(test_strs); i++) {
-       for (j = 0; j < G_N_ELEMENTS(test_strs); j++) {
-           char *expr = make_exact_disk_expression(test_strs[i]);
-           gboolean matched = match_disk(expr, test_strs[j]);
-           if (!!matched != !!(i == j)) {
-               ok = FALSE;
-               if (matched) {
-                   g_fprintf(stderr, "expr %s for str %s unexpectedly matched %s\n",
-                           expr, test_strs[i], test_strs[j]);
-               } else {
-                   g_fprintf(stderr, "expr %s for str %s should have matched %s\n",
-                           expr, test_strs[i], test_strs[j]);
-               }
+       { "^3$", "2", FALSE },
+       { "^3$", "3", TRUE },
+       { "^3$", "4", FALSE },
+       { "^3$", "32", FALSE },
+
+       /* prefix matches */
+       { "3", "2", FALSE },
+       { "3", "3", TRUE },
+       { "3", "4", FALSE },
+       { "3", "32", TRUE },
+
+       /* ranges */
+       { "2-5", "1", FALSE },
+       { "2-5", "13", FALSE },
+       { "2-5", "23", FALSE },
+       { "2-5", "2", TRUE },
+       { "2-5", "4", TRUE },
+       { "2-5", "5", TRUE },
+       { "2-5", "53", FALSE },
+       { "2-5", "63", FALSE },
+       { "2-5", "6", FALSE },
+
+       { "9-15", "8", FALSE },
+       { "9-15", "19", FALSE },
+       { "9-15", "91", FALSE },
+       { "9-15", "9", TRUE },
+       { "9-15", "14", TRUE },
+       { "9-15", "15", TRUE },
+       { "9-15", "152", FALSE },
+       { "9-15", "16", FALSE },
+
+       { "19-21", "18", FALSE },
+       { "19-21", "19", TRUE },
+       { "19-21", "21", TRUE },
+       { "19-21", "22", FALSE },
+
+       /* single range is the same as an exact match */
+       { "99-99", "98", FALSE },
+       { "99-99", "99", TRUE },
+       { "99-99", "100", FALSE },
+
+       /* reversed range never matches */
+       { "21-19", "18", FALSE },
+       { "21-19", "19", FALSE },
+       { "21-19", "21", FALSE },
+       { "21-19", "22", FALSE },
+
+       { NULL, NULL, FALSE },
+    }, *t;
+
+    for (t = tests; t->expr; t++) {
+       gboolean matched = match_level(t->expr, t->str);
+       if (!!matched != !!t->should_match) {
+           ok = FALSE;
+           if (t->should_match) {
+               g_fprintf(stderr, "%s should have matched level expr %s\n",
+                       t->str, t->expr);
+           } else {
+               g_fprintf(stderr, "%s unexpectedly matched level expr %s\n",
+                       t->str, t->expr);
            }
        }
     }
@@ -287,6 +927,7 @@ test_make_exact_disk_expression(void)
     return ok;
 }
 
+
 /*
  * Main driver
  */
@@ -295,10 +936,18 @@ int
 main(int argc, char **argv)
 {
     static TestUtilsTest tests[] = {
-       TU_TEST(test_host_match, 90),
-       TU_TEST(test_disk_match, 90),
+       TU_TEST(test_validate_regexp, 90),
+       TU_TEST(test_match, 90),
+       TU_TEST(test_validate_glob, 90),
+       TU_TEST(test_glob_to_regex, 90),
+       TU_TEST(test_match_glob, 90),
+       TU_TEST(test_match_tar, 90),
        TU_TEST(test_make_exact_host_expression, 90),
+       TU_TEST(test_match_host, 90),
        TU_TEST(test_make_exact_disk_expression, 90),
+       TU_TEST(test_match_disk, 90),
+       TU_TEST(test_match_datestamp, 90),
+       TU_TEST(test_match_level, 90),
        TU_END()
     };