github.com/google/osv-scalibr@v0.4.1/artifact/image/layerscanning/trace/trace_test.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package trace 16 17 import ( 18 "fmt" 19 "testing" 20 21 "github.com/google/go-cmp/cmp" 22 "github.com/google/go-cmp/cmp/cmpopts" 23 "github.com/google/osv-scalibr/artifact/image" 24 "github.com/google/osv-scalibr/artifact/image/layerscanning/testing/fakechainlayer" 25 "github.com/google/osv-scalibr/artifact/image/layerscanning/testing/fakelayer" 26 "github.com/google/osv-scalibr/artifact/image/layerscanning/testing/fakelayerbuilder" 27 "github.com/google/osv-scalibr/extractor" 28 "github.com/google/osv-scalibr/extractor/filesystem" 29 "github.com/google/osv-scalibr/inventory" 30 "github.com/google/osv-scalibr/purl" 31 "github.com/google/osv-scalibr/stats" 32 "github.com/opencontainers/go-digest" 33 ) 34 35 func TestPopulateLayerDetails(t *testing.T) { 36 lm := func(i int) *extractor.LayerMetadata { 37 return &extractor.LayerMetadata{ 38 Index: i, 39 DiffID: digest.Digest(fmt.Sprintf("sha256:diff-id-%d", i)), 40 ChainID: digest.Digest(fmt.Sprintf("sha256:chain-id-%d", i)), 41 Command: fmt.Sprintf("command-%d", i), 42 } 43 } 44 45 const ( 46 // Fake file names used in tests. 47 fooFile = "foo.txt" 48 barFile = "bar.txt" 49 bazFile = "baz.txt" 50 51 // Fake package names used in tests. 52 fooPackage = "foo" 53 foo2Package = "foo2" 54 barPackage = "bar" 55 bazPackage = "baz" 56 ) 57 58 fakeLayerExtractor := fakelayerbuilder.FakeTestLayersExtractor{} 59 fakeChainLayers := fakelayerbuilder.BuildFakeChainLayersFromPath(t, t.TempDir(), "testdata/populatelayers.yml") 60 61 tests := []struct { 62 name string 63 pkgs []*extractor.Package 64 extractor filesystem.Extractor 65 chainLayers []image.ChainLayer 66 wantPackages []*extractor.Package 67 }{ 68 { 69 name: "empty package", 70 pkgs: []*extractor.Package{}, 71 chainLayers: []image.ChainLayer{}, 72 wantPackages: []*extractor.Package{}, 73 }, 74 { 75 name: "empty_chain_layers", 76 pkgs: []*extractor.Package{ 77 { 78 Name: fooPackage, 79 PURLType: purl.TypeGeneric, 80 Locations: []string{fooFile}, 81 Plugins: []string{fakeLayerExtractor.Name()}, 82 }, 83 }, 84 chainLayers: []image.ChainLayer{}, 85 wantPackages: []*extractor.Package{ 86 { 87 Name: fooPackage, 88 PURLType: purl.TypeGeneric, 89 Locations: []string{fooFile}, 90 Plugins: []string{fakeLayerExtractor.Name()}, 91 }, 92 }, 93 }, 94 { 95 name: "package_with_nil_extractor", 96 pkgs: []*extractor.Package{ 97 { 98 Name: fooPackage, 99 PURLType: purl.TypeGeneric, 100 Locations: []string{fooFile}, 101 }, 102 }, 103 chainLayers: []image.ChainLayer{ 104 fakeChainLayers[0], 105 }, 106 wantPackages: []*extractor.Package{ 107 { 108 Name: fooPackage, 109 PURLType: purl.TypeGeneric, 110 Locations: []string{fooFile}, 111 }, 112 }, 113 }, 114 { 115 name: "package_in_single_chain_layer", 116 pkgs: []*extractor.Package{ 117 { 118 Name: fooPackage, 119 PURLType: purl.TypeGeneric, 120 Locations: []string{fooFile}, 121 Plugins: []string{fakeLayerExtractor.Name()}, 122 }, 123 { 124 Name: barPackage, 125 PURLType: purl.TypeGeneric, 126 Locations: []string{barFile}, 127 Plugins: []string{fakeLayerExtractor.Name()}, 128 }, 129 }, 130 extractor: fakeLayerExtractor, 131 chainLayers: []image.ChainLayer{ 132 fakeChainLayers[0], 133 }, 134 wantPackages: []*extractor.Package{ 135 { 136 Name: fooPackage, 137 PURLType: purl.TypeGeneric, 138 Locations: []string{fooFile}, 139 Plugins: []string{fakeLayerExtractor.Name()}, 140 LayerMetadata: lm(0), 141 }, 142 { 143 Name: barPackage, 144 PURLType: purl.TypeGeneric, 145 Locations: []string{barFile}, 146 Plugins: []string{fakeLayerExtractor.Name()}, 147 LayerMetadata: lm(0), 148 }, 149 }, 150 }, 151 { 152 name: "package_in_two_chain_layers_-_package_deleted_in_second_layer", 153 pkgs: []*extractor.Package{ 154 { 155 Name: "foo", 156 PURLType: purl.TypeGeneric, 157 Locations: []string{fooFile}, 158 Plugins: []string{fakeLayerExtractor.Name()}, 159 }, 160 }, 161 extractor: fakeLayerExtractor, 162 chainLayers: []image.ChainLayer{ 163 fakeChainLayers[0], 164 fakeChainLayers[1], 165 }, 166 wantPackages: []*extractor.Package{ 167 { 168 Name: fooPackage, 169 PURLType: purl.TypeGeneric, 170 Locations: []string{fooFile}, 171 Plugins: []string{fakeLayerExtractor.Name()}, 172 LayerMetadata: lm(0), 173 }, 174 }, 175 }, 176 { 177 name: "packages_in_multiple_chain_layers_-_package_added_in_third_layer", 178 pkgs: []*extractor.Package{ 179 { 180 Name: "foo", 181 PURLType: purl.TypeGeneric, 182 Locations: []string{fooFile}, 183 Plugins: []string{fakeLayerExtractor.Name()}, 184 }, 185 { 186 Name: "baz", 187 PURLType: purl.TypeGeneric, 188 Locations: []string{bazFile}, 189 Plugins: []string{fakeLayerExtractor.Name()}, 190 }, 191 }, 192 extractor: fakeLayerExtractor, 193 chainLayers: []image.ChainLayer{ 194 fakeChainLayers[0], 195 fakeChainLayers[1], 196 fakeChainLayers[2], 197 }, 198 wantPackages: []*extractor.Package{ 199 { 200 Name: fooPackage, 201 PURLType: purl.TypeGeneric, 202 Locations: []string{fooFile}, 203 Plugins: []string{fakeLayerExtractor.Name()}, 204 LayerMetadata: lm(0), 205 }, 206 { 207 Name: bazPackage, 208 PURLType: purl.TypeGeneric, 209 Locations: []string{bazFile}, 210 Plugins: []string{fakeLayerExtractor.Name()}, 211 LayerMetadata: lm(2), 212 }, 213 }, 214 }, 215 { 216 name: "packages_in_multiple_chain_layers_-_bar_package_added_back_in_last_layer", 217 pkgs: []*extractor.Package{ 218 { 219 Name: fooPackage, 220 PURLType: purl.TypeGeneric, 221 Locations: []string{fooFile}, 222 Plugins: []string{fakeLayerExtractor.Name()}, 223 }, 224 { 225 Name: barPackage, 226 PURLType: purl.TypeGeneric, 227 Locations: []string{barFile}, 228 Plugins: []string{fakeLayerExtractor.Name()}, 229 }, 230 { 231 Name: bazPackage, 232 PURLType: purl.TypeGeneric, 233 Locations: []string{bazFile}, 234 Plugins: []string{fakeLayerExtractor.Name()}, 235 }, 236 }, 237 extractor: fakeLayerExtractor, 238 chainLayers: []image.ChainLayer{ 239 fakeChainLayers[0], 240 fakeChainLayers[1], 241 fakeChainLayers[2], 242 fakeChainLayers[3], 243 }, 244 wantPackages: []*extractor.Package{ 245 { 246 Name: fooPackage, 247 PURLType: purl.TypeGeneric, 248 Locations: []string{fooFile}, 249 Plugins: []string{fakeLayerExtractor.Name()}, 250 LayerMetadata: lm(0), 251 }, 252 { 253 Name: barPackage, 254 PURLType: purl.TypeGeneric, 255 Locations: []string{barFile}, 256 Plugins: []string{fakeLayerExtractor.Name()}, 257 LayerMetadata: lm(3), 258 }, 259 { 260 Name: bazPackage, 261 PURLType: purl.TypeGeneric, 262 Locations: []string{bazFile}, 263 Plugins: []string{fakeLayerExtractor.Name()}, 264 LayerMetadata: lm(2), 265 }, 266 }, 267 }, 268 { 269 name: "package_in_multiple_chain_layers_-_foo_package_overwritten_in_last_layer", 270 pkgs: []*extractor.Package{ 271 { 272 Name: fooPackage, 273 PURLType: purl.TypeGeneric, 274 Locations: []string{fooFile}, 275 Plugins: []string{fakeLayerExtractor.Name()}, 276 }, 277 { 278 Name: foo2Package, 279 PURLType: purl.TypeGeneric, 280 Locations: []string{fooFile}, 281 Plugins: []string{fakeLayerExtractor.Name()}, 282 }, 283 { 284 Name: barPackage, 285 PURLType: purl.TypeGeneric, 286 Locations: []string{barFile}, 287 Plugins: []string{fakeLayerExtractor.Name()}, 288 }, 289 { 290 Name: bazPackage, 291 PURLType: purl.TypeGeneric, 292 Locations: []string{bazFile}, 293 Plugins: []string{fakeLayerExtractor.Name()}, 294 }, 295 }, 296 extractor: fakeLayerExtractor, 297 chainLayers: []image.ChainLayer{ 298 fakeChainLayers[0], 299 fakeChainLayers[1], 300 fakeChainLayers[2], 301 fakeChainLayers[3], 302 fakeChainLayers[4], 303 }, 304 wantPackages: []*extractor.Package{ 305 { 306 Name: fooPackage, 307 PURLType: purl.TypeGeneric, 308 Locations: []string{fooFile}, 309 Plugins: []string{fakeLayerExtractor.Name()}, 310 LayerMetadata: lm(0), 311 }, 312 { 313 Name: foo2Package, 314 PURLType: purl.TypeGeneric, 315 Locations: []string{fooFile}, 316 Plugins: []string{fakeLayerExtractor.Name()}, 317 LayerMetadata: lm(4), 318 }, 319 { 320 Name: barPackage, 321 PURLType: purl.TypeGeneric, 322 Locations: []string{barFile}, 323 Plugins: []string{fakeLayerExtractor.Name()}, 324 LayerMetadata: lm(3), 325 }, 326 { 327 Name: bazPackage, 328 PURLType: purl.TypeGeneric, 329 Locations: []string{bazFile}, 330 Plugins: []string{fakeLayerExtractor.Name()}, 331 LayerMetadata: lm(2), 332 }, 333 }, 334 }, 335 { 336 name: "chain_layer_with_invalid_diffID", 337 pkgs: []*extractor.Package{ 338 { 339 Name: fooPackage, 340 PURLType: purl.TypeGeneric, 341 Locations: []string{fooFile}, 342 Plugins: []string{fakeLayerExtractor.Name()}, 343 }, 344 }, 345 chainLayers: []image.ChainLayer{ 346 func() image.ChainLayer { 347 tmp := t.TempDir() 348 layer, err := fakelayer.New(tmp, digest.Digest(""), "command-0", map[string]string{fooFile: fooPackage}, false) 349 if err != nil { 350 t.Fatalf("failed creating fake layer: %v", err) 351 } 352 cfg := &fakechainlayer.Config{ 353 TestDir: tmp, 354 Index: 0, 355 DiffID: digest.Digest(""), 356 ChainID: digest.Digest("chain-id-invalid"), 357 Command: "command-0", 358 Layer: layer, 359 Files: map[string]string{fooFile: fooPackage}, 360 FilesAlreadyExist: false, 361 } 362 cl, err := fakechainlayer.New(cfg) 363 if err != nil { 364 t.Fatalf("failed creating fake chain layer: %v", err) 365 } 366 return cl 367 }(), 368 }, 369 wantPackages: []*extractor.Package{ 370 { 371 Name: fooPackage, 372 PURLType: purl.TypeGeneric, 373 Locations: []string{fooFile}, 374 Plugins: []string{fakeLayerExtractor.Name()}, 375 LayerMetadata: &extractor.LayerMetadata{ 376 Index: 0, 377 DiffID: digest.Digest(""), 378 ChainID: digest.Digest("chain-id-invalid"), 379 Command: "command-0", 380 }, 381 }, 382 }, 383 }, 384 } 385 for _, tc := range tests { 386 t.Run(tc.name, func(t *testing.T) { 387 config := &filesystem.Config{ 388 Stats: stats.NoopCollector{}, 389 PathsToExtract: []string{"Installed"}, 390 Extractors: []filesystem.Extractor{tc.extractor}, 391 } 392 inv := inventory.Inventory{Packages: tc.pkgs} 393 PopulateLayerDetails(t.Context(), &inv, tc.chainLayers, []filesystem.Extractor{fakeLayerExtractor}, config) 394 if diff := cmp.Diff(tc.wantPackages, inv.Packages, cmpopts.IgnoreFields(extractor.LayerMetadata{}, "ParentContainer")); diff != "" { 395 t.Errorf("PopulateLayerDetails(ctx, %v, %v, config) returned an unexpected diff (-want +got): %v", tc.pkgs, tc.chainLayers, diff) 396 } 397 }) 398 } 399 }