github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/pkg/cataloger/java/archive_parser_test.go (about) 1 package java 2 3 import ( 4 "bufio" 5 "context" 6 "fmt" 7 "io" 8 "net/http" 9 "net/http/httptest" 10 "os" 11 "os/exec" 12 "path/filepath" 13 "strings" 14 "syscall" 15 "testing" 16 17 "github.com/google/go-cmp/cmp" 18 "github.com/google/go-cmp/cmp/cmpopts" 19 "github.com/gookit/color" 20 "github.com/scylladb/go-set/strset" 21 "github.com/stretchr/testify/assert" 22 "github.com/stretchr/testify/require" 23 24 "github.com/anchore/syft/syft/artifact" 25 "github.com/anchore/syft/syft/file" 26 "github.com/anchore/syft/syft/license" 27 "github.com/anchore/syft/syft/pkg" 28 "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" 29 ) 30 31 func generateJavaBuildFixture(t *testing.T, fixturePath string) { 32 if _, err := os.Stat(fixturePath); !os.IsNotExist(err) { 33 // fixture already exists... 34 return 35 } 36 37 makeTask := strings.TrimPrefix(fixturePath, "test-fixtures/java-builds/") 38 t.Logf(color.Bold.Sprintf("Generating Fixture from 'make %s'", makeTask)) 39 40 cwd, err := os.Getwd() 41 if err != nil { 42 t.Errorf("unable to get cwd: %+v", err) 43 } 44 45 cmd := exec.Command("make", makeTask) 46 cmd.Dir = filepath.Join(cwd, "test-fixtures/java-builds/") 47 48 run(t, cmd) 49 } 50 51 func generateMockMavenHandler(responseFixture string) func(w http.ResponseWriter, r *http.Request) { 52 return func(w http.ResponseWriter, r *http.Request) { 53 w.WriteHeader(http.StatusOK) 54 // Set the Content-Type header to indicate that the response is XML 55 w.Header().Set("Content-Type", "application/xml") 56 // Copy the file's content to the response writer 57 file, err := os.Open(responseFixture) 58 if err != nil { 59 http.Error(w, err.Error(), http.StatusInternalServerError) 60 return 61 } 62 defer file.Close() 63 _, err = io.Copy(w, file) 64 if err != nil { 65 http.Error(w, err.Error(), http.StatusInternalServerError) 66 return 67 } 68 } 69 } 70 71 type handlerPath struct { 72 path string 73 handler func(w http.ResponseWriter, r *http.Request) 74 } 75 76 func TestSearchMavenForLicenses(t *testing.T) { 77 mux, url, teardown := setup() 78 defer teardown() 79 tests := []struct { 80 name string 81 fixture string 82 detectNested bool 83 config ArchiveCatalogerConfig 84 requestPath string 85 requestHandlers []handlerPath 86 expectedLicenses []pkg.License 87 }{ 88 { 89 name: "searchMavenForLicenses returns the expected licenses when search is set to true", 90 fixture: "opensaml-core-3.4.6", 91 detectNested: false, 92 config: ArchiveCatalogerConfig{ 93 UseNetwork: true, 94 MavenBaseURL: url, 95 MaxParentRecursiveDepth: 2, 96 }, 97 requestHandlers: []handlerPath{ 98 { 99 path: "/org/opensaml/opensaml-parent/3.4.6/opensaml-parent-3.4.6.pom", 100 handler: generateMockMavenHandler("test-fixtures/maven-xml-responses/opensaml-parent-3.4.6.pom"), 101 }, 102 { 103 path: "/net/shibboleth/parent/7.11.2/parent-7.11.2.pom", 104 handler: generateMockMavenHandler("test-fixtures/maven-xml-responses/parent-7.11.2.pom"), 105 }, 106 }, 107 expectedLicenses: []pkg.License{ 108 { 109 Type: license.Declared, 110 Value: `The Apache Software License, Version 2.0`, 111 SPDXExpression: ``, 112 }, 113 }, 114 }, 115 } 116 117 for _, tc := range tests { 118 t.Run(tc.name, func(t *testing.T) { 119 // configure maven central requests 120 for _, hdlr := range tc.requestHandlers { 121 mux.HandleFunc(hdlr.path, hdlr.handler) 122 } 123 124 // setup metadata fixture; note: 125 // this fixture has a pomProjectObject and has a parent object 126 // it has no licenses on either which is the condition for testing 127 // the searchMavenForLicenses functionality 128 jarName := generateJavaMetadataJarFixture(t, tc.fixture) 129 fixture, err := os.Open(jarName) 130 require.NoError(t, err) 131 132 // setup parser 133 ap, cleanupFn, err := newJavaArchiveParser( 134 file.LocationReadCloser{ 135 Location: file.NewLocation(fixture.Name()), 136 ReadCloser: fixture, 137 }, tc.detectNested, tc.config) 138 defer cleanupFn() 139 140 // assert licenses are discovered from upstream 141 _, _, licenses := ap.guessMainPackageNameAndVersionFromPomInfo(context.Background()) 142 assert.Equal(t, tc.expectedLicenses, licenses) 143 }) 144 } 145 } 146 147 func TestFormatMavenURL(t *testing.T) { 148 tests := []struct { 149 name string 150 groupID string 151 artifactID string 152 version string 153 expected string 154 }{ 155 { 156 name: "formatMavenURL correctly assembles the pom URL", 157 groupID: "org.springframework.boot", 158 artifactID: "spring-boot-starter-test", 159 version: "3.1.5", 160 expected: "https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-starter-test/3.1.5/spring-boot-starter-test-3.1.5.pom", 161 }, 162 } 163 164 for _, tc := range tests { 165 t.Run(tc.name, func(t *testing.T) { 166 requestURL, err := formatMavenPomURL(tc.groupID, tc.artifactID, tc.version, mavenBaseURL) 167 assert.NoError(t, err, "expected no err; got %w", err) 168 assert.Equal(t, tc.expected, requestURL) 169 }) 170 } 171 } 172 173 func TestParseJar(t *testing.T) { 174 tests := []struct { 175 name string 176 fixture string 177 expected map[string]pkg.Package 178 ignoreExtras []string 179 }{ 180 { 181 name: "example-jenkins-plugin", 182 fixture: "test-fixtures/java-builds/packages/example-jenkins-plugin.hpi", 183 ignoreExtras: []string{ 184 "Plugin-Version", // has dynamic date 185 "Built-By", // podman returns the real UID 186 "Build-Jdk", // can't guarantee the JDK used at build time 187 }, 188 expected: map[string]pkg.Package{ 189 "example-jenkins-plugin": { 190 Name: "example-jenkins-plugin", 191 Version: "1.0-SNAPSHOT", 192 PURL: "pkg:maven/io.jenkins.plugins/example-jenkins-plugin@1.0-SNAPSHOT", 193 Licenses: pkg.NewLicenseSet( 194 pkg.NewLicenseFromLocations("MIT License", file.NewLocation("test-fixtures/java-builds/packages/example-jenkins-plugin.hpi")), 195 ), 196 Language: pkg.Java, 197 Type: pkg.JenkinsPluginPkg, 198 Metadata: pkg.JavaArchive{ 199 VirtualPath: "test-fixtures/java-builds/packages/example-jenkins-plugin.hpi", 200 Manifest: &pkg.JavaManifest{ 201 Main: pkg.KeyValues{ 202 {Key: "Manifest-Version", Value: "1.0"}, 203 {Key: "Created-By", Value: "Maven Archiver 3.6.0"}, 204 {Key: "Build-Jdk-Spec", Value: "18"}, 205 {Key: "Specification-Title", Value: "Example Jenkins Plugin"}, 206 {Key: "Specification-Version", Value: "1.0"}, 207 {Key: "Implementation-Title", Value: "Example Jenkins Plugin"}, 208 {Key: "Implementation-Version", Value: "1.0-SNAPSHOT"}, 209 {Key: "Group-Id", Value: "io.jenkins.plugins"}, 210 {Key: "Short-Name", Value: "example-jenkins-plugin"}, 211 {Key: "Long-Name", Value: "Example Jenkins Plugin"}, 212 {Key: "Hudson-Version", Value: "2.204"}, 213 {Key: "Jenkins-Version", Value: "2.204"}, 214 {Key: "Plugin-Dependencies", Value: "structs:1.20"}, 215 {Key: "Plugin-Developers", Value: ""}, 216 {Key: "Plugin-License-Name", Value: "MIT License"}, 217 {Key: "Plugin-License-Url", Value: "https://opensource.org/licenses/MIT"}, 218 {Key: "Plugin-ScmUrl", Value: "https://github.com/jenkinsci/plugin-pom/example-jenkins-plugin"}, 219 // extra fields... 220 //{Key: "Minimum-Java-Version", Value: "1.8"}, 221 //{Key: "Archiver-Version", Value: "Plexus Archiver"}, 222 //{Key: "Built-By", Value: "?"}, 223 //{Key: "Build-Jdk", Value: "14.0.1"}, 224 //{Key: "Extension-Name", Value: "example-jenkins-plugin"}, 225 //{Key: "Plugin-Version", Value: "1.0-SNAPSHOT (private-07/09/2020 13:30-?)"}, 226 }, 227 }, 228 PomProperties: &pkg.JavaPomProperties{ 229 Path: "META-INF/maven/io.jenkins.plugins/example-jenkins-plugin/pom.properties", 230 GroupID: "io.jenkins.plugins", 231 ArtifactID: "example-jenkins-plugin", 232 Version: "1.0-SNAPSHOT", 233 }, 234 }, 235 }, 236 }, 237 }, 238 { 239 name: "example-java-app-gradle", 240 fixture: "test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar", 241 expected: map[string]pkg.Package{ 242 "example-java-app-gradle": { 243 Name: "example-java-app-gradle", 244 Version: "0.1.0", 245 PURL: "pkg:maven/example-java-app-gradle/example-java-app-gradle@0.1.0", 246 Language: pkg.Java, 247 Type: pkg.JavaPkg, 248 Licenses: pkg.NewLicenseSet( 249 pkg.License{ 250 Value: "Apache-2.0", 251 SPDXExpression: "Apache-2.0", 252 Type: license.Concluded, 253 Locations: file.NewLocationSet(file.NewLocation("test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar")), 254 }, 255 ), 256 Metadata: pkg.JavaArchive{ 257 VirtualPath: "test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar", 258 Manifest: &pkg.JavaManifest{ 259 Main: []pkg.KeyValue{ 260 { 261 Key: "Manifest-Version", 262 Value: "1.0", 263 }, 264 { 265 Key: "Main-Class", 266 Value: "hello.HelloWorld", 267 }, 268 }, 269 }, 270 }, 271 }, 272 "joda-time": { 273 Name: "joda-time", 274 Version: "2.2", 275 PURL: "pkg:maven/joda-time/joda-time@2.2", 276 Language: pkg.Java, 277 Type: pkg.JavaPkg, 278 Licenses: pkg.NewLicenseSet( 279 pkg.NewLicenseFromFields( 280 "Apache 2", 281 "http://www.apache.org/licenses/LICENSE-2.0.txt", 282 func() *file.Location { 283 l := file.NewLocation("test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar") 284 return &l 285 }(), 286 ), 287 ), 288 Metadata: pkg.JavaArchive{ 289 // ensure that nested packages with different names than that of the parent are appended as 290 // a suffix on the virtual path with a colon separator between group name and artifact name 291 VirtualPath: "test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar:joda-time:joda-time", 292 PomProperties: &pkg.JavaPomProperties{ 293 Path: "META-INF/maven/joda-time/joda-time/pom.properties", 294 GroupID: "joda-time", 295 ArtifactID: "joda-time", 296 Version: "2.2", 297 }, 298 PomProject: &pkg.JavaPomProject{ 299 Path: "META-INF/maven/joda-time/joda-time/pom.xml", 300 GroupID: "joda-time", 301 ArtifactID: "joda-time", 302 Version: "2.2", 303 Name: "Joda time", 304 Description: "Date and time library to replace JDK date handling", 305 URL: "http://joda-time.sourceforge.net", 306 }, 307 }, 308 }, 309 }, 310 }, 311 { 312 name: "example-java-app-maven", 313 fixture: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar", 314 ignoreExtras: []string{ 315 "Build-Jdk", // can't guarantee the JDK used at build time 316 "Built-By", // podman returns the real UID 317 }, 318 expected: map[string]pkg.Package{ 319 "example-java-app-maven": { 320 Name: "example-java-app-maven", 321 Version: "0.1.0", 322 PURL: "pkg:maven/org.anchore/example-java-app-maven@0.1.0", 323 Language: pkg.Java, 324 Type: pkg.JavaPkg, 325 Licenses: pkg.NewLicenseSet( 326 pkg.License{ 327 Value: "Apache-2.0", 328 SPDXExpression: "Apache-2.0", 329 Type: license.Concluded, 330 Locations: file.NewLocationSet(file.NewLocation("test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar")), 331 }, 332 ), 333 Metadata: pkg.JavaArchive{ 334 VirtualPath: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar", 335 Manifest: &pkg.JavaManifest{ 336 Main: []pkg.KeyValue{ 337 { 338 Key: "Manifest-Version", 339 Value: "1.0", 340 }, 341 // extra fields... 342 { 343 Key: "Archiver-Version", 344 Value: "Plexus Archiver", 345 }, 346 { 347 Key: "Created-By", 348 Value: "Apache Maven 3.8.6", 349 }, 350 //{ 351 // Key: "Built-By", 352 // Value: "?", 353 //}, 354 //{ 355 // Key: "Build-Jdk", 356 // Value: "14.0.1", 357 //}, 358 { 359 Key: "Main-Class", 360 Value: "hello.HelloWorld", 361 }, 362 }, 363 }, 364 PomProperties: &pkg.JavaPomProperties{ 365 Path: "META-INF/maven/org.anchore/example-java-app-maven/pom.properties", 366 GroupID: "org.anchore", 367 ArtifactID: "example-java-app-maven", 368 Version: "0.1.0", 369 }, 370 }, 371 }, 372 "joda-time": { 373 Name: "joda-time", 374 Version: "2.9.2", 375 PURL: "pkg:maven/joda-time/joda-time@2.9.2", 376 Licenses: pkg.NewLicenseSet( 377 pkg.NewLicenseFromFields( 378 "Apache 2", 379 "http://www.apache.org/licenses/LICENSE-2.0.txt", 380 func() *file.Location { 381 l := file.NewLocation("test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar") 382 return &l 383 }(), 384 ), 385 ), 386 Language: pkg.Java, 387 Type: pkg.JavaPkg, 388 Metadata: pkg.JavaArchive{ 389 // ensure that nested packages with different names than that of the parent are appended as 390 // a suffix on the virtual path 391 VirtualPath: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar:joda-time:joda-time", 392 PomProperties: &pkg.JavaPomProperties{ 393 Path: "META-INF/maven/joda-time/joda-time/pom.properties", 394 GroupID: "joda-time", 395 ArtifactID: "joda-time", 396 Version: "2.9.2", 397 }, 398 PomProject: &pkg.JavaPomProject{ 399 Path: "META-INF/maven/joda-time/joda-time/pom.xml", 400 GroupID: "joda-time", 401 ArtifactID: "joda-time", 402 Version: "2.9.2", 403 Name: "Joda-Time", 404 Description: "Date and time library to replace JDK date handling", 405 URL: "http://www.joda.org/joda-time/", 406 }, 407 }, 408 }, 409 }, 410 }, 411 } 412 413 for _, test := range tests { 414 t.Run(test.name, func(t *testing.T) { 415 416 generateJavaBuildFixture(t, test.fixture) 417 418 fixture, err := os.Open(test.fixture) 419 require.NoError(t, err) 420 421 for k := range test.expected { 422 p := test.expected[k] 423 p.Locations.Add(file.NewLocation(test.fixture)) 424 test.expected[k] = p 425 } 426 427 parser, cleanupFn, err := newJavaArchiveParser(file.LocationReadCloser{ 428 Location: file.NewLocation(fixture.Name()), 429 ReadCloser: fixture, 430 }, false, ArchiveCatalogerConfig{UseNetwork: false}) 431 defer cleanupFn() 432 require.NoError(t, err) 433 434 actual, _, err := parser.parse(context.Background()) 435 require.NoError(t, err) 436 437 if len(actual) != len(test.expected) { 438 for _, a := range actual { 439 t.Log(" ", a) 440 } 441 t.Fatalf("unexpected package count: %d!=%d", len(actual), len(test.expected)) 442 } 443 444 var parent *pkg.Package 445 for _, a := range actual { 446 a := a 447 if strings.Contains(a.Name, "example-") { 448 parent = &a 449 } 450 } 451 452 if parent == nil { 453 t.Fatal("could not find the parent pkg") 454 } 455 456 for _, a := range actual { 457 if a.ID() == "" { 458 t.Fatalf("empty package ID: %+v", a) 459 } 460 461 e, ok := test.expected[a.Name] 462 if !ok { 463 t.Errorf("entry not found: %s", a.Name) 464 continue 465 } 466 467 if a.Name != parent.Name && a.Metadata.(pkg.JavaArchive).Parent != nil && a.Metadata.(pkg.JavaArchive).Parent.Name != parent.Name { 468 t.Errorf("mismatched parent: %+v", a.Metadata.(pkg.JavaArchive).Parent) 469 } 470 471 // we need to compare the other fields without parent attached 472 metadata := a.Metadata.(pkg.JavaArchive) 473 metadata.Parent = nil 474 475 // redact Digest which is computed differently between CI and local 476 if len(metadata.ArchiveDigests) > 0 { 477 metadata.ArchiveDigests = nil 478 } 479 480 // ignore select fields (only works for the main section) 481 for _, field := range test.ignoreExtras { 482 if metadata.Manifest != nil && metadata.Manifest.Main != nil { 483 newMain := make(pkg.KeyValues, 0) 484 for i, kv := range metadata.Manifest.Main { 485 if kv.Key == field { 486 continue 487 } 488 newMain = append(newMain, metadata.Manifest.Main[i]) 489 } 490 metadata.Manifest.Main = newMain 491 } 492 } 493 494 // write censored data back 495 a.Metadata = metadata 496 497 pkgtest.AssertPackagesEqual(t, e, a) 498 } 499 }) 500 } 501 } 502 503 func TestParseNestedJar(t *testing.T) { 504 tests := []struct { 505 fixture string 506 expected []pkg.Package 507 ignoreExtras []string 508 }{ 509 { 510 fixture: "test-fixtures/java-builds/packages/spring-boot-0.0.1-SNAPSHOT.jar", 511 expected: []pkg.Package{ 512 { 513 Name: "spring-boot", 514 Version: "0.0.1-SNAPSHOT", 515 }, 516 { 517 Name: "spring-boot-starter", 518 Version: "2.2.2.RELEASE", 519 }, 520 { 521 Name: "jul-to-slf4j", 522 Version: "1.7.29", 523 }, 524 { 525 Name: "tomcat-embed-websocket", 526 Version: "9.0.29", 527 }, 528 { 529 Name: "spring-boot-starter-validation", 530 Version: "2.2.2.RELEASE", 531 }, 532 { 533 Name: "hibernate-validator", 534 Version: "6.0.18.Final", 535 }, 536 { 537 Name: "jboss-logging", 538 Version: "3.4.1.Final", 539 }, 540 { 541 Name: "spring-expression", 542 Version: "5.2.2.RELEASE", 543 }, 544 { 545 Name: "jakarta.validation-api", 546 Version: "2.0.1", 547 }, 548 { 549 Name: "spring-web", 550 Version: "5.2.2.RELEASE", 551 }, 552 { 553 Name: "spring-boot-starter-actuator", 554 Version: "2.2.2.RELEASE", 555 }, 556 { 557 Name: "log4j-api", 558 Version: "2.12.1", 559 }, 560 { 561 Name: "snakeyaml", 562 Version: "1.25", 563 }, 564 { 565 Name: "jackson-core", 566 Version: "2.10.1", 567 }, 568 { 569 Name: "jackson-datatype-jsr310", 570 Version: "2.10.1", 571 }, 572 { 573 Name: "spring-aop", 574 Version: "5.2.2.RELEASE", 575 }, 576 { 577 Name: "spring-boot-actuator-autoconfigure", 578 Version: "2.2.2.RELEASE", 579 }, 580 { 581 Name: "spring-jcl", 582 Version: "5.2.2.RELEASE", 583 }, 584 { 585 Name: "spring-boot", 586 Version: "2.2.2.RELEASE", 587 }, 588 { 589 Name: "spring-boot-starter-logging", 590 Version: "2.2.2.RELEASE", 591 }, 592 { 593 Name: "jakarta.annotation-api", 594 Version: "1.3.5", 595 }, 596 { 597 Name: "spring-webmvc", 598 Version: "5.2.2.RELEASE", 599 }, 600 { 601 Name: "HdrHistogram", 602 Version: "2.1.11", 603 }, 604 { 605 Name: "spring-boot-starter-web", 606 Version: "2.2.2.RELEASE", 607 }, 608 { 609 Name: "logback-classic", 610 Version: "1.2.3", 611 }, 612 { 613 Name: "log4j-to-slf4j", 614 Version: "2.12.1", 615 }, 616 { 617 Name: "spring-boot-starter-json", 618 Version: "2.2.2.RELEASE", 619 }, 620 { 621 Name: "jackson-databind", 622 Version: "2.10.1", 623 }, 624 { 625 Name: "jackson-module-parameter-names", 626 Version: "2.10.1", 627 }, 628 { 629 Name: "LatencyUtils", 630 Version: "2.0.3", 631 }, 632 { 633 Name: "spring-boot-autoconfigure", 634 Version: "2.2.2.RELEASE", 635 }, 636 { 637 Name: "jackson-datatype-jdk8", 638 Version: "2.10.1", 639 }, 640 { 641 Name: "tomcat-embed-core", 642 Version: "9.0.29", 643 }, 644 { 645 Name: "tomcat-embed-el", 646 Version: "9.0.29", 647 }, 648 { 649 Name: "spring-beans", 650 Version: "5.2.2.RELEASE", 651 }, 652 { 653 Name: "spring-boot-actuator", 654 Version: "2.2.2.RELEASE", 655 }, 656 { 657 Name: "slf4j-api", 658 Version: "1.7.29", 659 }, 660 { 661 Name: "spring-core", 662 Version: "5.2.2.RELEASE", 663 }, 664 { 665 Name: "logback-core", 666 Version: "1.2.3", 667 }, 668 { 669 Name: "micrometer-core", 670 Version: "1.3.1", 671 }, 672 { 673 Name: "pcollections", 674 Version: "3.1.0", 675 }, 676 { 677 Name: "jackson-annotations", 678 Version: "2.10.1", 679 }, 680 { 681 Name: "spring-boot-starter-tomcat", 682 Version: "2.2.2.RELEASE", 683 }, 684 { 685 Name: "classmate", 686 Version: "1.5.1", 687 }, 688 { 689 Name: "spring-context", 690 Version: "5.2.2.RELEASE", 691 }, 692 }, 693 }, 694 } 695 696 for _, test := range tests { 697 t.Run(test.fixture, func(t *testing.T) { 698 699 generateJavaBuildFixture(t, test.fixture) 700 701 fixture, err := os.Open(test.fixture) 702 require.NoError(t, err) 703 gap := newGenericArchiveParserAdapter(ArchiveCatalogerConfig{}) 704 705 actual, _, err := gap.parseJavaArchive(context.Background(), nil, nil, file.LocationReadCloser{ 706 Location: file.NewLocation(fixture.Name()), 707 ReadCloser: fixture, 708 }) 709 require.NoError(t, err) 710 711 expectedNameVersionPairSet := strset.New() 712 713 makeKey := func(p *pkg.Package) string { 714 if p == nil { 715 t.Fatal("cannot make key for nil pkg") 716 } 717 return fmt.Sprintf("%s|%s", p.Name, p.Version) 718 } 719 720 for _, e := range test.expected { 721 expectedNameVersionPairSet.Add(makeKey(&e)) 722 } 723 724 actualNameVersionPairSet := strset.New() 725 for _, a := range actual { 726 a := a 727 key := makeKey(&a) 728 actualNameVersionPairSet.Add(key) 729 if !expectedNameVersionPairSet.Has(key) { 730 t.Errorf("extra package: %s", a) 731 } 732 } 733 734 for _, key := range expectedNameVersionPairSet.List() { 735 if !actualNameVersionPairSet.Has(key) { 736 t.Errorf("missing package: %s", key) 737 } 738 } 739 740 if len(actual) != expectedNameVersionPairSet.Size() { 741 t.Fatalf("unexpected package count: %d!=%d", len(actual), expectedNameVersionPairSet.Size()) 742 } 743 744 for _, a := range actual { 745 a := a 746 actualKey := makeKey(&a) 747 748 metadata := a.Metadata.(pkg.JavaArchive) 749 if actualKey == "spring-boot|0.0.1-SNAPSHOT" { 750 if metadata.Parent != nil { 751 t.Errorf("expected no parent for root pkg, got %q", makeKey(metadata.Parent)) 752 } 753 } else { 754 if metadata.Parent == nil { 755 t.Errorf("unassigned error for pkg=%q", actualKey) 756 } else if makeKey(metadata.Parent) != "spring-boot|0.0.1-SNAPSHOT" { 757 // NB: this is a hard-coded condition to simplify the test harness to account for https://github.com/micrometer-metrics/micrometer/issues/1785 758 if a.Name == "pcollections" { 759 if metadata.Parent.Name != "micrometer-core" { 760 t.Errorf("nested 'pcollections' pkg has wrong parent: %q", metadata.Parent.Name) 761 } 762 } else { 763 t.Errorf("bad parent for pkg=%q parent=%q", actualKey, makeKey(metadata.Parent)) 764 } 765 } 766 } 767 } 768 }) 769 } 770 } 771 772 func Test_newPackageFromMavenData(t *testing.T) { 773 virtualPath := "given/virtual/path" 774 tests := []struct { 775 name string 776 props pkg.JavaPomProperties 777 project *parsedPomProject 778 parent *pkg.Package 779 expectedParent pkg.Package 780 expectedPackage *pkg.Package 781 }{ 782 { 783 name: "go case: get a single package from pom properties", 784 props: pkg.JavaPomProperties{ 785 Name: "some-name", 786 GroupID: "some-group-id", 787 ArtifactID: "some-artifact-id", 788 Version: "1.0", 789 }, 790 parent: &pkg.Package{ 791 Name: "some-parent-name", 792 Version: "2.0", 793 Metadata: pkg.JavaArchive{ 794 VirtualPath: "some-parent-virtual-path", 795 Manifest: nil, 796 PomProperties: nil, 797 Parent: nil, 798 }, 799 }, 800 // note: the SAME as the original parent values 801 expectedParent: pkg.Package{ 802 Name: "some-parent-name", 803 Version: "2.0", 804 Metadata: pkg.JavaArchive{ 805 VirtualPath: "some-parent-virtual-path", 806 Manifest: nil, 807 PomProperties: nil, 808 Parent: nil, 809 }, 810 }, 811 expectedPackage: &pkg.Package{ 812 Name: "some-artifact-id", 813 Version: "1.0", 814 Language: pkg.Java, 815 Type: pkg.JavaPkg, 816 Metadata: pkg.JavaArchive{ 817 VirtualPath: virtualPath + ":" + "some-group-id" + ":" + "some-artifact-id", 818 PomProperties: &pkg.JavaPomProperties{ 819 Name: "some-name", 820 GroupID: "some-group-id", 821 ArtifactID: "some-artifact-id", 822 Version: "1.0", 823 }, 824 Parent: &pkg.Package{ 825 Name: "some-parent-name", 826 Version: "2.0", 827 Metadata: pkg.JavaArchive{ 828 VirtualPath: "some-parent-virtual-path", 829 Manifest: nil, 830 PomProperties: nil, 831 Parent: nil, 832 }, 833 }, 834 }, 835 }, 836 }, 837 { 838 name: "get a single package from pom properties + project", 839 props: pkg.JavaPomProperties{ 840 Name: "some-name", 841 GroupID: "some-group-id", 842 ArtifactID: "some-artifact-id", 843 Version: "1.0", 844 }, 845 project: &parsedPomProject{ 846 JavaPomProject: &pkg.JavaPomProject{ 847 Parent: &pkg.JavaPomParent{ 848 GroupID: "some-parent-group-id", 849 ArtifactID: "some-parent-artifact-id", 850 Version: "1.0-parent", 851 }, 852 Name: "some-name", 853 GroupID: "some-group-id", 854 ArtifactID: "some-artifact-id", 855 Version: "1.0", 856 Description: "desc", 857 URL: "aweso.me", 858 }, 859 Licenses: []pkg.License{ 860 { 861 Value: "MIT", 862 SPDXExpression: "MIT", 863 Type: license.Declared, 864 URLs: []string{"https://opensource.org/licenses/MIT"}, 865 Locations: file.NewLocationSet(file.NewLocation("some-license-path")), 866 }, 867 }, 868 }, 869 parent: &pkg.Package{ 870 Name: "some-parent-name", 871 Version: "2.0", 872 Metadata: pkg.JavaArchive{ 873 VirtualPath: "some-parent-virtual-path", 874 Manifest: nil, 875 PomProperties: nil, 876 Parent: nil, 877 }, 878 }, 879 // note: the SAME as the original parent values 880 expectedParent: pkg.Package{ 881 Name: "some-parent-name", 882 Version: "2.0", 883 Metadata: pkg.JavaArchive{ 884 VirtualPath: "some-parent-virtual-path", 885 Manifest: nil, 886 PomProperties: nil, 887 Parent: nil, 888 }, 889 }, 890 expectedPackage: &pkg.Package{ 891 Name: "some-artifact-id", 892 Version: "1.0", 893 Language: pkg.Java, 894 Type: pkg.JavaPkg, 895 Licenses: pkg.NewLicenseSet( 896 pkg.License{ 897 Value: "MIT", 898 SPDXExpression: "MIT", 899 Type: license.Declared, 900 URLs: []string{"https://opensource.org/licenses/MIT"}, 901 Locations: file.NewLocationSet(file.NewLocation("some-license-path")), 902 }, 903 ), 904 Metadata: pkg.JavaArchive{ 905 VirtualPath: virtualPath + ":" + "some-group-id" + ":" + "some-artifact-id", 906 PomProperties: &pkg.JavaPomProperties{ 907 Name: "some-name", 908 GroupID: "some-group-id", 909 ArtifactID: "some-artifact-id", 910 Version: "1.0", 911 }, 912 PomProject: &pkg.JavaPomProject{ 913 Parent: &pkg.JavaPomParent{ 914 GroupID: "some-parent-group-id", 915 ArtifactID: "some-parent-artifact-id", 916 Version: "1.0-parent", 917 }, 918 Name: "some-name", 919 GroupID: "some-group-id", 920 ArtifactID: "some-artifact-id", 921 Version: "1.0", 922 Description: "desc", 923 URL: "aweso.me", 924 }, 925 Parent: &pkg.Package{ 926 Name: "some-parent-name", 927 Version: "2.0", 928 Metadata: pkg.JavaArchive{ 929 VirtualPath: "some-parent-virtual-path", 930 Manifest: nil, 931 PomProperties: nil, 932 Parent: nil, 933 }, 934 }, 935 }, 936 }, 937 }, 938 { 939 name: "single package from pom properties that's a Jenkins plugin", 940 props: pkg.JavaPomProperties{ 941 Name: "some-name", 942 GroupID: "com.cloudbees.jenkins.plugins", 943 ArtifactID: "some-artifact-id", 944 Version: "1.0", 945 }, 946 parent: &pkg.Package{ 947 Name: "some-parent-name", 948 Version: "2.0", 949 Metadata: pkg.JavaArchive{ 950 VirtualPath: "some-parent-virtual-path", 951 Manifest: nil, 952 PomProperties: nil, 953 Parent: nil, 954 }, 955 }, 956 // note: the SAME as the original parent values 957 expectedParent: pkg.Package{ 958 Name: "some-parent-name", 959 Version: "2.0", 960 Metadata: pkg.JavaArchive{ 961 VirtualPath: "some-parent-virtual-path", 962 Manifest: nil, 963 PomProperties: nil, 964 Parent: nil, 965 }, 966 }, 967 expectedPackage: &pkg.Package{ 968 Name: "some-artifact-id", 969 Version: "1.0", 970 Language: pkg.Java, 971 Type: pkg.JenkinsPluginPkg, 972 Metadata: pkg.JavaArchive{ 973 VirtualPath: virtualPath + ":" + "com.cloudbees.jenkins.plugins" + ":" + "some-artifact-id", 974 PomProperties: &pkg.JavaPomProperties{ 975 Name: "some-name", 976 GroupID: "com.cloudbees.jenkins.plugins", 977 ArtifactID: "some-artifact-id", 978 Version: "1.0", 979 }, 980 Parent: &pkg.Package{ 981 Name: "some-parent-name", 982 Version: "2.0", 983 Metadata: pkg.JavaArchive{ 984 VirtualPath: "some-parent-virtual-path", 985 Manifest: nil, 986 PomProperties: nil, 987 Parent: nil, 988 }, 989 }, 990 }, 991 }, 992 }, 993 { 994 name: "child matches parent by key", 995 props: pkg.JavaPomProperties{ 996 Name: "some-name", 997 GroupID: "some-group-id", 998 ArtifactID: "some-parent-name", // note: matches parent package 999 Version: "2.0", // note: matches parent package 1000 }, 1001 parent: &pkg.Package{ 1002 Name: "some-parent-name", 1003 Version: "2.0", 1004 Type: pkg.JavaPkg, 1005 Metadata: pkg.JavaArchive{ 1006 VirtualPath: "some-parent-virtual-path", 1007 Manifest: nil, 1008 PomProperties: nil, 1009 Parent: nil, 1010 }, 1011 }, 1012 // note: the SAME as the original parent values 1013 expectedParent: pkg.Package{ 1014 Name: "some-parent-name", 1015 Version: "2.0", 1016 Type: pkg.JavaPkg, 1017 Metadata: pkg.JavaArchive{ 1018 VirtualPath: "some-parent-virtual-path", 1019 Manifest: nil, 1020 // note: we attach the discovered pom properties data 1021 PomProperties: &pkg.JavaPomProperties{ 1022 Name: "some-name", 1023 GroupID: "some-group-id", 1024 ArtifactID: "some-parent-name", // note: matches parent package 1025 Version: "2.0", // note: matches parent package 1026 }, 1027 Parent: nil, 1028 }, 1029 }, 1030 expectedPackage: nil, 1031 }, 1032 { 1033 name: "child matches parent by key and is Jenkins plugin", 1034 props: pkg.JavaPomProperties{ 1035 Name: "some-name", 1036 GroupID: "com.cloudbees.jenkins.plugins", 1037 ArtifactID: "some-parent-name", // note: matches parent package 1038 Version: "2.0", // note: matches parent package 1039 }, 1040 parent: &pkg.Package{ 1041 Name: "some-parent-name", 1042 Version: "2.0", 1043 Type: pkg.JavaPkg, 1044 Metadata: pkg.JavaArchive{ 1045 VirtualPath: "some-parent-virtual-path", 1046 Manifest: nil, 1047 PomProperties: nil, 1048 Parent: nil, 1049 }, 1050 }, 1051 expectedParent: pkg.Package{ 1052 Name: "some-parent-name", 1053 Version: "2.0", 1054 Type: pkg.JenkinsPluginPkg, 1055 Metadata: pkg.JavaArchive{ 1056 VirtualPath: "some-parent-virtual-path", 1057 Manifest: nil, 1058 // note: we attach the discovered pom properties data 1059 PomProperties: &pkg.JavaPomProperties{ 1060 Name: "some-name", 1061 GroupID: "com.cloudbees.jenkins.plugins", 1062 ArtifactID: "some-parent-name", // note: matches parent package 1063 Version: "2.0", // note: matches parent package 1064 }, 1065 Parent: nil, 1066 }, 1067 }, 1068 expectedPackage: nil, 1069 }, 1070 { 1071 name: "child matches parent by artifact id", 1072 props: pkg.JavaPomProperties{ 1073 Name: "some-name", 1074 GroupID: "some-group-id", 1075 ArtifactID: "some-parent-name", // note: matches parent package 1076 Version: "NOT_THE_PARENT_VERSION", // note: DOES NOT match parent package 1077 }, 1078 parent: &pkg.Package{ 1079 Name: "some-parent-name", 1080 Version: "2.0", 1081 Type: pkg.JavaPkg, 1082 Metadata: pkg.JavaArchive{ 1083 VirtualPath: virtualPath + ":NEW_VIRTUAL_PATH", // note: DOES NOT match the existing virtual path 1084 Manifest: nil, 1085 PomProperties: nil, 1086 Parent: nil, 1087 }, 1088 }, 1089 // note: the SAME as the original parent values 1090 expectedParent: pkg.Package{ 1091 Name: "some-parent-name", 1092 Version: "NOT_THE_PARENT_VERSION", // note: the version is updated from pom properties 1093 Type: pkg.JavaPkg, 1094 Metadata: pkg.JavaArchive{ 1095 VirtualPath: virtualPath + ":NEW_VIRTUAL_PATH", 1096 Manifest: nil, 1097 // note: we attach the discovered pom properties data 1098 PomProperties: &pkg.JavaPomProperties{ 1099 Name: "some-name", 1100 GroupID: "some-group-id", 1101 ArtifactID: "some-parent-name", 1102 Version: "NOT_THE_PARENT_VERSION", 1103 }, 1104 Parent: nil, 1105 }, 1106 }, 1107 expectedPackage: nil, 1108 }, 1109 } 1110 1111 for _, test := range tests { 1112 t.Run(test.name, func(t *testing.T) { 1113 locations := file.NewLocationSet(file.NewLocation(virtualPath)) 1114 if test.expectedPackage != nil { 1115 test.expectedPackage.Locations = locations 1116 if test.expectedPackage.Metadata.(pkg.JavaArchive).Parent != nil { 1117 test.expectedPackage.Metadata.(pkg.JavaArchive).Parent.Locations = locations 1118 } 1119 } 1120 if test.parent != nil { 1121 test.parent.Locations = locations 1122 } 1123 test.expectedParent.Locations = locations 1124 1125 actualPackage := newPackageFromMavenData(context.Background(), test.props, test.project, test.parent, file.NewLocation(virtualPath), DefaultArchiveCatalogerConfig()) 1126 if test.expectedPackage == nil { 1127 require.Nil(t, actualPackage) 1128 } else { 1129 pkgtest.AssertPackagesEqual(t, *test.expectedPackage, *actualPackage) 1130 } 1131 1132 pkgtest.AssertPackagesEqual(t, test.expectedParent, *test.parent) 1133 }) 1134 } 1135 } 1136 1137 func Test_artifactIDMatchesFilename(t *testing.T) { 1138 tests := []struct { 1139 name string 1140 artifactID string 1141 fileName string // without version or extension 1142 want bool 1143 }{ 1144 { 1145 name: "artifact id within file name", 1146 artifactID: "atlassian-extras-api", 1147 fileName: "com.atlassian.extras_atlassian-extras-api", 1148 want: true, 1149 }, 1150 { 1151 name: "file name within artifact id", 1152 artifactID: "atlassian-extras-api-something", 1153 fileName: "atlassian-extras-api", 1154 want: true, 1155 }, 1156 } 1157 for _, tt := range tests { 1158 t.Run(tt.name, func(t *testing.T) { 1159 assert.Equal(t, tt.want, artifactIDMatchesFilename(tt.artifactID, tt.fileName)) 1160 }) 1161 } 1162 } 1163 1164 func Test_parseJavaArchive_regressions(t *testing.T) { 1165 tests := []struct { 1166 name string 1167 fixtureName string 1168 expectedPkgs []pkg.Package 1169 expectedRelationships []artifact.Relationship 1170 assignParent bool 1171 }{ 1172 { 1173 name: "duplicate jar regression - go case (issue #2130)", 1174 fixtureName: "jackson-core-2.15.2", 1175 expectedPkgs: []pkg.Package{ 1176 { 1177 Name: "jackson-core", 1178 Version: "2.15.2", 1179 Type: pkg.JavaPkg, 1180 Language: pkg.Java, 1181 PURL: "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.2", 1182 Locations: file.NewLocationSet(file.NewLocation("test-fixtures/jar-metadata/cache/jackson-core-2.15.2.jar")), 1183 Licenses: pkg.NewLicenseSet( 1184 pkg.NewLicensesFromLocation( 1185 file.NewLocation("test-fixtures/jar-metadata/cache/jackson-core-2.15.2.jar"), 1186 "https://www.apache.org/licenses/LICENSE-2.0.txt", 1187 )..., 1188 ), 1189 Metadata: pkg.JavaArchive{ 1190 VirtualPath: "test-fixtures/jar-metadata/cache/jackson-core-2.15.2.jar", 1191 Manifest: &pkg.JavaManifest{ 1192 Main: pkg.KeyValues{ 1193 {Key: "Manifest-Version", Value: "1.0"}, 1194 {Key: "Bundle-License", Value: "https://www.apache.org/licenses/LICENSE-2.0.txt"}, 1195 {Key: "Bundle-SymbolicName", Value: "com.fasterxml.jackson.core.jackson-core"}, 1196 {Key: "Implementation-Vendor-Id", Value: "com.fasterxml.jackson.core"}, 1197 {Key: "Specification-Title", Value: "Jackson-core"}, 1198 {Key: "Bundle-DocURL", Value: "https://github.com/FasterXML/jackson-core"}, 1199 {Key: "Import-Package", Value: "com.fasterxml.jackson.core;version=...snip"}, 1200 {Key: "Require-Capability", Value: `osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"`}, 1201 {Key: "Export-Package", Value: "com.fasterxml.jackson.core;version...snip"}, 1202 {Key: "Bundle-Name", Value: "Jackson-core"}, 1203 {Key: "Multi-Release", Value: "true"}, 1204 {Key: "Build-Jdk-Spec", Value: "1.8"}, 1205 {Key: "Bundle-Description", Value: "Core Jackson processing abstractions"}, 1206 {Key: "Implementation-Title", Value: "Jackson-core"}, 1207 {Key: "Implementation-Version", Value: "2.15.2"}, 1208 {Key: "Bundle-ManifestVersion", Value: "2"}, 1209 {Key: "Specification-Vendor", Value: "FasterXML"}, 1210 {Key: "Bundle-Vendor", Value: "FasterXML"}, 1211 {Key: "Tool", Value: "Bnd-6.3.1.202206071316"}, 1212 {Key: "Implementation-Vendor", Value: "FasterXML"}, 1213 {Key: "Bundle-Version", Value: "2.15.2"}, 1214 {Key: "X-Compile-Target-JDK", Value: "1.8"}, 1215 {Key: "X-Compile-Source-JDK", Value: "1.8"}, 1216 {Key: "Created-By", Value: "Apache Maven Bundle Plugin 5.1.8"}, 1217 {Key: "Specification-Version", Value: "2.15.2"}, 1218 }, 1219 }, 1220 // not under test 1221 //ArchiveDigests: []file.Digest{{Algorithm: "sha1", Value: "d8bc1d9c428c96fe447e2c429fc4304d141024df"}}, 1222 }, 1223 }, 1224 }, 1225 }, 1226 { 1227 name: "duplicate jar regression - bad case (issue #2130)", 1228 fixtureName: "com.fasterxml.jackson.core.jackson-core-2.15.2", 1229 expectedPkgs: []pkg.Package{ 1230 { 1231 Name: "jackson-core", 1232 Version: "2.15.2", 1233 Type: pkg.JavaPkg, 1234 Language: pkg.Java, 1235 PURL: "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.2", 1236 Locations: file.NewLocationSet(file.NewLocation("test-fixtures/jar-metadata/cache/com.fasterxml.jackson.core.jackson-core-2.15.2.jar")), 1237 Licenses: pkg.NewLicenseSet( 1238 pkg.NewLicensesFromLocation( 1239 file.NewLocation("test-fixtures/jar-metadata/cache/com.fasterxml.jackson.core.jackson-core-2.15.2.jar"), 1240 "https://www.apache.org/licenses/LICENSE-2.0.txt", 1241 )..., 1242 ), 1243 Metadata: pkg.JavaArchive{ 1244 VirtualPath: "test-fixtures/jar-metadata/cache/com.fasterxml.jackson.core.jackson-core-2.15.2.jar", 1245 Manifest: &pkg.JavaManifest{ 1246 Main: pkg.KeyValues{ 1247 {Key: "Manifest-Version", Value: "1.0"}, 1248 {Key: "Bundle-License", Value: "https://www.apache.org/licenses/LICENSE-2.0.txt"}, 1249 {Key: "Bundle-SymbolicName", Value: "com.fasterxml.jackson.core.jackson-core"}, 1250 {Key: "Implementation-Vendor-Id", Value: "com.fasterxml.jackson.core"}, 1251 {Key: "Specification-Title", Value: "Jackson-core"}, 1252 {Key: "Bundle-DocURL", Value: "https://github.com/FasterXML/jackson-core"}, 1253 {Key: "Import-Package", Value: "com.fasterxml.jackson.core;version=...snip"}, 1254 {Key: "Require-Capability", Value: `osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"`}, 1255 {Key: "Export-Package", Value: "com.fasterxml.jackson.core;version...snip"}, 1256 {Key: "Bundle-Name", Value: "Jackson-core"}, 1257 {Key: "Multi-Release", Value: "true"}, 1258 {Key: "Build-Jdk-Spec", Value: "1.8"}, 1259 {Key: "Bundle-Description", Value: "Core Jackson processing abstractions"}, 1260 {Key: "Implementation-Title", Value: "Jackson-core"}, 1261 {Key: "Implementation-Version", Value: "2.15.2"}, 1262 {Key: "Bundle-ManifestVersion", Value: "2"}, 1263 {Key: "Specification-Vendor", Value: "FasterXML"}, 1264 {Key: "Bundle-Vendor", Value: "FasterXML"}, 1265 {Key: "Tool", Value: "Bnd-6.3.1.202206071316"}, 1266 {Key: "Implementation-Vendor", Value: "FasterXML"}, 1267 {Key: "Bundle-Version", Value: "2.15.2"}, 1268 {Key: "X-Compile-Target-JDK", Value: "1.8"}, 1269 {Key: "X-Compile-Source-JDK", Value: "1.8"}, 1270 {Key: "Created-By", Value: "Apache Maven Bundle Plugin 5.1.8"}, 1271 {Key: "Specification-Version", Value: "2.15.2"}, 1272 }, 1273 }, 1274 // not under test 1275 //ArchiveDigests: []file.Digest{{Algorithm: "sha1", Value: "abd3e329270fc54a2acaceb45420fd5710ecefd5"}}, 1276 }, 1277 }, 1278 }, 1279 }, 1280 { 1281 name: "multiple pom for parent selection regression (pr 2231)", 1282 fixtureName: "api-all-2.0.0-sources", 1283 assignParent: true, 1284 expectedPkgs: []pkg.Package{ 1285 { 1286 Name: "api-all", 1287 Version: "2.0.0", 1288 Type: pkg.JavaPkg, 1289 Language: pkg.Java, 1290 PURL: "pkg:maven/org.apache.directory.api/api-all@2.0.0", 1291 Locations: file.NewLocationSet(file.NewLocation("test-fixtures/jar-metadata/cache/api-all-2.0.0-sources.jar")), 1292 Metadata: pkg.JavaArchive{ 1293 VirtualPath: "test-fixtures/jar-metadata/cache/api-all-2.0.0-sources.jar", 1294 Manifest: &pkg.JavaManifest{ 1295 Main: []pkg.KeyValue{ 1296 { 1297 Key: "Manifest-Version", 1298 Value: "1.0", 1299 }, 1300 { 1301 Key: "Built-By", 1302 Value: "elecharny", 1303 }, 1304 { 1305 Key: "Created-By", 1306 Value: "Apache Maven 3.6.0", 1307 }, 1308 { 1309 Key: "Build-Jdk", 1310 Value: "1.8.0_191", 1311 }, 1312 }, 1313 }, 1314 PomProperties: &pkg.JavaPomProperties{ 1315 Path: "META-INF/maven/org.apache.directory.api/api-all/pom.properties", 1316 GroupID: "org.apache.directory.api", 1317 ArtifactID: "api-all", 1318 Version: "2.0.0", 1319 }, 1320 }, 1321 }, 1322 { 1323 Name: "api-asn1-api", 1324 Version: "2.0.0", 1325 PURL: "pkg:maven/org.apache.directory.api/api-asn1-api@2.0.0", 1326 Locations: file.NewLocationSet(file.NewLocation("test-fixtures/jar-metadata/cache/api-all-2.0.0-sources.jar")), 1327 Type: pkg.JavaPkg, 1328 Language: pkg.Java, 1329 Metadata: pkg.JavaArchive{ 1330 VirtualPath: "test-fixtures/jar-metadata/cache/api-all-2.0.0-sources.jar:org.apache.directory.api:api-asn1-api", 1331 PomProperties: &pkg.JavaPomProperties{ 1332 Path: "META-INF/maven/org.apache.directory.api/api-asn1-api/pom.properties", 1333 GroupID: "org.apache.directory.api", 1334 ArtifactID: "api-asn1-api", 1335 Version: "2.0.0", 1336 }, 1337 PomProject: &pkg.JavaPomProject{ 1338 Path: "META-INF/maven/org.apache.directory.api/api-asn1-api/pom.xml", 1339 ArtifactID: "api-asn1-api", 1340 Name: "Apache Directory API ASN.1 API", 1341 Description: "ASN.1 API", 1342 Parent: &pkg.JavaPomParent{ 1343 GroupID: "org.apache.directory.api", 1344 ArtifactID: "api-asn1-parent", 1345 Version: "2.0.0", 1346 }, 1347 }, 1348 Parent: nil, 1349 }, 1350 }, 1351 }, 1352 }, 1353 { 1354 name: "exclude instrumentation jars with Weave-Classes in manifest", 1355 fixtureName: "spring-instrumentation-4.3.0-1.0", 1356 expectedPkgs: nil, // we expect no packages to be discovered when Weave-Classes present in the manifest 1357 }, 1358 } 1359 for _, tt := range tests { 1360 t.Run(tt.name, func(t *testing.T) { 1361 gap := newGenericArchiveParserAdapter(ArchiveCatalogerConfig{}) 1362 if tt.assignParent { 1363 assignParent(&tt.expectedPkgs[0], tt.expectedPkgs[1:]...) 1364 } 1365 for i := range tt.expectedPkgs { 1366 tt.expectedPkgs[i].SetID() 1367 } 1368 pkgtest.NewCatalogTester(). 1369 FromFile(t, generateJavaMetadataJarFixture(t, tt.fixtureName)). 1370 Expects(tt.expectedPkgs, tt.expectedRelationships). 1371 WithCompareOptions( 1372 cmpopts.IgnoreFields(pkg.JavaArchive{}, "ArchiveDigests"), 1373 cmp.Comparer(func(x, y pkg.KeyValue) bool { 1374 if x.Key != y.Key { 1375 return false 1376 } 1377 if x.Value != y.Value { 1378 return false 1379 } 1380 1381 return true 1382 }), 1383 ). 1384 TestParser(t, gap.parseJavaArchive) 1385 }) 1386 } 1387 } 1388 1389 func assignParent(parent *pkg.Package, childPackages ...pkg.Package) { 1390 for i, jp := range childPackages { 1391 if v, ok := jp.Metadata.(pkg.JavaArchive); ok { 1392 parent := *parent 1393 // PURL are not calculated after the fact for parent 1394 parent.PURL = "" 1395 v.Parent = &parent 1396 childPackages[i].Metadata = v 1397 } 1398 } 1399 } 1400 1401 func generateJavaMetadataJarFixture(t *testing.T, fixtureName string) string { 1402 fixturePath := filepath.Join("test-fixtures/jar-metadata/cache/", fixtureName+".jar") 1403 if _, err := os.Stat(fixturePath); !os.IsNotExist(err) { 1404 // fixture already exists... 1405 return fixturePath 1406 } 1407 1408 makeTask := filepath.Join("cache", fixtureName+".jar") 1409 t.Logf(color.Bold.Sprintf("Generating Fixture from 'make %s'", makeTask)) 1410 1411 cwd, err := os.Getwd() 1412 if err != nil { 1413 t.Errorf("unable to get cwd: %+v", err) 1414 } 1415 1416 cmd := exec.Command("make", makeTask) 1417 cmd.Dir = filepath.Join(cwd, "test-fixtures/jar-metadata") 1418 1419 run(t, cmd) 1420 1421 return fixturePath 1422 } 1423 1424 func run(t testing.TB, cmd *exec.Cmd) { 1425 1426 stderr, err := cmd.StderrPipe() 1427 if err != nil { 1428 t.Fatalf("could not get stderr: %+v", err) 1429 } 1430 stdout, err := cmd.StdoutPipe() 1431 if err != nil { 1432 t.Fatalf("could not get stdout: %+v", err) 1433 } 1434 1435 err = cmd.Start() 1436 if err != nil { 1437 t.Fatalf("failed to start cmd: %+v", err) 1438 } 1439 1440 show := func(label string, reader io.ReadCloser) { 1441 scanner := bufio.NewScanner(reader) 1442 scanner.Split(bufio.ScanLines) 1443 for scanner.Scan() { 1444 t.Logf("%s: %s", label, scanner.Text()) 1445 } 1446 } 1447 go show("out", stdout) 1448 go show("err", stderr) 1449 1450 if err := cmd.Wait(); err != nil { 1451 if exiterr, ok := err.(*exec.ExitError); ok { 1452 // The program has exited with an exit code != 0 1453 1454 // This works on both Unix and Windows. Although package 1455 // syscall is generally platform dependent, WaitStatus is 1456 // defined for both Unix and Windows and in both cases has 1457 // an ExitStatus() method with the same signature. 1458 if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { 1459 if status.ExitStatus() != 0 { 1460 t.Fatalf("failed to generate fixture: rc=%d", status.ExitStatus()) 1461 } 1462 } 1463 } else { 1464 t.Fatalf("unable to get generate fixture result: %+v", err) 1465 } 1466 } 1467 } 1468 1469 // setup sets up a test HTTP server for mocking requests to maven central. 1470 // The returned url is injected into the Config so the client uses the test server. 1471 // Tests should register handlers on mux to simulate the expected request/response structure 1472 func setup() (mux *http.ServeMux, serverURL string, teardown func()) { 1473 // mux is the HTTP request multiplexer used with the test server. 1474 mux = http.NewServeMux() 1475 1476 // We want to ensure that tests catch mistakes where the endpoint URL is 1477 // specified as absolute rather than relative. It only makes a difference 1478 // when there's a non-empty base URL path. So, use that. See issue #752. 1479 apiHandler := http.NewServeMux() 1480 apiHandler.Handle("/", mux) 1481 // server is a test HTTP server used to provide mock API responses. 1482 server := httptest.NewServer(apiHandler) 1483 1484 return mux, server.URL, server.Close 1485 }