github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/pkg/cataloger/internal/dependency/resolver_test.go (about)

     1  package dependency
     2  
     3  import (
     4  	"errors"
     5  	"testing"
     6  
     7  	"github.com/google/go-cmp/cmp"
     8  	"github.com/stretchr/testify/assert"
     9  
    10  	"github.com/anchore/syft/syft/artifact"
    11  	"github.com/anchore/syft/syft/pkg"
    12  )
    13  
    14  func Test_resolve(t *testing.T) {
    15  	a := pkg.Package{
    16  		Name: "a",
    17  	}
    18  	a.SetID()
    19  
    20  	b := pkg.Package{
    21  		Name: "b",
    22  	}
    23  	b.SetID()
    24  
    25  	c := pkg.Package{
    26  		Name: "c",
    27  	}
    28  	c.SetID()
    29  
    30  	subjects := []pkg.Package{a, b, c}
    31  
    32  	tests := []struct {
    33  		name string
    34  		s    Specifier
    35  		want map[string][]string
    36  	}{
    37  		{
    38  			name: "find relationships between packages",
    39  			s: newSpecifierBuilder().
    40  				WithProvides(a /* provides */, "a-resource").
    41  				WithRequires(b /* requires */, "a-resource").
    42  				Specifier(),
    43  			want: map[string][]string{
    44  				"b": /* depends on */ {"a"},
    45  			},
    46  		},
    47  		{
    48  			name: "deduplicates provider keys",
    49  			s: newSpecifierBuilder().
    50  				WithProvides(a /* provides */, "a-resource", "a-resource", "a-resource").
    51  				WithRequires(b /* requires */, "a-resource", "a-resource", "a-resource").
    52  				Specifier(),
    53  			want: map[string][]string{
    54  				"b": /* depends on */ {"a"},
    55  				// note: we're NOT seeing:
    56  				// "b": /* depends on */ {"a", "a", "a"},
    57  			},
    58  		},
    59  		{
    60  			name: "deduplicates crafted relationships",
    61  			s: newSpecifierBuilder().
    62  				WithProvides(a /* provides */, "a1-resource", "a2-resource", "a3-resource").
    63  				WithRequires(b /* requires */, "a1-resource", "a2-resource").
    64  				Specifier(),
    65  			want: map[string][]string{
    66  				"b": /* depends on */ {"a"},
    67  				// note: we're NOT seeing:
    68  				// "b": /* depends on */ {"a", "a"},
    69  			},
    70  		},
    71  	}
    72  	for _, tt := range tests {
    73  		t.Run(tt.name, func(t *testing.T) {
    74  			relationships := resolve(tt.s, subjects)
    75  			if d := cmp.Diff(tt.want, abstractRelationships(t, relationships)); d != "" {
    76  				t.Errorf("unexpected relationships (-want +got):\n%s", d)
    77  			}
    78  		})
    79  	}
    80  }
    81  
    82  type specifierBuilder struct {
    83  	provides map[string][]string
    84  	requires map[string][]string
    85  }
    86  
    87  func newSpecifierBuilder() *specifierBuilder {
    88  	return &specifierBuilder{
    89  		provides: make(map[string][]string),
    90  		requires: make(map[string][]string),
    91  	}
    92  }
    93  
    94  func (m *specifierBuilder) WithProvides(p pkg.Package, provides ...string) *specifierBuilder {
    95  	m.provides[p.Name] = append(m.provides[p.Name], provides...)
    96  	return m
    97  }
    98  
    99  func (m *specifierBuilder) WithRequires(p pkg.Package, requires ...string) *specifierBuilder {
   100  	m.requires[p.Name] = append(m.requires[p.Name], requires...)
   101  	return m
   102  }
   103  
   104  func (m specifierBuilder) Specifier() Specifier {
   105  	return func(p pkg.Package) Specification {
   106  		return Specification{
   107  			Provides: m.provides[p.Name],
   108  			Requires: m.requires[p.Name],
   109  		}
   110  	}
   111  }
   112  
   113  func abstractRelationships(t testing.TB, relationships []artifact.Relationship) map[string][]string {
   114  	t.Helper()
   115  
   116  	abstracted := make(map[string][]string)
   117  	for _, relationship := range relationships {
   118  		fromPkg, ok := relationship.From.(pkg.Package)
   119  		if !ok {
   120  			continue
   121  		}
   122  		toPkg, ok := relationship.To.(pkg.Package)
   123  		if !ok {
   124  			continue
   125  		}
   126  
   127  		// we build this backwards since we use DependencyOfRelationship instead of DependsOn
   128  		abstracted[toPkg.Name] = append(abstracted[toPkg.Name], fromPkg.Name)
   129  	}
   130  
   131  	return abstracted
   132  }
   133  
   134  func Test_Processor(t *testing.T) {
   135  	a := pkg.Package{
   136  		Name: "a",
   137  	}
   138  	a.SetID()
   139  
   140  	b := pkg.Package{
   141  		Name: "b",
   142  	}
   143  	b.SetID()
   144  
   145  	c := pkg.Package{
   146  		Name: "c",
   147  	}
   148  	c.SetID()
   149  
   150  	tests := []struct {
   151  		name         string
   152  		sp           Specifier
   153  		pkgs         []pkg.Package
   154  		rels         []artifact.Relationship
   155  		err          error
   156  		wantPkgCount int
   157  		wantRelCount int
   158  		wantErr      assert.ErrorAssertionFunc
   159  	}{
   160  		{
   161  			name: "happy path preserves decorated values",
   162  			sp: newSpecifierBuilder().
   163  				WithProvides(b, "b-resource").
   164  				WithRequires(c, "b-resource").
   165  				Specifier(),
   166  			pkgs: []pkg.Package{a, b, c},
   167  			rels: []artifact.Relationship{
   168  				{
   169  					From: a,
   170  					To:   b,
   171  					Type: artifact.DependencyOfRelationship,
   172  				},
   173  			},
   174  
   175  			wantPkgCount: 3,
   176  			wantRelCount: 2, // original + new
   177  		},
   178  		{
   179  			name: "error from cataloger is propagated",
   180  			sp: newSpecifierBuilder().
   181  				WithProvides(b, "b-resource").
   182  				WithRequires(c, "b-resource").
   183  				Specifier(),
   184  			err:  errors.New("surprise!"),
   185  			pkgs: []pkg.Package{a, b, c},
   186  			rels: []artifact.Relationship{
   187  				{
   188  					From: a,
   189  					To:   b,
   190  					Type: artifact.DependencyOfRelationship,
   191  				},
   192  			},
   193  			wantPkgCount: 3,
   194  			wantRelCount: 2, // original + new
   195  			wantErr:      assert.Error,
   196  		},
   197  	}
   198  	for _, tt := range tests {
   199  		t.Run(tt.name, func(t *testing.T) {
   200  			if tt.wantErr == nil {
   201  				tt.wantErr = assert.NoError
   202  			}
   203  
   204  			gotPkgs, gotRels, err := Processor(tt.sp)(tt.pkgs, tt.rels, tt.err)
   205  
   206  			tt.wantErr(t, err)
   207  			assert.Len(t, gotPkgs, tt.wantPkgCount)
   208  			assert.Len(t, gotRels, tt.wantRelCount)
   209  		})
   210  	}
   211  }