github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/rust/cargoauditable/cargoauditable_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 cargoauditable_test 16 17 import ( 18 "errors" 19 "io/fs" 20 "os" 21 "path/filepath" 22 "testing" 23 24 "github.com/google/go-cmp/cmp" 25 "github.com/google/go-cmp/cmp/cmpopts" 26 "github.com/google/osv-scalibr/extractor" 27 "github.com/google/osv-scalibr/extractor/filesystem" 28 "github.com/google/osv-scalibr/extractor/filesystem/language/rust/cargoauditable" 29 "github.com/google/osv-scalibr/extractor/filesystem/simplefileapi" 30 scalibrfs "github.com/google/osv-scalibr/fs" 31 "github.com/google/osv-scalibr/inventory" 32 "github.com/google/osv-scalibr/purl" 33 "github.com/google/osv-scalibr/stats" 34 "github.com/google/osv-scalibr/testing/fakefs" 35 "github.com/google/osv-scalibr/testing/testcollector" 36 ) 37 38 func TestFileRequired(t *testing.T) { 39 tests := []struct { 40 name string 41 path string 42 mode fs.FileMode 43 fileSizeBytes int64 44 maxFileSizeBytes int64 45 wantRequired bool 46 wantResultMetric stats.FileRequiredResult 47 }{ 48 { 49 name: "executable required if size less than maxFileSizeBytes", 50 path: "some/path/a", 51 mode: 0766, 52 fileSizeBytes: 100, 53 maxFileSizeBytes: 1000, 54 wantRequired: true, 55 wantResultMetric: stats.FileRequiredResultOK, 56 }, 57 { 58 name: "executable required if size equal to maxFileSizeBytes", 59 path: "some/path/a", 60 mode: 0766, 61 fileSizeBytes: 1000, 62 maxFileSizeBytes: 1000, 63 wantRequired: true, 64 wantResultMetric: stats.FileRequiredResultOK, 65 }, 66 { 67 name: "executable not required if size greater than maxFileSizeBytes", 68 path: "some/path/a", 69 mode: 0766, 70 fileSizeBytes: 1000, 71 maxFileSizeBytes: 100, 72 wantRequired: false, 73 wantResultMetric: stats.FileRequiredResultSizeLimitExceeded, 74 }, 75 { 76 name: "executable required if maxFileSizeBytes explicitly set to 0", 77 path: "some/path/a", 78 mode: 0766, 79 fileSizeBytes: 1000, 80 maxFileSizeBytes: 0, 81 wantRequired: true, 82 wantResultMetric: stats.FileRequiredResultOK, 83 }, 84 } 85 86 for _, tt := range tests { 87 t.Run(tt.name, func(t *testing.T) { 88 collector := testcollector.New() 89 e := cargoauditable.New(cargoauditable.Config{ 90 Stats: collector, 91 MaxFileSizeBytes: tt.maxFileSizeBytes, 92 }) 93 94 // Set a default file size if not specified. 95 fileSizeBytes := tt.fileSizeBytes 96 if fileSizeBytes == 0 { 97 fileSizeBytes = 1000 98 } 99 100 if got := e.FileRequired(simplefileapi.New(tt.path, fakefs.FakeFileInfo{ 101 FileName: filepath.Base(tt.path), 102 FileMode: tt.mode, 103 FileSize: fileSizeBytes, 104 })); got != tt.wantRequired { 105 t.Fatalf("FileRequired(%s): got %v, want %v", tt.path, got, tt.wantRequired) 106 } 107 108 gotResultMetric := collector.FileRequiredResult(tt.path) 109 if gotResultMetric != tt.wantResultMetric { 110 t.Errorf("FileRequired(%s) recorded result metric %v, want result metric %v", tt.path, gotResultMetric, tt.wantResultMetric) 111 } 112 }) 113 } 114 } 115 116 func TestExtract(t *testing.T) { 117 tests := []struct { 118 name string 119 path string 120 wantPackages []*extractor.Package 121 wantErr error 122 wantResultMetric stats.FileExtractedResult 123 }{ 124 { 125 name: "uses_serde_json", 126 path: "testdata/uses_serde_json/uses_serde_json", 127 wantPackages: []*extractor.Package{ 128 { 129 Name: "itoa", 130 Version: "1.0.14", 131 PURLType: purl.TypeCargo, 132 Locations: []string{"testdata/uses_serde_json/uses_serde_json"}, 133 }, 134 { 135 Name: "memchr", 136 Version: "2.7.4", 137 PURLType: purl.TypeCargo, 138 Locations: []string{"testdata/uses_serde_json/uses_serde_json"}, 139 }, 140 { 141 Name: "proc-macro2", 142 Version: "1.0.92", 143 PURLType: purl.TypeCargo, 144 Locations: []string{"testdata/uses_serde_json/uses_serde_json"}, 145 }, 146 { 147 Name: "quote", 148 Version: "1.0.38", 149 PURLType: purl.TypeCargo, 150 Locations: []string{"testdata/uses_serde_json/uses_serde_json"}, 151 }, 152 { 153 Name: "ryu", 154 Version: "1.0.18", 155 PURLType: purl.TypeCargo, 156 Locations: []string{"testdata/uses_serde_json/uses_serde_json"}, 157 }, 158 { 159 Name: "serde", 160 Version: "1.0.217", 161 PURLType: purl.TypeCargo, 162 Locations: []string{"testdata/uses_serde_json/uses_serde_json"}, 163 }, 164 { 165 Name: "serde_derive", 166 Version: "1.0.217", 167 PURLType: purl.TypeCargo, 168 Locations: []string{"testdata/uses_serde_json/uses_serde_json"}, 169 }, 170 { 171 Name: "serde_json", 172 Version: "1.0.135", 173 PURLType: purl.TypeCargo, 174 Locations: []string{"testdata/uses_serde_json/uses_serde_json"}, 175 }, 176 { 177 Name: "syn", 178 Version: "2.0.95", 179 PURLType: purl.TypeCargo, 180 Locations: []string{"testdata/uses_serde_json/uses_serde_json"}, 181 }, 182 { 183 Name: "unicode-ident", 184 Version: "1.0.14", 185 PURLType: purl.TypeCargo, 186 Locations: []string{"testdata/uses_serde_json/uses_serde_json"}, 187 }, 188 { 189 Name: "uses_json", 190 Version: "0.1.0", 191 PURLType: purl.TypeCargo, 192 Locations: []string{"testdata/uses_serde_json/uses_serde_json"}, 193 }, 194 }, 195 }, 196 { 197 name: "no_deps", 198 path: "testdata/no_deps/no_deps", 199 wantPackages: []*extractor.Package{ 200 { 201 Name: "no_deps", 202 Version: "0.1.0", 203 PURLType: purl.TypeCargo, 204 Locations: []string{"testdata/no_deps/no_deps"}, 205 }, 206 }, 207 }, 208 { 209 name: "not_binary", 210 path: "testdata/not_binary/not_binary", 211 wantPackages: nil, 212 wantResultMetric: stats.FileExtractedResultErrorUnknown, 213 }, 214 } 215 216 for _, tt := range tests { 217 t.Run(tt.name, func(t *testing.T) { 218 f, err := os.Open(tt.path) 219 if err != nil { 220 t.Fatalf("os.Open(%s) unexpected error: %v", tt.path, err) 221 } 222 defer f.Close() 223 224 info, err := f.Stat() 225 if err != nil { 226 t.Fatalf("f.Stat() for %q unexpected error: %v", tt.path, err) 227 } 228 229 collector := testcollector.New() 230 231 input := &filesystem.ScanInput{FS: scalibrfs.DirFS("."), Path: tt.path, Info: info, Reader: f} 232 233 e := cargoauditable.New(cargoauditable.Config{Stats: collector}) 234 got, err := e.Extract(t.Context(), input) 235 if !errors.Is(err, tt.wantErr) { 236 t.Fatalf("Extract(%s) got error: %v, want error: %v", tt.path, err, tt.wantErr) 237 } 238 sort := func(a, b *extractor.Package) bool { return a.Name < b.Name } 239 wantInv := inventory.Inventory{Packages: tt.wantPackages} 240 if diff := cmp.Diff(wantInv, got, cmpopts.SortSlices(sort)); diff != "" { 241 t.Fatalf("Extract(%s) (-want +got):\n%s", tt.path, diff) 242 } 243 244 wantResultMetric := tt.wantResultMetric 245 if wantResultMetric == "" && tt.wantErr == nil { 246 wantResultMetric = stats.FileExtractedResultSuccess 247 } 248 gotResultMetric := collector.FileExtractedResult(tt.path) 249 if gotResultMetric != wantResultMetric { 250 t.Errorf("Extract(%s) recorded result metric %v, want result metric %v", tt.path, gotResultMetric, wantResultMetric) 251 } 252 253 gotFileSizeMetric := collector.FileExtractedFileSize(tt.path) 254 if gotFileSizeMetric != info.Size() { 255 t.Errorf("Extract(%s) recorded file size %v, want file size %v", tt.path, gotFileSizeMetric, info.Size()) 256 } 257 }) 258 } 259 }