github.com/google/osv-scalibr@v0.4.1/annotator/noexecutable/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/noexecutable/dpkg"
    27  	"github.com/google/osv-scalibr/common/linux/dpkg/testing/dpkgutil"
    28  	"github.com/google/osv-scalibr/extractor"
    29  	dpkgmetadata "github.com/google/osv-scalibr/extractor/filesystem/os/dpkg/metadata"
    30  	scalibrfs "github.com/google/osv-scalibr/fs"
    31  	"github.com/google/osv-scalibr/inventory"
    32  	"github.com/google/osv-scalibr/inventory/vex"
    33  	"google.golang.org/protobuf/proto"
    34  )
    35  
    36  func TestAnnotate(t *testing.T) {
    37  	if runtime.GOOS != "linux" {
    38  		t.Skipf("Test skipped, OS unsupported: %v", runtime.GOOS)
    39  	}
    40  
    41  	cancelledContext, cancel := context.WithCancel(t.Context())
    42  	cancel()
    43  
    44  	copier := cpy.New(
    45  		cpy.Func(proto.Clone),
    46  		cpy.IgnoreAllUnexported(),
    47  	)
    48  
    49  	tests := []struct {
    50  		desc     string
    51  		packages []*extractor.Package
    52  		// the .list file content has been modified adding a trailing "/" at
    53  		// the end of each folder to simplify the setupDPKGInfo logic
    54  		infoContents map[string]string
    55  		//nolint:containedctx
    56  		ctx          context.Context
    57  		wantErr      error
    58  		wantPackages []*extractor.Package
    59  	}{
    60  		{
    61  			desc:         "missing_info_dir",
    62  			infoContents: nil,
    63  			wantErr:      cmpopts.AnyError,
    64  		},
    65  		{
    66  			desc:         "empty_info_dir",
    67  			infoContents: map[string]string{},
    68  			packages: []*extractor.Package{
    69  				{Name: "curl", Metadata: dpkgmetadata.Metadata{}},
    70  			},
    71  			wantPackages: []*extractor.Package{
    72  				{Name: "curl", Metadata: dpkgmetadata.Metadata{}},
    73  			},
    74  			wantErr: cmpopts.AnyError,
    75  		},
    76  		{
    77  			desc: "ctx_cancelled",
    78  			ctx:  cancelledContext,
    79  			infoContents: map[string]string{
    80  				"curl.list": "/usr/\n/usr/bin/\n/usr/bin/curl\n/usr/share/\n/usr/share/doc/\n/usr/share/doc/curl/\n/usr/share/doc/curl/README.Debian\n/usr/share/doc/curl/changelog.Debian.gz",
    81  			},
    82  			packages: []*extractor.Package{
    83  				{Name: "curl", Metadata: dpkgmetadata.Metadata{}},
    84  			},
    85  			wantPackages: []*extractor.Package{
    86  				{Name: "curl", Metadata: dpkgmetadata.Metadata{}},
    87  			},
    88  			wantErr: cmpopts.AnyError,
    89  		},
    90  		{
    91  			desc: "contains_binary",
    92  			infoContents: map[string]string{
    93  				"curl.list": "/usr/\n/usr/bin/\n/usr/bin/curl\n/usr/share/\n/usr/share/doc/\n/usr/share/doc/curl/\n/usr/share/doc/curl/README.Debian\n/usr/share/doc/curl/changelog.Debian.gz",
    94  			},
    95  			packages: []*extractor.Package{
    96  				{Name: "curl", Metadata: dpkgmetadata.Metadata{}},
    97  			},
    98  			wantPackages: []*extractor.Package{
    99  				{Name: "curl", Metadata: dpkgmetadata.Metadata{}},
   100  			},
   101  		},
   102  		{
   103  			desc: "does_not_contain_binary",
   104  			infoContents: map[string]string{
   105  				"curl.list": "/usr/\n/usr/share/\n/usr/share/doc/\n/usr/share/doc/curl/\n/usr/share/doc/curl/README.Debian\n/usr/share/doc/curl/changelog.Debian.gz",
   106  			},
   107  			packages: []*extractor.Package{
   108  				{Name: "curl", Metadata: dpkgmetadata.Metadata{}},
   109  			},
   110  			wantPackages: []*extractor.Package{
   111  				{
   112  					Name:     "curl",
   113  					Metadata: dpkgmetadata.Metadata{},
   114  					ExploitabilitySignals: []*vex.PackageExploitabilitySignal{
   115  						{
   116  							Plugin:          dpkg.Name,
   117  							Justification:   vex.ComponentNotPresent,
   118  							MatchesAllVulns: true,
   119  						},
   120  					}},
   121  			},
   122  		},
   123  		{
   124  			desc: "arch_specific_path",
   125  			infoContents: map[string]string{
   126  				"curl:arm64.list": "/usr/\n/usr/share/\n/usr/share/doc/\n/usr/share/doc/curl/\n/usr/share/doc/curl/README.Debian\n/usr/share/doc/curl/changelog.Debian.gz",
   127  			},
   128  			packages: []*extractor.Package{
   129  				{Name: "curl", Metadata: dpkgmetadata.Metadata{Architecture: "arm64"}},
   130  			},
   131  			wantPackages: []*extractor.Package{
   132  				{
   133  					Name:     "curl",
   134  					Metadata: dpkgmetadata.Metadata{Architecture: "arm64"},
   135  					ExploitabilitySignals: []*vex.PackageExploitabilitySignal{
   136  						{
   137  							Plugin:          dpkg.Name,
   138  							Justification:   vex.ComponentNotPresent,
   139  							MatchesAllVulns: true,
   140  						},
   141  					}},
   142  			},
   143  		},
   144  	}
   145  
   146  	for _, tt := range tests {
   147  		t.Run(tt.desc, func(t *testing.T) {
   148  			root := ""
   149  			if tt.infoContents != nil {
   150  				root = dpkgutil.SetupDPKGInfo(t, tt.infoContents, true)
   151  			}
   152  			if tt.ctx == nil {
   153  				tt.ctx = t.Context()
   154  			}
   155  			input := &annotator.ScanInput{
   156  				ScanRoot: scalibrfs.RealFSScanRoot(root),
   157  			}
   158  			// Deep copy the packages to avoid modifying the original inventory that is used in other tests.
   159  			packages := copier.Copy(tt.packages).([]*extractor.Package)
   160  			inv := &inventory.Inventory{Packages: packages}
   161  
   162  			err := dpkg.New().Annotate(tt.ctx, input, inv)
   163  			if !cmp.Equal(tt.wantErr, err, cmpopts.EquateErrors()) {
   164  				t.Fatalf("Annotate(%v) error: %v, want %v", tt.packages, err, tt.wantErr)
   165  			}
   166  
   167  			want := &inventory.Inventory{Packages: tt.wantPackages}
   168  			if diff := cmp.Diff(want, inv); diff != "" {
   169  				t.Errorf("Annotate(%v): unexpected diff (-want +got): %v", tt.packages, diff)
   170  			}
   171  		})
   172  	}
   173  }