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