github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/pkg/cataloger/binary/cataloger_test.go (about)

     1  package binary
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/google/go-cmp/cmp"
    11  	"github.com/google/go-cmp/cmp/cmpopts"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	"github.com/anchore/stereoscope/pkg/imagetest"
    16  	"github.com/anchore/syft/syft/file"
    17  	"github.com/anchore/syft/syft/pkg"
    18  	"github.com/anchore/syft/syft/source"
    19  )
    20  
    21  func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
    22  	tests := []struct {
    23  		name       string
    24  		fixtureDir string
    25  		expected   pkg.Package
    26  	}{
    27  		{
    28  			name:       "positive-postgresql-15beta4",
    29  			fixtureDir: "test-fixtures/classifiers/positive/postgresql-15beta4",
    30  			expected: pkg.Package{
    31  				Name:      "postgresql",
    32  				Version:   "15beta4",
    33  				Type:      "binary",
    34  				PURL:      "pkg:generic/postgresql@15beta4",
    35  				Locations: locations("postgres"),
    36  				Metadata:  metadata("postgresql-binary"),
    37  			},
    38  		},
    39  		{
    40  			name:       "positive-postgresql-15.1",
    41  			fixtureDir: "test-fixtures/classifiers/positive/postgresql-15.1",
    42  			expected: pkg.Package{
    43  				Name:      "postgresql",
    44  				Version:   "15.1",
    45  				Type:      "binary",
    46  				PURL:      "pkg:generic/postgresql@15.1",
    47  				Locations: locations("postgres"),
    48  				Metadata:  metadata("postgresql-binary"),
    49  			},
    50  		},
    51  		{
    52  			name:       "positive-postgresql-9.6.24",
    53  			fixtureDir: "test-fixtures/classifiers/positive/postgresql-9.6.24",
    54  			expected: pkg.Package{
    55  				Name:      "postgresql",
    56  				Version:   "9.6.24",
    57  				Type:      "binary",
    58  				PURL:      "pkg:generic/postgresql@9.6.24",
    59  				Locations: locations("postgres"),
    60  				Metadata:  metadata("postgresql-binary"),
    61  			},
    62  		},
    63  		{
    64  			name:       "positive-postgresql-9.5alpha1",
    65  			fixtureDir: "test-fixtures/classifiers/positive/postgresql-9.5alpha1",
    66  			expected: pkg.Package{
    67  				Name:      "postgresql",
    68  				Version:   "9.5alpha1",
    69  				Type:      "binary",
    70  				PURL:      "pkg:generic/postgresql@9.5alpha1",
    71  				Locations: locations("postgres"),
    72  				Metadata:  metadata("postgresql-binary"),
    73  			},
    74  		},
    75  		{
    76  			name:       "positive-mysql-8.0.34",
    77  			fixtureDir: "test-fixtures/classifiers/positive/mysql-8.0.34",
    78  			expected: pkg.Package{
    79  				Name:      "mysql",
    80  				Version:   "8.0.34",
    81  				Type:      "binary",
    82  				PURL:      "pkg:generic/mysql@8.0.34",
    83  				Locations: locations("mysql"),
    84  				Metadata:  metadata("mysql-binary"),
    85  			},
    86  		},
    87  		{
    88  			name:       "positive-mysql-5.6.51",
    89  			fixtureDir: "test-fixtures/classifiers/positive/mysql-5.6.51",
    90  			expected: pkg.Package{
    91  				Name:      "mysql",
    92  				Version:   "5.6.51",
    93  				Type:      "binary",
    94  				PURL:      "pkg:generic/mysql@5.6.51",
    95  				Locations: locations("mysql"),
    96  				Metadata:  metadata("mysql-binary"),
    97  			},
    98  		},
    99  		{
   100  			name:       "positive-mariadb-10.6.15",
   101  			fixtureDir: "test-fixtures/classifiers/positive/mariadb-10.6.15",
   102  			expected: pkg.Package{
   103  				Name:      "mariadb",
   104  				Version:   "10.6.15",
   105  				Type:      "binary",
   106  				PURL:      "pkg:generic/mariadb@10.6.15",
   107  				Locations: locations("mariadb"),
   108  				Metadata:  metadata("mariadb-binary"),
   109  			},
   110  		},
   111  		{
   112  			name:       "positive-traefik-2.9.6",
   113  			fixtureDir: "test-fixtures/classifiers/positive/traefik-2.9.6",
   114  			expected: pkg.Package{
   115  				Name:      "traefik",
   116  				Version:   "2.9.6",
   117  				Type:      "binary",
   118  				PURL:      "pkg:generic/traefik@2.9.6",
   119  				Locations: locations("traefik"),
   120  				Metadata:  metadata("traefik-binary"),
   121  			},
   122  		},
   123  		{
   124  			name:       "positive-traefik-1.7.34",
   125  			fixtureDir: "test-fixtures/classifiers/positive/traefik-1.7.34",
   126  			expected: pkg.Package{
   127  				Name:      "traefik",
   128  				Version:   "1.7.34",
   129  				Type:      "binary",
   130  				PURL:      "pkg:generic/traefik@1.7.34",
   131  				Locations: locations("traefik"),
   132  				Metadata:  metadata("traefik-binary"),
   133  			},
   134  		},
   135  		{
   136  			name:       "positive-memcached-1.6.18",
   137  			fixtureDir: "test-fixtures/classifiers/positive/memcached-1.6.18",
   138  			expected: pkg.Package{
   139  				Name:      "memcached",
   140  				Version:   "1.6.18",
   141  				Type:      "binary",
   142  				PURL:      "pkg:generic/memcached@1.6.18",
   143  				Locations: locations("memcached"),
   144  				Metadata:  metadata("memcached-binary"),
   145  			},
   146  		},
   147  		{
   148  			name:       "positive-httpd-2.4.54",
   149  			fixtureDir: "test-fixtures/classifiers/positive/httpd-2.4.54",
   150  			expected: pkg.Package{
   151  				Name:      "httpd",
   152  				Version:   "2.4.54",
   153  				Type:      "binary",
   154  				PURL:      "pkg:generic/httpd@2.4.54",
   155  				Locations: locations("httpd"),
   156  				Metadata:  metadata("httpd-binary"),
   157  			},
   158  		},
   159  		{
   160  			name:       "positive-php-cli-8.2.1",
   161  			fixtureDir: "test-fixtures/classifiers/positive/php-cli-8.2.1",
   162  			expected: pkg.Package{
   163  				Name:      "php-cli",
   164  				Version:   "8.2.1",
   165  				Type:      "binary",
   166  				PURL:      "pkg:generic/php-cli@8.2.1",
   167  				Locations: locations("php"),
   168  				Metadata:  metadata("php-cli-binary"),
   169  			},
   170  		},
   171  		{
   172  			name:       "positive-php-fpm-8.2.1",
   173  			fixtureDir: "test-fixtures/classifiers/positive/php-fpm-8.2.1",
   174  			expected: pkg.Package{
   175  				Name:      "php-fpm",
   176  				Version:   "8.2.1",
   177  				Type:      "binary",
   178  				PURL:      "pkg:generic/php-fpm@8.2.1",
   179  				Locations: locations("php-fpm"),
   180  				Metadata:  metadata("php-fpm-binary"),
   181  			},
   182  		},
   183  		{
   184  			name:       "positive-php-apache-8.2.1",
   185  			fixtureDir: "test-fixtures/classifiers/positive/php-apache-8.2.1",
   186  			expected: pkg.Package{
   187  				Name:      "libphp",
   188  				Version:   "8.2.1",
   189  				Type:      "binary",
   190  				PURL:      "pkg:generic/php@8.2.1",
   191  				Locations: locations("libphp.so"),
   192  				Metadata:  metadata("php-apache-binary"),
   193  			},
   194  		},
   195  		{
   196  			name:       "positive-perl-5.12.5",
   197  			fixtureDir: "test-fixtures/classifiers/positive/perl-5.12.5",
   198  			expected: pkg.Package{
   199  				Name:      "perl",
   200  				Version:   "5.12.5",
   201  				Type:      "binary",
   202  				PURL:      "pkg:generic/perl@5.12.5",
   203  				Locations: locations("perl"),
   204  				Metadata:  metadata("perl-binary"),
   205  			},
   206  		},
   207  		{
   208  			name:       "positive-perl-5.20.0",
   209  			fixtureDir: "test-fixtures/classifiers/positive/perl-5.20.0",
   210  			expected: pkg.Package{
   211  				Name:      "perl",
   212  				Version:   "5.20.0",
   213  				Type:      "binary",
   214  				PURL:      "pkg:generic/perl@5.20.0",
   215  				Locations: locations("perl"),
   216  				Metadata:  metadata("perl-binary"),
   217  			},
   218  		},
   219  		{
   220  			name:       "positive-perl-5.37.8",
   221  			fixtureDir: "test-fixtures/classifiers/positive/perl-5.37.8",
   222  			expected: pkg.Package{
   223  				Name:      "perl",
   224  				Version:   "5.37.8",
   225  				Type:      "binary",
   226  				PURL:      "pkg:generic/perl@5.37.8",
   227  				Locations: locations("perl"),
   228  				Metadata:  metadata("perl-binary"),
   229  			},
   230  		},
   231  		{
   232  			name:       "positive-haproxy-1.5.14",
   233  			fixtureDir: "test-fixtures/classifiers/positive/haproxy-1.5.14",
   234  			expected: pkg.Package{
   235  				Name:      "haproxy",
   236  				Version:   "1.5.14",
   237  				Type:      "binary",
   238  				PURL:      "pkg:generic/haproxy@1.5.14",
   239  				Locations: locations("haproxy"),
   240  				Metadata:  metadata("haproxy-binary"),
   241  			},
   242  		},
   243  		{
   244  			name:       "positive-haproxy-1.8.22",
   245  			fixtureDir: "test-fixtures/classifiers/positive/haproxy-1.8.22",
   246  			expected: pkg.Package{
   247  				Name:      "haproxy",
   248  				Version:   "1.8.22",
   249  				Type:      "binary",
   250  				PURL:      "pkg:generic/haproxy@1.8.22",
   251  				Locations: locations("haproxy"),
   252  				Metadata:  metadata("haproxy-binary"),
   253  			},
   254  		},
   255  		{
   256  			name:       "positive-haproxy-2.7.3",
   257  			fixtureDir: "test-fixtures/classifiers/positive/haproxy-2.7.3",
   258  			expected: pkg.Package{
   259  				Name:      "haproxy",
   260  				Version:   "2.7.3",
   261  				Type:      "binary",
   262  				PURL:      "pkg:generic/haproxy@2.7.3",
   263  				Locations: locations("haproxy"),
   264  				Metadata:  metadata("haproxy-binary"),
   265  			},
   266  		},
   267  		{
   268  			name:       "positive-redis-2.8.23",
   269  			fixtureDir: "test-fixtures/classifiers/positive/redis-server-2.8.23",
   270  			expected: pkg.Package{
   271  				Name:      "redis",
   272  				Version:   "2.8.23",
   273  				Type:      "binary",
   274  				PURL:      "pkg:generic/redis@2.8.23",
   275  				Locations: locations("redis-server"),
   276  				Metadata:  metadata("redis-binary"),
   277  			},
   278  		},
   279  		{
   280  			name:       "positive-helm-3.11.1",
   281  			fixtureDir: "test-fixtures/classifiers/dynamic/helm-3.11.1",
   282  			expected: pkg.Package{
   283  				Name:      "helm",
   284  				Version:   "3.11.1",
   285  				Type:      "binary",
   286  				PURL:      "pkg:golang/helm.sh/helm@3.11.1",
   287  				Locations: locations("helm"),
   288  				Metadata:  metadata("helm"),
   289  			},
   290  		},
   291  		{
   292  			name:       "positive-helm-3.10.3",
   293  			fixtureDir: "test-fixtures/classifiers/dynamic/helm-3.10.3",
   294  			expected: pkg.Package{
   295  				Name:      "helm",
   296  				Version:   "3.10.3",
   297  				Type:      "binary",
   298  				PURL:      "pkg:golang/helm.sh/helm@3.10.3",
   299  				Locations: locations("helm"),
   300  				Metadata:  metadata("helm"),
   301  			},
   302  		},
   303  		{
   304  			name:       "positive-redis-4.0.11",
   305  			fixtureDir: "test-fixtures/classifiers/positive/redis-server-4.0.11",
   306  			expected: pkg.Package{
   307  				Name:      "redis",
   308  				Version:   "4.0.11",
   309  				Type:      "binary",
   310  				PURL:      "pkg:generic/redis@4.0.11",
   311  				Locations: locations("redis-server"),
   312  				Metadata:  metadata("redis-binary"),
   313  			},
   314  		},
   315  		{
   316  			name:       "positive-redis-5.0.0",
   317  			fixtureDir: "test-fixtures/classifiers/positive/redis-server-5.0.0",
   318  			expected: pkg.Package{
   319  				Name:      "redis",
   320  				Version:   "5.0.0",
   321  				Type:      "binary",
   322  				PURL:      "pkg:generic/redis@5.0.0",
   323  				Locations: locations("redis-server"),
   324  				Metadata:  metadata("redis-binary"),
   325  			},
   326  		},
   327  		{
   328  			name:       "positive-redis-6.0.16",
   329  			fixtureDir: "test-fixtures/classifiers/positive/redis-server-6.0.16",
   330  			expected: pkg.Package{
   331  				Name:      "redis",
   332  				Version:   "6.0.16",
   333  				Type:      "binary",
   334  				PURL:      "pkg:generic/redis@6.0.16",
   335  				Locations: locations("redis-server"),
   336  				Metadata:  metadata("redis-binary"),
   337  			},
   338  		},
   339  		{
   340  			name:       "positive-redis-7.0.0",
   341  			fixtureDir: "test-fixtures/classifiers/positive/redis-server-7.0.0",
   342  			expected: pkg.Package{
   343  				Name:      "redis",
   344  				Version:   "7.0.0",
   345  				Type:      "binary",
   346  				PURL:      "pkg:generic/redis@7.0.0",
   347  				Locations: locations("redis-server"),
   348  				Metadata:  metadata("redis-binary"),
   349  			},
   350  		},
   351  		{
   352  			name:       "positive-redis-7.0.14",
   353  			fixtureDir: "test-fixtures/classifiers/positive/redis-server-7.0.14",
   354  			expected: pkg.Package{
   355  				Name:      "redis",
   356  				Version:   "7.0.14",
   357  				Type:      "binary",
   358  				PURL:      "pkg:generic/redis@7.0.14",
   359  				Locations: locations("redis-server"),
   360  				Metadata:  metadata("redis-binary"),
   361  			},
   362  		},
   363  		{
   364  			name:       "positive-redis-7.2.3-amd64",
   365  			fixtureDir: "test-fixtures/classifiers/positive/redis-server-7.2.3-amd64",
   366  			expected: pkg.Package{
   367  				Name:      "redis",
   368  				Version:   "7.2.3",
   369  				Type:      "binary",
   370  				PURL:      "pkg:generic/redis@7.2.3",
   371  				Locations: locations("redis-server"),
   372  				Metadata:  metadata("redis-binary"),
   373  			},
   374  		},
   375  		{
   376  			name:       "positive-redis-7.2.3-arm64",
   377  			fixtureDir: "test-fixtures/classifiers/positive/redis-server-7.2.3-arm64",
   378  			expected: pkg.Package{
   379  				Name:      "redis",
   380  				Version:   "7.2.3",
   381  				Type:      "binary",
   382  				PURL:      "pkg:generic/redis@7.2.3",
   383  				Locations: locations("redis-server"),
   384  				Metadata:  metadata("redis-binary"),
   385  			},
   386  		},
   387  		{
   388  			name:       "positive-libpython3.7.so",
   389  			fixtureDir: "test-fixtures/classifiers/positive/python-binary-lib-3.7",
   390  			expected: pkg.Package{
   391  				Name:      "python",
   392  				Version:   "3.7.4",
   393  				PURL:      "pkg:generic/python@3.7.4",
   394  				Locations: locations("libpython3.7.so"),
   395  				Metadata:  metadata("python-binary-lib"),
   396  			},
   397  		},
   398  		{
   399  			name:       "positive-python-3.11.2-from-shared-lib",
   400  			fixtureDir: "test-fixtures/classifiers/dynamic/python-binary-shared-lib-3.11",
   401  			expected: pkg.Package{
   402  				Name:      "python",
   403  				Version:   "3.11.2",
   404  				PURL:      "pkg:generic/python@3.11.2",
   405  				Locations: locations("python3", "libpython3.11.so.1.0"),
   406  				Metadata: pkg.BinarySignature{
   407  					Matches: []pkg.ClassifierMatch{
   408  						match("python-binary", "python3"),
   409  						match("python-binary", "libpython3.11.so.1.0"),
   410  						match("python-binary-lib", "libpython3.11.so.1.0"),
   411  					},
   412  				},
   413  			},
   414  		},
   415  		{
   416  			name:       "positive-python-3.9-from-shared-redhat-lib",
   417  			fixtureDir: "test-fixtures/classifiers/dynamic/python-binary-shared-lib-redhat-3.9",
   418  			expected: pkg.Package{
   419  				Name:      "python",
   420  				Version:   "3.9.13",
   421  				PURL:      "pkg:generic/python@3.9.13",
   422  				Locations: locations("python3.9", "libpython3.9.so.1.0"),
   423  				Metadata: pkg.BinarySignature{
   424  					Matches: []pkg.ClassifierMatch{
   425  						match("python-binary", "python3.9"),
   426  						match("python-binary", "libpython3.9.so.1.0"),
   427  						match("python-binary-lib", "libpython3.9.so.1.0"),
   428  					},
   429  				},
   430  			},
   431  		},
   432  		{
   433  			name:       "positive-python-binary-with-version-3.9",
   434  			fixtureDir: "test-fixtures/classifiers/dynamic/python-binary-with-version-3.9",
   435  			expected: pkg.Package{
   436  				Name:      "python",
   437  				Version:   "3.9.2",
   438  				PURL:      "pkg:generic/python@3.9.2",
   439  				Locations: locations("python3.9"),
   440  				Metadata: pkg.BinarySignature{
   441  					Matches: []pkg.ClassifierMatch{
   442  						match("python-binary", "python3.9"),
   443  					},
   444  				},
   445  			},
   446  		},
   447  		{
   448  			name:       "positive-python-binary-3.4-alpine",
   449  			fixtureDir: "test-fixtures/classifiers/dynamic/python-binary-3.4-alpine",
   450  			expected: pkg.Package{
   451  				Name:      "python",
   452  				Version:   "3.4.10",
   453  				PURL:      "pkg:generic/python@3.4.10",
   454  				Locations: locations("python3.4", "libpython3.4m.so.1.0"),
   455  				Metadata: pkg.BinarySignature{
   456  					Matches: []pkg.ClassifierMatch{
   457  						match("python-binary", "python3.4"),
   458  						match("python-binary", "libpython3.4m.so.1.0"),
   459  						match("python-binary-lib", "libpython3.4m.so.1.0"),
   460  					},
   461  				},
   462  			},
   463  		},
   464  		{
   465  			name:       "positive-python-3.5-with-incorrect-match",
   466  			fixtureDir: "test-fixtures/classifiers/positive/python-3.5-with-incorrect-match",
   467  			expected: pkg.Package{
   468  				Name:      "python",
   469  				Version:   "3.5.3",
   470  				PURL:      "pkg:generic/python@3.5.3",
   471  				Locations: locations("python3.5"),
   472  				Metadata:  metadata("python-binary"),
   473  			},
   474  		},
   475  		{
   476  			name:       "positive-python3.6",
   477  			fixtureDir: "test-fixtures/classifiers/positive/python-binary-3.6",
   478  			expected: pkg.Package{
   479  				Name:      "python",
   480  				Version:   "3.6.3",
   481  				PURL:      "pkg:generic/python@3.6.3",
   482  				Locations: locations("python3.6"),
   483  				Metadata:  metadata("python-binary"),
   484  			},
   485  		},
   486  		{
   487  			name:       "positive-python-duplicates",
   488  			fixtureDir: "test-fixtures/classifiers/positive/python-duplicates",
   489  			expected: pkg.Package{
   490  				Name:      "python",
   491  				Version:   "3.8.16",
   492  				Type:      "binary",
   493  				PURL:      "pkg:generic/python@3.8.16",
   494  				Locations: locations("dir/python3.8", "python3.8", "libpython3.8.so"),
   495  				Metadata: pkg.BinarySignature{
   496  					Matches: []pkg.ClassifierMatch{
   497  						match("python-binary", "dir/python3.8"),
   498  						match("python-binary", "python3.8"),
   499  						match("python-binary-lib", "libpython3.8.so"),
   500  					},
   501  				},
   502  			},
   503  		},
   504  		{
   505  			name:       "positive-go",
   506  			fixtureDir: "test-fixtures/classifiers/positive/go-1.14",
   507  			expected: pkg.Package{
   508  				Name:      "go",
   509  				Version:   "1.14",
   510  				PURL:      "pkg:generic/go@1.14",
   511  				Locations: locations("go"),
   512  				Metadata:  metadata("go-binary"),
   513  			},
   514  		},
   515  		{
   516  			name:       "positive-node",
   517  			fixtureDir: "test-fixtures/classifiers/positive/node-19.2.1",
   518  			expected: pkg.Package{
   519  				Name:      "node",
   520  				Version:   "19.2.1",
   521  				PURL:      "pkg:generic/node@19.2.1",
   522  				Locations: locations("node"),
   523  				Metadata:  metadata("nodejs-binary"),
   524  			},
   525  		},
   526  		{
   527  			name:       "positive-go-hint",
   528  			fixtureDir: "test-fixtures/classifiers/positive/go-hint-1.15",
   529  			expected: pkg.Package{
   530  				Name:      "go",
   531  				Version:   "1.15",
   532  				PURL:      "pkg:generic/go@1.15",
   533  				Locations: locations("VERSION"),
   534  				Metadata:  metadata("go-binary-hint"),
   535  			},
   536  		},
   537  		{
   538  			name:       "positive-busybox",
   539  			fixtureDir: "test-fixtures/classifiers/positive/busybox-3.33.3",
   540  			expected: pkg.Package{
   541  				Name:      "busybox",
   542  				Version:   "3.33.3",
   543  				Locations: locations("["), // note: busybox is a link to [
   544  				Metadata:  metadata("busybox-binary", "[", "busybox"),
   545  			},
   546  		},
   547  		{
   548  			name:       "positive-java-openjdk",
   549  			fixtureDir: "test-fixtures/classifiers/positive/openjdk",
   550  			expected: pkg.Package{
   551  				Name:      "java",
   552  				Version:   "1.8.0_352-b08",
   553  				Type:      "binary",
   554  				PURL:      "pkg:generic/java@1.8.0_352-b08",
   555  				Locations: locations("java"),
   556  				Metadata:  metadata("java-binary-openjdk", "java"),
   557  			},
   558  		},
   559  		{
   560  			name:       "positive-java-openjdk-lts",
   561  			fixtureDir: "test-fixtures/classifiers/positive/openjdk-lts",
   562  			expected: pkg.Package{
   563  				Name:      "java",
   564  				Version:   "11.0.17+8-LTS",
   565  				Type:      "binary",
   566  				PURL:      "pkg:generic/java@11.0.17+8-LTS",
   567  				Locations: locations("java"),
   568  				Metadata:  metadata("java-binary-openjdk", "java"),
   569  			},
   570  		},
   571  		{
   572  			name:       "positive-java-oracle",
   573  			fixtureDir: "test-fixtures/classifiers/positive/oracle",
   574  			expected: pkg.Package{
   575  				Name:      "java",
   576  				Version:   "19.0.1+10-21",
   577  				Type:      "binary",
   578  				PURL:      "pkg:generic/java@19.0.1+10-21",
   579  				Locations: locations("java"),
   580  				Metadata:  metadata("java-binary-oracle", "java"),
   581  			},
   582  		},
   583  		{
   584  			name:       "positive-java-oracle-macos",
   585  			fixtureDir: "test-fixtures/classifiers/positive/oracle-macos",
   586  			expected: pkg.Package{
   587  				Name:      "java",
   588  				Version:   "19.0.1+10-21",
   589  				Type:      "binary",
   590  				PURL:      "pkg:generic/java@19.0.1+10-21",
   591  				Locations: locations("java"),
   592  				Metadata:  metadata("java-binary-oracle", "java"),
   593  			},
   594  		},
   595  		{
   596  			name:       "positive-java-ibm",
   597  			fixtureDir: "test-fixtures/classifiers/positive/ibm",
   598  			expected: pkg.Package{
   599  				Name:      "java",
   600  				Version:   "1.8.0-foreman_2022_09_22_15_30-b00",
   601  				Type:      "binary",
   602  				PURL:      "pkg:generic/java@1.8.0-foreman_2022_09_22_15_30-b00",
   603  				Locations: locations("java"),
   604  				Metadata:  metadata("java-binary-ibm", "java"),
   605  			},
   606  		},
   607  		{
   608  			name:       "positive-rust-1.50.0-macos",
   609  			fixtureDir: "test-fixtures/classifiers/positive/rust-1.50.0",
   610  			expected: pkg.Package{
   611  				Name:      "rust",
   612  				Version:   "1.50.0",
   613  				Type:      "binary",
   614  				PURL:      "pkg:generic/rust@1.50.0",
   615  				Locations: locations("lib/rustlib/aarch64-apple-darwin/lib/libstd-f6f9eec1635e636a.dylib"),
   616  				Metadata:  metadata("rust-standard-library-macos"),
   617  			},
   618  		},
   619  		{
   620  			name:       "positive-rust-1.67.1-macos",
   621  			fixtureDir: "test-fixtures/classifiers/positive/rust-1.67.1/toolchains/stable-aarch64-apple-darwin",
   622  			expected: pkg.Package{
   623  				Name:      "rust",
   624  				Version:   "1.67.1",
   625  				Type:      "binary",
   626  				PURL:      "pkg:generic/rust@1.67.1",
   627  				Locations: locations("lib/libstd-16f2b65e77054c42.dylib"),
   628  				Metadata:  metadata("rust-standard-library-macos"),
   629  			},
   630  		},
   631  		{
   632  			name:       "positive-rust-1.67.1-linux",
   633  			fixtureDir: "test-fixtures/classifiers/positive/rust-1.67.1/toolchains/stable-x86_64-unknown-linux-musl",
   634  			expected: pkg.Package{
   635  				Name:      "rust",
   636  				Version:   "1.67.1",
   637  				Type:      "binary",
   638  				PURL:      "pkg:generic/rust@1.67.1",
   639  				Locations: locations("lib/libstd-86aefecbddda356d.so"),
   640  				Metadata:  metadata("rust-standard-library-linux"),
   641  			},
   642  		},
   643  		{
   644  			name:       "positive-ruby-3.2.1",
   645  			fixtureDir: "test-fixtures/classifiers/dynamic/ruby-library-3.2.1",
   646  			expected: pkg.Package{
   647  				Name:      "ruby",
   648  				Version:   "3.2.1",
   649  				Type:      "binary",
   650  				PURL:      "pkg:generic/ruby@3.2.1",
   651  				Locations: locations("ruby", "libruby.so.3.2.1"),
   652  				Metadata: pkg.BinarySignature{
   653  					Matches: []pkg.ClassifierMatch{
   654  						match("ruby-binary", "ruby"),
   655  						match("ruby-binary", "libruby.so.3.2.1"),
   656  					},
   657  				},
   658  			},
   659  		},
   660  		{
   661  			name:       "positive-ruby-2.7.7",
   662  			fixtureDir: "test-fixtures/classifiers/dynamic/ruby-library-2.7.7",
   663  			expected: pkg.Package{
   664  				Name:      "ruby",
   665  				Version:   "2.7.7p221",
   666  				Type:      "binary",
   667  				PURL:      "pkg:generic/ruby@2.7.7p221",
   668  				Locations: locations("ruby", "libruby.so.2.7.7"),
   669  				Metadata: pkg.BinarySignature{
   670  					Matches: []pkg.ClassifierMatch{
   671  						match("ruby-binary", "ruby"),
   672  						match("ruby-binary", "libruby.so.2.7.7"),
   673  					},
   674  				},
   675  			},
   676  		},
   677  		{
   678  			name:       "positive-ruby-2.6.10",
   679  			fixtureDir: "test-fixtures/classifiers/dynamic/ruby-library-2.6.10",
   680  			expected: pkg.Package{
   681  				Name:      "ruby",
   682  				Version:   "2.6.10p210",
   683  				Type:      "binary",
   684  				PURL:      "pkg:generic/ruby@2.6.10p210",
   685  				Locations: locations("ruby", "libruby.so.2.6.10"),
   686  				Metadata: pkg.BinarySignature{
   687  					Matches: []pkg.ClassifierMatch{
   688  						match("ruby-binary", "ruby"),
   689  						match("ruby-binary", "libruby.so.2.6.10"),
   690  					},
   691  				},
   692  			},
   693  		},
   694  		{
   695  			name:       "positive-ruby-1.9.3p551",
   696  			fixtureDir: "test-fixtures/classifiers/positive/ruby-1.9.3p551",
   697  			expected: pkg.Package{
   698  				Name:      "ruby",
   699  				Version:   "1.9.3p551",
   700  				Type:      "binary",
   701  				PURL:      "pkg:generic/ruby@1.9.3p551",
   702  				Locations: locations("ruby"),
   703  				Metadata:  metadata("ruby-binary"),
   704  			},
   705  		},
   706  		{
   707  			name:       "positive-consul-1.15.2",
   708  			fixtureDir: "test-fixtures/classifiers/dynamic/consul-1.15.2",
   709  			expected: pkg.Package{
   710  				Name:      "consul",
   711  				Version:   "1.15.2",
   712  				Type:      "binary",
   713  				PURL:      "pkg:golang/github.com/hashicorp/consul@1.15.2",
   714  				Locations: locations("consul"),
   715  				Metadata:  metadata("consul-binary"),
   716  			},
   717  		},
   718  		{
   719  			name:       "positive-nginx-1.25.1",
   720  			fixtureDir: "test-fixtures/classifiers/positive/nginx-1.25.1",
   721  			expected: pkg.Package{
   722  				Name:      "nginx",
   723  				Version:   "1.25.1",
   724  				Type:      "binary",
   725  				PURL:      "pkg:generic/nginx@1.25.1",
   726  				Locations: locations("nginx"),
   727  				Metadata:  metadata("nginx-binary"),
   728  			},
   729  		},
   730  		{
   731  			name:       "positive-nginx-openresty-1.21.4.2",
   732  			fixtureDir: "test-fixtures/classifiers/positive/nginx-openresty-1.21.4.2",
   733  			expected: pkg.Package{
   734  				Name:      "nginx",
   735  				Version:   "1.21.4",
   736  				Type:      "binary",
   737  				PURL:      "pkg:generic/nginx@1.21.4",
   738  				Locations: locations("nginx"),
   739  				Metadata:  metadata("nginx-binary"),
   740  			},
   741  		},
   742  		{
   743  			name:       "positive-bash-5.2.15",
   744  			fixtureDir: "test-fixtures/classifiers/positive/bash-5.2.15",
   745  			expected: pkg.Package{
   746  				Name:      "bash",
   747  				Version:   "5.2.15",
   748  				Type:      "binary",
   749  				PURL:      "pkg:generic/bash@5.2.15",
   750  				Locations: locations("bash"),
   751  				Metadata:  metadata("bash-binary"),
   752  			},
   753  		},
   754  	}
   755  
   756  	for _, test := range tests {
   757  		t.Run(test.name, func(t *testing.T) {
   758  			c := NewCataloger()
   759  
   760  			src, err := source.NewFromDirectoryPath(test.fixtureDir)
   761  			require.NoError(t, err)
   762  
   763  			resolver, err := src.FileResolver(source.SquashedScope)
   764  			require.NoError(t, err)
   765  
   766  			packages, _, err := c.Catalog(resolver)
   767  			require.NoError(t, err)
   768  
   769  			require.Len(t, packages, 1)
   770  
   771  			assertPackagesAreEqual(t, test.expected, packages[0])
   772  		})
   773  	}
   774  }
   775  
   776  func Test_Cataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T) {
   777  	tests := []struct {
   778  		name         string
   779  		fixtureImage string
   780  		expected     pkg.Package
   781  	}{
   782  		{
   783  			name:         "busybox-regression",
   784  			fixtureImage: "image-busybox",
   785  			expected: pkg.Package{
   786  				Name:      "busybox",
   787  				Version:   "1.35.0",
   788  				Locations: locations("/bin/["),
   789  				Metadata:  metadata("busybox-binary", "/bin/[", "/bin/busybox"),
   790  			},
   791  		},
   792  	}
   793  
   794  	for _, test := range tests {
   795  		t.Run(test.name, func(t *testing.T) {
   796  			c := NewCataloger()
   797  
   798  			img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureImage)
   799  			src, err := source.NewFromStereoscopeImageObject(img, test.fixtureImage, nil)
   800  			require.NoError(t, err)
   801  
   802  			resolver, err := src.FileResolver(source.SquashedScope)
   803  			require.NoError(t, err)
   804  
   805  			packages, _, err := c.Catalog(resolver)
   806  			require.NoError(t, err)
   807  
   808  			for _, p := range packages {
   809  				expectedLocations := test.expected.Locations.ToSlice()
   810  				gotLocations := p.Locations.ToSlice()
   811  				require.Len(t, gotLocations, len(expectedLocations))
   812  
   813  				for i, expectedLocation := range expectedLocations {
   814  					gotLocation := gotLocations[i]
   815  					if expectedLocation.RealPath != gotLocation.RealPath {
   816  						t.Fatalf("locations do not match; expected: %v got: %v", expectedLocations, gotLocations)
   817  					}
   818  				}
   819  
   820  				assertPackagesAreEqual(t, test.expected, p)
   821  			}
   822  		})
   823  	}
   824  }
   825  
   826  func TestClassifierCataloger_DefaultClassifiers_NegativeCases(t *testing.T) {
   827  	c := NewCataloger()
   828  
   829  	src, err := source.NewFromDirectoryPath("test-fixtures/classifiers/negative")
   830  	assert.NoError(t, err)
   831  
   832  	resolver, err := src.FileResolver(source.SquashedScope)
   833  	assert.NoError(t, err)
   834  
   835  	actualResults, _, err := c.Catalog(resolver)
   836  	assert.NoError(t, err)
   837  	assert.Equal(t, 0, len(actualResults))
   838  }
   839  
   840  func locations(locations ...string) file.LocationSet {
   841  	var locs []file.Location
   842  	for _, s := range locations {
   843  		locs = append(locs, file.NewLocation(s))
   844  	}
   845  	return file.NewLocationSet(locs...)
   846  }
   847  
   848  // metadata paths are: realPath, virtualPath
   849  func metadata(classifier string, paths ...string) pkg.BinarySignature {
   850  	return pkg.BinarySignature{
   851  		Matches: []pkg.ClassifierMatch{
   852  			match(classifier, paths...),
   853  		},
   854  	}
   855  }
   856  
   857  // match paths are: realPath, virtualPath
   858  func match(classifier string, paths ...string) pkg.ClassifierMatch {
   859  	realPath := ""
   860  	if len(paths) > 0 {
   861  		realPath = paths[0]
   862  	}
   863  	virtualPath := ""
   864  	if len(paths) > 1 {
   865  		virtualPath = paths[1]
   866  	}
   867  	return pkg.ClassifierMatch{
   868  		Classifier: classifier,
   869  		Location: file.NewVirtualLocationFromCoordinates(
   870  			file.Coordinates{
   871  				RealPath: realPath,
   872  			},
   873  			virtualPath,
   874  		),
   875  	}
   876  }
   877  
   878  func assertPackagesAreEqual(t *testing.T, expected pkg.Package, p pkg.Package) {
   879  	var failMessages []string
   880  	expectedLocations := expected.Locations.ToSlice()
   881  	gotLocations := p.Locations.ToSlice()
   882  
   883  	if len(expectedLocations) != len(gotLocations) {
   884  		failMessages = append(failMessages, "locations are not equal length")
   885  	} else {
   886  		for i, expectedLocation := range expectedLocations {
   887  			gotLocation := gotLocations[i]
   888  			if expectedLocation.RealPath != gotLocation.RealPath {
   889  				failMessages = append(failMessages, fmt.Sprintf("locations do not match; expected: %v got: %v", expectedLocation.RealPath, gotLocation.RealPath))
   890  			}
   891  		}
   892  	}
   893  
   894  	m1 := expected.Metadata.(pkg.BinarySignature).Matches
   895  	m2 := p.Metadata.(pkg.BinarySignature).Matches
   896  	matches := true
   897  	if len(m1) == len(m2) {
   898  		for i, m1 := range m1 {
   899  			m2 := m2[i]
   900  			if m1.Classifier != m2.Classifier {
   901  				matches = false
   902  				break
   903  			}
   904  			if m1.Location.RealPath != "" && m1.Location.RealPath != m2.Location.RealPath {
   905  				matches = false
   906  				break
   907  			}
   908  			if m1.Location.AccessPath != "" && m1.Location.AccessPath != m2.Location.AccessPath {
   909  				matches = false
   910  				break
   911  			}
   912  		}
   913  	} else {
   914  		matches = false
   915  	}
   916  
   917  	if !matches {
   918  		failMessages = append(failMessages, "classifier matches not equal")
   919  	}
   920  	if expected.Name != p.Name ||
   921  		expected.Version != p.Version ||
   922  		expected.PURL != p.PURL {
   923  		failMessages = append(failMessages, "packages do not match")
   924  	}
   925  
   926  	if len(failMessages) > 0 {
   927  		assert.Failf(t, strings.Join(failMessages, "; "), "diff: %s",
   928  			cmp.Diff(expected, p,
   929  				cmp.Transformer("Locations", func(l file.LocationSet) []file.Location {
   930  					return l.ToSlice()
   931  				}),
   932  				cmpopts.IgnoreUnexported(pkg.Package{}, file.Location{}),
   933  				cmpopts.IgnoreFields(pkg.Package{}, "CPEs", "FoundBy", "MetadataType", "Type"),
   934  			))
   935  	}
   936  }
   937  
   938  type panicyResolver struct {
   939  	searchCalled bool
   940  }
   941  
   942  func (p *panicyResolver) FilesByExtension(_ ...string) ([]file.Location, error) {
   943  	p.searchCalled = true
   944  	return nil, errors.New("not implemented")
   945  }
   946  
   947  func (p *panicyResolver) FilesByBasename(_ ...string) ([]file.Location, error) {
   948  	p.searchCalled = true
   949  	return nil, errors.New("not implemented")
   950  }
   951  
   952  func (p *panicyResolver) FilesByBasenameGlob(_ ...string) ([]file.Location, error) {
   953  	p.searchCalled = true
   954  	return nil, errors.New("not implemented")
   955  }
   956  
   957  func (p *panicyResolver) FileContentsByLocation(_ file.Location) (io.ReadCloser, error) {
   958  	p.searchCalled = true
   959  	return nil, errors.New("not implemented")
   960  }
   961  
   962  func (p *panicyResolver) HasPath(_ string) bool {
   963  	return true
   964  }
   965  
   966  func (p *panicyResolver) FilesByPath(_ ...string) ([]file.Location, error) {
   967  	p.searchCalled = true
   968  	return nil, errors.New("not implemented")
   969  }
   970  
   971  func (p *panicyResolver) FilesByGlob(_ ...string) ([]file.Location, error) {
   972  	p.searchCalled = true
   973  	return nil, errors.New("not implemented")
   974  }
   975  
   976  func (p *panicyResolver) FilesByMIMEType(_ ...string) ([]file.Location, error) {
   977  	p.searchCalled = true
   978  	return nil, errors.New("not implemented")
   979  }
   980  
   981  func (p *panicyResolver) RelativeFileByPath(_ file.Location, _ string) *file.Location {
   982  	return nil
   983  }
   984  
   985  func (p *panicyResolver) AllLocations() <-chan file.Location {
   986  	return nil
   987  }
   988  
   989  func (p *panicyResolver) FileMetadataByLocation(_ file.Location) (file.Metadata, error) {
   990  	return file.Metadata{}, errors.New("not implemented")
   991  }
   992  
   993  var _ file.Resolver = (*panicyResolver)(nil)
   994  
   995  func Test_Cataloger_ResilientToErrors(t *testing.T) {
   996  	c := NewCataloger()
   997  
   998  	resolver := &panicyResolver{}
   999  	_, _, err := c.Catalog(resolver)
  1000  	assert.NoError(t, err)
  1001  	assert.True(t, resolver.searchCalled)
  1002  }