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 }