diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0a492c6b4a9..159b3fae2c9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -23,7 +23,8 @@ **Changes** -* +* parametrize ids can accept None as specific test id. The + automatically generated id for that argument will be used. * @@ -45,7 +46,7 @@ **Bug Fixes** -* +* When receiving identical test ids in parametrize we generate unique test ids. * diff --git a/_pytest/python.py b/_pytest/python.py index 68a13224b9b..7b4b0a62230 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -967,7 +967,8 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, :arg ids: list of string ids, or a callable. If strings, each is corresponding to the argvalues so that they are - part of the test id. + part of the test id. If None is given as id of specific test, the + automatically generated id for that argument will be used. If callable, it should take one argument (a single argvalue) and return a string or return None. If None, the automatically generated id for that argument will be used. @@ -1028,8 +1029,7 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, if ids and len(ids) != len(argvalues): raise ValueError('%d tests specified with %d ids' %( len(argvalues), len(ids))) - if not ids: - ids = idmaker(argnames, argvalues, idfn) + ids = idmaker(argnames, argvalues, idfn, ids) newcalls = [] for callspec in self._calls or [CallSpec2(self)]: for param_index, valset in enumerate(argvalues): @@ -1141,13 +1141,16 @@ def _idval(val, argname, idx, idfn): pass return str(argname)+str(idx) -def _idvalset(idx, valset, argnames, idfn): - this_id = [_idval(val, argname, idx, idfn) - for val, argname in zip(valset, argnames)] - return "-".join(this_id) +def _idvalset(idx, valset, argnames, idfn, ids): + if ids is None or ids[idx] is None: + this_id = [_idval(val, argname, idx, idfn) + for val, argname in zip(valset, argnames)] + return "-".join(this_id) + else: + return ids[idx] -def idmaker(argnames, argvalues, idfn=None): - ids = [_idvalset(valindex, valset, argnames, idfn) +def idmaker(argnames, argvalues, idfn=None, ids=None): + ids = [_idvalset(valindex, valset, argnames, idfn, ids) for valindex, valset in enumerate(argvalues)] if len(set(ids)) < len(ids): # user may have provided a bad idfn which means the ids are not unique diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index faa687f4056..f0f3dbd4cbd 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -258,6 +258,20 @@ def ids(val): "three-b2", ] + def test_idmaker_with_ids(self): + from _pytest.python import idmaker + result = idmaker(("a", "b"), [(1, 2), + (3, 4)], + ids=["a", None]) + assert result == ["a", "3-4"] + + def test_idmaker_with_ids_unique_names(self): + from _pytest.python import idmaker + result = idmaker(("a", "b"), [(1, 2), + (3, 4)], + ids=["a", "a"]) + assert result == ["0a", "1a"] + def test_addcall_and_parametrize(self): def func(x, y): pass metafunc = self.Metafunc(func) @@ -789,6 +803,41 @@ def test_function(a, b): *test_function*1.3-b1* """) + def test_parametrize_with_None_in_ids(self, testdir): + testdir.makepyfile(""" + import pytest + def pytest_generate_tests(metafunc): + metafunc.parametrize(("a", "b"), [(1,1), (1,1), (1,2)], + ids=["basic", None, "advanced"]) + + def test_function(a, b): + assert a == b + """) + result = testdir.runpytest("-v") + assert result.ret == 1 + result.stdout.fnmatch_lines_random([ + "*test_function*basic*PASSED", + "*test_function*1-1*PASSED", + "*test_function*advanced*FAILED", + ]) + + def test_parametrize_with_identical_ids_get_unique_names(self, testdir): + testdir.makepyfile(""" + import pytest + def pytest_generate_tests(metafunc): + metafunc.parametrize(("a", "b"), [(1,1), (1,2)], + ids=["a", "a"]) + + def test_function(a, b): + assert a == b + """) + result = testdir.runpytest("-v") + assert result.ret == 1 + result.stdout.fnmatch_lines_random([ + "*test_function*0a*PASSED", + "*test_function*1a*FAILED" + ]) + @pytest.mark.parametrize(("scope", "length"), [("module", 2), ("function", 4)]) def test_parametrize_scope_overrides(self, testdir, scope, length):