Skip to content

Commit

Permalink
Merge branch 'trunk' into 9976-unreachable-internet
Browse files Browse the repository at this point in the history
  • Loading branch information
wsanchez authored Sep 25, 2020
2 parents 83ccdab + 84845b1 commit 572f44a
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 25 deletions.
9 changes: 0 additions & 9 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -258,12 +258,3 @@ warn_unreachable = False

[mypy-twisted.persisted.sob]
warn_unreachable = False

[mypy-twisted.python.test.test_inotify]
warn_unreachable = False

[mypy-twisted.web.http_headers]
warn_unreachable = False

[mypy-twisted.web.twcgi]
warn_unreachable = False
17 changes: 9 additions & 8 deletions src/twisted/internet/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -844,7 +844,8 @@ def deferLaterCancel(deferred):

def react(main, argv=(), _reactor=None):
"""
Call C{main} and run the reactor until the L{Deferred} it returns fires.
Call C{main} and run the reactor until the L{Deferred} it returns fires or
the coroutine it returns completes.
This is intended as the way to start up an application with a well-defined
completion condition. Use it to write clients or one-off asynchronous
Expand All @@ -860,14 +861,14 @@ def react(main, argv=(), _reactor=None):
The following demonstrates the signature of a C{main} function which can be
used with L{react}::
def main(reactor, username, password):
return defer.succeed('ok')
async def main(reactor, username, password):
return "ok"
task.react(main, ('alice', 'secret'))
task.react(main, ("alice", "secret"))
@param main: A callable which returns a L{Deferred}. It should
take the reactor as its first parameter, followed by the elements of
C{argv}.
@param main: A callable which returns a L{Deferred} or
coroutine. It should take the reactor as its first
parameter, followed by the elements of C{argv}.
@param argv: A list of arguments to pass to C{main}. If omitted the
callable will be invoked with no additional arguments.
Expand All @@ -879,7 +880,7 @@ def main(reactor, username, password):
"""
if _reactor is None:
from twisted.internet import reactor as _reactor
finished = main(_reactor, *argv)
finished = defer.ensureDeferred(main(_reactor, *argv))
codes = [0]

stopping = []
Expand Down
1 change: 1 addition & 0 deletions src/twisted/newsfragments/9974.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support a coroutine function in twisted.internet.task.react
Empty file.
Empty file.
18 changes: 12 additions & 6 deletions src/twisted/python/test/test_inotify.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,26 @@
from twisted.python.filepath import FilePath
from twisted.python.runtime import platform

if platform.supportsINotify():
from ctypes import c_int, c_uint32, c_char_p
try:
from twisted.python import _inotify
except ImportError:
inotify = None
else:
inotify = _inotify

if inotify and platform.supportsINotify():
from ctypes import c_int, c_uint32, c_char_p
from twisted.python._inotify import INotifyError, initializeModule, init, add
else:
_inotify = None # type: ignore[assignment]
inotify = None


class INotifyTests(TestCase):
"""
Tests for L{twisted.python._inotify}.
"""

if _inotify is None:
if inotify is None:
skip = "This platform doesn't support INotify."

def test_missingInit(self):
Expand Down Expand Up @@ -114,7 +120,7 @@ class libc:
def inotify_init(self):
return -1

self.patch(_inotify, "libc", libc())
self.patch(inotify, "libc", libc())
self.assertRaises(INotifyError, init)

def test_failedAddWatch(self):
Expand All @@ -127,5 +133,5 @@ class libc:
def inotify_add_watch(self, fd, path, mask):
return -1

self.patch(_inotify, "libc", libc())
self.patch(inotify, "libc", libc())
self.assertRaises(INotifyError, add, 3, FilePath("/foo"), 0)
211 changes: 211 additions & 0 deletions src/twisted/test/test_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -1205,3 +1205,214 @@ def main(reactor):
r = _FakeReactor()
exitError = self.assertRaises(SystemExit, task.react, main, [], _reactor=r)
self.assertEqual(0, exitError.code)


class ReactCoroutineFunctionTests(unittest.SynchronousTestCase):
"""
Tests for L{twisted.internet.task.react} with an C{async def} argument
"""

def test_runsUntilAsyncCallback(self):
"""
L{task.react} runs the reactor until the L{Deferred} returned by the
function it is passed is called back, then stops it.
"""
timePassed = []

async def main(reactor):
finished = defer.Deferred()
reactor.callLater(1, timePassed.append, True)
reactor.callLater(2, finished.callback, None)
return await finished

r = _FakeReactor()
exitError = self.assertRaises(SystemExit, task.react, main, _reactor=r)
self.assertEqual(0, exitError.code)
self.assertEqual(timePassed, [True])
self.assertEqual(r.seconds(), 2)

def test_runsUntilSyncCallback(self):
"""
L{task.react} returns quickly if the L{Deferred} returned by the
function it is passed has already been called back at the time it is
returned.
"""

async def main(reactor):
return await defer.succeed(None)

r = _FakeReactor()
exitError = self.assertRaises(SystemExit, task.react, main, _reactor=r)
self.assertEqual(0, exitError.code)
self.assertEqual(r.seconds(), 0)

def test_runsUntilAsyncErrback(self):
"""
L{task.react} runs the reactor until the L{defer.Deferred} returned by
the function it is passed is errbacked, then it stops the reactor and
reports the error.
"""

