Skip to content

Commit

Permalink
Fix AssertionError when AUTO cases are requested outside a "test_" mo…
Browse files Browse the repository at this point in the history
…dule (#320)
  • Loading branch information
michele-riva authored Nov 21, 2023
1 parent ac7f3c3 commit 998429f
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 6 deletions.
4 changes: 2 additions & 2 deletions docs/api_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ CaseType = Union[Callable, Type, ModuleRef]

A decorator for test functions or fixtures, to parametrize them based on test cases. It works similarly to [`@pytest.mark.parametrize`](https://docs.pytest.org/en/stable/parametrize.html): argnames represent a coma-separated string of arguments to inject in the decorated test function or fixture. The argument values (`argvalues` in [`@pytest.mark.parametrize`](https://docs.pytest.org/en/stable/parametrize.html)) are collected from the various case functions found according to `cases`, and injected as lazy values so that the case functions are called just before the test or fixture is executed.

By default (`cases=AUTO`) the list of test cases is automatically drawn from the python module file named `test_<name>_cases.py` or if not found, `case_<name>.py`, where `test_<name>` is the current module name.
By default (`cases=AUTO`) the list of test cases is automatically drawn from the python module file named `test_<name>_cases.py` or if not found, `cases_<name>.py`, where `test_<name>` is the current module name.

Finally, the `cases` argument also accepts an explicit case function, cases-containing class, module or module name; or a list containing any mix of these elements. Note that both absolute and relative module names are supported.

Expand All @@ -293,7 +293,7 @@ argvalues = get_parametrize_args(host_class_or_module_of_f, cases_funs)

- `argnames`: same than in `@pytest.mark.parametrize`

- `cases`: a case function, a class containing cases, a module object or a module name string (relative module names accepted). Or a list of such items. You may use `THIS_MODULE` or `'.'` to include current module. `AUTO` (default) means that the module named `test_<name>_cases.py` or if not found, `case_<name>.py`, will be loaded, where `test_<name>.py` is the module file of the decorated function. When a module is listed, all of its functions matching the `prefix`, `filter` and `has_tag` are selected, including those functions nested in classes following naming pattern `*Case*`. Nested subclasses are taken into account, as long as they follow the `*Case*` naming pattern. When classes are explicitly provided in the list, they can have any name and do not need to follow this `*Case*` pattern.
- `cases`: a case function, a class containing cases, a module object or a module name string (relative module names accepted). Or a list of such items. You may use `THIS_MODULE` or `'.'` to include current module. `AUTO` (default) means that the module named `test_<name>_cases.py` or if not found, `cases_<name>.py`, will be loaded, where `test_<name>.py` is the module file of the decorated function. When a module is listed, all of its functions matching the `prefix`, `filter` and `has_tag` are selected, including those functions nested in classes following naming pattern `*Case*`. Nested subclasses are taken into account, as long as they follow the `*Case*` naming pattern. When classes are explicitly provided in the list, they can have any name and do not need to follow this `*Case*` pattern.

- `prefix`: the prefix for case functions. Default is 'case_' but you might wish to use different prefixes to denote different kind of cases, for example 'data_', 'algo_', 'user_', etc.

Expand Down
11 changes: 11 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

### 3.8.2 (in progress) - bugfixes

- Corrected API documentation (and comments) for the second file-name
pattern for `AUTO`-cases lookup (`cases_<name>.py` instead of
`case_<name>.py`). PR [#320](https://github.com/smarie/python-pytest-cases/pull/320)
by [@michele-riva](https://github.com/michele-riva).
- Fixed `AssertionError` on `AUTO` cases outside a 'normal' test module.
Fixes [#309](https://github.com/smarie/python-pytest-cases/issues/309). PR
[#320](https://github.com/smarie/python-pytest-cases/pull/320) by
[@michele-riva](https://github.com/michele-riva).

### 3.8.1 - bugfixes

- Fixed `ScopeMismatch` with parametrized cases in non-trivial test
Expand Down
19 changes: 15 additions & 4 deletions src/pytest_cases/case_parametrizer_new.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def parametrize_with_cases(argnames, # type: Union[str, List[str]
:param argnames: same than in @pytest.mark.parametrize
:param cases: a case function, a class containing cases, a module object or a module name string (relative module
names accepted). Or a list of such items. You may use `THIS_MODULE` or `'.'` to include current module.
`AUTO` (default) means that the module named `test_<name>_cases.py` or if not found, `case_<name>.py`, will be
`AUTO` (default) means that the module named `test_<name>_cases.py` or if not found, `cases_<name>.py`, will be
loaded, where `test_<name>.py` is the module file of the decorated function. When a module is listed, all of
its functions matching the `prefix`, `filter` and `has_tag` are selected, including those functions nested in
classes following naming pattern `*Case*`. Nested subclasses are taken into account, as long as they follow the
Expand Down Expand Up @@ -224,7 +224,7 @@ def get_all_cases(parametrization_target=None, # type: Callable
names accepted). Or a list of such items. You may use `THIS_MODULE` or `'.'` to include current module.
`AUTO` (default) means that the module named `test_<name>_cases.py` will be loaded, where `test_<name>.py` is
the module file of the decorated function. `AUTO2` allows you to use the alternative naming scheme
`case_<name>.py`. When a module is listed, all of its functions matching the `prefix`, `filter` and `has_tag`
`cases_<name>.py`. When a module is listed, all of its functions matching the `prefix`, `filter` and `has_tag`
are selected, including those functions nested in classes following naming pattern `*Case*`. When classes are
explicitly provided in the list, they can have any name and do not need to follow this `*Case*` pattern.
:param prefix: the prefix for case functions. Default is 'case_' but you might wish to use different prefixes to
Expand Down Expand Up @@ -299,7 +299,18 @@ def get_all_cases(parametrization_target=None, # type: Callable
else:
# module
if c is AUTO:
# First try `test_<name>_cases.py` Then `case_<name>.py`
# Make sure we're in a test_<xxx>.py-like module.
# We cannot accept AUTO cases in, e.g., conftest.py
# as we don't know what to look for. We complain here
# rather than raising AssertionError in the call to
# import_default_cases_module. See #309.
if not caller_module_name.split('.')[-1].startswith('test_'):
raise ValueError(
'Cannot use `cases=AUTO` in file "%s". `cases=AUTO` is '
'only allowed in files whose name starts with "test_" '
% caller_module_name
)
# First try `test_<name>_cases.py` Then `cases_<name>.py`
c = import_default_cases_module(caller_module_name)

elif c is THIS_MODULE or c == '.':
Expand Down Expand Up @@ -680,7 +691,7 @@ def import_default_cases_module(test_module_name):
try:
cases_module = import_module(cases_module_name1)
except ModuleNotFoundError:
# Then try `case_<name>.py`
# Then try `cases_<name>.py`
parts = test_module_name.split('.')
assert parts[-1][0:5] == 'test_'
cases_module_name2 = "%s.cases_%s" % ('.'.join(parts[:-1]), parts[-1][5:])
Expand Down
14 changes: 14 additions & 0 deletions tests/cases/issues/issue_309/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from pytest_cases import fixture, get_all_cases
from pytest_cases.common_others import AUTO


def mock_parameterization_target():
"""A callable to use as parametrization target."""


@fixture
def get_all_cases_auto_fails():
"""Fail because we ask for AUTO cases in a non-'test_<...>' file."""
def _fail():
get_all_cases(mock_parameterization_target, cases=AUTO)
return _fail
7 changes: 7 additions & 0 deletions tests/cases/issues/issue_309/test_issue_309.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

import pytest


def test_get_all_cases_auto_raises(get_all_cases_auto_fails):
with pytest.raises(ValueError):
get_all_cases_auto_fails()

0 comments on commit 998429f

Please sign in to comment.