-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.
393 lines
13 KiB
Python
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
|