Add python check (#955)

* Add PythonCheck (#423)

This adds a Python specific check that performs 2 tasks:

1) Verify the egg-info is folder (setuptools based) and not file based
   (distutils style)

2) Check for Python specific forbidden dirs and output various warnings and
   errors about them.

* PythonCheck: remove src-in-package check

* PythonCheck: extend test cases to /usr/lib64

* PythonCheck: ignore tests in -test{,s} packages.

* Update rpmlint/descriptions/PythonCheck.toml

Co-authored-by: Miro Hrončok <miro@hroncok.cz>

* PythonCheck: update to the latest codebase

* Rename test file to be able to use get_tested_package

* Improve PythonCheck error descrpitions

Co-authored-by: Miro Hrončok <miro@hroncok.cz>

* PythonCheck: juse endswith with tuple instead of or

* PythonCheck: Use pathlib.Path instead of os.path

* PythonCheck: recommend dist-info instead of egg-info

* PythonCheck: compile re at module level

* fixup! PythonCheck: Use pathlib.Path instead of os.path

* PythonCheck: Update test binary packages

These binary packages are generated from the following source spec:
https://build.opensuse.org/package/show/devel:openSUSE:Factory:rpmlint:tests/pythoncheck

* PythonCheck: Convert python-tests-in-package to Error

* PythonCheck: Do not warn on python modules named doc

* PythonCheck: Improve python-tests-in-package descriptions

Co-authored-by: Miro Hrončok <miro@hroncok.cz>

* PythonCheck: Improve python-doc-in-package descriptions

Co-authored-by: Miro Hrončok <miro@hroncok.cz>

* PythonCheck: Improve python-egg-info-distutils-style descriptions

Co-authored-by: Miro Hrončok <miro@hroncok.cz>

Co-authored-by: Johannes Grassler <johannes.grassler@suse.com>
Co-authored-by: Johannes Grassler <jgr-github@btw23.de>
Co-authored-by: Miro Hrončok <miro@hroncok.cz>
This commit is contained in:
danigm 2022-11-15 14:06:52 +01:00 committed by GitHub
parent 52f85dfedd
commit 3b98cb263a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 223 additions and 0 deletions

View File

@ -0,0 +1,76 @@
from pathlib import Path
import re
from rpmlint.checks.AbstractCheck import AbstractFilesCheck
# Warning messages
WARNS = {
'doc': 'python-doc-in-package',
}
# Error messages
ERRS = {
'egg-distutils': 'python-egg-info-distutils-style',
'tests': 'python-tests-in-site-packages',
'doc': 'python-doc-in-site-packages',
'src': 'python-src-in-site-packages',
'tests-package': 'python-tests-in-package',
}
SITELIB_RE = '/usr/lib[^/]*/python[^/]*/site-packages'
# Paths that shouldn't be in any packages, ever, because they clobber global
# name space.
ERR_PATHS = [
(re.compile(f'{SITELIB_RE}/tests?$'), 'tests'),
(re.compile(f'{SITELIB_RE}/docs?$'), 'doc'),
(re.compile(f'{SITELIB_RE}/src$'), 'src'),
(re.compile(f'{SITELIB_RE}/[^/]+/tests?$'), 'tests-package'),
]
# Paths that shouldn't be in any packages, but might need to be under
# sufficiently special circumstances.
WARN_PATHS = [
(re.compile(f'{SITELIB_RE}/[^/]+/docs?$'), 'doc'),
]
class PythonCheck(AbstractFilesCheck):
def __init__(self, config, output):
super().__init__(config, output, r'.*')
def check_file(self, pkg, filename):
egg_info_re = re.compile('.*egg-info$')
if egg_info_re.match(filename):
self.check_egginfo(pkg, filename)
for path_re, key in WARN_PATHS:
if path_re.match(filename):
if key == 'doc':
# Check for __init__.py file inside doc, maybe this is a
# module, not documentation
module_file = f'{filename}/__init__.py'
if module_file in pkg.files.keys():
continue
self.output.add_info('W', pkg, WARNS[key], filename)
for path_re, key in ERR_PATHS:
if path_re.match(filename):
if key == 'tests-package':
# Ignore "-test" and "-tests" packages since these are
# supposed to contain tests.
if pkg.name.endswith(('test', 'tests')):
continue
self.output.add_info('E', pkg, ERRS[key], filename)
def check_egginfo(self, pkg, filename):
"""
Check type of egg-info metadata and check Requires against egg-info
metadata if applicable.
"""
filepath = Path(pkg.dir_name() or '/', filename.lstrip('/'))
# Check for (deprecated) distutils style metadata.
if filepath.is_file():
self.output.add_info('E', pkg, ERRS['egg-distutils'], filename)

View File

