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