summaryrefslogtreecommitdiff
path: root/src/mailman/testing
diff options
context:
space:
mode:
Diffstat (limited to 'src/mailman/testing')
-rw-r--r--src/mailman/testing/helpers.py10
-rw-r--r--src/mailman/testing/mta.py237
-rw-r--r--src/mailman/testing/nose.py2
-rw-r--r--src/mailman/testing/testing.cfg2
4 files changed, 114 insertions, 137 deletions
diff --git a/src/mailman/testing/helpers.py b/src/mailman/testing/helpers.py
index a57d883c7..2ce6956b7 100644
--- a/src/mailman/testing/helpers.py
+++ b/src/mailman/testing/helpers.py
@@ -18,6 +18,7 @@
"""Various test helpers."""
import os
+import sys
import json
import time
import uuid
@@ -118,8 +119,11 @@ def get_queue_messages(queue_name, sort_on=None, expected_count=None):
messages.append(_Bag(msg=msg, msgdata=msgdata))
queue.finish(filebase)
if expected_count is not None:
- assert len(messages) == expected_count, 'Wanted {}, got {}'.format(
- expected_count, len(messages))
+ if len(messages) != expected_count:
+ for item in messages:
+ print(item.msg, file=sys.stderr)
+ raise AssertionError('Wanted {}, got {}'.format(
+ expected_count, len(messages)))
if sort_on is not None:
messages.sort(key=lambda item: str(item.msg[sort_on]))
return messages
@@ -244,7 +248,7 @@ def get_nntp_server(cleanups):
cleanups.append(patcher.stop)
nntpd = server_class()
# A class for more convenient access to the posted message.
- class NNTPProxy: # noqa
+ class NNTPProxy: # noqa: E306
def get_message(self):
args = nntpd.post.call_args
return specialized_message_from_string(args[0][0].read())
diff --git a/src/mailman/testing/mta.py b/src/mailman/testing/mta.py
index c1327f428..81e4b8a62 100644
--- a/src/mailman/testing/mta.py
+++ b/src/mailman/testing/mta.py
@@ -17,19 +17,19 @@
"""Fake MTA for testing purposes."""
-import logging
+import socket
+import asyncio
+import smtplib
-from lazr.smtptest.controller import QueueController
-from lazr.smtptest.server import Channel, QueueServer
+from aiosmtpd.controller import Controller
+from aiosmtpd.handlers import Message as MessageHandler
+from aiosmtpd.smtp import SMTP
from mailman import public
from mailman.interfaces.mta import IMailTransportAgentLifecycle
from queue import Empty, Queue
from zope.interface import implementer
-log = logging.getLogger('lazr.smtptest')
-
-
@public
@implementer(IMailTransportAgentLifecycle)
class FakeMTA:
@@ -45,113 +45,73 @@ class FakeMTA:
pass
-class StatisticsChannel(Channel):
- """A channel that can answers to the fake STAT command."""
+class ConnectionCountingHandler(MessageHandler):
+ def __init__(self, msg_queue):
+ super().__init__()
+ self._msg_queue = msg_queue
+ self.connection_count = 0
- def __init__(self, server, connection, address):
- super().__init__(server, connection, address)
- self._auth_response = None
- self._waiting_for_auth_response = False
+ def handle_message(self, message):
+ self._msg_queue.put(message)
- def smtp_EHLO(self, arg):
- if not arg:
- self.push('501 Syntax: HELO hostname')
- return
- if self._SMTPChannel__greeting:
- self.push('503 Duplicate HELO/EHLO')
- else:
- self._SMTPChannel__greeting = arg
- self.push('250-%s' % self._SMTPChannel__fqdn)
- self.push('250 AUTH PLAIN')
- def smtp_STAT(self, arg):
- """Cause the server to send statistics to its controller."""
- self._server.send_statistics()
- self.push('250 Ok')
+class ConnectionCountingSMTP(SMTP):
+ def __init__(self, handler, oob_queue, err_queue, *args, **kws):
+ super().__init__(handler, *args, **kws)
+ self._auth_response = None
+ self._waiting_for_auth_response = False
+ self._oob_queue = oob_queue
+ self._err_queue = err_queue
+ self._last_error = None
- def _check_auth(self, response):
- # Base 64 for "testuser:testpass"
- if response == 'AHRlc3R1c2VyAHRlc3RwYXNz':
- self.push('235 Ok')
- self._server.send_auth(response)
- else:
- self.push('571 Bad authentication')
+ def connection_made(self, transport):
+ super().connection_made(transport)
+ # We can't keep the connection count on self here because the
+ # controller (via the factory() method) will create a new instance of
+ # this class for every connection. The handler instance is always the
+ # same though, so it's fine to stash this value away there.
+ self.event_handler.connection_count += 1
+ @asyncio.coroutine
def smtp_AUTH(self, arg):
"""Record that the AUTH occurred."""
args = arg.split()
if args[0].lower() == 'plain':
if len(args) == 2:
+ response = args[1]
# The second argument is the AUTH PLAIN <initial-response>
# which must be equal to the base 64 equivalent of the
# expected login string "testuser:testpass".
- self._check_auth(args[1])
+ if response == 'AHRlc3R1c2VyAHRlc3RwYXNz':
+ yield from self.push('235 Ok')
+ self._oob_queue.put(response)
+ else:
+ yield from self.push('571 Bad authentication')
else:
assert len(args) == 1, args
# Send a challenge and set us up to wait for the response.
- self.push('334 ')
+ yield from self.push('334 ')
self._waiting_for_auth_response = True
else:
- self.push('571 Bad authentication')
-
- def smtp_RCPT(self, arg):
- """For testing, sometimes cause a non-25x response."""
- code = self._server.next_error('rcpt')
- if code is None:
- # Everything's cool.
- Channel.smtp_RCPT(self, arg)
- else:
- # The test suite wants this to fail. The message corresponds to
- # the exception we expect smtplib.SMTP to raise.
- self.push('%d Error: SMTPRecipientsRefused' % code)
-
- def smtp_MAIL(self, arg):
- """For testing, sometimes cause a non-25x response."""
- code = self._server.next_error('mail')
- if code is None:
- # Everything's cool.
- Channel.smtp_MAIL(self, arg)
- else:
- # The test suite wants this to fail. The message corresponds to
- # the exception we expect smtplib.SMTP to raise.
- self.push('%d Error: SMTPResponseException' % code)
-
- def found_terminator(self):
- # Are we're waiting for the AUTH challenge response?
- if self._waiting_for_auth_response:
- line = self._emptystring.join(self.received_lines)
- self._auth_response = line
- self._waiting_for_auth_response = False
- self.received_lines = []
- # Now check to see if they authenticated correctly.
- self._check_auth(line)
- else:
- super().found_terminator()
-
+ yield from self.push('571 Bad authentication')
-class ConnectionCountingServer(QueueServer):
- """Count the number of SMTP connections opened."""
+ @asyncio.coroutine
+ def ehlo_hook(self):
+ yield from self.push('250-AUTH PLAIN')
- def __init__(self, host, port, queue, oob_queue, err_queue):
- """See `lazr.smtptest.server.QueueServer`.
+ @asyncio.coroutine
+ def rset_hook(self):
+ self.event_handler.connection_count = 0
- :param oob_queue: A queue for communicating information back to the
- controller, e.g. statistics.
- :type oob_queue: `Queue.Queue`
- :param err_queue: A queue for allowing the controller to request SMTP
- errors from the server.
- :type err_queue: `Queue.Queue`
- """
- QueueServer.__init__(self, host, port, queue)
- self._connection_count = 0
- self.last_auth = None
- # The out-of-band queue is where the server sends statistics to the
- # controller upon request.
- self._oob_queue = oob_queue
- self._err_queue = err_queue
- self._last_error = None
+ @asyncio.coroutine
+ def smtp_STAT(self, arg):
+ """Cause the server to send statistics to its controller."""
+ # Do not count the connection caused by the STAT connect.
+ self.event_handler.connection_count -= 1
+ self._oob_queue.put(self.event_handler.connection_count)
+ yield from self.push('250 Ok')
- def next_error(self, command):
+ def _next_error(self, command):
"""Return the next error for the SMTP command, if there is one.
:param command: The SMTP command for which an error might be
@@ -176,70 +136,83 @@ class ConnectionCountingServer(QueueServer):
return code
return None
- def handle_accept(self):
- """See `lazr.smtp.server.Server`."""
- connection, address = self.accept()
- self._connection_count += 1
- log.info('[ConnectionCountingServer] accepted: %s', address)
- StatisticsChannel(self, connection, address)
-
- def process_message(self, peer, mailfrom, rcpttos, data):
- # Provide a guaranteed order to recpttos.
- QueueServer.process_message(
- self, peer, mailfrom, sorted(rcpttos), data)
-
- def reset(self):
- """See `lazr.smtp.server.Server`."""
- QueueServer.reset(self)
- self._connection_count = 0
-
- def send_statistics(self):
- """Send the current connection statistics to the controller."""
- # Do not count the connection caused by the STAT connect.
- self._connection_count -= 1
- self._oob_queue.put(self._connection_count)
+ @asyncio.coroutine
+ def smtp_RCPT(self, arg):
+ """For testing, sometimes cause a non-25x response."""
+ code = self._next_error('rcpt')
+ if code is None:
+ # Everything's cool.
+ yield from super().smtp_RCPT(arg)
+ else:
+ # The test suite wants this to fail. The message corresponds to
+ # the exception we expect smtplib.SMTP to raise.
+ yield from self.push('%d Error: SMTPRecipientsRefused' % code)
- def send_auth(self, arg):
- """Echo back the authentication data."""
- self._oob_queue.put(arg)
+ @asyncio.coroutine
+ def smtp_MAIL(self, arg):
+ """For testing, sometimes cause a non-25x response."""
+ code = self._next_error('mail')
+ if code is None:
+ # Everything's cool.
+ yield from super().smtp_MAIL(arg)
+ else:
+ # The test suite wants this to fail. The message corresponds to
+ # the exception we expect smtplib.SMTP to raise.
+ yield from self.push('%d Error: SMTPResponseException' % code)
-class ConnectionCountingController(QueueController):
+class ConnectionCountingController(Controller):
"""Count the number of SMTP connections opened."""
def __init__(self, host, port):
- """See `lazr.smtptest.controller.QueueController`."""
- self.oob_queue = Queue()
+ self._msg_queue = Queue()
+ self._oob_queue = Queue()
self.err_queue = Queue()
- QueueController.__init__(self, host, port)
+ handler = ConnectionCountingHandler(self._msg_queue)
+ super().__init__(handler, hostname=host, port=port)
+
+ def factory(self):
+ return ConnectionCountingSMTP(
+ self.handler, self._oob_queue, self.err_queue)
- def _make_server(self, host, port):
- """See `lazr.smtptest.controller.QueueController`."""
- self.server = ConnectionCountingServer(
- host, port, self.queue, self.oob_queue, self.err_queue)
+ def make_socket(self):
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
+ return sock
def start(self):
- """See `lazr.smtptest.controller.QueueController`."""
- QueueController.start(self)
+ super().start()
# Reset the connection statistics, since the base class's start()
# method causes a connection to occur.
self.reset()
+ def _connect(self):
+ client = smtplib.SMTP()
+ client.connect(self.hostname, self.port)
+ return client
+
def get_connection_count(self):
"""Retrieve the number of connections.
:return: The number of connections to the server that have been made.
:rtype: integer
"""
- smtpd = self._connect()
- smtpd.docmd('STAT')
+ client = self._connect()
+ client.docmd('STAT')
# An Empty exception will occur if the data isn't available in 10
# seconds. Let that propagate.
- return self.oob_queue.get(block=True, timeout=10)
+ return self._oob_queue.get(block=True, timeout=10)
def get_authentication_credentials(self):
"""Retrieve the last authentication credentials."""
- return self.oob_queue.get(block=True, timeout=10)
+ return self._oob_queue.get(block=True, timeout=10)
+
+ def __iter__(self):
+ while True:
+ try:
+ yield self._msg_queue.get_nowait()
+ except Empty:
+ raise StopIteration
@property
def messages(self):
@@ -251,5 +224,5 @@ class ConnectionCountingController(QueueController):
list(self)
def reset(self):
- smtpd = self._connect()
- smtpd.docmd('RSET')
+ client = self._connect()
+ client.docmd('RSET')
diff --git a/src/mailman/testing/nose.py b/src/mailman/testing/nose.py
index f88ac409a..bbd361390 100644
--- a/src/mailman/testing/nose.py
+++ b/src/mailman/testing/nose.py
@@ -42,7 +42,7 @@ class NosePlugin(Plugin):
super().__init__()
self.patterns = []
self.stderr = False
- def set_stderr(ignore): # noqa
+ def set_stderr(ignore): # noqa: E306
self.stderr = True
self.addArgument(self.patterns, 'P', 'pattern',
'Add a test matching pattern')
diff --git a/src/mailman/testing/testing.cfg b/src/mailman/testing/testing.cfg
index 022f9f289..8b6a48c36 100644
--- a/src/mailman/testing/testing.cfg
+++ b/src/mailman/testing/testing.cfg
@@ -65,7 +65,7 @@ max_restarts: 1
max_restarts: 1
[archiver.prototype]
-enable: yes
+enable: no
[archiver.mail_archive]
enable: yes