asyncio, Tulip issue #136: Add get/set_debug() methods to BaseEventLoopTests.

Add also a PYTHONASYNCIODEBUG environment variable to debug coroutines since
Python startup, to be able to debug coroutines defined directly in the asyncio
module.
This commit is contained in:
Victor Stinner 2014-02-19 23:15:02 +01:00
parent f4558e8b54
commit 7ef60cd8c2
8 changed files with 83 additions and 5 deletions

View file

@ -1,5 +1,7 @@
.. currentmodule:: asyncio .. currentmodule:: asyncio
.. _asyncio-dev:
Develop with asyncio Develop with asyncio
==================== ====================
@ -81,10 +83,10 @@ Detect coroutine objects never scheduled
When a coroutine function is called but not passed to :func:`async` or to the When a coroutine function is called but not passed to :func:`async` or to the
:class:`Task` constructor, it is not scheduled and it is probably a bug. :class:`Task` constructor, it is not scheduled and it is probably a bug.
To detect such bug, set :data:`asyncio.tasks._DEBUG` to ``True``. When the To detect such bug, set the environment variable :envvar:`PYTHONASYNCIODEBUG`
coroutine object is destroyed by the garbage collector, a log will be emitted to ``1``. When the coroutine object is destroyed by the garbage collector, a
with the traceback where the coroutine function was called. See the log will be emitted with the traceback where the coroutine function was called.
:ref:`asyncio logger <asyncio-logger>`. See the :ref:`asyncio logger <asyncio-logger>`.
The debug flag changes the behaviour of the :func:`coroutine` decorator. The The debug flag changes the behaviour of the :func:`coroutine` decorator. The
debug flag value is only used when then coroutine function is defined, not when debug flag value is only used when then coroutine function is defined, not when

View file

@ -553,6 +553,22 @@ pool of processes). By default, an event loop uses a thread pool executor
Set the default executor used by :meth:`run_in_executor`. Set the default executor used by :meth:`run_in_executor`.
Debug mode
----------
.. method:: BaseEventLoop.get_debug()
Get the debug mode (:class:`bool`) of the event loop.
.. method:: BaseEventLoop.set_debug(enabled: bool)
Set the debug mode of the event loop.
.. seealso::
The :ref:`Develop with asyncio <asyncio-dev>` section.
Server Server
------ ------

View file

@ -614,6 +614,14 @@ conflict.
.. versionadded:: 3.4 .. versionadded:: 3.4
.. envvar:: PYTHONASYNCIODEBUG
If this environment variable is set to a non-empty string, enable the debug
mode of the :mod:`asyncio` module.
.. versionadded:: 3.4
Debug-mode variables Debug-mode variables
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~

View file

@ -123,6 +123,7 @@ class BaseEventLoop(events.AbstractEventLoop):
self._running = False self._running = False
self._clock_resolution = time.get_clock_info('monotonic').resolution self._clock_resolution = time.get_clock_info('monotonic').resolution
self._exception_handler = None self._exception_handler = None
self._debug = False
def _make_socket_transport(self, sock, protocol, waiter=None, *, def _make_socket_transport(self, sock, protocol, waiter=None, *,
extra=None, server=None): extra=None, server=None):
@ -795,3 +796,9 @@ class BaseEventLoop(events.AbstractEventLoop):
if not handle._cancelled: if not handle._cancelled:
handle._run() handle._run()
handle = None # Needed to break cycles when an exception occurs. handle = None # Needed to break cycles when an exception occurs.
def get_debug(self):
return self._debug
def set_debug(self, enabled):
self._debug = enabled

View file

@ -345,6 +345,14 @@ class AbstractEventLoop:
def call_exception_handler(self, context): def call_exception_handler(self, context):
raise NotImplementedError raise NotImplementedError
# Debug flag management.
def get_debug(self):
raise NotImplementedError
def set_debug(self, enabled):
raise NotImplementedError
class AbstractEventLoopPolicy: class AbstractEventLoopPolicy:
"""Abstract policy for accessing the event loop.""" """Abstract policy for accessing the event loop."""

View file

@ -12,6 +12,8 @@ import concurrent.futures
import functools import functools
import inspect import inspect
import linecache import linecache
import os
import sys
import traceback import traceback
import weakref import weakref
@ -28,7 +30,8 @@ from .log import logger
# before you define your coroutines. A downside of using this feature # before you define your coroutines. A downside of using this feature
# is that tracebacks show entries for the CoroWrapper.__next__ method # is that tracebacks show entries for the CoroWrapper.__next__ method
# when _DEBUG is true. # when _DEBUG is true.
_DEBUG = False _DEBUG = (not sys.flags.ignore_environment
and bool(os.environ.get('PYTHONASYNCIODEBUG')))
class CoroWrapper: class CoroWrapper:

View file

@ -197,6 +197,12 @@ class BaseEventLoopTests(unittest.TestCase):
self.assertEqual([h2], self.loop._scheduled) self.assertEqual([h2], self.loop._scheduled)
self.assertTrue(self.loop._process_events.called) self.assertTrue(self.loop._process_events.called)
def test_set_debug(self):
self.loop.set_debug(True)
self.assertTrue(self.loop.get_debug())
self.loop.set_debug(False)
self.assertFalse(self.loop.get_debug())
@unittest.mock.patch('asyncio.base_events.time') @unittest.mock.patch('asyncio.base_events.time')
@unittest.mock.patch('asyncio.base_events.logger') @unittest.mock.patch('asyncio.base_events.logger')
def test__run_once_logging(self, m_logger, m_time): def test__run_once_logging(self, m_logger, m_time):

View file

@ -1,7 +1,9 @@
"""Tests for tasks.py.""" """Tests for tasks.py."""
import gc import gc
import os.path
import unittest import unittest
from test.script_helper import assert_python_ok
import asyncio import asyncio
from asyncio import test_utils from asyncio import test_utils
@ -1461,6 +1463,32 @@ class GatherTestsBase:
cb.assert_called_once_with(fut) cb.assert_called_once_with(fut)
self.assertEqual(fut.result(), [3, 1, exc, exc2]) self.assertEqual(fut.result(), [3, 1, exc, exc2])
def test_env_var_debug(self):
path = os.path.dirname(asyncio.__file__)
path = os.path.normpath(os.path.join(path, '..'))
code = '\n'.join((
'import sys',
'sys.path.insert(0, %r)' % path,
'import asyncio.tasks',
'print(asyncio.tasks._DEBUG)'))
# Test with -E to not fail if the unit test was run with
# PYTHONASYNCIODEBUG set to a non-empty string
sts, stdout, stderr = assert_python_ok('-E', '-c', code)
self.assertEqual(stdout.rstrip(), b'False')
sts, stdout, stderr = assert_python_ok('-c', code,
PYTHONASYNCIODEBUG='')
self.assertEqual(stdout.rstrip(), b'False')
sts, stdout, stderr = assert_python_ok('-c', code,
PYTHONASYNCIODEBUG='1')
self.assertEqual(stdout.rstrip(), b'True')
sts, stdout, stderr = assert_python_ok('-E', '-c', code,
PYTHONASYNCIODEBUG='1')
self.assertEqual(stdout.rstrip(), b'False')
class FutureGatherTests(GatherTestsBase, unittest.TestCase): class FutureGatherTests(GatherTestsBase, unittest.TestCase):