summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mailman/database/base.py2
-rw-r--r--src/mailman/database/docs/migration.rst53
-rw-r--r--src/mailman/database/schema/mm_00000000000000_base.py21
-rw-r--r--src/mailman/database/schema/mm_20120407000000.py18
-rw-r--r--src/mailman/database/tests/data/__init__.py0
-rw-r--r--src/mailman/database/tests/data/mailman_01.dbbin0 -> 48128 bytes
-rw-r--r--src/mailman/database/tests/test_migrations.py72
-rw-r--r--src/mailman/rules/docs/news-moderation.rst2
-rw-r--r--src/mailman/rules/news_moderation.py2
-rw-r--r--src/mailman/testing/database.py47
10 files changed, 157 insertions, 60 deletions
diff --git a/src/mailman/database/base.py b/src/mailman/database/base.py
index 55dee2068..80c62658e 100644
--- a/src/mailman/database/base.py
+++ b/src/mailman/database/base.py
@@ -156,7 +156,7 @@ class StormBaseDatabase:
parent, dot, child = migrations_path.rpartition('.')
else:
parent = migrations_path
- child =''
+ child = ''
# If the database does not yet exist, load the base schema.
filenames = sorted(resource_listdir(parent, child))
# Find out which schema migrations have already been loaded.
diff --git a/src/mailman/database/docs/migration.rst b/src/mailman/database/docs/migration.rst
index 999700ca7..a4d25d648 100644
--- a/src/mailman/database/docs/migration.rst
+++ b/src/mailman/database/docs/migration.rst
@@ -24,17 +24,18 @@ Migrations are applied automatically when Mailman starts up, but can also be
applied at any time by calling in the API directly. Once applied, a
migration's version string is registered so it will not be applied again.
-We see that the base migration is already applied.
+We see that the base migration, as well as subsequent standard migrations, are
+already applied.
>>> from mailman.model.version import Version
>>> results = config.db.store.find(Version, component='schema')
>>> results.count()
- 1
- >>> base = results.one()
- >>> print base.component
- schema
- >>> print base.version
+ 2
+ >>> versions = sorted(result.version for result in results)
+ >>> for version in versions:
+ ... print version
00000000000000
+ 20120407000000
Migrations
@@ -77,7 +78,7 @@ This migration module just adds a marker to the `version` table.
>>> with open(os.path.join(path, '__init__.py'), 'w') as fp:
... pass
- >>> with open(os.path.join(path, 'mm_20120211000000.py'), 'w') as fp:
+ >>> with open(os.path.join(path, 'mm_20129999000000.py'), 'w') as fp:
... print >> fp, """
... from __future__ import unicode_literals
... from mailman.model.version import Version
@@ -94,14 +95,15 @@ This will load the new migration, since it hasn't been loaded before.
>>> for result in sorted(result.version for result in results):
... print result
00000000000000
- 20120211000000
+ 20120407000000
+ 20129999000000
>>> test = config.db.store.find(Version, component='test').one()
>>> print test.version
- 20120211000000
+ 20129999000000
Migrations will only be loaded once.
- >>> with open(os.path.join(path, 'mm_20120211000001.py'), 'w') as fp:
+ >>> with open(os.path.join(path, 'mm_20129999000001.py'), 'w') as fp:
... print >> fp, """
... from __future__ import unicode_literals
... from mailman.model.version import Version
@@ -123,13 +125,14 @@ The first time we load this new migration, we'll get the 801 marker.
>>> for result in sorted(result.version for result in results):
... print result
00000000000000
- 20120211000000
- 20120211000001
+ 20120407000000
+ 20129999000000
+ 20129999000001
>>> test = config.db.store.find(Version, component='test')
>>> for marker in sorted(marker.version for marker in test):
... print marker
00000000000801
- 20120211000000
+ 20129999000000
We do not get an 802 marker because the migration has already been loaded.
@@ -138,13 +141,14 @@ We do not get an 802 marker because the migration has already been loaded.
>>> for result in sorted(result.version for result in results):
... print result
00000000000000
- 20120211000000
- 20120211000001
+ 20120407000000
+ 20129999000000
+ 20129999000001
>>> test = config.db.store.find(Version, component='test')
>>> for marker in sorted(marker.version for marker in test):
... print marker
00000000000801
- 20120211000000
+ 20129999000000
Partial upgrades
@@ -157,13 +161,13 @@ additional migrations, intended to be applied in sequential order.
>>> from shutil import copyfile
>>> from mailman.testing.helpers import chdir
>>> with chdir(path):
- ... copyfile('mm_20120211000000.py', 'mm_20120211000002.py')
- ... copyfile('mm_20120211000000.py', 'mm_20120211000003.py')
- ... copyfile('mm_20120211000000.py', 'mm_20120211000004.py')
+ ... copyfile('mm_20129999000000.py', 'mm_20129999000002.py')
+ ... copyfile('mm_20129999000000.py', 'mm_20129999000003.py')
+ ... copyfile('mm_20129999000000.py', 'mm_20129999000004.py')
Now, only migrate to the ...03 timestamp.
- >>> config.db.load_migrations('20120211000003')
+ >>> config.db.load_migrations('20129999000003')
You'll notice that the ...04 version is not present.
@@ -171,7 +175,8 @@ You'll notice that the ...04 version is not present.
>>> for result in sorted(result.version for result in results):
... print result
00000000000000
- 20120211000000
- 20120211000001
- 20120211000002
- 20120211000003
+ 20120407000000
+ 20129999000000
+ 20129999000001
+ 20129999000002
+ 20129999000003
diff --git a/src/mailman/database/schema/mm_00000000000000_base.py b/src/mailman/database/schema/mm_00000000000000_base.py
index d703088d6..ef448d620 100644
--- a/src/mailman/database/schema/mm_00000000000000_base.py
+++ b/src/mailman/database/schema/mm_00000000000000_base.py
@@ -21,14 +21,14 @@ from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
- 'upgrade',
'post_reset',
'pre_reset',
+ 'upgrade',
]
-_migration_path = None
VERSION = '00000000000000'
+_helper = None
@@ -37,19 +37,12 @@ def upgrade(database, store, version, module_path):
database.load_schema(store, version, filename, module_path)
+
def pre_reset(store):
- global _migration_path
- # Save the entry in the Version table for the test suite reset. This will
- # be restored below.
- from mailman.model.version import Version
- result = store.find(Version, component=VERSION).one()
- # Yes, we abuse this field.
- _migration_path = result.version
+ global _helper
+ from mailman.testing.database import ResetHelper
+ _helper = ResetHelper(VERSION, store)
def post_reset(store):
- from mailman.model.version import Version
- # We need to preserve the Version table entry for this migration, since
- # its existence defines the fact that the tables have been loaded.
- store.add(Version(component='schema', version=VERSION))
- store.add(Version(component=VERSION, version=_migration_path))
+ _helper.restore(store)
diff --git a/src/mailman/database/schema/mm_20120407000000.py b/src/mailman/database/schema/mm_20120407000000.py
index 555d35ea2..ec4fcb2ee 100644
--- a/src/mailman/database/schema/mm_20120407000000.py
+++ b/src/mailman/database/schema/mm_20120407000000.py
@@ -21,6 +21,9 @@ from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
+ 'post_reset',
+ 'pre_reset',
+ 'upgrade',
]
@@ -28,6 +31,10 @@ from mailman.interfaces.archiver import ArchivePolicy
from mailman.interfaces.database import DatabaseError
+VERSION = '20120407000000'
+_helper = None
+
+
def upgrade(database, store, version, module_path):
if database.TAG == 'sqlite':
@@ -68,3 +75,14 @@ def upgrade_sqlite(database, store, version, module_path):
archive_policy, id))
store.execute('drop table mailinglist;')
store.execute('alter table ml_backup rename to mailinglist;')
+
+
+
+def pre_reset(store):
+ global _helper
+ from mailman.testing.database import ResetHelper
+ _helper = ResetHelper(VERSION, store)
+
+
+def post_reset(store):
+ _helper.restore(store)
diff --git a/src/mailman/database/tests/data/__init__.py b/src/mailman/database/tests/data/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/mailman/database/tests/data/__init__.py
diff --git a/src/mailman/database/tests/data/mailman_01.db b/src/mailman/database/tests/data/mailman_01.db
new file mode 100644
index 000000000..1ff8d8343
--- /dev/null
+++ b/src/mailman/database/tests/data/mailman_01.db
Binary files differ
diff --git a/src/mailman/database/tests/test_migrations.py b/src/mailman/database/tests/test_migrations.py
index bf3decc88..c772a63d5 100644
--- a/src/mailman/database/tests/test_migrations.py
+++ b/src/mailman/database/tests/test_migrations.py
@@ -31,11 +31,13 @@ import sqlite3
import tempfile
import unittest
+from pkg_resources import resource_filename
from zope.component import getUtility
-from mailman.app.lifecycle import create_list
from mailman.config import config
from mailman.interfaces.domain import IDomainManager
+from mailman.interfaces.listmanager import IListManager
+from mailman.interfaces.mailinglist import IAcceptableAliasSet
from mailman.testing.helpers import configuration, temporary_db
from mailman.testing.layers import ConfigLayer
from mailman.utilities.modules import call_name
@@ -66,7 +68,8 @@ class TestMigration20120407(unittest.TestCase):
shutil.rmtree(self._tempdir)
def test_sqlite_base(self):
- # Test the SQLite base schema.
+ # Test that before the migration, the old table columns are present
+ # and the new database columns are not.
url = 'sqlite:///' + os.path.join(self._tempdir, 'mailman.db')
database_class = config.database['class']
database = call_name(database_class)
@@ -74,14 +77,6 @@ class TestMigration20120407(unittest.TestCase):
database.initialize()
# Load all the database SQL to just before ours.
database.load_migrations('20120406999999')
- # Populate the test database with a domain and a mailing list.
- with temporary_db(database):
- getUtility(IDomainManager).add(
- 'example.com', 'An example domain.',
- 'http://lists.example.com', 'postmaster@example.com')
- mlist = create_list('test@example.com')
- del mlist
- database.commit()
# Verify that the database has not yet been migrated.
for missing in ('archive_policy',
'nntp_prefix_subject_too'):
@@ -100,7 +95,8 @@ class TestMigration20120407(unittest.TestCase):
'select {0} from mailinglist;'.format(present))
def test_sqlite_migration(self):
- # Test the SQLite base schema.
+ # Test that after the migration, the old table columns are missing
+ # and the new database columns are present.
url = 'sqlite:///' + os.path.join(self._tempdir, 'mailman.db')
database_class = config.database['class']
database = call_name(database_class)
@@ -108,14 +104,6 @@ class TestMigration20120407(unittest.TestCase):
database.initialize()
# Load all the database SQL to just before ours.
database.load_migrations('20120406999999')
- # Populate the test database with a domain and a mailing list.
- with temporary_db(database):
- getUtility(IDomainManager).add(
- 'example.com', 'An example domain.',
- 'http://lists.example.com', 'postmaster@example.com')
- mlist = create_list('test@example.com')
- del mlist
- database.commit()
# Load all migrations, up to and including this one.
database.load_migrations('20120407000000')
# Verify that the database has been migrated.
@@ -134,3 +122,49 @@ class TestMigration20120407(unittest.TestCase):
self.assertRaises(sqlite3.OperationalError,
database.store.execute,
'select {0} from mailinglist;'.format(missing))
+
+ def test_data_after_migration(self):
+ # Ensure that the existing data and foreign key references are
+ # preserved across a migration. Unfortunately, this requires sample
+ # data, which kind of sucks.
+ dst = os.path.join(self._tempdir, 'mailman.db')
+ src = resource_filename('mailman.database.tests.data', 'mailman_01.db')
+ shutil.copyfile(src, dst)
+ url = 'sqlite:///' + dst
+ database_class = config.database['class']
+ database = call_name(database_class)
+ with configuration('database', url=url):
+ # Initialize the database and perform the migrations.
+ database.initialize()
+ database.load_migrations('20120407000000')
+ with temporary_db(database):
+ # Check that the domains survived the migration. This table
+ # was not touched so it should be fine.
+ domains = list(getUtility(IDomainManager))
+ self.assertEqual(len(domains), 1)
+ self.assertEqual(domains[0].mail_host, 'example.com')
+ # There should be exactly one mailing list defined.
+ mlists = list(getUtility(IListManager).mailing_lists)
+ self.assertEqual(len(mlists), 1)
+ # Get the mailing list object and check its acceptable
+ # aliases. This tests that foreign keys continue to work.
+ mlist = mlists[0]
+ aliases_set = IAcceptableAliasSet(mlist)
+ self.assertEqual(set(aliases_set.aliases),
+ set(['foo@example.com', 'bar@example.com']))
+ # Test that all the members we expect are still there. Start
+ # with the two list delivery members.
+ addresses = set(address.email
+ for address in mlist.members.addresses)
+ self.assertEqual(addresses, set(['anne@example.com',
+ 'bart@example.com']))
+ # There is one owner.
+ owners = set(address.email
+ for address in mlist.owners.addresses)
+ self.assertEqual(len(owners), 1)
+ self.assertEqual(owners.pop(), 'anne@example.com')
+ # There is one moderator.
+ moderators = set(address.email
+ for address in mlist.moderators.addresses)
+ self.assertEqual(len(moderators), 1)
+ self.assertEqual(moderators.pop(), 'bart@example.com')
diff --git a/src/mailman/rules/docs/news-moderation.rst b/src/mailman/rules/docs/news-moderation.rst
index 651c21bb4..0400c8d9f 100644
--- a/src/mailman/rules/docs/news-moderation.rst
+++ b/src/mailman/rules/docs/news-moderation.rst
@@ -32,6 +32,6 @@ And now all messages will match the rule.
When moderation is turned off, the rule does not match.
- >>> mlist.news_moderation = NewsgroupModeration.none
+ >>> mlist.newsgroup_moderation = NewsgroupModeration.none
>>> rule.check(mlist, msg, {})
False
diff --git a/src/mailman/rules/news_moderation.py b/src/mailman/rules/news_moderation.py
index bd6eb6166..be0d56cb4 100644
--- a/src/mailman/rules/news_moderation.py
+++ b/src/mailman/rules/news_moderation.py
@@ -46,4 +46,4 @@ class ModeratedNewsgroup:
def check(self, mlist, msg, msgdata):
"""See `IRule`."""
- return mlist.news_moderation == NewsgroupModeration.moderated
+ return mlist.newsgroup_moderation == NewsgroupModeration.moderated
diff --git a/src/mailman/testing/database.py b/src/mailman/testing/database.py
new file mode 100644
index 000000000..b442031fa
--- /dev/null
+++ b/src/mailman/testing/database.py
@@ -0,0 +1,47 @@
+# Copyright (C) 2012 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman 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 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman 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
+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+
+"""Database test helpers."""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'ResetHelper',
+ ]
+
+
+from mailman.model.version import Version
+
+
+
+class ResetHelper:
+ """Help with database resets; used by schema migrations."""
+
+ def __init__(self, version, store):
+ self.version = version
+ # Save the entry in the Version table for the test suite reset. This
+ # will be restored below.
+ result = store.find(Version, component=version).one()
+ self.saved = result.version
+
+ def restore(self, store):
+ # We need to preserve the Version table entry for this migration,
+ # since its existence defines the fact that the tables have been
+ # loaded.
+ store.add(Version(component='schema', version=self.version))
+ store.add(Version(component=self.version, version=self.saved))