Skip to content

Commit

Permalink
100% coverage for trigger.py. Fix sockettrigger on Windows + Python 3.
Browse files Browse the repository at this point in the history
  • Loading branch information
jamadden committed Oct 26, 2017
1 parent 2bfe53a commit 524363e
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 7 deletions.
68 changes: 67 additions & 1 deletion src/zope/server/tests/test_trigger.py
Expand Up @@ -30,7 +30,73 @@ def _makeOne(self):
self.addCleanup(t.close)
return t

class TestSocketTrigger(unittest.TestCase):
def test_repr(self):
t = self._makeOne()
self.assertIn(self._getFUT().kind, repr(t))

def test_handle_connect(self):
t = self._makeOne()
t.handle_connect()

def test_handle_close(self):
t = self._makeOne()
t.handle_close()

def test_handle_read_error(self):
t = self._makeOne()
t.close()
def recv(_s):
import socket
raise socket.error
t.recv = recv
def thunk():
raise AssertionError("I should not be called")
t.thunks.append(thunk)
t.handle_read()

def test_thunk_error(self):
import io
buf = io.BytesIO() if bytes is str else io.StringIO()
def thunk():
raise Exception("TestException")
t = self._makeOne()
t.close()
t.thunks.append(thunk)
trigger.print = buf.write
try:
t.handle_read()
finally:
del trigger.print

self.assertIn('TestException', buf.getvalue())

def test_pull(self):
t = self._makeOne()
t.pull_trigger()
# The side effects of this are hard to test

class TestSocketTrigger(TestPipeTrigger):

def _getFUT(self):
return trigger.sockettrigger

def test_connect_failure_retry(self):

class T(self._getFUT()):
def _connect_client(self, w, connect_address):
import socket
import errno
raise socket.error(errno.EADDRINUSE)

with self.assertRaises(trigger.BindError):
T()

def test_connect_failure_no_retry(self):
import socket

class T(self._getFUT()):
def _connect_client(self, w, connect_address):
raise socket.error("Nope")

with self.assertRaises(socket.error):
T()
21 changes: 15 additions & 6 deletions src/zope/server/trigger.py
Expand Up @@ -11,7 +11,7 @@
# FOR A PARTICULAR PURPOSE
#
##############################################################################

from __future__ import print_function
import asyncore
import os
import socket
Expand Down Expand Up @@ -159,7 +159,10 @@ def __init__(self):

def _close(self):
for fd in self._fds:
os.close(fd)
try:
os.close(fd)
except OSError:
pass
self._fds = []

def _physical_pull(self):
Expand All @@ -173,6 +176,9 @@ class sockettrigger(_triggerbase, asyncore.dispatcher):
# on Windows.
kind = "loopback"

ADDR_IN_USE_CODES = (getattr(errno, 'EADDRINUSE', -1),
getattr(errno, 'WSAEADDRINUSE', -1))

def __init__(self):
_triggerbase.__init__(self)

Expand Down Expand Up @@ -205,10 +211,10 @@ def __init__(self):
connect_address = a.getsockname() # assigned (host, port) pair
a.listen(1)
try:
w.connect(connect_address)
self._connect_client(w, connect_address)
break # success
except socket.error as detail:
if detail.args[0] != errno.WSAEADDRINUSE:
if detail.args[0] not in self.ADDR_IN_USE_CODES:
# "Address already in use" is the only error
# I've seen on two WinXP Pro SP2 boxes, under
# Pythons 2.3.5 and 2.4.1.
Expand All @@ -228,15 +234,18 @@ def __init__(self):
self.trigger = w
asyncore.dispatcher.__init__(self, r)

def _connect_client(self, w, connect_address):
w.connect(connect_address)

def _close(self):
# self.socket is r, and self.trigger is w, from __init__
self.socket.close()
self.trigger.close()

def _physical_pull(self):
self.trigger.send('x')
self.trigger.send(b'x')

if os.name == 'posix':
trigger = pipetrigger
else:
else: # pragma: no cover
trigger = sockettrigger

0 comments on commit 524363e

Please sign in to comment.