summaryrefslogtreecommitdiff
path: root/Mailman/testing
diff options
context:
space:
mode:
authorbwarsaw2007-04-17 21:37:49 +0000
committerbwarsaw2007-04-17 21:37:49 +0000
commitcca5c6b455aa894078573f45f3dd2c0901e0103e (patch)
treeb0e3c481fae7bb44c9c27c258735f8318cadb530 /Mailman/testing
parent69b250129cac1edc2aef339127bdd9446b8ee5e7 (diff)
downloadmailman-cca5c6b455aa894078573f45f3dd2c0901e0103e.tar.gz
mailman-cca5c6b455aa894078573f45f3dd2c0901e0103e.tar.zst
mailman-cca5c6b455aa894078573f45f3dd2c0901e0103e.zip
Another round of merges of my Pycon branch...
bin/testall.py - Improvements to setting up the tests with temporary files for the db and configuration. Don't accept -C/--config because that will just confuse things. - Copy our new testing.cfg.in template file to a temp file, populate it with a few additional run-time calculate values, and use that in both the parent (bin/testall script) and children (bin/mailmanctl and friends). Mailman/initialize.py - Split up initialize() into two functions as required by the test suite reorg above. Almost everything else in Mailman will continue to use the initialize() function though. Mailman/configuration.py - Store the filename that was used to load the .cfg file from on the configuration object.
Diffstat (limited to 'Mailman/testing')
-rw-r--r--Mailman/testing/Makefile.in3
-rw-r--r--Mailman/testing/base.py45
-rw-r--r--Mailman/testing/emailbase.py13
-rw-r--r--Mailman/testing/inmemory.py488
-rw-r--r--Mailman/testing/test_enum.py3
-rw-r--r--Mailman/testing/testing.cfg.in14
6 files changed, 508 insertions, 58 deletions
diff --git a/Mailman/testing/Makefile.in b/Mailman/testing/Makefile.in
index 2bbfdc7c0..1326f67e5 100644
--- a/Mailman/testing/Makefile.in
+++ b/Mailman/testing/Makefile.in
@@ -42,6 +42,7 @@ PACKAGEDIR= $(prefix)/Mailman/testing
SHELL= /bin/sh
MODULES= *.py
+OTHERFILES= testing.cfg.in
# Modes for directories and executables created by the install
# process. Default to group-writable directories but
@@ -59,7 +60,7 @@ SUBDIRS= bounces
all:
install:
- for f in $(MODULES); \
+ for f in $(MODULES) $(OTHERFILES); \
do \
$(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(DESTDIR)$(PACKAGEDIR); \
done
diff --git a/Mailman/testing/base.py b/Mailman/testing/base.py
index 8a131175e..562443b1e 100644
--- a/Mailman/testing/base.py
+++ b/Mailman/testing/base.py
@@ -41,26 +41,7 @@ NL = '\n'
-def dummy_mta_function(*args, **kws):
- pass
-
-
-
class TestBase(unittest.TestCase):
- def _configure(self, fp):
- # Make sure that we don't pollute the real database with our test
- # mailing list.
- test_engine_url = 'sqlite:///' + self._dbfile
- print >> fp, 'SQLALCHEMY_ENGINE_URL = "%s"' % test_engine_url
- config.SQLALCHEMY_ENGINE_URL = test_engine_url
- # Use the Mailman.MTA.stub module
- print >> fp, 'MTA = "stub"'
- config.MTA = 'stub'
- print >> fp, 'add_domain("example.com", "www.example.com")'
- # Only add this domain once to the current process
- if 'example.com' not in config.domains:
- config.add_domain('example.com', 'www.example.com')
-
def ndiffAssertEqual(self, first, second):
"""Like failUnlessEqual except use ndiff for readable output."""
if first <> second:
@@ -72,30 +53,6 @@ class TestBase(unittest.TestCase):
raise self.failureException(fp.getvalue())
def setUp(self):
- mailman_uid = pwd.getpwnam(config.MAILMAN_USER).pw_uid
- mailman_gid = grp.getgrnam(config.MAILMAN_GROUP).gr_gid
- # Write a temporary configuration file, but allow for subclasses to
- # add additional data. Make sure the config and db files, which
- # mkstemp creates, has the proper ownership and permissions.
- fd, self._config = tempfile.mkstemp(dir=config.DATA_DIR, suffix='.cfg')
- os.close(fd)
- os.chmod(self._config, 0440)
- os.chown(self._config, mailman_uid, mailman_gid)
- fd, self._dbfile = tempfile.mkstemp(dir=config.DATA_DIR, suffix='.db')
- os.close(fd)
- os.chmod(self._dbfile, 0660)
- os.chown(self._dbfile, mailman_uid, mailman_gid)
- fp = open(self._config, 'w')
- try:
- self._configure(fp)
- finally:
- fp.close()
- # Create a fake new Mailman.MTA module which stubs out the create()
- # and remove() functions.
- stubmta_module = new.module('Mailman.MTA.stub')
- sys.modules['Mailman.MTA.stub'] = stubmta_module
- stubmta_module.create = dummy_mta_function
- stubmta_module.remove = dummy_mta_function
# Be sure to close the connection to the current database, and then
# reconnect to the new temporary SQLite database. Otherwise we end up
# with turds in the main database and our qrunner subprocesses won't
@@ -116,8 +73,6 @@ class TestBase(unittest.TestCase):
self._mlist.Unlock()
rmlist.delete_list(self._mlist.fqdn_listname, self._mlist,
archives=True, quiet=True)
- os.unlink(self._config)
- os.unlink(self._dbfile)
# Clear out any site locks, which can be left over if tests fail.
for filename in os.listdir(config.LOCK_DIR):
if filename.startswith('<site>'):
diff --git a/Mailman/testing/emailbase.py b/Mailman/testing/emailbase.py
index e033195de..d0fdbf7d4 100644
--- a/Mailman/testing/emailbase.py
+++ b/Mailman/testing/emailbase.py
@@ -52,15 +52,6 @@ class SinkServer(smtpd.SMTPServer):
class EmailBase(TestBase):
- def _configure(self, fp):
- TestBase._configure(self, fp)
- print >> fp, 'SMTPPORT =', TESTPORT
- config.SMTPPORT = TESTPORT
- # Don't go nuts on mailmanctl restarts. If a qrunner fails once, it
- # will keep failing.
- print >> fp, 'MAX_RESTARTS = 1'
- config.MAX_RESTARTS = 1
-
def setUp(self):
TestBase.setUp(self)
try:
@@ -70,7 +61,7 @@ class EmailBase(TestBase):
TestBase.tearDown(self)
raise
try:
- os.system('bin/mailmanctl -C %s -q start' % self._config)
+ os.system('bin/mailmanctl -C %s -q start' % config.filename)
# If any errors occur in the above, be sure to manually call
# tearDown(). unittest doesn't call tearDown() for errors in
# setUp().
@@ -79,7 +70,7 @@ class EmailBase(TestBase):
raise
def tearDown(self):
- os.system('bin/mailmanctl -C %s -q stop' % self._config)
+ os.system('bin/mailmanctl -C %s -q stop' % config.filename)
self._server.close()
# Wait a while until the server actually goes away
while True:
diff --git a/Mailman/testing/inmemory.py b/Mailman/testing/inmemory.py
new file mode 100644
index 000000000..ca5313452
--- /dev/null
+++ b/Mailman/testing/inmemory.py
@@ -0,0 +1,488 @@
+# Copyright (C) 2007 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+"""In-memory implementations of Mailman interfaces, for testing purposes."""
+
+import datetime
+import urlparse
+
+from Mailman import Utils
+from Mailman import passwords
+from Mailman.interfaces import *
+
+from zope.interface import implements
+
+
+
+class UserManager(object):
+ implements(IUserManager)
+
+ def __init__(self):
+ self._users = set()
+ self._next_id = 1
+
+ @property
+ def users(self):
+ for user in self._users:
+ yield user
+
+ def create_user(self):
+ user = User(self._next_id, self)
+ self._next_id += 1
+ self._users.add(user)
+ return user
+
+ def remove(self, user):
+ self._users.discard(user)
+
+ def get(self, address):
+ # Yes, this is slow and icky, but it's only for testing purposes
+ for user in self._users:
+ if user.controls(address):
+ return user
+ return None
+
+
+
+class User(object):
+ implements(IUser)
+
+ def __init__(self, user_id, user_mgr):
+ self._user_id = user_id
+ self._user_mgr = user_mgr
+ self._addresses = set()
+ self.real_name = u''
+ self.password = passwords.NoPasswordScheme.make_secret('ignore')
+ self.default_profile = None
+
+ def __eq__(self, other):
+ return (IUser.implementedBy(other) and
+ self.user_id == other.user_id and
+ self.user_manager is other.user_manager)
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ @property
+ def user_id(self):
+ return self._user_id
+
+ @property
+ def user_manager(self):
+ return self._user_mgr
+
+ @property
+ def addresses(self):
+ for address in self._addresses:
+ yield address
+
+ def add_address(self, address):
+ if self.controls(address):
+ return
+ user_address = Address(address, self)
+ self._addresses.add(user_address)
+
+ def remove_address(self, address):
+ if not self.controls(address):
+ return
+ user_address = Address(address, self)
+ self._addresses.discard(user_address)
+
+ def controls(self, address):
+ for user_address in self.addresses:
+ if user_address == address:
+ return True
+ return False
+
+
+
+class Address(object):
+ implements(IAddress)
+
+ def __init__(self, email_address, user, profile=None):
+ self._address = email_address
+ self._user = user
+ self.profile = profile or Profile()
+ self.validated_on = None
+
+ def __eq__(self, other):
+ return (IAddress.implementedBy(other) and
+ self.address == other.address and
+ self.user == other.user)
+
+ @property
+ def address(self):
+ return self._address
+
+ @property
+ def user(self):
+ return self._user
+
+
+
+class RegularDelivery(object):
+ implements(IRegularDelivery)
+
+
+class PlainTextDigestDelivery(object):
+ implements(IPlainTextDigestDelivery)
+
+
+class MIMEDigestDelivery(object):
+ implements(IMIMEDigestDeliver)
+
+
+
+class DeliveryEnabled(object):
+ implements(IDeliveryStatus)
+
+ @property
+ def enabled(self):
+ return True
+
+
+class DeliveryDisabled(object):
+ implements(IDeliveryStatus)
+
+ @property
+ def enabled(self):
+ return False
+
+
+class DeliveryDisabledByUser(DeliveryDisabled):
+ implements(IDeliveryDisabledByUser)
+
+
+class DeliveryDisabledbyAdministrator(DeliveryDisabled):
+ implements(IDeliveryDisabledByAdministrator)
+
+ reason = u'Unknown'
+
+
+class DeliveryDisabledByBounces(DeliveryDisabled):
+ implements(IDeliveryDisabledByBounces)
+
+ bounce_info = 'XXX'
+
+
+class DeliveryTemporarilySuspended(object):
+ implements(IDeliveryTemporarilySuspended)
+
+ def __init__(self, start_date, end_date):
+ self.start_date = start_date
+ self.end_date = end_date
+
+ @property
+ def enabled(self):
+ now = datetime.datetime.now()
+ return not (self.start_date <= now < self.end_date)
+
+
+
+class OkayToPost(object):
+ implements(IPostingPermission)
+
+ # XXX
+ okay_to_post = True
+
+
+
+class Profile(object):
+ implements(IProfile)
+
+ # System defaults
+ acknowledge = False
+ hide = True
+ language = 'en'
+ list_copy = True
+ own_postings = True
+ delivery_mode = RegularDelivery()
+ delivery_status = DeliveryEnabled()
+ posting_permission = OkayToPost()
+
+
+
+class Roster(object):
+ implements(IRoster)
+
+ def __init__(self, name):
+ self._name = name
+ self._members = set()
+
+ def __eq__(self, other):
+ return (IRoster.implementedBy(other) and
+ self.name == other.name)
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ @property
+ def name(self):
+ return self._name
+
+ def add(self, member):
+ self._members.add(member)
+
+ def remove(self, member):
+ self._members.remove(member)
+
+ @property
+ def members(self):
+ for member in self._members:
+ yield member
+
+
+
+class Member(object):
+ implements(IMember)
+
+ def __init__(self, address, roster, profile=None):
+ self._address = address
+ self._roster = roster
+ self.profile = profile or Profile()
+
+ @property
+ def address(self):
+ return self._address
+
+ @property
+ def roster(self):
+ return self._roster
+
+
+
+class ListManager(object):
+ implements(IListManager)
+
+ def __init__(self):
+ self._mlists = {}
+
+ def add(self, mlist):
+ self._mlists[mlist.fqdn_listname] = mlist
+
+ def remove(self, mlist):
+ del self._mlists[mlist.fqdn_listname]
+
+ @property
+ def mailing_lists(self):
+ return self._mlists.itervalues()
+
+ @property
+ def names(self):
+ return self._mlists.iterkeys()
+
+ def get(self, fqdn_listname):
+ return self._mlists.get(fqdn_listname)
+
+
+
+class MailingList(object):
+ implements(IMailingListIdentity,
+ IMailingListAddresses,
+ IMailingListURLs,
+ IMailingListRosters,
+ IMailingListStatistics,
+ )
+
+ def __init__(self, list_name, host_name, web_host):
+ self._listname = list_name
+ self._hostname = hostname
+ self._webhost = web_host
+ self._fqdn_listname = Utils.fqdn_listname(list_name, host_name)
+ # Rosters
+ self._owners = set(Roster(self.owner_address))
+ self._moderators = set(Roster(self._listname + '-moderators@' +
+ self._hostname))
+ self._members = set(Roster(self.posting_address))
+ # Statistics
+ self._created_on = datetime.datetime.now()
+ self._last_posting = None
+ self._post_number = 0
+ self._last_digest = None
+
+ # IMailingListIdentity
+
+ @property
+ def list_name(self):
+ return self._listname
+
+ @property
+ def host_name(self):
+ return self._hostname
+
+ @property
+ def fqdn_listname(self):
+ return self._fqdn_listname
+
+ # IMailingListAddresses
+
+ @property
+ def posting_address(self):
+ return self._fqdn_listname
+
+ @property
+ def noreply_address(self):
+ return self._listname + '-noreply@' + self._hostname
+
+ @property
+ def owner_address(self):
+ return self._listname + '-owner@' + self._hostname
+
+ @property
+ def request_address(self):
+ return self._listname + '-request@' + self._hostname
+
+ @property
+ def bounces_address(self):
+ return self._listname + '-bounces@' + self._hostname
+
+ @property
+ def confirm_address(self):
+ return self._listname + '-confirm@' + self._hostname
+
+ @property
+ def join_address(self):
+ return self._listname + '-join@' + self._hostname
+
+ @property
+ def leave_address(self):
+ return self._listname + '-leave@' + self._hostname
+
+ @property
+ def subscribe_address(self):
+ return self._listname + '-subscribe@' + self._hostname
+
+ @property
+ def unsubscribe_address(self):
+ return self._listname + '-unsubscribe@' + self._hostname
+
+ # IMailingListURLs
+
+ protocol = 'http'
+
+ @property
+ def web_host(self):
+ return self._webhost
+
+ def script_url(self, target, context=None):
+ if context is None:
+ return urlparse.urlunsplit((self.protocol, self.web_host, target,
+ # no extra query or fragment
+ '', ''))
+ return urlparse.urljoin(context.location, target)
+
+ # IMailingListRosters
+
+ @property
+ def owner_rosters(self):
+ return iter(self._owners)
+
+ @property
+ def moderator_rosters(self):
+ return iter(self._moderators)
+
+ @property
+ def member_rosters(self):
+ return iter(self._members)
+
+ def add_owner_roster(self, roster):
+ self._owners.add(roster)
+
+ def add_moderator_roster(self, roster):
+ self._moderators.add(roster)
+
+ def add_member_roster(self, roster):
+ self._members.add(roster)
+
+ def remove_owner_roster(self, roster):
+ self._owners.discard(roster)
+
+ def remove_moderator_roster(self, roster):
+ self._moderators.discard(roster)
+
+ def remove_member_roster(self, roster):
+ self._members.discard(roster)
+
+ @property
+ def owners(self):
+ for roster in self._owners:
+ for member in roster.members:
+ yield member
+
+ @property
+ def moderators(self):
+ for roster in self._moderators:
+ for member in roster.members:
+ yield member
+
+ @property
+ def administrators(self):
+ for member in self.owners:
+ yield member
+ for member in self.moderators:
+ yield member
+
+ @property
+ def members(self):
+ for roster in self._members:
+ for member in roster.members:
+ yield member
+
+ @property
+ def regular_members(self):
+ for member in self.members:
+ if IRegularDelivery.implementedBy(member.profile.delivery_mode):
+ yield member
+
+ @property
+ def digest_member(self):
+ for member in self.members:
+ if IDigestDelivery.implementedBy(member.profile.delivery_mode):
+ yield member
+
+ # Statistic
+
+ @property
+ def creation_date(self):
+ return self._created_on
+
+ @property
+ def last_post_date(self):
+ return self._last_posting
+
+ @property
+ def post_number(self):
+ return self._post_number
+
+ @property
+ def last_digest_date(self):
+ return self._last_digest
+
+
+
+class MailingListRequest(object):
+ implements(IMailingListRequest)
+
+ location = ''
+
+
+
+def initialize():
+ from Mailman.configuration import config
+ config.user_manager = UserManager()
+ config.list_manager = ListManager()
+ config.message_manager = None
diff --git a/Mailman/testing/test_enum.py b/Mailman/testing/test_enum.py
index de6877afc..a8c389bb4 100644
--- a/Mailman/testing/test_enum.py
+++ b/Mailman/testing/test_enum.py
@@ -93,9 +93,10 @@ class TestEnum(unittest.TestCase):
eq(int(Colors.blue), 3)
eq(int(MoreColors.red), 1)
eq(int(OtherColors.blue), 2)
-
+
def test_enum_duplicates(self):
try:
+ # This is bad because kyle and kenny have the same integer value.
class Bad(Enum):
cartman = 1
stan = 2
diff --git a/Mailman/testing/testing.cfg.in b/Mailman/testing/testing.cfg.in
new file mode 100644
index 000000000..80e5e8bfc
--- /dev/null
+++ b/Mailman/testing/testing.cfg.in
@@ -0,0 +1,14 @@
+# -*- python -*-
+
+# Configuration file template for the unit test suite. We need this because
+# both the process running the tests and all sub-processes (e.g. qrunners)
+# must share the same configuration file.
+
+MANAGERS_INIT_FUNCTION = 'Mailman.testing.inmemory.initialize'
+SMTPPORT = 10825
+MAX_RESTARTS = 1
+MTA = None
+
+add_domain('example.com', 'www.example.com')
+
+# bin/testall will add a SQLALCHEMY_ENGINE_URL below