1/*- 2 * Copyright (c) 2010 Jilles Tjoelker 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27#include <sys/cdefs.h> 28__FBSDID("$FreeBSD$"); 29 30#include <errno.h> 31#include <stdio.h> 32#include <stdlib.h> 33#include <string.h> 34#include <unistd.h> 35#include <fnmatch.h> 36 37struct testcase { 38 const char *pattern; 39 const char *string; 40 int flags; 41 int result; 42} testcases[] = { 43 "", "", 0, 0, 44 "a", "a", 0, 0, 45 "a", "b", 0, FNM_NOMATCH, 46 "a", "A", 0, FNM_NOMATCH, 47 "*", "a", 0, 0, 48 "*", "aa", 0, 0, 49 "*a", "a", 0, 0, 50 "*a", "b", 0, FNM_NOMATCH, 51 "*a*", "b", 0, FNM_NOMATCH, 52 "*a*b*", "ab", 0, 0, 53 "*a*b*", "qaqbq", 0, 0, 54 "*a*bb*", "qaqbqbbq", 0, 0, 55 "*a*bc*", "qaqbqbcq", 0, 0, 56 "*a*bb*", "qaqbqbb", 0, 0, 57 "*a*bc*", "qaqbqbc", 0, 0, 58 "*a*bb", "qaqbqbb", 0, 0, 59 "*a*bc", "qaqbqbc", 0, 0, 60 "*a*bb", "qaqbqbbq", 0, FNM_NOMATCH, 61 "*a*bc", "qaqbqbcq", 0, FNM_NOMATCH, 62 "*a*a*a*a*a*a*a*a*a*a*", "aaaaaaaaa", 0, FNM_NOMATCH, 63 "*a*a*a*a*a*a*a*a*a*a*", "aaaaaaaaaa", 0, 0, 64 "*a*a*a*a*a*a*a*a*a*a*", "aaaaaaaaaaa", 0, 0, 65 ".*.*.*.*.*.*.*.*.*.*", ".........", 0, FNM_NOMATCH, 66 ".*.*.*.*.*.*.*.*.*.*", "..........", 0, 0, 67 ".*.*.*.*.*.*.*.*.*.*", "...........", 0, 0, 68 "*?*?*?*?*?*?*?*?*?*?*", "123456789", 0, FNM_NOMATCH, 69 "??????????*", "123456789", 0, FNM_NOMATCH, 70 "*??????????", "123456789", 0, FNM_NOMATCH, 71 "*?*?*?*?*?*?*?*?*?*?*", "1234567890", 0, 0, 72 "??????????*", "1234567890", 0, 0, 73 "*??????????", "1234567890", 0, 0, 74 "*?*?*?*?*?*?*?*?*?*?*", "12345678901", 0, 0, 75 "??????????*", "12345678901", 0, 0, 76 "*??????????", "12345678901", 0, 0, 77 "[x]", "x", 0, 0, 78 "[*]", "*", 0, 0, 79 "[?]", "?", 0, 0, 80 "[", "[", 0, 0, 81 "[[]", "[", 0, 0, 82 "[[]", "x", 0, FNM_NOMATCH, 83 "[*]", "", 0, FNM_NOMATCH, 84 "[*]", "x", 0, FNM_NOMATCH, 85 "[?]", "x", 0, FNM_NOMATCH, 86 "*[*]*", "foo*foo", 0, 0, 87 "*[*]*", "foo", 0, FNM_NOMATCH, 88 "[0-9]", "0", 0, 0, 89 "[0-9]", "5", 0, 0, 90 "[0-9]", "9", 0, 0, 91 "[0-9]", "/", 0, FNM_NOMATCH, 92 "[0-9]", ":", 0, FNM_NOMATCH, 93 "[0-9]", "*", 0, FNM_NOMATCH, 94 "[!0-9]", "0", 0, FNM_NOMATCH, 95 "[!0-9]", "5", 0, FNM_NOMATCH, 96 "[!0-9]", "9", 0, FNM_NOMATCH, 97 "[!0-9]", "/", 0, 0, 98 "[!0-9]", ":", 0, 0, 99 "[!0-9]", "*", 0, 0, 100 "*[0-9]", "a0", 0, 0, 101 "*[0-9]", "a5", 0, 0, 102 "*[0-9]", "a9", 0, 0, 103 "*[0-9]", "a/", 0, FNM_NOMATCH, 104 "*[0-9]", "a:", 0, FNM_NOMATCH, 105 "*[0-9]", "a*", 0, FNM_NOMATCH, 106 "*[!0-9]", "a0", 0, FNM_NOMATCH, 107 "*[!0-9]", "a5", 0, FNM_NOMATCH, 108 "*[!0-9]", "a9", 0, FNM_NOMATCH, 109 "*[!0-9]", "a/", 0, 0, 110 "*[!0-9]", "a:", 0, 0, 111 "*[!0-9]", "a*", 0, 0, 112 "*[0-9]", "a00", 0, 0, 113 "*[0-9]", "a55", 0, 0, 114 "*[0-9]", "a99", 0, 0, 115 "*[0-9]", "a0a0", 0, 0, 116 "*[0-9]", "a5a5", 0, 0, 117 "*[0-9]", "a9a9", 0, 0, 118 "\\*", "*", 0, 0, 119 "\\?", "?", 0, 0, 120 "\\[x]", "[x]", 0, 0, 121 "\\[", "[", 0, 0, 122 "\\\\", "\\", 0, 0, 123 "*\\**", "foo*foo", 0, 0, 124 "*\\**", "foo", 0, FNM_NOMATCH, 125 "*\\\\*", "foo\\foo", 0, 0, 126 "*\\\\*", "foo", 0, FNM_NOMATCH, 127 "\\(", "(", 0, 0, 128 "\\a", "a", 0, 0, 129 "\\*", "a", 0, FNM_NOMATCH, 130 "\\?", "a", 0, FNM_NOMATCH, 131 "\\*", "\\*", 0, FNM_NOMATCH, 132 "\\?", "\\?", 0, FNM_NOMATCH, 133 "\\[x]", "\\[x]", 0, FNM_NOMATCH, 134 "\\[x]", "\\x", 0, FNM_NOMATCH, 135 "\\[", "\\[", 0, FNM_NOMATCH, 136 "\\(", "\\(", 0, FNM_NOMATCH, 137 "\\a", "\\a", 0, FNM_NOMATCH, 138 "\\", "\\", 0, FNM_NOMATCH, 139 "\\", "", 0, 0, 140 "\\*", "\\*", FNM_NOESCAPE, 0, 141 "\\?", "\\?", FNM_NOESCAPE, 0, 142 "\\", "\\", FNM_NOESCAPE, 0, 143 "\\\\", "\\", FNM_NOESCAPE, FNM_NOMATCH, 144 "\\\\", "\\\\", FNM_NOESCAPE, 0, 145 "*\\*", "foo\\foo", FNM_NOESCAPE, 0, 146 "*\\*", "foo", FNM_NOESCAPE, FNM_NOMATCH, 147 "*", ".", FNM_PERIOD, FNM_NOMATCH, 148 "?", ".", FNM_PERIOD, FNM_NOMATCH, 149 ".*", ".", 0, 0, 150 ".*", "..", 0, 0, 151 ".*", ".a", 0, 0, 152 "[0-9]", ".", FNM_PERIOD, FNM_NOMATCH, 153 "a*", "a.", 0, 0, 154 "a/a", "a/a", FNM_PATHNAME, 0, 155 "a/*", "a/a", FNM_PATHNAME, 0, 156 "*/a", "a/a", FNM_PATHNAME, 0, 157 "*/*", "a/a", FNM_PATHNAME, 0, 158 "a*b/*", "abbb/x", FNM_PATHNAME, 0, 159 "a*b/*", "abbb/.x", FNM_PATHNAME, 0, 160 "*", "a/a", FNM_PATHNAME, FNM_NOMATCH, 161 "*/*", "a/a/a", FNM_PATHNAME, FNM_NOMATCH, 162 "b/*", "b/.x", FNM_PATHNAME | FNM_PERIOD, FNM_NOMATCH, 163 "b*/*", "a/.x", FNM_PATHNAME | FNM_PERIOD, FNM_NOMATCH, 164 "b/.*", "b/.x", FNM_PATHNAME | FNM_PERIOD, 0, 165 "b*/.*", "b/.x", FNM_PATHNAME | FNM_PERIOD, 0, 166 "a", "A", FNM_CASEFOLD, 0, 167 "A", "a", FNM_CASEFOLD, 0, 168 "[a]", "A", FNM_CASEFOLD, 0, 169 "[A]", "a", FNM_CASEFOLD, 0, 170 "a", "b", FNM_CASEFOLD, FNM_NOMATCH, 171 "a", "a/b", FNM_PATHNAME, FNM_NOMATCH, 172 "*", "a/b", FNM_PATHNAME, FNM_NOMATCH, 173 "*b", "a/b", FNM_PATHNAME, FNM_NOMATCH, 174 "a", "a/b", FNM_PATHNAME | FNM_LEADING_DIR, 0, 175 "*", "a/b", FNM_PATHNAME | FNM_LEADING_DIR, 0, 176 "*", ".a/b", FNM_PATHNAME | FNM_LEADING_DIR, 0, 177 "*a", ".a/b", FNM_PATHNAME | FNM_LEADING_DIR, 0, 178 "*", ".a/b", FNM_PATHNAME | FNM_PERIOD | FNM_LEADING_DIR, FNM_NOMATCH, 179 "*a", ".a/b", FNM_PATHNAME | FNM_PERIOD | FNM_LEADING_DIR, FNM_NOMATCH, 180 "a*b/*", "abbb/.x", FNM_PATHNAME | FNM_PERIOD, FNM_NOMATCH, 181}; 182 183static const char * 184flags_to_string(int flags) 185{ 186 static const int flagvalues[] = { FNM_NOESCAPE, FNM_PATHNAME, 187 FNM_PERIOD, FNM_LEADING_DIR, FNM_CASEFOLD, 0 }; 188 static const char flagnames[] = "FNM_NOESCAPE\0FNM_PATHNAME\0FNM_PERIOD\0FNM_LEADING_DIR\0FNM_CASEFOLD\0"; 189 static char result[sizeof(flagnames) + 3 * sizeof(int) + 2]; 190 char *p; 191 size_t i, len; 192 const char *fp; 193 194 p = result; 195 fp = flagnames; 196 for (i = 0; flagvalues[i] != 0; i++) { 197 len = strlen(fp); 198 if (flags & flagvalues[i]) { 199 if (p != result) 200 *p++ = '|'; 201 memcpy(p, fp, len); 202 p += len; 203 flags &= ~flagvalues[i]; 204 } 205 fp += len + 1; 206 } 207 if (p == result) 208 memcpy(p, "0", 2); 209 else if (flags != 0) 210 sprintf(p, "%d", flags); 211 else 212 *p = '\0'; 213 return result; 214} 215 216static int 217write_sh_tests(const char *progname, int num) 218{ 219 size_t i, n; 220 struct testcase *t; 221 222 printf("# Generated by %s -s %d, do not edit.\n", progname, num); 223 printf("# $" "FreeBSD$\n"); 224 printf("failures=\n"); 225 printf("failed() { printf '%%s\\n' \"Failed: $1 '$2' '$3'\"; failures=x$failures; }\n"); 226 if (num == 1) { 227 printf("testmatch() { eval \"case \\$2 in ''$1) ;; *) failed testmatch \\\"\\$@\\\";; esac\"; }\n"); 228 printf("testnomatch() { eval \"case \\$2 in ''$1) failed testnomatch \\\"\\$@\\\";; esac\"; }\n"); 229 } else if (num == 2) { 230 printf("# We do not treat a backslash specially in this case,\n"); 231 printf("# but this is not the case in all shells.\n"); 232 printf("netestmatch() { case $2 in $1) ;; *) failed netestmatch \"$@\";; esac; }\n"); 233 printf("netestnomatch() { case $2 in $1) failed netestnomatch \"$@\";; esac; }\n"); 234 } 235 n = sizeof(testcases) / sizeof(testcases[0]); 236 for (i = 0; i < n; i++) { 237 t = &testcases[i]; 238 if (strchr(t->pattern, '\'') != NULL || 239 strchr(t->string, '\'') != NULL) 240 continue; 241 if (t->flags == 0 && strcmp(t->pattern, "\\") == 0) 242 continue; 243 if (num == 1 && t->flags == 0) 244 printf("test%smatch '%s' '%s'\n", 245 t->result == FNM_NOMATCH ? "no" : "", 246 t->pattern, t->string); 247 if (num == 2 && (t->flags == FNM_NOESCAPE || 248 (t->flags == 0 && strchr(t->pattern, '\\') == NULL))) 249 printf("netest%smatch '%s' '%s'\n", 250 t->result == FNM_NOMATCH ? "no" : "", 251 t->pattern, t->string); 252 } 253 printf("[ -z \"$failures\" ]\n"); 254 return 0; 255} 256 257int 258main(int argc, char *argv[]) 259{ 260 size_t i, n; 261 int opt, flags, result, extra, errors; 262 struct testcase *t; 263 264 while ((opt = getopt(argc, argv, "s:")) != -1) { 265 switch (opt) { 266 case 's': 267 return (write_sh_tests(argv[0], atoi(optarg))); 268 default: 269 fprintf(stderr, "usage: %s [-s num]\n", argv[0]); 270 fprintf(stderr, "-s option writes tests for sh(1), num is 1 or 2\n"); 271 exit(1); 272 } 273 } 274 n = sizeof(testcases) / sizeof(testcases[0]); 275 errors = 0; 276 printf("1..%zu\n", n); 277 for (i = 0; i < n; i++) { 278 t = &testcases[i]; 279 flags = t->flags; 280 extra = 0; 281 do { 282 result = fnmatch(t->pattern, t->string, flags); 283 if (result != t->result) 284 break; 285 if (strchr(t->pattern, '\\') == NULL && 286 !(flags & FNM_NOESCAPE)) { 287 flags |= FNM_NOESCAPE; 288 result = fnmatch(t->pattern, t->string, flags); 289 if (result != t->result) 290 break; 291 flags = t->flags; 292 extra++; 293 } 294 if (strchr(t->pattern, '\\') != NULL && 295 strchr(t->string, '\\') == NULL && 296 t->result == FNM_NOMATCH && 297 !(flags & (FNM_NOESCAPE | FNM_LEADING_DIR))) { 298 flags |= FNM_NOESCAPE; 299 result = fnmatch(t->pattern, t->string, flags); 300 if (result != t->result) 301 break; 302 flags = t->flags; 303 extra++; 304 } 305 if ((t->string[0] != '.' || t->pattern[0] == '.' || 306 t->result == FNM_NOMATCH) && 307 !(flags & (FNM_PATHNAME | FNM_PERIOD))) { 308 flags |= FNM_PERIOD; 309 result = fnmatch(t->pattern, t->string, flags); 310 if (result != t->result) 311 break; 312 flags = t->flags; 313 extra++; 314 } 315 if ((strchr(t->string, '/') == NULL || 316 t->result == FNM_NOMATCH) && 317 !(flags & FNM_PATHNAME)) { 318 flags |= FNM_PATHNAME; 319 result = fnmatch(t->pattern, t->string, flags); 320 if (result != t->result) 321 break; 322 flags = t->flags; 323 extra++; 324 } 325 if ((((t->string[0] != '.' || t->pattern[0] == '.') && 326 strstr(t->string, "/.") == NULL) || 327 t->result == FNM_NOMATCH) && 328 flags & FNM_PATHNAME && !(flags & FNM_PERIOD)) { 329 flags |= FNM_PERIOD; 330 result = fnmatch(t->pattern, t->string, flags); 331 if (result != t->result) 332 break; 333 flags = t->flags; 334 extra++; 335 } 336 if ((((t->string[0] != '.' || t->pattern[0] == '.') && 337 strchr(t->string, '/') == NULL) || 338 t->result == FNM_NOMATCH) && 339 !(flags & (FNM_PATHNAME | FNM_PERIOD))) { 340 flags |= FNM_PATHNAME | FNM_PERIOD; 341 result = fnmatch(t->pattern, t->string, flags); 342 if (result != t->result) 343 break; 344 flags = t->flags; 345 extra++; 346 } 347 if ((strchr(t->string, '/') == NULL || t->result == 0) 348 && !(flags & FNM_LEADING_DIR)) { 349 flags |= FNM_LEADING_DIR; 350 result = fnmatch(t->pattern, t->string, flags); 351 if (result != t->result) 352 break; 353 flags = t->flags; 354 extra++; 355 } 356 if (t->result == 0 && !(flags & FNM_CASEFOLD)) { 357 flags |= FNM_CASEFOLD; 358 result = fnmatch(t->pattern, t->string, flags); 359 if (result != t->result) 360 break; 361 flags = t->flags; 362 extra++; 363 } 364 if (strchr(t->pattern, '\\') == NULL && 365 t->result == 0 && 366 !(flags & (FNM_NOESCAPE | FNM_CASEFOLD))) { 367 flags |= FNM_NOESCAPE | FNM_CASEFOLD; 368 result = fnmatch(t->pattern, t->string, flags); 369 if (result != t->result) 370 break; 371 flags = t->flags; 372 extra++; 373 } 374 } while (0); 375 if (result == t->result) 376 printf("ok %zu - fnmatch(\"%s\", \"%s\", %s) = %d (+%d)\n", 377 i + 1, t->pattern, t->string, 378 flags_to_string(flags), 379 result, extra); 380 else { 381 printf("not ok %zu - fnmatch(\"%s\", \"%s\", %s) = %d != %d\n", 382 i + 1, t->pattern, t->string, 383 flags_to_string(flags), 384 result, t->result); 385 errors = 1; 386 } 387 } 388 389 return (errors); 390} 391