github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/java/archive_parser_test.go (about)

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