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