github.com/google/osv-scalibr@v0.4.1/annotator/osduplicate/dpkg/dpkg_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 dpkg_test
    16  
    17  import (
    18  	"context"
    19  	"runtime"
    20  	"testing"
    21  
    22  	"github.com/google/go-cmp/cmp"
    23  	"github.com/google/go-cmp/cmp/cmpopts"
    24  	"github.com/google/go-cpy/cpy"
    25  	"github.com/google/osv-scalibr/annotator"
    26  	"github.com/google/osv-scalibr/annotator/osduplicate/dpkg"
    27  	"github.com/google/osv-scalibr/common/linux/dpkg/testing/dpkgutil"
    28  	"github.com/google/osv-scalibr/extractor"
    29  	scalibrfs "github.com/google/osv-scalibr/fs"
    30  	"github.com/google/osv-scalibr/inventory"
    31  	"github.com/google/osv-scalibr/inventory/vex"
    32  	"google.golang.org/protobuf/proto"
    33  )
    34  
    35  func TestAnnotate(t *testing.T) {
    36  	if runtime.GOOS != "linux" {
    37  		t.Skipf("Test skipped, OS unsupported: %v", runtime.GOOS)
    38  	}
    39  
    40  	cancelledContext, cancel := context.WithCancel(t.Context())
    41  	cancel()
    42  
    43  	copier := cpy.New(
    44  		cpy.Func(proto.Clone),
    45  		cpy.IgnoreAllUnexported(),
    46  	)
    47  
    48  	tests := []struct {
    49  		desc         string
    50  		packages     []*extractor.Package
    51  		infoContents map[string]string
    52  		//nolint:containedctx
    53  		ctx          context.Context
    54  		wantErr      error
    55  		wantPackages []*extractor.Package
    56  	}{
    57  		{
    58  			desc:         "missing_info_dir",
    59  			infoContents: nil,
    60  		},
    61  		{
    62  			desc:         "empty_info_dir",
    63  			infoContents: map[string]string{},
    64  			packages: []*extractor.Package{
    65  				{
    66  					Name:      "file",
    67  					Locations: []string{"path/to/file"},
    68  				},
    69  			},
    70  			wantPackages: []*extractor.Package{
    71  				{
    72  					Name:      "file",
    73  					Locations: []string{"path/to/file"},
    74  				},
    75  			},
    76  		},
    77  		{
    78  			desc: "some_pkgs_found_in_info",
    79  			infoContents: map[string]string{
    80  				"some.list":       "/some/path\n/path/to/file-in-info\n/some/other/path",
    81  				"some.other.list": "/some/other/path",
    82  			},
    83  			packages: []*extractor.Package{
    84  				{
    85  					Name:      "file-in-info",
    86  					Locations: []string{"path/to/file-in-info"},
    87  				},
    88  				{
    89  					Name:      "file-not-in-info",
    90  					Locations: []string{"path/to/file-not-in-info"},
    91  				},
    92  			},
    93  			wantPackages: []*extractor.Package{
    94  				{
    95  					Name:      "file-in-info",
    96  					Locations: []string{"path/to/file-in-info"},
    97  					ExploitabilitySignals: []*vex.PackageExploitabilitySignal{&vex.PackageExploitabilitySignal{
    98  						Plugin:          dpkg.Name,
    99  						Justification:   vex.ComponentNotPresent,
   100  						MatchesAllVulns: true,
   101  					}},
   102  				},
   103  				{
   104  					Name:      "file-not-in-info",
   105  					Locations: []string{"path/to/file-not-in-info"},
   106  				},
   107  			},
   108  		},
   109  		{
   110  			desc: "pkg_found_in_file_with_wrong_extension",
   111  			infoContents: map[string]string{
   112  				"some.notlist": "/path/to/file",
   113  			},
   114  			packages: []*extractor.Package{
   115  				{
   116  					Name:      "file",
   117  					Locations: []string{"path/to/file"},
   118  				},
   119  			},
   120  			wantPackages: []*extractor.Package{
   121  				{
   122  					Name:      "file",
   123  					Locations: []string{"path/to/file"},
   124  					// No exploitability signals
   125  				},
   126  			},
   127  		},
   128  		{
   129  			desc: "pkg_has_no_location",
   130  			infoContents: map[string]string{
   131  				"some.list": "/path/to/file",
   132  			},
   133  			packages:     []*extractor.Package{{Name: "file"}},
   134  			wantPackages: []*extractor.Package{{Name: "file"}},
   135  		},
   136  		{
   137  			desc: "ctx_cancelled",
   138  			ctx:  cancelledContext,
   139  			infoContents: map[string]string{
   140  				"some.list": "/path/to/file",
   141  			},
   142  			packages: []*extractor.Package{
   143  				{
   144  					Name:      "file",
   145  					Locations: []string{"path/to/file"},
   146  				},
   147  			},
   148  			wantPackages: []*extractor.Package{
   149  				{
   150  					Name:      "file",
   151  					Locations: []string{"path/to/file"},
   152  					// No exploitability signals
   153  				},
   154  			},
   155  			wantErr: cmpopts.AnyError,
   156  		},
   157  	}
   158  
   159  	for _, tt := range tests {
   160  		t.Run(tt.desc, func(t *testing.T) {
   161  			var root string
   162  			if tt.infoContents != nil {
   163  				root = dpkgutil.SetupDPKGInfo(t, tt.infoContents, false)
   164  			} else {
   165  				root = t.TempDir()
   166  			}
   167  			if tt.ctx == nil {
   168  				tt.ctx = t.Context()
   169  			}
   170  			input := &annotator.ScanInput{
   171  				ScanRoot: scalibrfs.RealFSScanRoot(root),
   172  			}
   173  			// Deep copy the packages to avoid modifying the original inventory that is used in other tests.
   174  			packages := copier.Copy(tt.packages).([]*extractor.Package)
   175  			inv := &inventory.Inventory{Packages: packages}
   176  
   177  			err := dpkg.New().Annotate(tt.ctx, input, inv)
   178  			if !cmp.Equal(tt.wantErr, err, cmpopts.EquateErrors()) {
   179  				t.Fatalf("Annotate(%v) error: %v, want %v", tt.packages, tt.wantErr, err)
   180  			}
   181  
   182  			want := &inventory.Inventory{Packages: tt.wantPackages}
   183  			if diff := cmp.Diff(want, inv); diff != "" {
   184  				t.Errorf("Annotate(%v): unexpected diff (-want +got): %v", tt.packages, diff)
   185  			}
   186  		})
   187  	}
   188  }