diff --git a/src/twisted/internet/test/reactormixins.py b/src/twisted/internet/test/reactormixins.py index b5984669588..5bfb781e7c9 100644 --- a/src/twisted/internet/test/reactormixins.py +++ b/src/twisted/internet/test/reactormixins.py @@ -18,7 +18,7 @@ import os import signal import time -from typing import Dict, Optional, Sequence, Type, Union +from typing import TYPE_CHECKING, Dict, Optional, Sequence, Type, Union from zope.interface import Interface @@ -30,6 +30,11 @@ from twisted.trial.unittest import SkipTest, SynchronousTestCase from twisted.trial.util import DEFAULT_TIMEOUT_DURATION, acquireAttribute +if TYPE_CHECKING: + # Only bring in this name to support the type annotation below. We don't + # really want to import a reactor module this early at runtime. + from twisted.internet import asyncioreactor + # Access private APIs. try: from twisted.internet import process as _process @@ -153,7 +158,7 @@ class ReactorBuilder: ] ) - _reactors.append("twisted.internet.asyncioreactor.AsyncioSelectorReactor") + _reactors.append("twisted.internet.test.reactormixins.AsyncioSelectorReactor") if platform.isMacOSX(): _reactors.append("twisted.internet.cfreactor.CFReactor") @@ -366,3 +371,31 @@ class testcase(cls, SynchronousTestCase): # type: ignore[valid-type,misc] testcase.__qualname__ = ".".join(cls.__qualname__.split()[0:-1] + [name]) classes[testcase.__name__] = testcase return classes + + +def asyncioSelectorReactor(self: object) -> "asyncioreactor.AsyncioSelectorReactor": + """ + Make a new asyncio reactor associated with a new event loop. + + The test suite prefers this constructor because having a new event loop + for each reactor provides better test isolation. The real constructor + prefers to re-use (or create) a global loop because of how this interacts + with other asyncio-based libraries and applications (though maybe it + shouldn't). + + @param self: The L{ReactorBuilder} subclass this is being called on. We + don't use this parameter but we get called with it anyway. + """ + from asyncio import new_event_loop, set_event_loop + + from twisted.internet import asyncioreactor + + loop = new_event_loop() + set_event_loop(loop) + + return asyncioreactor.AsyncioSelectorReactor(loop) + + +# Give it an alias that makes the names of the generated test classes fit the +# pattern. +AsyncioSelectorReactor = asyncioSelectorReactor diff --git a/src/twisted/internet/test/test_threads.py b/src/twisted/internet/test/test_threads.py index 68487ef0724..3b45a4cff86 100644 --- a/src/twisted/internet/test/test_threads.py +++ b/src/twisted/internet/test/test_threads.py @@ -172,20 +172,8 @@ def test_cleanUpThreadPoolEvenBeforeReactorIsRun(self): reactor = self.buildReactor() threadPoolRef = ref(reactor.getThreadPool()) reactor.fireSystemEvent("shutdown") - - if reactor.__class__.__name__ == "AsyncioSelectorReactor": - self.assertIsNone(reactor.threadpool) - # ReactorBase.__init__ sets self.crash as a 'shutdown' - # event, which in turn calls stop on the underlying - # asyncio event loop, which in turn sets a _stopping - # attribute on it that's only unset after an iteration of - # the loop. Subsequent tests can only reuse the asyncio - # loop if it's allowed to run and unset that _stopping - # attribute. - self.runReactor(reactor) - else: - gc.collect() - self.assertIsNone(threadPoolRef()) + gc.collect() + self.assertIsNone(threadPoolRef()) def test_isInIOThread(self): """ diff --git a/src/twisted/internet/test/test_time.py b/src/twisted/internet/test/test_time.py index d68176cd79f..0e847f6e34c 100644 --- a/src/twisted/internet/test/test_time.py +++ b/src/twisted/internet/test/test_time.py @@ -43,7 +43,7 @@ def eventSource(reactor, event): else: raise SkipTest( - "Do not know how to synthesize non-time event to " "stop the test" + "Do not know how to synthesize non-time event to stop the test" ) # Pick a pretty big delay. diff --git a/src/twisted/newsfragments/11694.misc b/src/twisted/newsfragments/11694.misc new file mode 100644 index 00000000000..e69de29bb2d