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