github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/nix/derivation_test.go (about)

     1  package nix
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/nix-community/go-nix/pkg/derivation"
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/require"
     9  
    10  	"github.com/anchore/syft/syft/file"
    11  )
    12  
    13  func TestDerivationCollection_Add(t *testing.T) {
    14  	c := newDerivations()
    15  
    16  	d := derivationFile{
    17  		Location: file.NewLocation("/nix/store/xyz789-foo.drv"),
    18  		Derivation: derivation.Derivation{
    19  			Outputs: map[string]*derivation.Output{
    20  				"out": {
    21  					Path: "/nix/store/abc123-foo",
    22  				},
    23  				"dev": {
    24  					Path: "/nix/store/def456-foo-dev",
    25  				},
    26  			},
    27  		},
    28  	}
    29  
    30  	c.add(d)
    31  
    32  	assert.Len(t, c.derivationsByDrvPath, 1)
    33  	assert.Len(t, c.drvPathByOutputPath, 2)
    34  	assert.Equal(t, "/nix/store/xyz789-foo.drv", c.drvPathByOutputPath["/nix/store/abc123-foo"])
    35  	assert.Equal(t, "/nix/store/xyz789-foo.drv", c.drvPathByOutputPath["/nix/store/def456-foo-dev"])
    36  }
    37  
    38  func TestDerivationCollection_AddNilOutputs(t *testing.T) {
    39  	c := newDerivations()
    40  
    41  	d := derivationFile{
    42  		Location: file.NewLocation("/nix/store/xyz789-foo.drv"),
    43  		Derivation: derivation.Derivation{
    44  			Outputs: map[string]*derivation.Output{
    45  				"out": nil,
    46  				"dev": {
    47  					Path: "",
    48  				},
    49  			},
    50  		},
    51  	}
    52  
    53  	c.add(d)
    54  
    55  	assert.Len(t, c.derivationsByDrvPath, 1)
    56  	assert.Empty(t, c.drvPathByOutputPath)
    57  }
    58  func TestDerivationCollection_FindDerivationForOutputPath(t *testing.T) {
    59  	c := newDerivations()
    60  
    61  	// standard derivation
    62  	standardDrv := derivationFile{
    63  		Location: file.NewLocation("/nix/store/xyz789-foo.drv"),
    64  		Derivation: derivation.Derivation{
    65  			Outputs: map[string]*derivation.Output{
    66  				"out": {
    67  					Path: "/nix/store/abc123-foo",
    68  				},
    69  			},
    70  		},
    71  	}
    72  	c.add(standardDrv)
    73  
    74  	// derivation with multiple outputs
    75  	multiOutputDrv := derivationFile{
    76  		Location: file.NewLocation("/nix/store/multi-output.drv"),
    77  		Derivation: derivation.Derivation{
    78  			Outputs: map[string]*derivation.Output{
    79  				"out": {
    80  					Path: "/nix/store/multi-out-path",
    81  				},
    82  				"dev": {
    83  					Path: "/nix/store/multi-dev-path",
    84  				},
    85  				"doc": {
    86  					Path: "/nix/store/multi-doc-path",
    87  				},
    88  			},
    89  		},
    90  	}
    91  	c.add(multiOutputDrv)
    92  
    93  	// derivation with special characters in path
    94  	specialCharsDrv := derivationFile{
    95  		Location: file.NewLocation("/nix/store/special-chars+_.drv"),
    96  		Derivation: derivation.Derivation{
    97  			Outputs: map[string]*derivation.Output{
    98  				"out": {
    99  					Path: "/nix/store/special-chars+_-output",
   100  				},
   101  			},
   102  		},
   103  	}
   104  	c.add(specialCharsDrv)
   105  
   106  	// derivation with same output path as another (should override)
   107  	duplicateOutputDrv := derivationFile{
   108  		Location: file.NewLocation("/nix/store/duplicate.drv"),
   109  		Derivation: derivation.Derivation{
   110  			Outputs: map[string]*derivation.Output{
   111  				"out": {
   112  					Path: "/nix/store/abc123-foo", // same as standardDrv output
   113  				},
   114  			},
   115  		},
   116  	}
   117  	c.add(duplicateOutputDrv)
   118  
   119  	tests := []struct {
   120  		name       string
   121  		outputPath string
   122  		expected   *derivationFile
   123  	}{
   124  		{
   125  			name:       "output path exists",
   126  			outputPath: "/nix/store/abc123-foo",
   127  			expected:   &duplicateOutputDrv,
   128  		},
   129  		{
   130  			name:       "output path exists without leading slash",
   131  			outputPath: "nix/store/abc123-foo",
   132  			expected:   &duplicateOutputDrv,
   133  		},
   134  		{
   135  			name:       "output path does not exist",
   136  			outputPath: "/nix/store/nonexistent",
   137  		},
   138  		{
   139  			name:       "multiple output derivation - out path",
   140  			outputPath: "/nix/store/multi-out-path",
   141  			expected:   &multiOutputDrv,
   142  		},
   143  		{
   144  			name:       "multiple output derivation - dev path",
   145  			outputPath: "/nix/store/multi-dev-path",
   146  			expected:   &multiOutputDrv,
   147  		},
   148  		{
   149  			name:       "special characters in path",
   150  			outputPath: "/nix/store/special-chars+_-output",
   151  			expected:   &specialCharsDrv,
   152  		},
   153  		{
   154  			name:       "empty string path",
   155  			outputPath: "",
   156  		},
   157  		{
   158  			name:       "path with just a slash",
   159  			outputPath: "/",
   160  		},
   161  		{
   162  			name:       "drv path exists in mapping but not in derivations",
   163  			outputPath: "/nix/store/missing",
   164  		},
   165  	}
   166  
   167  	// add a path mapping to a derivation that doesn't exist
   168  	c.drvPathByOutputPath["/nix/store/missing"] = "/nix/store/nonexistent.drv"
   169  
   170  	for _, tt := range tests {
   171  		t.Run(tt.name, func(t *testing.T) {
   172  			result := c.findDerivationForOutputPath(tt.outputPath)
   173  			if tt.expected == nil {
   174  				assert.Nil(t, result)
   175  			} else {
   176  				require.NotNil(t, result)
   177  				assert.Equal(t, tt.expected.Location.RealPath, result.Location.RealPath)
   178  			}
   179  		})
   180  	}
   181  }
   182  
   183  func TestDerivationCollection_FindDependencies(t *testing.T) {
   184  	c := newDerivations()
   185  
   186  	// set up a dependency tree:
   187  	// - foo depends on bar and baz
   188  	// - bar depends on qux
   189  
   190  	// create "qux" derivation
   191  	quxDrv := derivationFile{
   192  		Location: file.NewLocation("/nix/store/qux.drv"),
   193  		Derivation: derivation.Derivation{
   194  			Outputs: map[string]*derivation.Output{
   195  				"out": {
   196  					Path: "/nix/store/qux-path",
   197  				},
   198  			},
   199  		},
   200  	}
   201  	c.add(quxDrv)
   202  
   203  	// create "bar" derivation which depends on qux
   204  	barDrv := derivationFile{
   205  		Location: file.NewLocation("/nix/store/bar.drv"),
   206  		Derivation: derivation.Derivation{
   207  			Outputs: map[string]*derivation.Output{
   208  				"out": {
   209  					Path: "/nix/store/bar-path",
   210  				},
   211  			},
   212  			InputDerivations: map[string][]string{
   213  				"/nix/store/qux.drv": {"out"},
   214  			},
   215  		},
   216  	}
   217  	c.add(barDrv)
   218  
   219  	// create "baz" derivation
   220  	bazDrv := derivationFile{
   221  		Location: file.NewLocation("/nix/store/baz.drv"),
   222  		Derivation: derivation.Derivation{
   223  			Outputs: map[string]*derivation.Output{
   224  				"out": {
   225  					Path: "/nix/store/baz-path",
   226  				},
   227  			},
   228  		},
   229  	}
   230  	c.add(bazDrv)
   231  
   232  	// create "foo" derivation which depends on bar and baz
   233  	fooDrv := derivationFile{
   234  		Location: file.NewLocation("/nix/store/foo.drv"),
   235  		Derivation: derivation.Derivation{
   236  			Outputs: map[string]*derivation.Output{
   237  				"out": {
   238  					Path: "/nix/store/foo-path",
   239  				},
   240  			},
   241  			InputDerivations: map[string][]string{
   242  				"/nix/store/bar.drv": {"out"},
   243  				"/nix/store/baz.drv": {"out"},
   244  			},
   245  			InputSources: []string{
   246  				"/nix/store/src1",
   247  				"/nix/store/src2",
   248  			},
   249  		},
   250  	}
   251  	c.add(fooDrv)
   252  
   253  	// add a test case for empty input names
   254  	emptyNamesDrv := derivationFile{
   255  		Location: file.NewLocation("/nix/store/empty-names.drv"),
   256  		Derivation: derivation.Derivation{
   257  			Outputs: map[string]*derivation.Output{
   258  				"out": {
   259  					Path: "/nix/store/empty-names-path",
   260  				},
   261  			},
   262  			InputDerivations: map[string][]string{
   263  				"/nix/store/bar.drv": {},
   264  			},
   265  		},
   266  	}
   267  	c.add(emptyNamesDrv)
   268  
   269  	// add a test case for empty input sources
   270  	emptySourcesDrv := derivationFile{
   271  		Location: file.NewLocation("/nix/store/empty-sources.drv"),
   272  		Derivation: derivation.Derivation{
   273  			Outputs: map[string]*derivation.Output{
   274  				"out": {
   275  					Path: "/nix/store/empty-sources-path",
   276  				},
   277  			},
   278  			InputDerivations: map[string][]string{
   279  				"/nix/store/bar.drv": {"out"},
   280  			},
   281  			InputSources: []string{
   282  				"",
   283  			},
   284  		},
   285  	}
   286  	c.add(emptySourcesDrv)
   287  
   288  	tests := []struct {
   289  		name     string
   290  		path     string
   291  		expected []string
   292  	}{
   293  		{
   294  			name: "lookup by derivation path",
   295  			path: "/nix/store/foo.drv",
   296  			expected: []string{
   297  				"/nix/store/bar-path",
   298  				"/nix/store/baz-path",
   299  				"/nix/store/src1",
   300  				"/nix/store/src2",
   301  			},
   302  		},
   303  		{
   304  			name: "lookup by output path",
   305  			path: "/nix/store/foo-path",
   306  			expected: []string{
   307  				"/nix/store/bar-path",
   308  				"/nix/store/baz-path",
   309  				"/nix/store/src1",
   310  				"/nix/store/src2",
   311  			},
   312  		},
   313  		{
   314  			name:     "lookup by derivation with no inputs",
   315  			path:     "/nix/store/qux.drv",
   316  			expected: nil,
   317  		},
   318  		{
   319  			name:     "lookup nonexistent path",
   320  			path:     "/nix/store/nonexistent",
   321  			expected: nil,
   322  		},
   323  		{
   324  			name:     "lookup derivation with empty input names",
   325  			path:     "/nix/store/empty-names.drv",
   326  			expected: nil,
   327  		},
   328  		{
   329  			name: "lookup derivation with empty input sources",
   330  			path: "/nix/store/empty-sources.drv",
   331  			expected: []string{
   332  				"/nix/store/bar-path",
   333  			},
   334  		},
   335  	}
   336  
   337  	for _, tt := range tests {
   338  		t.Run(tt.name, func(t *testing.T) {
   339  			result := c.findDependencies(tt.path)
   340  			if tt.expected == nil {
   341  				assert.Nil(t, result)
   342  			} else {
   343  				require.NotNil(t, result)
   344  				assert.ElementsMatch(t, tt.expected, result)
   345  			}
   346  		})
   347  	}
   348  }
   349  
   350  func TestDerivationCollection_NamedOutputStorePath(t *testing.T) {
   351  	c := newDerivations()
   352  
   353  	d := derivationFile{
   354  		Location: file.NewLocation("/nix/store/xyz789-foo.drv"),
   355  		Derivation: derivation.Derivation{
   356  			Outputs: map[string]*derivation.Output{
   357  				"out": {
   358  					Path: "/nix/store/abc123-foo",
   359  				},
   360  				"dev": {
   361  					Path: "/nix/store/def456-foo-dev",
   362  				},
   363  			},
   364  		},
   365  	}
   366  
   367  	c.add(d)
   368  
   369  	tests := []struct {
   370  		name     string
   371  		drvPath  string
   372  		outName  string
   373  		expected string
   374  	}{
   375  		{
   376  			name:     "existing drv and output",
   377  			drvPath:  "/nix/store/xyz789-foo.drv",
   378  			outName:  "out",
   379  			expected: "/nix/store/abc123-foo",
   380  		},
   381  		{
   382  			name:     "existing drv and dev output",
   383  			drvPath:  "/nix/store/xyz789-foo.drv",
   384  			outName:  "dev",
   385  			expected: "/nix/store/def456-foo-dev",
   386  		},
   387  		{
   388  			name:     "existing drv but nonexistent output",
   389  			drvPath:  "/nix/store/xyz789-foo.drv",
   390  			outName:  "nonexistent",
   391  			expected: "",
   392  		},
   393  		{
   394  			name:     "nonexistent drv",
   395  			drvPath:  "/nix/store/nonexistent.drv",
   396  			outName:  "out",
   397  			expected: "",
   398  		},
   399  	}
   400  
   401  	for _, tt := range tests {
   402  		t.Run(tt.name, func(t *testing.T) {
   403  			result := c.namedOutputStorePath(tt.drvPath, tt.outName)
   404  			assert.Equal(t, tt.expected, result)
   405  		})
   406  	}
   407  }