github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/os/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 "fmt" 19 "io/fs" 20 golog "log" 21 "os" 22 "path/filepath" 23 "reflect" 24 "strings" 25 "testing" 26 27 "github.com/google/go-cmp/cmp" 28 "github.com/google/go-cmp/cmp/cmpopts" 29 "github.com/google/osv-scalibr/extractor" 30 "github.com/google/osv-scalibr/extractor/filesystem" 31 "github.com/google/osv-scalibr/extractor/filesystem/internal/units" 32 "github.com/google/osv-scalibr/extractor/filesystem/os/dpkg" 33 dpkgmeta "github.com/google/osv-scalibr/extractor/filesystem/os/dpkg/metadata" 34 "github.com/google/osv-scalibr/extractor/filesystem/simplefileapi" 35 scalibrfs "github.com/google/osv-scalibr/fs" 36 "github.com/google/osv-scalibr/inventory" 37 "github.com/google/osv-scalibr/inventory/vex" 38 scalibrlog "github.com/google/osv-scalibr/log" 39 "github.com/google/osv-scalibr/purl" 40 "github.com/google/osv-scalibr/stats" 41 "github.com/google/osv-scalibr/testing/fakefs" 42 "github.com/google/osv-scalibr/testing/testcollector" 43 ) 44 45 func TestNew(t *testing.T) { 46 tests := []struct { 47 name string 48 cfg dpkg.Config 49 wantCfg dpkg.Config 50 }{ 51 { 52 name: "default", 53 cfg: dpkg.DefaultConfig(), 54 wantCfg: dpkg.Config{ 55 MaxFileSizeBytes: 100 * units.MiB, 56 IncludeNotInstalled: false, 57 }, 58 }, 59 { 60 name: "custom", 61 cfg: dpkg.Config{ 62 MaxFileSizeBytes: 10, 63 IncludeNotInstalled: true, 64 }, 65 wantCfg: dpkg.Config{ 66 MaxFileSizeBytes: 10, 67 IncludeNotInstalled: true, 68 }, 69 }, 70 } 71 72 for _, tt := range tests { 73 t.Run(tt.name, func(t *testing.T) { 74 got := dpkg.New(tt.cfg) 75 if !reflect.DeepEqual(got.Config(), tt.wantCfg) { 76 t.Errorf("New(%+v).Config(): got %+v, want %+v", tt.cfg, got.Config(), tt.wantCfg) 77 } 78 }) 79 } 80 } 81 82 func TestFileRequired(t *testing.T) { 83 tests := []struct { 84 name string 85 path string 86 fileSizeBytes int64 87 maxFileSizeBytes int64 88 wantRequired bool 89 wantResultMetric stats.FileRequiredResult 90 }{ 91 { 92 name: "status file", 93 path: "var/lib/dpkg/status", 94 wantRequired: true, 95 wantResultMetric: stats.FileRequiredResultOK, 96 }, 97 { 98 name: "file in status.d", 99 path: "var/lib/dpkg/status.d/foo", 100 wantRequired: true, 101 wantResultMetric: stats.FileRequiredResultOK, 102 }, 103 { 104 name: "ignore md5sums file", 105 path: "var/lib/dpkg/status.d/foo.md5sums", 106 wantRequired: false, 107 }, 108 { 109 name: "status.d as a file", 110 path: "var/lib/dpkg/status.d", 111 wantRequired: false, 112 }, 113 { 114 name: "status file required if file size < max file size", 115 path: "var/lib/dpkg/status", 116 fileSizeBytes: 100 * units.KiB, 117 maxFileSizeBytes: 1000 * units.KiB, 118 wantRequired: true, 119 wantResultMetric: stats.FileRequiredResultOK, 120 }, 121 { 122 name: "status file required if file size == max file size", 123 path: "var/lib/dpkg/status", 124 fileSizeBytes: 1000 * units.KiB, 125 maxFileSizeBytes: 1000 * units.KiB, 126 wantRequired: true, 127 wantResultMetric: stats.FileRequiredResultOK, 128 }, 129 { 130 name: "status file not required if file size > max file size", 131 path: "var/lib/dpkg/status", 132 fileSizeBytes: 1000 * units.KiB, 133 maxFileSizeBytes: 100 * units.KiB, 134 wantRequired: false, 135 wantResultMetric: stats.FileRequiredResultSizeLimitExceeded, 136 }, 137 { 138 name: "status file required if max file size set to 0", 139 path: "var/lib/dpkg/status", 140 fileSizeBytes: 100 * units.KiB, 141 maxFileSizeBytes: 0, 142 wantRequired: true, 143 wantResultMetric: stats.FileRequiredResultOK, 144 }, 145 { 146 name: "status file", 147 path: "usr/lib/opkg/status", 148 wantRequired: true, 149 wantResultMetric: stats.FileRequiredResultOK, 150 }, 151 { 152 name: "status as a directory", 153 path: "usr/lib/opkg/status/foo", 154 wantRequired: false, 155 }, 156 } 157 158 for _, tt := range tests { 159 // Note the subtest here 160 t.Run(tt.name, func(t *testing.T) { 161 collector := testcollector.New() 162 var e filesystem.Extractor = dpkg.New(dpkg.Config{ 163 Stats: collector, 164 MaxFileSizeBytes: tt.maxFileSizeBytes, 165 }) 166 167 // Set a default file size if not specified. 168 fileSizeBytes := tt.fileSizeBytes 169 if fileSizeBytes == 0 { 170 fileSizeBytes = 1000 171 } 172 173 isRequired := e.FileRequired(simplefileapi.New(tt.path, fakefs.FakeFileInfo{ 174 FileName: filepath.Base(tt.path), 175 FileMode: fs.ModePerm, 176 FileSize: fileSizeBytes, 177 })) 178 if isRequired != tt.wantRequired { 179 t.Fatalf("FileRequired(%s): got %v, want %v", tt.path, isRequired, tt.wantRequired) 180 } 181 182 gotResultMetric := collector.FileRequiredResult(tt.path) 183 if tt.wantResultMetric != "" && gotResultMetric != tt.wantResultMetric { 184 t.Errorf("FileRequired(%s) recorded result metric %v, want result metric %v", tt.path, gotResultMetric, tt.wantResultMetric) 185 } 186 }) 187 } 188 } 189 190 const DebianBookworm = `PRETTY_NAME="Debian GNU/Linux 12 (bookworm)" 191 NAME="Debian GNU/Linux" 192 VERSION_ID="12" 193 VERSION="12 (bookworm)" 194 VERSION_CODENAME=bookworm 195 ID=debian` 196 197 const OpkgRelease = `NAME="OpenWrt" 198 VERSION="21.02.1" 199 ID=openwrt 200 VERSION_ID="21.02.1" 201 VERSION_CODENAME="openwrt-21.02.1" 202 PRETTY_NAME="OpenWrt 21.02.1" 203 BUILD_ID="r16279-5cc53c7f44"` 204 205 func TestExtract(t *testing.T) { 206 tests := []struct { 207 name string 208 path string 209 osrelease string 210 cfg dpkg.Config 211 isOPKG bool 212 wantPackages []*extractor.Package 213 wantErr error 214 wantResultMetric stats.FileExtractedResult 215 wantLogWarn int 216 wantLogErr int 217 }{ 218 { 219 name: "valid status file", 220 path: "testdata/dpkg/valid", 221 osrelease: DebianBookworm, 222 wantPackages: []*extractor.Package{ 223 { 224 Name: "accountsservice", 225 Version: "22.08.8-6", 226 PURLType: purl.TypeDebian, 227 Metadata: &dpkgmeta.Metadata{ 228 PackageName: "accountsservice", 229 PackageVersion: "22.08.8-6", 230 Status: "install ok installed", 231 OSID: "debian", 232 OSVersionCodename: "bookworm", 233 OSVersionID: "12", 234 Maintainer: "Debian freedesktop.org maintainers <pkg-freedesktop-maintainers@lists.alioth.debian.org>", 235 Architecture: "amd64", 236 }, 237 Locations: []string{"var/lib/dpkg/status"}, 238 }, 239 { 240 Name: "acl", 241 Version: "2.3.1-3", 242 PURLType: purl.TypeDebian, 243 Metadata: &dpkgmeta.Metadata{ 244 PackageName: "acl", 245 PackageVersion: "2.3.1-3", 246 Status: "install ok installed", 247 OSID: "debian", 248 OSVersionCodename: "bookworm", 249 OSVersionID: "12", 250 Maintainer: "Guillem Jover <guillem@debian.org>", 251 Architecture: "amd64", 252 }, 253 Locations: []string{"var/lib/dpkg/status"}, 254 }, 255 { 256 Name: "adduser", 257 Version: "3.131", 258 PURLType: purl.TypeDebian, 259 Metadata: &dpkgmeta.Metadata{ 260 PackageName: "adduser", 261 PackageVersion: "3.131", 262 Status: "install ok installed", 263 OSID: "debian", 264 OSVersionCodename: "bookworm", 265 OSVersionID: "12", 266 Maintainer: "Debian Adduser Developers <adduser@packages.debian.org>", 267 Architecture: "all", 268 }, 269 Locations: []string{"var/lib/dpkg/status"}, 270 }, 271 { 272 Name: "admin-session", 273 Version: "2023.06.26.c543406313-00", 274 PURLType: purl.TypeDebian, 275 Metadata: &dpkgmeta.Metadata{ 276 PackageName: "admin-session", 277 PackageVersion: "2023.06.26.c543406313-00", 278 Status: "install ok installed", 279 OSID: "debian", 280 OSVersionCodename: "bookworm", 281 OSVersionID: "12", 282 Maintainer: "nobody@google.com", 283 Architecture: "amd64", 284 }, 285 Locations: []string{"var/lib/dpkg/status"}, 286 }, 287 { 288 Name: "attr", 289 Version: "1:2.5.1-4", 290 PURLType: purl.TypeDebian, 291 Metadata: &dpkgmeta.Metadata{ 292 PackageName: "attr", 293 PackageVersion: "1:2.5.1-4", 294 Status: "install ok installed", 295 OSID: "debian", 296 OSVersionCodename: "bookworm", 297 OSVersionID: "12", 298 Maintainer: "Guillem Jover <guillem@debian.org>", 299 Architecture: "amd64", 300 }, 301 Locations: []string{"var/lib/dpkg/status"}, 302 }, 303 // Expect source name. 304 { 305 Name: "libacl1", 306 Version: "2.3.1-3", 307 PURLType: purl.TypeDebian, 308 Metadata: &dpkgmeta.Metadata{ 309 PackageName: "libacl1", 310 PackageVersion: "2.3.1-3", 311 Status: "install ok installed", 312 SourceName: "acl", 313 OSID: "debian", 314 OSVersionCodename: "bookworm", 315 OSVersionID: "12", 316 Maintainer: "Guillem Jover <guillem@debian.org>", 317 Architecture: "amd64", 318 }, 319 Locations: []string{"var/lib/dpkg/status"}, 320 }, 321 // Expect source name and version. 322 { 323 Name: "util-linux-extra", 324 Version: "2.38.1-5+b1", 325 PURLType: purl.TypeDebian, 326 Metadata: &dpkgmeta.Metadata{ 327 PackageName: "util-linux-extra", 328 PackageVersion: "2.38.1-5+b1", 329 Status: "install ok installed", 330 SourceName: "util-linux", 331 SourceVersion: "2.38.1-5", 332 OSID: "debian", 333 OSVersionCodename: "bookworm", 334 OSVersionID: "12", 335 Maintainer: "util-linux packagers <util-linux@packages.debian.org>", 336 Architecture: "amd64", 337 }, 338 Locations: []string{"var/lib/dpkg/status"}, 339 }, 340 }, 341 wantResultMetric: stats.FileExtractedResultSuccess, 342 }, 343 { 344 name: "packages with no version set are skipped", 345 path: "testdata/dpkg/noversion", 346 osrelease: DebianBookworm, 347 wantPackages: []*extractor.Package{ 348 { 349 Name: "foo", 350 Version: "1.0", 351 PURLType: purl.TypeDebian, 352 Metadata: &dpkgmeta.Metadata{ 353 PackageName: "foo", 354 PackageVersion: "1.0", 355 Status: "install ok installed", 356 OSID: "debian", 357 OSVersionCodename: "bookworm", 358 OSVersionID: "12", 359 }, 360 Locations: []string{"var/lib/dpkg/status"}, 361 }, 362 { 363 Name: "bar", 364 Version: "2.0", 365 PURLType: purl.TypeDebian, 366 Metadata: &dpkgmeta.Metadata{ 367 PackageName: "bar", 368 PackageVersion: "2.0", 369 Status: "install ok installed", 370 OSID: "debian", 371 OSVersionCodename: "bookworm", 372 OSVersionID: "12", 373 }, 374 Locations: []string{"var/lib/dpkg/status"}, 375 }, 376 }, 377 wantResultMetric: stats.FileExtractedResultSuccess, 378 wantLogWarn: 1, 379 }, 380 { 381 name: "packages with no name set are skipped", 382 path: "testdata/dpkg/nopackage", 383 osrelease: DebianBookworm, 384 wantPackages: []*extractor.Package{ 385 { 386 Name: "foo", 387 Version: "1.0", 388 PURLType: purl.TypeDebian, 389 Metadata: &dpkgmeta.Metadata{ 390 PackageName: "foo", 391 PackageVersion: "1.0", 392 Status: "install ok installed", 393 OSID: "debian", 394 OSVersionCodename: "bookworm", 395 OSVersionID: "12", 396 }, 397 Locations: []string{"var/lib/dpkg/status"}, 398 }, 399 { 400 Name: "bar", 401 Version: "2.0", 402 PURLType: purl.TypeDebian, 403 Metadata: &dpkgmeta.Metadata{ 404 PackageName: "bar", 405 PackageVersion: "2.0", 406 Status: "install ok installed", 407 OSID: "debian", 408 OSVersionCodename: "bookworm", 409 OSVersionID: "12", 410 }, 411 Locations: []string{"var/lib/dpkg/status"}, 412 }, 413 }, 414 wantResultMetric: stats.FileExtractedResultSuccess, 415 wantLogWarn: 1, 416 }, 417 { 418 name: "statusfield", 419 path: "testdata/dpkg/statusfield", 420 osrelease: DebianBookworm, 421 wantPackages: []*extractor.Package{ 422 { 423 Name: "wantinstall_installed", 424 Version: "1.0", 425 PURLType: purl.TypeDebian, 426 Metadata: &dpkgmeta.Metadata{ 427 PackageName: "wantinstall_installed", 428 PackageVersion: "1.0", 429 Status: "install ok installed", 430 OSID: "debian", 431 OSVersionCodename: "bookworm", 432 OSVersionID: "12", 433 }, 434 Locations: []string{"var/lib/dpkg/status"}, 435 }, 436 { 437 Name: "wantdeinstall_installed", 438 Version: "1.0", 439 PURLType: purl.TypeDebian, 440 Metadata: &dpkgmeta.Metadata{ 441 PackageName: "wantdeinstall_installed", 442 PackageVersion: "1.0", 443 Status: "deinstall reinstreq installed", 444 OSID: "debian", 445 OSVersionCodename: "bookworm", 446 OSVersionID: "12", 447 }, 448 Locations: []string{"var/lib/dpkg/status"}, 449 }, 450 { 451 Name: "wantpurge_installed", 452 Version: "1.0", 453 PURLType: purl.TypeDebian, 454 Metadata: &dpkgmeta.Metadata{ 455 PackageName: "wantpurge_installed", 456 PackageVersion: "1.0", 457 Status: "purge ok installed", 458 OSID: "debian", 459 OSVersionCodename: "bookworm", 460 OSVersionID: "12", 461 }, 462 Locations: []string{"var/lib/dpkg/status"}, 463 }, 464 }, 465 wantResultMetric: stats.FileExtractedResultSuccess, 466 wantLogWarn: 1, 467 }, 468 { 469 name: "statusfield including not installed", 470 path: "testdata/dpkg/statusfield", 471 osrelease: DebianBookworm, 472 cfg: dpkg.Config{ 473 IncludeNotInstalled: true, 474 }, 475 wantPackages: []*extractor.Package{ 476 { 477 Name: "wantinstall_installed", 478 Version: "1.0", 479 PURLType: purl.TypeDebian, 480 Metadata: &dpkgmeta.Metadata{ 481 PackageName: "wantinstall_installed", 482 PackageVersion: "1.0", 483 Status: "install ok installed", 484 OSID: "debian", 485 OSVersionCodename: "bookworm", 486 OSVersionID: "12", 487 }, 488 Locations: []string{"var/lib/dpkg/status"}, 489 }, 490 { 491 Name: "wantdeinstall_installed", 492 Version: "1.0", 493 PURLType: purl.TypeDebian, 494 Metadata: &dpkgmeta.Metadata{ 495 PackageName: "wantdeinstall_installed", 496 PackageVersion: "1.0", 497 Status: "deinstall reinstreq installed", 498 OSID: "debian", 499 OSVersionCodename: "bookworm", 500 OSVersionID: "12", 501 }, 502 Locations: []string{"var/lib/dpkg/status"}, 503 }, 504 { 505 Name: "wantdeinstall_configfiles", 506 Version: "1.0", 507 PURLType: purl.TypeDebian, 508 Metadata: &dpkgmeta.Metadata{ 509 PackageName: "wantdeinstall_configfiles", 510 PackageVersion: "1.0", 511 Status: "deinstall ok config-files", 512 OSID: "debian", 513 OSVersionCodename: "bookworm", 514 OSVersionID: "12", 515 }, 516 Locations: []string{"var/lib/dpkg/status"}, 517 }, 518 { 519 Name: "wantinstall_unpacked", 520 Version: "1.0", 521 PURLType: purl.TypeDebian, 522 Metadata: &dpkgmeta.Metadata{ 523 PackageName: "wantinstall_unpacked", 524 PackageVersion: "1.0", 525 Status: "install ok unpacked", 526 OSID: "debian", 527 OSVersionCodename: "bookworm", 528 OSVersionID: "12", 529 }, 530 Locations: []string{"var/lib/dpkg/status"}, 531 }, 532 { 533 Name: "wantpurge_installed", 534 Version: "1.0", 535 PURLType: purl.TypeDebian, 536 Metadata: &dpkgmeta.Metadata{ 537 PackageName: "wantpurge_installed", 538 PackageVersion: "1.0", 539 Status: "purge ok installed", 540 OSID: "debian", 541 OSVersionCodename: "bookworm", 542 OSVersionID: "12", 543 }, 544 Locations: []string{"var/lib/dpkg/status"}, 545 }, 546 { 547 Name: "wantinstall_halfinstalled", 548 Version: "1.0", 549 PURLType: purl.TypeDebian, 550 Metadata: &dpkgmeta.Metadata{ 551 PackageName: "wantinstall_halfinstalled", 552 PackageVersion: "1.0", 553 Status: "install reinstreq half-installed", 554 OSID: "debian", 555 OSVersionCodename: "bookworm", 556 OSVersionID: "12", 557 }, 558 Locations: []string{"var/lib/dpkg/status"}, 559 }, 560 { 561 Name: "wantnostatus", 562 Version: "1.0", 563 PURLType: purl.TypeDebian, 564 Metadata: &dpkgmeta.Metadata{ 565 PackageName: "wantnostatus", 566 PackageVersion: "1.0", 567 OSID: "debian", 568 OSVersionCodename: "bookworm", 569 OSVersionID: "12", 570 }, 571 Locations: []string{"var/lib/dpkg/status"}, 572 }, 573 }, 574 wantResultMetric: stats.FileExtractedResultSuccess, 575 }, 576 { 577 name: "empty", 578 path: "testdata/dpkg/empty", 579 osrelease: DebianBookworm, 580 wantPackages: []*extractor.Package{}, 581 wantResultMetric: stats.FileExtractedResultSuccess, 582 }, 583 { 584 name: "invalid", 585 path: "testdata/dpkg/invalid", 586 osrelease: DebianBookworm, 587 wantPackages: []*extractor.Package{}, 588 wantErr: cmpopts.AnyError, 589 wantResultMetric: stats.FileExtractedResultErrorUnknown, 590 }, 591 { 592 name: "VERSION_CODENAME_not_set,_fallback_to_VERSION_ID", 593 path: "testdata/dpkg/single", 594 osrelease: `VERSION_ID="12" 595 ID=debian`, 596 wantPackages: []*extractor.Package{ 597 { 598 Name: "acl", 599 Version: "2.3.1-3", 600 PURLType: purl.TypeDebian, 601 Metadata: &dpkgmeta.Metadata{ 602 PackageName: "acl", 603 PackageVersion: "2.3.1-3", 604 Status: "install ok installed", 605 OSID: "debian", 606 OSVersionID: "12", 607 Maintainer: "Guillem Jover <guillem@debian.org>", 608 Architecture: "amd64", 609 }, 610 Locations: []string{"var/lib/dpkg/status"}, 611 }, 612 }, 613 wantResultMetric: stats.FileExtractedResultSuccess, 614 }, 615 { 616 name: "no version", 617 path: "testdata/dpkg/single", 618 osrelease: `ID=debian`, 619 wantPackages: []*extractor.Package{ 620 { 621 Name: "acl", 622 Version: "2.3.1-3", 623 PURLType: purl.TypeDebian, 624 Metadata: &dpkgmeta.Metadata{ 625 PackageName: "acl", 626 PackageVersion: "2.3.1-3", 627 Status: "install ok installed", 628 OSID: "debian", 629 Maintainer: "Guillem Jover <guillem@debian.org>", 630 Architecture: "amd64", 631 }, 632 Locations: []string{"var/lib/dpkg/status"}, 633 }, 634 }, 635 wantResultMetric: stats.FileExtractedResultSuccess, 636 }, 637 { 638 name: "osrelease id not set", 639 path: "testdata/dpkg/single", 640 osrelease: "VERSION_CODENAME=bookworm", 641 wantPackages: []*extractor.Package{ 642 { 643 Name: "acl", 644 Version: "2.3.1-3", 645 PURLType: purl.TypeDebian, 646 Metadata: &dpkgmeta.Metadata{ 647 PackageName: "acl", 648 PackageVersion: "2.3.1-3", 649 Status: "install ok installed", 650 OSVersionCodename: "bookworm", 651 Maintainer: "Guillem Jover <guillem@debian.org>", 652 Architecture: "amd64", 653 }, 654 Locations: []string{"var/lib/dpkg/status"}, 655 }, 656 }, 657 wantResultMetric: stats.FileExtractedResultSuccess, 658 }, 659 { 660 name: "ubuntu", 661 path: "testdata/dpkg/single", 662 osrelease: `VERSION_ID="22.04" 663 VERSION_CODENAME=jammy 664 ID=ubuntu 665 ID_LIKE=debian`, 666 wantPackages: []*extractor.Package{ 667 { 668 Name: "acl", 669 Version: "2.3.1-3", 670 PURLType: purl.TypeDebian, 671 Metadata: &dpkgmeta.Metadata{ 672 PackageName: "acl", 673 PackageVersion: "2.3.1-3", 674 Status: "install ok installed", 675 OSID: "ubuntu", 676 OSVersionCodename: "jammy", 677 OSVersionID: "22.04", 678 Maintainer: "Guillem Jover <guillem@debian.org>", 679 Architecture: "amd64", 680 }, 681 Locations: []string{"var/lib/dpkg/status"}, 682 }, 683 }, 684 wantResultMetric: stats.FileExtractedResultSuccess, 685 }, 686 { 687 name: "ubuntu", 688 path: "testdata/dpkg/trailingnewlines", 689 osrelease: `VERSION_ID="22.04" 690 VERSION_CODENAME=jammy 691 ID=ubuntu 692 ID_LIKE=debian`, 693 wantPackages: []*extractor.Package{ 694 { 695 Name: "acl", 696 Version: "2.3.1-3", 697 PURLType: purl.TypeDebian, 698 Metadata: &dpkgmeta.Metadata{ 699 PackageName: "acl", 700 PackageVersion: "2.3.1-3", 701 Status: "install ok installed", 702 OSID: "ubuntu", 703 OSVersionCodename: "jammy", 704 OSVersionID: "22.04", 705 Maintainer: "Guillem Jover <guillem@debian.org>", 706 Architecture: "amd64", 707 }, 708 Locations: []string{"var/lib/dpkg/status"}, 709 }, 710 }, 711 wantResultMetric: stats.FileExtractedResultSuccess, 712 wantLogWarn: 0, 713 wantLogErr: 0, 714 }, 715 { 716 name: "status.d file without Status field set should work", 717 path: "testdata/dpkg/status.d/foo", 718 osrelease: DebianBookworm, 719 wantPackages: []*extractor.Package{ 720 { 721 Name: "foo", 722 Version: "1.2.3", 723 PURLType: purl.TypeDebian, 724 Metadata: &dpkgmeta.Metadata{ 725 PackageName: "foo", 726 PackageVersion: "1.2.3", 727 OSID: "debian", 728 OSVersionCodename: "bookworm", 729 OSVersionID: "12", 730 Maintainer: "someone", 731 Architecture: "amd64", 732 }, 733 Locations: []string{"var/lib/dpkg/status.d/foo"}, 734 }, 735 }, 736 wantResultMetric: stats.FileExtractedResultSuccess, 737 }, 738 { 739 name: "status.d file without Status field set should work", 740 path: "testdata/dpkg/status.d/foo.md5sums", 741 osrelease: DebianBookworm, 742 wantPackages: []*extractor.Package{}, 743 wantResultMetric: stats.FileExtractedResultSuccess, 744 wantLogWarn: 1, 745 }, 746 { 747 name: "transitional packages should be annotated", 748 path: "testdata/dpkg/transitional", 749 osrelease: DebianBookworm, 750 wantPackages: []*extractor.Package{ 751 { 752 Name: "iceweasel", 753 Version: "78.13.0esr-1~deb10u1", 754 PURLType: purl.TypeDebian, 755 Metadata: &dpkgmeta.Metadata{ 756 PackageName: "iceweasel", 757 Status: "install ok installed", 758 PackageVersion: "78.13.0esr-1~deb10u1", 759 SourceName: "firefox-esr", 760 OSID: "debian", 761 OSVersionCodename: "bookworm", 762 OSVersionID: "12", 763 Maintainer: "Maintainers of Mozilla-related packages <team+pkg-mozilla@tracker.debian.org>", 764 Architecture: "all", 765 }, 766 Locations: []string{"var/lib/dpkg/status"}, 767 ExploitabilitySignals: []*vex.PackageExploitabilitySignal{&vex.PackageExploitabilitySignal{ 768 Plugin: dpkg.Name, 769 Justification: vex.ComponentNotPresent, 770 MatchesAllVulns: true, 771 }}, 772 }, 773 }, 774 wantResultMetric: stats.FileExtractedResultSuccess, 775 }, 776 { 777 name: "transitional dummy packages should be annotated", 778 path: "testdata/dpkg/transitional_dummy", 779 osrelease: DebianBookworm, 780 wantPackages: []*extractor.Package{ 781 { 782 Name: "git-core", 783 Version: "1:2.14.2-1", 784 PURLType: purl.TypeDebian, 785 Metadata: &dpkgmeta.Metadata{ 786 PackageName: "git-core", 787 Status: "install ok installed", 788 PackageVersion: "1:2.14.2-1", 789 SourceName: "git", 790 OSID: "debian", 791 OSVersionCodename: "bookworm", 792 OSVersionID: "12", 793 Maintainer: "Gerrit Pape <pape@smarden.org>", 794 Architecture: "all", 795 }, 796 Locations: []string{"var/lib/dpkg/status"}, 797 ExploitabilitySignals: []*vex.PackageExploitabilitySignal{&vex.PackageExploitabilitySignal{ 798 Plugin: dpkg.Name, 799 Justification: vex.ComponentNotPresent, 800 MatchesAllVulns: true, 801 }}, 802 }, 803 }, 804 wantResultMetric: stats.FileExtractedResultSuccess, 805 }, 806 { 807 name: "transitional empty packages should be annotated", 808 path: "testdata/dpkg/transitional_empty", 809 osrelease: DebianBookworm, 810 wantPackages: []*extractor.Package{ 811 { 812 Name: "runit-systemd", 813 Version: "2.1.2-54+usrmerge", 814 PURLType: purl.TypeDebian, 815 Metadata: &dpkgmeta.Metadata{ 816 PackageName: "runit-systemd", 817 Status: "install ok installed", 818 PackageVersion: "2.1.2-54+usrmerge", 819 SourceName: "runit", 820 OSID: "debian", 821 OSVersionCodename: "bookworm", 822 OSVersionID: "12", 823 Maintainer: "Lorenzo Puliti <plorenzo@disroot.org>", 824 Architecture: "all", 825 }, 826 Locations: []string{"var/lib/dpkg/status"}, 827 ExploitabilitySignals: []*vex.PackageExploitabilitySignal{&vex.PackageExploitabilitySignal{ 828 Plugin: dpkg.Name, 829 Justification: vex.ComponentNotPresent, 830 MatchesAllVulns: true, 831 }}, 832 }, 833 }, 834 wantResultMetric: stats.FileExtractedResultSuccess, 835 }, 836 { 837 name: "valid opkg status file", 838 path: "testdata/opkg/valid", // Path to your OPKG status file in the test data 839 osrelease: OpkgRelease, // You can mock the os-release data as needed 840 isOPKG: true, 841 wantPackages: []*extractor.Package{ 842 { 843 Name: "ubus", 844 Version: "2024.10.20~252a9b0c-r1", 845 PURLType: purl.TypeOpkg, 846 Metadata: &dpkgmeta.Metadata{ 847 PackageName: "ubus", 848 PackageVersion: "2024.10.20~252a9b0c-r1", 849 Status: "install ok installed", 850 Architecture: "x86_64", 851 OSID: "openwrt", 852 OSVersionCodename: "openwrt-21.02.1", 853 OSVersionID: "21.02.1", 854 }, 855 Locations: []string{"usr/lib/opkg/status"}, 856 }, 857 { 858 Name: "libuci20130104", 859 Version: "2023.08.10~5781664d-r1", 860 PURLType: purl.TypeOpkg, 861 Metadata: &dpkgmeta.Metadata{ 862 PackageName: "libuci20130104", 863 PackageVersion: "2023.08.10~5781664d-r1", 864 Status: "install ok installed", 865 Architecture: "x86_64", 866 OSID: "openwrt", 867 OSVersionCodename: "openwrt-21.02.1", 868 OSVersionID: "21.02.1", 869 }, 870 Locations: []string{"usr/lib/opkg/status"}, 871 }, 872 { 873 Name: "busybox", 874 Version: "1.36.1-r2", 875 PURLType: purl.TypeOpkg, 876 Metadata: &dpkgmeta.Metadata{ 877 PackageName: "busybox", 878 PackageVersion: "1.36.1-r2", 879 Status: "install ok installed", 880 Architecture: "x86_64", 881 OSID: "openwrt", 882 OSVersionCodename: "openwrt-21.02.1", 883 OSVersionID: "21.02.1", 884 }, 885 Locations: []string{"usr/lib/opkg/status"}, 886 }, 887 }, 888 wantResultMetric: stats.FileExtractedResultSuccess, 889 }, 890 { 891 name: "packages with no version set are skipped", 892 path: "testdata/opkg/noversion", 893 osrelease: OpkgRelease, 894 isOPKG: true, 895 wantPackages: []*extractor.Package{ 896 { 897 Name: "ubus", 898 Version: "2024.10.20~252a9b0c-r1", 899 PURLType: purl.TypeOpkg, 900 Metadata: &dpkgmeta.Metadata{ 901 PackageName: "ubus", 902 PackageVersion: "2024.10.20~252a9b0c-r1", 903 Status: "install ok installed", 904 Architecture: "x86_64", 905 OSID: "openwrt", 906 OSVersionCodename: "openwrt-21.02.1", 907 OSVersionID: "21.02.1", 908 }, 909 Locations: []string{"usr/lib/opkg/status"}, 910 }, 911 { 912 Name: "busybox", 913 Version: "1.36.1-r2", 914 PURLType: purl.TypeOpkg, 915 Metadata: &dpkgmeta.Metadata{ 916 PackageName: "busybox", 917 PackageVersion: "1.36.1-r2", 918 Status: "install ok installed", 919 Architecture: "x86_64", 920 OSID: "openwrt", 921 OSVersionCodename: "openwrt-21.02.1", 922 OSVersionID: "21.02.1", 923 }, 924 Locations: []string{"usr/lib/opkg/status"}, 925 }, 926 }, 927 wantResultMetric: stats.FileExtractedResultSuccess, 928 wantLogWarn: 1, 929 }, 930 { 931 name: "packages with no name set are skipped", 932 path: "testdata/opkg/nopackage", 933 osrelease: OpkgRelease, 934 isOPKG: true, 935 wantPackages: []*extractor.Package{ 936 { 937 Name: "ubus", 938 Version: "2024.10.20~252a9b0c-r1", 939 PURLType: purl.TypeOpkg, 940 Metadata: &dpkgmeta.Metadata{ 941 PackageName: "ubus", 942 PackageVersion: "2024.10.20~252a9b0c-r1", 943 Status: "install ok installed", 944 Architecture: "x86_64", 945 OSID: "openwrt", 946 OSVersionCodename: "openwrt-21.02.1", 947 OSVersionID: "21.02.1", 948 }, 949 Locations: []string{"usr/lib/opkg/status"}, 950 }, 951 { 952 Name: "busybox", 953 Version: "1.36.1-r2", 954 PURLType: purl.TypeOpkg, 955 Metadata: &dpkgmeta.Metadata{ 956 PackageName: "busybox", 957 PackageVersion: "1.36.1-r2", 958 Status: "install ok installed", 959 Architecture: "x86_64", 960 OSID: "openwrt", 961 OSVersionCodename: "openwrt-21.02.1", 962 OSVersionID: "21.02.1", 963 }, 964 Locations: []string{"usr/lib/opkg/status"}, 965 }, 966 }, 967 wantResultMetric: stats.FileExtractedResultSuccess, 968 wantLogWarn: 1, 969 }, 970 { 971 name: "statusfield", 972 path: "testdata/opkg/statusfield", // Path to your OPKG status file in the test data 973 osrelease: OpkgRelease, // You can mock the os-release data as needed 974 isOPKG: true, 975 wantPackages: []*extractor.Package{ 976 { 977 Name: "wantinstall_installed", 978 Version: "1.0", 979 PURLType: purl.TypeOpkg, 980 Metadata: &dpkgmeta.Metadata{ 981 PackageName: "wantinstall_installed", 982 PackageVersion: "1.0", 983 Status: "install ok installed", 984 OSID: "openwrt", 985 OSVersionCodename: "openwrt-21.02.1", 986 OSVersionID: "21.02.1", 987 }, 988 Locations: []string{"usr/lib/opkg/status"}, 989 }, 990 { 991 Name: "wantpurge_installed", 992 Version: "1.0", 993 PURLType: purl.TypeOpkg, 994 Metadata: &dpkgmeta.Metadata{ 995 PackageName: "wantpurge_installed", 996 PackageVersion: "1.0", 997 Status: "purge ok installed", 998 OSID: "openwrt", 999 OSVersionCodename: "openwrt-21.02.1", 1000 OSVersionID: "21.02.1", 1001 }, 1002 Locations: []string{"usr/lib/opkg/status"}, 1003 }, 1004 }, 1005 wantResultMetric: stats.FileExtractedResultSuccess, 1006 wantLogWarn: 1, 1007 }, 1008 { 1009 name: "statusfield including not installed", 1010 path: "testdata/opkg/statusfield", // Path to your OPKG status file in the test data 1011 osrelease: OpkgRelease, // You can mock the os-release data as needed 1012 isOPKG: true, 1013 cfg: dpkg.Config{ 1014 IncludeNotInstalled: true, 1015 }, 1016 wantPackages: []*extractor.Package{ 1017 { 1018 Name: "wantinstall_installed", 1019 Version: "1.0", 1020 PURLType: purl.TypeOpkg, 1021 Metadata: &dpkgmeta.Metadata{ 1022 PackageName: "wantinstall_installed", 1023 PackageVersion: "1.0", 1024 Status: "install ok installed", 1025 OSID: "openwrt", 1026 OSVersionCodename: "openwrt-21.02.1", 1027 OSVersionID: "21.02.1", 1028 }, 1029 Locations: []string{"usr/lib/opkg/status"}, 1030 }, 1031 { 1032 Name: "wantdeinstall_configfiles", 1033 Version: "1.0", 1034 PURLType: purl.TypeOpkg, 1035 Metadata: &dpkgmeta.Metadata{ 1036 PackageName: "wantdeinstall_configfiles", 1037 PackageVersion: "1.0", 1038 Status: "deinstall ok config-files", 1039 OSID: "openwrt", 1040 OSVersionCodename: "openwrt-21.02.1", 1041 OSVersionID: "21.02.1", 1042 }, 1043 Locations: []string{"usr/lib/opkg/status"}, 1044 }, 1045 { 1046 Name: "wantinstall_unpacked", 1047 Version: "1.0", 1048 PURLType: purl.TypeOpkg, 1049 Metadata: &dpkgmeta.Metadata{ 1050 PackageName: "wantinstall_unpacked", 1051 PackageVersion: "1.0", 1052 Status: "install ok unpacked", 1053 OSID: "openwrt", 1054 OSVersionCodename: "openwrt-21.02.1", 1055 OSVersionID: "21.02.1", 1056 }, 1057 Locations: []string{"usr/lib/opkg/status"}, 1058 }, 1059 { 1060 Name: "wantpurge_installed", 1061 Version: "1.0", 1062 PURLType: purl.TypeOpkg, 1063 Metadata: &dpkgmeta.Metadata{ 1064 PackageName: "wantpurge_installed", 1065 PackageVersion: "1.0", 1066 Status: "purge ok installed", 1067 OSID: "openwrt", 1068 OSVersionCodename: "openwrt-21.02.1", 1069 OSVersionID: "21.02.1", 1070 }, 1071 Locations: []string{"usr/lib/opkg/status"}, 1072 }, 1073 { 1074 Name: "wantpurge_notinstalled", 1075 Version: "1.0", 1076 PURLType: purl.TypeOpkg, 1077 Metadata: &dpkgmeta.Metadata{ 1078 PackageName: "wantpurge_notinstalled", 1079 PackageVersion: "1.0", 1080 Status: "purge ok not-installed", 1081 OSID: "openwrt", 1082 OSVersionCodename: "openwrt-21.02.1", 1083 OSVersionID: "21.02.1", 1084 }, 1085 Locations: []string{"usr/lib/opkg/status"}, 1086 }, 1087 { 1088 Name: "wantnostatus", 1089 Version: "1.0", 1090 PURLType: purl.TypeOpkg, 1091 Metadata: &dpkgmeta.Metadata{ 1092 PackageName: "wantnostatus", 1093 PackageVersion: "1.0", 1094 OSID: "openwrt", 1095 OSVersionCodename: "openwrt-21.02.1", 1096 OSVersionID: "21.02.1", 1097 }, 1098 Locations: []string{"usr/lib/opkg/status"}, 1099 }, 1100 }, 1101 wantResultMetric: stats.FileExtractedResultSuccess, 1102 }, 1103 { 1104 name: "empty", 1105 path: "testdata/opkg/empty", 1106 osrelease: OpkgRelease, 1107 isOPKG: true, 1108 wantPackages: []*extractor.Package{}, 1109 wantResultMetric: stats.FileExtractedResultSuccess, 1110 }, 1111 { 1112 name: "invalid", 1113 path: "testdata/opkg/invalid", 1114 osrelease: OpkgRelease, 1115 isOPKG: true, 1116 wantPackages: []*extractor.Package{}, 1117 wantErr: cmpopts.AnyError, 1118 wantResultMetric: stats.FileExtractedResultErrorUnknown, 1119 }, 1120 { 1121 name: "VERSION_CODENAME_not_set,_fallback_to_VERSION_ID", 1122 path: "testdata/opkg/single", 1123 osrelease: `VERSION_ID="21.02.1" 1124 ID=openwrt`, 1125 isOPKG: true, 1126 wantPackages: []*extractor.Package{ 1127 { 1128 Name: "ubus", 1129 Version: "2024.10.20~252a9b0c-r1", 1130 PURLType: purl.TypeOpkg, 1131 Metadata: &dpkgmeta.Metadata{ 1132 PackageName: "ubus", 1133 PackageVersion: "2024.10.20~252a9b0c-r1", 1134 Status: "install ok installed", 1135 Architecture: "x86_64", 1136 OSID: "openwrt", 1137 OSVersionID: "21.02.1", 1138 }, 1139 Locations: []string{"usr/lib/opkg/status"}, 1140 }, 1141 }, 1142 wantResultMetric: stats.FileExtractedResultSuccess, 1143 }, 1144 { 1145 name: "no version", 1146 path: "testdata/opkg/single", 1147 osrelease: `ID=openwrt`, 1148 isOPKG: true, 1149 wantPackages: []*extractor.Package{ 1150 { 1151 Name: "ubus", 1152 Version: "2024.10.20~252a9b0c-r1", 1153 PURLType: purl.TypeOpkg, 1154 Metadata: &dpkgmeta.Metadata{ 1155 PackageName: "ubus", 1156 PackageVersion: "2024.10.20~252a9b0c-r1", 1157 Status: "install ok installed", 1158 Architecture: "x86_64", 1159 OSID: "openwrt", 1160 }, 1161 Locations: []string{"usr/lib/opkg/status"}, 1162 }, 1163 }, 1164 wantResultMetric: stats.FileExtractedResultSuccess, 1165 }, 1166 { 1167 name: "osrelease id not set", 1168 path: "testdata/opkg/single", 1169 osrelease: `VERSION_CODENAME=openwrt-21.02.1`, 1170 isOPKG: true, 1171 wantPackages: []*extractor.Package{ 1172 { 1173 Name: "ubus", 1174 Version: "2024.10.20~252a9b0c-r1", 1175 PURLType: purl.TypeOpkg, 1176 Metadata: &dpkgmeta.Metadata{ 1177 PackageName: "ubus", 1178 PackageVersion: "2024.10.20~252a9b0c-r1", 1179 Status: "install ok installed", 1180 Architecture: "x86_64", 1181 OSVersionCodename: "openwrt-21.02.1", 1182 }, 1183 Locations: []string{"usr/lib/opkg/status"}, 1184 }, 1185 }, 1186 wantResultMetric: stats.FileExtractedResultSuccess, 1187 }, 1188 { 1189 name: "newlines", 1190 path: "testdata/opkg/trailingnewlines", 1191 osrelease: OpkgRelease, 1192 isOPKG: true, 1193 wantPackages: []*extractor.Package{ 1194 { 1195 Name: "ubus", 1196 Version: "2024.10.20~252a9b0c-r1", 1197 PURLType: purl.TypeOpkg, 1198 Metadata: &dpkgmeta.Metadata{ 1199 PackageName: "ubus", 1200 PackageVersion: "2024.10.20~252a9b0c-r1", 1201 Status: "install ok installed", 1202 Architecture: "x86_64", 1203 OSID: "openwrt", 1204 OSVersionCodename: "openwrt-21.02.1", 1205 OSVersionID: "21.02.1", 1206 }, 1207 Locations: []string{"usr/lib/opkg/status"}, 1208 }, 1209 }, 1210 wantResultMetric: stats.FileExtractedResultSuccess, 1211 wantLogWarn: 0, 1212 wantLogErr: 0, 1213 }, 1214 { 1215 name: "transitional packages should be annotated", 1216 path: "testdata/opkg/transitional", 1217 osrelease: OpkgRelease, 1218 isOPKG: true, 1219 wantPackages: []*extractor.Package{ 1220 { 1221 Name: "ubus", 1222 Version: "2024.10.20~252a9b0c-r1", 1223 PURLType: purl.TypeOpkg, 1224 Metadata: &dpkgmeta.Metadata{ 1225 PackageName: "ubus", 1226 PackageVersion: "2024.10.20~252a9b0c-r1", 1227 Status: "install ok installed", 1228 Architecture: "x86_64", 1229 OSID: "openwrt", 1230 OSVersionCodename: "openwrt-21.02.1", 1231 OSVersionID: "21.02.1", 1232 }, 1233 Locations: []string{"usr/lib/opkg/status"}, 1234 ExploitabilitySignals: []*vex.PackageExploitabilitySignal{&vex.PackageExploitabilitySignal{ 1235 Plugin: dpkg.Name, 1236 Justification: vex.ComponentNotPresent, 1237 MatchesAllVulns: true, 1238 }}, 1239 }, 1240 }, 1241 wantResultMetric: stats.FileExtractedResultSuccess, 1242 }, 1243 } 1244 1245 for _, tt := range tests { 1246 // Note the subtest here 1247 t.Run(tt.name, func(t *testing.T) { 1248 logger := &testLogger{} 1249 scalibrlog.SetLogger(logger) 1250 1251 collector := testcollector.New() 1252 tt.cfg.Stats = collector 1253 1254 d := t.TempDir() 1255 createOsRelease(t, d, tt.osrelease) 1256 1257 r, err := os.Open(tt.path) 1258 defer func() { 1259 if err = r.Close(); err != nil { 1260 t.Errorf("Close(): %v", err) 1261 } 1262 }() 1263 if err != nil { 1264 t.Fatal(err) 1265 } 1266 1267 info, err := os.Stat(tt.path) 1268 if err != nil { 1269 t.Fatalf("Failed to stat test file: %v", err) 1270 } 1271 1272 // Use valid os package descriptor paths instead of the testdata paths since the Extractor 1273 // uses the path to differentiate between things like PURL types. 1274 if tt.isOPKG { 1275 tt.path = "usr/lib/opkg/status" 1276 } else if strings.Contains(tt.path, "status.d") { 1277 tt.path = "var/lib/dpkg/status.d" + strings.Split(tt.path, "status.d")[1] 1278 } else { 1279 tt.path = "var/lib/dpkg/status" 1280 } 1281 input := &filesystem.ScanInput{ 1282 FS: scalibrfs.DirFS(d), Path: tt.path, Reader: r, Root: d, Info: info, 1283 } 1284 1285 e := dpkg.New(defaultConfigWith(tt.cfg)) 1286 got, err := e.Extract(t.Context(), input) 1287 if !cmp.Equal(err, tt.wantErr, cmpopts.EquateErrors()) { 1288 t.Fatalf("Extract(%+v) error: got %v, want %v\n", tt.path, err, tt.wantErr) 1289 } 1290 1291 ignoreOrder := cmpopts.SortSlices(func(a, b any) bool { 1292 return fmt.Sprintf("%+v", a) < fmt.Sprintf("%+v", b) 1293 }) 1294 wantInv := inventory.Inventory{Packages: tt.wantPackages} 1295 if diff := cmp.Diff(wantInv, got, ignoreOrder); diff != "" { 1296 t.Errorf("Extract(%s) (-want +got):\n%s", tt.path, diff) 1297 } 1298 1299 gotResultMetric := collector.FileExtractedResult(tt.path) 1300 if tt.wantResultMetric != "" && gotResultMetric != tt.wantResultMetric { 1301 t.Errorf("Extract(%s) recorded result metric %v, want result metric %v", tt.path, gotResultMetric, tt.wantResultMetric) 1302 } 1303 1304 gotFileSizeMetric := collector.FileExtractedFileSize(tt.path) 1305 if gotFileSizeMetric != info.Size() { 1306 t.Errorf("Extract(%s) recorded file size %v, want file size %v", tt.path, gotFileSizeMetric, info.Size()) 1307 } 1308 1309 if logger.warnings != tt.wantLogWarn { 1310 t.Errorf("Extract(%s) recorded %d warnings, want %d warnings", tt.path, logger.warnings, tt.wantLogWarn) 1311 } 1312 if logger.errors != tt.wantLogErr { 1313 t.Errorf("Extract(%s) recorded %d errors, want %d errors", tt.path, logger.errors, tt.wantLogErr) 1314 } 1315 }) 1316 } 1317 } 1318 1319 var _ scalibrlog.Logger = &testLogger{} 1320 1321 type testLogger struct { 1322 Verbose bool 1323 warnings int 1324 errors int 1325 } 1326 1327 // Errorf is the formatted error logging function. 1328 func (l *testLogger) Errorf(format string, args ...any) { 1329 golog.Printf(format, args...) 1330 l.errors++ 1331 } 1332 1333 // Warnf is the formatted warning logging function. 1334 func (l *testLogger) Warnf(format string, args ...any) { 1335 golog.Printf(format, args...) 1336 l.warnings++ 1337 } 1338 1339 // Infof is the formatted info logging function. 1340 func (testLogger) Infof(format string, args ...any) { 1341 golog.Printf(format, args...) 1342 } 1343 1344 // Debugf is the formatted debug logging function. 1345 func (l *testLogger) Debugf(format string, args ...any) { 1346 if l.Verbose { 1347 golog.Printf(format, args...) 1348 } 1349 } 1350 1351 // Error is the error logging function. 1352 func (l *testLogger) Error(args ...any) { 1353 golog.Println(args...) 1354 l.errors++ 1355 } 1356 1357 // Warn is the warning logging function. 1358 func (l *testLogger) Warn(args ...any) { 1359 golog.Println(args...) 1360 l.warnings++ 1361 } 1362 1363 // Info is the info logging function. 1364 func (testLogger) Info(args ...any) { 1365 golog.Println(args...) 1366 } 1367 1368 // Debug is the debug logging function. 1369 func (l *testLogger) Debug(args ...any) { 1370 if l.Verbose { 1371 golog.Println(args...) 1372 } 1373 } 1374 1375 func TestExtractNonexistentOSRelease(t *testing.T) { 1376 path := "testdata/dpkg/single" 1377 1378 want := inventory.Inventory{Packages: []*extractor.Package{ 1379 { 1380 Name: "acl", 1381 Version: "2.3.1-3", 1382 PURLType: purl.TypeDebian, 1383 Metadata: &dpkgmeta.Metadata{ 1384 PackageName: "acl", 1385 PackageVersion: "2.3.1-3", 1386 Status: "install ok installed", 1387 OSID: "", 1388 OSVersionID: "", 1389 Maintainer: "Guillem Jover <guillem@debian.org>", 1390 Architecture: "amd64", 1391 }, 1392 Locations: []string{path}, 1393 }, 1394 }} 1395 1396 r, err := os.Open(path) 1397 defer func() { 1398 if err = r.Close(); err != nil { 1399 t.Errorf("Close(): %v", err) 1400 } 1401 }() 1402 if err != nil { 1403 t.Fatal(err) 1404 } 1405 1406 info, err := os.Stat(path) 1407 if err != nil { 1408 t.Fatal(err) 1409 } 1410 // Note that we didn't create any OS release file. 1411 input := &filesystem.ScanInput{FS: scalibrfs.DirFS("."), Path: path, Info: info, Reader: r} 1412 1413 e := dpkg.New(dpkg.DefaultConfig()) 1414 got, err := e.Extract(t.Context(), input) 1415 if err != nil { 1416 t.Fatalf("Extract(%s) error: %v", path, err) 1417 } 1418 if diff := cmp.Diff(want, got); diff != "" { 1419 t.Errorf("Extract(%s) (-want +got):\n%s", path, diff) 1420 } 1421 } 1422 1423 func createOsRelease(t *testing.T, root string, content string) { 1424 t.Helper() 1425 _ = os.MkdirAll(filepath.Join(root, "etc"), 0755) 1426 err := os.WriteFile(filepath.Join(root, "etc/os-release"), []byte(content), 0644) 1427 if err != nil { 1428 t.Fatalf("write to %s: %v\n", filepath.Join(root, "etc/os-release"), err) 1429 } 1430 } 1431 1432 // defaultConfigWith combines any non-zero fields of cfg with packagejson.DefaultConfig(). 1433 func defaultConfigWith(cfg dpkg.Config) dpkg.Config { 1434 newCfg := dpkg.DefaultConfig() 1435 1436 if cfg.Stats != nil { 1437 newCfg.Stats = cfg.Stats 1438 } 1439 1440 if cfg.MaxFileSizeBytes > 0 { 1441 newCfg.MaxFileSizeBytes = cfg.MaxFileSizeBytes 1442 } 1443 1444 if cfg.IncludeNotInstalled { 1445 newCfg.IncludeNotInstalled = cfg.IncludeNotInstalled 1446 } 1447 1448 return newCfg 1449 }