classification
Title: mock side_effect should be checked for iterable not callable
Type: behavior Stage: resolved
Components: Tests Versions: Python 3.8
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: jazzblue, xtreak
Priority: normal Keywords: patch

Created on 2019-04-11 04:12 by jazzblue, last changed 2019-04-12 10:09 by xtreak. This issue is now closed.

Messages (4)
msg339923 - (view) Author: Gregory Ronin (jazzblue) Date: 2019-04-11 04:12
In mock.py, in method:
def _mock_call(_mock_self, *args, **kwargs):

There is a following piece of code:

    if not _callable(effect):
        result = next(effect)
        if _is_exception(result):
            raise result
        if result is DEFAULT:
            result = self.return_value
        return result

    ret_val = effect(*args, **kwargs)

This works correctly for iterables (such as lists) that are not defined as generators.
However, if one defined a generator as a function this would not work.

It seems like the check should be not for callable, but for iterable:

    try:
        iter(effect)
    except TypeError:
        # If not iterable then callable or exception
        if _callable(effect):
            ret_val = effect(*args, **kwargs)
        else:
            raise effect

    else:  # Iterable
        result = next(effect)
        if _is_exception(result):
            raise result
        if result is DEFAULT:
            result = self.return_value
        return result
msg339927 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python triager) Date: 2019-04-11 06:07
I am not sure if the snippets you are referring to are from testing-cabal/mock repo which could be different from master branch. Current code is at [0]

if effect is not None:
    if _is_exception(effect):
        raise effect
    elif not _callable(effect):
        result = next(effect)
        if _is_exception(result):
            raise result
    else:
        result = effect(*args, **kwargs)

    if result is not DEFAULT:
        return result

> This works correctly for iterables (such as lists) that are not defined as generators.
However, if one defined a generator as a function this would not work.

This does seem to work for generator function as below. Sorry, maybe I am getting it wrong with respect to terminologies and understanding the issue. Can you add a short script around what you are expecting?

$ cat ../backups/bpo36598.py
from unittest.mock import patch

def gen(i):
    while i < 5:
        yield i
        i += 1

def foo():
    return 1

with patch('__main__.foo', side_effect=gen(0)):
    for _ in range(2):
        print(foo())
    for _ in range(2):
        print(foo())
$ ./python.exe ../backups/bpo36598.py
0
1
2
3

[0] https://github.com/python/cpython/blob/a9bd8925c7fa50dd3cfab125b824ec192133ef49/Lib/unittest/mock.py#L1043
msg339992 - (view) Author: Gregory Ronin (jazzblue) Date: 2019-04-11 18:30
You are right. I was not calling generator the right way in mock. After I tried your suggestion it works.
msg340018 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python triager) Date: 2019-04-12 10:09
Thanks, I am closing this as not a bug. Feel free to reopen this if I have missed any.
History
Date User Action Args
2019-04-12 10:09:13xtreaksetstatus: open -> closed
resolution: not a bug
messages: + msg340018

stage: patch review -> resolved
2019-04-11 18:30:40jazzbluesetmessages: + msg339992
2019-04-11 16:55:28xtreaksetpull_requests: - pull_request12717
2019-04-11 16:53:28xtreaksetkeywords: + patch
stage: patch review
pull_requests: + pull_request12717
2019-04-11 06:07:46xtreaksetmessages: + msg339927
2019-04-11 05:32:52xtreaksetnosy: + xtreak

versions: + Python 3.8, - Python 2.7
2019-04-11 04:12:58jazzbluecreate