diff --git a/tests/test_cli.py b/tests/test_cli.py index d331361..af2da37 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -4,7 +4,7 @@ import tempfile import textwrap -from testsweet import test +from testsweet import test, test_params _REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent @@ -32,11 +32,31 @@ def {func_name}(): assert True """ - def all_pass_module_exits_zero(self): - result = _run_cli('tests.fixtures.runner.all_pass') + @test_params( + [ + ( + ('tests.fixtures.runner.all_pass',), + ['passes_one ... ok', 'passes_two ... ok'], + ), + ( + ('tests.fixtures.runner.class_simple',), + ['Simple.first ... ok', 'Simple.second ... ok'], + ), + ( + ('tests.fixtures.runner.params_simple',), + ['adds[0] ... ok', 'adds[1] ... ok'], + ), + ( + ('tests/fixtures/runner/all_pass.py',), + ['passes_one ... ok', 'passes_two ... ok'], + ), + ] + ) + def runs_target_with_expected_lines(self, args, expected_lines): + result = _run_cli(*args) assert result.returncode == 0 - assert 'passes_one ... ok' in result.stdout - assert 'passes_two ... ok' in result.stdout + for line in expected_lines: + assert line in result.stdout def failing_module_exits_one(self): result = _run_cli('tests.fixtures.runner.has_failure') @@ -45,33 +65,20 @@ def failing_module_exits_one(self): assert 'fails ... FAIL:' in result.stdout assert 'AssertionError' in result.stdout - def two_arguments_exits_two(self): - result = _run_cli('a', 'b') - assert result.returncode != 0 - assert 'ModuleNotFoundError' in result.stderr - - def unimportable_module_propagates(self): - result = _run_cli('not_a_real_module_xyzzy') + @test_params( + [ + (('a', 'b'), 'ModuleNotFoundError'), + (('not_a_real_module_xyzzy',), 'ModuleNotFoundError'), + ( + ('tests.fixtures.runner.all_pass.nonexistent',), + 'LookupError', + ), + ] + ) + def invalid_target_writes_to_stderr(self, args, expected_substring): + result = _run_cli(*args) assert result.returncode != 0 - assert 'ModuleNotFoundError' in result.stderr - - def class_method_qualname_in_output(self): - result = _run_cli('tests.fixtures.runner.class_simple') - assert result.returncode == 0 - assert 'Simple.first ... ok' in result.stdout - assert 'Simple.second ... ok' in result.stdout - - def parameterized_indices_in_output(self): - result = _run_cli('tests.fixtures.runner.params_simple') - assert result.returncode == 0 - assert 'adds[0] ... ok' in result.stdout - assert 'adds[1] ... ok' in result.stdout - - def file_path_argv(self): - result = _run_cli('tests/fixtures/runner/all_pass.py') - assert result.returncode == 0 - assert 'passes_one ... ok' in result.stdout - assert 'passes_two ... ok' in result.stdout + assert expected_substring in result.stderr def selector_argv_runs_one_method(self): result = _run_cli( @@ -100,11 +107,6 @@ def two_selectors_same_module_grouped(self): assert result.stdout.count('Simple.first ... ok') == 1 assert result.stdout.count('Simple.second ... ok') == 1 - def unmatched_selector_propagates_lookup_error(self): - result = _run_cli('tests.fixtures.runner.all_pass.nonexistent') - assert result.returncode != 0 - assert 'LookupError' in result.stderr - def module_target_overrides_selector_for_same_module(self): result = _run_cli( 'tests.fixtures.runner.all_pass', diff --git a/tests/test_config.py b/tests/test_config.py index 7a22ac5..a97fbf9 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2,7 +2,12 @@ import tempfile import textwrap -from testsweet import ConfigurationError, catch_exceptions, test +from testsweet import ( + ConfigurationError, + catch_exceptions, + test, + test_params, +) from testsweet._config import DiscoveryConfig, load_config @@ -57,48 +62,40 @@ def walks_up_from_subdirectory(self): config = load_config(sub) assert config.project_root == root.resolve() - def non_list_value_raises(self): - with tempfile.TemporaryDirectory() as tmp: - root = pathlib.Path(tmp) - (root / 'pyproject.toml').write_text( - textwrap.dedent(""" + @test_params( + [ + ( + """ [tool.testsweet.discovery] include_paths = "tests/" - """).lstrip() - ) - with catch_exceptions() as excs: - load_config(root) - assert len(excs) == 1 - assert isinstance(excs[0], ConfigurationError) - assert 'include_paths' in str(excs[0]) - - def list_with_non_string_raises(self): - with tempfile.TemporaryDirectory() as tmp: - root = pathlib.Path(tmp) - (root / 'pyproject.toml').write_text( - textwrap.dedent(""" + """, + 'include_paths', + ), + ( + """ [tool.testsweet.discovery] test_files = ["test_*.py", 42] - """).lstrip() - ) - with catch_exceptions() as excs: - load_config(root) - assert len(excs) == 1 - assert isinstance(excs[0], ConfigurationError) - assert 'test_files' in str(excs[0]) - - def unknown_key_raises(self): - with tempfile.TemporaryDirectory() as tmp: - root = pathlib.Path(tmp) - (root / 'pyproject.toml').write_text( - textwrap.dedent(""" + """, + 'test_files', + ), + ( + """ [tool.testsweet.discovery] include_paths = ["tests/**"] typoed_key = ["nope"] - """).lstrip() + """, + 'typoed_key', + ), + ] + ) + def invalid_pyproject_raises(self, body, expected_key): + with tempfile.TemporaryDirectory() as tmp: + root = pathlib.Path(tmp) + (root / 'pyproject.toml').write_text( + textwrap.dedent(body).lstrip() ) with catch_exceptions() as excs: load_config(root) assert len(excs) == 1 assert isinstance(excs[0], ConfigurationError) - assert 'typoed_key' in str(excs[0]) + assert expected_key in str(excs[0]) diff --git a/tests/test_discover.py b/tests/test_discover.py index 1053861..ed2e40f 100644 --- a/tests/test_discover.py +++ b/tests/test_discover.py @@ -1,56 +1,33 @@ import importlib -from testsweet import discover, test +from testsweet import discover, test, test_params @test class Discover: - def single_decorated_function(self): - mod = importlib.import_module('tests.fixtures.single') - result = discover(mod) - assert [f.__name__ for f in result] == ['only_test'] - - def multiple_in_definition_order(self): - mod = importlib.import_module('tests.fixtures.multiple') - result = discover(mod) - assert [f.__name__ for f in result] == ['a', 'b', 'c'] - - def skips_undecorated_functions(self): - mod = importlib.import_module('tests.fixtures.mixed') - result = discover(mod) - assert [f.__name__ for f in result] == [ - 'decorated_one', - 'decorated_two', - ] - - def skips_non_callable_marker_holder(self): - mod = importlib.import_module( - 'tests.fixtures.non_callable_marker', - ) - result = discover(mod) - assert result == [] - - def includes_imported_test_functions(self): - mod = importlib.import_module('tests.fixtures.imported_only') - result = discover(mod) - assert [f.__name__ for f in result] == ['only_test'] - - def mixed_local_and_imported_in_vars_order(self): - mod = importlib.import_module( - 'tests.fixtures.mixed_local_imported', - ) - result = discover(mod) - # `from ... import only_test` runs before `local_after` is - # defined, so vars() insertion order is imported-first. - assert [f.__name__ for f in result] == [ - 'only_test', - 'local_after', + @test_params( + [ + ('tests.fixtures.empty', []), + ('tests.fixtures.single', ['only_test']), + ('tests.fixtures.multiple', ['a', 'b', 'c']), + ( + 'tests.fixtures.mixed', + ['decorated_one', 'decorated_two'], + ), + ('tests.fixtures.non_callable_marker', []), + ('tests.fixtures.imported_only', ['only_test']), + # `from ... import only_test` runs before `local_after` is + # defined, so vars() insertion order is imported-first. + ( + 'tests.fixtures.mixed_local_imported', + ['only_test', 'local_after'], + ), ] - - def empty_module_returns_empty_list(self): - mod = importlib.import_module('tests.fixtures.empty') + ) + def names_match_fixture(self, module_name, expected): + mod = importlib.import_module(module_name) result = discover(mod) - assert result == [] + assert [f.__name__ for f in result] == expected def returns_fresh_list_each_call(self): mod = importlib.import_module('tests.fixtures.multiple') diff --git a/tests/test_targets.py b/tests/test_targets.py index 7f38f3e..8893feb 100644 --- a/tests/test_targets.py +++ b/tests/test_targets.py @@ -4,7 +4,7 @@ import sys import tempfile -from testsweet import catch_exceptions, test +from testsweet import catch_exceptions, test, test_params from testsweet._config import DiscoveryConfig from testsweet._targets import discover_targets, parse_target @@ -24,64 +24,46 @@ def dotted_module_no_selector(self): assert module is expected assert names is None - def relative_file_path(self): - result = parse_target('tests/fixtures/runner/all_pass.py') + @test_params( + [ + ('tests/fixtures/runner/all_pass.py',), + ('./tests/fixtures/runner/all_pass.py',), + (str((_FIXTURES / 'all_pass.py').resolve()),), + ] + ) + def file_path_loads_module(self, target): + result = parse_target(target) assert len(result) == 1 module, names = result[0] assert names is None assert hasattr(module, 'passes_one') - assert hasattr(module, 'passes_two') - def relative_file_path_with_dot(self): - result = parse_target('./tests/fixtures/runner/all_pass.py') - assert len(result) == 1 - module, names = result[0] - assert names is None - assert hasattr(module, 'passes_one') - - def absolute_file_path(self): - path = (_FIXTURES / 'all_pass.py').resolve() - result = parse_target(str(path)) - assert len(result) == 1 - module, names = result[0] - assert names is None - assert hasattr(module, 'passes_one') - - def dotted_selector_one_segment(self): - result = parse_target( - 'tests.fixtures.runner.all_pass.passes_one', - ) - assert len(result) == 1 - module, names = result[0] - expected = importlib.import_module( - 'tests.fixtures.runner.all_pass', - ) - assert module is expected - assert names == ['passes_one'] - - def dotted_selector_class_only(self): - result = parse_target( - 'tests.fixtures.runner.class_simple.Simple', - ) - assert len(result) == 1 - module, names = result[0] - expected = importlib.import_module( - 'tests.fixtures.runner.class_simple', - ) - assert module is expected - assert names == ['Simple'] - - def dotted_selector_class_method(self): - result = parse_target( - 'tests.fixtures.runner.class_simple.Simple.first', - ) + @test_params( + [ + ( + 'tests.fixtures.runner.all_pass.passes_one', + 'tests.fixtures.runner.all_pass', + ['passes_one'], + ), + ( + 'tests.fixtures.runner.class_simple.Simple', + 'tests.fixtures.runner.class_simple', + ['Simple'], + ), + ( + 'tests.fixtures.runner.class_simple.Simple.first', + 'tests.fixtures.runner.class_simple', + ['Simple.first'], + ), + ] + ) + def dotted_selector(self, target, expected_module, expected_names): + result = parse_target(target) assert len(result) == 1 module, names = result[0] - expected = importlib.import_module( - 'tests.fixtures.runner.class_simple', - ) + expected = importlib.import_module(expected_module) assert module is expected - assert names == ['Simple.first'] + assert names == expected_names def dotted_too_many_segments(self): with catch_exceptions() as excs: diff --git a/tests/test_walk.py b/tests/test_walk.py index 0ca721e..8e591d2 100644 --- a/tests/test_walk.py +++ b/tests/test_walk.py @@ -1,7 +1,7 @@ import pathlib import tempfile -from testsweet import test +from testsweet import test, test_params from testsweet._config import DiscoveryConfig from testsweet._walk import _walk_directory @@ -28,33 +28,14 @@ def recurses_subdirectories(self): names = [p.relative_to(root).as_posix() for p in paths] assert names == ['sub/inner.py', 'top.py'] - def excludes_hidden_directories(self): + @test_params([('.hidden',), ('__pycache__',), ('node_modules',)]) + def excludes_directory(self, dirname): with tempfile.TemporaryDirectory() as tmp: root = pathlib.Path(tmp) (root / 'visible.py').write_text('') - hidden = root / '.hidden' - hidden.mkdir() - (hidden / 'inside.py').write_text('') - paths = _walk_directory(root) - assert [p.name for p in paths] == ['visible.py'] - - def excludes_pycache(self): - with tempfile.TemporaryDirectory() as tmp: - root = pathlib.Path(tmp) - (root / 'visible.py').write_text('') - cache = root / '__pycache__' - cache.mkdir() - (cache / 'inside.py').write_text('') - paths = _walk_directory(root) - assert [p.name for p in paths] == ['visible.py'] - - def excludes_node_modules(self): - with tempfile.TemporaryDirectory() as tmp: - root = pathlib.Path(tmp) - (root / 'visible.py').write_text('') - nm = root / 'node_modules' - nm.mkdir() - (nm / 'inside.py').write_text('') + excluded = root / dirname + excluded.mkdir() + (excluded / 'inside.py').write_text('') paths = _walk_directory(root) assert [p.name for p in paths] == ['visible.py']