github.com/AndrewDeryabin/doublestar/v4@v4.0.0-20230123132908-d9476b7d41be/doublestar_test.go (about) 1 package doublestar 2 3 import ( 4 "io/fs" 5 "log" 6 "os" 7 "path" 8 "path/filepath" 9 "runtime" 10 "strings" 11 "testing" 12 ) 13 14 type MatchTest struct { 15 pattern, testPath string // a pattern and path to test the pattern on 16 shouldMatch bool // true if the pattern should match the path 17 shouldMatchGlob bool // true if glob should match the path 18 expectedErr error // an expected error 19 expectIOErr bool // whether or not to expect an io error 20 expectPatternNotExist bool // whether or not to expect ErrPatternNotExist 21 isStandard bool // pattern doesn't use any doublestar features (e.g. '**', '{a,b}') 22 testOnDisk bool // true: test pattern against files in "test" directory 23 numResults int // number of glob results if testing on disk 24 winNumResults int // number of glob results on Windows 25 } 26 27 // Tests which contain escapes and symlinks will not work on Windows 28 var onWindows = runtime.GOOS == "windows" 29 30 var matchTests = []MatchTest{ 31 {"*", "", true, true, nil, false, false, true, false, 0, 0}, 32 {"*", "/", false, false, nil, false, false, true, false, 0, 0}, 33 {"/*", "/", true, true, nil, false, false, true, false, 0, 0}, 34 {"/*", "/debug/", false, false, nil, false, false, true, false, 0, 0}, 35 {"/*", "//", false, false, nil, false, false, true, false, 0, 0}, 36 {"abc", "abc", true, true, nil, false, false, true, true, 1, 1}, 37 {"*", "abc", true, true, nil, false, false, true, true, 22, 17}, 38 {"*c", "abc", true, true, nil, false, false, true, true, 2, 2}, 39 {"*/", "a/", true, true, nil, false, false, true, false, 0, 0}, 40 {"a*", "a", true, true, nil, false, false, true, true, 9, 9}, 41 {"a*", "abc", true, true, nil, false, false, true, true, 9, 9}, 42 {"a*", "ab/c", false, false, nil, false, false, true, true, 9, 9}, 43 {"a*/b", "abc/b", true, true, nil, false, false, true, true, 2, 2}, 44 {"a*/b", "a/c/b", false, false, nil, false, false, true, true, 2, 2}, 45 {"a*/c/", "a/b", false, false, nil, false, false, false, true, 1, 1}, 46 {"a*b*c*d*e*", "axbxcxdxe", true, true, nil, false, false, true, true, 3, 3}, 47 {"a*b*c*d*e*/f", "axbxcxdxe/f", true, true, nil, false, false, true, true, 2, 2}, 48 {"a*b*c*d*e*/f", "axbxcxdxexxx/f", true, true, nil, false, false, true, true, 2, 2}, 49 {"a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false, false, nil, false, false, true, true, 2, 2}, 50 {"a*b*c*d*e*/f", "axbxcxdxexxx/fff", false, false, nil, false, false, true, true, 2, 2}, 51 {"a*b?c*x", "abxbbxdbxebxczzx", true, true, nil, false, false, true, true, 2, 2}, 52 {"a*b?c*x", "abxbbxdbxebxczzy", false, false, nil, false, false, true, true, 2, 2}, 53 {"ab[c]", "abc", true, true, nil, false, false, true, true, 1, 1}, 54 {"ab[b-d]", "abc", true, true, nil, false, false, true, true, 1, 1}, 55 {"ab[e-g]", "abc", false, false, nil, false, false, true, true, 0, 0}, 56 {"ab[^c]", "abc", false, false, nil, false, false, true, true, 0, 0}, 57 {"ab[^b-d]", "abc", false, false, nil, false, false, true, true, 0, 0}, 58 {"ab[^e-g]", "abc", true, true, nil, false, false, true, true, 1, 1}, 59 {"a\\*b", "ab", false, false, nil, false, true, true, !onWindows, 0, 0}, 60 {"a?b", "a☺b", true, true, nil, false, false, true, true, 1, 1}, 61 {"a[^a]b", "a☺b", true, true, nil, false, false, true, true, 1, 1}, 62 {"a[!a]b", "a☺b", true, true, nil, false, false, false, true, 1, 1}, 63 {"a???b", "a☺b", false, false, nil, false, false, true, true, 0, 0}, 64 {"a[^a][^a][^a]b", "a☺b", false, false, nil, false, false, true, true, 0, 0}, 65 {"[a-ζ]*", "α", true, true, nil, false, false, true, true, 20, 17}, 66 {"*[a-ζ]", "A", false, false, nil, false, false, true, true, 20, 17}, 67 {"a?b", "a/b", false, false, nil, false, false, true, true, 1, 1}, 68 {"a*b", "a/b", false, false, nil, false, false, true, true, 1, 1}, 69 {"[\\]a]", "]", true, true, nil, false, false, true, !onWindows, 2, 2}, 70 {"[\\-]", "-", true, true, nil, false, false, true, !onWindows, 1, 1}, 71 {"[x\\-]", "x", true, true, nil, false, false, true, !onWindows, 2, 2}, 72 {"[x\\-]", "-", true, true, nil, false, false, true, !onWindows, 2, 2}, 73 {"[x\\-]", "z", false, false, nil, false, false, true, !onWindows, 2, 2}, 74 {"[\\-x]", "x", true, true, nil, false, false, true, !onWindows, 2, 2}, 75 {"[\\-x]", "-", true, true, nil, false, false, true, !onWindows, 2, 2}, 76 {"[\\-x]", "a", false, false, nil, false, false, true, !onWindows, 2, 2}, 77 {"[]a]", "]", false, false, ErrBadPattern, false, false, true, true, 0, 0}, 78 // doublestar, like bash, allows these when path.Match() does not 79 {"[-]", "-", true, true, nil, false, false, false, !onWindows, 1, 0}, 80 {"[x-]", "x", true, true, nil, false, false, false, true, 2, 1}, 81 {"[x-]", "-", true, true, nil, false, false, false, !onWindows, 2, 1}, 82 {"[x-]", "z", false, false, nil, false, false, false, true, 2, 1}, 83 {"[-x]", "x", true, true, nil, false, false, false, true, 2, 1}, 84 {"[-x]", "-", true, true, nil, false, false, false, !onWindows, 2, 1}, 85 {"[-x]", "a", false, false, nil, false, false, false, true, 2, 1}, 86 {"[a-b-d]", "a", true, true, nil, false, false, false, true, 3, 2}, 87 {"[a-b-d]", "b", true, true, nil, false, false, false, true, 3, 2}, 88 {"[a-b-d]", "-", true, true, nil, false, false, false, !onWindows, 3, 2}, 89 {"[a-b-d]", "c", false, false, nil, false, false, false, true, 3, 2}, 90 {"[a-b-x]", "x", true, true, nil, false, false, false, true, 4, 3}, 91 {"\\", "a", false, false, ErrBadPattern, false, false, true, !onWindows, 0, 0}, 92 {"[", "a", false, false, ErrBadPattern, false, false, true, true, 0, 0}, 93 {"[^", "a", false, false, ErrBadPattern, false, false, true, true, 0, 0}, 94 {"[^bc", "a", false, false, ErrBadPattern, false, false, true, true, 0, 0}, 95 {"a[", "a", false, false, ErrBadPattern, false, false, true, true, 0, 0}, 96 {"a[", "ab", false, false, ErrBadPattern, false, false, true, true, 0, 0}, 97 {"ad[", "ab", false, false, ErrBadPattern, false, false, true, true, 0, 0}, 98 {"*x", "xxx", true, true, nil, false, false, true, true, 4, 4}, 99 {"[abc]", "b", true, true, nil, false, false, true, true, 3, 3}, 100 {"**", "", true, true, nil, false, false, false, false, 38, 38}, 101 {"a/**", "a", true, true, nil, false, false, false, true, 7, 7}, 102 {"a/**", "a/", true, true, nil, false, false, false, false, 7, 7}, 103 {"a/**", "a/b", true, true, nil, false, false, false, true, 7, 7}, 104 {"a/**", "a/b/c", true, true, nil, false, false, false, true, 7, 7}, 105 {"**/c", "c", true, true, nil, !onWindows, false, false, true, 5, 4}, 106 {"**/c", "b/c", true, true, nil, !onWindows, false, false, true, 5, 4}, 107 {"**/c", "a/b/c", true, true, nil, !onWindows, false, false, true, 5, 4}, 108 {"**/c", "a/b", false, false, nil, !onWindows, false, false, true, 5, 4}, 109 {"**/c", "abcd", false, false, nil, !onWindows, false, false, true, 5, 4}, 110 {"**/c", "a/abc", false, false, nil, !onWindows, false, false, true, 5, 4}, 111 {"a/**/b", "a/b", true, true, nil, false, false, false, true, 2, 2}, 112 {"a/**/c", "a/b/c", true, true, nil, false, false, false, true, 2, 2}, 113 {"a/**/d", "a/b/c/d", true, true, nil, false, false, false, true, 1, 1}, 114 {"a/\\**", "a/b/c", false, false, nil, false, false, false, !onWindows, 0, 0}, 115 {"a/\\[*\\]", "a/bc", false, false, nil, false, false, true, !onWindows, 0, 0}, 116 // this fails the FilepathGlob test on Windows 117 {"a/b/c", "a/b//c", false, false, nil, false, false, true, !onWindows, 1, 1}, 118 // odd: Glob + filepath.Glob return results 119 {"a/", "a", false, false, nil, false, false, true, false, 0, 0}, 120 {"ab{c,d}", "abc", true, true, nil, false, true, false, true, 1, 1}, 121 {"ab{c,d,*}", "abcde", true, true, nil, false, true, false, true, 5, 5}, 122 {"ab{c,d}[", "abcd", false, false, ErrBadPattern, false, false, false, true, 0, 0}, 123 {"a{,bc}", "a", true, true, nil, false, false, false, true, 2, 2}, 124 {"a{,bc}", "abc", true, true, nil, false, false, false, true, 2, 2}, 125 {"a/{b/c,c/b}", "a/b/c", true, true, nil, false, false, false, true, 2, 2}, 126 {"a/{b/c,c/b}", "a/c/b", true, true, nil, false, false, false, true, 2, 2}, 127 {"a/a*{b,c}", "a/abc", true, true, nil, false, false, false, true, 1, 1}, 128 {"{a/{b,c},abc}", "a/b", true, true, nil, false, false, false, true, 3, 3}, 129 {"{a/{b,c},abc}", "a/c", true, true, nil, false, false, false, true, 3, 3}, 130 {"{a/{b,c},abc}", "abc", true, true, nil, false, false, false, true, 3, 3}, 131 {"{a/{b,c},abc}", "a/b/c", false, false, nil, false, false, false, true, 3, 3}, 132 {"{a/ab*}", "a/abc", true, true, nil, false, false, false, true, 1, 1}, 133 {"{a/*}", "a/b", true, true, nil, false, false, false, true, 3, 3}, 134 {"{a/abc}", "a/abc", true, true, nil, false, false, false, true, 1, 1}, 135 {"{a/b,a/c}", "a/c", true, true, nil, false, false, false, true, 2, 2}, 136 {"abc/**", "abc/b", true, true, nil, false, false, false, true, 3, 3}, 137 {"**/abc", "abc", true, true, nil, !onWindows, false, false, true, 2, 2}, 138 {"abc**", "abc/b", false, false, nil, false, false, false, true, 3, 3}, 139 {"**/*.txt", "abc/【test】.txt", true, true, nil, !onWindows, false, false, true, 1, 1}, 140 {"**/【*", "abc/【test】.txt", true, true, nil, !onWindows, false, false, true, 1, 1}, 141 {"**/{a,b}", "a/b", true, true, nil, !onWindows, false, false, true, 5, 5}, 142 // unfortunately, io/fs can't handle this, so neither can Glob =( 143 {"broken-symlink", "broken-symlink", true, true, nil, false, false, true, false, 1, 1}, 144 {"broken-symlink/*", "a", false, false, nil, false, true, true, true, 0, 0}, 145 {"broken*/*", "a", false, false, nil, false, false, true, true, 0, 0}, 146 {"working-symlink/c/*", "working-symlink/c/d", true, true, nil, false, false, true, !onWindows, 1, 1}, 147 {"working-sym*/*", "working-symlink/c", true, true, nil, false, false, true, !onWindows, 1, 1}, 148 {"b/**/f", "b/symlink-dir/f", true, true, nil, false, false, false, !onWindows, 2, 2}, 149 {"*/symlink-dir/*", "b/symlink-dir/f", true, true, nil, !onWindows, false, true, !onWindows, 2, 2}, 150 {"e/**", "e/**", true, true, nil, false, false, false, !onWindows, 11, 6}, 151 {"e/**", "e/*", true, true, nil, false, false, false, !onWindows, 11, 6}, 152 {"e/**", "e/?", true, true, nil, false, false, false, !onWindows, 11, 6}, 153 {"e/**", "e/[", true, true, nil, false, false, false, true, 11, 6}, 154 {"e/**", "e/]", true, true, nil, false, false, false, true, 11, 6}, 155 {"e/**", "e/[]", true, true, nil, false, false, false, true, 11, 6}, 156 {"e/**", "e/{", true, true, nil, false, false, false, true, 11, 6}, 157 {"e/**", "e/}", true, true, nil, false, false, false, true, 11, 6}, 158 {"e/**", "e/\\", true, true, nil, false, false, false, !onWindows, 11, 6}, 159 {"e/*", "e/*", true, true, nil, false, false, true, !onWindows, 10, 5}, 160 {"e/?", "e/?", true, true, nil, false, false, true, !onWindows, 7, 4}, 161 {"e/?", "e/*", true, true, nil, false, false, true, !onWindows, 7, 4}, 162 {"e/?", "e/[", true, true, nil, false, false, true, true, 7, 4}, 163 {"e/?", "e/]", true, true, nil, false, false, true, true, 7, 4}, 164 {"e/?", "e/{", true, true, nil, false, false, true, true, 7, 4}, 165 {"e/?", "e/}", true, true, nil, false, false, true, true, 7, 4}, 166 {"e/\\[", "e/[", true, true, nil, false, false, true, !onWindows, 1, 1}, 167 {"e/[", "e/[", false, false, ErrBadPattern, false, false, true, true, 0, 0}, 168 {"e/]", "e/]", true, true, nil, false, false, true, true, 1, 1}, 169 {"e/\\]", "e/]", true, true, nil, false, false, true, !onWindows, 1, 1}, 170 {"e/\\{", "e/{", true, true, nil, false, false, true, !onWindows, 1, 1}, 171 {"e/\\}", "e/}", true, true, nil, false, false, true, !onWindows, 1, 1}, 172 {"e/[\\*\\?]", "e/*", true, true, nil, false, false, true, !onWindows, 2, 2}, 173 {"e/[\\*\\?]", "e/?", true, true, nil, false, false, true, !onWindows, 2, 2}, 174 {"e/[\\*\\?]", "e/**", false, false, nil, false, false, true, !onWindows, 2, 2}, 175 {"e/[\\*\\?]?", "e/**", true, true, nil, false, false, true, !onWindows, 1, 1}, 176 {"e/{\\*,\\?}", "e/*", true, true, nil, false, false, false, !onWindows, 2, 2}, 177 {"e/{\\*,\\?}", "e/?", true, true, nil, false, false, false, !onWindows, 2, 2}, 178 {"e/\\*", "e/*", true, true, nil, false, false, true, !onWindows, 1, 1}, 179 {"e/\\?", "e/?", true, true, nil, false, false, true, !onWindows, 1, 1}, 180 {"e/\\?", "e/**", false, false, nil, false, false, true, !onWindows, 1, 1}, 181 {"*\\}", "}", true, true, nil, false, false, true, !onWindows, 1, 1}, 182 {"nonexistent-path", "a", false, false, nil, false, true, true, true, 0, 0}, 183 {"nonexistent-path/", "a", false, false, nil, false, true, true, true, 0, 0}, 184 {"nonexistent-path/file", "a", false, false, nil, false, true, true, true, 0, 0}, 185 {"nonexistent-path/*", "a", false, false, nil, false, true, true, true, 0, 0}, 186 {"nonexistent-path/**", "a", false, false, nil, false, true, true, true, 0, 0}, 187 {"nopermission/*", "nopermission/file", true, false, nil, true, false, true, !onWindows, 0, 0}, 188 {"nopermission/dir/", "nopermission/dir", false, false, nil, true, false, true, !onWindows, 0, 0}, 189 {"nopermission/file", "nopermission/file", true, false, nil, true, false, true, !onWindows, 0, 0}, 190 } 191 192 // Calculate the number of results that we expect 193 // WithFilesOnly at runtime and memoize them here 194 var numResultsFilesOnly []int 195 196 // Calculate the number of results that we expect 197 // WithNoFollow at runtime and memoize them here 198 var numResultsNoFollow []int 199 200 // Calculate the number of results that we expect with all 201 // of the options enabled at runtime and memoize them here 202 var numResultsAllOpts []int 203 204 func TestValidatePattern(t *testing.T) { 205 for idx, tt := range matchTests { 206 testValidatePatternWith(t, idx, tt) 207 } 208 } 209 210 func testValidatePatternWith(t *testing.T, idx int, tt MatchTest) { 211 defer func() { 212 if r := recover(); r != nil { 213 t.Errorf("#%v. Validate(%#q) panicked: %#v", idx, tt.pattern, r) 214 } 215 }() 216 217 result := ValidatePattern(tt.pattern) 218 if result != (tt.expectedErr == nil) { 219 t.Errorf("#%v. ValidatePattern(%#q) = %v want %v", idx, tt.pattern, result, !result) 220 } 221 } 222 223 func TestMatch(t *testing.T) { 224 for idx, tt := range matchTests { 225 // Since Match() always uses "/" as the separator, we 226 // don't need to worry about the tt.testOnDisk flag 227 testMatchWith(t, idx, tt) 228 } 229 } 230 231 func testMatchWith(t *testing.T, idx int, tt MatchTest) { 232 defer func() { 233 if r := recover(); r != nil { 234 t.Errorf("#%v. Match(%#q, %#q) panicked: %#v", idx, tt.pattern, tt.testPath, r) 235 } 236 }() 237 238 // Match() always uses "/" as the separator 239 ok, err := Match(tt.pattern, tt.testPath) 240 if ok != tt.shouldMatch || err != tt.expectedErr { 241 t.Errorf("#%v. Match(%#q, %#q) = %v, %v want %v, %v", idx, tt.pattern, tt.testPath, ok, err, tt.shouldMatch, tt.expectedErr) 242 } 243 244 if tt.isStandard { 245 stdOk, stdErr := path.Match(tt.pattern, tt.testPath) 246 if ok != stdOk || !compareErrors(err, stdErr) { 247 t.Errorf("#%v. Match(%#q, %#q) != path.Match(...). Got %v, %v want %v, %v", idx, tt.pattern, tt.testPath, ok, err, stdOk, stdErr) 248 } 249 } 250 } 251 252 func BenchmarkMatch(b *testing.B) { 253 b.ReportAllocs() 254 for i := 0; i < b.N; i++ { 255 for _, tt := range matchTests { 256 if tt.isStandard { 257 Match(tt.pattern, tt.testPath) 258 } 259 } 260 } 261 } 262 263 func BenchmarkGoMatch(b *testing.B) { 264 b.ReportAllocs() 265 for i := 0; i < b.N; i++ { 266 for _, tt := range matchTests { 267 if tt.isStandard { 268 path.Match(tt.pattern, tt.testPath) 269 } 270 } 271 } 272 } 273 274 func TestPathMatch(t *testing.T) { 275 for idx, tt := range matchTests { 276 // Even though we aren't actually matching paths on disk, we are using 277 // PathMatch() which will use the system's separator. As a result, any 278 // patterns that might cause problems on-disk need to also be avoided 279 // here in this test. 280 if tt.testOnDisk { 281 testPathMatchWith(t, idx, tt) 282 } 283 } 284 } 285 286 func testPathMatchWith(t *testing.T, idx int, tt MatchTest) { 287 defer func() { 288 if r := recover(); r != nil { 289 t.Errorf("#%v. Match(%#q, %#q) panicked: %#v", idx, tt.pattern, tt.testPath, r) 290 } 291 }() 292 293 pattern := filepath.FromSlash(tt.pattern) 294 testPath := filepath.FromSlash(tt.testPath) 295 ok, err := PathMatch(pattern, testPath) 296 if ok != tt.shouldMatch || err != tt.expectedErr { 297 t.Errorf("#%v. PathMatch(%#q, %#q) = %v, %v want %v, %v", idx, pattern, testPath, ok, err, tt.shouldMatch, tt.expectedErr) 298 } 299 300 if tt.isStandard { 301 stdOk, stdErr := filepath.Match(pattern, testPath) 302 if ok != stdOk || !compareErrors(err, stdErr) { 303 t.Errorf("#%v. PathMatch(%#q, %#q) != filepath.Match(...). Got %v, %v want %v, %v", idx, pattern, testPath, ok, err, stdOk, stdErr) 304 } 305 } 306 } 307 308 func TestPathMatchFake(t *testing.T) { 309 // This test fakes that our path separator is `\\` so we can test what it 310 // would be like on Windows - obviously, we don't need to do that if we 311 // actually _are_ on Windows, since TestPathMatch will cover it. 312 if onWindows { 313 return 314 } 315 316 for idx, tt := range matchTests { 317 // Even though we aren't actually matching paths on disk, we are using 318 // PathMatch() which will use the system's separator. As a result, any 319 // patterns that might cause problems on-disk need to also be avoided 320 // here in this test. 321 // On Windows, escaping is disabled. Instead, '\\' is treated as path separator. 322 // So it's not possible to match escaped wild characters. 323 if tt.testOnDisk && !strings.Contains(tt.pattern, "\\") { 324 testPathMatchFakeWith(t, idx, tt) 325 } 326 } 327 } 328 329 func testPathMatchFakeWith(t *testing.T, idx int, tt MatchTest) { 330 defer func() { 331 if r := recover(); r != nil { 332 t.Errorf("#%v. Match(%#q, %#q) panicked: %#v", idx, tt.pattern, tt.testPath, r) 333 } 334 }() 335 336 pattern := strings.ReplaceAll(tt.pattern, "/", "\\") 337 testPath := strings.ReplaceAll(tt.testPath, "/", "\\") 338 ok, err := matchWithSeparator(pattern, testPath, '\\', true) 339 if ok != tt.shouldMatch || err != tt.expectedErr { 340 t.Errorf("#%v. PathMatch(%#q, %#q) = %v, %v want %v, %v", idx, pattern, testPath, ok, err, tt.shouldMatch, tt.expectedErr) 341 } 342 } 343 344 func BenchmarkPathMatch(b *testing.B) { 345 b.ReportAllocs() 346 for i := 0; i < b.N; i++ { 347 for _, tt := range matchTests { 348 if tt.isStandard && tt.testOnDisk { 349 pattern := filepath.FromSlash(tt.pattern) 350 testPath := filepath.FromSlash(tt.testPath) 351 PathMatch(pattern, testPath) 352 } 353 } 354 } 355 } 356 357 func BenchmarkGoPathMatch(b *testing.B) { 358 b.ReportAllocs() 359 for i := 0; i < b.N; i++ { 360 for _, tt := range matchTests { 361 if tt.isStandard && tt.testOnDisk { 362 pattern := filepath.FromSlash(tt.pattern) 363 testPath := filepath.FromSlash(tt.testPath) 364 filepath.Match(pattern, testPath) 365 } 366 } 367 } 368 } 369 370 func TestGlob(t *testing.T) { 371 doGlobTest(t) 372 } 373 374 func TestGlobWithFailOnIOErrors(t *testing.T) { 375 doGlobTest(t, WithFailOnIOErrors()) 376 } 377 378 func TestGlobWithFailOnPatternNotExist(t *testing.T) { 379 doGlobTest(t, WithFailOnPatternNotExist()) 380 } 381 382 func TestGlobWithFilesOnly(t *testing.T) { 383 doGlobTest(t, WithFilesOnly()) 384 } 385 386 func TestGlobWithNoFollow(t *testing.T) { 387 doGlobTest(t, WithNoFollow()) 388 } 389 390 func TestGlobWithAllOptions(t *testing.T) { 391 doGlobTest(t, WithFailOnIOErrors(), WithFailOnPatternNotExist(), WithFilesOnly(), WithNoFollow()) 392 } 393 394 func doGlobTest(t *testing.T, opts ...GlobOption) { 395 glob := newGlob(opts...) 396 fsys := os.DirFS("test") 397 for idx, tt := range matchTests { 398 if tt.testOnDisk { 399 testGlobWith(t, idx, tt, glob, opts, fsys) 400 } 401 } 402 } 403 404 func testGlobWith(t *testing.T, idx int, tt MatchTest, g *glob, opts []GlobOption, fsys fs.FS) { 405 defer func() { 406 if r := recover(); r != nil { 407 t.Errorf("#%v. Glob(%#q, %#v) panicked: %#v", idx, tt.pattern, g, r) 408 } 409 }() 410 411 matches, err := Glob(fsys, tt.pattern, opts...) 412 verifyGlobResults(t, idx, "Glob", tt, g, fsys, matches, err) 413 if len(opts) == 0 { 414 testStandardGlob(t, idx, "Glob", tt, fsys, matches, err) 415 } 416 } 417 418 func TestGlobWalk(t *testing.T) { 419 doGlobWalkTest(t) 420 } 421 422 func TestGlobWalkWithFailOnIOErrors(t *testing.T) { 423 doGlobWalkTest(t, WithFailOnIOErrors()) 424 } 425 426 func TestGlobWalkWithFailOnPatternNotExist(t *testing.T) { 427 doGlobWalkTest(t, WithFailOnPatternNotExist()) 428 } 429 430 func TestGlobWalkWithFilesOnly(t *testing.T) { 431 doGlobWalkTest(t, WithFilesOnly()) 432 } 433 434 func TestGlobWalkWithNoFollow(t *testing.T) { 435 doGlobWalkTest(t, WithNoFollow()) 436 } 437 438 func TestGlobWalkWithAllOptions(t *testing.T) { 439 doGlobWalkTest(t, WithFailOnIOErrors(), WithFailOnPatternNotExist(), WithFilesOnly(), WithNoFollow()) 440 } 441 442 func doGlobWalkTest(t *testing.T, opts ...GlobOption) { 443 glob := newGlob(opts...) 444 fsys := os.DirFS("test") 445 for idx, tt := range matchTests { 446 if tt.testOnDisk { 447 testGlobWalkWith(t, idx, tt, glob, opts, fsys) 448 } 449 } 450 } 451 452 func testGlobWalkWith(t *testing.T, idx int, tt MatchTest, g *glob, opts []GlobOption, fsys fs.FS) { 453 defer func() { 454 if r := recover(); r != nil { 455 t.Errorf("#%v. Glob(%#q, %#v) panicked: %#v", idx, tt.pattern, opts, r) 456 } 457 }() 458 459 var matches []string 460 err := GlobWalk(fsys, tt.pattern, func(p string, d fs.DirEntry) error { 461 matches = append(matches, p) 462 return nil 463 }, opts...) 464 verifyGlobResults(t, idx, "GlobWalk", tt, g, fsys, matches, err) 465 if len(opts) == 0 { 466 testStandardGlob(t, idx, "GlobWalk", tt, fsys, matches, err) 467 } 468 } 469 470 func testStandardGlob(t *testing.T, idx int, fn string, tt MatchTest, fsys fs.FS, matches []string, err error) { 471 if tt.isStandard { 472 stdMatches, stdErr := fs.Glob(fsys, tt.pattern) 473 if !compareSlices(matches, stdMatches) || !compareErrors(err, stdErr) { 474 t.Errorf("#%v. %v(%#q) != fs.Glob(...). Got %#v, %v want %#v, %v", idx, fn, tt.pattern, matches, err, stdMatches, stdErr) 475 } 476 } 477 } 478 479 func TestFilepathGlob(t *testing.T) { 480 doFilepathGlobTest(t) 481 } 482 483 func TestFilepathGlobWithFailOnIOErrors(t *testing.T) { 484 doFilepathGlobTest(t, WithFailOnIOErrors()) 485 } 486 487 func TestFilepathGlobWithFailOnPatternNotExist(t *testing.T) { 488 doFilepathGlobTest(t, WithFailOnPatternNotExist()) 489 } 490 491 func TestFilepathGlobWithFilesOnly(t *testing.T) { 492 doFilepathGlobTest(t, WithFilesOnly()) 493 } 494 495 func TestFilepathGlobWithNoFollow(t *testing.T) { 496 doFilepathGlobTest(t, WithNoFollow()) 497 } 498 499 func doFilepathGlobTest(t *testing.T, opts ...GlobOption) { 500 glob := newGlob(opts...) 501 fsys := os.DirFS("test") 502 503 // The patterns are relative to the "test" sub-directory. 504 defer func() { 505 os.Chdir("..") 506 }() 507 os.Chdir("test") 508 509 for idx, tt := range matchTests { 510 if tt.testOnDisk { 511 ttmod := tt 512 ttmod.pattern = filepath.FromSlash(tt.pattern) 513 ttmod.testPath = filepath.FromSlash(tt.testPath) 514 testFilepathGlobWith(t, idx, ttmod, glob, opts, fsys) 515 } 516 } 517 } 518 519 func testFilepathGlobWith(t *testing.T, idx int, tt MatchTest, g *glob, opts []GlobOption, fsys fs.FS) { 520 defer func() { 521 if r := recover(); r != nil { 522 t.Errorf("#%v. FilepathGlob(%#q, %#v) panicked: %#v", idx, tt.pattern, g, r) 523 } 524 }() 525 526 matches, err := FilepathGlob(tt.pattern, opts...) 527 verifyGlobResults(t, idx, "FilepathGlob", tt, g, fsys, matches, err) 528 529 if tt.isStandard && len(opts) == 0 { 530 stdMatches, stdErr := filepath.Glob(tt.pattern) 531 if !compareSlices(matches, stdMatches) || !compareErrors(err, stdErr) { 532 t.Errorf("#%v. FilepathGlob(%#q, %#v) != filepath.Glob(...). Got %#v, %v want %#v, %v", idx, tt.pattern, g, matches, err, stdMatches, stdErr) 533 } 534 } 535 } 536 537 func verifyGlobResults(t *testing.T, idx int, fn string, tt MatchTest, g *glob, fsys fs.FS, matches []string, err error) { 538 expectedErr := tt.expectedErr 539 if g.failOnPatternNotExist && tt.expectPatternNotExist { 540 expectedErr = ErrPatternNotExist 541 } 542 543 if g.failOnIOErrors { 544 if tt.expectIOErr { 545 if err == nil { 546 t.Errorf("#%v. %v(%#q, %#v) does not have an error, but should", idx, fn, tt.pattern, g) 547 } 548 return 549 } else if err != nil && err != expectedErr { 550 t.Errorf("#%v. %v(%#q, %#v) has error %v, but should not", idx, fn, tt.pattern, g, err) 551 return 552 } 553 } 554 555 if !g.failOnPatternNotExist || !tt.expectPatternNotExist { 556 numResults := tt.numResults 557 if onWindows { 558 numResults = tt.winNumResults 559 } 560 if g.filesOnly { 561 if g.noFollow { 562 numResults = numResultsAllOpts[idx] 563 } else { 564 numResults = numResultsFilesOnly[idx] 565 } 566 } else if g.noFollow { 567 numResults = numResultsNoFollow[idx] 568 } 569 570 if len(matches) != numResults { 571 t.Errorf("#%v. %v(%#q, %#v) = %#v - should have %#v results, got %#v", idx, fn, tt.pattern, g, matches, numResults, len(matches)) 572 } 573 if !g.filesOnly && !g.noFollow && inSlice(tt.testPath, matches) != tt.shouldMatchGlob { 574 if tt.shouldMatchGlob { 575 t.Errorf("#%v. %v(%#q, %#v) = %#v - doesn't contain %v, but should", idx, fn, tt.pattern, g, matches, tt.testPath) 576 } else { 577 t.Errorf("#%v. %v(%#q, %#v) = %#v - contains %v, but shouldn't", idx, fn, tt.pattern, g, matches, tt.testPath) 578 } 579 } 580 } 581 if err != expectedErr { 582 t.Errorf("#%v. %v(%#q, %#v) has error %v, but should be %v", idx, fn, tt.pattern, g, err, expectedErr) 583 } 584 } 585 586 func TestGlobSorted(t *testing.T) { 587 fsys := os.DirFS("test") 588 expected := []string{"a", "abc", "abcd", "abcde", "abxbbxdbxebxczzx", "abxbbxdbxebxczzy", "axbxcxdxe", "axbxcxdxexxx", "a☺b"} 589 matches, err := Glob(fsys, "a*") 590 if err != nil { 591 t.Errorf("Unexpected error %v", err) 592 return 593 } 594 595 if len(matches) != len(expected) { 596 t.Errorf("Glob returned %#v; expected %#v", matches, expected) 597 return 598 } 599 for idx, match := range matches { 600 if match != expected[idx] { 601 t.Errorf("Glob returned %#v; expected %#v", matches, expected) 602 return 603 } 604 } 605 } 606 607 func BenchmarkGlob(b *testing.B) { 608 fsys := os.DirFS("test") 609 b.ReportAllocs() 610 for i := 0; i < b.N; i++ { 611 for _, tt := range matchTests { 612 if tt.isStandard && tt.testOnDisk { 613 Glob(fsys, tt.pattern) 614 } 615 } 616 } 617 } 618 619 func BenchmarkGlobWalk(b *testing.B) { 620 fsys := os.DirFS("test") 621 b.ReportAllocs() 622 for i := 0; i < b.N; i++ { 623 for _, tt := range matchTests { 624 if tt.isStandard && tt.testOnDisk { 625 GlobWalk(fsys, tt.pattern, func(p string, d fs.DirEntry) error { 626 return nil 627 }) 628 } 629 } 630 } 631 } 632 633 func BenchmarkGoGlob(b *testing.B) { 634 fsys := os.DirFS("test") 635 b.ReportAllocs() 636 for i := 0; i < b.N; i++ { 637 for _, tt := range matchTests { 638 if tt.isStandard && tt.testOnDisk { 639 fs.Glob(fsys, tt.pattern) 640 } 641 } 642 } 643 } 644 645 func compareErrors(a, b error) bool { 646 if a == nil { 647 return b == nil 648 } 649 return b != nil 650 } 651 652 func inSlice(s string, a []string) bool { 653 for _, i := range a { 654 if i == s { 655 return true 656 } 657 } 658 return false 659 } 660 661 func compareSlices(a, b []string) bool { 662 if len(a) != len(b) { 663 return false 664 } 665 666 diff := make(map[string]int, len(a)) 667 668 for _, x := range a { 669 diff[x]++ 670 } 671 672 for _, y := range b { 673 if _, ok := diff[y]; !ok { 674 return false 675 } 676 677 diff[y]-- 678 if diff[y] == 0 { 679 delete(diff, y) 680 } 681 } 682 683 return len(diff) == 0 684 } 685 686 func buildNumResults() { 687 testLen := len(matchTests) 688 numResultsFilesOnly = make([]int, testLen, testLen) 689 numResultsNoFollow = make([]int, testLen, testLen) 690 numResultsAllOpts = make([]int, testLen, testLen) 691 692 fsys := os.DirFS("test") 693 g := newGlob() 694 for idx, tt := range matchTests { 695 if tt.testOnDisk { 696 filesOnly := 0 697 noFollow := 0 698 allOpts := 0 699 GlobWalk(fsys, tt.pattern, func(p string, d fs.DirEntry) error { 700 isDir, _ := g.isDir(fsys, "", p, d) 701 if !isDir { 702 filesOnly++ 703 } 704 705 hasNoFollow := (strings.HasPrefix(tt.pattern, "working-symlink") || !strings.Contains(p, "working-symlink/")) && !strings.Contains(p, "/symlink-dir/") 706 if hasNoFollow { 707 noFollow++ 708 } 709 710 if hasNoFollow && (!isDir || p == "working-symlink") { 711 allOpts++ 712 } 713 714 return nil 715 }) 716 717 numResultsFilesOnly[idx] = filesOnly 718 numResultsNoFollow[idx] = noFollow 719 numResultsAllOpts[idx] = allOpts 720 } 721 } 722 } 723 724 func mkdirp(parts ...string) { 725 dirs := path.Join(parts...) 726 err := os.MkdirAll(dirs, 0755) 727 if err != nil { 728 log.Fatalf("Could not create test directories %v: %v\n", dirs, err) 729 } 730 } 731 732 func touch(parts ...string) { 733 filename := path.Join(parts...) 734 f, err := os.Create(filename) 735 if err != nil { 736 log.Fatalf("Could not create test file %v: %v\n", filename, err) 737 } 738 f.Close() 739 } 740 741 func symlink(oldname, newname string) { 742 // since this will only run on non-windows, we can assume "/" as path separator 743 err := os.Symlink(oldname, newname) 744 if err != nil && !os.IsExist(err) { 745 log.Fatalf("Could not create symlink %v -> %v: %v\n", oldname, newname, err) 746 } 747 } 748 749 func exists(parts ...string) bool { 750 p := path.Join(parts...) 751 _, err := os.Lstat(p) 752 return err == nil 753 } 754 755 func TestMain(m *testing.M) { 756 // create the test directory 757 mkdirp("test", "a", "b", "c") 758 mkdirp("test", "a", "c") 759 mkdirp("test", "abc") 760 mkdirp("test", "axbxcxdxe", "xxx") 761 mkdirp("test", "axbxcxdxexxx") 762 mkdirp("test", "b") 763 mkdirp("test", "e") 764 765 // create test files 766 touch("test", "a", "abc") 767 touch("test", "a", "b", "c", "d") 768 touch("test", "a", "c", "b") 769 touch("test", "abc", "b") 770 touch("test", "abcd") 771 touch("test", "abcde") 772 touch("test", "abxbbxdbxebxczzx") 773 touch("test", "abxbbxdbxebxczzy") 774 touch("test", "axbxcxdxe", "f") 775 touch("test", "axbxcxdxe", "xxx", "f") 776 touch("test", "axbxcxdxexxx", "f") 777 touch("test", "axbxcxdxexxx", "fff") 778 touch("test", "a☺b") 779 touch("test", "b", "c") 780 touch("test", "c") 781 touch("test", "x") 782 touch("test", "xxx") 783 touch("test", "z") 784 touch("test", "α") 785 touch("test", "abc", "【test】.txt") 786 787 touch("test", "e", "[") 788 touch("test", "e", "]") 789 touch("test", "e", "{") 790 touch("test", "e", "}") 791 touch("test", "e", "[]") 792 793 touch("test", "}") 794 795 if !onWindows { 796 // these files/symlinks won't work on Windows 797 touch("test", "-") 798 touch("test", "]") 799 touch("test", "e", "*") 800 touch("test", "e", "**") 801 touch("test", "e", "****") 802 touch("test", "e", "?") 803 touch("test", "e", "\\") 804 805 symlink("../axbxcxdxe/", "test/b/symlink-dir") 806 symlink("/tmp/nonexistant-file-20160902155705", "test/broken-symlink") 807 symlink("a/b", "test/working-symlink") 808 809 if !exists("test", "nopermission") { 810 mkdirp("test", "nopermission", "dir") 811 touch("test", "nopermission", "file") 812 os.Chmod(path.Join("test", "nopermission"), 0) 813 } 814 } 815 816 // initialize numResultsFilesOnly 817 buildNumResults() 818 819 os.Exit(m.Run()) 820 }