diff options
Diffstat (limited to 'src/mailman/testing/mta.py')
| -rw-r--r-- | src/mailman/testing/mta.py | 156 |
1 files changed, 154 insertions, 2 deletions
diff --git a/src/mailman/testing/mta.py b/src/mailman/testing/mta.py index a10ba3c81..d16b9f955 100644 --- a/src/mailman/testing/mta.py +++ b/src/mailman/testing/mta.py @@ -25,16 +25,25 @@ __all__ = [ ] +import logging + +from Queue import Empty, Queue + +from lazr.smtptest.controller import QueueController +from lazr.smtptest.server import Channel, QueueServer from zope.interface import implements -from mailman.interfaces.mta import IMailTransportAgent +from mailman.interfaces.mta import IMailTransportAgentAliases + + +log = logging.getLogger('lazr.smtptest') class FakeMTA: """Fake MTA for testing purposes.""" - implements(IMailTransportAgent) + implements(IMailTransportAgentAliases) def create(self, mlist): pass @@ -44,3 +53,146 @@ class FakeMTA: def regenerate(self): pass + + + +class StatisticsChannel(Channel): + """A channel that can answers to the fake STAT command.""" + + def smtp_STAT(self, arg): + """Cause the server to send statistics to its controller.""" + self._server.send_statistics() + self.push(b'250 Ok') + + 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(b'%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(b'%d Error: SMTPResponseException' % code) + + + +class ConnectionCountingServer(QueueServer): + """Count the number of SMTP connections opened.""" + + def __init__(self, host, port, queue, oob_queue, err_queue): + """See `lazr.smtptest.server.QueueServer`. + + :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 + # 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 + + 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 + expected. If the next error matches the given command, the + expected error code is returned. + :type command: string, lower-cased + :return: An SMTP error code + :rtype: integer + """ + # If the last error we pulled from the queue didn't match, then we're + # caching it, and it might match this expected error. If there is no + # last error in the cache, get one from the queue now. + if self._last_error is None: + try: + self._last_error = self._err_queue.get_nowait() + except Empty: + # No error is expected + return None + if self._last_error[0] == command: + code = self._last_error[1] + self._last_error = None + 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 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) + + + +class ConnectionCountingController(QueueController): + """Count the number of SMTP connections opened.""" + + def __init__(self, host, port): + """See `lazr.smtptest.controller.QueueController`.""" + self.oob_queue = Queue() + self.err_queue = Queue() + QueueController.__init__(self, host, port) + + 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 start(self): + """See `lazr.smtptest.controller.QueueController`.""" + QueueController.start(self) + # Reset the connection statistics, since the base class's start() + # method causes a connection to occur. + self.reset() + + 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(b'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) + + @property + def messages(self): + """Return all the messages received by the SMTP server.""" + for message in self: + yield message + + def clear(self): + """Clear all the messages from the queue.""" + list(self) |
