Home | History | Annotate | Download | only in pathtools
      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