github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/pkg/cataloger/python/cataloger_test.go (about)

     1  package python
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/require"
     8  
     9  	"github.com/anchore/syft/syft/file"
    10  	"github.com/anchore/syft/syft/pkg"
    11  	"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
    12  )
    13  
    14  func Test_PackageCataloger(t *testing.T) {
    15  	tests := []struct {
    16  		name            string
    17  		fixtures        []string
    18  		expectedPackage pkg.Package
    19  	}{
    20  		{
    21  			name:     "egg-file-no-version",
    22  			fixtures: []string{"test-fixtures/no-version-py3.8.egg-info"},
    23  			expectedPackage: pkg.Package{
    24  				Name:     "no-version",
    25  				PURL:     "pkg:pypi/no-version",
    26  				Type:     pkg.PythonPkg,
    27  				Language: pkg.Python,
    28  				FoundBy:  "python-installed-package-cataloger",
    29  				Metadata: pkg.PythonPackage{
    30  					Name:                 "no-version",
    31  					SitePackagesRootPath: "test-fixtures",
    32  				},
    33  			},
    34  		},
    35  		{
    36  			name: "egg-info directory",
    37  			fixtures: []string{
    38  				"test-fixtures/egg-info/PKG-INFO",
    39  				"test-fixtures/egg-info/RECORD",
    40  				"test-fixtures/egg-info/top_level.txt",
    41  			},
    42  			expectedPackage: pkg.Package{
    43  				Name:     "requests",
    44  				Version:  "2.22.0",
    45  				PURL:     "pkg:pypi/requests@2.22.0",
    46  				Type:     pkg.PythonPkg,
    47  				Language: pkg.Python,
    48  				Licenses: pkg.NewLicenseSet(
    49  					pkg.NewLicenseFromLocations("Apache 2.0", file.NewLocation("test-fixtures/egg-info/PKG-INFO")),
    50  				),
    51  				FoundBy: "python-installed-package-cataloger",
    52  				Metadata: pkg.PythonPackage{
    53  					Name:                 "requests",
    54  					Version:              "2.22.0",
    55  					Platform:             "UNKNOWN",
    56  					Author:               "Kenneth Reitz",
    57  					AuthorEmail:          "me@kennethreitz.org",
    58  					SitePackagesRootPath: "test-fixtures",
    59  					Files: []pkg.PythonFileRecord{
    60  						{Path: "requests-2.22.0.dist-info/INSTALLER", Digest: &pkg.PythonFileDigest{"sha256", "zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg"}, Size: "4"},
    61  						{Path: "requests/__init__.py", Digest: &pkg.PythonFileDigest{"sha256", "PnKCgjcTq44LaAMzB-7--B2FdewRrE8F_vjZeaG9NhA"}, Size: "3921"},
    62  						{Path: "requests/__pycache__/__version__.cpython-38.pyc"},
    63  						{Path: "requests/__pycache__/utils.cpython-38.pyc"},
    64  						{Path: "requests/__version__.py", Digest: &pkg.PythonFileDigest{"sha256", "Bm-GFstQaFezsFlnmEMrJDe8JNROz9n2XXYtODdvjjc"}, Size: "436"},
    65  						{Path: "requests/utils.py", Digest: &pkg.PythonFileDigest{"sha256", "LtPJ1db6mJff2TJSJWKi7rBpzjPS3mSOrjC9zRhoD3A"}, Size: "30049"},
    66  					},
    67  					TopLevelPackages: []string{"requests"},
    68  				},
    69  			},
    70  		},
    71  		{
    72  			name: "egg-info directory case sensitive",
    73  			fixtures: []string{
    74  				"test-fixtures/casesensitive/EGG-INFO/PKG-INFO",
    75  				"test-fixtures/casesensitive/EGG-INFO/RECORD",
    76  				"test-fixtures/casesensitive/EGG-INFO/top_level.txt",
    77  			},
    78  			expectedPackage: pkg.Package{
    79  				Name:     "requests",
    80  				Version:  "2.22.0",
    81  				PURL:     "pkg:pypi/requests@2.22.0",
    82  				Type:     pkg.PythonPkg,
    83  				Language: pkg.Python,
    84  				Licenses: pkg.NewLicenseSet(
    85  					pkg.NewLicenseFromLocations("Apache 2.0", file.NewLocation("test-fixtures/casesensitive/EGG-INFO/PKG-INFO")),
    86  				),
    87  				FoundBy: "python-installed-package-cataloger",
    88  				Metadata: pkg.PythonPackage{
    89  					Name:                 "requests",
    90  					Version:              "2.22.0",
    91  					Platform:             "UNKNOWN",
    92  					Author:               "Kenneth Reitz",
    93  					AuthorEmail:          "me@kennethreitz.org",
    94  					SitePackagesRootPath: "test-fixtures/casesensitive",
    95  					Files: []pkg.PythonFileRecord{
    96  						{Path: "requests-2.22.0.dist-info/INSTALLER", Digest: &pkg.PythonFileDigest{"sha256", "zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg"}, Size: "4"},
    97  						{Path: "requests/__init__.py", Digest: &pkg.PythonFileDigest{"sha256", "PnKCgjcTq44LaAMzB-7--B2FdewRrE8F_vjZeaG9NhA"}, Size: "3921"},
    98  						{Path: "requests/__pycache__/__version__.cpython-38.pyc"},
    99  						{Path: "requests/__pycache__/utils.cpython-38.pyc"},
   100  						{Path: "requests/__version__.py", Digest: &pkg.PythonFileDigest{"sha256", "Bm-GFstQaFezsFlnmEMrJDe8JNROz9n2XXYtODdvjjc"}, Size: "436"},
   101  						{Path: "requests/utils.py", Digest: &pkg.PythonFileDigest{"sha256", "LtPJ1db6mJff2TJSJWKi7rBpzjPS3mSOrjC9zRhoD3A"}, Size: "30049"},
   102  					},
   103  					TopLevelPackages: []string{"requests"},
   104  				},
   105  			},
   106  		},
   107  		{
   108  			name: "dist-info directory",
   109  			fixtures: []string{
   110  				"test-fixtures/dist-info/METADATA",
   111  				"test-fixtures/dist-info/RECORD",
   112  				"test-fixtures/dist-info/top_level.txt",
   113  				"test-fixtures/dist-info/direct_url.json",
   114  			},
   115  			expectedPackage: pkg.Package{
   116  				Name:     "Pygments",
   117  				Version:  "2.6.1",
   118  				PURL:     "pkg:pypi/Pygments@2.6.1?vcs_url=git%2Bhttps://github.com/python-test/test.git%40aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
   119  				Type:     pkg.PythonPkg,
   120  				Language: pkg.Python,
   121  				Licenses: pkg.NewLicenseSet(
   122  					pkg.NewLicenseFromLocations("BSD License", file.NewLocation("test-fixtures/dist-info/METADATA")),
   123  				),
   124  				FoundBy: "python-installed-package-cataloger",
   125  				Metadata: pkg.PythonPackage{
   126  					Name:                 "Pygments",
   127  					Version:              "2.6.1",
   128  					Platform:             "any",
   129  					Author:               "Georg Brandl",
   130  					AuthorEmail:          "georg@python.org",
   131  					SitePackagesRootPath: "test-fixtures",
   132  					Files: []pkg.PythonFileRecord{
   133  						{Path: "../../../bin/pygmentize", Digest: &pkg.PythonFileDigest{"sha256", "dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8"}, Size: "220"},
   134  						{Path: "Pygments-2.6.1.dist-info/AUTHORS", Digest: &pkg.PythonFileDigest{"sha256", "PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY"}, Size: "8449"},
   135  						{Path: "Pygments-2.6.1.dist-info/RECORD"},
   136  						{Path: "pygments/__pycache__/__init__.cpython-38.pyc"},
   137  						{Path: "pygments/util.py", Digest: &pkg.PythonFileDigest{"sha256", "586xXHiJGGZxqk5PMBu3vBhE68DLuAe5MBARWrSPGxA"}, Size: "10778"},
   138  
   139  						{Path: "pygments/x_util.py", Digest: &pkg.PythonFileDigest{"sha256", "qpzzsOW31KT955agi-7NS--90I0iNiJCyLJQnRCHgKI="}, Size: "10778"},
   140  					},
   141  					TopLevelPackages: []string{"pygments", "something_else"},
   142  					DirectURLOrigin:  &pkg.PythonDirectURLOriginInfo{URL: "https://github.com/python-test/test.git", VCS: "git", CommitID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
   143  				},
   144  			},
   145  		},
   146  		{
   147  			name: "dist-info directory case sensitive",
   148  			fixtures: []string{
   149  				"test-fixtures/casesensitive/DIST-INFO/METADATA",
   150  				"test-fixtures/casesensitive/DIST-INFO/RECORD",
   151  				"test-fixtures/casesensitive/DIST-INFO/top_level.txt",
   152  				"test-fixtures/casesensitive/DIST-INFO/direct_url.json",
   153  			},
   154  			expectedPackage: pkg.Package{
   155  				Name:     "Pygments",
   156  				Version:  "2.6.1",
   157  				PURL:     "pkg:pypi/Pygments@2.6.1?vcs_url=git%2Bhttps://github.com/python-test/test.git%40aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
   158  				Type:     pkg.PythonPkg,
   159  				Language: pkg.Python,
   160  				Licenses: pkg.NewLicenseSet(
   161  					pkg.NewLicenseFromLocations("BSD License", file.NewLocation("test-fixtures/casesensitive/DIST-INFO/METADATA")),
   162  				),
   163  				FoundBy: "python-installed-package-cataloger",
   164  				Metadata: pkg.PythonPackage{
   165  					Name:                 "Pygments",
   166  					Version:              "2.6.1",
   167  					Platform:             "any",
   168  					Author:               "Georg Brandl",
   169  					AuthorEmail:          "georg@python.org",
   170  					SitePackagesRootPath: "test-fixtures/casesensitive",
   171  					Files: []pkg.PythonFileRecord{
   172  						{Path: "../../../bin/pygmentize", Digest: &pkg.PythonFileDigest{"sha256", "dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8"}, Size: "220"},
   173  						{Path: "Pygments-2.6.1.dist-info/AUTHORS", Digest: &pkg.PythonFileDigest{"sha256", "PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY"}, Size: "8449"},
   174  						{Path: "Pygments-2.6.1.dist-info/RECORD"},
   175  						{Path: "pygments/__pycache__/__init__.cpython-38.pyc"},
   176  						{Path: "pygments/util.py", Digest: &pkg.PythonFileDigest{"sha256", "586xXHiJGGZxqk5PMBu3vBhE68DLuAe5MBARWrSPGxA"}, Size: "10778"},
   177  
   178  						{Path: "pygments/x_util.py", Digest: &pkg.PythonFileDigest{"sha256", "qpzzsOW31KT955agi-7NS--90I0iNiJCyLJQnRCHgKI="}, Size: "10778"},
   179  					},
   180  					TopLevelPackages: []string{"pygments", "something_else"},
   181  					DirectURLOrigin:  &pkg.PythonDirectURLOriginInfo{URL: "https://github.com/python-test/test.git", VCS: "git", CommitID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
   182  				},
   183  			},
   184  		},
   185  		{
   186  			name: "malformed-record",
   187  			fixtures: []string{
   188  				"test-fixtures/malformed-record/dist-info/METADATA",
   189  				"test-fixtures/malformed-record/dist-info/RECORD",
   190  			},
   191  			expectedPackage: pkg.Package{
   192  				Name:     "Pygments",
   193  				Version:  "2.6.1",
   194  				PURL:     "pkg:pypi/Pygments@2.6.1",
   195  				Type:     pkg.PythonPkg,
   196  				Language: pkg.Python,
   197  				Licenses: pkg.NewLicenseSet(
   198  					pkg.NewLicenseFromLocations("BSD License", file.NewLocation("test-fixtures/malformed-record/dist-info/METADATA")),
   199  				),
   200  				FoundBy: "python-installed-package-cataloger",
   201  				Metadata: pkg.PythonPackage{
   202  					Name:                 "Pygments",
   203  					Version:              "2.6.1",
   204  					Platform:             "any",
   205  					Author:               "Georg Brandl",
   206  					AuthorEmail:          "georg@python.org",
   207  					SitePackagesRootPath: "test-fixtures/malformed-record",
   208  					Files: []pkg.PythonFileRecord{
   209  						{Path: "flask/json/tag.py", Digest: &pkg.PythonFileDigest{"sha256", "9ehzrmt5k7hxf7ZEK0NOs3swvQyU9fWNe-pnYe69N60"}, Size: "8223"},
   210  						{Path: "../../Scripts/flask.exe", Digest: &pkg.PythonFileDigest{"sha256", "mPrbVeZCDX20himZ_bRai1nCs_tgr7jHIOGZlcgn-T4"}, Size: "93063"},
   211  						{Path: "../../Scripts/flask.exe", Size: "89470", Digest: &pkg.PythonFileDigest{"sha256", "jvqh4N3qOqXLlq40i6ZOLCY9tAOwfwdzIpLDYhRjoqQ"}},
   212  						{Path: "Flask-1.0.2.dist-info/INSTALLER", Size: "4", Digest: &pkg.PythonFileDigest{"sha256", "zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg"}},
   213  					},
   214  				},
   215  			},
   216  		},
   217  		{
   218  			// in cases where the metadata file is available and the record is not we should still record there is a package
   219  			// additionally empty top_level.txt files should not result in an error
   220  			name:     "partial dist-info directory",
   221  			fixtures: []string{"test-fixtures/partial.dist-info/METADATA"},
   222  			expectedPackage: pkg.Package{
   223  				Name:     "Pygments",
   224  				Version:  "2.6.1",
   225  				PURL:     "pkg:pypi/Pygments@2.6.1",
   226  				Type:     pkg.PythonPkg,
   227  				Language: pkg.Python,
   228  				Licenses: pkg.NewLicenseSet(
   229  					pkg.NewLicenseFromLocations("BSD License", file.NewLocation("test-fixtures/partial.dist-info/METADATA")),
   230  				),
   231  				FoundBy: "python-installed-package-cataloger",
   232  				Metadata: pkg.PythonPackage{
   233  					Name:                 "Pygments",
   234  					Version:              "2.6.1",
   235  					Platform:             "any",
   236  					Author:               "Georg Brandl",
   237  					AuthorEmail:          "georg@python.org",
   238  					SitePackagesRootPath: "test-fixtures",
   239  				},
   240  			},
   241  		},
   242  		{
   243  			name:     "egg-info regular file",
   244  			fixtures: []string{"test-fixtures/test.egg-info"},
   245  			expectedPackage: pkg.Package{
   246  				Name:     "requests",
   247  				Version:  "2.22.0",
   248  				PURL:     "pkg:pypi/requests@2.22.0",
   249  				Type:     pkg.PythonPkg,
   250  				Language: pkg.Python,
   251  				Licenses: pkg.NewLicenseSet(
   252  					pkg.NewLicenseFromLocations("Apache 2.0", file.NewLocation("test-fixtures/test.egg-info")),
   253  				),
   254  				FoundBy: "python-installed-package-cataloger",
   255  				Metadata: pkg.PythonPackage{
   256  					Name:                 "requests",
   257  					Version:              "2.22.0",
   258  					Platform:             "UNKNOWN",
   259  					Author:               "Kenneth Reitz",
   260  					AuthorEmail:          "me@kennethreitz.org",
   261  					SitePackagesRootPath: "test-fixtures",
   262  				},
   263  			},
   264  		},
   265  	}
   266  
   267  	for _, test := range tests {
   268  		t.Run(test.name, func(t *testing.T) {
   269  			resolver := file.NewMockResolverForPaths(test.fixtures...)
   270  
   271  			locations, err := resolver.FilesByPath(test.fixtures...)
   272  			require.NoError(t, err)
   273  
   274  			test.expectedPackage.Locations = file.NewLocationSet(locations...)
   275  
   276  			pkgtest.NewCatalogTester().
   277  				WithResolver(resolver).
   278  				Expects([]pkg.Package{test.expectedPackage}, nil).
   279  				TestCataloger(t, NewInstalledPackageCataloger())
   280  		})
   281  	}
   282  }
   283  
   284  func Test_PackageCataloger_IgnorePackage(t *testing.T) {
   285  	tests := []struct {
   286  		MetadataFixture string
   287  	}{
   288  		{
   289  			MetadataFixture: "test-fixtures/Python-2.7.egg-info",
   290  		},
   291  		{
   292  			MetadataFixture: "test-fixtures/empty-1.0.0-py3.8.egg-info",
   293  		},
   294  	}
   295  
   296  	for _, test := range tests {
   297  		t.Run(test.MetadataFixture, func(t *testing.T) {
   298  			resolver := file.NewMockResolverForPaths(test.MetadataFixture)
   299  
   300  			actual, _, err := NewInstalledPackageCataloger().Catalog(context.Background(), resolver)
   301  			require.NoError(t, err)
   302  
   303  			if len(actual) != 0 {
   304  				t.Fatalf("Expected 0 packages but found: %d", len(actual))
   305  			}
   306  		})
   307  	}
   308  }
   309  
   310  func Test_IndexCataloger_Globs(t *testing.T) {
   311  	tests := []struct {
   312  		name     string
   313  		fixture  string
   314  		expected []string
   315  	}{
   316  		{
   317  			name:    "obtain index files",
   318  			fixture: "test-fixtures/glob-paths",
   319  			expected: []string{
   320  				"src/requirements.txt",
   321  				"src/extra-requirements.txt",
   322  				"src/requirements-dev.txt",
   323  				"src/1-requirements-dev.txt",
   324  				"src/setup.py",
   325  				"src/poetry.lock",
   326  				"src/Pipfile.lock",
   327  			},
   328  		},
   329  	}
   330  
   331  	for _, test := range tests {
   332  		t.Run(test.name, func(t *testing.T) {
   333  			pkgtest.NewCatalogTester().
   334  				FromDirectory(t, test.fixture).
   335  				ExpectsResolverContentQueries(test.expected).
   336  				TestCataloger(t, NewPackageCataloger(DefaultCatalogerConfig()))
   337  		})
   338  	}
   339  }
   340  
   341  func Test_PackageCataloger_Globs(t *testing.T) {
   342  	tests := []struct {
   343  		name     string
   344  		fixture  string
   345  		expected []string
   346  	}{
   347  		{
   348  			name:    "obtain index files",
   349  			fixture: "test-fixtures/glob-paths",
   350  			expected: []string{
   351  				"site-packages/v.DIST-INFO/METADATA",
   352  				"site-packages/w.EGG-INFO/PKG-INFO",
   353  				"site-packages/x.dist-info/METADATA",
   354  				"site-packages/y.egg-info/PKG-INFO",
   355  				"site-packages/z.egg-info",
   356  			},
   357  		},
   358  	}
   359  
   360  	for _, test := range tests {
   361  		t.Run(test.name, func(t *testing.T) {
   362  			pkgtest.NewCatalogTester().
   363  				FromDirectory(t, test.fixture).
   364  				ExpectsResolverContentQueries(test.expected).
   365  				TestCataloger(t, NewInstalledPackageCataloger())
   366  		})
   367  	}
   368  }