1 // Copyright 2018 Google Inc. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package pathtools 16 17 import ( 18 "os" 19 "path/filepath" 20 "reflect" 21 "syscall" 22 "testing" 23 ) 24 25 func symlinkMockFs() *mockFs { 26 files := []string{ 27 "a/a/a", 28 "a/a/f -> ../../f", 29 "b -> a", 30 "c -> a/a", 31 "d -> c", 32 "e -> a/a/a", 33 "dangling -> missing", 34 "f", 35 } 36 37 mockFiles := make(map[string][]byte) 38 39 for _, f := range files { 40 mockFiles[f] = nil 41 mockFiles[filepath.Join(pwd, "testdata", f)] = nil 42 } 43 44 return MockFs(mockFiles).(*mockFs) 45 } 46 47 func TestMockFs_followSymlinks(t *testing.T) { 48 49 testCases := []struct { 50 from, to string 51 }{ 52 {".", "."}, 53 {"/", "/"}, 54 55 {"a", "a"}, 56 {"a/a", "a/a"}, 57 {"a/a/a", "a/a/a"}, 58 {"a/a/f", "f"}, 59 60 {"b", "a"}, 61 {"b/a", "a/a"}, 62 {"b/a/a", "a/a/a"}, 63 {"b/a/f", "f"}, 64 65 {"c/a", "a/a/a"}, 66 {"c/f", "f"}, 67 68 {"d/a", "a/a/a"}, 69 {"d/f", "f"}, 70 71 {"e", "a/a/a"}, 72 73 {"f", "f"}, 74 75 {"dangling", "missing"}, 76 77 {"a/missing", "a/missing"}, 78 {"b/missing", "a/missing"}, 79 {"c/missing", "a/a/missing"}, 80 {"d/missing", "a/a/missing"}, 81 {"e/missing", "a/a/a/missing"}, 82 {"dangling/missing", "missing/missing"}, 83 84 {"a/missing/missing", "a/missing/missing"}, 85 {"b/missing/missing", "a/missing/missing"}, 86 {"c/missing/missing", "a/a/missing/missing"}, 87 {"d/missing/missing", "a/a/missing/missing"}, 88 {"e/missing/missing", "a/a/a/missing/missing"}, 89 {"dangling/missing/missing", "missing/missing/missing"}, 90 } 91 92 mock := symlinkMockFs() 93 94 for _, test := range testCases { 95 t.Run(test.from, func(t *testing.T) { 96 got := mock.followSymlinks(test.from) 97 if got != test.to { 98 t.Errorf("want: %v, got %v", test.to, got) 99 } 100 }) 101 } 102 } 103 104 func TestFs_IsDir(t *testing.T) { 105 testCases := []struct { 106 name string 107 isDir bool 108 err error 109 }{ 110 {"a", true, nil}, 111 {"a/a", true, nil}, 112 {"a/a/a", false, nil}, 113 {"a/a/f", false, nil}, 114 115 {"b", true, nil}, 116 {"b/a", true, nil}, 117 {"b/a/a", false, nil}, 118 {"b/a/f", false, nil}, 119 120 {"c", true, nil}, 121 {"c/a", false, nil}, 122 {"c/f", false, nil}, 123 124 {"d", true, nil}, 125 {"d/a", false, nil}, 126 {"d/f", false, nil}, 127 128 {"e", false, nil}, 129 130 {"f", false, nil}, 131 132 {"dangling", false, os.ErrNotExist}, 133 134 {"a/missing", false, os.ErrNotExist}, 135 {"b/missing", false, os.ErrNotExist}, 136 {"c/missing", false, os.ErrNotExist}, 137 {"d/missing", false, os.ErrNotExist}, 138 {"e/missing", false, syscall.ENOTDIR}, 139 {"dangling/missing", false, os.ErrNotExist}, 140 141 {"a/missing/missing", false, os.ErrNotExist}, 142 {"b/missing/missing", false, os.ErrNotExist}, 143 {"c/missing/missing", false, os.ErrNotExist}, 144 {"d/missing/missing", false, os.ErrNotExist}, 145 {"e/missing/missing", false, syscall.ENOTDIR}, 146 {"dangling/missing/missing", false, os.ErrNotExist}, 147 148 {"c/f/missing", false, syscall.ENOTDIR}, 149 } 150 151 mock := symlinkMockFs() 152 fsList := []FileSystem{mock, OsFs} 153 names := []string{"mock", "os"} 154 155 os.Chdir("testdata/dangling") 156 defer os.Chdir("../..") 157 158 for i, fs := range fsList { 159 t.Run(names[i], func(t *testing.T) { 160 for _, test := range testCases { 161 t.Run(test.name, func(t *testing.T) { 162 got, err := fs.IsDir(test.name) 163 checkErr(t, test.err, err) 164 if got != test.isDir { 165 t.Errorf("want: %v, got %v", test.isDir, got) 166 } 167 }) 168 } 169 }) 170 } 171 } 172 173 func TestFs_ListDirsRecursiveFollowSymlinks(t *testing.T) { 174 testCases := []struct { 175 name string 176 dirs []string 177 err error 178 }{ 179 {".", []string{".", "a", "a/a", "b", "b/a", "c", "d"}, nil}, 180 181 {"a", []string{"a", "a/a"}, nil}, 182 {"a/a", []string{"a/a"}, nil}, 183 {"a/a/a", nil, nil}, 184 185 {"b", []string{"b", "b/a"}, nil}, 186 {"b/a", []string{"b/a"}, nil}, 187 {"b/a/a", nil, nil}, 188 189 {"c", []string{"c"}, nil}, 190 {"c/a", nil, nil}, 191 192 {"d", []string{"d"}, nil}, 193 {"d/a", nil, nil}, 194 195 {"e", nil, nil}, 196 197 {"dangling", nil, os.ErrNotExist}, 198 199 {"missing", nil, os.ErrNotExist}, 200 } 201 202 mock := symlinkMockFs() 203 fsList := []FileSystem{mock, OsFs} 204 names := []string{"mock", "os"} 205 206 os.Chdir("testdata/dangling") 207 defer os.Chdir("../..") 208 209 for i, fs := range fsList { 210 t.Run(names[i], func(t *testing.T) { 211 212 for _, test := range testCases { 213 t.Run(test.name, func(t *testing.T) { 214 got, err := fs.ListDirsRecursive(test.name, FollowSymlinks) 215 checkErr(t, test.err, err) 216 if !reflect.DeepEqual(got, test.dirs) { 217 t.Errorf("want: %v, got %v", test.dirs, got) 218 } 219 }) 220 } 221 }) 222 } 223 } 224 225 func TestFs_ListDirsRecursiveDontFollowSymlinks(t *testing.T) { 226 testCases := []struct { 227 name string 228 dirs []string 229 err error 230 }{ 231 {".", []string{".", "a", "a/a"}, nil}, 232 233 {"a", []string{"a", "a/a"}, nil}, 234 {"a/a", []string{"a/a"}, nil}, 235 {"a/a/a", nil, nil}, 236 237 {"b", []string{"b", "b/a"}, nil}, 238 {"b/a", []string{"b/a"}, nil}, 239 {"b/a/a", nil, nil}, 240 241 {"c", []string{"c"}, nil}, 242 {"c/a", nil, nil}, 243 244 {"d", []string{"d"}, nil}, 245 {"d/a", nil, nil}, 246 247 {"e", nil, nil}, 248 249 {"dangling", nil, os.ErrNotExist}, 250 251 {"missing", nil, os.ErrNotExist}, 252 } 253 254 mock := symlinkMockFs() 255 fsList := []FileSystem{mock, OsFs} 256 names := []string{"mock", "os"} 257 258 os.Chdir("testdata/dangling") 259 defer os.Chdir("../..") 260 261 for i, fs := range fsList { 262 t.Run(names[i], func(t *testing.T) { 263 264 for _, test := range testCases { 265 t.Run(test.name, func(t *testing.T) { 266 got, err := fs.ListDirsRecursive(test.name, DontFollowSymlinks) 267 checkErr(t, test.err, err) 268 if !reflect.DeepEqual(got, test.dirs) { 269 t.Errorf("want: %v, got %v", test.dirs, got) 270 } 271 }) 272 } 273 }) 274 } 275 } 276 277 func TestFs_Readlink(t *testing.T) { 278 testCases := []struct { 279 from, to string 280 err error 281 }{ 282 {".", "", syscall.EINVAL}, 283 {"/", "", syscall.EINVAL}, 284 285 {"a", "", syscall.EINVAL}, 286 {"a/a", "", syscall.EINVAL}, 287 {"a/a/a", "", syscall.EINVAL}, 288 {"a/a/f", "../../f", nil}, 289 290 {"b", "a", nil}, 291 {"b/a", "", syscall.EINVAL}, 292 {"b/a/a", "", syscall.EINVAL}, 293 {"b/a/f", "../../f", nil}, 294 295 {"c", "a/a", nil}, 296 {"c/a", "", syscall.EINVAL}, 297 {"c/f", "../../f", nil}, 298 299 {"d/a", "", syscall.EINVAL}, 300 {"d/f", "../../f", nil}, 301 302 {"e", "a/a/a", nil}, 303 304 {"f", "", syscall.EINVAL}, 305 306 {"dangling", "missing", nil}, 307 308 {"a/missing", "", os.ErrNotExist}, 309 {"b/missing", "", os.ErrNotExist}, 310 {"c/missing", "", os.ErrNotExist}, 311 {"d/missing", "", os.ErrNotExist}, 312 {"e/missing", "", os.ErrNotExist}, 313 {"dangling/missing", "", os.ErrNotExist}, 314 315 {"a/missing/missing", "", os.ErrNotExist}, 316 {"b/missing/missing", "", os.ErrNotExist}, 317 {"c/missing/missing", "", os.ErrNotExist}, 318 {"d/missing/missing", "", os.ErrNotExist}, 319 {"e/missing/missing", "", os.ErrNotExist}, 320 {"dangling/missing/missing", "", os.ErrNotExist}, 321 } 322 323 mock := symlinkMockFs() 324 fsList := []FileSystem{mock, OsFs} 325 names := []string{"mock", "os"} 326 327 os.Chdir("testdata/dangling") 328 defer os.Chdir("../..") 329 330 for i, fs := range fsList { 331 t.Run(names[i], func(t *testing.T) { 332 333 for _, test := range testCases { 334 t.Run(test.from, func(t *testing.T) { 335 got, err := fs.Readlink(test.from) 336 checkErr(t, test.err, err) 337 if got != test.to { 338 t.Errorf("fs.Readlink(%q) want: %q, got %q", test.from, test.to, got) 339 } 340 }) 341 } 342 }) 343 } 344 } 345 346 func TestFs_Lstat(t *testing.T) { 347 testCases := []struct { 348 name string 349 mode os.FileMode 350 size int64 351 err error 352 }{ 353 {".", os.ModeDir, 0, nil}, 354 {"/", os.ModeDir, 0, nil}, 355 356 {"a", os.ModeDir, 0, nil}, 357 {"a/a", os.ModeDir, 0, nil}, 358 {"a/a/a", 0, 0, nil}, 359 {"a/a/f", os.ModeSymlink, 7, nil}, 360 361 {"b", os.ModeSymlink, 1, nil}, 362 {"b/a", os.ModeDir, 0, nil}, 363 {"b/a/a", 0, 0, nil}, 364 {"b/a/f", os.ModeSymlink, 7, nil}, 365 366 {"c", os.ModeSymlink, 3, nil}, 367 {"c/a", 0, 0, nil}, 368 {"c/f", os.ModeSymlink, 7, nil}, 369 370 {"d/a", 0, 0, nil}, 371 {"d/f", os.ModeSymlink, 7, nil}, 372 373 {"e", os.ModeSymlink, 5, nil}, 374 375 {"f", 0, 0, nil}, 376 377 {"dangling", os.ModeSymlink, 7, nil}, 378 379 {"a/missing", 0, 0, os.ErrNotExist}, 380 {"b/missing", 0, 0, os.ErrNotExist}, 381 {"c/missing", 0, 0, os.ErrNotExist}, 382 {"d/missing", 0, 0, os.ErrNotExist}, 383 {"e/missing", 0, 0, os.ErrNotExist}, 384 {"dangling/missing", 0, 0, os.ErrNotExist}, 385 386 {"a/missing/missing", 0, 0, os.ErrNotExist}, 387 {"b/missing/missing", 0, 0, os.ErrNotExist}, 388 {"c/missing/missing", 0, 0, os.ErrNotExist}, 389 {"d/missing/missing", 0, 0, os.ErrNotExist}, 390 {"e/missing/missing", 0, 0, os.ErrNotExist}, 391 {"dangling/missing/missing", 0, 0, os.ErrNotExist}, 392 } 393 394 mock := symlinkMockFs() 395 fsList := []FileSystem{mock, OsFs} 396 names := []string{"mock", "os"} 397 398 os.Chdir("testdata/dangling") 399 defer os.Chdir("../..") 400 401 for i, fs := range fsList { 402 t.Run(names[i], func(t *testing.T) { 403 404 for _, test := range testCases { 405 t.Run(test.name, func(t *testing.T) { 406 got, err := fs.Lstat(test.name) 407 checkErr(t, test.err, err) 408 if err != nil { 409 return 410 } 411 if got.Mode()&os.ModeType != test.mode { 412 t.Errorf("fs.Lstat(%q).Mode()&os.ModeType want: %x, got %x", 413 test.name, test.mode, got.Mode()&os.ModeType) 414 } 415 if test.mode == 0 && got.Size() != test.size { 416 t.Errorf("fs.Lstat(%q).Size() want: %d, got %d", test.name, test.size, got.Size()) 417 } 418 }) 419 } 420 }) 421 } 422 } 423 424 func TestFs_Stat(t *testing.T) { 425 testCases := []struct { 426 name string 427 mode os.FileMode 428 size int64 429 err error 430 }{ 431 {".", os.ModeDir, 0, nil}, 432 {"/", os.ModeDir, 0, nil}, 433 434 {"a", os.ModeDir, 0, nil}, 435 {"a/a", os.ModeDir, 0, nil}, 436 {"a/a/a", 0, 0, nil}, 437 {"a/a/f", 0, 0, nil}, 438 439 {"b", os.ModeDir, 0, nil}, 440 {"b/a", os.ModeDir, 0, nil}, 441 {"b/a/a", 0, 0, nil}, 442 {"b/a/f", 0, 0, nil}, 443 444 {"c", os.ModeDir, 0, nil}, 445 {"c/a", 0, 0, nil}, 446 {"c/f", 0, 0, nil}, 447 448 {"d/a", 0, 0, nil}, 449 {"d/f", 0, 0, nil}, 450 451 {"e", 0, 0, nil}, 452 453 {"f", 0, 0, nil}, 454 455 {"dangling", 0, 0, os.ErrNotExist}, 456 457 {"a/missing", 0, 0, os.ErrNotExist}, 458 {"b/missing", 0, 0, os.ErrNotExist}, 459 {"c/missing", 0, 0, os.ErrNotExist}, 460 {"d/missing", 0, 0, os.ErrNotExist}, 461 {"e/missing", 0, 0, os.ErrNotExist}, 462 {"dangling/missing", 0, 0, os.ErrNotExist}, 463 464 {"a/missing/missing", 0, 0, os.ErrNotExist}, 465 {"b/missing/missing", 0, 0, os.ErrNotExist}, 466 {"c/missing/missing", 0, 0, os.ErrNotExist}, 467 {"d/missing/missing", 0, 0, os.ErrNotExist}, 468 {"e/missing/missing", 0, 0, os.ErrNotExist}, 469 {"dangling/missing/missing", 0, 0, os.ErrNotExist}, 470 } 471 472 mock := symlinkMockFs() 473 fsList := []FileSystem{mock, OsFs} 474 names := []string{"mock", "os"} 475 476 os.Chdir("testdata/dangling") 477 defer os.Chdir("../..") 478 479 for i, fs := range fsList { 480 t.Run(names[i], func(t *testing.T) { 481 482 for _, test := range testCases { 483 t.Run(test.name, func(t *testing.T) { 484 got, err := fs.Stat(test.name) 485 checkErr(t, test.err, err) 486 if err != nil { 487 return 488 } 489 if got.Mode()&os.ModeType != test.mode { 490 t.Errorf("fs.Stat(%q).Mode()&os.ModeType want: %x, got %x", 491 test.name, test.mode, got.Mode()&os.ModeType) 492 } 493 if test.mode == 0 && got.Size() != test.size { 494 t.Errorf("fs.Stat(%q).Size() want: %d, got %d", test.name, test.size, got.Size()) 495 } 496 }) 497 } 498 }) 499 } 500 } 501 502 func TestMockFs_glob(t *testing.T) { 503 testCases := []struct { 504 pattern string 505 files []string 506 }{ 507 {"*", []string{"a", "b", "c", "d", "dangling", "e", "f"}}, 508 {"./*", []string{"a", "b", "c", "d", "dangling", "e", "f"}}, 509 {"a", []string{"a"}}, 510 {"a/a", []string{"a/a"}}, 511 {"a/*", []string{"a/a"}}, 512 {"a/a/a", []string{"a/a/a"}}, 513 {"a/a/f", []string{"a/a/f"}}, 514 {"a/a/*", []string{"a/a/a", "a/a/f"}}, 515 516 {"b", []string{"b"}}, 517 {"b/a", []string{"b/a"}}, 518 {"b/*", []string{"b/a"}}, 519 {"b/a/a", []string{"b/a/a"}}, 520 {"b/a/f", []string{"b/a/f"}}, 521 {"b/a/*", []string{"b/a/a", "b/a/f"}}, 522 523 {"c", []string{"c"}}, 524 {"c/a", []string{"c/a"}}, 525 {"c/f", []string{"c/f"}}, 526 {"c/*", []string{"c/a", "c/f"}}, 527 528 {"d", []string{"d"}}, 529 {"d/a", []string{"d/a"}}, 530 {"d/f", []string{"d/f"}}, 531 {"d/*", []string{"d/a", "d/f"}}, 532 533 {"e", []string{"e"}}, 534 535 {"dangling", []string{"dangling"}}, 536 537 {"missing", nil}, 538 } 539 540 mock := symlinkMockFs() 541 fsList := []FileSystem{mock, OsFs} 542 names := []string{"mock", "os"} 543 544 os.Chdir("testdata/dangling") 545 defer os.Chdir("../..") 546 547 for i, fs := range fsList { 548 t.Run(names[i], func(t *testing.T) { 549 for _, test := range testCases { 550 t.Run(test.pattern, func(t *testing.T) { 551 got, err := fs.glob(test.pattern) 552 if err != nil { 553 t.Fatal(err) 554 } 555 if !reflect.DeepEqual(got, test.files) { 556 t.Errorf("want: %v, got %v", test.files, got) 557 } 558 }) 559 } 560 }) 561 } 562 } 563 564 func syscallError(err error) error { 565 if serr, ok := err.(*os.SyscallError); ok { 566 return serr.Err.(syscall.Errno) 567 } else if serr, ok := err.(syscall.Errno); ok { 568 return serr 569 } else { 570 return nil 571 } 572 } 573 574 func checkErr(t *testing.T, want, got error) { 575 t.Helper() 576 if (got != nil) != (want != nil) { 577 t.Fatalf("want: %v, got %v", want, got) 578 } 579 580 if os.IsNotExist(got) == os.IsNotExist(want) { 581 return 582 } 583 584 if syscallError(got) == syscallError(want) { 585 return 586 } 587 588 t.Fatalf("want: %v, got %v", want, got) 589 } 590