diff options
| -rw-r--r-- | src/mailman/database/base.py | 2 | ||||
| -rw-r--r-- | src/mailman/database/docs/migration.rst | 53 | ||||
| -rw-r--r-- | src/mailman/database/schema/mm_00000000000000_base.py | 21 | ||||
| -rw-r--r-- | src/mailman/database/schema/mm_20120407000000.py | 18 | ||||
| -rw-r--r-- | src/mailman/database/tests/data/__init__.py | 0 | ||||
| -rw-r--r-- | src/mailman/database/tests/data/mailman_01.db | bin | 0 -> 48128 bytes | |||
| -rw-r--r-- | src/mailman/database/tests/test_migrations.py | 72 | ||||
| -rw-r--r-- | src/mailman/rules/docs/news-moderation.rst | 2 | ||||
| -rw-r--r-- | src/mailman/rules/news_moderation.py | 2 | ||||
| -rw-r--r-- | src/mailman/testing/database.py | 47 |
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 Binary files differnew file mode 100644 index 000000000..1ff8d8343 --- /dev/null +++ b/src/mailman/database/tests/data/mailman_01.db 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)) |
