mirror of
https://github.com/python/cpython.git
synced 2025-09-27 02:39:58 +00:00
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:
parent
f4558e8b54
commit
7ef60cd8c2
8 changed files with 83 additions and 5 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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."""
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue