|
13 | 13 | import logging
|
14 | 14 | import os
|
15 | 15 | import re
|
| 16 | +import textwrap |
16 | 17 | import time
|
| 18 | +import traceback |
17 | 19 | import unittest
|
18 | 20 |
|
19 | 21 |
|
@@ -109,6 +111,21 @@ def tearDownClass(cls):
|
109 | 111 | cls.loop.close()
|
110 | 112 | asyncio.set_event_loop(None)
|
111 | 113 |
|
| 114 | + def setUp(self): |
| 115 | + self.loop.set_exception_handler(self.loop_exception_handler) |
| 116 | + self.__unhandled_exceptions = [] |
| 117 | + |
| 118 | + def tearDown(self): |
| 119 | + if self.__unhandled_exceptions: |
| 120 | + formatted = [] |
| 121 | + |
| 122 | + for i, context in enumerate(self.__unhandled_exceptions): |
| 123 | + formatted.append(self._format_loop_exception(context, i + 1)) |
| 124 | + |
| 125 | + self.fail( |
| 126 | + 'unexpected exceptions in asynchronous code:\n' + |
| 127 | + '\n'.join(formatted)) |
| 128 | + |
112 | 129 | @contextlib.contextmanager
|
113 | 130 | def assertRunUnder(self, delta):
|
114 | 131 | st = time.monotonic()
|
@@ -146,6 +163,44 @@ def handler(loop, ctx):
|
146 | 163 | finally:
|
147 | 164 | self.loop.set_exception_handler(old_handler)
|
148 | 165 |
|
| 166 | + def loop_exception_handler(self, loop, context): |
| 167 | + self.__unhandled_exceptions.append(context) |
| 168 | + loop.default_exception_handler(context) |
| 169 | + |
| 170 | + def _format_loop_exception(self, context, n): |
| 171 | + message = context.get('message', 'Unhandled exception in event loop') |
| 172 | + exception = context.get('exception') |
| 173 | + if exception is not None: |
| 174 | + exc_info = (type(exception), exception, exception.__traceback__) |
| 175 | + else: |
| 176 | + exc_info = None |
| 177 | + |
| 178 | + lines = [] |
| 179 | + for key in sorted(context): |
| 180 | + if key in {'message', 'exception'}: |
| 181 | + continue |
| 182 | + value = context[key] |
| 183 | + if key == 'source_traceback': |
| 184 | + tb = ''.join(traceback.format_list(value)) |
| 185 | + value = 'Object created at (most recent call last):\n' |
| 186 | + value += tb.rstrip() |
| 187 | + else: |
| 188 | + try: |
| 189 | + value = repr(value) |
| 190 | + except Exception as ex: |
| 191 | + value = ('Exception in __repr__ {!r}; ' |
| 192 | + 'value type: {!r}'.format(ex, type(value))) |
| 193 | + lines.append('[{}]: {}\n\n'.format(key, value)) |
| 194 | + |
| 195 | + if exc_info is not None: |
| 196 | + lines.append('[exception]:\n') |
| 197 | + formatted_exc = textwrap.indent( |
| 198 | + ''.join(traceback.format_exception(*exc_info)), ' ') |
| 199 | + lines.append(formatted_exc) |
| 200 | + |
| 201 | + details = textwrap.indent(''.join(lines), ' ') |
| 202 | + return '{:02d}. {}:\n{}\n'.format(n, message, details) |
| 203 | + |
149 | 204 |
|
150 | 205 | _default_cluster = None
|
151 | 206 |
|
|
0 commit comments