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 }