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