class ExpectedException(Exception):
pass

async def main(reactor):
finished = defer.Deferred()
reactor.callLater(1, finished.errback, ExpectedException())
return await finished

r = _FakeReactor()
exitError = self.assertRaises(SystemExit, task.react, main, _reactor=r)

self.assertEqual(1, exitError.code)

errors = self.flushLoggedErrors(ExpectedException)
self.assertEqual(len(errors), 1)

def test_runsUntilSyncErrback(self):
"""
L{task.react} returns quickly if the L{defer.Deferred} returned by the
function it is passed has already been errbacked at the time it is
returned.
"""

class ExpectedException(Exception):
pass

async def main(reactor):
return await defer.fail(ExpectedException())

r = _FakeReactor()
exitError = self.assertRaises(SystemExit, task.react, main, _reactor=r)
self.assertEqual(1, exitError.code)
self.assertEqual(r.seconds(), 0)
errors = self.flushLoggedErrors(ExpectedException)
self.assertEqual(len(errors), 1)

def test_singleStopCallback(self):
"""
L{task.react} doesn't try to stop the reactor if the L{defer.Deferred}
the function it is passed is called back after the reactor has already
been stopped.
"""

async def main(reactor):
reactor.callLater(1, reactor.stop)
finished = defer.Deferred()
reactor.addSystemEventTrigger("during", "shutdown", finished.callback, None)
return await finished

r = _FakeReactor()
exitError = self.assertRaises(SystemExit, task.react, main, _reactor=r)
self.assertEqual(r.seconds(), 1)

self.assertEqual(0, exitError.code)

def test_singleStopErrback(self):
"""
L{task.react} doesn't try to stop the reactor if the L{defer.Deferred}
the function it is passed is errbacked after the reactor has already
been stopped.
"""

class ExpectedException(Exception):
pass

async def main(reactor):
reactor.callLater(1, reactor.stop)
finished = defer.Deferred()
reactor.addSystemEventTrigger(
"during", "shutdown", finished.errback, ExpectedException()
)
return await finished

r = _FakeReactor()
exitError = self.assertRaises(SystemExit, task.react, main, _reactor=r)

self.assertEqual(1, exitError.code)

self.assertEqual(r.seconds(), 1)
errors = self.flushLoggedErrors(ExpectedException)
self.assertEqual(len(errors), 1)

def test_arguments(self):
"""
L{task.react} passes the elements of the list it is passed as
positional arguments to the function it is passed.
"""
args = []

async def main(reactor, x, y, z):
args.extend((x, y, z))
return await defer.succeed(None)

r = _FakeReactor()
exitError = self.assertRaises(
SystemExit, task.react, main, [1, 2, 3], _reactor=r
)
self.assertEqual(0, exitError.code)
self.assertEqual(args, [1, 2, 3])

def test_defaultReactor(self):
"""
L{twisted.internet.reactor} is used if no reactor argument is passed to
L{task.react}.
"""

async def main(reactor):
self.passedReactor = reactor
return await defer.succeed(None)

reactor = _FakeReactor()
with NoReactor():
installReactor(reactor)
exitError = self.assertRaises(SystemExit, task.react, main, [])
self.assertEqual(0, exitError.code)
self.assertIs(reactor, self.passedReactor)

def test_exitWithDefinedCode(self):
"""
L{task.react} forwards the exit code specified by the C{SystemExit}
error returned by the passed function, if any.
"""

async def main(reactor):
return await defer.fail(SystemExit(23))

r = _FakeReactor()
exitError = self.assertRaises(SystemExit, task.react, main, [], _reactor=r)
self.assertEqual(23, exitError.code)

def test_synchronousStop(self):
"""
L{task.react} handles when the reactor is stopped just before the
returned L{Deferred} fires.
"""

async def main(reactor):
d = defer.Deferred()

def stop():
reactor.stop()
d.callback(None)

reactor.callWhenRunning(stop)
return await d

r = _FakeReactor()
exitError = self.assertRaises(SystemExit, task.react, main, [], _reactor=r)
self.assertEqual(0, exitError.code)

def test_asynchronousStop(self):
"""
L{task.react} handles when the reactor is stopped and the
returned L{Deferred} doesn't fire.
"""

async def main(reactor):
reactor.callLater(1, reactor.stop)
return await defer.Deferred()

r = _FakeReactor()
exitError = self.assertRaises(SystemExit, task.react, main, [], _reactor=r)
self.assertEqual(0, exitError.code)
2 changes: 1 addition & 1 deletion src/twisted/web/http_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ def getRawHeaders(
return default

if isinstance(name, str):
return [v if isinstance(v, str) else v.decode("utf8") for v in values]
return [v.decode("utf8") for v in values]
return values

def getAllRawHeaders(self) -> Iterator[Tuple[bytes, List[bytes]]]:
Expand Down
1 change: 0 additions & 1 deletion src/twisted/web/twcgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ def getChild(self, path, request):
return CGIDirectory(fnp.path)
else:
return CGIScript(fnp.path)
return resource.NoResource()

def render(self, request):
notFound = resource.NoResource(
Expand Down

0 comments on commit 572f44a

Please sign in to comment.