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  }