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  }