@ -20,6 +20,7 @@ Checks = [
"MixedOwnershipCheck",
"PkgConfigCheck",
"PostCheck",
"PythonCheck",
"SignatureCheck",
"SourceCheck",
"SpecCheck",

View File

@ -0,0 +1,20 @@
python-tests-in-package="""
test/ or tests/ directory in Python package directory. Tests in %{python_sitelib}/<pkgname> should be packaged in a separate test or tests subpackage or not packaged."""
python-doc-in-package="""
doc/ or docs/ directory in Python package directory. Documentation should go into %{docdir}, not %{python_sitelib}/<pkgname>"""
python-egg-info-distutils-style="""
The Python package's egg-info is a distutils style file.
Please update to dist-info standardized core metadata.
"""
python-tests-in-site-packages="""
test/ or tests/ directory in %{python_sitelib}. This should never happen since
this is a global name space not owned by any particular package.
"""
python-doc-in-site-packages="""
doc/ or docs directory installed to %{python_sitelib}. This should never happen
since this is a global name space not owned by any particular package.
"""
python-src-in-site-packages="""
src/ directory installed to %{python_sitelib}. This should never happen
since this is a global name space not owned by any particular package.
"""

View File

@ -48,6 +48,7 @@ basic_tests = [
'MixedOwnershipCheck',
'PkgConfigCheck',
'PostCheck',
'PythonCheck',
'SignatureCheck',
'SourceCheck',
'SpecCheck',

125
test/test_python.py Normal file
View File

@ -0,0 +1,125 @@
import pytest
from rpmlint.checks.PythonCheck import PythonCheck
from rpmlint.filter import Filter
from Testing import CONFIG, get_tested_package
@pytest.fixture(scope='function', autouse=True)
def pythoncheck():
CONFIG.info = True
output = Filter(CONFIG)
test = PythonCheck(CONFIG, output)
return output, test
@pytest.mark.parametrize('package', ['binary/pythoncheck-python-doc-in-package'])
def test_python_doc_in_package(tmpdir, package, pythoncheck):
output, test = pythoncheck
test.check(get_tested_package(package, tmpdir))
out = output.print_results(output.results)
assert 'W: python-doc-in-package /usr/lib/python2.7/site-packages/python-mypackage/doc' in out
assert 'W: python-doc-in-package /usr/lib/python2.7/site-packages/python-mypackage/docs' in out
assert 'W: python-doc-in-package /usr/lib/python3.10/site-packages/python-mypackage/doc' in out
assert 'W: python-doc-in-package /usr/lib/python3.10/site-packages/python-mypackage/docs' in out
assert 'W: python-doc-in-package /usr/lib64/python2.7/site-packages/python-mypackage/doc' in out
assert 'W: python-doc-in-package /usr/lib64/python2.7/site-packages/python-mypackage/docs' in out
assert 'W: python-doc-in-package /usr/lib64/python3.10/site-packages/python-mypackage/doc' in out
assert 'W: python-doc-in-package /usr/lib64/python3.10/site-packages/python-mypackage/docs' in out
@pytest.mark.parametrize('package', ['binary/pythoncheck-python-doc-module-in-package'])
def test_python_doc_module_in_package(tmpdir, package, pythoncheck):
output, test = pythoncheck
test.check(get_tested_package(package, tmpdir))
out = output.print_results(output.results)
assert 'W: python-doc-in-package /usr/lib/python2.7/site-packages/python-mypackage/doc' not in out
assert 'W: python-doc-in-package /usr/lib/python2.7/site-packages/python-mypackage/docs' not in out
assert 'W: python-doc-in-package /usr/lib/python3.10/site-packages/python-mypackage/doc' not in out
assert 'W: python-doc-in-package /usr/lib/python3.10/site-packages/python-mypackage/docs' not in out
assert 'W: python-doc-in-package /usr/lib64/python2.7/site-packages/python-mypackage/doc' not in out
assert 'W: python-doc-in-package /usr/lib64/python2.7/site-packages/python-mypackage/docs' not in out
assert 'W: python-doc-in-package /usr/lib64/python3.10/site-packages/python-mypackage/doc' not in out
assert 'W: python-doc-in-package /usr/lib64/python3.10/site-packages/python-mypackage/docs' not in out
@pytest.mark.parametrize('package', ['binary/pythoncheck-python-tests-in-package2'])
def test_python_tests_in_package(tmpdir, package, pythoncheck):
output, test = pythoncheck
test.check(get_tested_package(package, tmpdir))
out = output.print_results(output.results)
assert 'E: python-tests-in-package /usr/lib/python2.7/site-packages/python-mypackage/test' in out
assert 'E: python-tests-in-package /usr/lib/python2.7/site-packages/python-mypackage/tests' in out
assert 'E: python-tests-in-package /usr/lib/python3.10/site-packages/python-mypackage/test' in out
assert 'E: python-tests-in-package /usr/lib/python3.10/site-packages/python-mypackage/tests' in out
assert 'E: python-tests-in-package /usr/lib64/python2.7/site-packages/python-mypackage/test' in out
assert 'E: python-tests-in-package /usr/lib64/python2.7/site-packages/python-mypackage/tests' in out
assert 'E: python-tests-in-package /usr/lib64/python3.10/site-packages/python-mypackage/test' in out
assert 'E: python-tests-in-package /usr/lib64/python3.10/site-packages/python-mypackage/tests' in out
@pytest.mark.parametrize('package', ['binary/pythoncheck-python-tests-in-package-test'])
def test_python_tests_in_test_package(tmpdir, package, pythoncheck):
output, test = pythoncheck
test.check(get_tested_package(package, tmpdir))
out = output.print_results(output.results)
assert 'E: python-tests-in-package /usr/lib/python2.7/site-packages/python-mypackage/test' not in out
assert 'E: python-tests-in-package /usr/lib/python2.7/site-packages/python-mypackage/tests' not in out
assert 'E: python-tests-in-package /usr/lib/python3.10/site-packages/python-mypackage/test' not in out
assert 'E: python-tests-in-package /usr/lib/python3.10/site-packages/python-mypackage/tests' not in out
assert 'E: python-tests-in-package /usr/lib64/python2.7/site-packages/python-mypackage/test' not in out
assert 'E: python-tests-in-package /usr/lib64/python2.7/site-packages/python-mypackage/tests' not in out
assert 'E: python-tests-in-package /usr/lib64/python3.10/site-packages/python-mypackage/test' not in out
assert 'E: python-tests-in-package /usr/lib64/python3.10/site-packages/python-mypackage/tests' not in out
@pytest.mark.parametrize('package', ['binary/pythoncheck-python-egg-info-distutils-style'])
def test_python_distutils_egg_info(tmpdir, package, pythoncheck):
output, test = pythoncheck
test.check(get_tested_package(package, tmpdir))
out = output.print_results(output.results)
assert 'E: python-egg-info-distutils-style /usr/lib/python2.7/site-packages/mydistutilspackage.egg-info' in out
assert 'E: python-egg-info-distutils-style /usr/lib/python3.10/site-packages/mydistutilspackage.egg-info' in out
assert 'E: python-egg-info-distutils-style /usr/lib64/python2.7/site-packages/mydistutilspackage.egg-info' in out
assert 'E: python-egg-info-distutils-style /usr/lib64/python3.10/site-packages/mydistutilspackage.egg-info' in out
@pytest.mark.parametrize('package', ['binary/pythoncheck-python-doc-in-site-packages'])
def test_python_doc_in_site_packages(tmpdir, package, pythoncheck):
output, test = pythoncheck
test.check(get_tested_package(package, tmpdir))
out = output.print_results(output.results)
assert 'E: python-doc-in-site-packages /usr/lib/python2.7/site-packages/doc' in out
assert 'E: python-doc-in-site-packages /usr/lib/python2.7/site-packages/docs' in out
assert 'E: python-doc-in-site-packages /usr/lib/python3.10/site-packages/doc' in out
assert 'E: python-doc-in-site-packages /usr/lib/python3.10/site-packages/docs' in out
assert 'E: python-doc-in-site-packages /usr/lib64/python2.7/site-packages/doc' in out
assert 'E: python-doc-in-site-packages /usr/lib64/python2.7/site-packages/docs' in out
assert 'E: python-doc-in-site-packages /usr/lib64/python3.10/site-packages/doc' in out
assert 'E: python-doc-in-site-packages /usr/lib64/python3.10/site-packages/docs' in out
@pytest.mark.parametrize('package', ['binary/pythoncheck-python-src-in-site-packages'])
def test_python_src_in_site_packages(tmpdir, package, pythoncheck):
output, test = pythoncheck
test.check(get_tested_package(package, tmpdir))
out = output.print_results(output.results)
assert 'E: python-src-in-site-packages /usr/lib/python2.7/site-packages/src' in out
assert 'E: python-src-in-site-packages /usr/lib/python3.10/site-packages/src' in out
assert 'E: python-src-in-site-packages /usr/lib64/python2.7/site-packages/src' in out
assert 'E: python-src-in-site-packages /usr/lib64/python3.10/site-packages/src' in out
@pytest.mark.parametrize('package', ['binary/pythoncheck-python-tests-in-site-packages'])
def test_python_tests_in_site_packages(tmpdir, package, pythoncheck):
output, test = pythoncheck
test.check(get_tested_package(package, tmpdir))
out = output.print_results(output.results)
assert 'E: python-tests-in-site-packages /usr/lib/python2.7/site-packages/test' in out
assert 'E: python-tests-in-site-packages /usr/lib/python2.7/site-packages/tests' in out
assert 'E: python-tests-in-site-packages /usr/lib/python3.10/site-packages/test' in out
assert 'E: python-tests-in-site-packages /usr/lib/python3.10/site-packages/tests' in out
assert 'E: python-tests-in-site-packages /usr/lib64/python2.7/site-packages/test' in out
assert 'E: python-tests-in-site-packages /usr/lib64/python2.7/site-packages/tests' in out
assert 'E: python-tests-in-site-packages /usr/lib64/python3.10/site-packages/test' in out
assert 'E: python-tests-in-site-packages /usr/lib64/python3.10/site-packages/tests' in out