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 }