syft/syft/pkg/package_test.go
Alex Goodman d7a51a69dd
Update java generic cataloger (#1329)
* remove centralize pURL generation

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* port java cataloger to new generic cataloger pattern

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* remove common.GenericCataloger

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* update format test fixtures to reflect ID updates

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* fix package sort instability for encode-decode-encode cycles

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2022-11-09 14:55:54 +00:00

466 lines
11 KiB
Go

package pkg
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/syft/syft/source"
)
func TestIDUniqueness(t *testing.T) {
originalLocation := source.Location{
Coordinates: source.Coordinates{
RealPath: "39.0742° N, 21.8243° E",
FileSystemID: "Earth",
},
VirtualPath: "/Ancient-Greece",
}
originalPkg := Package{
Name: "pi",
Version: "3.14",
FoundBy: "Archimedes",
Locations: source.NewLocationSet(
originalLocation,
),
Licenses: []string{
"cc0-1.0",
"MIT",
},
Language: "math",
Type: PythonPkg,
CPEs: []CPE{
must(NewCPE(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`)),
},
PURL: "pkg:pypi/pi@3.14",
MetadataType: PythonPackageMetadataType,
Metadata: PythonPackageMetadata{
Name: "pi",
Version: "3.14",
License: "cc0-1.0",
Author: "Archimedes",
AuthorEmail: "Archimedes@circles.io",
Platform: "universe",
SitePackagesRootPath: "Pi",
},
}
// this is a set of differential tests, ensuring that select mutations are reflected in the fingerprint (or not)
tests := []struct {
name string
transform func(pkg Package) Package
expectedIDComparison assert.ComparisonAssertionFunc
}{
{
name: "go case (no transform)",
transform: func(pkg Package) Package {
// do nothing!
return pkg
},
expectedIDComparison: assert.Equal,
},
{
name: "same metadata is ignored",
transform: func(pkg Package) Package {
// note: this is the same as the original values, just a new allocation
pkg.Metadata = PythonPackageMetadata{
Name: "pi",
Version: "3.14",
License: "cc0-1.0",
Author: "Archimedes",
AuthorEmail: "Archimedes@circles.io",
Platform: "universe",
SitePackagesRootPath: "Pi",
}
return pkg
},
expectedIDComparison: assert.Equal,
},
{
name: "licenses order is ignored",
transform: func(pkg Package) Package {
// note: same as the original package, only a different order
pkg.Licenses = []string{
"MIT",
"cc0-1.0",
}
return pkg
},
expectedIDComparison: assert.Equal,
},
{
name: "name is reflected",
transform: func(pkg Package) Package {
pkg.Name = "new!"
return pkg
},
expectedIDComparison: assert.NotEqual,
},
{
name: "location is reflected",
transform: func(pkg Package) Package {
locations := source.NewLocationSet(pkg.Locations.ToSlice()...)
locations.Add(source.NewLocation("/somewhere/new"))
pkg.Locations = locations
return pkg
},
expectedIDComparison: assert.NotEqual,
},
{
name: "same path for different filesystem is NOT reflected",
transform: func(pkg Package) Package {
newLocation := originalLocation
newLocation.FileSystemID = "Mars"
pkg.Locations = source.NewLocationSet(newLocation)
return pkg
},
expectedIDComparison: assert.Equal,
},
{
name: "multiple equivalent paths for different filesystem is NOT reflected",
transform: func(pkg Package) Package {
newLocation := originalLocation
newLocation.FileSystemID = "Mars"
locations := source.NewLocationSet(pkg.Locations.ToSlice()...)
locations.Add(newLocation, originalLocation)
pkg.Locations = locations
return pkg
},
expectedIDComparison: assert.Equal,
},
{
name: "version is reflected",
transform: func(pkg Package) Package {
pkg.Version = "new!"
return pkg
},
expectedIDComparison: assert.NotEqual,
},
{
name: "licenses is reflected",
transform: func(pkg Package) Package {
pkg.Licenses = []string{"new!"}
return pkg
},
expectedIDComparison: assert.NotEqual,
},
{
name: "type is reflected",
transform: func(pkg Package) Package {
pkg.Type = RustPkg
return pkg
},
expectedIDComparison: assert.NotEqual,
},
{
name: "metadata type is reflected",
transform: func(pkg Package) Package {
pkg.MetadataType = RustCargoPackageMetadataType
return pkg
},
expectedIDComparison: assert.NotEqual,
},
{
name: "CPEs is ignored",
transform: func(pkg Package) Package {
pkg.CPEs = []CPE{}
return pkg
},
expectedIDComparison: assert.Equal,
},
{
name: "pURL is ignored",
transform: func(pkg Package) Package {
pkg.PURL = "new!"
return pkg
},
expectedIDComparison: assert.Equal,
},
{
name: "language is NOT reflected",
transform: func(pkg Package) Package {
pkg.Language = Rust
return pkg
},
expectedIDComparison: assert.Equal,
},
{
name: "metadata mutation is reflected",
transform: func(pkg Package) Package {
metadata := pkg.Metadata.(PythonPackageMetadata)
metadata.Name = "new!"
pkg.Metadata = metadata
return pkg
},
expectedIDComparison: assert.NotEqual,
},
{
name: "new metadata is reflected",
transform: func(pkg Package) Package {
pkg.Metadata = PythonPackageMetadata{
Name: "new!",
}
return pkg
},
expectedIDComparison: assert.NotEqual,
},
{
name: "nil metadata is reflected",
transform: func(pkg Package) Package {
pkg.Metadata = nil
return pkg
},
expectedIDComparison: assert.NotEqual,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
originalPkg.SetID()
transformedPkg := test.transform(originalPkg)
transformedPkg.SetID()
originalFingerprint := originalPkg.ID()
assert.NotEmpty(t, originalFingerprint)
transformedFingerprint := transformedPkg.ID()
assert.NotEmpty(t, transformedFingerprint)
test.expectedIDComparison(t, originalFingerprint, transformedFingerprint)
})
}
}
func TestPackage_Merge(t *testing.T) {
originalLocation := source.Location{
Coordinates: source.Coordinates{
RealPath: "39.0742° N, 21.8243° E",
FileSystemID: "Earth",
},
VirtualPath: "/Ancient-Greece",
}
similarLocation := originalLocation
similarLocation.FileSystemID = "Mars"
tests := []struct {
name string
subject Package
other Package
expected *Package
}{
{
name: "merge two packages (different cpes + locations)",
subject: Package{
Name: "pi",
Version: "3.14",
FoundBy: "Archimedes",
Locations: source.NewLocationSet(
originalLocation,
),
Licenses: []string{
"cc0-1.0",
"MIT",
},
Language: "math",
Type: PythonPkg,
CPEs: []CPE{
must(NewCPE(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`)),
},
PURL: "pkg:pypi/pi@3.14",
MetadataType: PythonPackageMetadataType,
Metadata: PythonPackageMetadata{
Name: "pi",
Version: "3.14",
License: "cc0-1.0",
Author: "Archimedes",
AuthorEmail: "Archimedes@circles.io",
Platform: "universe",
SitePackagesRootPath: "Pi",
},
},
other: Package{
Name: "pi",
Version: "3.14",
FoundBy: "Archimedes",
Locations: source.NewLocationSet(
similarLocation, // NOTE: difference; we have a different layer but the same path
),
Licenses: []string{
"cc0-1.0",
"MIT",
},
Language: "math",
Type: PythonPkg,
CPEs: []CPE{
must(NewCPE(`cpe:2.3:a:DIFFERENT:pi:3.14:*:*:*:*:math:*:*`)), // NOTE: difference
},
PURL: "pkg:pypi/pi@3.14",
MetadataType: PythonPackageMetadataType,
Metadata: PythonPackageMetadata{
Name: "pi",
Version: "3.14",
License: "cc0-1.0",
Author: "Archimedes",
AuthorEmail: "Archimedes@circles.io",
Platform: "universe",
SitePackagesRootPath: "Pi",
},
},
expected: &Package{
Name: "pi",
Version: "3.14",
FoundBy: "Archimedes",
Locations: source.NewLocationSet(
originalLocation,
similarLocation, // NOTE: merge!
),
Licenses: []string{
"cc0-1.0",
"MIT",
},
Language: "math",
Type: PythonPkg,
CPEs: []CPE{
must(NewCPE(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`)),
must(NewCPE(`cpe:2.3:a:DIFFERENT:pi:3.14:*:*:*:*:math:*:*`)), // NOTE: merge!
},
PURL: "pkg:pypi/pi@3.14",
MetadataType: PythonPackageMetadataType,
Metadata: PythonPackageMetadata{
Name: "pi",
Version: "3.14",
License: "cc0-1.0",
Author: "Archimedes",
AuthorEmail: "Archimedes@circles.io",
Platform: "universe",
SitePackagesRootPath: "Pi",
},
},
},
{
name: "error when there are different IDs",
subject: Package{
Name: "pi",
Version: "3.14",
FoundBy: "Archimedes",
Locations: source.NewLocationSet(
originalLocation,
),
Licenses: []string{
"cc0-1.0",
"MIT",
},
Language: "math",
Type: PythonPkg,
CPEs: []CPE{
must(NewCPE(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`)),
},
PURL: "pkg:pypi/pi@3.14",
MetadataType: PythonPackageMetadataType,
Metadata: PythonPackageMetadata{
Name: "pi",
Version: "3.14",
License: "cc0-1.0",
Author: "Archimedes",
AuthorEmail: "Archimedes@circles.io",
Platform: "universe",
SitePackagesRootPath: "Pi",
},
},
other: Package{
Name: "pi-DIFFERENT", // difference
Version: "3.14",
FoundBy: "Archimedes",
Locations: source.NewLocationSet(
originalLocation,
),
Licenses: []string{
"cc0-1.0",
"MIT",
},
Language: "math",
Type: PythonPkg,
CPEs: []CPE{
must(NewCPE(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`)),
},
PURL: "pkg:pypi/pi@3.14",
MetadataType: PythonPackageMetadataType,
Metadata: PythonPackageMetadata{
Name: "pi",
Version: "3.14",
License: "cc0-1.0",
Author: "Archimedes",
AuthorEmail: "Archimedes@circles.io",
Platform: "universe",
SitePackagesRootPath: "Pi",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.subject.SetID()
tt.other.SetID()
err := tt.subject.merge(tt.other)
if tt.expected == nil {
require.Error(t, err)
return
}
require.NoError(t, err)
tt.expected.SetID()
require.Equal(t, tt.expected.id, tt.subject.id)
if diff := cmp.Diff(*tt.expected, tt.subject,
cmp.AllowUnexported(Package{}),
cmp.Comparer(
func(x, y source.LocationSet) bool {
return cmp.Equal(
x.ToSlice(), y.ToSlice(),
cmp.AllowUnexported(source.Location{}),
cmp.AllowUnexported(file.Reference{}),
)
},
),
); diff != "" {
t.Errorf("unexpected result from parsing (-expected +actual)\n%s", diff)
}
})
}
}
func TestIsValid(t *testing.T) {
cases := []struct {
name string
given *Package
want bool
}{
{
name: "nil",
given: nil,
want: false,
},
{
name: "has-name",
given: &Package{Name: "paul"},
want: true,
},
{
name: "has-no-name",
given: &Package{},
want: false,
},
}
for _, c := range cases {
require.Equal(t, c.want, IsValid(c.given), "when package: %s", c.name)
}
}