diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 339768db2..1a67bb33b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -27,7 +27,8 @@ whether to filter the traceback based on the ``ExceptionInfo`` object passed to it. -* +* New ``pytest_make_parametrize_id`` hook. + Thanks `@palaviv`_ for the PR. **Changes** diff --git a/_pytest/hookspec.py b/_pytest/hookspec.py index 60e9b47d2..424ee2069 100644 --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -156,6 +156,12 @@ def pytest_pyfunc_call(pyfuncitem): def pytest_generate_tests(metafunc): """ generate (multiple) parametrized calls to a test function.""" +@hookspec(firstresult=True) +def pytest_make_parametrize_id(config, val): + """Return a user-friendly string representation of the given ``val`` that will be used + by @pytest.mark.parametrize calls. Return None if the hook doesn't know about ``val``. + """ + # ------------------------------------------------------------------------- # generic runtest related hooks # ------------------------------------------------------------------------- diff --git a/_pytest/python.py b/_pytest/python.py index a18f13b43..b434e6b40 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -342,6 +342,9 @@ def pytest_pycollect_makeitem(collector, name, obj): res = list(collector._genfunctions(name, obj)) outcome.force_result(res) +def pytest_make_parametrize_id(config, val): + return None + def is_generator(func): try: return _pytest._code.getrawcode(func).co_flags & 32 # generator function @@ -1038,7 +1041,7 @@ class Metafunc(FuncargnamesCompatAttr): if ids and len(ids) != len(argvalues): raise ValueError('%d tests specified with %d ids' %( len(argvalues), len(ids))) - ids = idmaker(argnames, argvalues, idfn, ids) + ids = idmaker(argnames, argvalues, idfn, ids, self.config) newcalls = [] for callspec in self._calls or [CallSpec2(self)]: for param_index, valset in enumerate(argvalues): @@ -1138,7 +1141,7 @@ else: return val.encode('unicode-escape') -def _idval(val, argname, idx, idfn): +def _idval(val, argname, idx, idfn, config=None): if idfn: try: s = idfn(val) @@ -1147,6 +1150,11 @@ def _idval(val, argname, idx, idfn): except Exception: pass + if config: + hook_id = config.hook.pytest_make_parametrize_id(config=config, val=val) + if hook_id: + return hook_id + if isinstance(val, (bytes, str)) or (_PY2 and isinstance(val, unicode)): return _escape_strings(val) elif isinstance(val, (float, int, bool, NoneType)): @@ -1159,16 +1167,16 @@ def _idval(val, argname, idx, idfn): return val.__name__ return str(argname)+str(idx) -def _idvalset(idx, valset, argnames, idfn, ids): +def _idvalset(idx, valset, argnames, idfn, ids, config=None): if ids is None or ids[idx] is None: - this_id = [_idval(val, argname, idx, idfn) + this_id = [_idval(val, argname, idx, idfn, config) for val, argname in zip(valset, argnames)] return "-".join(this_id) else: return _escape_strings(ids[idx]) -def idmaker(argnames, argvalues, idfn=None, ids=None): - ids = [_idvalset(valindex, valset, argnames, idfn, ids) +def idmaker(argnames, argvalues, idfn=None, ids=None, config=None): + ids = [_idvalset(valindex, valset, argnames, idfn, ids, config) for valindex, valset in enumerate(argvalues)] if len(set(ids)) != len(ids): # The ids are not unique diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index cc346aaa8..38d47bf6d 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -470,6 +470,7 @@ you can use the following hook: .. autofunction:: pytest_pycollect_makeitem .. autofunction:: pytest_generate_tests +.. autofunction:: pytest_make_parametrize_id After collection is complete, you can modify the order of items, delete or otherwise amend the test items: diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 6ae3ca43f..6ce6cb751 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -1156,3 +1156,21 @@ class TestMarkersWithParametrization: """) reprec = testdir.inline_run() reprec.assertoutcome(passed=2) + + def test_pytest_make_parametrize_id(self, testdir): + testdir.makeconftest(""" + def pytest_make_parametrize_id(config, val): + return str(val * 2) + """) + testdir.makepyfile(""" + import pytest + + @pytest.mark.parametrize("x", range(2)) + def test_func(x): + pass + """) + result = testdir.runpytest("-v") + result.stdout.fnmatch_lines([ + "*test_func*0*PASS*", + "*test_func*2*PASS*", + ])