python-rpm-macros/tests/test_import_all_modules.py
Karolina Surma 2d0673afb1 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.
2021-10-27 15:57:37 +02:00

393 lines
13 KiB
Python

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