Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix AssertionError when AUTO cases are requested outside a "test_" module #320

Merged
merged 7 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from 6 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
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
9 changes: 9 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

- Corrected API documentation (and comments) for the second file-name
smarie marked this conversation as resolved.
Show resolved Hide resolved
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()
Loading