Add new options for %%py{3}_check_import: -f, -t, -e
-f: optionally read a file with module names to test -t: bool flag - if set, filter only top-level modules -e: optionally exclude module names matching the given glob (Unix shell-style wildcards) Importing all modules may cause bogus failures in some cases, eg. when the imported code assumes there is an existing graphical window. Such behaviour may be by design, hence for automatic processing it's more convinient to - in some cases - check only for top-level modules or filter out the troublemakers. Related: rhbz#1950291
This commit is contained in:
parent
5cf7712d6f
commit
eb3f38ce4b
152
import_all_modules.py
Normal file
152
import_all_modules.py
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
'''Script to perform import of each module given to %%py_check_import
|
||||||
|
'''
|
||||||
|
import argparse
|
||||||
|
import importlib
|
||||||
|
import fnmatch
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def read_modules_files(file_paths):
|
||||||
|
'''Read module names from the files (modules must be newline separated).
|
||||||
|
|
||||||
|
Return the module names list or, if no files were provided, an empty list.
|
||||||
|
'''
|
||||||
|
|
||||||
|
if not file_paths:
|
||||||
|
return []
|
||||||
|
|
||||||
|
modules = []
|
||||||
|
for file in file_paths:
|
||||||
|
file_contents = file.read_text()
|
||||||
|
modules.extend(file_contents.split())
|
||||||
|
return modules
|
||||||
|
|
||||||
|
|
||||||
|
def read_modules_from_cli(argv):
|
||||||
|
'''Read module names from command-line arguments (space or comma separated).
|
||||||
|
|
||||||
|
Return the module names list.
|
||||||
|
'''
|
||||||
|
|
||||||
|
if not argv:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# %%py3_check_import allows to separate module list with comma or whitespace,
|
||||||
|
# we need to unify the output to a list of particular elements
|
||||||
|
modules_as_str = ' '.join(argv)
|
||||||
|
modules = re.split(r'[\s,]+', modules_as_str)
|
||||||
|
return modules
|
||||||
|
|
||||||
|
|
||||||
|
def filter_top_level_modules_only(modules):
|
||||||
|
'''Filter out entries with nested modules (containing dot) ie. 'foo.bar'.
|
||||||
|
|
||||||
|
Return the list of top-level modules.
|
||||||
|
'''
|
||||||
|
|
||||||
|
return [module for module in modules if '.' not in module]
|
||||||
|
|
||||||
|
|
||||||
|
def any_match(text, globs):
|
||||||
|
'''Return True if any of given globs fnmatchcase's the given text.'''
|
||||||
|
|
||||||
|
return any(fnmatch.fnmatchcase(text, g) for g in globs)
|
||||||
|
|
||||||
|
|
||||||
|
def exclude_unwanted_module_globs(globs, modules):
|
||||||
|
'''Filter out entries which match the either of the globs given as argv.
|
||||||
|
|
||||||
|
Return the list of filtered modules.
|
||||||
|
'''
|
||||||
|
|
||||||
|
return [m for m in modules if not any_match(m, globs)]
|
||||||
|
|
||||||
|
|
||||||
|
def read_modules_from_all_args(args):
|
||||||
|
'''Return a joined list of modules from all given command-line arguments.
|
||||||
|
'''
|
||||||
|
|
||||||
|
modules = read_modules_files(args.filename)
|
||||||
|
modules.extend(read_modules_from_cli(args.modules))
|
||||||
|
if args.exclude:
|
||||||
|
modules = exclude_unwanted_module_globs(args.exclude, modules)
|
||||||
|
|
||||||
|
if args.top_level:
|
||||||
|
modules = filter_top_level_modules_only(modules)
|
||||||
|
|
||||||
|
# Error when someone accidentally managed to filter out everything
|
||||||
|
if len(modules) == 0:
|
||||||
|
raise ValueError('No modules to check were left')
|
||||||
|
|
||||||
|
return modules
|
||||||
|
|
||||||
|
|
||||||
|
def import_modules(modules):
|
||||||
|
'''Procedure to perform import check for each module name from the given list of modules.
|
||||||
|
'''
|
||||||
|
|
||||||
|
for module in modules:
|
||||||
|
print('Check import:', module, file=sys.stderr)
|
||||||
|
importlib.import_module(module)
|
||||||
|
|
||||||
|
|
||||||
|
def argparser():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Generate list of all importable modules for import check.'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'modules', nargs='*',
|
||||||
|
help=('Add modules to check the import (space or comma separated).'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-f', '--filename', action='append', type=Path,
|
||||||
|
help='Add importable module names list from file.',
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-t', '--top-level', action='store_true',
|
||||||
|
help='Check only top-level modules.',
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-e', '--exclude', action='append',
|
||||||
|
help='Provide modules globs to be excluded from the check.',
|
||||||
|
)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def remove_unwanteds_from_sys_path():
|
||||||
|
'''Remove cwd and this script's parent from sys.path for the import test.
|
||||||
|
Bring the original contents back after import is done (or failed)
|
||||||
|
'''
|
||||||
|
|
||||||
|
cwd_absolute = Path.cwd().absolute()
|
||||||
|
this_file_parent = Path(__file__).parent.absolute()
|
||||||
|
old_sys_path = list(sys.path)
|
||||||
|
for path in old_sys_path:
|
||||||
|
if Path(path).absolute() in (cwd_absolute, this_file_parent):
|
||||||
|
sys.path.remove(path)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
sys.path = old_sys_path
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
|
||||||
|
cli_args = argparser().parse_args(argv)
|
||||||
|
|
||||||
|
if not cli_args.modules and not cli_args.filename:
|
||||||
|
raise ValueError('No modules to check were provided')
|
||||||
|
|
||||||
|
modules = read_modules_from_all_args(cli_args)
|
||||||
|
|
||||||
|
with remove_unwanteds_from_sys_path():
|
||||||
|
import_modules(modules)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
@ -69,16 +69,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
# With $PATH and $PYTHONPATH set to the %%buildroot,
|
# With $PATH and $PYTHONPATH set to the %%buildroot,
|
||||||
# try to import the given Python module(s).
|
# try to import the Python module(s) given as command-line args or read from file (-f).
|
||||||
|
# Filter and check import on only top-level modules using -t flag.
|
||||||
|
# Exclude unwanted modules by passing their globs to -e option.
|
||||||
# Useful as a smoke test in %%check when running tests is not feasible.
|
# Useful as a smoke test in %%check when running tests is not feasible.
|
||||||
# Use spaces or commas as separators.
|
# Use spaces or commas as separators if providing list directly.
|
||||||
%py_check_import() %{expand:\\\
|
# Use newlines as separators if providing list in a file.
|
||||||
(cd %{_topdir} &&\\\
|
%py_check_import(e:tf:) %{expand:\\\
|
||||||
PATH="%{buildroot}%{_bindir}:$PATH"\\\
|
PATH="%{buildroot}%{_bindir}:$PATH"\\\
|
||||||
PYTHONPATH="${PYTHONPATH:-%{buildroot}%{python_sitearch}:%{buildroot}%{python_sitelib}}"\\\
|
PYTHONPATH="${PYTHONPATH:-%{buildroot}%{python_sitearch}:%{buildroot}%{python_sitelib}}"\\\
|
||||||
PYTHONDONTWRITEBYTECODE=1\\\
|
PYTHONDONTWRITEBYTECODE=1\\\
|
||||||
%{__python} -c "import %{lua:local m=rpm.expand('%{?*}'):gsub('[%s,]+', ', ');print(m)}"
|
%{__python} -%{py_shebang_flags} %{_rpmconfigdir}/redhat/import_all_modules.py %{?**}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
%python_provide() %{lua:
|
%python_provide() %{lua:
|
||||||
|
@ -67,16 +67,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
# With $PATH and $PYTHONPATH set to the %%buildroot,
|
# With $PATH and $PYTHONPATH set to the %%buildroot,
|
||||||
# try to import the given Python 3 module(s).
|
# try to import the Python 3 module(s) given as command-line args or read from file (-f).
|
||||||
|
# Filter and check import on only top-level modules using -t flag.
|
||||||
|
# Exclude unwanted modules by passing their globs to -e option.
|
||||||
# Useful as a smoke test in %%check when running tests is not feasible.
|
# Useful as a smoke test in %%check when running tests is not feasible.
|
||||||
# Use spaces or commas as separators.
|
# Use spaces or commas as separators if providing list directly.
|
||||||
%py3_check_import() %{expand:\\\
|
# Use newlines as separators if providing list in a file.
|
||||||
(cd %{_topdir} &&\\\
|
%py3_check_import(e:tf:) %{expand:\\\
|
||||||
PATH="%{buildroot}%{_bindir}:$PATH"\\\
|
PATH="%{buildroot}%{_bindir}:$PATH"\\\
|
||||||
PYTHONPATH="${PYTHONPATH:-%{buildroot}%{python3_sitearch}:%{buildroot}%{python3_sitelib}}"\\\
|
PYTHONPATH="${PYTHONPATH:-%{buildroot}%{python3_sitearch}:%{buildroot}%{python3_sitelib}}"\\\
|
||||||
PYTHONDONTWRITEBYTECODE=1\\\
|
PYTHONDONTWRITEBYTECODE=1\\\
|
||||||
%{__python3} -c "import %{lua:local m=rpm.expand('%{?*}'):gsub('[%s,]+', ', ');print(m)}"
|
%{__python3} -%{py3_shebang_flags} %{_rpmconfigdir}/redhat/import_all_modules.py %{?**}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# This only supports Python 3.5+ and will never work with Python 2.
|
# This only supports Python 3.5+ and will never work with Python 2.
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
Name: python-rpm-macros
|
Name: python-rpm-macros
|
||||||
Version: 3.9
|
Version: 3.9
|
||||||
Release: 44%{?dist}
|
Release: 45%{?dist}
|
||||||
Summary: The common Python RPM macros
|
Summary: The common Python RPM macros
|
||||||
URL: https://src.fedoraproject.org/rpms/python-rpm-macros/
|
URL: https://src.fedoraproject.org/rpms/python-rpm-macros/
|
||||||
|
|
||||||
# macros and lua: MIT, compileall2.py: PSFv2
|
# macros and lua: MIT
|
||||||
|
# import_all_modules.py: MIT
|
||||||
|
# compileall2.py: PSFv2
|
||||||
License: MIT and Python
|
License: MIT and Python
|
||||||
|
|
||||||
# Macros:
|
# Macros:
|
||||||
@ -19,6 +21,7 @@ Source201: python.lua
|
|||||||
# Python code
|
# Python code
|
||||||
%global compileall2_version 0.7.1
|
%global compileall2_version 0.7.1
|
||||||
Source301: https://github.com/fedora-python/compileall2/raw/v%{compileall2_version}/compileall2.py
|
Source301: https://github.com/fedora-python/compileall2/raw/v%{compileall2_version}/compileall2.py
|
||||||
|
Source302: import_all_modules.py
|
||||||
|
|
||||||
BuildArch: noarch
|
BuildArch: noarch
|
||||||
|
|
||||||
@ -75,6 +78,7 @@ install -p -m 644 -t %{buildroot}%{_rpmluadir}/fedora/srpm python.lua
|
|||||||
|
|
||||||
mkdir -p %{buildroot}%{_rpmconfigdir}/redhat
|
mkdir -p %{buildroot}%{_rpmconfigdir}/redhat
|
||||||
install -m 644 compileall2.py %{buildroot}%{_rpmconfigdir}/redhat/
|
install -m 644 compileall2.py %{buildroot}%{_rpmconfigdir}/redhat/
|
||||||
|
install -m 644 import_all_modules.py %{buildroot}%{_rpmconfigdir}/redhat/
|
||||||
|
|
||||||
|
|
||||||
%check
|
%check
|
||||||
@ -89,6 +93,7 @@ install -m 644 compileall2.py %{buildroot}%{_rpmconfigdir}/redhat/
|
|||||||
%files -n python-srpm-macros
|
%files -n python-srpm-macros
|
||||||
%{rpmmacrodir}/macros.python-srpm
|
%{rpmmacrodir}/macros.python-srpm
|
||||||
%{_rpmconfigdir}/redhat/compileall2.py
|
%{_rpmconfigdir}/redhat/compileall2.py
|
||||||
|
%{_rpmconfigdir}/redhat/import_all_modules.py
|
||||||
%{_rpmluadir}/fedora/srpm/python.lua
|
%{_rpmluadir}/fedora/srpm/python.lua
|
||||||
|
|
||||||
%files -n python3-rpm-macros
|
%files -n python3-rpm-macros
|
||||||
@ -96,6 +101,11 @@ install -m 644 compileall2.py %{buildroot}%{_rpmconfigdir}/redhat/
|
|||||||
|
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Mon Oct 25 2021 Karolina Surma <ksurma@redhat.com> - 3.9-45
|
||||||
|
- Introduce -f (read from file) option to %%py{3}_check_import
|
||||||
|
- Introduce -t (filter top-level modules) option to %%py{3}_check_import
|
||||||
|
- Introduce -e (exclude module globs) option to %%py{3}_check_import
|
||||||
|
|
||||||
* Thu Sep 09 2021 Miro Hrončok <mhroncok@redhat.com> - 3.9-44
|
* Thu Sep 09 2021 Miro Hrončok <mhroncok@redhat.com> - 3.9-44
|
||||||
- Set $RPM_BUILD_ROOT in %%{python3_...} macros
|
- Set $RPM_BUILD_ROOT in %%{python3_...} macros
|
||||||
to allow selecting alternate sysconfig install scheme based on that variable
|
to allow selecting alternate sysconfig install scheme based on that variable
|
||||||
|
@ -653,35 +653,36 @@ def test_python3_sitearch_value_alternate_python(lib, alt_x_y):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'args, imports',
|
'args',
|
||||||
[
|
[
|
||||||
('six', 'six'),
|
'six',
|
||||||
('five six seven', 'five, six, seven'),
|
'-f foo.txt',
|
||||||
('six,seven, eight', 'six, seven, eight'),
|
'-t -f foo.txt six, seven',
|
||||||
('six.quarter six.half,, SIX', 'six.quarter, six.half, SIX'),
|
'-e "foo*" -f foo.txt six, seven',
|
||||||
|
'six.quarter six.half,, SIX',
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@pytest.mark.parametrize('__python3',
|
@pytest.mark.parametrize('__python3',
|
||||||
[None,
|
[None,
|
||||||
f'/usr/bin/python{X_Y}',
|
f'/usr/bin/python{X_Y}',
|
||||||
'/usr/bin/pythonX.Y'])
|
'/usr/bin/pythonX.Y'])
|
||||||
def test_py3_check_import(args, imports, __python3, lib):
|
def test_py3_check_import(args, __python3, lib):
|
||||||
x_y = X_Y
|
x_y = X_Y
|
||||||
macors = {
|
macros = {
|
||||||
'buildroot': 'BUILDROOT',
|
'buildroot': 'BUILDROOT',
|
||||||
'_topdir': 'TOPDIR',
|
'_rpmconfigdir': 'RPMCONFIGDIR',
|
||||||
}
|
}
|
||||||
if __python3 is not None:
|
if __python3 is not None:
|
||||||
if 'X.Y' in __python3:
|
if 'X.Y' in __python3:
|
||||||
__python3 = __python3.replace('X.Y', get_alt_x_y())
|
__python3 = __python3.replace('X.Y', get_alt_x_y())
|
||||||
macors['__python3'] = __python3
|
macros['__python3'] = __python3
|
||||||
# If the __python3 command has version at the end, parse it and expect it.
|
# If the __python3 command has version at the end, parse it and expect it.
|
||||||
# Note that the command is used to determine %python3_sitelib and %python3_sitearch,
|
# Note that the command is used to determine %python3_sitelib and %python3_sitearch,
|
||||||
# so we only test known CPython schemes here and not PyPy for simplicity.
|
# so we only test known CPython schemes here and not PyPy for simplicity.
|
||||||
if (match := re.match(r'.+python(\d+\.\d+)$', __python3)):
|
if (match := re.match(r'.+python(\d+\.\d+)$', __python3)):
|
||||||
x_y = match.group(1)
|
x_y = match.group(1)
|
||||||
|
|
||||||
lines = rpm_eval(f'%py3_check_import {args}', **macors)
|
lines = rpm_eval(f'%py3_check_import {args}', **macros)
|
||||||
|
|
||||||
# An equality check is a bit inflexible here,
|
# An equality check is a bit inflexible here,
|
||||||
# every time we change the macro we need to change this test.
|
# every time we change the macro we need to change this test.
|
||||||
@ -689,11 +690,9 @@ def test_py3_check_import(args, imports, __python3, lib):
|
|||||||
# At least, let's make the lines saner to check:
|
# At least, let's make the lines saner to check:
|
||||||
lines = [line.rstrip('\\').strip() for line in lines]
|
lines = [line.rstrip('\\').strip() for line in lines]
|
||||||
expected = textwrap.dedent(fr"""
|
expected = textwrap.dedent(fr"""
|
||||||
(cd TOPDIR &&
|
|
||||||
PATH="BUILDROOT/usr/bin:$PATH"
|
PATH="BUILDROOT/usr/bin:$PATH"
|
||||||
PYTHONPATH="${{PYTHONPATH:-BUILDROOT/usr/{lib}/python{x_y}/site-packages:BUILDROOT/usr/lib/python{x_y}/site-packages}}"
|
PYTHONPATH="${{PYTHONPATH:-BUILDROOT/usr/{lib}/python{x_y}/site-packages:BUILDROOT/usr/lib/python{x_y}/site-packages}}"
|
||||||
PYTHONDONTWRITEBYTECODE=1
|
PYTHONDONTWRITEBYTECODE=1
|
||||||
{__python3 or '/usr/bin/python3'} -c "import {imports}"
|
{__python3 or '/usr/bin/python3'} -s RPMCONFIGDIR/redhat/import_all_modules.py {args}
|
||||||
)
|
|
||||||
""")
|
""")
|
||||||
assert lines == expected.splitlines()
|
assert lines == expected.splitlines()
|
||||||
|
392
tests/test_import_all_modules.py
Normal file
392
tests/test_import_all_modules.py
Normal file
@ -0,0 +1,392 @@
|
|||||||
|
from import_all_modules import argparser, exclude_unwanted_module_globs
|
||||||
|
from import_all_modules import main as modules_main
|
||||||
|
from import_all_modules import read_modules_from_cli, filter_top_level_modules_only
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import shlex
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def preserve_sys_path():
|
||||||
|
original_sys_path = list(sys.path)
|
||||||
|
yield
|
||||||
|
sys.path = original_sys_path
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def preserve_sys_modules():
|
||||||
|
original_sys_modules = dict(sys.modules)
|
||||||
|
yield
|
||||||
|
sys.modules = original_sys_modules
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'args, imports',
|
||||||
|
[
|
||||||
|
('six', ['six']),
|
||||||
|
('five six seven', ['five', 'six', 'seven']),
|
||||||
|
('six,seven, eight', ['six', 'seven', 'eight']),
|
||||||
|
('six.quarter six.half,, SIX', ['six.quarter', 'six.half', 'SIX']),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_read_modules_from_cli(args, imports):
|
||||||
|
argv = shlex.split(args)
|
||||||
|
cli_args = argparser().parse_args(argv)
|
||||||
|
assert read_modules_from_cli(cli_args.modules) == imports
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'all_mods, imports',
|
||||||
|
[
|
||||||
|
(['six'], ['six']),
|
||||||
|
(['five', 'six', 'seven'], ['five', 'six', 'seven']),
|
||||||
|
(['six.seven', 'eight'], ['eight']),
|
||||||
|
(['SIX', 'six.quarter', 'six.half.and.sth', 'seven'], ['SIX', 'seven']),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_filter_top_level_modules_only(all_mods, imports):
|
||||||
|
assert filter_top_level_modules_only(all_mods) == imports
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'globs, expected',
|
||||||
|
[
|
||||||
|
(['*.*'], ['foo', 'boo']),
|
||||||
|
(['?oo'], ['foo.bar', 'foo.bar.baz', 'foo.baz']),
|
||||||
|
(['*.baz'], ['foo', 'foo.bar', 'boo']),
|
||||||
|
(['foo'], ['foo.bar', 'foo.bar.baz', 'foo.baz', 'boo']),
|
||||||
|
(['foo*'], ['boo']),
|
||||||
|
(['foo*', '*bar'], ['boo']),
|
||||||
|
(['foo', 'bar'], ['foo.bar', 'foo.bar.baz', 'foo.baz', 'boo']),
|
||||||
|
(['*'], []),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_exclude_unwanted_module_globs(globs, expected):
|
||||||
|
my_modules = ['foo', 'foo.bar', 'foo.bar.baz', 'foo.baz', 'boo']
|
||||||
|
tested = exclude_unwanted_module_globs(globs, my_modules)
|
||||||
|
assert tested == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_with_all_args():
|
||||||
|
'''A smoke test, all args must be parsed correctly.'''
|
||||||
|
mods = ['foo', 'foo.bar', 'baz']
|
||||||
|
files = ['-f', './foo']
|
||||||
|
top = ['-t']
|
||||||
|
exclude = ['-e', 'foo*']
|
||||||
|
cli_args = argparser().parse_args([*mods, *files, *top, *exclude])
|
||||||
|
|
||||||
|
assert cli_args.filename == [Path('foo')]
|
||||||
|
assert cli_args.top_level is True
|
||||||
|
assert cli_args.modules == ['foo', 'foo.bar', 'baz']
|
||||||
|
assert cli_args.exclude == ['foo*']
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_without_filename_toplevel():
|
||||||
|
'''Modules provided on command line (without files) must be parsed correctly.'''
|
||||||
|
mods = ['foo', 'foo.bar', 'baz']
|
||||||
|
cli_args = argparser().parse_args(mods)
|
||||||
|
|
||||||
|
assert cli_args.filename is None
|
||||||
|
assert cli_args.top_level is False
|
||||||
|
assert cli_args.modules == ['foo', 'foo.bar', 'baz']
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_with_filename_no_cli_mods():
|
||||||
|
'''Files (without any modules provided on command line) must be parsed correctly.'''
|
||||||
|
|
||||||
|
files = ['-f', './foo', '-f', './bar', '-f', './baz']
|
||||||
|
cli_args = argparser().parse_args(files)
|
||||||
|
|
||||||
|
assert cli_args.filename == [Path('foo'), Path('./bar'), Path('./baz')]
|
||||||
|
assert not cli_args.top_level
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_raises_error_when_no_modules_provided():
|
||||||
|
'''If no filename nor modules were provided, ValueError is raised.'''
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
modules_main([])
|
||||||
|
|
||||||
|
|
||||||
|
def test_import_all_modules_does_not_import():
|
||||||
|
'''Ensure the files from /usr/lib/rpm/redhat cannot be imported and
|
||||||
|
checked for import'''
|
||||||
|
|
||||||
|
# We already imported it in this file once, make sure it's not imported
|
||||||
|
# from the cache
|
||||||
|
sys.modules.pop('import_all_modules')
|
||||||
|
with pytest.raises(ModuleNotFoundError):
|
||||||
|
modules_main(['import_all_modules'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_modules_from_cwd_not_found(tmp_path, monkeypatch):
|
||||||
|
test_module = tmp_path / 'this_is_a_module_in_cwd.py'
|
||||||
|
test_module.write_text('')
|
||||||
|
monkeypatch.chdir(tmp_path)
|
||||||
|
with pytest.raises(ModuleNotFoundError):
|
||||||
|
modules_main(['this_is_a_module_in_cwd'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_modules_from_sys_path_found(tmp_path):
|
||||||
|
test_module = tmp_path / 'this_is_a_module_in_sys_path.py'
|
||||||
|
test_module.write_text('')
|
||||||
|
sys.path.append(str(tmp_path))
|
||||||
|
modules_main(['this_is_a_module_in_sys_path'])
|
||||||
|
assert 'this_is_a_module_in_sys_path' in sys.modules
|
||||||
|
|
||||||
|
|
||||||
|
def test_modules_from_file_are_found(tmp_path):
|
||||||
|
test_file = tmp_path / 'this_is_a_file_in_tmp_path.txt'
|
||||||
|
test_file.write_text('math\nwave\nsunau\n')
|
||||||
|
|
||||||
|
# Make sure the tested modules are not already in sys.modules
|
||||||
|
for m in ('math', 'wave', 'sunau'):
|
||||||
|
sys.modules.pop(m, None)
|
||||||
|
|
||||||
|
modules_main(['-f', str(test_file)])
|
||||||
|
|
||||||
|
assert 'sunau' in sys.modules
|
||||||
|
assert 'math' in sys.modules
|
||||||
|
assert 'wave' in sys.modules
|
||||||
|
|
||||||
|
|
||||||
|
def test_modules_from_files_are_found(tmp_path):
|
||||||
|
test_file_1 = tmp_path / 'this_is_a_file_in_tmp_path_1.txt'
|
||||||
|
test_file_2 = tmp_path / 'this_is_a_file_in_tmp_path_2.txt'
|
||||||
|
test_file_3 = tmp_path / 'this_is_a_file_in_tmp_path_3.txt'
|
||||||
|
|
||||||
|
test_file_1.write_text('math\nwave\n')
|
||||||
|
test_file_2.write_text('sunau\npathlib\n')
|
||||||
|
test_file_3.write_text('logging\nsunau\n')
|
||||||
|
|
||||||
|
# Make sure the tested modules are not already in sys.modules
|
||||||
|
for m in ('math', 'wave', 'sunau', 'pathlib', 'logging'):
|
||||||
|
sys.modules.pop(m, None)
|
||||||
|
|
||||||
|
modules_main(['-f', str(test_file_1), '-f', str(test_file_2), '-f', str(test_file_3), ])
|
||||||
|
for module in ('sunau', 'math', 'wave', 'pathlib', 'logging'):
|
||||||
|
assert module in sys.modules
|
||||||
|
|
||||||
|
|
||||||
|
def test_nonexisting_modules_raise_exception_on_import(tmp_path):
|
||||||
|
test_file = tmp_path / 'this_is_a_file_in_tmp_path.txt'
|
||||||
|
test_file.write_text('nonexisting_module\nanother\n')
|
||||||
|
with pytest.raises(ModuleNotFoundError):
|
||||||
|
modules_main(['-f', str(test_file)])
|
||||||
|
|
||||||
|
|
||||||
|
def test_nested_modules_found_when_expected(tmp_path, monkeypatch, capsys):
|
||||||
|
|
||||||
|
# This one is supposed to raise an error
|
||||||
|
cwd_path = tmp_path / 'test_cwd'
|
||||||
|
Path.mkdir(cwd_path)
|
||||||
|
test_module_1 = cwd_path / 'this_is_a_module_in_cwd.py'
|
||||||
|
|
||||||
|
# Nested structure that is supposed to be importable
|
||||||
|
nested_path_1 = tmp_path / 'nested'
|
||||||
|
nested_path_2 = nested_path_1 / 'more_nested'
|
||||||
|
|
||||||
|
for path in (nested_path_1, nested_path_2):
|
||||||
|
Path.mkdir(path)
|
||||||
|
|
||||||
|
test_module_2 = tmp_path / 'this_is_a_module_in_level_0.py'
|
||||||
|
test_module_3 = nested_path_1 / 'this_is_a_module_in_level_1.py'
|
||||||
|
test_module_4 = nested_path_2 / 'this_is_a_module_in_level_2.py'
|
||||||
|
|
||||||
|
for module in (test_module_1, test_module_2, test_module_3, test_module_4):
|
||||||
|
module.write_text('')
|
||||||
|
|
||||||
|
sys.path.append(str(tmp_path))
|
||||||
|
monkeypatch.chdir(cwd_path)
|
||||||
|
|
||||||
|
with pytest.raises(ModuleNotFoundError):
|
||||||
|
modules_main([
|
||||||
|
'this_is_a_module_in_level_0',
|
||||||
|
'nested.this_is_a_module_in_level_1',
|
||||||
|
'nested.more_nested.this_is_a_module_in_level_2',
|
||||||
|
'this_is_a_module_in_cwd'])
|
||||||
|
|
||||||
|
_, err = capsys.readouterr()
|
||||||
|
assert 'Check import: this_is_a_module_in_level_0' in err
|
||||||
|
assert 'Check import: nested.this_is_a_module_in_level_1' in err
|
||||||
|
assert 'Check import: nested.more_nested.this_is_a_module_in_level_2' in err
|
||||||
|
assert 'Check import: this_is_a_module_in_cwd' in err
|
||||||
|
|
||||||
|
|
||||||
|
def test_modules_both_from_files_and_cli_are_imported(tmp_path):
|
||||||
|
test_file_1 = tmp_path / 'this_is_a_file_in_tmp_path_1.txt'
|
||||||
|
test_file_1.write_text('this_is_a_module_in_tmp_path_1')
|
||||||
|
|
||||||
|
test_file_2 = tmp_path / 'this_is_a_file_in_tmp_path_2.txt'
|
||||||
|
test_file_2.write_text('this_is_a_module_in_tmp_path_2')
|
||||||
|
|
||||||
|
test_module_1 = tmp_path / 'this_is_a_module_in_tmp_path_1.py'
|
||||||
|
test_module_2 = tmp_path / 'this_is_a_module_in_tmp_path_2.py'
|
||||||
|
test_module_3 = tmp_path / 'this_is_a_module_in_tmp_path_3.py'
|
||||||
|
|
||||||
|
for module in (test_module_1, test_module_2, test_module_3):
|
||||||
|
module.write_text('')
|
||||||
|
|
||||||
|
sys.path.append(str(tmp_path))
|
||||||
|
modules_main([
|
||||||
|
'-f', str(test_file_1),
|
||||||
|
'this_is_a_module_in_tmp_path_3',
|
||||||
|
'-f', str(test_file_2),
|
||||||
|
])
|
||||||
|
|
||||||
|
expected = (
|
||||||
|
'this_is_a_module_in_tmp_path_1',
|
||||||
|
'this_is_a_module_in_tmp_path_2',
|
||||||
|
'this_is_a_module_in_tmp_path_3',
|
||||||
|
)
|
||||||
|
for module in expected:
|
||||||
|
assert module in sys.modules
|
||||||
|
|
||||||
|
|
||||||
|
def test_non_existing_module_raises_exception(tmp_path):
|
||||||
|
|
||||||
|
test_module_1 = tmp_path / 'this_is_a_module_in_tmp_path_1.py'
|
||||||
|
test_module_1.write_text('')
|
||||||
|
sys.path.append(str(tmp_path))
|
||||||
|
|
||||||
|
with pytest.raises(ModuleNotFoundError):
|
||||||
|
modules_main([
|
||||||
|
'this_is_a_module_in_tmp_path_1',
|
||||||
|
'this_is_a_module_in_tmp_path_2',
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def test_module_with_error_propagates_exception(tmp_path):
|
||||||
|
|
||||||
|
test_module_1 = tmp_path / 'this_is_a_module_in_tmp_path_1.py'
|
||||||
|
test_module_1.write_text('0/0')
|
||||||
|
sys.path.append(str(tmp_path))
|
||||||
|
|
||||||
|
# The correct exception must be raised
|
||||||
|
with pytest.raises(ZeroDivisionError):
|
||||||
|
modules_main([
|
||||||
|
'this_is_a_module_in_tmp_path_1',
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def test_correct_modules_are_excluded(tmp_path):
|
||||||
|
test_module_1 = tmp_path / 'module_in_tmp_path_1.py'
|
||||||
|
test_module_2 = tmp_path / 'module_in_tmp_path_2.py'
|
||||||
|
test_module_3 = tmp_path / 'module_in_tmp_path_3.py'
|
||||||
|
|
||||||
|
for module in (test_module_1, test_module_2, test_module_3):
|
||||||
|
module.write_text('')
|
||||||
|
|
||||||
|
sys.path.append(str(tmp_path))
|
||||||
|
test_file_1 = tmp_path / 'a_file_in_tmp_path_1.txt'
|
||||||
|
test_file_1.write_text('module_in_tmp_path_1\nmodule_in_tmp_path_2\nmodule_in_tmp_path_3\n')
|
||||||
|
|
||||||
|
modules_main([
|
||||||
|
'-e', 'module_in_tmp_path_2',
|
||||||
|
'-f', str(test_file_1),
|
||||||
|
'-e', 'module_in_tmp_path_3',
|
||||||
|
])
|
||||||
|
|
||||||
|
assert 'module_in_tmp_path_1' in sys.modules
|
||||||
|
assert 'module_in_tmp_path_2' not in sys.modules
|
||||||
|
assert 'module_in_tmp_path_3' not in sys.modules
|
||||||
|
|
||||||
|
|
||||||
|
def test_excluding_all_modules_raises_error(tmp_path):
|
||||||
|
test_module_1 = tmp_path / 'module_in_tmp_path_1.py'
|
||||||
|
test_module_2 = tmp_path / 'module_in_tmp_path_2.py'
|
||||||
|
test_module_3 = tmp_path / 'module_in_tmp_path_3.py'
|
||||||
|
|
||||||
|
for module in (test_module_1, test_module_2, test_module_3):
|
||||||
|
module.write_text('')
|
||||||
|
|
||||||
|
sys.path.append(str(tmp_path))
|
||||||
|
test_file_1 = tmp_path / 'a_file_in_tmp_path_1.txt'
|
||||||
|
test_file_1.write_text('module_in_tmp_path_1\nmodule_in_tmp_path_2\nmodule_in_tmp_path_3\n')
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
modules_main([
|
||||||
|
'-e', 'module_in_tmp_path*',
|
||||||
|
'-f', str(test_file_1),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def test_only_toplevel_modules_found(tmp_path):
|
||||||
|
|
||||||
|
# Nested structure that is supposed to be importable
|
||||||
|
nested_path_1 = tmp_path / 'nested'
|
||||||
|
nested_path_2 = nested_path_1 / 'more_nested'
|
||||||
|
|
||||||
|
for path in (nested_path_1, nested_path_2):
|
||||||
|
Path.mkdir(path)
|
||||||
|
|
||||||
|
test_module_1 = tmp_path / 'this_is_a_module_in_level_0.py'
|
||||||
|
test_module_2 = nested_path_1 / 'this_is_a_module_in_level_1.py'
|
||||||
|
test_module_3 = nested_path_2 / 'this_is_a_module_in_level_2.py'
|
||||||
|
|
||||||
|
for module in (test_module_1, test_module_2, test_module_3):
|
||||||
|
module.write_text('')
|
||||||
|
|
||||||
|
sys.path.append(str(tmp_path))
|
||||||
|
|
||||||
|
modules_main([
|
||||||
|
'this_is_a_module_in_level_0',
|
||||||
|
'nested.this_is_a_module_in_level_1',
|
||||||
|
'nested.more_nested.this_is_a_module_in_level_2',
|
||||||
|
'-t'])
|
||||||
|
|
||||||
|
assert 'nested.this_is_a_module_in_level_1' not in sys.modules
|
||||||
|
assert 'nested.more_nested.this_is_a_module_in_level_2' not in sys.modules
|
||||||
|
|
||||||
|
|
||||||
|
def test_only_toplevel_included_modules_found(tmp_path):
|
||||||
|
|
||||||
|
# Nested structure that is supposed to be importable
|
||||||
|
nested_path_1 = tmp_path / 'nested'
|
||||||
|
nested_path_2 = nested_path_1 / 'more_nested'
|
||||||
|
|
||||||
|
for path in (nested_path_1, nested_path_2):
|
||||||
|
Path.mkdir(path)
|
||||||
|
|
||||||
|
test_module_1 = tmp_path / 'this_is_a_module_in_level_0.py'
|
||||||
|
test_module_4 = tmp_path / 'this_is_another_module_in_level_0.py'
|
||||||
|
|
||||||
|
test_module_2 = nested_path_1 / 'this_is_a_module_in_level_1.py'
|
||||||
|
test_module_3 = nested_path_2 / 'this_is_a_module_in_level_2.py'
|
||||||
|
|
||||||
|
for module in (test_module_1, test_module_2, test_module_3, test_module_4):
|
||||||
|
module.write_text('')
|
||||||
|
|
||||||
|
sys.path.append(str(tmp_path))
|
||||||
|
|
||||||
|
modules_main([
|
||||||
|
'this_is_a_module_in_level_0',
|
||||||
|
'this_is_another_module_in_level_0',
|
||||||
|
'nested.this_is_a_module_in_level_1',
|
||||||
|
'nested.more_nested.this_is_a_module_in_level_2',
|
||||||
|
'-t',
|
||||||
|
'-e', '*another*'
|
||||||
|
])
|
||||||
|
|
||||||
|
assert 'nested.this_is_a_module_in_level_1' not in sys.modules
|
||||||
|
assert 'nested.more_nested.this_is_a_module_in_level_2' not in sys.modules
|
||||||
|
assert 'this_is_another_module_in_level_0' not in sys.modules
|
||||||
|
assert 'this_is_a_module_in_level_0' in sys.modules
|
||||||
|
|
||||||
|
|
||||||
|
def test_module_list_from_relative_path(tmp_path, monkeypatch):
|
||||||
|
|
||||||
|
monkeypatch.chdir(tmp_path)
|
||||||
|
test_file_1 = Path('this_is_a_file_in_cwd.txt')
|
||||||
|
test_file_1.write_text('wave')
|
||||||
|
|
||||||
|
sys.modules.pop('wave', None)
|
||||||
|
|
||||||
|
modules_main([
|
||||||
|
'-f', 'this_is_a_file_in_cwd.txt'
|
||||||
|
])
|
||||||
|
|
||||||
|
assert 'wave' in sys.modules
|
Loading…
Reference in New Issue
Block a user