github.com/wrdlbrnft/go-gitignore@v0.0.0-20201129201858-74ef740b8b77/repository_test.go (about) 1 package gitignore_test 2 3 import ( 4 "os" 5 "path/filepath" 6 "testing" 7 8 "github.com/denormal/go-gitignore" 9 ) 10 11 type repositorytest struct { 12 file string 13 directory string 14 cache gitignore.Cache 15 cached bool 16 error func(e gitignore.Error) bool 17 errors []gitignore.Error 18 bad int 19 instance func(string) (gitignore.GitIgnore, error) 20 exclude string 21 gitdir string 22 } // repostorytest{} 23 24 func (r *repositorytest) create(path string, gitdir bool) (gitignore.GitIgnore, error) { 25 // if we have an error handler, reset the list of errors 26 if r.error != nil { 27 r.errors = make([]gitignore.Error, 0) 28 } 29 30 if r.file == gitignore.File || r.file == "" { 31 // should we create the global exclude file 32 r.gitdir = os.Getenv("GIT_DIR") 33 if gitdir { 34 // create a temporary file for the global exclude file 35 _exclude, _err := exclude(_GITEXCLUDE) 36 if _err != nil { 37 return nil, _err 38 } 39 40 // extract the current value of the GIT_DIR environment variable 41 // and set the value to be that of the temporary file 42 r.exclude = _exclude 43 _err = os.Setenv("GIT_DIR", r.exclude) 44 if _err != nil { 45 return nil, _err 46 } 47 } else { 48 _err := os.Unsetenv("GIT_DIR") 49 if _err != nil { 50 return nil, _err 51 } 52 } 53 } 54 55 // attempt to create the GitIgnore instance 56 _repository, _err := r.instance(path) 57 58 // if we encountered errors, and the first error has a zero position 59 // then it represents a file access error 60 // - extract the error and return it 61 // - remove it from the list of errors 62 if len(r.errors) > 0 { 63 if r.errors[0].Position().Zero() { 64 _err = r.errors[0].Underlying() 65 r.errors = r.errors[1:] 66 } 67 } 68 69 // return the GitIgnore instance 70 return _repository, _err 71 } // create() 72 73 func (r *repositorytest) destroy() { 74 // remove the temporary files and directories 75 for _, _path := range []string{r.directory, r.exclude} { 76 if _path != "" { 77 defer os.RemoveAll(_path) 78 } 79 } 80 81 if r.file == gitignore.File || r.file == "" { 82 // reset the GIT_DIR environment variable 83 if r.gitdir == "" { 84 defer os.Unsetenv("GIT_DIR") 85 } else { 86 defer os.Setenv("GIT_DIR", r.gitdir) 87 } 88 } 89 } // destroy() 90 91 type invalidtest struct { 92 *repositorytest 93 tag string 94 match func() gitignore.Match 95 } // invalidtest{} 96 97 func TestRepository(t *testing.T) { 98 _test := &repositorytest{} 99 _test.bad = _GITREPOSITORYERRORS 100 _test.instance = func(path string) (gitignore.GitIgnore, error) { 101 return gitignore.NewRepository(path) 102 } 103 104 // perform the repository tests 105 repository(t, _test, _REPOSITORYMATCHES) 106 107 // remove the temporary directory used for this test 108 defer _test.destroy() 109 } // TestRepository() 110 111 func TestRepositoryWithFile(t *testing.T) { 112 _test := &repositorytest{} 113 _test.bad = _GITREPOSITORYERRORS 114 _test.file = gitignore.File + "-with-file" 115 _test.instance = func(path string) (gitignore.GitIgnore, error) { 116 return gitignore.NewRepositoryWithFile(path, _test.file) 117 } 118 119 // perform the repository tests 120 repository(t, _test, _REPOSITORYMATCHES) 121 122 // remove the temporary directory used for this test 123 defer _test.destroy() 124 } // TestRepositoryWithFile() 125 126 func TestRepositoryWithErrors(t *testing.T) { 127 _test := &repositorytest{} 128 _test.bad = _GITREPOSITORYERRORS 129 _test.file = gitignore.File + "-with-errors" 130 _test.error = func(e gitignore.Error) bool { 131 _test.errors = append(_test.errors, e) 132 return true 133 } 134 _test.instance = func(path string) (gitignore.GitIgnore, error) { 135 return gitignore.NewRepositoryWithErrors( 136 path, _test.file, _test.error, 137 ), nil 138 } 139 140 // perform the repository tests 141 repository(t, _test, _REPOSITORYMATCHES) 142 143 // remove the temporary directory used for this test 144 defer _test.destroy() 145 } // TestRepositoryWithErrors() 146 147 func TestRepositoryWithErrorsFalse(t *testing.T) { 148 _test := &repositorytest{} 149 _test.bad = _GITREPOSITORYERRORSFALSE 150 _test.file = gitignore.File + "-with-errors-false" 151 _test.error = func(e gitignore.Error) bool { 152 _test.errors = append(_test.errors, e) 153 return false 154 } 155 _test.instance = func(path string) (gitignore.GitIgnore, error) { 156 return gitignore.NewRepositoryWithErrors( 157 path, _test.file, _test.error, 158 ), nil 159 } 160 161 // perform the repository tests 162 repository(t, _test, _REPOSITORYMATCHESFALSE) 163 164 // remove the temporary directory used for this test 165 defer _test.destroy() 166 } // TestRepositoryWithErrorsFalse() 167 168 func TestRepositoryWithCache(t *testing.T) { 169 _test := &repositorytest{} 170 _test.bad = _GITREPOSITORYERRORS 171 _test.cache = gitignore.NewCache() 172 _test.cached = true 173 _test.instance = func(path string) (gitignore.GitIgnore, error) { 174 return gitignore.NewRepositoryWithCache( 175 path, _test.file, _test.cache, _test.error, 176 ), nil 177 } 178 179 // perform the repository tests 180 repository(t, _test, _REPOSITORYMATCHES) 181 182 // clean up 183 defer _test.destroy() 184 185 // rerun the tests while accumulating errors 186 _test.directory = "" 187 _test.file = gitignore.File + "-with-cache" 188 _test.error = func(e gitignore.Error) bool { 189 _test.errors = append(_test.errors, e) 190 return true 191 } 192 repository(t, _test, _REPOSITORYMATCHES) 193 194 // remove the temporary directory used for this test 195 _err := os.RemoveAll(_test.directory) 196 if _err != nil { 197 t.Fatalf( 198 "unable to remove temporary directory %s: %s", 199 _test.directory, _err.Error(), 200 ) 201 } 202 203 // recreate the temporary directory 204 // - this remove & recreate gives us an empty directory for the 205 // repository test 206 // - this lets us test the caching 207 _err = os.MkdirAll(_test.directory, _GITMASK) 208 if _err != nil { 209 t.Fatalf( 210 "unable to recreate temporary directory %s: %s", 211 _test.directory, _err.Error(), 212 ) 213 } 214 defer _test.destroy() 215 216 // repeat the repository tests 217 // - these should succeed using just the cache data 218 repository(t, _test, _REPOSITORYMATCHES) 219 } // TestRepositoryWithCache() 220 221 func TestInvalidRepository(t *testing.T) { 222 _test := &repositorytest{} 223 _test.instance = func(path string) (gitignore.GitIgnore, error) { 224 return gitignore.NewRepository(path) 225 } 226 227 // perform the invalid repository tests 228 invalid(t, _test) 229 } // TestInvalidRepository() 230 231 func TestInvalidRepositoryWithFile(t *testing.T) { 232 _test := &repositorytest{} 233 _test.file = gitignore.File + "-invalid-with-file" 234 _test.instance = func(path string) (gitignore.GitIgnore, error) { 235 return gitignore.NewRepositoryWithFile(path, _test.file) 236 } 237 238 // perform the invalid repository tests 239 invalid(t, _test) 240 } // TestInvalidRepositoryWithFile() 241 242 func TestInvalidRepositoryWithErrors(t *testing.T) { 243 _test := &repositorytest{} 244 _test.file = gitignore.File + "-invalid-with-errors" 245 _test.error = func(e gitignore.Error) bool { 246 _test.errors = append(_test.errors, e) 247 return true 248 } 249 _test.instance = func(path string) (gitignore.GitIgnore, error) { 250 return gitignore.NewRepositoryWithErrors( 251 path, _test.file, _test.error, 252 ), nil 253 } 254 255 // perform the invalid repository tests 256 invalid(t, _test) 257 } // TestInvalidRepositoryWithErrors() 258 259 func TestInvalidRepositoryWithErrorsFalse(t *testing.T) { 260 _test := &repositorytest{} 261 _test.file = gitignore.File + "-invalid-with-errors-false" 262 _test.error = func(e gitignore.Error) bool { 263 _test.errors = append(_test.errors, e) 264 return false 265 } 266 _test.instance = func(path string) (gitignore.GitIgnore, error) { 267 return gitignore.NewRepositoryWithErrors( 268 path, _test.file, _test.error, 269 ), nil 270 } 271 272 // perform the invalid repository tests 273 invalid(t, _test) 274 } // TestInvalidRepositoryWithErrorsFalse() 275 276 func TestInvalidRepositoryWithCache(t *testing.T) { 277 _test := &repositorytest{} 278 _test.file = gitignore.File + "-invalid-with-cache" 279 _test.cache = gitignore.NewCache() 280 _test.cached = true 281 _test.error = func(e gitignore.Error) bool { 282 _test.errors = append(_test.errors, e) 283 return true 284 } 285 _test.instance = func(path string) (gitignore.GitIgnore, error) { 286 return gitignore.NewRepositoryWithCache( 287 path, _test.file, _test.cache, _test.error, 288 ), nil 289 } 290 291 // perform the invalid repository tests 292 invalid(t, _test) 293 294 // repeat the tests using a default cache 295 _test.cache = nil 296 invalid(t, _test) 297 } // TestInvalidRepositoryWithCache() 298 299 // 300 // helper functions 301 // 302 303 func repository(t *testing.T, test *repositorytest, m []match) { 304 // if the test has no configured directory, then create a new 305 // directory with the required .gitignore files 306 if test.directory == "" { 307 // what name should we use for the .gitignore file? 308 // - if none is given, use the default 309 _file := test.file 310 if _file == "" { 311 _file = gitignore.File 312 } 313 314 // create a temporary directory populated with sample .gitignore files 315 // - first, augment the test data to include file names 316 _map := make(map[string]string) 317 for _k, _content := range _GITREPOSITORY { 318 _map[_k+"/"+_file] = _content 319 } 320 _dir, _err := dir(_map) 321 if _err != nil { 322 t.Fatalf("unable to create temporary directory: %s", _err.Error()) 323 } 324 test.directory = _dir 325 } 326 327 // create the repository 328 _repository, _err := test.create(test.directory, true) 329 if _err != nil { 330 t.Fatalf("unable to create repository: %s", _err.Error()) 331 } 332 333 // ensure we have a non-nill repository returned 334 if _repository == nil { 335 t.Error("expected non-nill GitIgnore repository instance; nil found") 336 } 337 338 // ensure the base of the repository is correct 339 if _repository.Base() != test.directory { 340 t.Errorf( 341 "repository.Base() mismatch; expected %q, got %q", 342 test.directory, _repository.Base(), 343 ) 344 } 345 346 // we need to check each test to see if it's matching against a 347 // GIT_DIR/info/exclude 348 // - we only do this if the target does not use .gitignore 349 // as the name of the ignore file 350 _prepare := func(m match) match { 351 if test.file == "" || test.file == gitignore.File { 352 return m 353 } else if m.Exclude { 354 return match{m.Path, "", false, m.Exclude} 355 } else { 356 return m 357 } 358 } // _prepare() 359 360 // perform the repository matching using absolute paths 361 _cb := func(path string, isdir bool) gitignore.Match { 362 _path := filepath.Join(_repository.Base(), path) 363 return _repository.Absolute(_path, isdir) 364 } 365 for _, _test := range m { 366 do(t, _cb, _prepare(_test)) 367 } 368 369 // repeat the tests using relative paths 370 _repository, _err = test.create(test.directory, true) 371 if _err != nil { 372 t.Fatalf("unable to create repository: %s", _err.Error()) 373 } 374 _cb = func(path string, isdir bool) gitignore.Match { 375 return _repository.Relative(path, isdir) 376 } 377 for _, _test := range m { 378 do(t, _cb, _prepare(_test)) 379 } 380 381 // perform absolute path tests with paths not under the same repository 382 _map := make(map[string]string) 383 for _, _test := range m { 384 _map[_test.Path] = " " 385 } 386 _new, _err := dir(_map) 387 if _err != nil { 388 t.Fatalf("unable to create temporary directory: %s", _err.Error()) 389 } 390 defer os.RemoveAll(_new) 391 392 // first, perform Match() tests 393 _repository, _err = test.create(test.directory, true) 394 if _err != nil { 395 t.Fatalf("unable to create repository: %s", _err.Error()) 396 } 397 for _, _test := range m { 398 _path := filepath.Join(_new, _test.Local()) 399 _match := _repository.Match(_path) 400 if _match != nil { 401 t.Fatalf("unexpected match; expected nil, got %v", _match) 402 } 403 } 404 405 // next, perform Absolute() tests 406 _repository, _err = test.create(test.directory, true) 407 if _err != nil { 408 t.Fatalf("unable to create repository: %s", _err.Error()) 409 } 410 for _, _test := range m { 411 // build the absolute path 412 _path := filepath.Join(_new, _test.Local()) 413 414 // we don't expect to match paths not under this repository 415 _match := _repository.Absolute(_path, _test.IsDir()) 416 if _match != nil { 417 t.Fatalf("unexpected match; expected nil, got %v", _match) 418 } 419 } 420 421 // now, repeat the Match() test after having first removed the 422 // temporary directory 423 // - we are testing correct handling of missing files 424 _err = os.RemoveAll(_new) 425 if _err != nil { 426 t.Fatalf( 427 "unable to remove temporary directory %s: %s", 428 _new, _err.Error(), 429 ) 430 } 431 _repository, _err = test.create(test.directory, true) 432 if _err != nil { 433 t.Fatalf("unable to create repository: %s", _err.Error()) 434 } 435 for _, _test := range m { 436 _path := filepath.Join(_new, _test.Local()) 437 438 // if we have an error handler configured, we should be recording 439 // and error in this call to Match() 440 _before := len(test.errors) 441 442 // perform the match 443 _match := _repository.Match(_path) 444 if _match != nil { 445 t.Fatalf("unexpected match; expected nil, got %v", _match) 446 } 447 448 // were we recording errors? 449 if test.error != nil { 450 _after := len(test.errors) 451 if !(_after > _before) { 452 t.Fatalf( 453 "expected Match() error; none found for %s", 454 _path, 455 ) 456 } 457 458 // ensure the most recent error is "not exists" 459 _latest := test.errors[_after-1] 460 _underlying := _latest.Underlying() 461 if !os.IsNotExist(_underlying) { 462 t.Fatalf( 463 "unexpected Match() error for %s; expected %q, got %q", 464 _path, os.ErrNotExist.Error(), _underlying.Error(), 465 ) 466 } 467 } 468 } 469 470 // ensure Match() behaves as expected if the absolute path cannot 471 // be determined 472 // - we do this by choosing as our working directory a path 473 // that this process does not have permission to 474 _dir, _err := dir(nil) 475 if _err != nil { 476 t.Fatalf("unable to create temporary directory: %s", _err.Error()) 477 } 478 defer os.RemoveAll(_dir) 479 480 _cwd, _err := os.Getwd() 481 if _err != nil { 482 t.Fatalf("unable to retrieve working directory: %s", _err.Error()) 483 } 484 _err = os.Chdir(_dir) 485 if _err != nil { 486 t.Fatalf("unable to chdir into temporary directory: %s", _err.Error()) 487 } 488 defer os.Chdir(_cwd) 489 490 // remove permission from the temporary directory 491 _err = os.Chmod(_dir, 0) 492 if _err != nil { 493 t.Fatalf( 494 "unable to remove temporary directory %s: %s", 495 _dir, _err.Error(), 496 ) 497 } 498 499 // perform the repository tests 500 _repository, _err = test.create(test.directory, true) 501 if _err != nil { 502 t.Fatalf("unable to create repository: %s", _err.Error()) 503 } 504 for _, _test := range m { 505 _match := _repository.Match(_test.Local()) 506 if _match != nil { 507 t.Fatalf("unexpected match; expected nil, not %v", _match) 508 } 509 } 510 511 if test.errors != nil { 512 // ensure the number of errors is expected 513 if len(test.errors) != test.bad { 514 t.Fatalf( 515 "unexpected repository errors; expected %d, got %d", 516 test.bad, len(test.errors), 517 ) 518 } else { 519 // if we're here, then we intended to record errors 520 // - ensure we recorded the expected errors 521 for _i := 0; _i < len(test.errors); _i++ { 522 _got := test.errors[_i] 523 _underlying := _got.Underlying() 524 if os.IsNotExist(_underlying) || 525 os.IsPermission(_underlying) { 526 continue 527 } else { 528 t.Log(_i) 529 t.Fatalf("unexpected repository error: %s", _got.Error()) 530 } 531 } 532 } 533 } 534 } // repository() 535 536 func invalid(t *testing.T, test *repositorytest) { 537 // create a temporary file to use as the repository 538 _file, _err := file("") 539 if _err != nil { 540 t.Fatalf("unable to create temporary file: %s", _err.Error()) 541 } 542 defer os.Remove(_file.Name()) 543 544 // test repository instance creation against a file 545 _repository, _err := test.create(_file.Name(), false) 546 if _err == nil { 547 t.Errorf( 548 "invalid repository error; expected %q, got nil", 549 gitignore.InvalidDirectoryError.Error(), 550 ) 551 } else if _err != gitignore.InvalidDirectoryError { 552 t.Errorf( 553 "invalid repository mismatch; expected %q, got %q", 554 gitignore.InvalidDirectoryError.Error(), _err.Error(), 555 ) 556 } 557 558 // ensure no repository is returned 559 if _repository != nil { 560 t.Errorf( 561 "invalid repository; expected nil, got %v", 562 _repository, 563 ) 564 } 565 566 // now, remove the temporary file and repeat the tests 567 _err = os.Remove(_file.Name()) 568 if _err != nil { 569 t.Fatalf( 570 "unable to remove temporary file %s: %s", 571 _file.Name(), _err.Error(), 572 ) 573 } 574 575 // test repository instance creating against a missing file 576 _repository, _err = test.create(_file.Name(), false) 577 if _err == nil { 578 t.Errorf( 579 "invalid repository error; expected %q, got nil", 580 gitignore.InvalidDirectoryError.Error(), 581 ) 582 } else if !os.IsNotExist(_err) { 583 t.Errorf( 584 "invalid repository mismatch; "+ 585 "expected no such file or directory, got %q", 586 _err.Error(), 587 ) 588 } 589 590 // ensure no repository is returned 591 if _repository != nil { 592 t.Errorf( 593 "invalid repository; expected nil, got %v", 594 _repository, 595 ) 596 } 597 598 // ensure we can't create a repository instance where the absolute path 599 // of the repository cannot be determined 600 // - we do this by choosing a working directory this process does 601 // not have access to and using a relative path 602 _map := map[string]string{gitignore.File: _GITIGNORE} 603 _dir, _err := dir(_map) 604 if _err != nil { 605 t.Fatalf("unable to create a temporary directory: %s", _err.Error()) 606 } 607 defer os.RemoveAll(_dir) 608 609 // now change the working directory 610 _cwd, _err := os.Getwd() 611 if _err != nil { 612 t.Fatalf("unable to retrieve working directory: %s", _err.Error()) 613 } 614 _err = os.Chdir(_dir) 615 if _err != nil { 616 t.Fatalf("unable to chdir into temporary directory: %s", _err.Error()) 617 } 618 defer os.Chdir(_cwd) 619 620 // remove permissions from the working directory 621 _err = os.Chmod(_dir, 0) 622 if _err != nil { 623 t.Fatalf("unable remove temporary directory permissions: %s: %s", 624 _dir, _err.Error(), 625 ) 626 } 627 628 // test repository instance creating against a relative path 629 // - the relative path exists 630 _repository, _err = test.create(gitignore.File, false) 631 if _err == nil { 632 t.Errorf("expected repository error, got nil") 633 } else if os.IsNotExist(_err) { 634 t.Errorf( 635 "unexpected repository error; file exists, but %q returned", 636 _err.Error(), 637 ) 638 } 639 640 // next, create a repository where we do not have read permission 641 // to a .gitignore file within the repository 642 // - this should trigger a panic() when attempting a file match 643 for _, _test := range _REPOSITORYMATCHES { 644 _map[_test.Path] = " " 645 } 646 _dir, _err = dir(_map) 647 if _err != nil { 648 t.Fatalf("unable to create a temporary directory: %s", _err.Error()) 649 } 650 defer os.RemoveAll(_dir) 651 652 _git := filepath.Join(_dir, gitignore.File) 653 _err = os.Chmod(_git, 0) 654 if _err != nil { 655 t.Fatalf("unable remove temporary .gitignore permissions: %s: %s", 656 _git, _err.Error(), 657 ) 658 } 659 660 // attempt to match a path in this repository 661 // - it can be anything, so we just use the .gitignore itself 662 // - between each test we recreate the repository instance to 663 // remove the effect of any caching 664 _instance := func() gitignore.GitIgnore { 665 // reset the cache 666 if test.cached { 667 if test.cache != nil { 668 test.cache = gitignore.NewCache() 669 } 670 } 671 672 // create the new repository 673 _repository, _err := test.create(_dir, false) 674 if _err != nil { 675 t.Fatalf("unable to create repository: %s", _err.Error()) 676 } 677 678 // return the repository 679 return _repository 680 } 681 for _, _match := range _REPOSITORYMATCHES { 682 _local := _match.Local() 683 _isdir := _match.IsDir() 684 _path := filepath.Join(_dir, _local) 685 686 // try Match() with an absolute path 687 _test := &invalidtest{repositorytest: test} 688 _test.tag = "Match()" 689 _test.match = func() gitignore.Match { 690 return _instance().Match(_path) 691 } 692 run(t, _test) 693 694 // try Absolute() with an absolute path 695 _test = &invalidtest{repositorytest: test} 696 _test.tag = "Absolute()" 697 _test.match = func() gitignore.Match { 698 return _instance().Absolute(_path, _isdir) 699 } 700 run(t, _test) 701 702 // try Absolute() with an absolute path 703 _test = &invalidtest{repositorytest: test} 704 _test.tag = "Relative()" 705 _test.match = func() gitignore.Match { 706 return _instance().Relative(_local, _isdir) 707 } 708 run(t, _test) 709 } 710 } // invalid() 711 712 func run(t *testing.T, test *invalidtest) { 713 // perform the match, and ensure it returns nil, nil 714 _match := test.match() 715 if _match != nil { 716 t.Fatalf("%s: unexpected match: %v", test.tag, _match) 717 } else if test.errors == nil { 718 return 719 } 720 721 // if we're here, then we intended to record errors 722 // - ensure we recorded the expected errors 723 for _i := 0; _i < len(test.errors); _i++ { 724 _got := test.errors[_i] 725 _underlying := _got.Underlying() 726 if os.IsNotExist(_underlying) || 727 os.IsPermission(_underlying) { 728 continue 729 } else { 730 t.Fatalf( 731 "%s: unexpected error: %q", 732 test.tag, _got.Error(), 733 ) 734 } 735 } 736 } // run()