github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/pkg/cataloger/apkdb/parse_apk_db_test.go (about)

     1  package apkdb
     2  
     3  import (
     4  	"io"
     5  	"os"
     6  	"path/filepath"
     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/syft/syft/artifact"
    16  	"github.com/anchore/syft/syft/file"
    17  	"github.com/anchore/syft/syft/linux"
    18  	"github.com/anchore/syft/syft/pkg"
    19  	"github.com/anchore/syft/syft/pkg/cataloger/generic"
    20  	"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
    21  )
    22  
    23  func TestExtraFileAttributes(t *testing.T) {
    24  	tests := []struct {
    25  		name     string
    26  		expected pkg.ApkMetadata
    27  	}{
    28  		{
    29  			name: "test extra file attributes (checksum) are ignored",
    30  			expected: pkg.ApkMetadata{
    31  				Files: []pkg.ApkFileRecord{
    32  					{
    33  						Path: "/usr",
    34  					},
    35  					{
    36  						Path: "/usr/lib",
    37  					},
    38  					{
    39  						Path: "/usr/lib/jvm",
    40  					},
    41  					{
    42  						Path: "/usr/lib/jvm/java-1.8-openjdk",
    43  					},
    44  					{
    45  						Path: "/usr/lib/jvm/java-1.8-openjdk/bin",
    46  					},
    47  					{
    48  						Path:        "/usr/lib/jvm/java-1.8-openjdk/bin/policytool",
    49  						OwnerUID:    "0",
    50  						OwnerGID:    "0",
    51  						Permissions: "755",
    52  						Digest: &file.Digest{
    53  							Algorithm: "'Q1'+base64(sha1)",
    54  							Value:     "Q1M0C9qfC/+kdRiOodeihG2GMRtkE=",
    55  						},
    56  					},
    57  				},
    58  			},
    59  		},
    60  	}
    61  
    62  	for _, test := range tests {
    63  		t.Run(test.name, func(t *testing.T) {
    64  			fixturePath := "test-fixtures/extra-file-attributes"
    65  			lrc := newLocationReadCloser(t, fixturePath)
    66  
    67  			pkgs, _, err := parseApkDB(nil, new(generic.Environment), lrc)
    68  			assert.NoError(t, err)
    69  			require.Len(t, pkgs, 1)
    70  			metadata := pkgs[0].Metadata.(pkg.ApkMetadata)
    71  
    72  			if diff := cmp.Diff(test.expected.Files, metadata.Files); diff != "" {
    73  				t.Errorf("Files mismatch (-want +got):\n%s", diff)
    74  			}
    75  		})
    76  	}
    77  }
    78  
    79  func TestSinglePackageDetails(t *testing.T) {
    80  	tests := []struct {
    81  		fixture  string
    82  		expected pkg.Package
    83  	}{
    84  		{
    85  			fixture: "test-fixtures/single",
    86  			expected: pkg.Package{
    87  				Name:    "musl-utils",
    88  				Version: "1.1.24-r2",
    89  				Licenses: pkg.NewLicenseSet(
    90  					pkg.NewLicense("MIT"),
    91  					pkg.NewLicense("BSD"),
    92  					pkg.NewLicense("GPL2+"),
    93  				),
    94  				Type:         pkg.ApkPkg,
    95  				MetadataType: pkg.ApkMetadataType,
    96  				Metadata: pkg.ApkMetadata{
    97  					Package:       "musl-utils",
    98  					OriginPackage: "musl",
    99  					Version:       "1.1.24-r2",
   100  					Description:   "the musl c library (libc) implementation",
   101  					Maintainer:    "Timo Teräs <timo.teras@iki.fi>",
   102  					Architecture:  "x86_64",
   103  					URL:           "https://musl.libc.org/",
   104  					Size:          37944,
   105  					InstalledSize: 151552,
   106  					Dependencies:  []string{"scanelf", "so:libc.musl-x86_64.so.1"},
   107  					Provides:      []string{"cmd:getconf", "cmd:getent", "cmd:iconv", "cmd:ldconfig", "cmd:ldd"},
   108  					Checksum:      "Q1bTtF5526tETKfL+lnigzIDvm+2o=",
   109  					GitCommit:     "4024cc3b29ad4c65544ad068b8f59172b5494306",
   110  					Files: []pkg.ApkFileRecord{
   111  						{
   112  							Path: "/sbin",
   113  						},
   114  						{
   115  							Path:        "/sbin/ldconfig",
   116  							OwnerUID:    "0",
   117  							OwnerGID:    "0",
   118  							Permissions: "755",
   119  							Digest: &file.Digest{
   120  								Algorithm: "'Q1'+base64(sha1)",
   121  								Value:     "Q1Kja2+POZKxEkUOZqwSjC6kmaED4=",
   122  							},
   123  						},
   124  						{
   125  							Path: "/usr",
   126  						},
   127  						{
   128  							Path: "/usr/bin",
   129  						},
   130  						{
   131  							Path:        "/usr/bin/iconv",
   132  							OwnerUID:    "0",
   133  							OwnerGID:    "0",
   134  							Permissions: "755",
   135  							Digest: &file.Digest{
   136  								Algorithm: "'Q1'+base64(sha1)",
   137  								Value:     "Q1CVmFbdY+Hv6/jAHl1gec2Kbx1EY=",
   138  							},
   139  						},
   140  						{
   141  							Path:        "/usr/bin/ldd",
   142  							OwnerUID:    "0",
   143  							OwnerGID:    "0",
   144  							Permissions: "755",
   145  							Digest: &file.Digest{
   146  								Algorithm: "'Q1'+base64(sha1)",
   147  								Value:     "Q1yFAhGggmL7ERgbIA7KQxyTzf3ks=",
   148  							},
   149  						},
   150  						{
   151  							Path:        "/usr/bin/getconf",
   152  							OwnerUID:    "0",
   153  							OwnerGID:    "0",
   154  							Permissions: "755",
   155  							Digest: &file.Digest{
   156  								Algorithm: "'Q1'+base64(sha1)",
   157  								Value:     "Q1dAdYK8M/INibRQF5B3Rw7cmNDDA=",
   158  							},
   159  						},
   160  						{
   161  							Path:        "/usr/bin/getent",
   162  							OwnerUID:    "0",
   163  							OwnerGID:    "0",
   164  							Permissions: "755",
   165  							Digest: &file.Digest{
   166  								Algorithm: "'Q1'+base64(sha1)",
   167  								Value:     "Q1eR2Dz/WylabgbWMTkd2+hGmEya4=",
   168  							},
   169  						},
   170  					},
   171  				},
   172  			},
   173  		},
   174  		{
   175  			fixture: "test-fixtures/empty-deps-and-provides",
   176  			expected: pkg.Package{
   177  				Name:    "alpine-baselayout-data",
   178  				Version: "3.4.0-r0",
   179  				Licenses: pkg.NewLicenseSet(
   180  					pkg.NewLicense("GPL-2.0-only"),
   181  				),
   182  				Type:         pkg.ApkPkg,
   183  				MetadataType: pkg.ApkMetadataType,
   184  				Metadata: pkg.ApkMetadata{
   185  					Package:       "alpine-baselayout-data",
   186  					OriginPackage: "alpine-baselayout",
   187  					Version:       "3.4.0-r0",
   188  					Description:   "Alpine base dir structure and init scripts",
   189  					Maintainer:    "Natanael Copa <ncopa@alpinelinux.org>",
   190  					Architecture:  "x86_64",
   191  					URL:           "https://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout",
   192  					Size:          11664,
   193  					InstalledSize: 77824,
   194  					Dependencies:  []string{},
   195  					Provides:      []string{},
   196  					Checksum:      "Q15ffjKT28lB7iSXjzpI/eDdYRCwM=",
   197  					GitCommit:     "bd965a7ebf7fd8f07d7a0cc0d7375bf3e4eb9b24",
   198  					Files: []pkg.ApkFileRecord{
   199  						{Path: "/etc"},
   200  						{Path: "/etc/fstab"},
   201  						{Path: "/etc/group"},
   202  						{Path: "/etc/hostname"},
   203  						{Path: "/etc/hosts"},
   204  						{Path: "/etc/inittab"},
   205  						{Path: "/etc/modules"},
   206  						{Path: "/etc/mtab", OwnerUID: "0", OwnerGID: "0", Permissions: "0777"},
   207  						{Path: "/etc/nsswitch.conf"},
   208  						{Path: "/etc/passwd"},
   209  						{Path: "/etc/profile"},
   210  						{Path: "/etc/protocols"},
   211  						{Path: "/etc/services"},
   212  						{Path: "/etc/shadow", OwnerUID: "0", OwnerGID: "148", Permissions: "0640"},
   213  						{Path: "/etc/shells"},
   214  						{Path: "/etc/sysctl.conf"},
   215  					},
   216  				},
   217  			},
   218  		},
   219  		{
   220  			fixture: "test-fixtures/base",
   221  			expected: pkg.Package{
   222  				Name:    "alpine-baselayout",
   223  				Version: "3.2.0-r6",
   224  				Licenses: pkg.NewLicenseSet(
   225  					pkg.NewLicense("GPL-2.0-only"),
   226  				),
   227  				Type:         pkg.ApkPkg,
   228  				PURL:         "",
   229  				MetadataType: pkg.ApkMetadataType,
   230  				Metadata: pkg.ApkMetadata{
   231  					Package:       "alpine-baselayout",
   232  					OriginPackage: "alpine-baselayout",
   233  					Version:       "3.2.0-r6",
   234  					Description:   "Alpine base dir structure and init scripts",
   235  					Maintainer:    "Natanael Copa <ncopa@alpinelinux.org>",
   236  					Architecture:  "x86_64",
   237  					URL:           "https://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout",
   238  					Size:          19917,
   239  					InstalledSize: 409600,
   240  					Dependencies:  []string{"/bin/sh", "so:libc.musl-x86_64.so.1"},
   241  					Provides:      []string{"cmd:mkmntdirs"},
   242  					Checksum:      "Q1myMNfd7u5v5UTgNHeq1e31qTjZU=",
   243  					GitCommit:     "e1c51734fa96fa4bac92e9f14a474324c67916fc",
   244  					Files: []pkg.ApkFileRecord{
   245  						{
   246  							Path: "/dev",
   247  						},
   248  						{
   249  							Path: "/dev/pts",
   250  						},
   251  						{
   252  							Path: "/dev/shm",
   253  						},
   254  						{
   255  							Path: "/etc",
   256  						},
   257  						{
   258  							Path: "/etc/fstab",
   259  							Digest: &file.Digest{
   260  								Algorithm: "'Q1'+base64(sha1)",
   261  								Value:     "Q11Q7hNe8QpDS531guqCdrXBzoA/o=",
   262  							},
   263  						},
   264  						{
   265  							Path: "/etc/group",
   266  							Digest: &file.Digest{
   267  								Algorithm: "'Q1'+base64(sha1)",
   268  								Value:     "Q1oJ16xWudgKOrXIEquEDzlF2Lsm4=",
   269  							},
   270  						},
   271  						{
   272  							Path: "/etc/hostname",
   273  							Digest: &file.Digest{
   274  								Algorithm: "'Q1'+base64(sha1)",
   275  								Value:     "Q16nVwYVXP/tChvUPdukVD2ifXOmc=",
   276  							},
   277  						},
   278  						{
   279  							Path: "/etc/hosts",
   280  							Digest: &file.Digest{
   281  								Algorithm: "'Q1'+base64(sha1)",
   282  								Value:     "Q1BD6zJKZTRWyqGnPi4tSfd3krsMU=",
   283  							},
   284  						},
   285  						{
   286  							Path: "/etc/inittab",
   287  							Digest: &file.Digest{
   288  								Algorithm: "'Q1'+base64(sha1)",
   289  								Value:     "Q1TsthbhW7QzWRe1E/NKwTOuD4pHc=",
   290  							},
   291  						},
   292  						{
   293  							Path: "/etc/modules",
   294  							Digest: &file.Digest{
   295  								Algorithm: "'Q1'+base64(sha1)",
   296  								Value:     "Q1toogjUipHGcMgECgPJX64SwUT1M=",
   297  							},
   298  						},
   299  						{
   300  							Path: "/etc/motd",
   301  							Digest: &file.Digest{
   302  								Algorithm: "'Q1'+base64(sha1)",
   303  								Value:     "Q1XmduVVNURHQ27TvYp1Lr5TMtFcA=",
   304  							},
   305  						},
   306  						{
   307  							Path:        "/etc/mtab",
   308  							OwnerUID:    "0",
   309  							OwnerGID:    "0",
   310  							Permissions: "777",
   311  							Digest: &file.Digest{
   312  								Algorithm: "'Q1'+base64(sha1)",
   313  								Value:     "Q1kiljhXXH1LlQroHsEJIkPZg2eiw=",
   314  							},
   315  						},
   316  						{
   317  							Path: "/etc/passwd",
   318  							Digest: &file.Digest{
   319  								Algorithm: "'Q1'+base64(sha1)",
   320  								Value:     "Q1TchuuLUfur0izvfZQZxgN/LJhB8=",
   321  							},
   322  						},
   323  						{
   324  							Path: "/etc/profile",
   325  							Digest: &file.Digest{
   326  								Algorithm: "'Q1'+base64(sha1)",
   327  								Value:     "Q1KpFb8kl5LvwXWlY3e58FNsjrI34=",
   328  							},
   329  						},
   330  						{
   331  							Path: "/etc/protocols",
   332  							Digest: &file.Digest{
   333  								Algorithm: "'Q1'+base64(sha1)",
   334  								Value:     "Q13FqXUnvuOpMDrH/6rehxuYAEE34=",
   335  							},
   336  						},
   337  						{
   338  							Path: "/etc/services",
   339  							Digest: &file.Digest{
   340  								Algorithm: "'Q1'+base64(sha1)",
   341  								Value:     "Q1C6HJNgQvLWqt5VY+n7MZJ1rsDuY=",
   342  							},
   343  						},
   344  						{
   345  							Path:        "/etc/shadow",
   346  							OwnerUID:    "0",
   347  							OwnerGID:    "42",
   348  							Permissions: "640",
   349  							Digest: &file.Digest{
   350  								Algorithm: "'Q1'+base64(sha1)",
   351  								Value:     "Q1ltrPIAW2zHeDiajsex2Bdmq3uqA=",
   352  							},
   353  						},
   354  						{
   355  							Path: "/etc/shells",
   356  							Digest: &file.Digest{
   357  								Algorithm: "'Q1'+base64(sha1)",
   358  								Value:     "Q1ojm2YdpCJ6B/apGDaZ/Sdb2xJkA=",
   359  							},
   360  						},
   361  						{
   362  							Path: "/etc/sysctl.conf",
   363  							Digest: &file.Digest{
   364  								Algorithm: "'Q1'+base64(sha1)",
   365  								Value:     "Q14upz3tfnNxZkIEsUhWn7Xoiw96g=",
   366  							},
   367  						},
   368  						{
   369  							Path: "/etc/apk",
   370  						},
   371  						{
   372  							Path: "/etc/conf.d",
   373  						},
   374  						{
   375  							Path: "/etc/crontabs",
   376  						},
   377  						{
   378  							Path:        "/etc/crontabs/root",
   379  							OwnerUID:    "0",
   380  							OwnerGID:    "0",
   381  							Permissions: "600",
   382  							Digest: &file.Digest{
   383  								Algorithm: "'Q1'+base64(sha1)",
   384  								Value:     "Q1vfk1apUWI4yLJGhhNRd0kJixfvY=",
   385  							},
   386  						},
   387  						{
   388  							Path: "/etc/init.d",
   389  						},
   390  						{
   391  							Path: "/etc/modprobe.d",
   392  						},
   393  						{
   394  							Path: "/etc/modprobe.d/aliases.conf",
   395  							Digest: &file.Digest{
   396  								Algorithm: "'Q1'+base64(sha1)",
   397  								Value:     "Q1WUbh6TBYNVK7e4Y+uUvLs/7viqk=",
   398  							},
   399  						},
   400  						{
   401  							Path: "/etc/modprobe.d/blacklist.conf",
   402  							Digest: &file.Digest{
   403  								Algorithm: "'Q1'+base64(sha1)",
   404  								Value:     "Q1xxYGU6S6TLQvb7ervPrWWwAWqMg=",
   405  							},
   406  						},
   407  						{
   408  							Path: "/etc/modprobe.d/i386.conf",
   409  							Digest: &file.Digest{
   410  								Algorithm: "'Q1'+base64(sha1)",
   411  								Value:     "Q1pnay/njn6ol9cCssL7KiZZ8etlc=",
   412  							},
   413  						},
   414  						{
   415  							Path: "/etc/modprobe.d/kms.conf",
   416  							Digest: &file.Digest{
   417  								Algorithm: "'Q1'+base64(sha1)",
   418  								Value:     "Q1ynbLn3GYDpvajba/ldp1niayeog=",
   419  							},
   420  						},
   421  						{
   422  							Path: "/etc/modules-load.d",
   423  						},
   424  						{
   425  							Path: "/etc/network",
   426  						},
   427  						{
   428  							Path: "/etc/network/if-down.d",
   429  						},
   430  						{
   431  							Path: "/etc/network/if-post-down.d",
   432  						},
   433  						{
   434  							Path: "/etc/network/if-pre-up.d",
   435  						},
   436  						{
   437  							Path: "/etc/network/if-up.d",
   438  						},
   439  						{
   440  							Path: "/etc/opt",
   441  						},
   442  						{
   443  							Path: "/etc/periodic",
   444  						},
   445  						{
   446  							Path: "/etc/periodic/15min",
   447  						},
   448  						{
   449  							Path: "/etc/periodic/daily",
   450  						},
   451  						{
   452  							Path: "/etc/periodic/hourly",
   453  						},
   454  						{
   455  							Path: "/etc/periodic/monthly",
   456  						},
   457  						{
   458  							Path: "/etc/periodic/weekly",
   459  						},
   460  						{
   461  							Path: "/etc/profile.d",
   462  						},
   463  						{
   464  							Path: "/etc/profile.d/color_prompt",
   465  							Digest: &file.Digest{
   466  								Algorithm: "'Q1'+base64(sha1)",
   467  								Value:     "Q10wL23GuSCVfumMRgakabUI6EsSk=",
   468  							},
   469  						},
   470  						{
   471  							Path: "/etc/profile.d/locale",
   472  							Digest: &file.Digest{
   473  								Algorithm: "'Q1'+base64(sha1)",
   474  								Value:     "Q1R4bIEpnKxxOSrlnZy9AoawqZ5DU=",
   475  							},
   476  						},
   477  						{
   478  							Path: "/etc/sysctl.d",
   479  						},
   480  						{
   481  							Path: "/home",
   482  						},
   483  						{
   484  							Path: "/lib",
   485  						},
   486  						{
   487  							Path: "/lib/firmware",
   488  						},
   489  						{
   490  							Path: "/lib/mdev",
   491  						},
   492  						{
   493  							Path: "/lib/modules-load.d",
   494  						},
   495  						{
   496  							Path: "/lib/sysctl.d",
   497  						},
   498  						{
   499  							Path: "/lib/sysctl.d/00-alpine.conf",
   500  							Digest: &file.Digest{
   501  								Algorithm: "'Q1'+base64(sha1)",
   502  								Value:     "Q1HpElzW1xEgmKfERtTy7oommnq6c=",
   503  							},
   504  						},
   505  						{
   506  							Path: "/media",
   507  						},
   508  						{
   509  							Path: "/media/cdrom",
   510  						},
   511  						{
   512  							Path: "/media/floppy",
   513  						},
   514  						{
   515  							Path: "/media/usb",
   516  						},
   517  						{
   518  							Path: "/mnt",
   519  						},
   520  						{
   521  							Path: "/opt",
   522  						},
   523  						{
   524  							Path: "/proc",
   525  						},
   526  						{
   527  							Path:        "/root",
   528  							OwnerUID:    "0",
   529  							OwnerGID:    "0",
   530  							Permissions: "700",
   531  						},
   532  						{
   533  							Path: "/run",
   534  						},
   535  						{
   536  							Path: "/sbin",
   537  						},
   538  						{
   539  							Path:        "/sbin/mkmntdirs",
   540  							OwnerUID:    "0",
   541  							OwnerGID:    "0",
   542  							Permissions: "755",
   543  							Digest: &file.Digest{
   544  								Algorithm: "'Q1'+base64(sha1)",
   545  								Value:     "Q1YeuSmC7iDbEWrusPzA/zUQF6YSg=",
   546  							},
   547  						},
   548  						{
   549  							Path: "/srv",
   550  						},
   551  						{
   552  							Path: "/sys",
   553  						},
   554  						{
   555  							Path:        "/tmp",
   556  							OwnerUID:    "0",
   557  							OwnerGID:    "0",
   558  							Permissions: "1777",
   559  						},
   560  						{
   561  							Path: "/usr",
   562  						},
   563  						{
   564  							Path: "/usr/lib",
   565  						},
   566  						{
   567  							Path: "/usr/lib/modules-load.d",
   568  						},
   569  						{
   570  							Path: "/usr/local",
   571  						},
   572  						{
   573  							Path: "/usr/local/bin",
   574  						},
   575  						{
   576  							Path: "/usr/local/lib",
   577  						},
   578  						{
   579  							Path: "/usr/local/share",
   580  						},
   581  						{
   582  							Path: "/usr/sbin",
   583  						},
   584  						{
   585  							Path: "/usr/share",
   586  						},
   587  						{
   588  							Path: "/usr/share/man",
   589  						},
   590  						{
   591  							Path: "/usr/share/misc",
   592  						},
   593  						{
   594  							Path: "/var",
   595  						},
   596  						{
   597  							Path:        "/var/run",
   598  							OwnerUID:    "0",
   599  							OwnerGID:    "0",
   600  							Permissions: "777",
   601  							Digest: &file.Digest{
   602  								Algorithm: "'Q1'+base64(sha1)",
   603  								Value:     "Q11/SNZz/8cK2dSKK+cJpVrZIuF4Q=",
   604  							},
   605  						},
   606  						{
   607  							Path: "/var/cache",
   608  						},
   609  						{
   610  							Path: "/var/cache/misc",
   611  						},
   612  						{
   613  							Path:        "/var/empty",
   614  							OwnerUID:    "0",
   615  							OwnerGID:    "0",
   616  							Permissions: "555",
   617  						},
   618  						{
   619  							Path: "/var/lib",
   620  						},
   621  						{
   622  							Path: "/var/lib/misc",
   623  						},
   624  						{
   625  							Path: "/var/local",
   626  						},
   627  						{
   628  							Path: "/var/lock",
   629  						},
   630  						{
   631  							Path: "/var/lock/subsys",
   632  						},
   633  						{
   634  							Path: "/var/log",
   635  						},
   636  						{
   637  							Path: "/var/mail",
   638  						},
   639  						{
   640  							Path: "/var/opt",
   641  						},
   642  						{
   643  							Path: "/var/spool",
   644  						},
   645  						{
   646  							Path:        "/var/spool/mail",
   647  							OwnerUID:    "0",
   648  							OwnerGID:    "0",
   649  							Permissions: "777",
   650  							Digest: &file.Digest{
   651  								Algorithm: "'Q1'+base64(sha1)",
   652  								Value:     "Q1dzbdazYZA2nTzSIG3YyNw7d4Juc=",
   653  							},
   654  						},
   655  						{
   656  							Path: "/var/spool/cron",
   657  						},
   658  						{
   659  							Path:        "/var/spool/cron/crontabs",
   660  							OwnerUID:    "0",
   661  							OwnerGID:    "0",
   662  							Permissions: "777",
   663  							Digest: &file.Digest{
   664  								Algorithm: "'Q1'+base64(sha1)",
   665  								Value:     "Q1OFZt+ZMp7j0Gny0rqSKuWJyqYmA=",
   666  							},
   667  						},
   668  						{
   669  							Path:        "/var/tmp",
   670  							OwnerUID:    "0",
   671  							OwnerGID:    "0",
   672  							Permissions: "1777",
   673  						},
   674  					},
   675  				},
   676  			},
   677  		},
   678  	}
   679  
   680  	for _, test := range tests {
   681  		t.Run(test.fixture, func(t *testing.T) {
   682  			fixtureLocation := file.NewLocation(test.fixture)
   683  			test.expected.Locations = file.NewLocationSet(fixtureLocation)
   684  			licenses := test.expected.Licenses.ToSlice()
   685  			for i := range licenses {
   686  				licenses[i].Locations.Add(fixtureLocation)
   687  			}
   688  			test.expected.Licenses = pkg.NewLicenseSet(licenses...)
   689  			pkgtest.TestFileParser(t, test.fixture, parseApkDB, []pkg.Package{test.expected}, nil)
   690  		})
   691  	}
   692  }
   693  
   694  func TestMultiplePackages(t *testing.T) {
   695  	fixture := "test-fixtures/multiple"
   696  	location := file.NewLocation(fixture)
   697  	fixtureLocationSet := file.NewLocationSet(location)
   698  	expectedPkgs := []pkg.Package{
   699  		{
   700  			Name:    "libc-utils",
   701  			Version: "0.7.2-r0",
   702  			Licenses: pkg.NewLicenseSet(
   703  				pkg.NewLicenseFromLocations("MPL-2.0 AND MIT", location),
   704  			),
   705  			Type:         pkg.ApkPkg,
   706  			PURL:         "pkg:apk/alpine/libc-utils@0.7.2-r0?arch=x86_64&upstream=libc-dev&distro=alpine-3.12",
   707  			Locations:    fixtureLocationSet,
   708  			MetadataType: pkg.ApkMetadataType,
   709  			Metadata: pkg.ApkMetadata{
   710  				Package:       "libc-utils",
   711  				OriginPackage: "libc-dev",
   712  				Maintainer:    "Natanael Copa <ncopa@alpinelinux.org>",
   713  				Version:       "0.7.2-r0",
   714  				Architecture:  "x86_64",
   715  				URL:           "http://alpinelinux.org",
   716  				Description:   "Meta package to pull in correct libc",
   717  				Size:          1175,
   718  				InstalledSize: 4096,
   719  				Checksum:      "Q1p78yvTLG094tHE1+dToJGbmYzQE=",
   720  				GitCommit:     "97b1c2842faa3bfa30f5811ffbf16d5ff9f1a479",
   721  				Dependencies:  []string{"musl-utils"},
   722  				Provides:      []string{},
   723  				Files:         []pkg.ApkFileRecord{},
   724  			},
   725  		},
   726  		{
   727  			Name:      "musl-utils",
   728  			Version:   "1.1.24-r2",
   729  			Type:      pkg.ApkPkg,
   730  			PURL:      "pkg:apk/alpine/musl-utils@1.1.24-r2?arch=x86_64&upstream=musl&distro=alpine-3.12",
   731  			Locations: fixtureLocationSet,
   732  			Licenses: pkg.NewLicenseSet(
   733  				pkg.NewLicenseFromLocations("MIT", location),
   734  				pkg.NewLicenseFromLocations("BSD", location),
   735  				pkg.NewLicenseFromLocations("GPL2+", location),
   736  			),
   737  			MetadataType: pkg.ApkMetadataType,
   738  			Metadata: pkg.ApkMetadata{
   739  				Package:       "musl-utils",
   740  				OriginPackage: "musl",
   741  				Version:       "1.1.24-r2",
   742  				Description:   "the musl c library (libc) implementation",
   743  				Maintainer:    "Timo Teräs <timo.teras@iki.fi>",
   744  				Architecture:  "x86_64",
   745  				URL:           "https://musl.libc.org/",
   746  				Size:          37944,
   747  				InstalledSize: 151552,
   748  				GitCommit:     "4024cc3b29ad4c65544ad068b8f59172b5494306",
   749  				Dependencies:  []string{"scanelf", "so:libc.musl-x86_64.so.1"},
   750  				Provides:      []string{"cmd:getconf", "cmd:getent", "cmd:iconv", "cmd:ldconfig", "cmd:ldd"},
   751  				Checksum:      "Q1bTtF5526tETKfL+lnigzIDvm+2o=",
   752  				Files: []pkg.ApkFileRecord{
   753  					{
   754  						Path: "/sbin",
   755  					},
   756  					{
   757  						Path:        "/sbin/ldconfig",
   758  						OwnerUID:    "0",
   759  						OwnerGID:    "0",
   760  						Permissions: "755",
   761  						Digest: &file.Digest{
   762  							Algorithm: "'Q1'+base64(sha1)",
   763  							Value:     "Q1Kja2+POZKxEkUOZqwSjC6kmaED4=",
   764  						},
   765  					},
   766  					{
   767  						Path: "/usr",
   768  					},
   769  					{
   770  						Path: "/usr/bin",
   771  					},
   772  					{
   773  						Path:        "/usr/bin/iconv",
   774  						OwnerUID:    "0",
   775  						OwnerGID:    "0",
   776  						Permissions: "755",
   777  						Digest: &file.Digest{
   778  							Algorithm: "'Q1'+base64(sha1)",
   779  							Value:     "Q1CVmFbdY+Hv6/jAHl1gec2Kbx1EY=",
   780  						},
   781  					},
   782  					{
   783  						Path:        "/usr/bin/ldd",
   784  						OwnerUID:    "0",
   785  						OwnerGID:    "0",
   786  						Permissions: "755",
   787  						Digest: &file.Digest{
   788  							Algorithm: "'Q1'+base64(sha1)",
   789  							Value:     "Q1yFAhGggmL7ERgbIA7KQxyTzf3ks=",
   790  						},
   791  					},
   792  					{
   793  						Path:        "/usr/bin/getconf",
   794  						OwnerUID:    "0",
   795  						OwnerGID:    "0",
   796  						Permissions: "755",
   797  						Digest: &file.Digest{
   798  							Algorithm: "'Q1'+base64(sha1)",
   799  							Value:     "Q1dAdYK8M/INibRQF5B3Rw7cmNDDA=",
   800  						},
   801  					},
   802  					{
   803  						Path:        "/usr/bin/getent",
   804  						OwnerUID:    "0",
   805  						OwnerGID:    "0",
   806  						Permissions: "755",
   807  						Digest: &file.Digest{
   808  							Algorithm: "'Q1'+base64(sha1)",
   809  							Value:     "Q1eR2Dz/WylabgbWMTkd2+hGmEya4=",
   810  						},
   811  					},
   812  				},
   813  			},
   814  		},
   815  	}
   816  
   817  	expectedRelationships := []artifact.Relationship{
   818  		{
   819  			From: expectedPkgs[1], // musl-utils
   820  			To:   expectedPkgs[0], // libc-utils
   821  			Type: artifact.DependencyOfRelationship,
   822  			Data: nil,
   823  		},
   824  	}
   825  
   826  	env := generic.Environment{LinuxRelease: &linux.Release{
   827  		ID:        "alpine",
   828  		VersionID: "3.12",
   829  	}}
   830  
   831  	pkgtest.TestFileParserWithEnv(t, fixture, parseApkDB, &env, expectedPkgs, expectedRelationships)
   832  }
   833  
   834  func Test_processChecksum(t *testing.T) {
   835  	tests := []struct {
   836  		name  string
   837  		value string
   838  		want  file.Digest
   839  	}{
   840  		{
   841  			name:  "md5",
   842  			value: "38870ede8700535d7382ff66a46fcc2f",
   843  			want: file.Digest{
   844  				Algorithm: "md5",
   845  				Value:     "38870ede8700535d7382ff66a46fcc2f",
   846  			},
   847  		},
   848  		{
   849  			name:  "sha1",
   850  			value: "Q1Kja2+POZKxEkUOZqwSjC6kmaED4=",
   851  			want: file.Digest{
   852  				Algorithm: "'Q1'+base64(sha1)",
   853  				Value:     "Q1Kja2+POZKxEkUOZqwSjC6kmaED4=",
   854  			},
   855  		},
   856  	}
   857  
   858  	for _, test := range tests {
   859  		t.Run(test.name, func(t *testing.T) {
   860  			assert.Equal(t, &test.want, processChecksum(test.value))
   861  		})
   862  	}
   863  }
   864  
   865  func Test_discoverPackageDependencies(t *testing.T) {
   866  	tests := []struct {
   867  		name  string
   868  		genFn func() ([]pkg.Package, []artifact.Relationship)
   869  	}{
   870  		{
   871  			name: "has no dependency",
   872  			genFn: func() ([]pkg.Package, []artifact.Relationship) {
   873  				a := pkg.Package{
   874  					Name: "package-a",
   875  					Metadata: pkg.ApkMetadata{
   876  						Provides: []string{"a-thing"},
   877  					},
   878  				}
   879  				a.SetID()
   880  				b := pkg.Package{
   881  					Name: "package-b",
   882  					Metadata: pkg.ApkMetadata{
   883  						Provides: []string{"b-thing"},
   884  					},
   885  				}
   886  				b.SetID()
   887  
   888  				return []pkg.Package{a, b}, nil
   889  			},
   890  		},
   891  		{
   892  			name: "has 1 dependency",
   893  			genFn: func() ([]pkg.Package, []artifact.Relationship) {
   894  				a := pkg.Package{
   895  					Name: "package-a",
   896  					Metadata: pkg.ApkMetadata{
   897  						Dependencies: []string{"b-thing"},
   898  					},
   899  				}
   900  				a.SetID()
   901  				b := pkg.Package{
   902  					Name: "package-b",
   903  					Metadata: pkg.ApkMetadata{
   904  						Provides: []string{"b-thing"},
   905  					},
   906  				}
   907  				b.SetID()
   908  
   909  				return []pkg.Package{a, b}, []artifact.Relationship{
   910  					{
   911  						From: b,
   912  						To:   a,
   913  						Type: artifact.DependencyOfRelationship,
   914  					},
   915  				}
   916  			},
   917  		},
   918  		{
   919  			name: "strip version specifiers",
   920  			genFn: func() ([]pkg.Package, []artifact.Relationship) {
   921  				a := pkg.Package{
   922  					Name: "package-a",
   923  					Metadata: pkg.ApkMetadata{
   924  						Dependencies: []string{"so:libc.musl-x86_64.so.1"},
   925  					},
   926  				}
   927  				a.SetID()
   928  				b := pkg.Package{
   929  					Name: "package-b",
   930  					Metadata: pkg.ApkMetadata{
   931  						Provides: []string{"so:libc.musl-x86_64.so.1=1"},
   932  					},
   933  				}
   934  				b.SetID()
   935  
   936  				return []pkg.Package{a, b}, []artifact.Relationship{
   937  					{
   938  						From: b,
   939  						To:   a,
   940  						Type: artifact.DependencyOfRelationship,
   941  					},
   942  				}
   943  			},
   944  		},
   945  		{
   946  			name: "strip version specifiers with empty provides value",
   947  			genFn: func() ([]pkg.Package, []artifact.Relationship) {
   948  				a := pkg.Package{
   949  					Name: "package-a",
   950  					Metadata: pkg.ApkMetadata{
   951  						Dependencies: []string{"so:libc.musl-x86_64.so.1"},
   952  					},
   953  				}
   954  				a.SetID()
   955  				b := pkg.Package{
   956  					Name: "package-b",
   957  					Metadata: pkg.ApkMetadata{
   958  						Provides: []string{""},
   959  					},
   960  				}
   961  				b.SetID()
   962  
   963  				return []pkg.Package{a, b}, nil
   964  			},
   965  		},
   966  		{
   967  			name: "depends on package name",
   968  			genFn: func() ([]pkg.Package, []artifact.Relationship) {
   969  				a := pkg.Package{
   970  					Name: "package-a",
   971  					Metadata: pkg.ApkMetadata{
   972  						Dependencies: []string{"musl>=1.2"},
   973  					},
   974  				}
   975  				a.SetID()
   976  				b := pkg.Package{
   977  					Name: "musl",
   978  					Metadata: pkg.ApkMetadata{
   979  						Provides: []string{"so:libc.musl-x86_64.so.1=1"},
   980  					},
   981  				}
   982  				b.SetID()
   983  
   984  				return []pkg.Package{a, b}, []artifact.Relationship{
   985  					{
   986  						From: b,
   987  						To:   a,
   988  						Type: artifact.DependencyOfRelationship,
   989  					},
   990  				}
   991  			},
   992  		},
   993  		{
   994  			name: "depends on package file",
   995  			genFn: func() ([]pkg.Package, []artifact.Relationship) {
   996  				a := pkg.Package{
   997  					Name: "alpine-baselayout",
   998  					Metadata: pkg.ApkMetadata{
   999  						Dependencies: []string{"/bin/sh"},
  1000  					},
  1001  				}
  1002  				a.SetID()
  1003  				b := pkg.Package{
  1004  					Name: "busybox",
  1005  					Metadata: pkg.ApkMetadata{
  1006  						Provides: []string{"/bin/sh"},
  1007  					},
  1008  				}
  1009  				b.SetID()
  1010  
  1011  				return []pkg.Package{a, b}, []artifact.Relationship{
  1012  					{
  1013  						From: b,
  1014  						To:   a,
  1015  						Type: artifact.DependencyOfRelationship,
  1016  					},
  1017  				}
  1018  			},
  1019  		},
  1020  	}
  1021  
  1022  	for _, test := range tests {
  1023  		t.Run(test.name, func(t *testing.T) {
  1024  			pkgs, wantRelationships := test.genFn()
  1025  			gotRelationships := discoverPackageDependencies(pkgs)
  1026  			d := cmp.Diff(wantRelationships, gotRelationships, cmpopts.IgnoreUnexported(pkg.Package{}, file.LocationSet{}, pkg.LicenseSet{}))
  1027  			if d != "" {
  1028  				t.Fail()
  1029  				t.Log(d)
  1030  			}
  1031  		})
  1032  	}
  1033  }
  1034  
  1035  func TestPackageDbDependenciesByParse(t *testing.T) {
  1036  	tests := []struct {
  1037  		fixture  string
  1038  		expected map[string][]string
  1039  	}{
  1040  		{
  1041  			fixture: "test-fixtures/installed",
  1042  			expected: map[string][]string{
  1043  				"alpine-baselayout": {"alpine-baselayout-data", "busybox", "musl"},
  1044  				"apk-tools":         {"musl", "ca-certificates-bundle", "musl", "libcrypto1.1", "libssl1.1", "zlib"},
  1045  				"busybox":           {"musl"},
  1046  				"libc-utils":        {"musl-utils"},
  1047  				"libcrypto1.1":      {"musl"},
  1048  				"libssl1.1":         {"musl", "libcrypto1.1"},
  1049  				"musl-utils":        {"scanelf", "musl"},
  1050  				"scanelf":           {"musl"},
  1051  				"ssl_client":        {"musl", "libcrypto1.1", "libssl1.1"},
  1052  				"zlib":              {"musl"},
  1053  			},
  1054  		},
  1055  	}
  1056  
  1057  	for _, test := range tests {
  1058  		t.Run(test.fixture, func(t *testing.T) {
  1059  			f, err := os.Open(test.fixture)
  1060  			require.NoError(t, err)
  1061  			t.Cleanup(func() { require.NoError(t, f.Close()) })
  1062  
  1063  			pkgs, relationships, err := parseApkDB(nil, nil, file.LocationReadCloser{
  1064  				Location:   file.NewLocation(test.fixture),
  1065  				ReadCloser: f,
  1066  			})
  1067  			require.NoError(t, err)
  1068  
  1069  			pkgsByID := make(map[artifact.ID]pkg.Package)
  1070  			for _, p := range pkgs {
  1071  				p.SetID()
  1072  				pkgsByID[p.ID()] = p
  1073  			}
  1074  
  1075  			actualDependencies := make(map[string][]string)
  1076  
  1077  			for _, r := range relationships {
  1078  				switch r.Type {
  1079  				case artifact.DependencyOfRelationship:
  1080  					to := pkgsByID[r.To.ID()]
  1081  					from := pkgsByID[r.From.ID()]
  1082  					actualDependencies[to.Name] = append(actualDependencies[to.Name], from.Name)
  1083  				default:
  1084  					t.Fatalf("unexpected relationship type: %+v", r.Type)
  1085  				}
  1086  			}
  1087  
  1088  			if d := cmp.Diff(test.expected, actualDependencies); d != "" {
  1089  				t.Fail()
  1090  				t.Log(d)
  1091  			}
  1092  		})
  1093  	}
  1094  }
  1095  
  1096  func Test_parseApkDB_expectedPkgNames(t *testing.T) {
  1097  	tests := []struct {
  1098  		fixture      string
  1099  		wantPkgNames []string
  1100  		wantErr      assert.ErrorAssertionFunc
  1101  	}{
  1102  		{
  1103  			fixture: "very-large-entries",
  1104  			wantPkgNames: []string{
  1105  				"ca-certificates-bundle",
  1106  				"glibc-locale-posix",
  1107  				"wolfi-baselayout",
  1108  				"glibc",
  1109  				"libcrypto3",
  1110  				"libssl3",
  1111  				"zlib",
  1112  				"apk-tools",
  1113  				"ncurses-terminfo-base",
  1114  				"ncurses",
  1115  				"bash",
  1116  				"libcap",
  1117  				"bubblewrap",
  1118  				"busybox",
  1119  				"libbrotlicommon1",
  1120  				"libbrotlidec1",
  1121  				"libnghttp2-14",
  1122  				"libcurl4",
  1123  				"curl",
  1124  				"expat",
  1125  				"libpcre2-8-0",
  1126  				"git",
  1127  				"binutils",
  1128  				"libstdc++-dev",
  1129  				"libgcc",
  1130  				"libstdc++",
  1131  				"gmp",
  1132  				"isl",
  1133  				"mpfr",
  1134  				"mpc",
  1135  				"gcc",
  1136  				"linux-headers",
  1137  				"glibc-dev",
  1138  				"make",
  1139  				"pkgconf",
  1140  				"build-base",
  1141  				"go",
  1142  				"tree",
  1143  				"sdk",
  1144  			},
  1145  			wantErr: assert.NoError,
  1146  		},
  1147  	}
  1148  
  1149  	for _, test := range tests {
  1150  		t.Run(test.fixture, func(t *testing.T) {
  1151  			fixturePath := filepath.Join("test-fixtures", test.fixture)
  1152  			lrc := newLocationReadCloser(t, fixturePath)
  1153  
  1154  			pkgs, _, err := parseApkDB(nil, new(generic.Environment), lrc)
  1155  			test.wantErr(t, err)
  1156  
  1157  			names := toPackageNames(pkgs)
  1158  			if diff := cmp.Diff(test.wantPkgNames, names); diff != "" {
  1159  				t.Errorf("Packages mismatch (-want +got):\n%s", diff)
  1160  			}
  1161  		})
  1162  	}
  1163  }
  1164  
  1165  func toPackageNames(pkgs []pkg.Package) []string {
  1166  	names := make([]string, 0, len(pkgs))
  1167  	for _, p := range pkgs {
  1168  		names = append(names, p.Name)
  1169  	}
  1170  
  1171  	return names
  1172  }
  1173  
  1174  func newLocationReadCloser(t *testing.T, path string) file.LocationReadCloser {
  1175  	f, err := os.Open(path)
  1176  	require.NoError(t, err)
  1177  	t.Cleanup(func() { f.Close() })
  1178  
  1179  	return file.NewLocationReadCloser(file.NewLocation(path), f)
  1180  }
  1181  
  1182  func Test_stripVersionSpecifier(t *testing.T) {
  1183  	tests := []struct {
  1184  		name    string
  1185  		version string
  1186  		want    string
  1187  	}{
  1188  		{
  1189  			name:    "empty expression",
  1190  			version: "",
  1191  			want:    "",
  1192  		},
  1193  		{
  1194  			name:    "no expression",
  1195  			version: "cmd:foo",
  1196  			want:    "cmd:foo",
  1197  		},
  1198  		{
  1199  			name:    "=",
  1200  			version: "cmd:scanelf=1.3.4-r0",
  1201  			want:    "cmd:scanelf",
  1202  		},
  1203  		{
  1204  			name:    ">=",
  1205  			version: "cmd:scanelf>=1.3.4-r0",
  1206  			want:    "cmd:scanelf",
  1207  		},
  1208  		{
  1209  			name:    "<",
  1210  			version: "cmd:scanelf<1.3.4-r0",
  1211  			want:    "cmd:scanelf",
  1212  		},
  1213  	}
  1214  	for _, tt := range tests {
  1215  		t.Run(tt.name, func(t *testing.T) {
  1216  			assert.Equal(t, tt.want, stripVersionSpecifier(tt.version))
  1217  		})
  1218  	}
  1219  }
  1220  
  1221  func TestParseReleasesFromAPKRepository(t *testing.T) {
  1222  	tests := []struct {
  1223  		repos string
  1224  		want  []linux.Release
  1225  		desc  string
  1226  	}{
  1227  		{
  1228  			"https://foo.alpinelinux.org/alpine/v3.14/main",
  1229  			[]linux.Release{
  1230  				{Name: "Alpine Linux", ID: "alpine", VersionID: "3.14"},
  1231  			},
  1232  			"single repo",
  1233  		},
  1234  		{
  1235  			`https://foo.alpinelinux.org/alpine/v3.14/main
  1236  https://foo.alpinelinux.org/alpine/v3.14/community`,
  1237  			[]linux.Release{
  1238  				{Name: "Alpine Linux", ID: "alpine", VersionID: "3.14"},
  1239  				{Name: "Alpine Linux", ID: "alpine", VersionID: "3.14"},
  1240  			},
  1241  			"multiple repos",
  1242  		},
  1243  		{
  1244  			``,
  1245  			nil,
  1246  			"empty",
  1247  		},
  1248  		{
  1249  			`https://foo.bar.org/alpine/v3.14/main
  1250  https://foo.them.org/alpine/v3.14/community`,
  1251  			nil,
  1252  			"invalid repos",
  1253  		},
  1254  	}
  1255  	for _, tt := range tests {
  1256  		t.Run(tt.desc, func(t *testing.T) {
  1257  			reposReader := io.NopCloser(strings.NewReader(tt.repos))
  1258  			got := parseReleasesFromAPKRepository(file.LocationReadCloser{
  1259  				Location:   file.NewLocation("test"),
  1260  				ReadCloser: reposReader,
  1261  			})
  1262  			assert.Equal(t, tt.want, got)
  1263  		})
  1264  	}
  1265  }