Commit d74900eb authored by Josiah Carlson's avatar Josiah Carlson

Committing Py3k version of changelist 64080 and 64257, along with updated tests

for smtpd, which required updating with the new semantics.
parent d51ee54a
...@@ -81,6 +81,12 @@ connection requests. ...@@ -81,6 +81,12 @@ connection requests.
:exc:`NotImplementedError` exception. :exc:`NotImplementedError` exception.
.. method:: async_chat._collect_incoming_data(data)
Sample implementation of a data collection rutine to be used in conjunction
with :meth:`_get_data` in a user-specified :meth:`found_terminator`.
.. method:: async_chat.discard_buffers() .. method:: async_chat.discard_buffers()
In emergencies this method will discard any data held in the input and/or In emergencies this method will discard any data held in the input and/or
...@@ -95,6 +101,12 @@ connection requests. ...@@ -95,6 +101,12 @@ connection requests.
should be available via an instance attribute. should be available via an instance attribute.
.. method:: async_chat._get_data()
Will return and clear the data received with the sample
:meth:`_collect_incoming_data` implementation.
.. method:: async_chat.get_terminator() .. method:: async_chat.get_terminator()
Returns the current terminator for the channel. Returns the current terminator for the channel.
......
...@@ -222,6 +222,21 @@ any that have been added to the map during asynchronous service) is closed. ...@@ -222,6 +222,21 @@ any that have been added to the map during asynchronous service) is closed.
flushed). Sockets are automatically closed when they are flushed). Sockets are automatically closed when they are
garbage-collected. garbage-collected.
.. class:: file_dispatcher()
A file_dispatcher takes a file descriptor or file object along with an
optional map argument and wraps it for use with the :cfunc:`poll` or
:cfunc:`loop` functions. If provided a file object or anything with a
:cfunc:`fileno` method, that method will be called and passed to the
:class:`file_wrapper` constructor. Availability: UNIX.
.. class:: file_wrapper()
A file_wrapper takes an integer file descriptor and calls :func:`os.dup` to
duplicate the handle so that the original handle may be closed independently
of the file_wrapper. This class implements sufficient methods to emulate a
socket for use by the :class:`file_dispatcher` class. Availability: UNIX.
.. _asyncore-example: .. _asyncore-example:
......
...@@ -45,12 +45,23 @@ command will be accumulated (using your own 'collect_incoming_data' ...@@ -45,12 +45,23 @@ command will be accumulated (using your own 'collect_incoming_data'
method) up to the terminator, and then control will be returned to method) up to the terminator, and then control will be returned to
you - by calling your self.found_terminator() method. you - by calling your self.found_terminator() method.
""" """
import sys
import socket import socket
import asyncore import asyncore
from collections import deque from collections import deque
def buffer(obj, start=None, stop=None):
# if memoryview objects gain slicing semantics,
# this function will change for the better
# memoryview used for the TypeError
memoryview(obj)
if start == None:
start = 0
if stop == None:
stop = len(obj)
x = obj[start:stop]
## print("buffer type is: %s"%(type(x),))
return x
class async_chat (asyncore.dispatcher): class async_chat (asyncore.dispatcher):
"""This is an abstract class. You must derive from this class, and add """This is an abstract class. You must derive from this class, and add
the two methods collect_incoming_data() and found_terminator()""" the two methods collect_incoming_data() and found_terminator()"""
...@@ -60,20 +71,47 @@ class async_chat (asyncore.dispatcher): ...@@ -60,20 +71,47 @@ class async_chat (asyncore.dispatcher):
ac_in_buffer_size = 4096 ac_in_buffer_size = 4096
ac_out_buffer_size = 4096 ac_out_buffer_size = 4096
# we don't want to enable the use of encoding by default, because that is a
# sign of an application bug that we don't want to pass silently
use_encoding = 0
encoding = 'latin1'
def __init__ (self, conn=None): def __init__ (self, conn=None):
# for string terminator matching
self.ac_in_buffer = b'' self.ac_in_buffer = b''
self.ac_out_buffer = b''
self.producer_fifo = fifo() # we use a list here rather than cStringIO for a few reasons...
# del lst[:] is faster than sio.truncate(0)
# lst = [] is faster than sio.truncate(0)
# cStringIO will be gaining unicode support in py3k, which
# will negatively affect the performance of bytes compared to
# a ''.join() equivalent
self.incoming = []
# we toss the use of the "simple producer" and replace it with
# a pure deque, which the original fifo was a wrapping of
self.producer_fifo = deque()
asyncore.dispatcher.__init__ (self, conn) asyncore.dispatcher.__init__ (self, conn)
def collect_incoming_data(self, data): def collect_incoming_data(self, data):
raise NotImplementedError("must be implemented in subclass") raise NotImplementedError("must be implemented in subclass")
def _collect_incoming_data(self, data):
self.incoming.append(data)
def _get_data(self):
d = b''.join(self.incoming)
del self.incoming[:]
return d
def found_terminator(self): def found_terminator(self):
raise NotImplementedError("must be implemented in subclass") raise NotImplementedError("must be implemented in subclass")
def set_terminator (self, term): def set_terminator (self, term):
"Set the input delimiter. Can be a fixed string of any length, an integer, or None" "Set the input delimiter. Can be a fixed string of any length, an integer, or None"
if isinstance(term, str) and self.use_encoding:
term = bytes(term, self.encoding)
self.terminator = term self.terminator = term
def get_terminator (self): def get_terminator (self):
...@@ -92,14 +130,14 @@ class async_chat (asyncore.dispatcher): ...@@ -92,14 +130,14 @@ class async_chat (asyncore.dispatcher):
self.handle_error() self.handle_error()
return return
if isinstance(data, str): if isinstance(data, str) and self.use_encoding:
data = data.encode('ascii') data = bytes(str, self.encoding)
self.ac_in_buffer = self.ac_in_buffer + bytes(data) self.ac_in_buffer = self.ac_in_buffer + data
# Continue to search for self.terminator in self.ac_in_buffer, # Continue to search for self.terminator in self.ac_in_buffer,
# while calling self.collect_incoming_data. The while loop # while calling self.collect_incoming_data. The while loop
# is necessary because we might read several data+terminator # is necessary because we might read several data+terminator
# combos with a single recv(1024). # combos with a single recv(4096).
while self.ac_in_buffer: while self.ac_in_buffer:
lb = len(self.ac_in_buffer) lb = len(self.ac_in_buffer)
...@@ -108,7 +146,7 @@ class async_chat (asyncore.dispatcher): ...@@ -108,7 +146,7 @@ class async_chat (asyncore.dispatcher):
# no terminator, collect it all # no terminator, collect it all
self.collect_incoming_data (self.ac_in_buffer) self.collect_incoming_data (self.ac_in_buffer)
self.ac_in_buffer = b'' self.ac_in_buffer = b''
elif isinstance(terminator, int) or isinstance(terminator, int): elif isinstance(terminator, int):
# numeric terminator # numeric terminator
n = terminator n = terminator
if lb < n: if lb < n:
...@@ -129,8 +167,6 @@ class async_chat (asyncore.dispatcher): ...@@ -129,8 +167,6 @@ class async_chat (asyncore.dispatcher):
# 3) end of buffer does not match any prefix: # 3) end of buffer does not match any prefix:
# collect data # collect data
terminator_len = len(terminator) terminator_len = len(terminator)
if isinstance(terminator, str):
terminator = terminator.encode('ascii')
index = self.ac_in_buffer.find(terminator) index = self.ac_in_buffer.find(terminator)
if index != -1: if index != -1:
# we found the terminator # we found the terminator
...@@ -155,91 +191,87 @@ class async_chat (asyncore.dispatcher): ...@@ -155,91 +191,87 @@ class async_chat (asyncore.dispatcher):
self.ac_in_buffer = b'' self.ac_in_buffer = b''
def handle_write (self): def handle_write (self):
self.initiate_send () self.initiate_send()
def handle_close (self): def handle_close (self):
self.close() self.close()
def push (self, data): def push (self, data):
self.producer_fifo.push (simple_producer (data)) sabs = self.ac_out_buffer_size
if len(data) > sabs:
for i in range(0, len(data), sabs):
self.producer_fifo.append(data[i:i+sabs])
else:
self.producer_fifo.append(data)
self.initiate_send() self.initiate_send()
def push_with_producer (self, producer): def push_with_producer (self, producer):
self.producer_fifo.push (producer) self.producer_fifo.append(producer)
self.initiate_send() self.initiate_send()
def readable (self): def readable (self):
"predicate for inclusion in the readable for select()" "predicate for inclusion in the readable for select()"
return (len(self.ac_in_buffer) <= self.ac_in_buffer_size) # cannot use the old predicate, it violates the claim of the
# set_terminator method.
# return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
return 1
def writable (self): def writable (self):
"predicate for inclusion in the writable for select()" "predicate for inclusion in the writable for select()"
# return len(self.ac_out_buffer) or len(self.producer_fifo) or (not self.connected) return self.producer_fifo or (not self.connected)
# this is about twice as fast, though not as clear.
return not (
(self.ac_out_buffer == b'') and
self.producer_fifo.is_empty() and
self.connected
)
def close_when_done (self): def close_when_done (self):
"automatically close this channel once the outgoing queue is empty" "automatically close this channel once the outgoing queue is empty"
self.producer_fifo.push (None) self.producer_fifo.append(None)
# refill the outgoing buffer by calling the more() method def initiate_send(self):
# of the first producer in the queue while self.producer_fifo and self.connected:
def refill_buffer (self): first = self.producer_fifo[0]
while 1: # handle empty string/buffer or None entry
if len(self.producer_fifo): if not first:
p = self.producer_fifo.first() del self.producer_fifo[0]
# a 'None' in the producer fifo is a sentinel, if first is None:
# telling us to close the channel. ## print("first is None")
if p is None: self.handle_close()
if not self.ac_out_buffer:
self.producer_fifo.pop()
self.close()
return return
elif isinstance(p, str) or isinstance(p, bytes): ## print("first is not None")
if isinstance(p, str):
p = p.encode('ascii') # handle classic producer behavior
self.producer_fifo.pop() obs = self.ac_out_buffer_size
self.ac_out_buffer = self.ac_out_buffer + p try:
return data = buffer(first, 0, obs)
data = p.more() except TypeError:
data = first.more()
if data: if data:
if isinstance(data, str): self.producer_fifo.appendleft(data)
data = data.encode('ascii')
self.ac_out_buffer = self.ac_out_buffer + bytes(data)
return
else: else:
self.producer_fifo.pop() del self.producer_fifo[0]
else: continue
return
def initiate_send (self): if isinstance(data, str) and self.use_encoding:
obs = self.ac_out_buffer_size data = bytes(data, self.encoding)
# try to refill the buffer
if (len (self.ac_out_buffer) < obs):
self.refill_buffer()
if self.ac_out_buffer and self.connected: # send the data
# try to send the buffer
try: try:
num_sent = self.send (self.ac_out_buffer[:obs]) num_sent = self.send(data)
if num_sent: except socket.error:
self.ac_out_buffer = self.ac_out_buffer[num_sent:]
except socket.error as why:
self.handle_error() self.handle_error()
return return
if num_sent:
if num_sent < len(data) or obs < len(first):
self.producer_fifo[0] = first[num_sent:]
else:
del self.producer_fifo[0]
# we tried to send some actual data
return
def discard_buffers (self): def discard_buffers (self):
# Emergencies only! # Emergencies only!
self.ac_in_buffer = b'' self.ac_in_buffer = b''
self.ac_out_buffer = b'' del self.incoming[:]
while self.producer_fifo: self.producer_fifo.clear()
self.producer_fifo.pop()
class simple_producer: class simple_producer:
......
...@@ -50,23 +50,28 @@ import select ...@@ -50,23 +50,28 @@ import select
import socket import socket
import sys import sys
import time import time
import os import os
from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, \ from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, \
ENOTCONN, ESHUTDOWN, EINTR, EISCONN, errorcode ENOTCONN, ESHUTDOWN, EINTR, EISCONN, EBADF, ECONNABORTED, errorcode
try: try:
socket_map socket_map
except NameError: except NameError:
socket_map = {} socket_map = {}
def _strerror(err):
res = os.strerror(err)
if res == 'Unknown error':
res = errorcode[err]
return res
class ExitNow(Exception): class ExitNow(Exception):
pass pass
def read(obj): def read(obj):
try: try:
obj.handle_read_event() obj.handle_read_event()
except ExitNow: except (ExitNow, KeyboardInterrupt, SystemExit):
raise raise
except: except:
obj.handle_error() obj.handle_error()
...@@ -74,15 +79,15 @@ def read(obj): ...@@ -74,15 +79,15 @@ def read(obj):
def write(obj): def write(obj):
try: try:
obj.handle_write_event() obj.handle_write_event()
except ExitNow: except (ExitNow, KeyboardInterrupt, SystemExit):
raise raise
except: except:
obj.handle_error() obj.handle_error()
def _exception (obj): def _exception(obj):
try: try:
obj.handle_expt_event() obj.handle_expt_event()
except ExitNow: except (ExitNow, KeyboardInterrupt, SystemExit):
raise raise
except: except:
obj.handle_error() obj.handle_error()
...@@ -95,7 +100,7 @@ def readwrite(obj, flags): ...@@ -95,7 +100,7 @@ def readwrite(obj, flags):
obj.handle_write_event() obj.handle_write_event()
if flags & (select.POLLERR | select.POLLHUP | select.POLLNVAL): if flags & (select.POLLERR | select.POLLHUP | select.POLLNVAL):
obj.handle_expt_event() obj.handle_expt_event()
except ExitNow: except (ExitNow, KeyboardInterrupt, SystemExit):
raise raise
except: except:
obj.handle_error() obj.handle_error()
...@@ -105,7 +110,7 @@ def poll(timeout=0.0, map=None): ...@@ -105,7 +110,7 @@ def poll(timeout=0.0, map=None):
map = socket_map map = socket_map
if map: if map:
r = []; w = []; e = [] r = []; w = []; e = []
for fd, obj in map.items(): for fd, obj in list(map.items()):
is_r = obj.readable() is_r = obj.readable()
is_w = obj.writable() is_w = obj.writable()
if is_r: if is_r:
...@@ -116,11 +121,12 @@ def poll(timeout=0.0, map=None): ...@@ -116,11 +121,12 @@ def poll(timeout=0.0, map=None):
e.append(fd) e.append(fd)
if [] == r == w == e: if [] == r == w == e:
time.sleep(timeout) time.sleep(timeout)
else: return
try: try:
r, w, e = select.select(r, w, e, timeout) r, w, e = select.select(r, w, e, timeout)
except select.error as err: except select.error as err:
if err.args[0] != EINTR: if err[0] != EINTR:
raise raise
else: else:
return return
...@@ -152,7 +158,7 @@ def poll2(timeout=0.0, map=None): ...@@ -152,7 +158,7 @@ def poll2(timeout=0.0, map=None):
timeout = int(timeout*1000) timeout = int(timeout*1000)
pollster = select.poll() pollster = select.poll()
if map: if map:
for fd, obj in map.items(): for fd, obj in list(map.items()):
flags = 0 flags = 0
if obj.readable(): if obj.readable():
flags |= select.POLLIN | select.POLLPRI flags |= select.POLLIN | select.POLLPRI
...@@ -166,7 +172,7 @@ def poll2(timeout=0.0, map=None): ...@@ -166,7 +172,7 @@ def poll2(timeout=0.0, map=None):
try: try:
r = pollster.poll(timeout) r = pollster.poll(timeout)
except select.error as err: except select.error as err:
if err.args[0] != EINTR: if err[0] != EINTR:
raise raise
r = [] r = []
for fd, flags in r: for fd, flags in r:
...@@ -209,18 +215,29 @@ class dispatcher: ...@@ -209,18 +215,29 @@ class dispatcher:
else: else:
self._map = map self._map = map
self._fileno = None
if sock: if sock:
# Set to nonblocking just to make sure for cases where we
# get a socket from a blocking source.
sock.setblocking(0)
self.set_socket(sock, map) self.set_socket(sock, map)
# I think it should inherit this anyway
self.socket.setblocking(0)
self.connected = True self.connected = True
# XXX Does the constructor require that the socket passed # The constructor no longer requires that the socket
# be connected? # passed be connected.
try: try:
self.addr = sock.getpeername() self.addr = sock.getpeername()
except socket.error: except socket.error as err:
# The addr isn't crucial if err[0] == ENOTCONN:
pass # To handle the case where we got an unconnected
# socket.
self.connected = False
else:
# The socket is broken in some unknown way, alert
# the user and remove it from the map (to prevent
# polling of broken sockets).
self.del_channel(map)
raise
else: else:
self.socket = None self.socket = None
...@@ -254,10 +271,9 @@ class dispatcher: ...@@ -254,10 +271,9 @@ class dispatcher:
def create_socket(self, family, type): def create_socket(self, family, type):
self.family_and_type = family, type self.family_and_type = family, type
self.socket = socket.socket(family, type) sock = socket.socket(family, type)
self.socket.setblocking(0) sock.setblocking(0)
self._fileno = self.socket.fileno() self.set_socket(sock)
self.add_channel()
def set_socket(self, sock, map=None): def set_socket(self, sock, map=None):
self.socket = sock self.socket = sock
...@@ -295,7 +311,7 @@ class dispatcher: ...@@ -295,7 +311,7 @@ class dispatcher:
def listen(self, num): def listen(self, num):
self.accepting = True self.accepting = True
if os.name == 'nt' and num > 5: if os.name == 'nt' and num > 5:
num = 1 num = 5
return self.socket.listen(num) return self.socket.listen(num)
def bind(self, addr): def bind(self, addr):
...@@ -310,8 +326,7 @@ class dispatcher: ...@@ -310,8 +326,7 @@ class dispatcher:
return return
if err in (0, EISCONN): if err in (0, EISCONN):
self.addr = address self.addr = address
self.connected = True self.handle_connect_event()
self.handle_connect()
else: else:
raise socket.error(err, errorcode[err]) raise socket.error(err, errorcode[err])
...@@ -321,7 +336,7 @@ class dispatcher: ...@@ -321,7 +336,7 @@ class dispatcher:
conn, addr = self.socket.accept() conn, addr = self.socket.accept()
return conn, addr return conn, addr
except socket.error as why: except socket.error as why:
if why.args[0] == EWOULDBLOCK: if why[0] == EWOULDBLOCK:
pass pass
else: else:
raise raise
...@@ -331,11 +346,13 @@ class dispatcher: ...@@ -331,11 +346,13 @@ class dispatcher:
result = self.socket.send(data) result = self.socket.send(data)
return result return result
except socket.error as why: except socket.error as why:
if why.args[0] == EWOULDBLOCK: if why[0] == EWOULDBLOCK:
return 0
elif why[0] in (ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED):
self.handle_close()
return 0 return 0
else: else:
raise raise
return 0
def recv(self, buffer_size): def recv(self, buffer_size):
try: try:
...@@ -349,15 +366,21 @@ class dispatcher: ...@@ -349,15 +366,21 @@ class dispatcher:
return data return data
except socket.error as why: except socket.error as why:
# winsock sometimes throws ENOTCONN # winsock sometimes throws ENOTCONN
if why.args[0] in [ECONNRESET, ENOTCONN, ESHUTDOWN]: if why[0] in [ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED]:
self.handle_close() self.handle_close()
return b'' return b''
else: else:
raise raise
def close(self): def close(self):
self.connected = False
self.accepting = False
self.del_channel() self.del_channel()
try:
self.socket.close() self.socket.close()
except socket.error as why:
if why[0] not in (ENOTCONN, EBADF):
raise
# cheap inheritance, used to pass all other attribute # cheap inheritance, used to pass all other attribute
# references to the underlying socket object. # references to the underlying socket object.
...@@ -377,26 +400,52 @@ class dispatcher: ...@@ -377,26 +400,52 @@ class dispatcher:
def handle_read_event(self): def handle_read_event(self):
if self.accepting: if self.accepting:
# for an accepting socket, getting a read implies # accepting sockets are never connected, they "spawn" new
# that we are connected # sockets that are connected
if not self.connected:
self.connected = True
self.handle_accept() self.handle_accept()
elif not self.connected: elif not self.connected:
self.handle_connect() self.handle_connect_event()
self.connected = True
self.handle_read() self.handle_read()
else: else:
self.handle_read() self.handle_read()
def handle_connect_event(self):
self.connected = True
self.handle_connect()
def handle_write_event(self): def handle_write_event(self):
# getting a write implies that we are connected if self.accepting:
# Accepting sockets shouldn't get a write event.
# We will pretend it didn't happen.
return
if not self.connected: if not self.connected:
self.handle_connect() #check for errors
self.connected = True err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
if err != 0:
raise socket.error(err, _strerror(err))
self.handle_connect_event()
self.handle_write() self.handle_write()
def handle_expt_event(self): def handle_expt_event(self):
# if the handle_expt is the same default worthless method,
# we'll not even bother calling it, we'll instead generate
# a useful error
x = True
try:
y1 = self.handle_expt.__func__
y2 = dispatcher.handle_expt
x = y1 is y2
except AttributeError:
pass
if x:
err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
msg = _strerror(err)
raise socket.error(err, msg)
else:
self.handle_expt() self.handle_expt()
def handle_error(self): def handle_error(self):
...@@ -461,7 +510,6 @@ class dispatcher_with_send(dispatcher): ...@@ -461,7 +510,6 @@ class dispatcher_with_send(dispatcher):
return (not self.connected) or len(self.out_buffer) return (not self.connected) or len(self.out_buffer)
def send(self, data): def send(self, data):
assert isinstance(data, bytes)
if self.debug: if self.debug:
self.log_info('sending %s' % repr(data)) self.log_info('sending %s' % repr(data))
self.out_buffer = self.out_buffer + data self.out_buffer = self.out_buffer + data
...@@ -474,7 +522,8 @@ class dispatcher_with_send(dispatcher): ...@@ -474,7 +522,8 @@ class dispatcher_with_send(dispatcher):
def compact_traceback(): def compact_traceback():
t, v, tb = sys.exc_info() t, v, tb = sys.exc_info()
tbinfo = [] tbinfo = []
assert tb # Must have a traceback if not tb: # Must have a traceback
raise AssertionError("traceback does not exist")
while tb: while tb:
tbinfo.append(( tbinfo.append((
tb.tb_frame.f_code.co_filename, tb.tb_frame.f_code.co_filename,
...@@ -490,11 +539,22 @@ def compact_traceback(): ...@@ -490,11 +539,22 @@ def compact_traceback():
info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo]) info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo])
return (file, function, line), t, v, info return (file, function, line), t, v, info
def close_all(map=None): def close_all(map=None, ignore_all=False):
if map is None: if map is None:
map = socket_map map = socket_map
for x in map.values(): for x in list(map.values()):
x.socket.close() try:
x.close()
except OSError as x:
if x[0] == EBADF:
pass
elif not ignore_all:
raise
except (ExitNow, KeyboardInterrupt, SystemExit):
raise
except:
if not ignore_all:
raise
map.clear() map.clear()
# Asynchronous File I/O: # Asynchronous File I/O:
...@@ -514,11 +574,12 @@ if os.name == 'posix': ...@@ -514,11 +574,12 @@ if os.name == 'posix':
import fcntl import fcntl
class file_wrapper: class file_wrapper:
# here we override just enough to make a file # Here we override just enough to make a file
# look like a socket for the purposes of asyncore. # look like a socket for the purposes of asyncore.
# The passed fd is automatically os.dup()'d
def __init__(self, fd): def __init__(self, fd):
self.fd = fd self.fd = os.dup(fd)
def recv(self, *args): def recv(self, *args):
return os.read(self.fd, *args) return os.read(self.fd, *args)
...@@ -540,6 +601,10 @@ if os.name == 'posix': ...@@ -540,6 +601,10 @@ if os.name == 'posix':
def __init__(self, fd, map=None): def __init__(self, fd, map=None):
dispatcher.__init__(self, None, map) dispatcher.__init__(self, None, map)
self.connected = True self.connected = True
try:
fd = fd.fileno()
except AttributeError:
pass
self.set_file(fd) self.set_file(fd)
# set it to non-blocking mode # set it to non-blocking mode
flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0) flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0)
......
...@@ -124,11 +124,11 @@ class SMTPChannel(asynchat.async_chat): ...@@ -124,11 +124,11 @@ class SMTPChannel(asynchat.async_chat):
self.__peer = conn.getpeername() self.__peer = conn.getpeername()
print('Peer:', repr(self.__peer), file=DEBUGSTREAM) print('Peer:', repr(self.__peer), file=DEBUGSTREAM)
self.push('220 %s %s' % (self.__fqdn, __version__)) self.push('220 %s %s' % (self.__fqdn, __version__))
self.set_terminator('\r\n') self.set_terminator(b'\r\n')
# Overrides base class for convenience # Overrides base class for convenience
def push(self, msg): def push(self, msg):
asynchat.async_chat.push(self, msg + '\r\n') asynchat.async_chat.push(self, bytes(msg + '\r\n', 'ascii'))
# Implementation of base class abstract method # Implementation of base class abstract method
def collect_incoming_data(self, data): def collect_incoming_data(self, data):
...@@ -177,7 +177,7 @@ class SMTPChannel(asynchat.async_chat): ...@@ -177,7 +177,7 @@ class SMTPChannel(asynchat.async_chat):
self.__rcpttos = [] self.__rcpttos = []
self.__mailfrom = None self.__mailfrom = None
self.__state = self.COMMAND self.__state = self.COMMAND
self.set_terminator('\r\n') self.set_terminator(b'\r\n')
if not status: if not status:
self.push('250 Ok') self.push('250 Ok')
else: else:
...@@ -264,7 +264,7 @@ class SMTPChannel(asynchat.async_chat): ...@@ -264,7 +264,7 @@ class SMTPChannel(asynchat.async_chat):
self.push('501 Syntax: DATA') self.push('501 Syntax: DATA')
return return
self.__state = self.DATA self.__state = self.DATA
self.set_terminator('\r\n.\r\n') self.set_terminator(b'\r\n.\r\n')
self.push('354 End data with <CR><LF>.<CR><LF>') self.push('354 End data with <CR><LF>.<CR><LF>')
......
...@@ -105,8 +105,8 @@ class TestAsynchat(unittest.TestCase): ...@@ -105,8 +105,8 @@ class TestAsynchat(unittest.TestCase):
time.sleep(0.01) # Give server time to start accepting. time.sleep(0.01) # Give server time to start accepting.
c = echo_client(term, s.port) c = echo_client(term, s.port)
c.push(b"hello ") c.push(b"hello ")
c.push(bytes("world%s" % term, "ascii")) c.push(b"world" + term)
c.push(bytes("I'm not dead yet!%s" % term, "ascii")) c.push(b"I'm not dead yet!" + term)
c.push(SERVER_QUIT) c.push(SERVER_QUIT)
asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01) asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
s.join() s.join()
...@@ -120,17 +120,17 @@ class TestAsynchat(unittest.TestCase): ...@@ -120,17 +120,17 @@ class TestAsynchat(unittest.TestCase):
def test_line_terminator1(self): def test_line_terminator1(self):
# test one-character terminator # test one-character terminator
for l in (1,2,3): for l in (1,2,3):
self.line_terminator_check('\n', l) self.line_terminator_check(b'\n', l)
def test_line_terminator2(self): def test_line_terminator2(self):
# test two-character terminator # test two-character terminator
for l in (1,2,3): for l in (1,2,3):
self.line_terminator_check('\r\n', l) self.line_terminator_check(b'\r\n', l)
def test_line_terminator3(self): def test_line_terminator3(self):
# test three-character terminator # test three-character terminator
for l in (1,2,3): for l in (1,2,3):
self.line_terminator_check('qqq', l) self.line_terminator_check(b'qqq', l)
def numeric_terminator_check(self, termlen): def numeric_terminator_check(self, termlen):
# Try reading a fixed number of bytes # Try reading a fixed number of bytes
...@@ -190,7 +190,7 @@ class TestAsynchat(unittest.TestCase): ...@@ -190,7 +190,7 @@ class TestAsynchat(unittest.TestCase):
# checks that empty lines are handled correctly # checks that empty lines are handled correctly
s, event = start_echo_server() s, event = start_echo_server()
c = echo_client(b'\n', s.port) c = echo_client(b'\n', s.port)
c.push("hello world\n\nI'm not dead yet!\n") c.push(b"hello world\n\nI'm not dead yet!\n")
c.push(SERVER_QUIT) c.push(SERVER_QUIT)
asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01) asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
s.join() s.join()
...@@ -201,7 +201,7 @@ class TestAsynchat(unittest.TestCase): ...@@ -201,7 +201,7 @@ class TestAsynchat(unittest.TestCase):
def test_close_when_done(self): def test_close_when_done(self):
s, event = start_echo_server() s, event = start_echo_server()
c = echo_client(b'\n', s.port) c = echo_client(b'\n', s.port)
c.push("hello world\nI'm not dead yet!\n") c.push(b"hello world\nI'm not dead yet!\n")
c.push(SERVER_QUIT) c.push(SERVER_QUIT)
c.close_when_done() c.close_when_done()
asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01) asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
......
...@@ -28,6 +28,9 @@ class dummychannel: ...@@ -28,6 +28,9 @@ class dummychannel:
def __init__(self): def __init__(self):
self.socket = dummysocket() self.socket = dummysocket()
def close(self):
self.socket.close()
class exitingdummy: class exitingdummy:
def __init__(self): def __init__(self):
pass pass
...@@ -382,8 +385,8 @@ if hasattr(asyncore, 'file_wrapper'): ...@@ -382,8 +385,8 @@ if hasattr(asyncore, 'file_wrapper'):
fd = os.open(TESTFN, os.O_RDONLY) fd = os.open(TESTFN, os.O_RDONLY)
w = asyncore.file_wrapper(fd) w = asyncore.file_wrapper(fd)
self.assertEqual(w.fd, fd) self.assertNotEqual(w.fd, fd)
self.assertEqual(w.fileno(), fd) self.assertNotEqual(w.fileno(), fd)
self.assertEqual(w.recv(13), b"It's not dead") self.assertEqual(w.recv(13), b"It's not dead")
self.assertEqual(w.read(6), b", it's") self.assertEqual(w.read(6), b", it's")
w.close() w.close()
......
...@@ -14,6 +14,14 @@ from test import support ...@@ -14,6 +14,14 @@ from test import support
HOST = support.HOST HOST = support.HOST
if sys.platform == 'darwin':
# select.poll returns a select.POLLHUP at the end of the tests
# on darwin, so just ignore it
def handle_expt(self):
pass
smtpd.SMTPChannel.handle_expt = handle_expt
def server(evt, buf, serv): def server(evt, buf, serv):
serv.listen(5) serv.listen(5)
evt.set() evt.set()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment