Skip to content

python.py:813 (in "parametrize") raises "TypeError: object of type 'int' has no len()" in some cases #638

Closed

Description

@pytestbot

Originally reported by: Eric Dand (BitBucket: eric_dand, GitHub: eric_dand)


Passing a one-element array for argnames and an array of ints as argvalues to parametrize causes the assert at line 813 to fail. This is only caused by this edge case:

  • If argnames is a single-element string (ie. no commas, not enclosed in an array), 788-791 will try to split argnames by commas, find at 790 that len(argnames) == 1, and will box up argvalues's values into tuples.
  • If argnames is an array that has more than one element, each element of argvalues should be a tuple. This is then the intended behaviour, and therefore not an issue.
  • If argnames is a single-element array, however, it sidesteps the check at 788, if not isinstance(argnames, (tuple, list)):, wrongly assuming there is more than one element in argname, and therefore does not pack up argvalues into tuples.

The simplest fix is to simply dedent lines 790 and 791 to check the length of argnames regardless of whether it was passed as a string or not. I'm not sure if this change is entirely safe, but it did resolve the issue in my case.


Activity

  1. pytestbot commented on Jan 20, 2015

    @pytestbot
    ContributorAuthor

    Original comment by Brianna Laugher (BitBucket: pfctdayelise, GitHub: pfctdayelise):


    Hi Eric, I had trouble recreating this problem based on your description. Would you be able to provide a brief example test that causes the problem?

  2. pytestbot commented on Jan 22, 2015

    @pytestbot
    ContributorAuthor

    Original comment by Eric Dand (BitBucket: eric_dand, GitHub: eric_dand):


    Adapted minimally from http://pytest.org/latest/example/parametrize.html:

    conftest.py:

    #!python
    
    def pytest_addoption(parser):
        parser.addoption("--all", action="store_true",
                help="run all combinations")
    
    def pytest_generate_tests(metafunc):
        if 'param1' in metafunc.fixturenames:
            if metafunc.config.option.all:
                end = 5
            else:
                end = 2
            import pdb; pdb.set_trace()
            metafunc.parametrize(['param1'], range(end))
    
    

    test_compute.py (unchanged):

    #!python
    
    def test_compute(param1):
        assert param1 < 4
    
    

    The only change I made to the above code was to put braces around 'param1' in the call to metafunc.parametrize.

  3. pytestbot commented on Jan 24, 2015

    @pytestbot
    ContributorAuthor

    Original comment by Brianna Laugher (BitBucket: pfctdayelise, GitHub: pfctdayelise):


    Your fix is not going to work for the case where there is only one argname, and the argvalues are already packed into tuples. e.g.

    #!python
    
    @pytest.mark.parametrize("param2", [0, 1])
    def test_compute1(param2):
        assert isinstance(param2, int)
        assert param2 < 4
    
    @pytest.mark.parametrize("param2", [(0,), (1,)])
    def test_compute2(param2):
        assert isinstance(param2, int)
        assert param2 < 4
    
    @pytest.mark.parametrize(["param2"], [(0,), (1,)])
    def test_compute3(param2):
        assert isinstance(param2, int)
        assert param2 < 4
    
    @pytest.mark.parametrize(["param2"], [0, 1])
    def test_compute4(param2):
        assert isinstance(param2, int)
        assert param2 < 4
    

    Your change would fix compute4 but break compute2 and compute3.

    In general I think it is safest not to change argvalues very much, as we cannot reliably tell if tuples/lists represent different test cases or a single value within a test case. I would treat the syntax of compute1 as a convenience, i.e. when you specify a single argname in a plain string you don't have to bother packing your argvalues into tuples.

  4. self-assigned this
    on Jul 10, 2015
  5. removed their assignment
    on Jan 26, 2016
  6. asottile commented on Jul 8, 2018

    @asottile
    Member

    Here's a minimal reproduction:

    @pytest.mark.parametrize(['p'], (1, 2, 3))
    def test_compute(p):
        assert p < 4
    $ ./venv/bin/pytest  t.py
    ============================= test session starts ==============================
    platform linux -- Python 3.6.5, pytest-3.6.4.dev9+g42bbb4fa.d20180707, py-1.5.4, pluggy-0.6.0
    rootdir: /tmp/pytest, inifile: tox.ini
    collected 0 items / 1 errors                                                   
    
    ==================================== ERRORS ====================================
    ____________________________ ERROR collecting t.py _____________________________
    venv/lib/python3.6/site-packages/pluggy/__init__.py:617: in __call__
        return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
    venv/lib/python3.6/site-packages/pluggy/__init__.py:222: in _hookexec
        return self._inner_hookexec(hook, methods, kwargs)
    venv/lib/python3.6/site-packages/pluggy/__init__.py:216: in <lambda>
        firstresult=hook.spec_opts.get('firstresult'),
    venv/lib/python3.6/site-packages/_pytest/python.py:243: in pytest_pycollect_makeitem
        res = list(collector._genfunctions(name, obj))
    venv/lib/python3.6/site-packages/_pytest/python.py:433: in _genfunctions
        self.ihook.pytest_generate_tests(metafunc=metafunc)
    venv/lib/python3.6/site-packages/pluggy/__init__.py:617: in __call__
        return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
    venv/lib/python3.6/site-packages/pluggy/__init__.py:222: in _hookexec
        return self._inner_hookexec(hook, methods, kwargs)
    venv/lib/python3.6/site-packages/pluggy/__init__.py:216: in <lambda>
        firstresult=hook.spec_opts.get('firstresult'),
    venv/lib/python3.6/site-packages/_pytest/python.py:164: in pytest_generate_tests
        metafunc.parametrize(*marker.args, **marker.kwargs)
    venv/lib/python3.6/site-packages/_pytest/python.py:923: in parametrize
        ids = idmaker(argnames, parameters, idfn, ids, self.config)
    venv/lib/python3.6/site-packages/_pytest/python.py:1075: in idmaker
        for valindex, parameterset in enumerate(parametersets)
    venv/lib/python3.6/site-packages/_pytest/python.py:1075: in <listcomp>
        for valindex, parameterset in enumerate(parametersets)
    venv/lib/python3.6/site-packages/_pytest/python.py:1065: in _idvalset
        for val, argname in zip(parameterset.values, argnames)
    E   TypeError: zip argument #1 must support iteration
    =========================== short test summary info ============================
    ERROR t.py
    !!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!!!
    =========================== 1 error in 0.26 seconds ============================
    
  7. nicoddemus commented on Jan 17, 2019

    @nicoddemus
    Member

    I'm not sure this is solvable without breaking existing cases, as @pfctdayelise points out. Perhaps we should instead focus in improving the message at least?

  8. barclay13 commented on May 30, 2019

    @barclay13

    My first ever test with pytest I seem to have hit this issue with the below example test
    (the error was if len(param.values) != len(argnames): E TypeError: object of type 'int' has no len())

    import pytest
    
    
    def add(a, b):
        return a + b
    
    
    @pytest.mark.parametrize('a,b,expected', (2, 2, 4))
    def test_add(a, b, expected):
        assert add(a, b) == expected
  9. asottile commented on May 30, 2019

    @asottile
    Member

    You need an additional set of iterable. The second parameter is an iterable of case tuples:

    @pytest.mark.parametrize('a,b,expected', [(2, 2, 4)])
    def test_add(a, b, expected):
        assert add(a, b) == expected
  10. barclay13 commented on May 30, 2019

    @barclay13

    @asottile Thanks, that worked perfectly. Perhaps an alternative error message might guide other users more clearly.
    Thanks for your help.

  11. added a commit that references this issue on Jan 4, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    topic: parametrizerelated to @pytest.mark.parametrizetype: bugproblem that needs to be addressed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions