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  }