[Checkins] SVN: zope.sendmail/trunk/src/zope/sendmail/ Moved the QueueProcessorThread from zope.sendmail.delivery to its own module
Fabio Tranchitella
kobold at kobold.it
Thu Jan 7 02:11:43 EST 2010
Log message for revision 107770:
Moved the QueueProcessorThread from zope.sendmail.delivery to its own module
zope.sendmail.queue; moved the QueueProcessorThread unit tests from
zope.sendmail.tests.test_delivery to their own module test_queue.
Changed:
U zope.sendmail/trunk/src/zope/sendmail/delivery.py
A zope.sendmail/trunk/src/zope/sendmail/queue.py
U zope.sendmail/trunk/src/zope/sendmail/tests/test_delivery.py
U zope.sendmail/trunk/src/zope/sendmail/tests/test_directives.py
A zope.sendmail/trunk/src/zope/sendmail/tests/test_queue.py
U zope.sendmail/trunk/src/zope/sendmail/zcml.py
-=-
Modified: zope.sendmail/trunk/src/zope/sendmail/delivery.py
===================================================================
--- zope.sendmail/trunk/src/zope/sendmail/delivery.py 2010-01-07 06:31:54 UTC (rev 107769)
+++ zope.sendmail/trunk/src/zope/sendmail/delivery.py 2010-01-07 07:11:43 UTC (rev 107770)
@@ -19,15 +19,8 @@
"""
__docformat__ = 'restructuredtext'
-import atexit
-import logging
import os
-import os.path
import rfc822
-import smtplib
-import stat
-import threading
-import time
from cStringIO import StringIO
from random import randrange
from time import strftime
@@ -39,19 +32,7 @@
from transaction.interfaces import IDataManager
import transaction
-import sys
-if sys.platform == 'win32':
- import win32file
- _os_link = lambda src, dst: win32file.CreateHardLink(dst, src, None)
-else:
- _os_link = os.link
-# The longest time sending a file is expected to take. Longer than this and
-# the send attempt will be assumed to have failed. This means that sending
-# very large files or using very slow mail servers could result in duplicate
-# messages sent.
-MAX_SEND_TIME = 60*60*3
-
class MailDataManager(object):
implements(IDataManager)
@@ -151,280 +132,3 @@
msg.write(message)
msg.close()
return MailDataManager(msg.commit, onAbort=msg.abort)
-
-
-# The below diagram depicts the operations performed while sending a message in
-# the ``run`` method of ``QueueProcessorThread``. This sequence of operations
-# will be performed for each file in the maildir each time the thread "wakes
-# up" to send messages.
-#
-# Any error conditions not depected on the diagram will provoke the catch-all
-# exception logging of the ``run`` method.
-#
-# In the diagram the "message file" is the file in the maildir's "cur" directory
-# that contains the message and "tmp file" is a hard link to the message file
-# created in the maildir's "tmp" directory.
-#
-# ( start trying to deliver a message )
-# |
-# |
-# V
-# +-----( get tmp file mtime )
-# | |
-# | | file exists
-# | V
-# | ( check age )-----------------------------+
-# tmp file | | file is new |
-# does not | | file is old |
-# exist | | |
-# | ( unlink tmp file )-----------------------+ |
-# | | file does | |
-# | | file unlinked not exist | |
-# | V | |
-# +---->( touch message file )------------------+ | |
-# | file does | | |
-# | not exist | | |
-# V | | |
-# ( link message file to tmp file )----------+ | | |
-# | tmp file | | | |
-# | already exists | | | |
-# | | | | |
-# V V V V V
-# ( send message ) ( skip this message )
-# |
-# V
-# ( unlink message file )---------+
-# | |
-# | file unlinked | file no longer exists
-# | |
-# | +-----------------+
-# | |
-# | V
-# ( unlink tmp file )------------+
-# | |
-# | file unlinked | file no longer exists
-# V |
-# ( message delivered )<---------+
-
-class QueueProcessorThread(threading.Thread):
- """This thread is started at configuration time from the
- `mail:queuedDelivery` directive handler.
- """
-
- log = logging.getLogger("QueueProcessorThread")
- _stopped = False
- interval = 3.0 # process queue every X second
-
- def __init__(self, interval=3.0):
- threading.Thread.__init__(self)
- self.interval = interval
- self._lock = threading.Lock()
- self.setDaemon(True)
-
- def setMaildir(self, maildir):
- """Set the maildir.
-
- This method is used just to provide a `maildir` stubs ."""
- self.maildir = maildir
-
- def setQueuePath(self, path):
- self.maildir = Maildir(path, True)
-
- def setMailer(self, mailer):
- self.mailer = mailer
-
- def _parseMessage(self, message):
- """Extract fromaddr and toaddrs from the first two lines of
- the `message`.
-
- Returns a fromaddr string, a toaddrs tuple and the message
- string.
- """
-
- fromaddr = ""
- toaddrs = ()
- rest = ""
-
- try:
- first, second, rest = message.split('\n', 2)
- except ValueError:
- return fromaddr, toaddrs, message
-
- if first.startswith("X-Zope-From: "):
- i = len("X-Zope-From: ")
- fromaddr = first[i:]
-
- if second.startswith("X-Zope-To: "):
- i = len("X-Zope-To: ")
- toaddrs = tuple(second[i:].split(", "))
-
- return fromaddr, toaddrs, rest
-
- def run(self, forever=True):
- atexit.register(self.stop)
- while not self._stopped:
- for filename in self.maildir:
- # if we are asked to stop while sending messages, do so
- if self._stopped:
- break
-
- fromaddr = ''
- toaddrs = ()
- head, tail = os.path.split(filename)
- tmp_filename = os.path.join(head, '.sending-' + tail)
- rejected_filename = os.path.join(head, '.rejected-' + tail)
- try:
- # perform a series of operations in an attempt to ensure
- # that no two threads/processes send this message
- # simultaneously as well as attempting to not generate
- # spurious failure messages in the log; a diagram that
- # represents these operations is included in a
- # comment above this class
- try:
- # find the age of the tmp file (if it exists)
- age = None
- mtime = os.stat(tmp_filename)[stat.ST_MTIME]
- age = time.time() - mtime
- except OSError, e:
- if e.errno == 2: # file does not exist
- # the tmp file could not be stated because it
- # doesn't exist, that's fine, keep going
- pass
- else:
- # the tmp file could not be stated for some reason
- # other than not existing; we'll report the error
- raise
-
- # if the tmp file exists, check it's age
- if age is not None:
- try:
- if age > MAX_SEND_TIME:
- # the tmp file is "too old"; this suggests
- # that during an attemt to send it, the
- # process died; remove the tmp file so we
- # can try again
- os.unlink(tmp_filename)
- else:
- # the tmp file is "new", so someone else may
- # be sending this message, try again later
- continue
- # if we get here, the file existed, but was too
- # old, so it was unlinked
- except OSError, e:
- if e.errno == 2: # file does not exist
- # it looks like someone else removed the tmp
- # file, that's fine, we'll try to deliver the
- # message again later
- continue
-
- # now we know that the tmp file doesn't exist, we need to
- # "touch" the message before we create the tmp file so the
- # mtime will reflect the fact that the file is being
- # processed (there is a race here, but it's OK for two or
- # more processes to touch the file "simultaneously")
- try:
- os.utime(filename, None)
- except OSError, e:
- if e.errno == 2: # file does not exist
- # someone removed the message before we could
- # touch it, no need to complain, we'll just keep
- # going
- continue
-
- # creating this hard link will fail if another process is
- # also sending this message
- try:
- #os.link(filename, tmp_filename)
- _os_link(filename, tmp_filename)
- except OSError, e:
- if e.errno == 17: # file exists, *nix
- # it looks like someone else is sending this
- # message too; we'll try again later
- continue
- except error, e:
- if e[0] == 183 and e[1] == 'CreateHardLink':
- # file exists, win32
- continue
-
- # read message file and send contents
- file = open(filename)
- message = file.read()
- file.close()
- fromaddr, toaddrs, message = self._parseMessage(message)
- # The next block is the only one that is sensitive to
- # interruptions. Everywhere else, if this daemon thread
- # stops, we should be able to correctly handle a restart.
- # In this block, if we send the message, but we are
- # stopped before we unlink the file, we will resend the
- # message when we are restarted. We limit the likelihood
- # of this somewhat by using a lock to link the two
- # operations. When the process gets an interrupt, it
- # will call the atexit that we registered (``stop``
- # below). This will try to get the same lock before it
- # lets go. Because this can cause the daemon thread to
- # continue (that is, to not act like a daemon thread), we
- # still use the _stopped flag to communicate.
- self._lock.acquire()
- try:
- try:
- self.mailer.send(fromaddr, toaddrs, message)
- except smtplib.SMTPResponseException, e:
- if 500 <= e.smtp_code <= 599:
- # permanent error, ditch the message
- self.log.error(
- "Discarding email from %s to %s due to"
- " a permanent error: %s",
- fromaddr, ", ".join(toaddrs), str(e))
- #os.link(filename, rejected_filename)
- _os_link(filename, rejected_filename)
- else:
- # Log an error and retry later
- raise
-
- try:
- os.unlink(filename)
- except OSError, e:
- if e.errno == 2: # file does not exist
- # someone else unlinked the file; oh well
- pass
- else:
- # something bad happend, log it
- raise
- finally:
- self._lock.release()
- try:
- os.unlink(tmp_filename)
- except OSError, e:
- if e.errno == 2: # file does not exist
- # someone else unlinked the file; oh well
- pass
- else:
- # something bad happend, log it
- raise
-
- # TODO: maybe log the Message-Id of the message sent
- self.log.info("Mail from %s to %s sent.",
- fromaddr, ", ".join(toaddrs))
- # Blanket except because we don't want
- # this thread to ever die
- except:
- if fromaddr != '' or toaddrs != ():
- self.log.error(
- "Error while sending mail from %s to %s.",
- fromaddr, ", ".join(toaddrs), exc_info=True)
- else:
- self.log.error(
- "Error while sending mail : %s ",
- filename, exc_info=True)
- else:
- if forever:
- time.sleep(self.interval)
-
- # A testing plug
- if not forever:
- break
-
- def stop(self):
- self._stopped = True
- self._lock.acquire()
- self._lock.release()
Copied: zope.sendmail/trunk/src/zope/sendmail/queue.py (from rev 106770, zope.sendmail/trunk/src/zope/sendmail/delivery.py)
===================================================================
--- zope.sendmail/trunk/src/zope/sendmail/queue.py (rev 0)
+++ zope.sendmail/trunk/src/zope/sendmail/queue.py 2010-01-07 07:11:43 UTC (rev 107770)
@@ -0,0 +1,319 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Mail Delivery utility implementation
+
+This module contains various implementations of `MailDeliverys`.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import atexit
+import logging
+import os
+import smtplib
+import stat
+import threading
+import time
+
+from zope.sendmail.maildir import Maildir
+
+import sys
+if sys.platform == 'win32':
+ import win32file
+ _os_link = lambda src, dst: win32file.CreateHardLink(dst, src, None)
+else:
+ _os_link = os.link
+
+# The longest time sending a file is expected to take. Longer than this and
+# the send attempt will be assumed to have failed. This means that sending
+# very large files or using very slow mail servers could result in duplicate
+# messages sent.
+MAX_SEND_TIME = 60*60*3
+
+# The below diagram depicts the operations performed while sending a message in
+# the ``run`` method of ``QueueProcessorThread``. This sequence of operations
+# will be performed for each file in the maildir each time the thread "wakes
+# up" to send messages.
+#
+# Any error conditions not depected on the diagram will provoke the catch-all
+# exception logging of the ``run`` method.
+#
+# In the diagram the "message file" is the file in the maildir's "cur" directory
+# that contains the message and "tmp file" is a hard link to the message file
+# created in the maildir's "tmp" directory.
+#
+# ( start trying to deliver a message )
+# |
+# |
+# V
+# +-----( get tmp file mtime )
+# | |
+# | | file exists
+# | V
+# | ( check age )-----------------------------+
+# tmp file | | file is new |
+# does not | | file is old |
+# exist | | |
+# | ( unlink tmp file )-----------------------+ |
+# | | file does | |
+# | | file unlinked not exist | |
+# | V | |
+# +---->( touch message file )------------------+ | |
+# | file does | | |
+# | not exist | | |
+# V | | |
+# ( link message file to tmp file )----------+ | | |
+# | tmp file | | | |
+# | already exists | | | |
+# | | | | |
+# V V V V V
+# ( send message ) ( skip this message )
+# |
+# V
+# ( unlink message file )---------+
+# | |
+# | file unlinked | file no longer exists
+# | |
+# | +-----------------+
+# | |
+# | V
+# ( unlink tmp file )------------+
+# | |
+# | file unlinked | file no longer exists
+# V |
+# ( message delivered )<---------+
+
+class QueueProcessorThread(threading.Thread):
+ """This thread is started at configuration time from the
+ `mail:queuedDelivery` directive handler.
+ """
+
+ log = logging.getLogger("QueueProcessorThread")
+ _stopped = False
+ interval = 3.0 # process queue every X second
+
+ def __init__(self, interval=3.0):
+ threading.Thread.__init__(self)
+ self.interval = interval
+ self._lock = threading.Lock()
+ self.setDaemon(True)
+
+ def setMaildir(self, maildir):
+ """Set the maildir.
+
+ This method is used just to provide a `maildir` stubs ."""
+ self.maildir = maildir
+
+ def setQueuePath(self, path):
+ self.maildir = Maildir(path, True)
+
+ def setMailer(self, mailer):
+ self.mailer = mailer
+
+ def _parseMessage(self, message):
+ """Extract fromaddr and toaddrs from the first two lines of
+ the `message`.
+
+ Returns a fromaddr string, a toaddrs tuple and the message
+ string.
+ """
+
+ fromaddr = ""
+ toaddrs = ()
+ rest = ""
+
+ try:
+ first, second, rest = message.split('\n', 2)
+ except ValueError:
+ return fromaddr, toaddrs, message
+
+ if first.startswith("X-Zope-From: "):
+ i = len("X-Zope-From: ")
+ fromaddr = first[i:]
+
+ if second.startswith("X-Zope-To: "):
+ i = len("X-Zope-To: ")
+ toaddrs = tuple(second[i:].split(", "))
+
+ return fromaddr, toaddrs, rest
+
+ def run(self, forever=True):
+ atexit.register(self.stop)
+ while not self._stopped:
+ for filename in self.maildir:
+ # if we are asked to stop while sending messages, do so
+ if self._stopped:
+ break
+
+ fromaddr = ''
+ toaddrs = ()
+ head, tail = os.path.split(filename)
+ tmp_filename = os.path.join(head, '.sending-' + tail)
+ rejected_filename = os.path.join(head, '.rejected-' + tail)
+ try:
+ # perform a series of operations in an attempt to ensure
+ # that no two threads/processes send this message
+ # simultaneously as well as attempting to not generate
+ # spurious failure messages in the log; a diagram that
+ # represents these operations is included in a
+ # comment above this class
+ try:
+ # find the age of the tmp file (if it exists)
+ age = None
+ mtime = os.stat(tmp_filename)[stat.ST_MTIME]
+ age = time.time() - mtime
+ except OSError, e:
+ if e.errno == 2: # file does not exist
+ # the tmp file could not be stated because it
+ # doesn't exist, that's fine, keep going
+ pass
+ else:
+ # the tmp file could not be stated for some reason
+ # other than not existing; we'll report the error
+ raise
+
+ # if the tmp file exists, check it's age
+ if age is not None:
+ try:
+ if age > MAX_SEND_TIME:
+ # the tmp file is "too old"; this suggests
+ # that during an attemt to send it, the
+ # process died; remove the tmp file so we
+ # can try again
+ os.unlink(tmp_filename)
+ else:
+ # the tmp file is "new", so someone else may
+ # be sending this message, try again later
+ continue
+ # if we get here, the file existed, but was too
+ # old, so it was unlinked
+ except OSError, e:
+ if e.errno == 2: # file does not exist
+ # it looks like someone else removed the tmp
+ # file, that's fine, we'll try to deliver the
+ # message again later
+ continue
+
+ # now we know that the tmp file doesn't exist, we need to
+ # "touch" the message before we create the tmp file so the
+ # mtime will reflect the fact that the file is being
+ # processed (there is a race here, but it's OK for two or
+ # more processes to touch the file "simultaneously")
+ try:
+ os.utime(filename, None)
+ except OSError, e:
+ if e.errno == 2: # file does not exist
+ # someone removed the message before we could
+ # touch it, no need to complain, we'll just keep
+ # going
+ continue
+
+ # creating this hard link will fail if another process is
+ # also sending this message
+ try:
+ #os.link(filename, tmp_filename)
+ _os_link(filename, tmp_filename)
+ except OSError, e:
+ if e.errno == 17: # file exists, *nix
+ # it looks like someone else is sending this
+ # message too; we'll try again later
+ continue
+ except Exception, e:
+ if e[0] == 183 and e[1] == 'CreateHardLink':
+ # file exists, win32
+ continue
+
+ # read message file and send contents
+ file = open(filename)
+ message = file.read()
+ file.close()
+ fromaddr, toaddrs, message = self._parseMessage(message)
+ # The next block is the only one that is sensitive to
+ # interruptions. Everywhere else, if this daemon thread
+ # stops, we should be able to correctly handle a restart.
+ # In this block, if we send the message, but we are
+ # stopped before we unlink the file, we will resend the
+ # message when we are restarted. We limit the likelihood
+ # of this somewhat by using a lock to link the two
+ # operations. When the process gets an interrupt, it
+ # will call the atexit that we registered (``stop``
+ # below). This will try to get the same lock before it
+ # lets go. Because this can cause the daemon thread to
+ # continue (that is, to not act like a daemon thread), we
+ # still use the _stopped flag to communicate.
+ self._lock.acquire()
+ try:
+ try:
+ self.mailer.send(fromaddr, toaddrs, message)
+ except smtplib.SMTPResponseException, e:
+ if 500 <= e.smtp_code <= 599:
+ # permanent error, ditch the message
+ self.log.error(
+ "Discarding email from %s to %s due to"
+ " a permanent error: %s",
+ fromaddr, ", ".join(toaddrs), str(e))
+ #os.link(filename, rejected_filename)
+ _os_link(filename, rejected_filename)
+ else:
+ # Log an error and retry later
+ raise
+
+ try:
+ os.unlink(filename)
+ except OSError, e:
+ if e.errno == 2: # file does not exist
+ # someone else unlinked the file; oh well
+ pass
+ else:
+ # something bad happend, log it
+ raise
+ finally:
+ self._lock.release()
+ try:
+ os.unlink(tmp_filename)
+ except OSError, e:
+ if e.errno == 2: # file does not exist
+ # someone else unlinked the file; oh well
+ pass
+ else:
+ # something bad happend, log it
+ raise
+
+ # TODO: maybe log the Message-Id of the message sent
+ self.log.info("Mail from %s to %s sent.",
+ fromaddr, ", ".join(toaddrs))
+ # Blanket except because we don't want
+ # this thread to ever die
+ except:
+ if fromaddr != '' or toaddrs != ():
+ self.log.error(
+ "Error while sending mail from %s to %s.",
+ fromaddr, ", ".join(toaddrs), exc_info=True)
+ else:
+ self.log.error(
+ "Error while sending mail : %s ",
+ filename, exc_info=True)
+ else:
+ if forever:
+ time.sleep(self.interval)
+
+ # A testing plug
+ if not forever:
+ break
+
+ def stop(self):
+ self._stopped = True
+ self._lock.acquire()
+ self._lock.release()
Property changes on: zope.sendmail/trunk/src/zope/sendmail/queue.py
___________________________________________________________________
Added: cvs2svn:cvs-rev
+ 1.1
Added: svn:keywords
+ Id
Added: svn:mergeinfo
+
Added: svn:eol-style
+ native
Modified: zope.sendmail/trunk/src/zope/sendmail/tests/test_delivery.py
===================================================================
--- zope.sendmail/trunk/src/zope/sendmail/tests/test_delivery.py 2010-01-07 06:31:54 UTC (rev 107769)
+++ zope.sendmail/trunk/src/zope/sendmail/tests/test_delivery.py 2010-01-07 07:11:43 UTC (rev 107770)
@@ -18,11 +18,8 @@
$Id$
"""
-import os.path
-import shutil
import smtplib
-from tempfile import mkdtemp
-from unittest import TestCase, TestSuite, makeSuite
+from unittest import TestCase, TestSuite, makeSuite, main
import transaction
from zope.testing import doctest
@@ -202,7 +199,7 @@
self.infos = []
self.errors = []
- def getLogger(name):
+ def getLogger(self, name):
return self
def error(self, msg, *args, **kwargs):
@@ -304,120 +301,13 @@
self.assertEquals(len(MaildirWriterStub.aborted_messages), 1)
-class TestQueueProcessorThread(TestCase):
-
- def setUp(self):
- from zope.sendmail.delivery import QueueProcessorThread
- self.md = MaildirStub('/foo/bar/baz')
- self.thread = QueueProcessorThread()
- self.thread.setMaildir(self.md)
- self.mailer = MailerStub()
- self.thread.setMailer(self.mailer)
- self.thread.log = LoggerStub()
- self.dir = mkdtemp()
-
- def tearDown(self):
- shutil.rmtree(self.dir)
-
- def test_parseMessage(self):
- hdr = ('X-Zope-From: foo at example.com\n'
- 'X-Zope-To: bar at example.com, baz at example.com\n')
- msg = ('Header: value\n'
- '\n'
- 'Body\n')
- f, t, m = self.thread._parseMessage(hdr + msg)
- self.assertEquals(f, 'foo at example.com')
- self.assertEquals(t, ('bar at example.com', 'baz at example.com'))
- self.assertEquals(m, msg)
-
- def test_deliveration(self):
- self.filename = os.path.join(self.dir, 'message')
- temp = open(self.filename, "w+b")
- temp.write('X-Zope-From: foo at example.com\n'
- 'X-Zope-To: bar at example.com, baz at example.com\n'
- 'Header: value\n\nBody\n')
- temp.close()
- self.md.files.append(self.filename)
- self.thread.run(forever=False)
- self.assertEquals(self.mailer.sent_messages,
- [('foo at example.com',
- ('bar at example.com', 'baz at example.com'),
- 'Header: value\n\nBody\n')])
- self.failIf(os.path.exists(self.filename), 'File exists')
- self.assertEquals(self.thread.log.infos,
- [('Mail from %s to %s sent.',
- ('foo at example.com',
- 'bar at example.com, baz at example.com'),
- {})])
-
- def test_error_logging(self):
- self.thread.setMailer(BrokenMailerStub())
- self.filename = os.path.join(self.dir, 'message')
- temp = open(self.filename, "w+b")
- temp.write('X-Zope-From: foo at example.com\n'
- 'X-Zope-To: bar at example.com, baz at example.com\n'
- 'Header: value\n\nBody\n')
- temp.close()
- self.md.files.append(self.filename)
- self.thread.run(forever=False)
- self.assertEquals(self.thread.log.errors,
- [('Error while sending mail from %s to %s.',
- ('foo at example.com',
- 'bar at example.com, baz at example.com'),
- {'exc_info': 1})])
-
- def test_smtp_response_error_transient(self):
- # Test a transient error
- self.thread.setMailer(SMTPResponseExceptionMailerStub(451))
- self.filename = os.path.join(self.dir, 'message')
- temp = open(self.filename, "w+b")
- temp.write('X-Zope-From: foo at example.com\n'
- 'X-Zope-To: bar at example.com, baz at example.com\n'
- 'Header: value\n\nBody\n')
- temp.close()
- self.md.files.append(self.filename)
- self.thread.run(forever=False)
-
- # File must remail were it was, so it will be retried
- self.failUnless(os.path.exists(self.filename))
- self.assertEquals(self.thread.log.errors,
- [('Error while sending mail from %s to %s.',
- ('foo at example.com',
- 'bar at example.com, baz at example.com'),
- {'exc_info': 1})])
-
- def test_smtp_response_error_permanent(self):
- # Test a permanent error
- self.thread.setMailer(SMTPResponseExceptionMailerStub(550))
- self.filename = os.path.join(self.dir, 'message')
- temp = open(self.filename, "w+b")
- temp.write('X-Zope-From: foo at example.com\n'
- 'X-Zope-To: bar at example.com, baz at example.com\n'
- 'Header: value\n\nBody\n')
- temp.close()
- self.md.files.append(self.filename)
- self.thread.run(forever=False)
-
- # File must be moved aside
- self.failIf(os.path.exists(self.filename))
- self.failUnless(os.path.exists(os.path.join(self.dir,
- '.rejected-message')))
- self.assertEquals(self.thread.log.errors,
- [('Discarding email from %s to %s due to a '
- 'permanent error: %s',
- ('foo at example.com',
- 'bar at example.com, baz at example.com',
- "(550, 'Serious Error')"), {})])
-
-
def test_suite():
return TestSuite((
makeSuite(TestMailDataManager),
makeSuite(TestDirectMailDelivery),
makeSuite(TestQueuedMailDelivery),
- makeSuite(TestQueueProcessorThread),
doctest.DocTestSuite(),
))
if __name__ == '__main__':
- unittest.main()
+ main()
Modified: zope.sendmail/trunk/src/zope/sendmail/tests/test_directives.py
===================================================================
--- zope.sendmail/trunk/src/zope/sendmail/tests/test_directives.py 2010-01-07 06:31:54 UTC (rev 107769)
+++ zope.sendmail/trunk/src/zope/sendmail/tests/test_directives.py 2010-01-07 07:11:43 UTC (rev 107770)
@@ -29,8 +29,8 @@
from zope.sendmail.interfaces import \
IMailDelivery, IMailer, ISMTPMailer
-from zope.sendmail.delivery import QueueProcessorThread
from zope.sendmail import delivery
+from zope.sendmail.queue import QueueProcessorThread
import zope.sendmail.tests
Copied: zope.sendmail/trunk/src/zope/sendmail/tests/test_queue.py (from rev 106770, zope.sendmail/trunk/src/zope/sendmail/tests/test_delivery.py)
===================================================================
--- zope.sendmail/trunk/src/zope/sendmail/tests/test_queue.py (rev 0)
+++ zope.sendmail/trunk/src/zope/sendmail/tests/test_queue.py 2010-01-07 07:11:43 UTC (rev 107770)
@@ -0,0 +1,143 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Mail Delivery Tests
+
+Simple implementation of the MailDelivery, Mailers and MailEvents.
+
+$Id$
+"""
+
+import os.path
+import shutil
+
+from tempfile import mkdtemp
+from unittest import TestCase, TestSuite, makeSuite, main
+
+from zope.sendmail.tests.test_delivery import MaildirStub, LoggerStub, \
+ BrokenMailerStub, SMTPResponseExceptionMailerStub, MailerStub
+
+
+class TestQueueProcessorThread(TestCase):
+
+ def setUp(self):
+ from zope.sendmail.queue import QueueProcessorThread
+ self.md = MaildirStub('/foo/bar/baz')
+ self.thread = QueueProcessorThread()
+ self.thread.setMaildir(self.md)
+ self.mailer = MailerStub()
+ self.thread.setMailer(self.mailer)
+ self.thread.log = LoggerStub()
+ self.dir = mkdtemp()
+
+ def tearDown(self):
+ shutil.rmtree(self.dir)
+
+ def test_parseMessage(self):
+ hdr = ('X-Zope-From: foo at example.com\n'
+ 'X-Zope-To: bar at example.com, baz at example.com\n')
+ msg = ('Header: value\n'
+ '\n'
+ 'Body\n')
+ f, t, m = self.thread._parseMessage(hdr + msg)
+ self.assertEquals(f, 'foo at example.com')
+ self.assertEquals(t, ('bar at example.com', 'baz at example.com'))
+ self.assertEquals(m, msg)
+
+ def test_deliveration(self):
+ self.filename = os.path.join(self.dir, 'message')
+ temp = open(self.filename, "w+b")
+ temp.write('X-Zope-From: foo at example.com\n'
+ 'X-Zope-To: bar at example.com, baz at example.com\n'
+ 'Header: value\n\nBody\n')
+ temp.close()
+ self.md.files.append(self.filename)
+ self.thread.run(forever=False)
+ self.assertEquals(self.mailer.sent_messages,
+ [('foo at example.com',
+ ('bar at example.com', 'baz at example.com'),
+ 'Header: value\n\nBody\n')])
+ self.failIf(os.path.exists(self.filename), 'File exists')
+ self.assertEquals(self.thread.log.infos,
+ [('Mail from %s to %s sent.',
+ ('foo at example.com',
+ 'bar at example.com, baz at example.com'),
+ {})])
+
+ def test_error_logging(self):
+ self.thread.setMailer(BrokenMailerStub())
+ self.filename = os.path.join(self.dir, 'message')
+ temp = open(self.filename, "w+b")
+ temp.write('X-Zope-From: foo at example.com\n'
+ 'X-Zope-To: bar at example.com, baz at example.com\n'
+ 'Header: value\n\nBody\n')
+ temp.close()
+ self.md.files.append(self.filename)
+ self.thread.run(forever=False)
+ self.assertEquals(self.thread.log.errors,
+ [('Error while sending mail from %s to %s.',
+ ('foo at example.com',
+ 'bar at example.com, baz at example.com'),
+ {'exc_info': 1})])
+
+ def test_smtp_response_error_transient(self):
+ # Test a transient error
+ self.thread.setMailer(SMTPResponseExceptionMailerStub(451))
+ self.filename = os.path.join(self.dir, 'message')
+ temp = open(self.filename, "w+b")
+ temp.write('X-Zope-From: foo at example.com\n'
+ 'X-Zope-To: bar at example.com, baz at example.com\n'
+ 'Header: value\n\nBody\n')
+ temp.close()
+ self.md.files.append(self.filename)
+ self.thread.run(forever=False)
+
+ # File must remail were it was, so it will be retried
+ self.failUnless(os.path.exists(self.filename))
+ self.assertEquals(self.thread.log.errors,
+ [('Error while sending mail from %s to %s.',
+ ('foo at example.com',
+ 'bar at example.com, baz at example.com'),
+ {'exc_info': 1})])
+
+ def test_smtp_response_error_permanent(self):
+ # Test a permanent error
+ self.thread.setMailer(SMTPResponseExceptionMailerStub(550))
+ self.filename = os.path.join(self.dir, 'message')
+ temp = open(self.filename, "w+b")
+ temp.write('X-Zope-From: foo at example.com\n'
+ 'X-Zope-To: bar at example.com, baz at example.com\n'
+ 'Header: value\n\nBody\n')
+ temp.close()
+ self.md.files.append(self.filename)
+ self.thread.run(forever=False)
+
+ # File must be moved aside
+ self.failIf(os.path.exists(self.filename))
+ self.failUnless(os.path.exists(os.path.join(self.dir,
+ '.rejected-message')))
+ self.assertEquals(self.thread.log.errors,
+ [('Discarding email from %s to %s due to a '
+ 'permanent error: %s',
+ ('foo at example.com',
+ 'bar at example.com, baz at example.com',
+ "(550, 'Serious Error')"), {})])
+
+
+def test_suite():
+ return TestSuite((
+ makeSuite(TestQueueProcessorThread),
+ ))
+
+if __name__ == '__main__':
+ main()
Property changes on: zope.sendmail/trunk/src/zope/sendmail/tests/test_queue.py
___________________________________________________________________
Added: cvs2svn:cvs-rev
+ 1.1
Added: svn:keywords
+ Id
Added: svn:mergeinfo
+
Added: svn:eol-style
+ native
Modified: zope.sendmail/trunk/src/zope/sendmail/zcml.py
===================================================================
--- zope.sendmail/trunk/src/zope/sendmail/zcml.py 2010-01-07 06:31:54 UTC (rev 107769)
+++ zope.sendmail/trunk/src/zope/sendmail/zcml.py 2010-01-07 07:11:43 UTC (rev 107770)
@@ -25,9 +25,9 @@
from zope.schema import TextLine, BytesLine, Int, Bool
from zope.sendmail.delivery import QueuedMailDelivery, DirectMailDelivery
-from zope.sendmail.delivery import QueueProcessorThread
from zope.sendmail.interfaces import IMailer, IMailDelivery
from zope.sendmail.mailer import SMTPMailer
+from zope.sendmail.queue import QueueProcessorThread
try:
from zope.component.security import proxify
More information about the checkins
mailing list