fix: SPDX output performance with many relationships (#3053)

This commit is contained in:
Keith Zantow
2024-07-24 10:14:20 -04:00
committed by GitHub
parent 9573f557d1
commit 741c8fb9bd
9 changed files with 412 additions and 293 deletions
+208 -200
View File
@@ -3,223 +3,231 @@ package relationship
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
)
func Test_newRelationshipIndex(t *testing.T) {
from := fakeIdentifiable{id: "from"}
to := fakeIdentifiable{id: "to"}
tests := []struct {
name string
given []artifact.Relationship
track []artifact.Relationship
add []artifact.Relationship
wantExisting []string
wantAdditional []string
}{
{
name: "empty",
},
{
name: "tracks existing relationships",
given: []artifact.Relationship{
{
From: from,
To: to,
Type: artifact.EvidentByRelationship,
},
},
wantExisting: []string{"from [evident-by] to"},
},
{
name: "deduplicate tracked relationships",
given: []artifact.Relationship{
{
From: from,
To: to,
Type: artifact.EvidentByRelationship,
},
{
From: from,
To: to,
Type: artifact.EvidentByRelationship,
},
{
From: from,
To: to,
Type: artifact.EvidentByRelationship,
},
},
track: []artifact.Relationship{
{
From: from,
To: to,
Type: artifact.EvidentByRelationship,
},
{
From: from,
To: to,
Type: artifact.EvidentByRelationship,
},
},
wantExisting: []string{"from [evident-by] to"},
},
{
name: "deduplicate any input relationships",
given: []artifact.Relationship{
{
From: from,
To: to,
Type: artifact.EvidentByRelationship,
},
{
From: from,
To: to,
Type: artifact.EvidentByRelationship,
},
},
track: []artifact.Relationship{
{
From: from,
To: to,
Type: artifact.EvidentByRelationship,
},
{
From: from,
To: to,
Type: artifact.EvidentByRelationship,
},
},
add: []artifact.Relationship{
{
From: from,
To: to,
Type: artifact.EvidentByRelationship,
},
{
From: from,
To: to,
Type: artifact.EvidentByRelationship,
},
},
wantExisting: []string{"from [evident-by] to"},
},
{
name: "deduplicate any added relationships",
add: []artifact.Relationship{
{
From: from,
To: to,
Type: artifact.EvidentByRelationship,
},
{
From: from,
To: to,
Type: artifact.EvidentByRelationship,
},
},
wantAdditional: []string{"from [evident-by] to"},
},
func Test_Index(t *testing.T) {
p1 := pkg.Package{
Name: "pkg-1",
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
idx := NewIndex(tt.given...)
idx.TrackAll(tt.track...)
idx.AddAll(tt.add...)
diffRelationships(t, tt.wantExisting, idx.existing)
diffRelationships(t, tt.wantAdditional, idx.additional)
})
p2 := pkg.Package{
Name: "pkg-2",
}
p3 := pkg.Package{
Name: "pkg-3",
}
c1 := file.Coordinates{
RealPath: "/coords/1",
}
c2 := file.Coordinates{
RealPath: "/coords/2",
}
for _, p := range []*pkg.Package{&p1, &p2, &p3} {
p.SetID()
}
r1 := artifact.Relationship{
From: p1,
To: p2,
Type: artifact.DependencyOfRelationship,
}
r2 := artifact.Relationship{
From: p1,
To: p3,
Type: artifact.DependencyOfRelationship,
}
r3 := artifact.Relationship{
From: p1,
To: c1,
Type: artifact.ContainsRelationship,
}
r4 := artifact.Relationship{
From: p2,
To: c2,
Type: artifact.ContainsRelationship,
}
r5 := artifact.Relationship{
From: p3,
To: c2,
Type: artifact.ContainsRelationship,
}
dup := artifact.Relationship{
From: p3,
To: c2,
Type: artifact.ContainsRelationship,
}
idx := NewIndex(r1, r2, r3, r4, r5, dup)
require.ElementsMatch(t, slice(r1, r2, r3, r4, r5), idx.All())
require.ElementsMatch(t, slice(r1, r4), idx.References(p2))
require.ElementsMatch(t, slice(r4), idx.References(p2, artifact.ContainsRelationship))
require.ElementsMatch(t, slice(r1), idx.To(p2))
require.ElementsMatch(t, []artifact.Relationship(nil), idx.To(p2, artifact.ContainsRelationship))
require.ElementsMatch(t, slice(r4), idx.From(p2))
require.ElementsMatch(t, slice(r4), idx.From(p2, artifact.ContainsRelationship))
}
func diffRelationships(t *testing.T, expected []string, actual []artifact.Relationship) {
if d := cmp.Diff(expected, stringRelationships(actual)); d != "" {
t.Errorf("unexpected relationships (-want, +got): %s", d)
func Test_sortOrder(t *testing.T) {
r1 := artifact.Relationship{
From: id("1"),
To: id("2"),
Type: "1",
}
}
func stringRelationships(relationships []artifact.Relationship) []string {
var result []string
for _, r := range relationships {
result = append(result, string(r.From.ID())+" ["+string(r.Type)+"] "+string(r.To.ID()))
r2 := artifact.Relationship{
From: id("2"),
To: id("3"),
Type: "1",
}
return result
}
func Test_relationshipIndex_track(t *testing.T) {
from := fakeIdentifiable{id: "from"}
to := fakeIdentifiable{id: "to"}
relationship := artifact.Relationship{From: from, To: to, Type: artifact.EvidentByRelationship}
tests := []struct {
name string
existing []artifact.Relationship
given artifact.Relationship
want bool
}{
{
name: "track returns true for a new relationship",
existing: []artifact.Relationship{},
given: relationship,
want: true,
},
{
name: "track returns false for an existing relationship",
existing: []artifact.Relationship{relationship},
given: relationship,
want: false,
},
r3 := artifact.Relationship{
From: id("3"),
To: id("4"),
Type: "1",
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
i := NewIndex(tt.existing...)
if got := i.Track(tt.given); got != tt.want {
t.Errorf("track() = %v, want %v", got, tt.want)
}
})
r4 := artifact.Relationship{
From: id("1"),
To: id("2"),
Type: "2",
}
}
func Test_relationshipIndex_add(t *testing.T) {
from := fakeIdentifiable{id: "from"}
to := fakeIdentifiable{id: "to"}
relationship := artifact.Relationship{From: from, To: to, Type: artifact.EvidentByRelationship}
tests := []struct {
name string
existing []artifact.Relationship
given artifact.Relationship
want bool
}{
{
name: "add returns true for a new relationship",
existing: []artifact.Relationship{},
given: relationship,
want: true,
},
{
name: "add returns false for an existing relationship",
existing: []artifact.Relationship{relationship},
given: relationship,
want: false,
},
r5 := artifact.Relationship{
From: id("2"),
To: id("3"),
Type: "2",
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
i := NewIndex(tt.existing...)
if got := i.Add(tt.given); got != tt.want {
t.Errorf("add() = %v, want %v", got, tt.want)
}
})
dup := artifact.Relationship{
From: id("2"),
To: id("3"),
Type: "2",
}
r6 := artifact.Relationship{
From: id("2"),
To: id("3"),
Type: "3",
}
idx := NewIndex(r5, r2, r6, r4, r1, r3, dup)
require.EqualValues(t, slice(r1, r2, r3, r4, r5, r6), idx.All())
require.EqualValues(t, slice(r1, r4), idx.From(id("1")))
require.EqualValues(t, slice(r2, r5, r6), idx.To(id("3")))
rLast := artifact.Relationship{
From: id("0"),
To: id("3"),
Type: "9999",
}
rFirst := artifact.Relationship{
From: id("0"),
To: id("3"),
Type: "1",
}
rMid := artifact.Relationship{
From: id("0"),
To: id("1"),
Type: "2",
}
idx.Add(rLast, rFirst, rMid)
require.EqualValues(t, slice(rFirst, r1, r2, r3, rMid, r4, r5, r6, rLast), idx.All())
require.EqualValues(t, slice(rFirst, r2, r5, r6, rLast), idx.To(id("3")))
}
type fakeIdentifiable struct {
id string
func Test_Coordinates(t *testing.T) {
p1 := pkg.Package{
Name: "pkg-1",
}
p2 := pkg.Package{
Name: "pkg-2",
}
p3 := pkg.Package{
Name: "pkg-3",
}
c1 := file.Coordinates{
RealPath: "/coords/1",
}
c2 := file.Coordinates{
RealPath: "/coords/2",
}
c3 := file.Coordinates{
RealPath: "/coords/3",
}
c4 := file.Coordinates{
RealPath: "/coords/4",
}
for _, p := range []*pkg.Package{&p1, &p2, &p3} {
p.SetID()
}
r1 := artifact.Relationship{
From: p1,
To: p2,
Type: artifact.DependencyOfRelationship,
}
r2 := artifact.Relationship{
From: p1,
To: p3,
Type: artifact.DependencyOfRelationship,
}
r3 := artifact.Relationship{
From: p1,
To: c1,
Type: artifact.ContainsRelationship,
}
r4 := artifact.Relationship{
From: p2,
To: c2,
Type: artifact.ContainsRelationship,
}
r5 := artifact.Relationship{
From: p3,
To: c1,
Type: artifact.ContainsRelationship,
}
r6 := artifact.Relationship{
From: p3,
To: c2,
Type: artifact.ContainsRelationship,
}
r7 := artifact.Relationship{
From: c1,
To: c3,
Type: artifact.ContainsRelationship,
}
r8 := artifact.Relationship{
From: c3,
To: c4,
Type: artifact.ContainsRelationship,
}
idx := NewIndex(r1, r2, r3, r4, r5, r6, r7, r8)
got := idx.Coordinates(p1)
require.ElementsMatch(t, slice(c1), got)
got = idx.Coordinates(p3)
require.ElementsMatch(t, slice(c1, c2), got)
}
func (f fakeIdentifiable) ID() artifact.ID {
return artifact.ID(f.id)
type id string
func (i id) ID() artifact.ID {
return artifact.ID(i)
}
func slice[T any](values ...T) []T {
return values
}