github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/pkg/fileutils/fileutils_test.go (about) 1 package fileutils // import "github.com/demonoid81/moby/pkg/fileutils" 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path" 8 "path/filepath" 9 "runtime" 10 "strings" 11 "testing" 12 13 "gotest.tools/v3/assert" 14 is "gotest.tools/v3/assert/cmp" 15 ) 16 17 // CopyFile with invalid src 18 func TestCopyFileWithInvalidSrc(t *testing.T) { 19 tempFolder, err := ioutil.TempDir("", "docker-fileutils-test") // #nosec G303 20 defer os.RemoveAll(tempFolder) 21 if err != nil { 22 t.Fatal(err) 23 } 24 bytes, err := CopyFile("/invalid/file/path", path.Join(tempFolder, "dest")) 25 if err == nil { 26 t.Fatal("Should have fail to copy an invalid src file") 27 } 28 if bytes != 0 { 29 t.Fatal("Should have written 0 bytes") 30 } 31 32 } 33 34 // CopyFile with invalid dest 35 func TestCopyFileWithInvalidDest(t *testing.T) { 36 tempFolder, err := ioutil.TempDir("", "docker-fileutils-test") 37 defer os.RemoveAll(tempFolder) 38 if err != nil { 39 t.Fatal(err) 40 } 41 src := path.Join(tempFolder, "file") 42 err = ioutil.WriteFile(src, []byte("content"), 0740) 43 if err != nil { 44 t.Fatal(err) 45 } 46 bytes, err := CopyFile(src, path.Join(tempFolder, "/invalid/dest/path")) 47 if err == nil { 48 t.Fatal("Should have fail to copy an invalid src file") 49 } 50 if bytes != 0 { 51 t.Fatal("Should have written 0 bytes") 52 } 53 54 } 55 56 // CopyFile with same src and dest 57 func TestCopyFileWithSameSrcAndDest(t *testing.T) { 58 tempFolder, err := ioutil.TempDir("", "docker-fileutils-test") 59 defer os.RemoveAll(tempFolder) 60 if err != nil { 61 t.Fatal(err) 62 } 63 file := path.Join(tempFolder, "file") 64 err = ioutil.WriteFile(file, []byte("content"), 0740) 65 if err != nil { 66 t.Fatal(err) 67 } 68 bytes, err := CopyFile(file, file) 69 if err != nil { 70 t.Fatal(err) 71 } 72 if bytes != 0 { 73 t.Fatal("Should have written 0 bytes as it is the same file.") 74 } 75 } 76 77 // CopyFile with same src and dest but path is different and not clean 78 func TestCopyFileWithSameSrcAndDestWithPathNameDifferent(t *testing.T) { 79 tempFolder, err := ioutil.TempDir("", "docker-fileutils-test") 80 defer os.RemoveAll(tempFolder) 81 if err != nil { 82 t.Fatal(err) 83 } 84 testFolder := path.Join(tempFolder, "test") 85 err = os.MkdirAll(testFolder, 0740) 86 if err != nil { 87 t.Fatal(err) 88 } 89 file := path.Join(testFolder, "file") 90 sameFile := testFolder + "/../test/file" 91 err = ioutil.WriteFile(file, []byte("content"), 0740) 92 if err != nil { 93 t.Fatal(err) 94 } 95 bytes, err := CopyFile(file, sameFile) 96 if err != nil { 97 t.Fatal(err) 98 } 99 if bytes != 0 { 100 t.Fatal("Should have written 0 bytes as it is the same file.") 101 } 102 } 103 104 func TestCopyFile(t *testing.T) { 105 tempFolder, err := ioutil.TempDir("", "docker-fileutils-test") 106 defer os.RemoveAll(tempFolder) 107 if err != nil { 108 t.Fatal(err) 109 } 110 src := path.Join(tempFolder, "src") 111 dest := path.Join(tempFolder, "dest") 112 ioutil.WriteFile(src, []byte("content"), 0777) 113 ioutil.WriteFile(dest, []byte("destContent"), 0777) 114 bytes, err := CopyFile(src, dest) 115 if err != nil { 116 t.Fatal(err) 117 } 118 if bytes != 7 { 119 t.Fatalf("Should have written %d bytes but wrote %d", 7, bytes) 120 } 121 actual, err := ioutil.ReadFile(dest) 122 if err != nil { 123 t.Fatal(err) 124 } 125 if string(actual) != "content" { 126 t.Fatalf("Dest content was '%s', expected '%s'", string(actual), "content") 127 } 128 } 129 130 // Reading a symlink to a directory must return the directory 131 func TestReadSymlinkedDirectoryExistingDirectory(t *testing.T) { 132 // TODO Windows: Port this test 133 if runtime.GOOS == "windows" { 134 t.Skip("Needs porting to Windows") 135 } 136 var err error 137 if err = os.Mkdir("/tmp/testReadSymlinkToExistingDirectory", 0777); err != nil { 138 t.Errorf("failed to create directory: %s", err) 139 } 140 141 if err = os.Symlink("/tmp/testReadSymlinkToExistingDirectory", "/tmp/dirLinkTest"); err != nil { 142 t.Errorf("failed to create symlink: %s", err) 143 } 144 145 var path string 146 if path, err = ReadSymlinkedDirectory("/tmp/dirLinkTest"); err != nil { 147 t.Fatalf("failed to read symlink to directory: %s", err) 148 } 149 150 if path != "/tmp/testReadSymlinkToExistingDirectory" { 151 t.Fatalf("symlink returned unexpected directory: %s", path) 152 } 153 154 if err = os.Remove("/tmp/testReadSymlinkToExistingDirectory"); err != nil { 155 t.Errorf("failed to remove temporary directory: %s", err) 156 } 157 158 if err = os.Remove("/tmp/dirLinkTest"); err != nil { 159 t.Errorf("failed to remove symlink: %s", err) 160 } 161 } 162 163 // Reading a non-existing symlink must fail 164 func TestReadSymlinkedDirectoryNonExistingSymlink(t *testing.T) { 165 var path string 166 var err error 167 if path, err = ReadSymlinkedDirectory("/tmp/test/foo/Non/ExistingPath"); err == nil { 168 t.Fatalf("error expected for non-existing symlink") 169 } 170 171 if path != "" { 172 t.Fatalf("expected empty path, but '%s' was returned", path) 173 } 174 } 175 176 // Reading a symlink to a file must fail 177 func TestReadSymlinkedDirectoryToFile(t *testing.T) { 178 // TODO Windows: Port this test 179 if runtime.GOOS == "windows" { 180 t.Skip("Needs porting to Windows") 181 } 182 var err error 183 var file *os.File 184 185 // #nosec G303 186 if file, err = os.Create("/tmp/testReadSymlinkToFile"); err != nil { 187 t.Fatalf("failed to create file: %s", err) 188 } 189 190 file.Close() 191 192 if err = os.Symlink("/tmp/testReadSymlinkToFile", "/tmp/fileLinkTest"); err != nil { 193 t.Errorf("failed to create symlink: %s", err) 194 } 195 196 var path string 197 if path, err = ReadSymlinkedDirectory("/tmp/fileLinkTest"); err == nil { 198 t.Fatalf("ReadSymlinkedDirectory on a symlink to a file should've failed") 199 } 200 201 if path != "" { 202 t.Fatalf("path should've been empty: %s", path) 203 } 204 205 if err = os.Remove("/tmp/testReadSymlinkToFile"); err != nil { 206 t.Errorf("failed to remove file: %s", err) 207 } 208 209 if err = os.Remove("/tmp/fileLinkTest"); err != nil { 210 t.Errorf("failed to remove symlink: %s", err) 211 } 212 } 213 214 func TestWildcardMatches(t *testing.T) { 215 match, _ := Matches("fileutils.go", []string{"*"}) 216 if !match { 217 t.Errorf("failed to get a wildcard match, got %v", match) 218 } 219 } 220 221 // A simple pattern match should return true. 222 func TestPatternMatches(t *testing.T) { 223 match, _ := Matches("fileutils.go", []string{"*.go"}) 224 if !match { 225 t.Errorf("failed to get a match, got %v", match) 226 } 227 } 228 229 // An exclusion followed by an inclusion should return true. 230 func TestExclusionPatternMatchesPatternBefore(t *testing.T) { 231 match, _ := Matches("fileutils.go", []string{"!fileutils.go", "*.go"}) 232 if !match { 233 t.Errorf("failed to get true match on exclusion pattern, got %v", match) 234 } 235 } 236 237 // A folder pattern followed by an exception should return false. 238 func TestPatternMatchesFolderExclusions(t *testing.T) { 239 match, _ := Matches("docs/README.md", []string{"docs", "!docs/README.md"}) 240 if match { 241 t.Errorf("failed to get a false match on exclusion pattern, got %v", match) 242 } 243 } 244 245 // A folder pattern followed by an exception should return false. 246 func TestPatternMatchesFolderWithSlashExclusions(t *testing.T) { 247 match, _ := Matches("docs/README.md", []string{"docs/", "!docs/README.md"}) 248 if match { 249 t.Errorf("failed to get a false match on exclusion pattern, got %v", match) 250 } 251 } 252 253 // A folder pattern followed by an exception should return false. 254 func TestPatternMatchesFolderWildcardExclusions(t *testing.T) { 255 match, _ := Matches("docs/README.md", []string{"docs/*", "!docs/README.md"}) 256 if match { 257 t.Errorf("failed to get a false match on exclusion pattern, got %v", match) 258 } 259 } 260 261 // A pattern followed by an exclusion should return false. 262 func TestExclusionPatternMatchesPatternAfter(t *testing.T) { 263 match, _ := Matches("fileutils.go", []string{"*.go", "!fileutils.go"}) 264 if match { 265 t.Errorf("failed to get false match on exclusion pattern, got %v", match) 266 } 267 } 268 269 // A filename evaluating to . should return false. 270 func TestExclusionPatternMatchesWholeDirectory(t *testing.T) { 271 match, _ := Matches(".", []string{"*.go"}) 272 if match { 273 t.Errorf("failed to get false match on ., got %v", match) 274 } 275 } 276 277 // A single ! pattern should return an error. 278 func TestSingleExclamationError(t *testing.T) { 279 _, err := Matches("fileutils.go", []string{"!"}) 280 if err == nil { 281 t.Errorf("failed to get an error for a single exclamation point, got %v", err) 282 } 283 } 284 285 // Matches with no patterns 286 func TestMatchesWithNoPatterns(t *testing.T) { 287 matches, err := Matches("/any/path/there", []string{}) 288 if err != nil { 289 t.Fatal(err) 290 } 291 if matches { 292 t.Fatalf("Should not have match anything") 293 } 294 } 295 296 // Matches with malformed patterns 297 func TestMatchesWithMalformedPatterns(t *testing.T) { 298 matches, err := Matches("/any/path/there", []string{"["}) 299 if err == nil { 300 t.Fatal("Should have failed because of a malformed syntax in the pattern") 301 } 302 if matches { 303 t.Fatalf("Should not have match anything") 304 } 305 } 306 307 type matchesTestCase struct { 308 pattern string 309 text string 310 pass bool 311 } 312 313 func TestMatches(t *testing.T) { 314 tests := []matchesTestCase{ 315 {"**", "file", true}, 316 {"**", "file/", true}, 317 {"**/", "file", true}, // weird one 318 {"**/", "file/", true}, 319 {"**", "/", true}, 320 {"**/", "/", true}, 321 {"**", "dir/file", true}, 322 {"**/", "dir/file", true}, 323 {"**", "dir/file/", true}, 324 {"**/", "dir/file/", true}, 325 {"**/**", "dir/file", true}, 326 {"**/**", "dir/file/", true}, 327 {"dir/**", "dir/file", true}, 328 {"dir/**", "dir/file/", true}, 329 {"dir/**", "dir/dir2/file", true}, 330 {"dir/**", "dir/dir2/file/", true}, 331 {"**/dir2/*", "dir/dir2/file", true}, 332 {"**/dir2/*", "dir/dir2/file/", true}, 333 {"**/dir2/**", "dir/dir2/dir3/file", true}, 334 {"**/dir2/**", "dir/dir2/dir3/file/", true}, 335 {"**file", "file", true}, 336 {"**file", "dir/file", true}, 337 {"**/file", "dir/file", true}, 338 {"**file", "dir/dir/file", true}, 339 {"**/file", "dir/dir/file", true}, 340 {"**/file*", "dir/dir/file", true}, 341 {"**/file*", "dir/dir/file.txt", true}, 342 {"**/file*txt", "dir/dir/file.txt", true}, 343 {"**/file*.txt", "dir/dir/file.txt", true}, 344 {"**/file*.txt*", "dir/dir/file.txt", true}, 345 {"**/**/*.txt", "dir/dir/file.txt", true}, 346 {"**/**/*.txt2", "dir/dir/file.txt", false}, 347 {"**/*.txt", "file.txt", true}, 348 {"**/**/*.txt", "file.txt", true}, 349 {"a**/*.txt", "a/file.txt", true}, 350 {"a**/*.txt", "a/dir/file.txt", true}, 351 {"a**/*.txt", "a/dir/dir/file.txt", true}, 352 {"a/*.txt", "a/dir/file.txt", false}, 353 {"a/*.txt", "a/file.txt", true}, 354 {"a/*.txt**", "a/file.txt", true}, 355 {"a[b-d]e", "ae", false}, 356 {"a[b-d]e", "ace", true}, 357 {"a[b-d]e", "aae", false}, 358 {"a[^b-d]e", "aze", true}, 359 {".*", ".foo", true}, 360 {".*", "foo", false}, 361 {"abc.def", "abcdef", false}, 362 {"abc.def", "abc.def", true}, 363 {"abc.def", "abcZdef", false}, 364 {"abc?def", "abcZdef", true}, 365 {"abc?def", "abcdef", false}, 366 {"a\\\\", "a\\", true}, 367 {"**/foo/bar", "foo/bar", true}, 368 {"**/foo/bar", "dir/foo/bar", true}, 369 {"**/foo/bar", "dir/dir2/foo/bar", true}, 370 {"abc/**", "abc", false}, 371 {"abc/**", "abc/def", true}, 372 {"abc/**", "abc/def/ghi", true}, 373 {"**/.foo", ".foo", true}, 374 {"**/.foo", "bar.foo", false}, 375 } 376 377 if runtime.GOOS != "windows" { 378 tests = append(tests, []matchesTestCase{ 379 {"a\\*b", "a*b", true}, 380 {"a\\", "a", false}, 381 {"a\\", "a\\", false}, 382 }...) 383 } 384 385 for _, test := range tests { 386 desc := fmt.Sprintf("pattern=%q text=%q", test.pattern, test.text) 387 pm, err := NewPatternMatcher([]string{test.pattern}) 388 assert.NilError(t, err, desc) 389 res, _ := pm.Matches(test.text) 390 assert.Check(t, is.Equal(test.pass, res), desc) 391 } 392 } 393 394 func TestCleanPatterns(t *testing.T) { 395 patterns := []string{"docs", "config"} 396 pm, err := NewPatternMatcher(patterns) 397 if err != nil { 398 t.Fatalf("invalid pattern %v", patterns) 399 } 400 cleaned := pm.Patterns() 401 if len(cleaned) != 2 { 402 t.Errorf("expected 2 element slice, got %v", len(cleaned)) 403 } 404 } 405 406 func TestCleanPatternsStripEmptyPatterns(t *testing.T) { 407 patterns := []string{"docs", "config", ""} 408 pm, err := NewPatternMatcher(patterns) 409 if err != nil { 410 t.Fatalf("invalid pattern %v", patterns) 411 } 412 cleaned := pm.Patterns() 413 if len(cleaned) != 2 { 414 t.Errorf("expected 2 element slice, got %v", len(cleaned)) 415 } 416 } 417 418 func TestCleanPatternsExceptionFlag(t *testing.T) { 419 patterns := []string{"docs", "!docs/README.md"} 420 pm, err := NewPatternMatcher(patterns) 421 if err != nil { 422 t.Fatalf("invalid pattern %v", patterns) 423 } 424 if !pm.Exclusions() { 425 t.Errorf("expected exceptions to be true, got %v", pm.Exclusions()) 426 } 427 } 428 429 func TestCleanPatternsLeadingSpaceTrimmed(t *testing.T) { 430 patterns := []string{"docs", " !docs/README.md"} 431 pm, err := NewPatternMatcher(patterns) 432 if err != nil { 433 t.Fatalf("invalid pattern %v", patterns) 434 } 435 if !pm.Exclusions() { 436 t.Errorf("expected exceptions to be true, got %v", pm.Exclusions()) 437 } 438 } 439 440 func TestCleanPatternsTrailingSpaceTrimmed(t *testing.T) { 441 patterns := []string{"docs", "!docs/README.md "} 442 pm, err := NewPatternMatcher(patterns) 443 if err != nil { 444 t.Fatalf("invalid pattern %v", patterns) 445 } 446 if !pm.Exclusions() { 447 t.Errorf("expected exceptions to be true, got %v", pm.Exclusions()) 448 } 449 } 450 451 func TestCleanPatternsErrorSingleException(t *testing.T) { 452 patterns := []string{"!"} 453 _, err := NewPatternMatcher(patterns) 454 if err == nil { 455 t.Errorf("expected error on single exclamation point, got %v", err) 456 } 457 } 458 459 func TestCreateIfNotExistsDir(t *testing.T) { 460 tempFolder, err := ioutil.TempDir("", "docker-fileutils-test") 461 if err != nil { 462 t.Fatal(err) 463 } 464 defer os.RemoveAll(tempFolder) 465 466 folderToCreate := filepath.Join(tempFolder, "tocreate") 467 468 if err := CreateIfNotExists(folderToCreate, true); err != nil { 469 t.Fatal(err) 470 } 471 fileinfo, err := os.Stat(folderToCreate) 472 if err != nil { 473 t.Fatalf("Should have create a folder, got %v", err) 474 } 475 476 if !fileinfo.IsDir() { 477 t.Fatalf("Should have been a dir, seems it's not") 478 } 479 } 480 481 func TestCreateIfNotExistsFile(t *testing.T) { 482 tempFolder, err := ioutil.TempDir("", "docker-fileutils-test") 483 if err != nil { 484 t.Fatal(err) 485 } 486 defer os.RemoveAll(tempFolder) 487 488 fileToCreate := filepath.Join(tempFolder, "file/to/create") 489 490 if err := CreateIfNotExists(fileToCreate, false); err != nil { 491 t.Fatal(err) 492 } 493 fileinfo, err := os.Stat(fileToCreate) 494 if err != nil { 495 t.Fatalf("Should have create a file, got %v", err) 496 } 497 498 if fileinfo.IsDir() { 499 t.Fatalf("Should have been a file, seems it's not") 500 } 501 } 502 503 // These matchTests are stolen from go's filepath Match tests. 504 type matchTest struct { 505 pattern, s string 506 match bool 507 err error 508 } 509 510 var matchTests = []matchTest{ 511 {"abc", "abc", true, nil}, 512 {"*", "abc", true, nil}, 513 {"*c", "abc", true, nil}, 514 {"a*", "a", true, nil}, 515 {"a*", "abc", true, nil}, 516 {"a*", "ab/c", true, nil}, 517 {"a*/b", "abc/b", true, nil}, 518 {"a*/b", "a/c/b", false, nil}, 519 {"a*b*c*d*e*/f", "axbxcxdxe/f", true, nil}, 520 {"a*b*c*d*e*/f", "axbxcxdxexxx/f", true, nil}, 521 {"a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false, nil}, 522 {"a*b*c*d*e*/f", "axbxcxdxexxx/fff", false, nil}, 523 {"a*b?c*x", "abxbbxdbxebxczzx", true, nil}, 524 {"a*b?c*x", "abxbbxdbxebxczzy", false, nil}, 525 {"ab[c]", "abc", true, nil}, 526 {"ab[b-d]", "abc", true, nil}, 527 {"ab[e-g]", "abc", false, nil}, 528 {"ab[^c]", "abc", false, nil}, 529 {"ab[^b-d]", "abc", false, nil}, 530 {"ab[^e-g]", "abc", true, nil}, 531 {"a\\*b", "a*b", true, nil}, 532 {"a\\*b", "ab", false, nil}, 533 {"a?b", "a☺b", true, nil}, 534 {"a[^a]b", "a☺b", true, nil}, 535 {"a???b", "a☺b", false, nil}, 536 {"a[^a][^a][^a]b", "a☺b", false, nil}, 537 {"[a-ζ]*", "α", true, nil}, 538 {"*[a-ζ]", "A", false, nil}, 539 {"a?b", "a/b", false, nil}, 540 {"a*b", "a/b", false, nil}, 541 {"[\\]a]", "]", true, nil}, 542 {"[\\-]", "-", true, nil}, 543 {"[x\\-]", "x", true, nil}, 544 {"[x\\-]", "-", true, nil}, 545 {"[x\\-]", "z", false, nil}, 546 {"[\\-x]", "x", true, nil}, 547 {"[\\-x]", "-", true, nil}, 548 {"[\\-x]", "a", false, nil}, 549 {"[]a]", "]", false, filepath.ErrBadPattern}, 550 {"[-]", "-", false, filepath.ErrBadPattern}, 551 {"[x-]", "x", false, filepath.ErrBadPattern}, 552 {"[x-]", "-", false, filepath.ErrBadPattern}, 553 {"[x-]", "z", false, filepath.ErrBadPattern}, 554 {"[-x]", "x", false, filepath.ErrBadPattern}, 555 {"[-x]", "-", false, filepath.ErrBadPattern}, 556 {"[-x]", "a", false, filepath.ErrBadPattern}, 557 {"\\", "a", false, filepath.ErrBadPattern}, 558 {"[a-b-c]", "a", false, filepath.ErrBadPattern}, 559 {"[", "a", false, filepath.ErrBadPattern}, 560 {"[^", "a", false, filepath.ErrBadPattern}, 561 {"[^bc", "a", false, filepath.ErrBadPattern}, 562 {"a[", "a", false, filepath.ErrBadPattern}, // was nil but IMO its wrong 563 {"a[", "ab", false, filepath.ErrBadPattern}, 564 {"*x", "xxx", true, nil}, 565 } 566 567 func errp(e error) string { 568 if e == nil { 569 return "<nil>" 570 } 571 return e.Error() 572 } 573 574 // TestMatch test's our version of filepath.Match, called regexpMatch. 575 func TestMatch(t *testing.T) { 576 for _, tt := range matchTests { 577 pattern := tt.pattern 578 s := tt.s 579 if runtime.GOOS == "windows" { 580 if strings.Contains(pattern, "\\") { 581 // no escape allowed on windows. 582 continue 583 } 584 pattern = filepath.Clean(pattern) 585 s = filepath.Clean(s) 586 } 587 ok, err := Matches(s, []string{pattern}) 588 if ok != tt.match || err != tt.err { 589 t.Fatalf("Match(%#q, %#q) = %v, %q want %v, %q", pattern, s, ok, errp(err), tt.match, errp(tt.err)) 590 } 591 } 592 }