github.com/anchore/syft@v1.38.2/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: "find relationships between packages with variants",
    49  			s: newSpecifierBuilder().
    50  				WithProvides(a /* provides */, "a-resource").
    51  				WithRequires(b /* requires */, "a[variant]").
    52  				WithProvides(c /* provides */, "c-resource").
    53  				WithVariant(a /* provides */, "variant" /* which requires */, "c-resource").
    54  				Specifier(),
    55  			want: map[string][]string{
    56  				"b":/* depends on */ {"a"},
    57  				"a":/* depends on */ {"c"},
    58  			},
    59  		},
    60  		{
    61  			name: "deduplicates provider keys",
    62  			s: newSpecifierBuilder().
    63  				WithProvides(a /* provides */, "a-resource", "a-resource", "a-resource").
    64  				WithRequires(b /* requires */, "a-resource", "a-resource", "a-resource").
    65  				Specifier(),
    66  			want: map[string][]string{
    67  				"b": /* depends on */ {"a"},
    68  				// note: we're NOT seeing:
    69  				// "b": /* depends on */ {"a", "a", "a"},
    70  			},
    71  		},
    72  		{
    73  			name: "deduplicates crafted relationships",
    74  			s: newSpecifierBuilder().
    75  				WithProvides(a /* provides */, "a1-resource", "a2-resource", "a3-resource").
    76  				WithRequires(b /* requires */, "a1-resource", "a2-resource").
    77  				Specifier(),
    78  			want: map[string][]string{
    79  				"b": /* depends on */ {"a"},
    80  				// note: we're NOT seeing:
    81  				// "b": /* depends on */ {"a", "a"},
    82  			},
    83  		},
    84  	}
    85  	for _, tt := range tests {
    86  		t.Run(tt.name, func(t *testing.T) {
    87  			relationships := Resolve(tt.s, subjects)
    88  			if d := cmp.Diff(tt.want, abstractRelationships(t, relationships)); d != "" {
    89  				t.Errorf("unexpected relationships (-want +got):\n%s", d)
    90  			}
    91  		})
    92  	}
    93  }
    94  
    95  type specifierBuilder struct {
    96  	provides map[string][]string
    97  	requires map[string][]string
    98  	variants map[string]map[string][]string
    99  }
   100  
   101  func newSpecifierBuilder() *specifierBuilder {
   102  	return &specifierBuilder{
   103  		provides: make(map[string][]string),
   104  		requires: make(map[string][]string),
   105  		variants: make(map[string]map[string][]string),
   106  	}
   107  }
   108  
   109  func (m *specifierBuilder) WithProvides(p pkg.Package, provides ...string) *specifierBuilder {
   110  	m.provides[p.Name] = append(m.provides[p.Name], provides...)
   111  	return m
   112  }
   113  
   114  func (m *specifierBuilder) WithRequires(p pkg.Package, requires ...string) *specifierBuilder {
   115  	m.requires[p.Name] = append(m.requires[p.Name], requires...)
   116  	return m
   117  }
   118  
   119  func (m *specifierBuilder) WithVariant(p pkg.Package, variantName string, requires ...string) *specifierBuilder {
   120  	if _, ok := m.variants[p.Name]; !ok {
   121  		m.variants[p.Name] = make(map[string][]string)
   122  	}
   123  	m.variants[p.Name][variantName] = append(m.variants[p.Name][variantName], requires...)
   124  	return m
   125  }
   126  
   127  func (m specifierBuilder) Specifier() Specifier {
   128  	return func(p pkg.Package) Specification {
   129  		var prs []ProvidesRequires
   130  		for variantName, requires := range m.variants[p.Name] {
   131  			prs = append(prs, ProvidesRequires{
   132  				Provides: []string{p.Name + "[" + variantName + "]"},
   133  				Requires: requires,
   134  			})
   135  		}
   136  
   137  		return Specification{
   138  			ProvidesRequires: ProvidesRequires{
   139  				Provides: m.provides[p.Name],
   140  				Requires: m.requires[p.Name],
   141  			},
   142  			Variants: prs,
   143  		}
   144  	}
   145  }
   146  
   147  func abstractRelationships(t testing.TB, relationships []artifact.Relationship) map[string][]string {
   148  	t.Helper()
   149  
   150  	abstracted := make(map[string][]string)
   151  	for _, relationship := range relationships {
   152  		fromPkg, ok := relationship.From.(pkg.Package)
   153  		if !ok {
   154  			continue
   155  		}
   156  		toPkg, ok := relationship.To.(pkg.Package)
   157  		if !ok {
   158  			continue
   159  		}
   160  
   161  		// we build this backwards since we use DependencyOfRelationship instead of DependsOn
   162  		abstracted[toPkg.Name] = append(abstracted[toPkg.Name], fromPkg.Name)
   163  	}
   164  
   165  	return abstracted
   166  }
   167  
   168  func Test_Processor(t *testing.T) {
   169  	a := pkg.Package{
   170  		Name: "a",
   171  	}
   172  	a.SetID()
   173  
   174  	b := pkg.Package{
   175  		Name: "b",
   176  	}
   177  	b.SetID()
   178  
   179  	c := pkg.Package{
   180  		Name: "c",
   181  	}
   182  	c.SetID()
   183  
   184  	tests := []struct {
   185  		name         string
   186  		sp           Specifier
   187  		pkgs         []pkg.Package
   188  		rels         []artifact.Relationship
   189  		err          error
   190  		wantPkgCount int
   191  		wantRelCount int
   192  		wantErr      assert.ErrorAssertionFunc
   193  	}{
   194  		{
   195  			name: "happy path preserves decorated values",
   196  			sp: newSpecifierBuilder().
   197  				WithProvides(b, "b-resource").
   198  				WithRequires(c, "b-resource").
   199  				Specifier(),
   200  			pkgs: []pkg.Package{a, b, c},
   201  			rels: []artifact.Relationship{
   202  				{
   203  					From: a,
   204  					To:   b,
   205  					Type: artifact.DependencyOfRelationship,
   206  				},
   207  			},
   208  
   209  			wantPkgCount: 3,
   210  			wantRelCount: 2, // original + new
   211  		},
   212  		{
   213  			name: "error from cataloger is propagated",
   214  			sp: newSpecifierBuilder().
   215  				WithProvides(b, "b-resource").
   216  				WithRequires(c, "b-resource").
   217  				Specifier(),
   218  			err:  errors.New("surprise!"),
   219  			pkgs: []pkg.Package{a, b, c},
   220  			rels: []artifact.Relationship{
   221  				{
   222  					From: a,
   223  					To:   b,
   224  					Type: artifact.DependencyOfRelationship,
   225  				},
   226  			},
   227  			wantPkgCount: 3,
   228  			wantRelCount: 2, // original + new
   229  			wantErr:      assert.Error,
   230  		},
   231  	}
   232  	for _, tt := range tests {
   233  		t.Run(tt.name, func(t *testing.T) {
   234  			if tt.wantErr == nil {
   235  				tt.wantErr = assert.NoError
   236  			}
   237  
   238  			gotPkgs, gotRels, err := Processor(tt.sp)(tt.pkgs, tt.rels, tt.err)
   239  
   240  			tt.wantErr(t, err)
   241  			assert.Len(t, gotPkgs, tt.wantPkgCount)
   242  			assert.Len(t, gotRels, tt.wantRelCount)
   243  		})
   244  	}
   245  }