github.com/nextlinux/gosbom@v0.81.1-0.20230627115839-1ff50c281391/gosbom/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/nextlinux/gosbom/gosbom/file"
    13  	"github.com/nextlinux/gosbom/gosbom/pkg"
    14  	"github.com/nextlinux/gosbom/gosbom/source"
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  
    18  	"github.com/anchore/stereoscope/pkg/imagetest"
    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  
   648  	for _, test := range tests {
   649  		t.Run(test.name, func(t *testing.T) {
   650  			c := NewCataloger()
   651  
   652  			src, err := source.NewFromDirectory(test.fixtureDir)
   653  			require.NoError(t, err)
   654  
   655  			resolver, err := src.FileResolver(source.SquashedScope)
   656  			require.NoError(t, err)
   657  
   658  			packages, _, err := c.Catalog(resolver)
   659  			require.NoError(t, err)
   660  
   661  			require.Len(t, packages, 1)
   662  
   663  			assertPackagesAreEqual(t, test.expected, packages[0])
   664  		})
   665  	}
   666  }
   667  
   668  func Test_Cataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T) {
   669  	tests := []struct {
   670  		name         string
   671  		fixtureImage string
   672  		expected     pkg.Package
   673  	}{
   674  		{
   675  			name:         "busybox-regression",
   676  			fixtureImage: "image-busybox",
   677  			expected: pkg.Package{
   678  				Name:      "busybox",
   679  				Version:   "1.35.0",
   680  				Locations: locations("/bin/["),
   681  				Metadata:  metadata("busybox-binary", "/bin/[", "/bin/busybox"),
   682  			},
   683  		},
   684  	}
   685  
   686  	for _, test := range tests {
   687  		t.Run(test.name, func(t *testing.T) {
   688  			c := NewCataloger()
   689  
   690  			img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureImage)
   691  			src, err := source.NewFromImage(img, "test-img")
   692  			require.NoError(t, err)
   693  
   694  			resolver, err := src.FileResolver(source.SquashedScope)
   695  			require.NoError(t, err)
   696  
   697  			packages, _, err := c.Catalog(resolver)
   698  			require.NoError(t, err)
   699  
   700  			for _, p := range packages {
   701  				expectedLocations := test.expected.Locations.ToSlice()
   702  				gotLocations := p.Locations.ToSlice()
   703  				require.Len(t, gotLocations, len(expectedLocations))
   704  
   705  				for i, expectedLocation := range expectedLocations {
   706  					gotLocation := gotLocations[i]
   707  					if expectedLocation.RealPath != gotLocation.RealPath {
   708  						t.Fatalf("locations do not match; expected: %v got: %v", expectedLocations, gotLocations)
   709  					}
   710  				}
   711  
   712  				assertPackagesAreEqual(t, test.expected, p)
   713  			}
   714  		})
   715  	}
   716  }
   717  
   718  func TestClassifierCataloger_DefaultClassifiers_NegativeCases(t *testing.T) {
   719  	c := NewCataloger()
   720  
   721  	src, err := source.NewFromDirectory("test-fixtures/classifiers/negative")
   722  	assert.NoError(t, err)
   723  
   724  	resolver, err := src.FileResolver(source.SquashedScope)
   725  	assert.NoError(t, err)
   726  
   727  	actualResults, _, err := c.Catalog(resolver)
   728  	assert.NoError(t, err)
   729  	assert.Equal(t, 0, len(actualResults))
   730  }
   731  
   732  func locations(locations ...string) file.LocationSet {
   733  	var locs []file.Location
   734  	for _, s := range locations {
   735  		locs = append(locs, file.NewLocation(s))
   736  	}
   737  	return file.NewLocationSet(locs...)
   738  }
   739  
   740  // metadata paths are: realPath, virtualPath
   741  func metadata(classifier string, paths ...string) pkg.BinaryMetadata {
   742  	return pkg.BinaryMetadata{
   743  		Matches: []pkg.ClassifierMatch{
   744  			match(classifier, paths...),
   745  		},
   746  	}
   747  }
   748  
   749  // match paths are: realPath, virtualPath
   750  func match(classifier string, paths ...string) pkg.ClassifierMatch {
   751  	realPath := ""
   752  	if len(paths) > 0 {
   753  		realPath = paths[0]
   754  	}
   755  	virtualPath := ""
   756  	if len(paths) > 1 {
   757  		virtualPath = paths[1]
   758  	}
   759  	return pkg.ClassifierMatch{
   760  		Classifier: classifier,
   761  		Location: file.NewVirtualLocationFromCoordinates(
   762  			file.Coordinates{
   763  				RealPath: realPath,
   764  			},
   765  			virtualPath,
   766  		),
   767  	}
   768  }
   769  
   770  func assertPackagesAreEqual(t *testing.T, expected pkg.Package, p pkg.Package) {
   771  	var failMessages []string
   772  	expectedLocations := expected.Locations.ToSlice()
   773  	gotLocations := p.Locations.ToSlice()
   774  
   775  	if len(expectedLocations) != len(gotLocations) {
   776  		failMessages = append(failMessages, "locations are not equal length")
   777  	} else {
   778  		for i, expectedLocation := range expectedLocations {
   779  			gotLocation := gotLocations[i]
   780  			if expectedLocation.RealPath != gotLocation.RealPath {
   781  				failMessages = append(failMessages, fmt.Sprintf("locations do not match; expected: %v got: %v", expectedLocation.RealPath, gotLocation.RealPath))
   782  			}
   783  		}
   784  	}
   785  
   786  	m1 := expected.Metadata.(pkg.BinaryMetadata).Matches
   787  	m2 := p.Metadata.(pkg.BinaryMetadata).Matches
   788  	matches := true
   789  	if len(m1) == len(m2) {
   790  		for i, m1 := range m1 {
   791  			m2 := m2[i]
   792  			if m1.Classifier != m2.Classifier {
   793  				matches = false
   794  				break
   795  			}
   796  			if m1.Location.RealPath != "" && m1.Location.RealPath != m2.Location.RealPath {
   797  				matches = false
   798  				break
   799  			}
   800  			if m1.Location.VirtualPath != "" && m1.Location.VirtualPath != m2.Location.VirtualPath {
   801  				matches = false
   802  				break
   803  			}
   804  		}
   805  	} else {
   806  		matches = false
   807  	}
   808  
   809  	if !matches {
   810  		failMessages = append(failMessages, "classifier matches not equal")
   811  	}
   812  	if expected.Name != p.Name ||
   813  		expected.Version != p.Version ||
   814  		expected.PURL != p.PURL {
   815  		failMessages = append(failMessages, "packages do not match")
   816  	}
   817  
   818  	if len(failMessages) > 0 {
   819  		assert.Failf(t, strings.Join(failMessages, "; "), "diff: %s",
   820  			cmp.Diff(expected, p,
   821  				cmp.Transformer("Locations", func(l file.LocationSet) []file.Location {
   822  					return l.ToSlice()
   823  				}),
   824  				cmpopts.IgnoreUnexported(pkg.Package{}, file.Location{}),
   825  				cmpopts.IgnoreFields(pkg.Package{}, "CPEs", "FoundBy", "MetadataType", "Type"),
   826  			))
   827  	}
   828  }
   829  
   830  type panicyResolver struct {
   831  	searchCalled bool
   832  }
   833  
   834  func (p *panicyResolver) FilesByExtension(_ ...string) ([]file.Location, error) {
   835  	p.searchCalled = true
   836  	return nil, errors.New("not implemented")
   837  }
   838  
   839  func (p *panicyResolver) FilesByBasename(_ ...string) ([]file.Location, error) {
   840  	p.searchCalled = true
   841  	return nil, errors.New("not implemented")
   842  }
   843  
   844  func (p *panicyResolver) FilesByBasenameGlob(_ ...string) ([]file.Location, error) {
   845  	p.searchCalled = true
   846  	return nil, errors.New("not implemented")
   847  }
   848  
   849  func (p *panicyResolver) FileContentsByLocation(_ file.Location) (io.ReadCloser, error) {
   850  	p.searchCalled = true
   851  	return nil, errors.New("not implemented")
   852  }
   853  
   854  func (p *panicyResolver) HasPath(_ string) bool {
   855  	return true
   856  }
   857  
   858  func (p *panicyResolver) FilesByPath(_ ...string) ([]file.Location, error) {
   859  	p.searchCalled = true
   860  	return nil, errors.New("not implemented")
   861  }
   862  
   863  func (p *panicyResolver) FilesByGlob(_ ...string) ([]file.Location, error) {
   864  	p.searchCalled = true
   865  	return nil, errors.New("not implemented")
   866  }
   867  
   868  func (p *panicyResolver) FilesByMIMEType(_ ...string) ([]file.Location, error) {
   869  	p.searchCalled = true
   870  	return nil, errors.New("not implemented")
   871  }
   872  
   873  func (p *panicyResolver) RelativeFileByPath(_ file.Location, _ string) *file.Location {
   874  	return nil
   875  }
   876  
   877  func (p *panicyResolver) AllLocations() <-chan file.Location {
   878  	return nil
   879  }
   880  
   881  func (p *panicyResolver) FileMetadataByLocation(_ file.Location) (file.Metadata, error) {
   882  	return file.Metadata{}, errors.New("not implemented")
   883  }
   884  
   885  var _ file.Resolver = (*panicyResolver)(nil)
   886  
   887  func Test_Cataloger_ResilientToErrors(t *testing.T) {
   888  	c := NewCataloger()
   889  
   890  	resolver := &panicyResolver{}
   891  	_, _, err := c.Catalog(resolver)
   892  	assert.NoError(t, err)
   893  	assert.True(t, resolver.searchCalled)
   894  }