Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 38 additions & 36 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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')
Expand All @@ -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(
Expand Down Expand Up @@ -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',
Expand Down
65 changes: 31 additions & 34 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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])
67 changes: 22 additions & 45 deletions tests/test_discover.py
Original file line number Diff line number Diff line change
@@ -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')
Expand Down
84 changes: 33 additions & 51 deletions tests/test_targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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:
Expand Down
Loading
Loading