github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/binary/classifier_cataloger_test.go (about) 1 package binary 2 3 import ( 4 "context" 5 "errors" 6 "flag" 7 "fmt" 8 "io" 9 "strings" 10 "testing" 11 12 "github.com/google/go-cmp/cmp" 13 "github.com/google/go-cmp/cmp/cmpopts" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 17 "github.com/anchore/packageurl-go" 18 "github.com/anchore/stereoscope/pkg/imagetest" 19 "github.com/anchore/syft/syft/cpe" 20 "github.com/anchore/syft/syft/file" 21 "github.com/anchore/syft/syft/pkg" 22 "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/testutil" 23 "github.com/anchore/syft/syft/pkg/cataloger/internal/binutils" 24 "github.com/anchore/syft/syft/source" 25 "github.com/anchore/syft/syft/source/directorysource" 26 "github.com/anchore/syft/syft/source/stereoscopesource" 27 ) 28 29 var mustUseOriginalBinaries = flag.Bool("must-use-original-binaries", false, "force the use of binaries for testing (instead of snippets)") 30 31 func Test_Cataloger_PositiveCases(t *testing.T) { 32 tests := []struct { 33 name string 34 // logicalFixture is the logical path to the full binary or snippet. This is relative to the test-fixtures/classifiers/snippets 35 // or test-fixtures/classifiers/bin directory . Snippets are searched for first, and if not found, then existing binaries are 36 // used. If no binary or snippet is found the test will fail. If '-must-use-original-binaries' is used the only 37 // full binaries are tested (no snippets), and if no binary is found the test will be skipped. 38 logicalFixture string 39 expected pkg.Package 40 }{ 41 { 42 logicalFixture: "arangodb/3.11.8/linux-amd64", 43 expected: pkg.Package{ 44 Name: "arangodb", 45 Version: "3.11.8", 46 Type: "binary", 47 PURL: "pkg:generic/arangodb@3.11.8", 48 Locations: locations("arangosh"), 49 Metadata: metadata("arangodb-binary"), 50 }, 51 }, 52 { 53 logicalFixture: "arangodb/3.12.0-2/linux-amd64", 54 expected: pkg.Package{ 55 Name: "arangodb", 56 Version: "3.12.0-2", 57 Type: "binary", 58 PURL: "pkg:generic/arangodb@3.12.0-2", 59 Locations: locations("arangosh"), 60 Metadata: metadata("arangodb-binary"), 61 }, 62 }, 63 { 64 logicalFixture: "postgres/15beta4/linux-amd64", 65 expected: pkg.Package{ 66 Name: "postgresql", 67 Version: "15beta4", 68 Type: "binary", 69 PURL: "pkg:generic/postgresql@15beta4", 70 Locations: locations("postgres"), 71 Metadata: metadata("postgresql-binary"), 72 }, 73 }, 74 { 75 logicalFixture: "postgres/15.1/linux-amd64", 76 expected: pkg.Package{ 77 Name: "postgresql", 78 Version: "15.1", 79 Type: "binary", 80 PURL: "pkg:generic/postgresql@15.1", 81 Locations: locations("postgres"), 82 Metadata: metadata("postgresql-binary"), 83 }, 84 }, 85 { 86 logicalFixture: "postgres/9.6.24/linux-amd64", 87 expected: pkg.Package{ 88 Name: "postgresql", 89 Version: "9.6.24", 90 Type: "binary", 91 PURL: "pkg:generic/postgresql@9.6.24", 92 Locations: locations("postgres"), 93 Metadata: metadata("postgresql-binary"), 94 }, 95 }, 96 { 97 // TODO: find original binary... 98 // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo 99 logicalFixture: "postgres/9.5alpha1/linux-amd64", 100 expected: pkg.Package{ 101 Name: "postgresql", 102 Version: "9.5alpha1", 103 Type: "binary", 104 PURL: "pkg:generic/postgresql@9.5alpha1", 105 Locations: locations("postgres"), 106 Metadata: metadata("postgresql-binary"), 107 }, 108 }, 109 { 110 logicalFixture: "mysql/8.0.34/linux-amd64", 111 expected: pkg.Package{ 112 Name: "mysql", 113 Version: "8.0.34", 114 Type: "binary", 115 PURL: "pkg:generic/mysql@8.0.34", 116 Locations: locations("mysql"), 117 Metadata: metadata("mysql-binary"), 118 }, 119 }, 120 { 121 logicalFixture: "mysql/8.0.37/linux-amd64", 122 expected: pkg.Package{ 123 Name: "mysql", 124 Version: "8.0.37", 125 Type: "binary", 126 PURL: "pkg:generic/mysql@8.0.37", 127 Locations: locations("mysql"), 128 Metadata: metadata("mysql-binary"), 129 }, 130 }, 131 { 132 logicalFixture: "percona-server/8.0.35/linux-amd64", 133 expected: pkg.Package{ 134 Name: "percona-server", 135 Version: "8.0.35", 136 Type: "binary", 137 PURL: "pkg:generic/percona-server@8.0.35", 138 Locations: locations("mysql"), 139 Metadata: metadata("mysql-binary"), 140 }, 141 }, 142 { 143 logicalFixture: "percona-xtradb-cluster/8.0.34/linux-amd64", 144 expected: pkg.Package{ 145 Name: "percona-xtradb-cluster", 146 Version: "8.0.34", 147 Type: "binary", 148 PURL: "pkg:generic/percona-xtradb-cluster@8.0.34", 149 Locations: locations("mysql"), 150 Metadata: metadata("mysql-binary"), 151 }, 152 }, 153 { 154 logicalFixture: "percona-xtrabackup/8.0.35/linux-amd64", 155 expected: pkg.Package{ 156 Name: "percona-xtrabackup", 157 Version: "8.0.35", 158 Type: "binary", 159 PURL: "pkg:generic/percona-xtrabackup@8.0.35", 160 Locations: locations("xtrabackup"), 161 Metadata: metadata("xtrabackup-binary"), 162 }, 163 }, 164 { 165 logicalFixture: "mysql/5.6.51/linux-amd64", 166 expected: pkg.Package{ 167 Name: "mysql", 168 Version: "5.6.51", 169 Type: "binary", 170 PURL: "pkg:generic/mysql@5.6.51", 171 Locations: locations("mysql"), 172 Metadata: metadata("mysql-binary"), 173 }, 174 }, 175 { 176 logicalFixture: "mariadb/10.6.15/linux-amd64", 177 expected: pkg.Package{ 178 Name: "mariadb", 179 Version: "10.6.15", 180 Type: "binary", 181 PURL: "pkg:generic/mariadb@10.6.15", 182 Locations: locations("mariadb"), 183 Metadata: metadata("mariadb-binary"), 184 }, 185 }, 186 { 187 logicalFixture: "traefik/1.7.34/linux-amd64", 188 expected: pkg.Package{ 189 Name: "traefik", 190 Version: "1.7.34", 191 Type: "binary", 192 PURL: "pkg:generic/traefik@1.7.34", 193 Locations: locations("traefik"), 194 Metadata: metadata("traefik-binary"), 195 }, 196 }, 197 { 198 logicalFixture: "traefik/2.9.6/linux-amd64", 199 expected: pkg.Package{ 200 Name: "traefik", 201 Version: "2.9.6", 202 Type: "binary", 203 PURL: "pkg:generic/traefik@2.9.6", 204 Locations: locations("traefik"), 205 Metadata: metadata("traefik-binary"), 206 }, 207 }, 208 { 209 logicalFixture: "traefik/2.10.7/linux-amd64", 210 expected: pkg.Package{ 211 Name: "traefik", 212 Version: "2.10.7", 213 Type: "binary", 214 PURL: "pkg:generic/traefik@2.10.7", 215 Locations: locations("traefik"), 216 Metadata: metadata("traefik-binary"), 217 }, 218 }, 219 { 220 logicalFixture: "traefik/3.0.4/linux-riscv64", 221 expected: pkg.Package{ 222 Name: "traefik", 223 Version: "3.0.4", 224 Type: "binary", 225 PURL: "pkg:generic/traefik@3.0.4", 226 Locations: locations("traefik"), 227 Metadata: metadata("traefik-binary"), 228 }, 229 }, 230 { 231 logicalFixture: "memcached/1.6.18/linux-amd64", 232 expected: pkg.Package{ 233 Name: "memcached", 234 Version: "1.6.18", 235 Type: "binary", 236 PURL: "pkg:generic/memcached@1.6.18", 237 Locations: locations("memcached"), 238 Metadata: metadata("memcached-binary"), 239 }, 240 }, 241 { 242 logicalFixture: "httpd/2.4.54/linux-amd64", 243 expected: pkg.Package{ 244 Name: "httpd", 245 Version: "2.4.54", 246 Type: "binary", 247 PURL: "pkg:generic/httpd@2.4.54", 248 Locations: locations("httpd"), 249 Metadata: metadata("httpd-binary"), 250 }, 251 }, 252 { 253 // TODO: original binary is different than whats in config.yaml 254 // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo 255 logicalFixture: "perl/5.12.5/linux-amd64", 256 expected: pkg.Package{ 257 Name: "perl", 258 Version: "5.12.5", 259 Type: "binary", 260 PURL: "pkg:generic/perl@5.12.5", 261 Locations: locations("perl"), 262 Metadata: metadata("perl-binary"), 263 }, 264 }, 265 { 266 // TODO: original binary is different than whats in config.yaml 267 // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo 268 logicalFixture: "perl/5.20.0/linux-amd64", 269 expected: pkg.Package{ 270 Name: "perl", 271 Version: "5.20.0", 272 Type: "binary", 273 PURL: "pkg:generic/perl@5.20.0", 274 Locations: locations("perl"), 275 Metadata: metadata("perl-binary"), 276 }, 277 }, 278 { 279 // TODO: original binary is different than whats in config.yaml 280 // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo 281 logicalFixture: "perl/5.37.8/linux-amd64", 282 expected: pkg.Package{ 283 Name: "perl", 284 Version: "5.37.8", 285 Type: "binary", 286 PURL: "pkg:generic/perl@5.37.8", 287 Locations: locations("perl"), 288 Metadata: metadata("perl-binary"), 289 }, 290 }, 291 { 292 logicalFixture: "haproxy/1.5.14/linux-amd64", 293 expected: pkg.Package{ 294 Name: "haproxy", 295 Version: "1.5.14", 296 Type: "binary", 297 PURL: "pkg:generic/haproxy@1.5.14", 298 Locations: locations("haproxy"), 299 Metadata: metadata("haproxy-binary"), 300 }, 301 }, 302 { 303 logicalFixture: "haproxy/1.8.22/linux-amd64", 304 expected: pkg.Package{ 305 Name: "haproxy", 306 Version: "1.8.22", 307 Type: "binary", 308 PURL: "pkg:generic/haproxy@1.8.22", 309 Locations: locations("haproxy"), 310 Metadata: metadata("haproxy-binary"), 311 }, 312 }, 313 { 314 logicalFixture: "haproxy/2.0.0/linux-amd64", 315 expected: pkg.Package{ 316 Name: "haproxy", 317 Version: "2.0.0", 318 Type: "binary", 319 PURL: "pkg:generic/haproxy@2.0.0", 320 Locations: locations("haproxy"), 321 Metadata: metadata("haproxy-binary"), 322 }, 323 }, 324 { 325 logicalFixture: "haproxy/2.7.3/linux-amd64", 326 expected: pkg.Package{ 327 Name: "haproxy", 328 Version: "2.7.3", 329 Type: "binary", 330 PURL: "pkg:generic/haproxy@2.7.3", 331 Locations: locations("haproxy"), 332 Metadata: metadata("haproxy-binary"), 333 }, 334 }, 335 { 336 logicalFixture: "haproxy/3.1-dev0/linux-amd64", 337 expected: pkg.Package{ 338 Name: "haproxy", 339 Version: "3.1-dev0", 340 Type: "binary", 341 PURL: "pkg:generic/haproxy@3.1-dev0", 342 Locations: locations("haproxy"), 343 Metadata: metadata("haproxy-binary"), 344 }, 345 }, 346 { 347 logicalFixture: "helm/3.11.1/linux-amd64", 348 expected: pkg.Package{ 349 Name: "helm", 350 Version: "3.11.1", 351 Type: "binary", 352 PURL: "pkg:golang/helm.sh/helm@3.11.1", 353 Locations: locations("helm"), 354 Metadata: metadata("helm"), 355 }, 356 }, 357 { 358 logicalFixture: "helm/3.10.3/linux-amd64", 359 expected: pkg.Package{ 360 Name: "helm", 361 Version: "3.10.3", 362 Type: "binary", 363 PURL: "pkg:golang/helm.sh/helm@3.10.3", 364 Locations: locations("helm"), 365 Metadata: metadata("helm"), 366 }, 367 }, 368 369 { 370 // note: dynamic (non-snippet) test case 371 logicalFixture: "redis-server/2.8.23/linux-amd64", 372 expected: pkg.Package{ 373 Name: "redis", 374 Version: "2.8.23", 375 Type: "binary", 376 PURL: "pkg:generic/redis@2.8.23", 377 Locations: locations("redis-server"), 378 Metadata: metadata("redis-binary"), 379 }, 380 }, 381 { 382 // note: dynamic (non-snippet) test case 383 logicalFixture: "redis-server/4.0.11/linux-amd64", 384 expected: pkg.Package{ 385 Name: "redis", 386 Version: "4.0.11", 387 Type: "binary", 388 PURL: "pkg:generic/redis@4.0.11", 389 Locations: locations("redis-server"), 390 Metadata: metadata("redis-binary"), 391 }, 392 }, 393 { 394 logicalFixture: "redis-server/5.0.0/linux-amd64", 395 expected: pkg.Package{ 396 Name: "redis", 397 Version: "5.0.0", 398 Type: "binary", 399 PURL: "pkg:generic/redis@5.0.0", 400 Locations: locations("redis-server"), 401 Metadata: metadata("redis-binary"), 402 }, 403 }, 404 { 405 logicalFixture: "redis-server/6.0.16/linux-amd64", 406 expected: pkg.Package{ 407 Name: "redis", 408 Version: "6.0.16", 409 Type: "binary", 410 PURL: "pkg:generic/redis@6.0.16", 411 Locations: locations("redis-server"), 412 Metadata: metadata("redis-binary"), 413 }, 414 }, 415 { 416 logicalFixture: "redis-server/7.0.0/linux-amd64", 417 expected: pkg.Package{ 418 Name: "redis", 419 Version: "7.0.0", 420 Type: "binary", 421 PURL: "pkg:generic/redis@7.0.0", 422 Locations: locations("redis-server"), 423 Metadata: metadata("redis-binary"), 424 }, 425 }, 426 { 427 logicalFixture: "redis-server/7.0.14/linux-amd64", 428 expected: pkg.Package{ 429 Name: "redis", 430 Version: "7.0.14", 431 Type: "binary", 432 PURL: "pkg:generic/redis@7.0.14", 433 Locations: locations("redis-server"), 434 Metadata: metadata("redis-binary"), 435 }, 436 }, 437 { 438 // note: dynamic (non-snippet) test case 439 logicalFixture: "redis-server/7.2.3/linux-amd64", 440 expected: pkg.Package{ 441 Name: "redis", 442 Version: "7.2.3", 443 Type: "binary", 444 PURL: "pkg:generic/redis@7.2.3", 445 Locations: locations("redis-server"), 446 Metadata: metadata("redis-binary"), 447 }, 448 }, 449 { 450 // note: dynamic (non-snippet) test case 451 logicalFixture: "redis-server/7.2.3/linux-arm64", 452 expected: pkg.Package{ 453 Name: "redis", 454 Version: "7.2.3", 455 Type: "binary", 456 PURL: "pkg:generic/redis@7.2.3", 457 Locations: locations("redis-server"), 458 Metadata: metadata("redis-binary"), 459 }, 460 }, 461 { 462 logicalFixture: "redis-server/7.2.5/linux-386", 463 expected: pkg.Package{ 464 Name: "redis", 465 Version: "7.2.5", 466 Type: "binary", 467 PURL: "pkg:generic/redis@7.2.5", 468 Locations: locations("redis-server"), 469 Metadata: metadata("redis-binary"), 470 }, 471 }, 472 { 473 logicalFixture: "python-shared-lib/3.7.4/linux-amd64", 474 expected: pkg.Package{ 475 Name: "python", 476 Version: "3.7.4", 477 PURL: "pkg:generic/python@3.7.4", 478 Locations: locations("libpython3.7m.so.1.0"), 479 Metadata: metadata("python-binary-lib"), 480 }, 481 }, 482 483 { 484 // note: dynamic (non-snippet) test case 485 logicalFixture: "python-slim-shared-libs/3.11/linux-amd64", 486 expected: pkg.Package{ 487 Name: "python", 488 Version: "3.11.2", 489 PURL: "pkg:generic/python@3.11.2", 490 Locations: locations("python3.11", "libpython3.11.so.1.0"), 491 Metadata: pkg.BinarySignature{ 492 Matches: []pkg.ClassifierMatch{ 493 match("python-binary", "python3.11"), 494 match("python-binary", "libpython3.11.so.1.0"), 495 match("python-binary-lib", "libpython3.11.so.1.0"), 496 }, 497 }, 498 }, 499 }, 500 { 501 // note: dynamic (non-snippet) test case 502 logicalFixture: "python-rhel-shared-libs/3.9/linux-amd64", 503 expected: pkg.Package{ 504 Name: "python", 505 Version: "3.9.13", 506 PURL: "pkg:generic/python@3.9.13", 507 Locations: locations("python3.9", "libpython3.9.so.1.0"), 508 Metadata: pkg.BinarySignature{ 509 Matches: []pkg.ClassifierMatch{ 510 match("python-binary", "python3.9"), 511 match("python-binary", "libpython3.9.so.1.0"), 512 match("python-binary-lib", "libpython3.9.so.1.0"), 513 }, 514 }, 515 }, 516 }, 517 { 518 // note: dynamic (non-snippet) test case 519 logicalFixture: "python3.9/3.9.16/linux-amd64", 520 expected: pkg.Package{ 521 Name: "python", 522 Version: "3.9.2", 523 PURL: "pkg:generic/python@3.9.2", 524 Locations: locations("python3.9"), 525 Metadata: pkg.BinarySignature{ 526 Matches: []pkg.ClassifierMatch{ 527 match("python-binary", "python3.9"), 528 }, 529 }, 530 }, 531 }, 532 { 533 // note: dynamic (non-snippet) test case 534 logicalFixture: "python-alpine-shared-libs/3.4/linux-amd64", 535 expected: pkg.Package{ 536 Name: "python", 537 Version: "3.4.10", 538 PURL: "pkg:generic/python@3.4.10", 539 Locations: locations("python3.4", "libpython3.4m.so.1.0"), 540 Metadata: pkg.BinarySignature{ 541 Matches: []pkg.ClassifierMatch{ 542 match("python-binary", "python3.4"), 543 match("python-binary", "libpython3.4m.so.1.0"), 544 match("python-binary-lib", "libpython3.4m.so.1.0"), 545 }, 546 }, 547 }, 548 }, 549 { 550 // TODO: find original binary... 551 // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo 552 logicalFixture: "python-with-incorrect-match/3.5.3/linux-amd64", 553 expected: pkg.Package{ 554 Name: "python", 555 Version: "3.5.3", 556 PURL: "pkg:generic/python@3.5.3", 557 Locations: locations("python3.5"), 558 Metadata: metadata("python-binary"), 559 }, 560 }, 561 { 562 // TODO: find original binary... 563 // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo 564 logicalFixture: "python/3.6.3/linux-amd64", 565 expected: pkg.Package{ 566 Name: "python", 567 Version: "3.6.3", 568 PURL: "pkg:generic/python@3.6.3", 569 Locations: locations("python3.6"), 570 Metadata: metadata("python-binary"), 571 }, 572 }, 573 { 574 // TODO: find original binary... 575 // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo 576 logicalFixture: "python-duplicates/3.8.16/linux-amd64", 577 expected: pkg.Package{ 578 Name: "python", 579 Version: "3.8.16", 580 Type: "binary", 581 PURL: "pkg:generic/python@3.8.16", 582 Locations: locations("dir/python3.8", "python3.8", "libpython3.8.so"), 583 Metadata: pkg.BinarySignature{ 584 Matches: []pkg.ClassifierMatch{ 585 match("python-binary", "dir/python3.8"), 586 match("python-binary", "python3.8"), 587 match("python-binary-lib", "libpython3.8.so"), 588 }, 589 }, 590 }, 591 }, 592 { 593 logicalFixture: "pypy-shared-lib/7.3.14/linux-amd64", 594 expected: pkg.Package{ 595 Name: "pypy", 596 Version: "7.3.14", 597 PURL: "pkg:generic/pypy@7.3.14", 598 Locations: locations("libpypy3.9-c.so"), 599 Metadata: metadata("pypy-binary-lib"), 600 }, 601 }, 602 { 603 logicalFixture: "go/1.21.3/linux-amd64", 604 expected: pkg.Package{ 605 Name: "go", 606 Version: "1.21.3", 607 PURL: "pkg:generic/go@1.21.3", 608 Locations: locations("go"), 609 Metadata: metadata("go-binary"), 610 }, 611 }, 612 { 613 logicalFixture: "node/0.10.48/linux-amd64", 614 expected: pkg.Package{ 615 Name: "node", 616 Version: "0.10.48", 617 PURL: "pkg:generic/node@0.10.48", 618 Locations: locations("node"), 619 Metadata: metadata("nodejs-binary"), 620 }, 621 }, 622 { 623 logicalFixture: "node/0.12.18/linux-amd64", 624 expected: pkg.Package{ 625 Name: "node", 626 Version: "0.12.18", 627 PURL: "pkg:generic/node@0.12.18", 628 Locations: locations("node"), 629 Metadata: metadata("nodejs-binary"), 630 }, 631 }, 632 { 633 logicalFixture: "node/4.9.1/linux-amd64", 634 expected: pkg.Package{ 635 Name: "node", 636 Version: "4.9.1", 637 PURL: "pkg:generic/node@4.9.1", 638 Locations: locations("node"), 639 Metadata: metadata("nodejs-binary"), 640 }, 641 }, 642 { 643 logicalFixture: "node/19.2.0/linux-amd64", 644 expected: pkg.Package{ 645 Name: "node", 646 Version: "19.2.0", 647 PURL: "pkg:generic/node@19.2.0", 648 Locations: locations("node"), 649 Metadata: metadata("nodejs-binary"), 650 }, 651 }, 652 { 653 // TODO: find original binary... 654 // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo 655 logicalFixture: "go-version-hint/1.15/any", 656 expected: pkg.Package{ 657 Name: "go", 658 Version: "1.15", 659 PURL: "pkg:generic/go@1.15", 660 Locations: locations("VERSION"), 661 Metadata: metadata("go-binary-hint"), 662 }, 663 }, 664 { 665 // note: this is for compatability with dev version of golang tip image, which resolves the issue #3681 666 logicalFixture: "go-version-hint/1.25/any", 667 expected: pkg.Package{ 668 Name: "go", 669 Version: "1.25-d524e1e", 670 PURL: "pkg:generic/go@1.25-d524e1e", 671 Locations: locations("VERSION.cache"), 672 Metadata: metadata("go-binary-hint"), 673 }, 674 }, 675 { 676 // note: this is testing BUSYBOX which is typically through a link to "[" (in this case a symlink but in 677 // practice this is often a hard link). 678 logicalFixture: `busybox/1.36.1/linux-amd64`, 679 expected: pkg.Package{ 680 Name: "busybox", 681 Version: "1.36.1", 682 PURL: "pkg:generic/busybox@1.36.1", 683 Locations: locations("["), // note: busybox is a link to [ 684 Metadata: metadata("busybox-binary", "[", "busybox"), 685 }, 686 }, 687 { 688 logicalFixture: `util-linux/2.37.4/linux-amd64`, 689 expected: pkg.Package{ 690 Name: "util-linux", 691 Version: "2.37.4", 692 PURL: "pkg:generic/util-linux@2.37.4", 693 Locations: locations("getopt"), 694 Metadata: metadata("util-linux-binary"), 695 }, 696 }, 697 { 698 logicalFixture: "java-jre-openjdk/1.8.0_352-b08/linux-amd64", 699 expected: pkg.Package{ 700 Name: "openjdk", 701 Version: "1.8.0_352-b08", 702 Type: "binary", 703 PURL: "pkg:generic/oracle/openjdk@1.8.0_352-b08", 704 Locations: locations("java"), 705 Metadata: metadata("java-binary-openjdk-with-update", "java"), 706 }, 707 }, 708 { 709 logicalFixture: "java-jre-openjdk/11.0.17/linux-amd64", 710 expected: pkg.Package{ 711 Name: "openjdk", 712 Version: "11.0.17+8-LTS", 713 Type: "binary", 714 PURL: "pkg:generic/oracle/openjdk@11.0.17%2B8-LTS", 715 Locations: locations("java"), 716 Metadata: metadata("java-binary-openjdk", "java"), 717 }, 718 }, 719 { 720 logicalFixture: "java-jre-openjdk-eclipse/11.0.22/linux-amd64", 721 expected: pkg.Package{ 722 Name: "openjdk", 723 Version: "11.0.22+7", 724 Type: "binary", 725 PURL: "pkg:generic/oracle/openjdk@11.0.22%2B7", 726 Locations: locations("java"), 727 Metadata: metadata("java-binary-openjdk", "java"), 728 }, 729 }, 730 { 731 logicalFixture: "java-jre-openjdk-arm64-eclipse/11.0.22/linux-arm64", 732 expected: pkg.Package{ 733 Name: "openjdk", 734 Version: "11.0.22+7", 735 Type: "binary", 736 PURL: "pkg:generic/oracle/openjdk@11.0.22%2B7", 737 Locations: locations("java"), 738 Metadata: metadata("java-binary-openjdk", "java"), 739 }, 740 }, 741 { 742 logicalFixture: "java-graal-openjdk/17.0.3+7-jvmci-22.1-b06/linux-amd64", 743 expected: pkg.Package{ 744 Name: "graalvm", 745 Version: "17.0.3+7-jvmci-22.1-b06", 746 Type: "binary", 747 PURL: "pkg:generic/oracle/graalvm@17.0.3%2B7-jvmci-22.1-b06", 748 Locations: locations("java"), 749 Metadata: metadata("java-binary-graalvm", "java"), 750 }, 751 }, 752 { 753 // TODO: find original binary... 754 // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo 755 logicalFixture: "java-jre-oracle/19.0.1/linux-amd64", 756 expected: pkg.Package{ 757 Name: "jre", 758 Version: "19.0.1+10-21", 759 Type: "binary", 760 PURL: "pkg:generic/oracle/jre@19.0.1%2B10-21", 761 Locations: locations("java"), 762 Metadata: metadata("java-binary-oracle", "java"), 763 }, 764 }, 765 { 766 // TODO: find original binary... 767 // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo 768 logicalFixture: "java-jre-oracle/19.0.1/darwin", 769 expected: pkg.Package{ 770 Name: "jre", 771 Version: "19.0.1+10-21", 772 Type: "binary", 773 PURL: "pkg:generic/oracle/jre@19.0.1%2B10-21", 774 Locations: locations("java"), 775 Metadata: metadata("java-binary-oracle", "java"), 776 }, 777 }, 778 { 779 logicalFixture: "java-jdk-openjdk/21.0.2+13-LTS/linux-amd64", 780 expected: pkg.Package{ 781 Name: "openjdk", 782 Version: "21.0.2+13-LTS", 783 Type: "binary", 784 PURL: "pkg:generic/oracle/openjdk@21.0.2%2B13-LTS", 785 Locations: locations("jdb"), 786 Metadata: metadata("java-binary-openjdk-fallthrough", "jdb"), 787 }, 788 }, 789 { 790 logicalFixture: "rust-libstd/1.50.0/linux-amd64", 791 expected: pkg.Package{ 792 Name: "rust", 793 Version: "1.50.0", 794 Type: "binary", 795 PURL: "pkg:generic/rust@1.50.0", 796 Locations: locations("libstd-6f77337c1826707d.so"), 797 Metadata: metadata("rust-standard-library-linux"), 798 }, 799 }, 800 { 801 // TODO: find original binary... 802 // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo 803 logicalFixture: "rust-libstd/1.50.0/darwin", 804 expected: pkg.Package{ 805 Name: "rust", 806 Version: "1.50.0", 807 Type: "binary", 808 PURL: "pkg:generic/rust@1.50.0", 809 Locations: locations("libstd-f6f9eec1635e636a.dylib"), 810 Metadata: metadata("rust-standard-library-macos"), 811 }, 812 }, 813 { 814 // TODO: find original binary... 815 // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo 816 logicalFixture: "rust-libstd/1.67.1/darwin", 817 expected: pkg.Package{ 818 Name: "rust", 819 Version: "1.67.1", 820 Type: "binary", 821 PURL: "pkg:generic/rust@1.67.1", 822 Locations: locations("libstd-16f2b65e77054c42.dylib"), 823 Metadata: metadata("rust-standard-library-macos"), 824 }, 825 }, 826 { 827 logicalFixture: "rust-libstd-musl/1.67.1/linux-amd64", 828 expected: pkg.Package{ 829 Name: "rust", 830 Version: "1.67.1", 831 Type: "binary", 832 PURL: "pkg:generic/rust@1.67.1", 833 Locations: locations("libstd-86aefecbddda356d.so"), 834 Metadata: metadata("rust-standard-library-linux"), 835 }, 836 }, 837 { 838 logicalFixture: "rust-libstd/1.67.1/linux-amd64", 839 expected: pkg.Package{ 840 Name: "rust", 841 Version: "1.67.1", 842 Type: "binary", 843 PURL: "pkg:generic/rust@1.67.1", 844 Locations: locations("libstd-c6192dd4c4d410ac.so"), 845 Metadata: metadata("rust-standard-library-linux"), 846 }, 847 }, 848 { 849 // note: dynamic (non-snippet) test case 850 851 name: "positive-ruby-3.2.1", 852 logicalFixture: "ruby-bullseye-shared-libs/3.2.1/linux-amd64", 853 expected: pkg.Package{ 854 Name: "ruby", 855 Version: "3.2.1", 856 Type: "binary", 857 PURL: "pkg:generic/ruby@3.2.1", 858 Locations: locations("ruby", "libruby.so.3.2.1"), 859 Metadata: pkg.BinarySignature{ 860 Matches: []pkg.ClassifierMatch{ 861 match("ruby-binary", "ruby"), 862 match("ruby-binary", "libruby.so.3.2.1"), 863 }, 864 }, 865 }, 866 }, 867 { 868 // note: dynamic (non-snippet) test case 869 name: "positive-ruby-3.4.0-dev", 870 logicalFixture: "ruby-shared-libs/3.4.0-dev/linux-amd64", 871 expected: pkg.Package{ 872 Name: "ruby", 873 Version: "3.4.0dev", 874 Type: "binary", 875 PURL: "pkg:generic/ruby@3.4.0dev", 876 Locations: locations("ruby", "libruby.so.3.4.0"), 877 Metadata: pkg.BinarySignature{ 878 Matches: []pkg.ClassifierMatch{ 879 match("ruby-binary", "ruby"), 880 match("ruby-binary", "libruby.so.3.4.0"), 881 }, 882 }, 883 }, 884 }, 885 { 886 // note: dynamic (non-snippet) test case 887 name: "positive-ruby-3.4.0-preview1", 888 logicalFixture: "ruby-shared-libs/3.4.0-preview1/linux-amd64", 889 expected: pkg.Package{ 890 Name: "ruby", 891 Version: "3.4.0preview1", 892 Type: "binary", 893 PURL: "pkg:generic/ruby@3.4.0preview1", 894 Locations: locations("ruby", "libruby.so.3.4.0"), 895 Metadata: pkg.BinarySignature{ 896 Matches: []pkg.ClassifierMatch{ 897 match("ruby-binary", "ruby"), 898 match("ruby-binary", "libruby.so.3.4.0"), 899 }, 900 }, 901 }, 902 }, 903 { 904 // note: dynamic (non-snippet) test case 905 name: "positive-ruby-3.3.0-rc1", 906 logicalFixture: "ruby-shared-libs/3.3.0-rc1/linux-amd64", 907 expected: pkg.Package{ 908 Name: "ruby", 909 Version: "3.3.0rc1", 910 Type: "binary", 911 PURL: "pkg:generic/ruby@3.3.0rc1", 912 Locations: locations("ruby", "libruby.so.3.3.0"), 913 Metadata: pkg.BinarySignature{ 914 Matches: []pkg.ClassifierMatch{ 915 match("ruby-binary", "ruby"), 916 match("ruby-binary", "libruby.so.3.3.0"), 917 }, 918 }, 919 }, 920 }, 921 { 922 // note: dynamic (non-snippet) test case 923 logicalFixture: "ruby-bullseye-shared-libs/2.7.7/linux-amd64", 924 expected: pkg.Package{ 925 Name: "ruby", 926 Version: "2.7.7p221", 927 Type: "binary", 928 PURL: "pkg:generic/ruby@2.7.7p221", 929 Locations: locations("ruby", "libruby.so.2.7.7"), 930 Metadata: pkg.BinarySignature{ 931 Matches: []pkg.ClassifierMatch{ 932 match("ruby-binary", "ruby"), 933 match("ruby-binary", "libruby.so.2.7.7"), 934 }, 935 }, 936 }, 937 }, 938 { 939 // note: dynamic (non-snippet) test case 940 logicalFixture: "ruby-shared-libs/2.6.10/linux-amd64", 941 expected: pkg.Package{ 942 Name: "ruby", 943 Version: "2.6.10p210", 944 Type: "binary", 945 PURL: "pkg:generic/ruby@2.6.10p210", 946 Locations: locations("ruby", "libruby.so.2.6.10"), 947 Metadata: pkg.BinarySignature{ 948 Matches: []pkg.ClassifierMatch{ 949 match("ruby-binary", "ruby"), 950 match("ruby-binary", "libruby.so.2.6.10"), 951 }, 952 }, 953 }, 954 }, 955 { 956 logicalFixture: "ruby/1.9.3p551/linux-amd64", 957 expected: pkg.Package{ 958 Name: "ruby", 959 Version: "1.9.3p551", 960 Type: "binary", 961 PURL: "pkg:generic/ruby@1.9.3p551", 962 Locations: locations("ruby"), 963 Metadata: metadata("ruby-binary"), 964 }, 965 }, 966 { 967 logicalFixture: "consul/1.15.2/linux-amd64", 968 expected: pkg.Package{ 969 Name: "consul", 970 Version: "1.15.2", 971 Type: "binary", 972 PURL: "pkg:golang/github.com/hashicorp/consul@1.15.2", 973 Locations: locations("consul"), 974 Metadata: metadata("consul-binary"), 975 }, 976 }, 977 { 978 logicalFixture: "vault/1.20.2/linux-amd64", 979 expected: pkg.Package{ 980 Name: "github.com/hashicorp/vault", 981 Version: "1.20.2", 982 Type: "golang", 983 PURL: "pkg:golang/github.com/hashicorp/vault@1.20.2", 984 Locations: locations("vault"), 985 Metadata: metadata("hashicorp-vault-binary"), 986 }, 987 }, 988 { 989 logicalFixture: "vault/1.19.4/linux-arm64", 990 expected: pkg.Package{ 991 Name: "github.com/hashicorp/vault", 992 Version: "1.19.4", 993 Type: "golang", 994 PURL: "pkg:golang/github.com/hashicorp/vault@1.19.4", 995 Locations: locations("vault"), 996 Metadata: metadata("hashicorp-vault-binary"), 997 }, 998 }, 999 { 1000 logicalFixture: "erlang/25.3.2.6/linux-amd64", 1001 expected: pkg.Package{ 1002 Name: "erlang", 1003 Version: "25.3.2.6", 1004 Type: "binary", 1005 PURL: "pkg:generic/erlang@25.3.2.6", 1006 Locations: locations("erlexec"), 1007 Metadata: metadata("erlang-binary"), 1008 }, 1009 }, 1010 { 1011 logicalFixture: "erlang/26.2.0.0/linux-amd64", 1012 expected: pkg.Package{ 1013 Name: "erlang", 1014 Version: "26.2", 1015 Type: "binary", 1016 PURL: "pkg:generic/erlang@26.2", 1017 Locations: locations("erlexec"), 1018 Metadata: metadata("erlang-binary"), 1019 }, 1020 }, 1021 { 1022 logicalFixture: "erlang/26.2.4/linux-amd64", 1023 expected: pkg.Package{ 1024 Name: "erlang", 1025 Version: "26.2.4", 1026 Type: "binary", 1027 PURL: "pkg:generic/erlang@26.2.4", 1028 Locations: locations("liberts_internal.a"), 1029 Metadata: metadata("erlang-library"), 1030 }, 1031 }, 1032 { 1033 logicalFixture: "erlang/27.0/linux-amd64", 1034 expected: pkg.Package{ 1035 Name: "erlang", 1036 Version: "27.0", 1037 Type: "binary", 1038 PURL: "pkg:generic/erlang@27.0", 1039 Locations: locations("beam.smp"), 1040 Metadata: metadata("erlang-alpine-binary"), 1041 }, 1042 }, 1043 { 1044 logicalFixture: "erlang/26.1.2/linux-arm64", 1045 expected: pkg.Package{ 1046 Name: "erlang", 1047 Version: "26.1.2", 1048 Type: "binary", 1049 PURL: "pkg:generic/erlang@26.1.2", 1050 Locations: locations("beam.smp"), 1051 Metadata: metadata("erlang-alpine-binary"), 1052 }, 1053 }, 1054 { 1055 logicalFixture: "swipl/9.3.8/linux-amd64", 1056 expected: pkg.Package{ 1057 Name: "swipl", 1058 Version: "9.3.8", 1059 Type: "binary", 1060 PURL: "pkg:generic/swipl@9.3.8", 1061 Locations: locations("swipl"), 1062 Metadata: metadata("swipl-binary"), 1063 }, 1064 }, 1065 { 1066 logicalFixture: "dart/2.12.4/linux-amd64", 1067 expected: pkg.Package{ 1068 Name: "dart", 1069 Version: "2.12.4", 1070 Type: "binary", 1071 PURL: "pkg:generic/dart@2.12.4", 1072 Locations: locations("dart"), 1073 Metadata: metadata("dart-binary"), 1074 }, 1075 }, 1076 { 1077 logicalFixture: "dart/3.0.0/linux-arm", 1078 expected: pkg.Package{ 1079 Name: "dart", 1080 Version: "3.0.0", 1081 Type: "binary", 1082 PURL: "pkg:generic/dart@3.0.0", 1083 Locations: locations("dart"), 1084 Metadata: metadata("dart-binary"), 1085 }, 1086 }, 1087 { 1088 logicalFixture: "dart/3.5.2/linux-amd64", 1089 expected: pkg.Package{ 1090 Name: "dart", 1091 Version: "3.5.2", 1092 Type: "binary", 1093 PURL: "pkg:generic/dart@3.5.2", 1094 Locations: locations("dart"), 1095 Metadata: metadata("dart-binary"), 1096 }, 1097 }, 1098 { 1099 logicalFixture: "dart/3.6.0-216.1.beta/linux-amd64", 1100 expected: pkg.Package{ 1101 Name: "dart", 1102 Version: "3.6.0-216.1.beta", 1103 Type: "binary", 1104 PURL: "pkg:generic/dart@3.6.0-216.1.beta", 1105 Locations: locations("dart"), 1106 Metadata: metadata("dart-binary"), 1107 }, 1108 }, 1109 { 1110 logicalFixture: "haskell-ghc/9.6.5/linux-amd64", 1111 expected: pkg.Package{ 1112 Name: "haskell/ghc", 1113 Version: "9.6.5", 1114 Type: "binary", 1115 PURL: "pkg:generic/haskell/ghc@9.6.5", 1116 Locations: locations("ghc-9.6.5"), 1117 Metadata: metadata("haskell-ghc-binary"), 1118 }, 1119 }, 1120 { 1121 logicalFixture: "haskell-cabal/3.10.3.0/linux-amd64", 1122 expected: pkg.Package{ 1123 Name: "haskell/cabal", 1124 Version: "3.10.3.0", 1125 Type: "binary", 1126 PURL: "pkg:generic/haskell/cabal@3.10.3.0", 1127 Locations: locations("cabal"), 1128 Metadata: metadata("haskell-cabal-binary"), 1129 }, 1130 }, 1131 { 1132 logicalFixture: "nginx/1.25.1/linux-amd64", 1133 expected: pkg.Package{ 1134 Name: "nginx", 1135 Version: "1.25.1", 1136 Type: "binary", 1137 PURL: "pkg:generic/nginx@1.25.1", 1138 Locations: locations("nginx"), 1139 Metadata: metadata("nginx-binary"), 1140 }, 1141 }, 1142 { 1143 logicalFixture: "nginx-openresty/1.21.4.3/linux-amd64", 1144 expected: pkg.Package{ 1145 Name: "nginx", 1146 Version: "1.21.4", 1147 Type: "binary", 1148 PURL: "pkg:generic/nginx@1.21.4", 1149 Locations: locations("nginx"), 1150 Metadata: metadata("nginx-binary"), 1151 }, 1152 }, 1153 { 1154 logicalFixture: "bash/5.1.16/linux-amd64", 1155 expected: pkg.Package{ 1156 Name: "bash", 1157 Version: "5.1.16", 1158 Type: "binary", 1159 PURL: "pkg:generic/bash@5.1.16", 1160 Locations: locations("bash"), 1161 Metadata: metadata("bash-binary"), 1162 }, 1163 }, 1164 { 1165 logicalFixture: "openssl/3.1.4/linux-amd64", 1166 expected: pkg.Package{ 1167 Name: "openssl", 1168 Version: "3.1.4", 1169 Type: "binary", 1170 PURL: "pkg:generic/openssl@3.1.4", 1171 Locations: locations("openssl"), 1172 Metadata: metadata("openssl-binary"), 1173 }, 1174 }, 1175 { 1176 logicalFixture: "openssl/1.1.1w/linux-arm64", 1177 expected: pkg.Package{ 1178 Name: "openssl", 1179 Version: "1.1.1w", 1180 Type: "binary", 1181 PURL: "pkg:generic/openssl@1.1.1w", 1182 Locations: locations("openssl"), 1183 Metadata: metadata("openssl-binary"), 1184 }, 1185 }, 1186 { 1187 logicalFixture: "openssl/1.1.1zb/linux-arm64", 1188 expected: pkg.Package{ 1189 Name: "openssl", 1190 Version: "1.1.1zb", 1191 Type: "binary", 1192 PURL: "pkg:generic/openssl@1.1.1zb", 1193 Locations: locations("openssl"), 1194 Metadata: metadata("openssl-binary"), 1195 }, 1196 }, 1197 { 1198 logicalFixture: "gcc/12.3.0/linux-amd64", 1199 expected: pkg.Package{ 1200 Name: "gcc", 1201 Version: "12.3.0", 1202 Type: "binary", 1203 PURL: "pkg:generic/gcc@12.3.0", 1204 Locations: locations("gcc"), 1205 Metadata: metadata("gcc-binary"), 1206 }, 1207 }, 1208 { 1209 logicalFixture: "fluent-bit/3.0.2/linux-amd64", 1210 expected: pkg.Package{ 1211 Name: "fluent-bit", 1212 Version: "3.0.2", 1213 Type: "binary", 1214 PURL: "pkg:github/fluent/fluent-bit@3.0.2", 1215 Locations: locations("fluent-bit"), 1216 Metadata: metadata("fluent-bit-binary"), 1217 }, 1218 }, 1219 { 1220 logicalFixture: "fluent-bit/2.2.1/linux-arm64", 1221 expected: pkg.Package{ 1222 Name: "fluent-bit", 1223 Version: "2.2.1", 1224 Type: "binary", 1225 PURL: "pkg:github/fluent/fluent-bit@2.2.1", 1226 Locations: locations("fluent-bit"), 1227 Metadata: metadata("fluent-bit-binary"), 1228 }, 1229 }, 1230 { 1231 logicalFixture: "fluent-bit/1.7.0-dev-3/linux-amd64", 1232 expected: pkg.Package{ 1233 Name: "fluent-bit", 1234 Version: "1.7.0", 1235 Type: "binary", 1236 PURL: "pkg:github/fluent/fluent-bit@1.7.0", 1237 Locations: locations("fluent-bit"), 1238 Metadata: metadata("fluent-bit-binary"), 1239 }, 1240 }, 1241 { 1242 logicalFixture: "fluent-bit/1.3.10/linux-arm", 1243 expected: pkg.Package{ 1244 Name: "fluent-bit", 1245 Version: "1.3.10", 1246 Type: "binary", 1247 PURL: "pkg:github/fluent/fluent-bit@1.3.10", 1248 Locations: locations("fluent-bit"), 1249 Metadata: metadata("fluent-bit-binary"), 1250 }, 1251 }, 1252 { 1253 logicalFixture: "wp/2.9.0/linux-amd64", 1254 expected: pkg.Package{ 1255 Name: "wp-cli", 1256 Version: "2.9.0", 1257 Type: "binary", 1258 PURL: "pkg:generic/wp-cli@2.9.0", 1259 Locations: locations("wp"), 1260 Metadata: metadata("wordpress-cli-binary"), 1261 }, 1262 }, 1263 { 1264 logicalFixture: "lighttpd/1.4.76/linux-amd64", 1265 expected: pkg.Package{ 1266 Name: "lighttpd", 1267 Version: "1.4.76", 1268 Type: "binary", 1269 PURL: "pkg:generic/lighttpd@1.4.76", 1270 Locations: locations("lighttpd"), 1271 Metadata: metadata("lighttpd-binary"), 1272 }, 1273 }, 1274 { 1275 logicalFixture: "proftpd/1.3.8b/linux-amd64", 1276 expected: pkg.Package{ 1277 Name: "proftpd", 1278 Version: "1.3.8b", 1279 Type: "binary", 1280 PURL: "pkg:generic/proftpd@1.3.8b", 1281 Locations: locations("proftpd"), 1282 Metadata: metadata("proftpd-binary"), 1283 }, 1284 }, 1285 { 1286 logicalFixture: "zstd/1.5.6/linux-amd64", 1287 expected: pkg.Package{ 1288 Name: "zstd", 1289 Version: "1.5.6", 1290 Type: "binary", 1291 PURL: "pkg:generic/zstd@1.5.6", 1292 Locations: locations("zstd"), 1293 Metadata: metadata("zstd-binary"), 1294 }, 1295 }, 1296 { 1297 logicalFixture: "zstd/1.5.6/linux-amd64", 1298 expected: pkg.Package{ 1299 Name: "zstd", 1300 Version: "1.5.6", 1301 Type: "binary", 1302 PURL: "pkg:generic/zstd@1.5.6", 1303 Locations: locations("zstd"), 1304 Metadata: metadata("zstd-binary"), 1305 }, 1306 }, 1307 { 1308 logicalFixture: "xz/5.6.2/linux-amd64", 1309 expected: pkg.Package{ 1310 Name: "xz", 1311 Version: "5.6.2", 1312 Type: "binary", 1313 PURL: "pkg:generic/xz@5.6.2", 1314 Locations: locations("xz"), 1315 Metadata: metadata("xz-binary"), 1316 }, 1317 }, 1318 { 1319 logicalFixture: "gzip/1.12/linux-amd64", 1320 expected: pkg.Package{ 1321 Name: "gzip", 1322 Version: "1.12", 1323 Type: "binary", 1324 PURL: "pkg:generic/gzip@1.12", 1325 Locations: locations("gzip"), 1326 Metadata: metadata("gzip-binary"), 1327 }, 1328 }, 1329 { 1330 logicalFixture: "sqlcipher/4.5.5/linux-amd64", 1331 expected: pkg.Package{ 1332 Name: "sqlcipher", 1333 Version: "4.5.5", 1334 Type: "binary", 1335 PURL: "pkg:generic/sqlcipher@4.5.5", 1336 Locations: locations("sqlcipher"), 1337 Metadata: metadata("sqlcipher-binary"), 1338 }, 1339 }, 1340 { 1341 logicalFixture: "jq/1.7.1/linux-amd64", 1342 expected: pkg.Package{ 1343 Name: "jq", 1344 Version: "1.7.1", 1345 Type: "binary", 1346 PURL: "pkg:generic/jq@1.7.1", 1347 Locations: locations("jq"), 1348 Metadata: metadata("jq-binary"), 1349 }, 1350 }, 1351 { 1352 logicalFixture: "chrome/126.0.6478.182/linux-amd64", 1353 expected: pkg.Package{ 1354 Name: "chrome", 1355 Version: "126.0.6478.182", 1356 Type: "binary", 1357 PURL: "pkg:generic/chrome@126.0.6478.182", 1358 Locations: locations("chrome"), 1359 Metadata: metadata("chrome-binary"), 1360 }, 1361 }, 1362 { 1363 logicalFixture: "chrome/127.0.6533.119/linux-amd64", 1364 expected: pkg.Package{ 1365 Name: "chrome", 1366 Version: "127.0.6533.119", 1367 Type: "binary", 1368 PURL: "pkg:generic/chrome@127.0.6533.119", 1369 Locations: locations("chrome"), 1370 Metadata: metadata("chrome-binary"), 1371 }, 1372 }, 1373 { 1374 logicalFixture: "ffmpeg/7.1.1/darwin-arm64", 1375 expected: pkg.Package{ 1376 Name: "ffmpeg", 1377 Version: "7.1.1", 1378 Type: "binary", 1379 PURL: "pkg:generic/ffmpeg@7.1.1", 1380 Locations: locations("ffmpeg"), 1381 Metadata: metadata("ffmpeg-binary"), 1382 }, 1383 }, 1384 { 1385 logicalFixture: "ffmpeg/6.1.1/linux-amd64", 1386 expected: pkg.Package{ 1387 Name: "ffmpeg", 1388 Version: "6.1.1", 1389 Type: "binary", 1390 PURL: "pkg:generic/ffmpeg@6.1.1", 1391 Locations: locations("ffmpeg"), 1392 Metadata: metadata("ffmpeg-binary"), 1393 }, 1394 }, 1395 { 1396 logicalFixture: "ffmpeg-shared-libs/5.1.4/linux-amd64", 1397 expected: pkg.Package{ 1398 Name: "ffmpeg", 1399 Version: "5.1.4", 1400 Type: "binary", 1401 PURL: "pkg:generic/ffmpeg@5.1.4", 1402 Locations: locations("libavcodec-9aae324f.so.59.37.100"), 1403 Metadata: metadata("ffmpeg-library"), 1404 }, 1405 }, 1406 { 1407 logicalFixture: "elixir/1.19.1/linux-amd64", 1408 expected: pkg.Package{ 1409 Name: "elixir", 1410 Version: "1.19.1", 1411 Type: "binary", 1412 PURL: "pkg:generic/elixir@1.19.1", 1413 Locations: locations("elixir", "lib/elixir/ebin/elixir.app"), 1414 Metadata: pkg.BinarySignature{ 1415 Matches: []pkg.ClassifierMatch{ 1416 match("elixir-binary", "elixir"), 1417 match("elixir-library", "lib/elixir/ebin/elixir.app"), 1418 }, 1419 }, 1420 }, 1421 }, 1422 } 1423 1424 for _, test := range tests { 1425 t.Run(test.logicalFixture, func(t *testing.T) { 1426 c := NewClassifierCataloger(DefaultClassifierCatalogerConfig()) 1427 1428 // logicalFixture is the logical path to the full binary or snippet. This is relative to the test-fixtures/classifiers/snippets 1429 // or test-fixtures/classifiers/bin directory . Snippets are searched for first, and if not found, then existing binaries are 1430 // used. If no binary or snippet is found the test will fail. If '-must-use-original-binaries' is used the only 1431 // full binaries are tested (no snippets), and if no binary is found the test will be skipped. 1432 path := testutil.SnippetOrBinary(t, test.logicalFixture, *mustUseOriginalBinaries) 1433 1434 src, err := directorysource.NewFromPath(path) 1435 require.NoError(t, err) 1436 1437 resolver, err := src.FileResolver(source.SquashedScope) 1438 require.NoError(t, err) 1439 1440 packages, _, err := c.Catalog(context.Background(), resolver) 1441 require.NoError(t, err) 1442 1443 require.Len(t, packages, 1, "mismatched package count") 1444 1445 assertPackagesAreEqual(t, test.expected, packages[0]) 1446 }) 1447 } 1448 } 1449 1450 func Test_Cataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T) { 1451 tests := []struct { 1452 name string 1453 fixtureImage string 1454 expected pkg.Package 1455 }{ 1456 { 1457 name: "busybox-regression", 1458 fixtureImage: "image-busybox", 1459 expected: pkg.Package{ 1460 Name: "busybox", 1461 Version: "1.35.0", 1462 PURL: "pkg:generic/busybox@1.35.0", 1463 Locations: locations("/bin/["), 1464 Metadata: metadata("busybox-binary", "/bin/[", "/bin/busybox"), 1465 }, 1466 }, 1467 } 1468 1469 for _, test := range tests { 1470 t.Run(test.name, func(t *testing.T) { 1471 c := NewClassifierCataloger(DefaultClassifierCatalogerConfig()) 1472 1473 img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureImage) 1474 src := stereoscopesource.New(img, stereoscopesource.ImageConfig{ 1475 Reference: test.fixtureImage, 1476 }) 1477 1478 resolver, err := src.FileResolver(source.SquashedScope) 1479 require.NoError(t, err) 1480 1481 packages, _, err := c.Catalog(context.Background(), resolver) 1482 require.NoError(t, err) 1483 1484 for _, p := range packages { 1485 expectedLocations := test.expected.Locations.ToSlice() 1486 gotLocations := p.Locations.ToSlice() 1487 require.Len(t, gotLocations, len(expectedLocations)) 1488 1489 for i, expectedLocation := range expectedLocations { 1490 gotLocation := gotLocations[i] 1491 if expectedLocation.RealPath != gotLocation.RealPath { 1492 t.Fatalf("locations do not match; expected: %v got: %v", expectedLocations, gotLocations) 1493 } 1494 } 1495 1496 assertPackagesAreEqual(t, test.expected, p) 1497 } 1498 }) 1499 } 1500 } 1501 1502 func TestClassifierCataloger_DefaultClassifiers_NegativeCases(t *testing.T) { 1503 c := NewClassifierCataloger(DefaultClassifierCatalogerConfig()) 1504 1505 src, err := directorysource.NewFromPath("test-fixtures/classifiers/negative") 1506 assert.NoError(t, err) 1507 1508 resolver, err := src.FileResolver(source.SquashedScope) 1509 assert.NoError(t, err) 1510 1511 actualResults, _, err := c.Catalog(context.Background(), resolver) 1512 assert.NoError(t, err) 1513 assert.Equal(t, 0, len(actualResults)) 1514 } 1515 1516 func Test_Cataloger_CustomClassifiers(t *testing.T) { 1517 defaultClassifers := DefaultClassifiers() 1518 1519 golangExpected := pkg.Package{ 1520 Name: "go", 1521 Version: "1.14", 1522 PURL: "pkg:generic/go@1.14", 1523 Locations: locations("go"), 1524 Metadata: metadata("go-binary"), 1525 } 1526 customExpected := pkg.Package{ 1527 Name: "foo", 1528 Version: "1.2.3", 1529 PURL: "pkg:generic/foo@1.2.3", 1530 Locations: locations("foo"), 1531 Metadata: metadata("foo-binary"), 1532 } 1533 fooClassifier := binutils.Classifier{ 1534 Class: "foo-binary", 1535 FileGlob: "**/foo", 1536 EvidenceMatcher: binutils.FileContentsVersionMatcher( 1537 catalogerName, 1538 `(?m)foobar\s(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`, 1539 ), 1540 Package: "foo", 1541 PURL: mustPURL("pkg:generic/foo@version"), 1542 CPEs: singleCPE("cpe:2.3:a:foo:foo:*:*:*:*:*:*:*:*"), 1543 } 1544 1545 tests := []struct { 1546 name string 1547 config ClassifierCatalogerConfig 1548 fixtureDir string 1549 expected *pkg.Package 1550 }{ 1551 { 1552 name: "empty-negative", 1553 config: ClassifierCatalogerConfig{ 1554 Classifiers: []binutils.Classifier{}, 1555 }, 1556 fixtureDir: "test-fixtures/custom/go-1.14", 1557 expected: nil, 1558 }, 1559 { 1560 name: "default-positive", 1561 config: ClassifierCatalogerConfig{ 1562 Classifiers: defaultClassifers, 1563 }, 1564 fixtureDir: "test-fixtures/custom/go-1.14", 1565 expected: &golangExpected, 1566 }, 1567 { 1568 name: "nodefault-negative", 1569 config: ClassifierCatalogerConfig{ 1570 Classifiers: []binutils.Classifier{fooClassifier}, 1571 }, 1572 fixtureDir: "test-fixtures/custom/go-1.14", 1573 expected: nil, 1574 }, 1575 { 1576 name: "default-extended-positive", 1577 config: ClassifierCatalogerConfig{ 1578 Classifiers: append( 1579 append([]binutils.Classifier{}, defaultClassifers...), 1580 fooClassifier, 1581 ), 1582 }, 1583 fixtureDir: "test-fixtures/custom/go-1.14", 1584 expected: &golangExpected, 1585 }, 1586 { 1587 name: "default-custom-negative", 1588 config: ClassifierCatalogerConfig{ 1589 1590 Classifiers: append( 1591 append([]binutils.Classifier{}, defaultClassifers...), 1592 binutils.Classifier{ 1593 Class: "foo-binary", 1594 FileGlob: "**/foo", 1595 EvidenceMatcher: binutils.FileContentsVersionMatcher(catalogerName, `(?m)not there`), 1596 Package: "foo", 1597 PURL: mustPURL("pkg:generic/foo@version"), 1598 CPEs: singleCPE("cpe:2.3:a:foo:foo:*:*:*:*:*:*:*:*"), 1599 }, 1600 ), 1601 }, 1602 fixtureDir: "test-fixtures/custom/extra", 1603 expected: nil, 1604 }, 1605 { 1606 name: "default-cutsom-positive", 1607 config: ClassifierCatalogerConfig{ 1608 Classifiers: append( 1609 append([]binutils.Classifier{}, defaultClassifers...), 1610 fooClassifier, 1611 ), 1612 }, 1613 fixtureDir: "test-fixtures/custom/extra", 1614 expected: &customExpected, 1615 }, 1616 } 1617 for _, test := range tests { 1618 t.Run(test.name, func(t *testing.T) { 1619 c := NewClassifierCataloger(test.config) 1620 1621 src, err := directorysource.NewFromPath(test.fixtureDir) 1622 require.NoError(t, err) 1623 1624 resolver, err := src.FileResolver(source.SquashedScope) 1625 require.NoError(t, err) 1626 1627 packages, _, err := c.Catalog(context.Background(), resolver) 1628 require.NoError(t, err) 1629 1630 if test.expected == nil { 1631 assert.Equal(t, 0, len(packages)) 1632 } else { 1633 require.Len(t, packages, 1) 1634 1635 assertPackagesAreEqual(t, *test.expected, packages[0]) 1636 } 1637 }) 1638 } 1639 } 1640 1641 func locations(locations ...string) file.LocationSet { 1642 var locs []file.Location 1643 for _, s := range locations { 1644 locs = append(locs, file.NewLocation(s)) 1645 } 1646 return file.NewLocationSet(locs...) 1647 } 1648 1649 // metadata paths are: realPath, virtualPath 1650 func metadata(classifier string, paths ...string) pkg.BinarySignature { 1651 return pkg.BinarySignature{ 1652 Matches: []pkg.ClassifierMatch{ 1653 match(classifier, paths...), 1654 }, 1655 } 1656 } 1657 1658 // match paths are: realPath, virtualPath 1659 func match(classifier string, paths ...string) pkg.ClassifierMatch { 1660 realPath := "" 1661 if len(paths) > 0 { 1662 realPath = paths[0] 1663 } 1664 virtualPath := "" 1665 if len(paths) > 1 { 1666 virtualPath = paths[1] 1667 } 1668 return pkg.ClassifierMatch{ 1669 Classifier: classifier, 1670 Location: file.NewVirtualLocationFromCoordinates( 1671 file.Coordinates{ 1672 RealPath: realPath, 1673 }, 1674 virtualPath, 1675 ), 1676 } 1677 } 1678 1679 func assertPackagesAreEqual(t *testing.T, expected pkg.Package, p pkg.Package) { 1680 var failMessages []string 1681 expectedLocations := expected.Locations.ToSlice() 1682 gotLocations := p.Locations.ToSlice() 1683 1684 if len(expectedLocations) != len(gotLocations) { 1685 failMessages = append(failMessages, "locations are not equal length") 1686 } else { 1687 for i, expectedLocation := range expectedLocations { 1688 gotLocation := gotLocations[i] 1689 if expectedLocation.RealPath != gotLocation.RealPath { 1690 failMessages = append(failMessages, fmt.Sprintf("locations do not match; expected: %v got: %v", expectedLocation.RealPath, gotLocation.RealPath)) 1691 } 1692 } 1693 } 1694 1695 m1 := expected.Metadata.(pkg.BinarySignature).Matches 1696 m2 := p.Metadata.(pkg.BinarySignature).Matches 1697 matches := true 1698 if len(m1) == len(m2) { 1699 for i, m1 := range m1 { 1700 m2 := m2[i] 1701 if m1.Classifier != m2.Classifier { 1702 matches = false 1703 break 1704 } 1705 } 1706 } else { 1707 matches = false 1708 } 1709 1710 if !matches { 1711 failMessages = append(failMessages, "classifier matches not equal") 1712 } 1713 if expected.Name != p.Name || 1714 expected.Version != p.Version || 1715 expected.PURL != p.PURL { 1716 failMessages = append(failMessages, "packages do not match") 1717 } 1718 1719 if len(failMessages) > 0 { 1720 assert.Failf(t, strings.Join(failMessages, "; "), "diff: %s", 1721 cmp.Diff(expected, p, 1722 cmp.Transformer("Locations", func(l file.LocationSet) []file.Location { 1723 return l.ToSlice() 1724 }), 1725 cmpopts.IgnoreUnexported(pkg.Package{}, file.LocationData{}), 1726 cmpopts.IgnoreFields(pkg.Package{}, "CPEs", "FoundBy", "Type", "Locations", "Licenses"), 1727 ), 1728 ) 1729 } 1730 } 1731 1732 type panicyResolver struct { 1733 searchCalled bool 1734 } 1735 1736 func (p *panicyResolver) FilesByExtension(_ ...string) ([]file.Location, error) { 1737 p.searchCalled = true 1738 return nil, errors.New("not implemented") 1739 } 1740 1741 func (p *panicyResolver) FilesByBasename(_ ...string) ([]file.Location, error) { 1742 p.searchCalled = true 1743 return nil, errors.New("not implemented") 1744 } 1745 1746 func (p *panicyResolver) FilesByBasenameGlob(_ ...string) ([]file.Location, error) { 1747 p.searchCalled = true 1748 return nil, errors.New("not implemented") 1749 } 1750 1751 func (p *panicyResolver) FileContentsByLocation(_ file.Location) (io.ReadCloser, error) { 1752 p.searchCalled = true 1753 return nil, errors.New("not implemented") 1754 } 1755 1756 func (p *panicyResolver) HasPath(_ string) bool { 1757 return true 1758 } 1759 1760 func (p *panicyResolver) FilesByPath(_ ...string) ([]file.Location, error) { 1761 p.searchCalled = true 1762 return nil, errors.New("not implemented") 1763 } 1764 1765 func (p *panicyResolver) FilesByGlob(_ ...string) ([]file.Location, error) { 1766 p.searchCalled = true 1767 return nil, errors.New("not implemented") 1768 } 1769 1770 func (p *panicyResolver) FilesByMIMEType(_ ...string) ([]file.Location, error) { 1771 p.searchCalled = true 1772 return nil, errors.New("not implemented") 1773 } 1774 1775 func (p *panicyResolver) RelativeFileByPath(_ file.Location, _ string) *file.Location { 1776 return nil 1777 } 1778 1779 func (p *panicyResolver) AllLocations(_ context.Context) <-chan file.Location { 1780 return nil 1781 } 1782 1783 func (p *panicyResolver) FileMetadataByLocation(_ file.Location) (file.Metadata, error) { 1784 return file.Metadata{}, errors.New("not implemented") 1785 } 1786 1787 var _ file.Resolver = (*panicyResolver)(nil) 1788 1789 func Test_Cataloger_ResilientToErrors(t *testing.T) { 1790 c := NewClassifierCataloger(DefaultClassifierCatalogerConfig()) 1791 1792 resolver := &panicyResolver{} 1793 _, _, err := c.Catalog(context.Background(), resolver) 1794 assert.Nil(t, err) // non-coordinate-based FindBy* errors are now logged and not returned 1795 assert.True(t, resolver.searchCalled) 1796 } 1797 1798 func TestCatalogerConfig_MarshalJSON(t *testing.T) { 1799 1800 tests := []struct { 1801 name string 1802 cfg ClassifierCatalogerConfig 1803 want string 1804 wantErr assert.ErrorAssertionFunc 1805 }{ 1806 { 1807 name: "only show names of classes", 1808 cfg: ClassifierCatalogerConfig{ 1809 Classifiers: []binutils.Classifier{ 1810 { 1811 Class: "class", 1812 FileGlob: "glob", 1813 EvidenceMatcher: binutils.FileContentsVersionMatcher(catalogerName, ".thing"), 1814 Package: "pkg", 1815 PURL: packageurl.PackageURL{ 1816 Type: "type", 1817 Namespace: "namespace", 1818 Name: "name", 1819 Version: "version", 1820 Qualifiers: nil, 1821 Subpath: "subpath", 1822 }, 1823 CPEs: []cpe.CPE{cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*", cpe.GeneratedSource)}, 1824 }, 1825 }, 1826 }, 1827 want: `["class"]`, 1828 }, 1829 } 1830 for _, tt := range tests { 1831 t.Run(tt.name, func(t *testing.T) { 1832 if tt.wantErr == nil { 1833 tt.wantErr = assert.NoError 1834 } 1835 got, err := tt.cfg.MarshalJSON() 1836 if !tt.wantErr(t, err) { 1837 return 1838 } 1839 assert.Equal(t, tt.want, string(got)) 1840 }) 1841 } 1842 }