Skip to content

Commit

Permalink
TaskTask use saved coro_repr for __repr__ and ignore some Tasks
Browse files Browse the repository at this point in the history
Summary:
Tasks that are done() and have a result of None, should be considered managed.  thrift-py3 does this in servers when it calls rpc methods, we don't have a way for the thrift server to monitor the python task since its results are stored in a C++ Folly Promise object.

Also the coro object can be garbage collected before the end of the test which makes understanding left over task errors very hard. So use the coro_repr we save in the `__init__` to make `__repr__` more useful.

Reviewed By: cooperlees

Differential Revision: D24550663

fbshipit-source-id: a8a4e20f1fbc670b75d4f1c4dc9c4f289c3a9123
  • Loading branch information
fried authored and facebook-github-bot committed Oct 26, 2020
1 parent 48616c5 commit 1b8c09d
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 2 deletions.
2 changes: 1 addition & 1 deletion later/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from .task import START_TASK, Watcher, WatcherError, as_task, cancel


__version__ = "20.2.1"
__version__ = "20.10.1"
__all__ = [
"BiDirectionalEvent",
"START_TASK",
Expand Down
22 changes: 22 additions & 0 deletions later/tests/unittest/test_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,28 @@ async def test_managed_task_done(self):
)
await saved_done_task

async def test_managed_task_done_none(self):
global saved_done_task

async def coro(e: asyncio.Event) -> None:
e.set()

event = asyncio.Event()
saved_done_task = asyncio.get_running_loop().create_task(coro(event))
await event.wait()

@unittest.expectedFailure
async def test_unmanaged_task_done_value(self):
global saved_done_task

async def coro(e: asyncio.Event) -> bool:
e.set()
return False

event = asyncio.Event()
saved_done_task = asyncio.get_running_loop().create_task(coro(event))
await event.wait()

@unittest.expectedFailure
async def test_garbage_collected_task_during_testmethod(self):
t = asyncio.get_running_loop().create_task(asyncio.sleep(10))
Expand Down
22 changes: 21 additions & 1 deletion later/unittest/case.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import asyncio.futures
import asyncio.log
import asyncio.tasks
import sys
import unittest.mock as mock
from functools import wraps
from typing import Any, Callable, TypeVar
Expand All @@ -33,6 +34,7 @@
_F = TypeVar("_F", bound=Callable[..., Any])
_IGNORE_TASK_LEAKS_ATTR = "__later_testcase_ignore_tasks__"
_IGNORE_AIO_ERRS_ATTR = "__later_testcase_ignore_asyncio__"
atleastpy38 = sys.version_info[:2] >= (3, 8)


class TestTask(asyncio.Task):
Expand All @@ -43,6 +45,15 @@ def __init__(self, coro, *args, **kws):
self._coro_repr = asyncio.coroutines._format_coroutine(coro)
super().__init__(coro, *args, **kws)

def __repr__(self):
repr_info = self._repr_info()
coro = f"coro={self._coro_repr}"
if atleastpy38: # py3.8 added name=
repr_info[2] = coro # pragma: nocover
else:
repr_info[1] = coro # pragma: nocover
return f"<{self.__class__.__name__} {' '.join(repr_info)}>"

def __await__(self):
self._managed = True
return super().__await__()
Expand All @@ -66,7 +77,15 @@ def mark_managed(fut):
super().add_done_callback(mark_managed, context=context)

def was_managed(self):
return self._managed
if self._managed:
return True
# If the task is done() and the result is None, let it pass as managed
return (
self.done()
and not self.cancelled()
and not self.exception()
and self.result() is None
)

def __del__(self):
# So a pattern is to create_task, and not save the results.
Expand Down Expand Up @@ -148,6 +167,7 @@ def _callTestMethod(self, testMethod):
if not ignore_tasks:
# install our own task factory for monitoring usage
loop.set_task_factory(task_factory)

# Track existing tasks
start_tasks = all_tasks(loop)
# Setup a patch for the asyncio logger
Expand Down

0 comments on commit 1b8c09d

Please sign in to comment.