github.com/nextlinux/gosbom@v0.81.1-0.20230627115839-1ff50c281391/gosbom/source/source_test.go (about) 1 //go:build !windows 2 // +build !windows 3 4 package source 5 6 import ( 7 "io" 8 "io/fs" 9 "os" 10 "os/exec" 11 "path" 12 "path/filepath" 13 "sort" 14 "strings" 15 "syscall" 16 "testing" 17 "time" 18 19 "github.com/google/go-cmp/cmp" 20 "github.com/nextlinux/gosbom/gosbom/artifact" 21 "github.com/nextlinux/gosbom/gosbom/internal/fileresolver" 22 "github.com/stretchr/testify/assert" 23 "github.com/stretchr/testify/require" 24 25 "github.com/anchore/stereoscope/pkg/image" 26 "github.com/anchore/stereoscope/pkg/imagetest" 27 ) 28 29 func TestParseInput(t *testing.T) { 30 tests := []struct { 31 name string 32 input string 33 platform string 34 expected Scheme 35 errFn require.ErrorAssertionFunc 36 }{ 37 { 38 name: "ParseInput parses a file input", 39 input: "test-fixtures/image-simple/file-1.txt", 40 expected: FileScheme, 41 }, 42 { 43 name: "errors out when using platform for non-image scheme", 44 input: "test-fixtures/image-simple/file-1.txt", 45 platform: "arm64", 46 errFn: require.Error, 47 }, 48 } 49 50 for _, test := range tests { 51 t.Run(test.name, func(t *testing.T) { 52 if test.errFn == nil { 53 test.errFn = require.NoError 54 } 55 sourceInput, err := ParseInput(test.input, test.platform) 56 test.errFn(t, err) 57 if test.expected != "" { 58 require.NotNil(t, sourceInput) 59 assert.Equal(t, sourceInput.Scheme, test.expected) 60 } 61 }) 62 } 63 } 64 65 func TestNewFromImageFails(t *testing.T) { 66 t.Run("no image given", func(t *testing.T) { 67 _, err := NewFromImage(nil, "") 68 if err == nil { 69 t.Errorf("expected an error condition but none was given") 70 } 71 }) 72 } 73 74 func TestSetID(t *testing.T) { 75 layer := image.NewLayer(nil) 76 layer.Metadata = image.LayerMetadata{ 77 Digest: "sha256:6f4fb385d4e698647bf2a450749dfbb7bc2831ec9a730ef4046c78c08d468e89", 78 } 79 img := image.Image{ 80 Layers: []*image.Layer{layer}, 81 } 82 83 tests := []struct { 84 name string 85 input *Source 86 expected artifact.ID 87 }{ 88 { 89 name: "source.SetID sets the ID for FileScheme", 90 input: &Source{ 91 Metadata: Metadata{ 92 Scheme: FileScheme, 93 Path: "test-fixtures/image-simple/file-1.txt", 94 }, 95 }, 96 expected: artifact.ID("55096713247489add592ce977637be868497132b36d1e294a3831925ec64319a"), 97 }, 98 { 99 name: "source.SetID sets the ID for ImageScheme", 100 input: &Source{ 101 Image: &img, 102 Metadata: Metadata{ 103 Scheme: ImageScheme, 104 }, 105 }, 106 expected: artifact.ID("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"), 107 }, 108 { 109 name: "source.SetID sets the ID for DirectoryScheme", 110 input: &Source{ 111 Image: &img, 112 Metadata: Metadata{ 113 Scheme: DirectoryScheme, 114 Path: "test-fixtures/image-simple", 115 }, 116 }, 117 expected: artifact.ID("91db61e5e0ae097ef764796ce85e442a93f2a03e5313d4c7307e9b413f62e8c4"), 118 }, 119 { 120 name: "source.SetID sets the ID for UnknownScheme", 121 input: &Source{ 122 Image: &img, 123 Metadata: Metadata{ 124 Scheme: UnknownScheme, 125 Path: "test-fixtures/image-simple", 126 }, 127 }, 128 expected: artifact.ID("9ee9e786412d6ae5"), 129 }, 130 } 131 132 for _, test := range tests { 133 t.Run(test.name, func(t *testing.T) { 134 test.input.SetID() 135 assert.Equal(t, test.expected, test.input.ID()) 136 }) 137 } 138 } 139 140 func TestNewFromImage(t *testing.T) { 141 layer := image.NewLayer(nil) 142 img := image.Image{ 143 Layers: []*image.Layer{layer}, 144 } 145 146 t.Run("create a new source object from image", func(t *testing.T) { 147 _, err := NewFromImage(&img, "") 148 if err != nil { 149 t.Errorf("unexpected error when creating a new Locations from img: %+v", err) 150 } 151 }) 152 } 153 154 func TestNewFromDirectory(t *testing.T) { 155 testCases := []struct { 156 desc string 157 input string 158 expString string 159 inputPaths []string 160 expectedRefs int 161 expectedErr bool 162 }{ 163 { 164 desc: "no paths exist", 165 input: "foobar/", 166 inputPaths: []string{"/opt/", "/other"}, 167 expectedErr: true, 168 }, 169 { 170 desc: "path detected", 171 input: "test-fixtures", 172 inputPaths: []string{"path-detected/.vimrc"}, 173 expectedRefs: 1, 174 }, 175 { 176 desc: "directory ignored", 177 input: "test-fixtures", 178 inputPaths: []string{"path-detected"}, 179 expectedRefs: 0, 180 }, 181 { 182 desc: "no files-by-path detected", 183 input: "test-fixtures", 184 inputPaths: []string{"no-path-detected"}, 185 expectedRefs: 0, 186 }, 187 } 188 for _, test := range testCases { 189 t.Run(test.desc, func(t *testing.T) { 190 src, err := NewFromDirectory(test.input) 191 require.NoError(t, err) 192 assert.Equal(t, test.input, src.Metadata.Path) 193 194 res, err := src.FileResolver(SquashedScope) 195 if test.expectedErr { 196 if err == nil { 197 t.Fatal("expected an error when making the resolver but got none") 198 } 199 return 200 } else { 201 require.NoError(t, err) 202 } 203 204 refs, err := res.FilesByPath(test.inputPaths...) 205 if err != nil { 206 t.Errorf("FilesByPath call produced an error: %+v", err) 207 } 208 if len(refs) != test.expectedRefs { 209 t.Errorf("unexpected number of refs returned: %d != %d", len(refs), test.expectedRefs) 210 211 } 212 213 }) 214 } 215 } 216 217 func TestNewFromFile(t *testing.T) { 218 testCases := []struct { 219 desc string 220 input string 221 expString string 222 inputPaths []string 223 expRefs int 224 }{ 225 { 226 desc: "path detected", 227 input: "test-fixtures/path-detected", 228 inputPaths: []string{"/.vimrc"}, 229 expRefs: 1, 230 }, 231 } 232 for _, test := range testCases { 233 t.Run(test.desc, func(t *testing.T) { 234 src, cleanup := NewFromFile(test.input) 235 if cleanup != nil { 236 t.Cleanup(cleanup) 237 } 238 239 assert.Equal(t, test.input, src.Metadata.Path) 240 assert.Equal(t, src.Metadata.Path, src.path) 241 242 res, err := src.FileResolver(SquashedScope) 243 require.NoError(t, err) 244 245 refs, err := res.FilesByPath(test.inputPaths...) 246 require.NoError(t, err) 247 assert.Len(t, refs, test.expRefs) 248 249 }) 250 } 251 } 252 253 func TestNewFromFile_WithArchive(t *testing.T) { 254 testCases := []struct { 255 desc string 256 input string 257 expString string 258 inputPaths []string 259 expRefs int 260 layer2 bool 261 contents string 262 }{ 263 { 264 desc: "path detected", 265 input: "test-fixtures/path-detected", 266 inputPaths: []string{"/.vimrc"}, 267 expRefs: 1, 268 }, 269 { 270 desc: "lest entry for duplicate paths", 271 input: "test-fixtures/path-detected", 272 inputPaths: []string{"/.vimrc"}, 273 expRefs: 1, 274 layer2: true, 275 contents: "Another .vimrc file", 276 }, 277 } 278 for _, test := range testCases { 279 t.Run(test.desc, func(t *testing.T) { 280 archivePath := setupArchiveTest(t, test.input, test.layer2) 281 282 src, cleanup := NewFromFile(archivePath) 283 if cleanup != nil { 284 t.Cleanup(cleanup) 285 } 286 287 assert.Equal(t, archivePath, src.Metadata.Path) 288 assert.NotEqual(t, src.Metadata.Path, src.path) 289 290 res, err := src.FileResolver(SquashedScope) 291 require.NoError(t, err) 292 293 refs, err := res.FilesByPath(test.inputPaths...) 294 require.NoError(t, err) 295 assert.Len(t, refs, test.expRefs) 296 297 if test.contents != "" { 298 reader, err := res.FileContentsByLocation(refs[0]) 299 require.NoError(t, err) 300 301 data, err := io.ReadAll(reader) 302 require.NoError(t, err) 303 304 assert.Equal(t, test.contents, string(data)) 305 } 306 307 }) 308 } 309 } 310 311 func TestNewFromDirectoryShared(t *testing.T) { 312 testCases := []struct { 313 desc string 314 input string 315 expString string 316 notExist string 317 inputPaths []string 318 expRefs int 319 }{ 320 { 321 desc: "path detected", 322 input: "test-fixtures", 323 notExist: "foobar/", 324 inputPaths: []string{"path-detected/.vimrc"}, 325 expRefs: 1, 326 }, 327 { 328 desc: "directory ignored", 329 input: "test-fixtures", 330 notExist: "foobar/", 331 inputPaths: []string{"path-detected"}, 332 expRefs: 0, 333 }, 334 { 335 desc: "no files-by-path detected", 336 input: "test-fixtures", 337 notExist: "foobar/", 338 inputPaths: []string{"no-path-detected"}, 339 expRefs: 0, 340 }, 341 } 342 for _, test := range testCases { 343 t.Run(test.desc, func(t *testing.T) { 344 src, err := NewFromDirectory(test.input) 345 346 if err != nil { 347 t.Errorf("could not create NewDirScope: %+v", err) 348 } 349 if src.Metadata.Path != test.input { 350 t.Errorf("mismatched stringer: '%s' != '%s'", src.Metadata.Path, test.input) 351 } 352 353 _, err = src.FileResolver(SquashedScope) 354 assert.NoError(t, err) 355 356 src.Metadata.Path = test.notExist 357 resolver, err := src.FileResolver(SquashedScope) 358 assert.NoError(t, err) 359 360 refs, err := resolver.FilesByPath(test.inputPaths...) 361 if err != nil { 362 t.Errorf("FilesByPath call produced an error: %+v", err) 363 } 364 if len(refs) != test.expRefs { 365 t.Errorf("unexpected number of refs returned: %d != %d", len(refs), test.expRefs) 366 367 } 368 369 }) 370 } 371 } 372 373 func TestFilesByPathDoesNotExist(t *testing.T) { 374 testCases := []struct { 375 desc string 376 input string 377 path string 378 expected string 379 }{ 380 { 381 input: "test-fixtures/path-detected", 382 desc: "path does not exist", 383 path: "foo", 384 }, 385 } 386 for _, test := range testCases { 387 t.Run(test.desc, func(t *testing.T) { 388 src, err := NewFromDirectory(test.input) 389 if err != nil { 390 t.Errorf("could not create NewDirScope: %+v", err) 391 } 392 res, err := src.FileResolver(SquashedScope) 393 if err != nil { 394 t.Errorf("could not get resolver error: %+v", err) 395 } 396 refs, err := res.FilesByPath(test.path) 397 if err != nil { 398 t.Errorf("could not get file references from path: %s, %v", test.path, err) 399 } 400 401 if len(refs) != 0 { 402 t.Errorf("didnt' expect a ref, but got: %d", len(refs)) 403 } 404 405 }) 406 } 407 } 408 409 func TestFilesByGlob(t *testing.T) { 410 testCases := []struct { 411 desc string 412 input string 413 glob string 414 expected int 415 }{ 416 { 417 input: "test-fixtures", 418 desc: "no matches", 419 glob: "bar/foo", 420 expected: 0, 421 }, 422 { 423 input: "test-fixtures/path-detected", 424 desc: "a single match", 425 glob: "**/*vimrc", 426 expected: 1, 427 }, 428 { 429 input: "test-fixtures/path-detected", 430 desc: "multiple matches", 431 glob: "**", 432 expected: 2, 433 }, 434 } 435 for _, test := range testCases { 436 t.Run(test.desc, func(t *testing.T) { 437 src, err := NewFromDirectory(test.input) 438 if err != nil { 439 t.Errorf("could not create NewDirScope: %+v", err) 440 } 441 res, err := src.FileResolver(SquashedScope) 442 if err != nil { 443 t.Errorf("could not get resolver error: %+v", err) 444 } 445 contents, err := res.FilesByGlob(test.glob) 446 if err != nil { 447 t.Errorf("could not get files by glob: %s+v", err) 448 } 449 if len(contents) != test.expected { 450 t.Errorf("unexpected number of files found by glob (%s): %d != %d", test.glob, len(contents), test.expected) 451 } 452 453 }) 454 } 455 } 456 457 func TestDirectoryExclusions(t *testing.T) { 458 testCases := []struct { 459 desc string 460 input string 461 glob string 462 expected []string 463 exclusions []string 464 err bool 465 }{ 466 { 467 input: "test-fixtures/system_paths", 468 desc: "exclude everything", 469 glob: "**", 470 expected: nil, 471 exclusions: []string{"**/*"}, 472 }, 473 { 474 input: "test-fixtures/image-simple", 475 desc: "a single path excluded", 476 glob: "**", 477 expected: []string{ 478 "Dockerfile", 479 "file-1.txt", 480 "file-2.txt", 481 }, 482 exclusions: []string{"**/target/**"}, 483 }, 484 { 485 input: "test-fixtures/image-simple", 486 desc: "exclude explicit directory relative to the root", 487 glob: "**", 488 expected: []string{ 489 "Dockerfile", 490 "file-1.txt", 491 "file-2.txt", 492 //"target/really/nested/file-3.txt", // explicitly skipped 493 }, 494 exclusions: []string{"./target"}, 495 }, 496 { 497 input: "test-fixtures/image-simple", 498 desc: "exclude explicit file relative to the root", 499 glob: "**", 500 expected: []string{ 501 "Dockerfile", 502 //"file-1.txt", // explicitly skipped 503 "file-2.txt", 504 "target/really/nested/file-3.txt", 505 }, 506 exclusions: []string{"./file-1.txt"}, 507 }, 508 { 509 input: "test-fixtures/image-simple", 510 desc: "exclude wildcard relative to the root", 511 glob: "**", 512 expected: []string{ 513 "Dockerfile", 514 //"file-1.txt", // explicitly skipped 515 //"file-2.txt", // explicitly skipped 516 "target/really/nested/file-3.txt", 517 }, 518 exclusions: []string{"./*.txt"}, 519 }, 520 { 521 input: "test-fixtures/image-simple", 522 desc: "exclude files deeper", 523 glob: "**", 524 expected: []string{ 525 "Dockerfile", 526 "file-1.txt", 527 "file-2.txt", 528 //"target/really/nested/file-3.txt", // explicitly skipped 529 }, 530 exclusions: []string{"**/really/**"}, 531 }, 532 { 533 input: "test-fixtures/image-simple", 534 desc: "files excluded with extension", 535 glob: "**", 536 expected: []string{ 537 "Dockerfile", 538 //"file-1.txt", // explicitly skipped 539 //"file-2.txt", // explicitly skipped 540 //"target/really/nested/file-3.txt", // explicitly skipped 541 }, 542 exclusions: []string{"**/*.txt"}, 543 }, 544 { 545 input: "test-fixtures/image-simple", 546 desc: "keep files with different extensions", 547 glob: "**", 548 expected: []string{ 549 "Dockerfile", 550 "file-1.txt", 551 "file-2.txt", 552 "target/really/nested/file-3.txt", 553 }, 554 exclusions: []string{"**/target/**/*.jar"}, 555 }, 556 { 557 input: "test-fixtures/path-detected", 558 desc: "file directly excluded", 559 glob: "**", 560 expected: []string{ 561 ".vimrc", 562 }, 563 exclusions: []string{"**/empty"}, 564 }, 565 { 566 input: "test-fixtures/path-detected", 567 desc: "pattern error containing **/", 568 glob: "**", 569 expected: []string{ 570 ".vimrc", 571 }, 572 exclusions: []string{"/**/empty"}, 573 err: true, 574 }, 575 { 576 input: "test-fixtures/path-detected", 577 desc: "pattern error incorrect start", 578 glob: "**", 579 expected: []string{ 580 ".vimrc", 581 }, 582 exclusions: []string{"empty"}, 583 err: true, 584 }, 585 { 586 input: "test-fixtures/path-detected", 587 desc: "pattern error starting with /", 588 glob: "**", 589 expected: []string{ 590 ".vimrc", 591 }, 592 exclusions: []string{"/empty"}, 593 err: true, 594 }, 595 } 596 registryOpts := &image.RegistryOptions{} 597 for _, test := range testCases { 598 t.Run(test.desc, func(t *testing.T) { 599 sourceInput, err := ParseInput("dir:"+test.input, "") 600 require.NoError(t, err) 601 src, fn, err := New(*sourceInput, registryOpts, test.exclusions) 602 defer fn() 603 604 if test.err { 605 _, err = src.FileResolver(SquashedScope) 606 if err == nil { 607 t.Errorf("expected an error for patterns: %s", strings.Join(test.exclusions, " or ")) 608 } 609 return 610 } 611 612 if err != nil { 613 t.Errorf("could not create NewDirScope: %+v", err) 614 } 615 res, err := src.FileResolver(SquashedScope) 616 if err != nil { 617 t.Errorf("could not get resolver error: %+v", err) 618 } 619 locations, err := res.FilesByGlob(test.glob) 620 if err != nil { 621 t.Errorf("could not get files by glob: %s+v", err) 622 } 623 var actual []string 624 for _, l := range locations { 625 actual = append(actual, l.RealPath) 626 } 627 628 sort.Strings(test.expected) 629 sort.Strings(actual) 630 631 assert.Equal(t, test.expected, actual, "diff \n"+cmp.Diff(test.expected, actual)) 632 }) 633 } 634 } 635 636 func TestImageExclusions(t *testing.T) { 637 testCases := []struct { 638 desc string 639 input string 640 glob string 641 expected int 642 exclusions []string 643 }{ 644 // NOTE: in the Dockerfile, /target is moved to /, which makes /really a top-level dir 645 { 646 input: "image-simple", 647 desc: "a single path excluded", 648 glob: "**", 649 expected: 2, 650 exclusions: []string{"/really/**"}, 651 }, 652 { 653 input: "image-simple", 654 desc: "a directly referenced directory is excluded", 655 glob: "**", 656 expected: 2, 657 exclusions: []string{"/really"}, 658 }, 659 { 660 input: "image-simple", 661 desc: "a partial directory is not excluded", 662 glob: "**", 663 expected: 3, 664 exclusions: []string{"/reall"}, 665 }, 666 { 667 input: "image-simple", 668 desc: "exclude files deeper", 669 glob: "**", 670 expected: 2, 671 exclusions: []string{"**/nested/**"}, 672 }, 673 { 674 input: "image-simple", 675 desc: "files excluded with extension", 676 glob: "**", 677 expected: 2, 678 exclusions: []string{"**/*1.txt"}, 679 }, 680 { 681 input: "image-simple", 682 desc: "keep files with different extensions", 683 glob: "**", 684 expected: 3, 685 exclusions: []string{"**/target/**/*.jar"}, 686 }, 687 { 688 input: "image-simple", 689 desc: "file directly excluded", 690 glob: "**", 691 expected: 2, 692 exclusions: []string{"**/somefile-1.txt"}, // file-1 renamed to somefile-1 in Dockerfile 693 }, 694 } 695 registryOpts := &image.RegistryOptions{} 696 for _, test := range testCases { 697 t.Run(test.desc, func(t *testing.T) { 698 archiveLocation := imagetest.PrepareFixtureImage(t, "docker-archive", test.input) 699 sourceInput, err := ParseInput(archiveLocation, "") 700 require.NoError(t, err) 701 src, fn, err := New(*sourceInput, registryOpts, test.exclusions) 702 defer fn() 703 704 if err != nil { 705 t.Errorf("could not create NewDirScope: %+v", err) 706 } 707 res, err := src.FileResolver(SquashedScope) 708 if err != nil { 709 t.Errorf("could not get resolver error: %+v", err) 710 } 711 contents, err := res.FilesByGlob(test.glob) 712 if err != nil { 713 t.Errorf("could not get files by glob: %s+v", err) 714 } 715 if len(contents) != test.expected { 716 t.Errorf("wrong number of files after exclusions (%s): %d != %d", test.glob, len(contents), test.expected) 717 } 718 }) 719 } 720 } 721 722 type dummyInfo struct { 723 isDir bool 724 } 725 726 func (d dummyInfo) Name() string { 727 //TODO implement me 728 panic("implement me") 729 } 730 731 func (d dummyInfo) Size() int64 { 732 //TODO implement me 733 panic("implement me") 734 } 735 736 func (d dummyInfo) Mode() fs.FileMode { 737 //TODO implement me 738 panic("implement me") 739 } 740 741 func (d dummyInfo) ModTime() time.Time { 742 //TODO implement me 743 panic("implement me") 744 } 745 746 func (d dummyInfo) IsDir() bool { 747 return d.isDir 748 } 749 750 func (d dummyInfo) Sys() any { 751 //TODO implement me 752 panic("implement me") 753 } 754 755 func Test_crossPlatformExclusions(t *testing.T) { 756 testCases := []struct { 757 desc string 758 root string 759 path string 760 finfo os.FileInfo 761 exclude string 762 walkHint error 763 }{ 764 { 765 desc: "directory exclusion", 766 root: "/", 767 path: "/usr/var/lib", 768 exclude: "**/var/lib", 769 finfo: dummyInfo{isDir: true}, 770 walkHint: fs.SkipDir, 771 }, 772 { 773 desc: "no file info", 774 root: "/", 775 path: "/usr/var/lib", 776 exclude: "**/var/lib", 777 walkHint: fileresolver.ErrSkipPath, 778 }, 779 // linux specific tests... 780 { 781 desc: "linux doublestar", 782 root: "/usr", 783 path: "/usr/var/lib/etc.txt", 784 exclude: "**/*.txt", 785 finfo: dummyInfo{isDir: false}, 786 walkHint: fileresolver.ErrSkipPath, 787 }, 788 { 789 desc: "linux relative", 790 root: "/usr/var/lib", 791 path: "/usr/var/lib/etc.txt", 792 exclude: "./*.txt", 793 finfo: dummyInfo{isDir: false}, 794 795 walkHint: fileresolver.ErrSkipPath, 796 }, 797 { 798 desc: "linux one level", 799 root: "/usr", 800 path: "/usr/var/lib/etc.txt", 801 exclude: "*/*.txt", 802 finfo: dummyInfo{isDir: false}, 803 walkHint: nil, 804 }, 805 // NOTE: since these tests will run in linux and macOS, the windows paths will be 806 // considered relative if they do not start with a forward slash and paths with backslashes 807 // won't be modified by the filepath.ToSlash call, so these are emulating the result of 808 // filepath.ToSlash usage 809 810 // windows specific tests... 811 { 812 desc: "windows doublestar", 813 root: "/C:/User/stuff", 814 path: "/C:/User/stuff/thing.txt", 815 exclude: "**/*.txt", 816 finfo: dummyInfo{isDir: false}, 817 walkHint: fileresolver.ErrSkipPath, 818 }, 819 { 820 desc: "windows relative", 821 root: "/C:/User/stuff", 822 path: "/C:/User/stuff/thing.txt", 823 exclude: "./*.txt", 824 finfo: dummyInfo{isDir: false}, 825 walkHint: fileresolver.ErrSkipPath, 826 }, 827 { 828 desc: "windows one level", 829 root: "/C:/User/stuff", 830 path: "/C:/User/stuff/thing.txt", 831 exclude: "*/*.txt", 832 finfo: dummyInfo{isDir: false}, 833 walkHint: nil, 834 }, 835 } 836 837 for _, test := range testCases { 838 t.Run(test.desc, func(t *testing.T) { 839 fns, err := getDirectoryExclusionFunctions(test.root, []string{test.exclude}) 840 require.NoError(t, err) 841 842 for _, f := range fns { 843 result := f(test.path, test.finfo, nil) 844 require.Equal(t, test.walkHint, result) 845 } 846 }) 847 } 848 } 849 850 // createArchive creates a new archive file at destinationArchivePath based on the directory found at sourceDirPath. 851 func createArchive(t testing.TB, sourceDirPath, destinationArchivePath string, layer2 bool) { 852 t.Helper() 853 854 cwd, err := os.Getwd() 855 if err != nil { 856 t.Fatalf("unable to get cwd: %+v", err) 857 } 858 859 cmd := exec.Command("./generate-tar-fixture-from-source-dir.sh", destinationArchivePath, path.Base(sourceDirPath)) 860 cmd.Dir = filepath.Join(cwd, "test-fixtures") 861 862 if err := cmd.Start(); err != nil { 863 t.Fatalf("unable to start generate zip fixture script: %+v", err) 864 } 865 866 if err := cmd.Wait(); err != nil { 867 if exiterr, ok := err.(*exec.ExitError); ok { 868 // The program has exited with an exit code != 0 869 870 // This works on both Unix and Windows. Although package 871 // syscall is generally platform dependent, WaitStatus is 872 // defined for both Unix and Windows and in both cases has 873 // an ExitStatus() method with the same signature. 874 if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { 875 if status.ExitStatus() != 0 { 876 t.Fatalf("failed to generate fixture: rc=%d", status.ExitStatus()) 877 } 878 } 879 } else { 880 t.Fatalf("unable to get generate fixture script result: %+v", err) 881 } 882 } 883 884 if layer2 { 885 cmd = exec.Command("tar", "-rvf", destinationArchivePath, ".") 886 cmd.Dir = filepath.Join(cwd, "test-fixtures", path.Base(sourceDirPath+"-2")) 887 if err := cmd.Start(); err != nil { 888 t.Fatalf("unable to start tar appending fixture script: %+v", err) 889 } 890 _ = cmd.Wait() 891 } 892 } 893 894 // setupArchiveTest encapsulates common test setup work for tar file tests. It returns a cleanup function, 895 // which should be called (typically deferred) by the caller, the path of the created tar archive, and an error, 896 // which should trigger a fatal test failure in the consuming test. The returned cleanup function will never be nil 897 // (even if there's an error), and it should always be called. 898 func setupArchiveTest(t testing.TB, sourceDirPath string, layer2 bool) string { 899 t.Helper() 900 901 archivePrefix, err := os.CreateTemp(t.TempDir(), "gosbom-archive-TEST-") 902 require.NoError(t, err) 903 904 destinationArchiveFilePath := archivePrefix.Name() + ".tar" 905 t.Logf("archive path: %s", destinationArchiveFilePath) 906 createArchive(t, sourceDirPath, destinationArchiveFilePath, layer2) 907 908 cwd, err := os.Getwd() 909 require.NoError(t, err) 910 911 t.Logf("running from: %s", cwd) 912 913 return destinationArchiveFilePath 914 } 915 916 func assertNoError(t testing.TB, fn func() error) func() { 917 return func() { 918 assert.NoError(t, fn()) 919 } 920 }