classification
Title: await_args_list in AsyncMock always refers to the last awaited call object
Type: behavior Stage: resolved
Components: asyncio Versions: Python 3.9, Python 3.8
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: Nosy List: Mads Sejersen, asvetlov, cjw296, lisroach, mariocj89, michael.foord, miss-islington, xtreak, yselivanov
Priority: normal Keywords: patch

Created on 2020-03-09 16:29 by Mads Sejersen, last changed 2020-03-14 07:17 by xtreak. This issue is now closed.

Files
File name Uploaded Description Edit
fail.py Mads Sejersen, 2020-03-09 16:29
Pull Requests
URL Status Linked Edit
PR 18924 merged xtreak, 2020-03-11 14:37
PR 18927 merged miss-islington, 2020-03-11 15:06
Messages (6)
msg363748 - (view) Author: Mads Sejersen (Mads Sejersen) Date: 2020-03-09 16:29
When calling asyncio.gather the await_args_list is not correct. In the attached minimal example it contains only the latest call and not each of the three actual calls.

Expected output:
[call(0), call(1), call(2)]
[call(1), call(2), call(3)]  # or any permutation hereof

Actual output:
[call(0), call(1), call(2)]
[call(3), call(3), call(3)]
msg363815 - (view) Author: Mads Sejersen (Mads Sejersen) Date: 2020-03-10 08:46
It can actually be boiled down to an even more minimal example. It looks like the problem is that the function call is stored for later when called, then overwritten by other subsequent calls. Then, once awaited, the latest registered call is added to the await_args_list instead of the call that actually happened.

```
import asyncio
from unittest.mock import AsyncMock


async def main():
    foo = AsyncMock()

    foo1 = foo(1)
    foo2 = foo(2)
    await foo1
    await foo2
    print(foo.await_args_list)


asyncio.run(main())
```
msg363817 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python committer) Date: 2020-03-10 09:20
Thanks for the report. At [0] self.call_args is used. This value is always the last call. So when the object is awaited it will be always the last call object and gets appended. The id of the call objects remain the same. The fix would be to construct the call object with args and kwargs in asynchronous definition of _execute_mock_call like [1]. This would need tests too. It seems there is only test for one await call and assertion over await_args_list. Having different args and kwargs with multiple await to assert await_args_list would help here. In synchronous case the call object is eagerly appended so it won't be an issue.

[0] https://github.com/python/cpython/blob/8510f430781118d9b603c3a2f06945d6ebc5fe42/Lib/unittest/mock.py#L2174
[1] https://github.com/python/cpython/blob/8510f430781118d9b603c3a2f06945d6ebc5fe42/Lib/unittest/mock.py#L1106

A potential patch would be as below : 

diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py
index 9e692981a2..20daf724c1 100644
--- a/Lib/unittest/mock.py
+++ b/Lib/unittest/mock.py
@@ -2171,7 +2171,7 @@ class AsyncMockMixin(Base):
         # This is nearly just like super(), except for special handling
         # of coroutines
 
-        _call = self.call_args
+        _call = _Call((args, kwargs), two=True)
         self.await_count += 1
         self.await_args = _call
         self.await_args_list.append(_call)
msg363928 - (view) Author: Chris Withers (cjw296) * (Python committer) Date: 2020-03-11 15:06
New changeset e553f204bf0e39b1d701a364bc71b286acb9433f by Karthikeyan Singaravelan in branch 'master':
bpo-39915: Ensure await_args_list is updated according to the order in which coroutines were awaited (GH-18924)
https://github.com/python/cpython/commit/e553f204bf0e39b1d701a364bc71b286acb9433f
msg364147 - (view) Author: Chris Withers (cjw296) * (Python committer) Date: 2020-03-14 07:13
New changeset f6bdac1bf718eab0cc5b6554f363f21252d245ce by Miss Islington (bot) in branch '3.8':
bpo-39915: Ensure await_args_list is updated according to the order in which coroutines were awaited (GH-18927)
https://github.com/python/cpython/commit/f6bdac1bf718eab0cc5b6554f363f21252d245ce
msg364148 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python committer) Date: 2020-03-14 07:17
Thanks Mads Sejersen for the report. Closing this as fixed as it's merged to 3.9 and 3.8.
History
Date User Action Args
2020-03-14 07:17:26xtreaksetstatus: open -> closed
versions: + Python 3.9
messages: + msg364148

resolution: fixed
stage: patch review -> resolved
2020-03-14 07:13:01cjw296setmessages: + msg364147
2020-03-11 15:06:31miss-islingtonsetnosy: + miss-islington
pull_requests: + pull_request18279
2020-03-11 15:06:18cjw296setmessages: + msg363928
2020-03-11 14:37:57xtreaksetkeywords: + patch
stage: patch review
pull_requests: + pull_request18276
2020-03-10 09:20:14xtreaksetnosy: + cjw296, michael.foord, mariocj89

messages: + msg363817
title: AsyncMock doesn't work with asyncio.gather -> await_args_list in AsyncMock always refers to the last awaited call object
2020-03-10 08:46:18Mads Sejersensetmessages: + msg363815
2020-03-09 17:47:06xtreaksetnosy: + lisroach, xtreak
2020-03-09 16:29:08Mads Sejersencreate