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