github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/python/pylock/pylock_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 pylock_test 16 17 import ( 18 "testing" 19 20 "github.com/google/go-cmp/cmp" 21 "github.com/google/go-cmp/cmp/cmpopts" 22 "github.com/google/osv-scalibr/extractor" 23 "github.com/google/osv-scalibr/extractor/filesystem/language/python/pylock" 24 "github.com/google/osv-scalibr/extractor/filesystem/simplefileapi" 25 "github.com/google/osv-scalibr/inventory" 26 "github.com/google/osv-scalibr/purl" 27 "github.com/google/osv-scalibr/testing/extracttest" 28 ) 29 30 func TestExtractor_FileRequired(t *testing.T) { 31 tests := []struct { 32 name string 33 inputPath string 34 want bool 35 }{ 36 { 37 name: "", 38 inputPath: "", 39 want: false, 40 }, 41 { 42 name: "", 43 inputPath: "mypylock.toml", 44 want: false, 45 }, 46 { 47 name: "", 48 inputPath: "pylock.toml", 49 want: true, 50 }, 51 { 52 name: "", 53 inputPath: "pylock.spam.toml", 54 want: true, 55 }, 56 { 57 name: "", 58 inputPath: "pylock.beans.toml", 59 want: true, 60 }, 61 { 62 name: "", 63 inputPath: "PYLOCK.spam.toml", 64 want: false, 65 }, 66 { 67 name: "", 68 inputPath: "path/to/my/pylock.toml", 69 want: true, 70 }, 71 { 72 name: "", 73 inputPath: "path/to/my/pylock.spam.toml", 74 want: true, 75 }, 76 { 77 name: "", 78 inputPath: "path/to/my/pylock.toml/file", 79 want: false, 80 }, 81 { 82 name: "", 83 inputPath: "path/to/my/pylock.toml.file", 84 want: false, 85 }, 86 { 87 name: "", 88 inputPath: "path.to.my.pylock.toml", 89 want: false, 90 }, 91 } 92 for _, tt := range tests { 93 t.Run(tt.name, func(t *testing.T) { 94 e := pylock.Extractor{} 95 got := e.FileRequired(simplefileapi.New(tt.inputPath, nil)) 96 if got != tt.want { 97 t.Errorf("FileRequired(%q, FileInfo) got = %v, want %v", tt.inputPath, got, tt.want) 98 } 99 }) 100 } 101 } 102 103 func TestExtractor_Extract(t *testing.T) { 104 tests := []extracttest.TestTableEntry{ 105 { 106 Name: "invalid toml", 107 InputConfig: extracttest.ScanInputMockConfig{ 108 Path: "testdata/not-toml.txt", 109 }, 110 WantErr: extracttest.ContainsErrStr{Str: "could not extract"}, 111 WantPackages: nil, 112 }, 113 { 114 Name: "example", 115 InputConfig: extracttest.ScanInputMockConfig{ 116 Path: "testdata/example.toml", 117 }, 118 WantPackages: []*extractor.Package{ 119 { 120 Name: "attrs", 121 Version: "25.1.0", 122 PURLType: purl.TypePyPi, 123 Locations: []string{"testdata/example.toml"}, 124 }, 125 { 126 Name: "cattrs", 127 Version: "24.1.2", 128 PURLType: purl.TypePyPi, 129 Locations: []string{"testdata/example.toml"}, 130 }, 131 { 132 Name: "numpy", 133 Version: "2.2.3", 134 PURLType: purl.TypePyPi, 135 Locations: []string{"testdata/example.toml"}, 136 }, 137 }, 138 }, 139 { 140 Name: "package_with_commits", 141 InputConfig: extracttest.ScanInputMockConfig{ 142 Path: "testdata/commits.toml", 143 }, 144 WantPackages: []*extractor.Package{ 145 { 146 Name: "click", 147 Version: "8.2.1", 148 PURLType: purl.TypePyPi, 149 Locations: []string{"testdata/commits.toml"}, 150 }, 151 { 152 Name: "mleroc", 153 Version: "0.1.0", 154 PURLType: purl.TypePyPi, 155 Locations: []string{"testdata/commits.toml"}, 156 SourceCode: &extractor.SourceCodeIdentifier{ 157 Commit: "735093f03c4d8be70bfaaae44074ac92d7419b6d", 158 }, 159 }, 160 { 161 Name: "packaging", 162 Version: "24.2", 163 PURLType: purl.TypePyPi, 164 Locations: []string{"testdata/commits.toml"}, 165 }, 166 { 167 Name: "pathspec", 168 Version: "0.12.1", 169 PURLType: purl.TypePyPi, 170 Locations: []string{"testdata/commits.toml"}, 171 }, 172 { 173 Name: "python-dateutil", 174 Version: "2.9.0.post0", 175 PURLType: purl.TypePyPi, 176 Locations: []string{"testdata/commits.toml"}, 177 }, 178 { 179 Name: "scikit-learn", 180 Version: "1.6.1", 181 PURLType: purl.TypePyPi, 182 Locations: []string{"testdata/commits.toml"}, 183 }, 184 { 185 Name: "tqdm", 186 Version: "4.67.1", 187 PURLType: purl.TypePyPi, 188 Locations: []string{"testdata/commits.toml"}, 189 }, 190 }, 191 }, 192 { 193 Name: "created_by_pip_with_just_self", 194 InputConfig: extracttest.ScanInputMockConfig{ 195 Path: "testdata/pip-just-self.toml", 196 }, 197 WantPackages: []*extractor.Package{}, 198 }, 199 { 200 Name: "created_by_pip", 201 InputConfig: extracttest.ScanInputMockConfig{ 202 Path: "testdata/pip-full.toml", 203 }, 204 WantPackages: []*extractor.Package{ 205 { 206 Name: "annotated-types", 207 Version: "0.7.0", 208 PURLType: purl.TypePyPi, 209 Locations: []string{"testdata/pip-full.toml"}, 210 }, 211 { 212 Name: "packaging", 213 Version: "25.0", 214 PURLType: purl.TypePyPi, 215 Locations: []string{"testdata/pip-full.toml"}, 216 }, 217 { 218 Name: "pyproject-toml", 219 Version: "0.1.0", 220 PURLType: purl.TypePyPi, 221 Locations: []string{"testdata/pip-full.toml"}, 222 }, 223 { 224 Name: "setuptools", 225 Version: "80.9.0", 226 PURLType: purl.TypePyPi, 227 Locations: []string{"testdata/pip-full.toml"}, 228 }, 229 { 230 Name: "wheel", 231 Version: "0.45.1", 232 PURLType: purl.TypePyPi, 233 Locations: []string{"testdata/pip-full.toml"}, 234 }, 235 }, 236 }, 237 { 238 Name: "created_by_pdm", 239 InputConfig: extracttest.ScanInputMockConfig{ 240 Path: "testdata/pdm-full.toml", 241 }, 242 WantPackages: []*extractor.Package{ 243 { 244 Name: "certifi", 245 Version: "2025.1.31", 246 PURLType: purl.TypePyPi, 247 Locations: []string{"testdata/pdm-full.toml"}, 248 }, 249 { 250 Name: "chardet", 251 Version: "3.0.4", 252 PURLType: purl.TypePyPi, 253 Locations: []string{"testdata/pdm-full.toml"}, 254 }, 255 { 256 Name: "charset-normalizer", 257 Version: "2.0.12", 258 PURLType: purl.TypePyPi, 259 Locations: []string{"testdata/pdm-full.toml"}, 260 }, 261 { 262 Name: "colorama", 263 Version: "0.3.9", 264 PURLType: purl.TypePyPi, 265 Locations: []string{"testdata/pdm-full.toml"}, 266 }, 267 { 268 Name: "idna", 269 Version: "2.7", 270 PURLType: purl.TypePyPi, 271 Locations: []string{"testdata/pdm-full.toml"}, 272 }, 273 { 274 Name: "py", 275 Version: "1.4.34", 276 PURLType: purl.TypePyPi, 277 Locations: []string{"testdata/pdm-full.toml"}, 278 }, 279 { 280 Name: "pytest", 281 Version: "3.2.5", 282 PURLType: purl.TypePyPi, 283 Locations: []string{"testdata/pdm-full.toml"}, 284 }, 285 { 286 Name: "requests", 287 Version: "2.27.1", 288 PURLType: purl.TypePyPi, 289 Locations: []string{"testdata/pdm-full.toml"}, 290 }, 291 { 292 Name: "setuptools", 293 Version: "39.2.0", 294 PURLType: purl.TypePyPi, 295 Locations: []string{"testdata/pdm-full.toml"}, 296 }, 297 { 298 Name: "urllib3", 299 Version: "1.26.20", 300 PURLType: purl.TypePyPi, 301 Locations: []string{"testdata/pdm-full.toml"}, 302 }, 303 }, 304 }, 305 } 306 307 for _, tt := range tests { 308 t.Run(tt.Name, func(t *testing.T) { 309 extr := pylock.Extractor{} 310 311 scanInput := extracttest.GenerateScanInputMock(t, tt.InputConfig) 312 defer extracttest.CloseTestScanInput(t, scanInput) 313 314 got, err := extr.Extract(t.Context(), &scanInput) 315 316 if diff := cmp.Diff(tt.WantErr, err, cmpopts.EquateErrors()); diff != "" { 317 t.Errorf("%s.Extract(%q) error diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff) 318 return 319 } 320 321 wantInv := inventory.Inventory{Packages: tt.WantPackages} 322 if diff := cmp.Diff(wantInv, got, cmpopts.SortSlices(extracttest.PackageCmpLess)); diff != "" { 323 t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff) 324 } 325 }) 326 } 327 }