From e9ab3f31504a176f483c8340b3ad2ba7679fe285 Mon Sep 17 00:00:00 2001 From: Abhilash Raj Date: Wed, 24 Sep 2014 23:13:12 +0530 Subject: added support for migrations via alembic --- src/mailman/testing/layers.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/mailman/testing') diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py index eb51e309f..3d2a50782 100644 --- a/src/mailman/testing/layers.py +++ b/src/mailman/testing/layers.py @@ -190,6 +190,10 @@ class ConfigLayer(MockAndMonkeyLayer): @classmethod def tearDown(cls): assert cls.var_dir is not None, 'Layer not set up' + # Reset the test database after the tests are done so that there + # is no data incase the tests are rerun with a database layer like + # mysql or postgresql which are not deleted in teardown. + reset_the_world() config.pop('test config') shutil.rmtree(cls.var_dir) cls.var_dir = None -- cgit v1.3.1 From 95fc64b4894e5985bb8d0e5e944b2cda38c9a58c Mon Sep 17 00:00:00 2001 From: Abhilash Raj Date: Thu, 25 Sep 2014 18:36:24 +0530 Subject: Add support for postgresql * revert changes in message_id_has encoding by barry * Change message_id_hash column to LargeBinary (from previously mistaken one i.e.unicode) * add missing import in database/types.py * fix a bug in database/Model.py, transaction has no method abort(), instead it is rollback() --- src/mailman/database/model.py | 2 +- src/mailman/database/postgresql.py | 9 ++++----- src/mailman/database/types.py | 3 ++- src/mailman/model/docs/messagestore.rst | 5 ++--- src/mailman/model/message.py | 4 ++-- src/mailman/model/messagestore.py | 10 +++++++--- src/mailman/model/pending.py | 2 +- src/mailman/styles/base.py | 4 ++-- src/mailman/testing/testing.cfg | 6 +++--- src/mailman/utilities/importer.py | 9 +++++++++ 10 files changed, 33 insertions(+), 21 deletions(-) (limited to 'src/mailman/testing') diff --git a/src/mailman/database/model.py b/src/mailman/database/model.py index 06705dfe9..b65d7d5eb 100644 --- a/src/mailman/database/model.py +++ b/src/mailman/database/model.py @@ -49,7 +49,7 @@ class ModelMeta: for table in reversed(Model.metadata.sorted_tables): connection.execute(table.delete()) except: - transaction.abort() + transaction.rollback() raise else: transaction.commit() diff --git a/src/mailman/database/postgresql.py b/src/mailman/database/postgresql.py index 59fff0865..ca1068302 100644 --- a/src/mailman/database/postgresql.py +++ b/src/mailman/database/postgresql.py @@ -40,15 +40,14 @@ class PostgreSQLDatabase(SABaseDatabase): restart from zero for new tests. """ super(PostgreSQLDatabase, self)._post_reset(store) - from mailman.database.model import ModelMeta - classes = sorted(ModelMeta._class_registry, - key=attrgetter('__storm_table__')) + from mailman.database.model import Model + tables = reversed(Model.metadata.sorted_tables) # Recipe adapted from # http://stackoverflow.com/questions/544791/ # django-postgresql-how-to-reset-primary-key - for model_class in classes: + for table in tables: store.execute("""\ SELECT setval('"{0}_id_seq"', coalesce(max("id"), 1), max("id") IS NOT null) FROM "{0}"; - """.format(model_class.__storm_table__)) + """.format(table)) diff --git a/src/mailman/database/types.py b/src/mailman/database/types.py index 641e065ba..eec6df6d5 100644 --- a/src/mailman/database/types.py +++ b/src/mailman/database/types.py @@ -30,6 +30,7 @@ import uuid from sqlalchemy import Integer from sqlalchemy.types import TypeDecorator, CHAR +from sqlalchemy.dialects import postgresql @@ -68,7 +69,7 @@ class UUID(TypeDecorator): def load_dialect_impl(self, dialect): if dialect.name == 'postgresql': - return dialect.type_descriptor(UUID()) + return dialect.type_descriptor(postgresql.UUID()) else: return dialect.type_descriptor(CHAR(32)) diff --git a/src/mailman/model/docs/messagestore.rst b/src/mailman/model/docs/messagestore.rst index f2f2ca9d2..bb853e575 100644 --- a/src/mailman/model/docs/messagestore.rst +++ b/src/mailman/model/docs/messagestore.rst @@ -28,9 +28,8 @@ header, you will get an exception. However, if the message has a ``Message-ID`` header, it can be stored. >>> msg['Message-ID'] = '<87myycy5eh.fsf@uwakimon.sk.tsukuba.ac.jp>' - >>> x_message_id_hash = message_store.add(msg) - >>> print(x_message_id_hash) - AGDWSNXXKCWEILKKNYTBOHRDQGOX3Y35 + >>> message_store.add(msg) + 'AGDWSNXXKCWEILKKNYTBOHRDQGOX3Y35' >>> print(msg.as_string()) Subject: An important message Message-ID: <87myycy5eh.fsf@uwakimon.sk.tsukuba.ac.jp> diff --git a/src/mailman/model/message.py b/src/mailman/model/message.py index 74a76ac30..c03d4bbd6 100644 --- a/src/mailman/model/message.py +++ b/src/mailman/model/message.py @@ -41,8 +41,8 @@ class Message(Model): id = Column(Integer, primary_key=True) message_id = Column(Unicode) - message_id_hash = Column(Unicode) - path = Column(LargeBinary) # TODO : was RawStr() + message_id_hash = Column(LargeBinary) + path = Column(LargeBinary) # This is a Messge-ID field representation, not a database row id. @dbconnection diff --git a/src/mailman/model/messagestore.py b/src/mailman/model/messagestore.py index 225d3d1ce..0b8a0ac78 100644 --- a/src/mailman/model/messagestore.py +++ b/src/mailman/model/messagestore.py @@ -66,7 +66,7 @@ class MessageStore: 'Message ID already exists in message store: {0}'.format( message_id)) shaobj = hashlib.sha1(message_id) - hash32 = base64.b32encode(shaobj.digest()).decode('ascii') + hash32 = base64.b32encode(shaobj.digest()) del message['X-Message-ID-Hash'] message['X-Message-ID-Hash'] = hash32 # Calculate the path on disk where we're going to store this message @@ -115,8 +115,12 @@ class MessageStore: @dbconnection def get_message_by_hash(self, store, message_id_hash): - if isinstance(message_id_hash, bytes): - message_id_hash = message_id_hash.decode('utf-8') + # It's possible the hash came from a message header, in which case it + # will be a Unicode. However when coming from source code, it may be + # an 8-string. Coerce to the latter if necessary; it must be + # US-ASCII. + if isinstance(message_id_hash, unicode): + message_id_hash = message_id_hash.encode('ascii') row = store.query(Message).filter_by( message_id_hash=message_id_hash).first() if row is None: diff --git a/src/mailman/model/pending.py b/src/mailman/model/pending.py index 68a8cd63e..691e94fd9 100644 --- a/src/mailman/model/pending.py +++ b/src/mailman/model/pending.py @@ -71,7 +71,7 @@ class Pended(Model): __tablename__ = 'pended' id = Column(Integer, primary_key=True) - token = Column(LargeBinary) # TODO : was RawStr() + token = Column(LargeBinary) expiration_date = Column(DateTime) key_values = relationship('PendedKeyValue') diff --git a/src/mailman/styles/base.py b/src/mailman/styles/base.py index d83b2e2a5..4f051c13c 100644 --- a/src/mailman/styles/base.py +++ b/src/mailman/styles/base.py @@ -67,9 +67,9 @@ class Identity: # Set this to Never if the list's preferred language uses us-ascii, # otherwise set it to As Needed. if mlist.preferred_language.charset == 'us-ascii': - mlist.encode_ascii_prefixes = 0 + mlist.encode_ascii_prefixes = False else: - mlist.encode_ascii_prefixes = 2 + mlist.encode_ascii_prefixes = True diff --git a/src/mailman/testing/testing.cfg b/src/mailman/testing/testing.cfg index fc76aa361..eb9de5513 100644 --- a/src/mailman/testing/testing.cfg +++ b/src/mailman/testing/testing.cfg @@ -18,9 +18,9 @@ # A testing configuration. # For testing against PostgreSQL. -# [database] -# class: mailman.database.postgresql.PostgreSQLDatabase -# url: postgres://barry:barry@localhost/mailman +[database] +class: mailman.database.postgresql.PostgreSQLDatabase +url: postgresql://maxking:maxking@localhost/mailman_test [mailman] site_owner: noreply@example.com diff --git a/src/mailman/utilities/importer.py b/src/mailman/utilities/importer.py index 26b7261e3..99531194f 100644 --- a/src/mailman/utilities/importer.py +++ b/src/mailman/utilities/importer.py @@ -128,6 +128,12 @@ def nonmember_action_mapping(value): }[value] +def int_to_bool(value): + if value: + return True + else: + return False + def check_language_code(code): if code is None: @@ -172,6 +178,9 @@ TYPES = dict( personalize=Personalization, preferred_language=check_language_code, reply_goes_to_list=ReplyToMunging, + allow_list_posts=int_to_bool, + include_rfc2369_headers=int_to_bool, + nntp_prefix_subject_too=int_to_bool, ) -- cgit v1.3.1 From 19850b6abd93a27fe22f80b20b8c7ba9bcb24205 Mon Sep 17 00:00:00 2001 From: Aurélien Bompard Date: Fri, 3 Oct 2014 17:08:11 +0200 Subject: New DB testing layer that does not auto-create the DB --- src/mailman/testing/layers.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'src/mailman/testing') diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py index 3d2a50782..304f9a891 100644 --- a/src/mailman/testing/layers.py +++ b/src/mailman/testing/layers.py @@ -47,6 +47,7 @@ import tempfile from lazr.config import as_boolean from pkg_resources import resource_string +from sqlalchemy import MetaData from textwrap import dedent from zope.component import getUtility @@ -54,6 +55,7 @@ from mailman.config import config from mailman.core import initialize from mailman.core.initialize import INHIBIT_CONFIG_FILE from mailman.core.logging import get_handler +from mailman.database.model import Model from mailman.database.transaction import transaction from mailman.interfaces.domain import IDomainManager from mailman.testing.helpers import ( @@ -309,6 +311,34 @@ class RESTLayer(SMTPLayer): cls.server = None + +class DatabaseLayer(MockAndMonkeyLayer): + """Layer for database tests""" + + @classmethod + def _drop_all_tables(cls): + Model.metadata.drop_all(config.db.engine) + md = MetaData() + md.reflect(bind=config.db.engine) + if "alembic_version" in md.tables: + md.tables["alembic_version"].drop(config.db.engine) + + @classmethod + def setUp(cls): + # Set up the basic configuration stuff. Turn off path creation. + config.create_paths = False + initialize.initialize_1(INHIBIT_CONFIG_FILE) + # Don't initialize the database. + + @classmethod + def tearDown(cls): + cls._drop_all_tables() + + @classmethod + def testTearDown(cls): + cls._drop_all_tables() + + def is_testing(): """Return a 'testing' flag for use with the predictable factories. -- cgit v1.3.1 From 061799ef5031977bd343bbe54a6ad809138bdb45 Mon Sep 17 00:00:00 2001 From: Abhilash Raj Date: Fri, 3 Oct 2014 21:56:50 +0530 Subject: add central alembic config --- src/mailman/commands/cli_migrate.py | 5 +---- src/mailman/config/alembic.cfg | 8 ++++---- src/mailman/database/alembic/__init__.py | 32 ++++++++++++++++++++++++++++++++ src/mailman/database/alembic/env.py | 17 ++++------------- src/mailman/database/base.py | 5 +---- src/mailman/database/factory.py | 8 +++----- src/mailman/testing/testing.cfg | 2 +- 7 files changed, 46 insertions(+), 31 deletions(-) (limited to 'src/mailman/testing') diff --git a/src/mailman/commands/cli_migrate.py b/src/mailman/commands/cli_migrate.py index ca9d5e9d4..82bf4a708 100644 --- a/src/mailman/commands/cli_migrate.py +++ b/src/mailman/commands/cli_migrate.py @@ -26,11 +26,11 @@ __all__ = [ from alembic import command -from alembic.config import Config from zope.interface import implementer from mailman.config import config from mailman.core.i18n import _ +from mailman.database.alembic import alembic_cfg from mailman.interfaces.command import ICLISubCommand from mailman.utilities.modules import expand_path @@ -54,9 +54,6 @@ class Migrate: help=('Produce less output.')) def process(self, args): - alembic_cfg = Config() - alembic_cfg.set_main_option( - 'script_location', expand_path(config.database['alembic_scripts'])) if args.autogenerate: command.revision(alembic_cfg, autogenerate=True) else: diff --git a/src/mailman/config/alembic.cfg b/src/mailman/config/alembic.cfg index 46ac90b61..b1f53a3d2 100644 --- a/src/mailman/config/alembic.cfg +++ b/src/mailman/config/alembic.cfg @@ -38,18 +38,18 @@ qualname = [logger_sqlalchemy] level = WARN -handlers = +handlers = console qualname = sqlalchemy.engine [logger_alembic] -level = INFO -handlers = +level = WARN +handlers = console qualname = alembic [handler_console] class = StreamHandler args = (sys.stderr,) -level = NOTSET +level = WARN formatter = generic [formatter_generic] diff --git a/src/mailman/database/alembic/__init__.py b/src/mailman/database/alembic/__init__.py index e69de29bb..73f30832e 100644 --- a/src/mailman/database/alembic/__init__.py +++ b/src/mailman/database/alembic/__init__.py @@ -0,0 +1,32 @@ +# Copyright (C) 2014 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 . + +"Alembic config init." + +from __future__ import absolute_import, print_function, unicode_literals + +__metaclass__ = type +__all__ = [ + 'alembic_cfg' +] + + +from alembic.config import Config +from mailman.utilities.modules import expand_path + + +alembic_cfg=Config(expand_path("python:mailman.config.alembic")) diff --git a/src/mailman/database/alembic/env.py b/src/mailman/database/alembic/env.py index 5e4f6bfdb..d1caec58d 100644 --- a/src/mailman/database/alembic/env.py +++ b/src/mailman/database/alembic/env.py @@ -27,17 +27,20 @@ __all__ = [ from alembic import context -from alembic.config import Config from contextlib import closing +from logging.config import fileConfig from sqlalchemy import create_engine from mailman.core import initialize from mailman.config import config +from mailman.database.alembic import alembic_cfg from mailman.database.model import Model from mailman.utilities.modules import expand_path from mailman.utilities.string import expand +fileConfig(alembic_cfg.config_file_name) + def run_migrations_offline(): """Run migrations in 'offline' mode. @@ -49,11 +52,6 @@ def run_migrations_offline(): Calls to context.execute() here emit the given string to the script output. """ - if not config.initialized: - initialize.initialize_1(context.config.config_file_name) - alembic_cfg= Config() - alembic_cfg.set_main_option( - "script_location", config.alembic['script_location']) url = expand(config.database.url, config.paths) context.configure(url=url, target_metadata=Model.metadata) with context.begin_transaction(): @@ -66,13 +64,6 @@ def run_migrations_online(): In this scenario we need to create an Engine and associate a connection with the context. """ - - if not config.initialized: - initialize.initialize_1(context.config.config_file_name) - alembic_cfg = Config() - alembic_cfg.set_main_option( - 'script_location', expand_path(config.database['alembic_scripts'])) - alembic_cfg.set_section_option('logger_alembic' ,'level' , 'ERROR') url = expand(config.database.url, config.paths) engine = create_engine(url) diff --git a/src/mailman/database/base.py b/src/mailman/database/base.py index 26b6ddbbb..dcaedade0 100644 --- a/src/mailman/database/base.py +++ b/src/mailman/database/base.py @@ -26,12 +26,12 @@ __all__ = [ import logging from alembic import command -from alembic.config import Config from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from zope.interface import implementer from mailman.config import config +from mailman.database.alembic import alembic_cfg from mailman.interfaces.database import IDatabase from mailman.utilities.string import expand @@ -97,9 +97,6 @@ class SABaseDatabase: # create_all() ceates the latest schema. This patches the database # with the latest Alembic version to add an entry in the # alembic_version table. - alembic_cfg = Config() - alembic_cfg.set_main_option( - 'script_location', config.database['alembic_scripts']) command.stamp(alembic_cfg, 'head') diff --git a/src/mailman/database/factory.py b/src/mailman/database/factory.py index 6111be8c5..469ed5d18 100644 --- a/src/mailman/database/factory.py +++ b/src/mailman/database/factory.py @@ -30,7 +30,6 @@ import os import types from alembic import command -from alembic.config import Config as AlembicConfig from alembic.migration import MigrationContext from alembic.script import ScriptDirectory from flufl.lock import Lock @@ -40,8 +39,9 @@ from zope.interface.verify import verifyObject from mailman.config import config from mailman.database.model import Model +from mailman.database.alembic import alembic_cfg from mailman.interfaces.database import IDatabase, IDatabaseFactory -from mailman.utilities.modules import call_name +from mailman.utilities.modules import call_name, expand_path @@ -70,9 +70,7 @@ class SchemaManager: def __init__(self, database): self.database = database - self.alembic_cfg = AlembicConfig() - self.alembic_cfg.set_main_option( - "script_location", config.alembic['script_location']) + self.alembic_cfg = alembic_cfg self.script = ScriptDirectory.from_config(self.alembic_cfg) def get_storm_schema_version(self): diff --git a/src/mailman/testing/testing.cfg b/src/mailman/testing/testing.cfg index fc76aa361..39d1d922a 100644 --- a/src/mailman/testing/testing.cfg +++ b/src/mailman/testing/testing.cfg @@ -20,7 +20,7 @@ # For testing against PostgreSQL. # [database] # class: mailman.database.postgresql.PostgreSQLDatabase -# url: postgres://barry:barry@localhost/mailman +# url: postgres://maxking:maxking@localhost/mailman_test [mailman] site_owner: noreply@example.com -- cgit v1.3.1 From cbbac03083357ca928d104d386d9e3008c937581 Mon Sep 17 00:00:00 2001 From: Aurélien Bompard Date: Mon, 6 Oct 2014 14:47:38 +0200 Subject: Fix DB unit tests --- src/mailman/database/tests/test_factory.py | 54 +++++++++++------------------- src/mailman/testing/layers.py | 38 ++++++++++++++++++--- 2 files changed, 53 insertions(+), 39 deletions(-) (limited to 'src/mailman/testing') diff --git a/src/mailman/database/tests/test_factory.py b/src/mailman/database/tests/test_factory.py index 7becdbfde..42a05fe1b 100644 --- a/src/mailman/database/tests/test_factory.py +++ b/src/mailman/database/tests/test_factory.py @@ -40,6 +40,7 @@ from mailman.database.model import Model class TestSchemaManager(unittest.TestCase): + layer = DatabaseLayer def setUp(self): @@ -55,23 +56,22 @@ class TestSchemaManager(unittest.TestCase): Model.metadata.remove(version) - def _db_is_setup(self): - md = MetaData() - md.reflect(bind=config.db.engine) - return "mailinglist" in md.tables and "alembic_version" in md.tables - def _table_exists(self, tablename): md = MetaData() md.reflect(bind=config.db.engine) return tablename in md.tables - def _create_storm_version_table(self): - table = Table("version", Model.metadata, - Column("id", Integer, primary_key=True), - Column("component", Unicode), - Column("version", Unicode), - ) - table.create(config.db.engine) + def _create_storm_version_table(self, revision): + version_table = Table("version", Model.metadata, + Column("id", Integer, primary_key=True), + Column("component", Unicode), + Column("version", Unicode), + ) + version_table.create(config.db.engine) + config.db.store.execute(version_table.insert().values( + component='schema', version=revision)) + config.db.commit() + def test_current_db(self): """The database is already at the latest version""" @@ -84,27 +84,19 @@ class TestSchemaManager(unittest.TestCase): def test_initial(self): """No existing database""" - self.assertFalse(self._table_exists("mailinglist") - and self._table_exists("alembic_version")) + self.assertFalse(self._table_exists("mailinglist")) + self.assertFalse(self._table_exists("alembic_version")) self.schema_mgr._upgrade = Mock() self.schema_mgr.setup_db() self.assertFalse(self.schema_mgr._upgrade.called) - self.assertTrue(self._table_exists("mailinglist") - and self._table_exists("alembic_version")) + self.assertTrue(self._table_exists("mailinglist")) + self.assertTrue(self._table_exists("alembic_version")) def test_storm(self): """Existing Storm database""" Model.metadata.create_all(config.db.engine) - version_table = Table("version", Model.metadata, - Column("id", Integer, primary_key=True), - Column("component", Unicode), - Column("version", Unicode), - ) - version_table.create(config.db.engine) - config.db.store.execute(version_table.insert().values( - component='schema', - version=self.schema_mgr.LAST_STORM_SCHEMA_VERSION)) - config.db.commit() + self._create_storm_version_table( + self.schema_mgr.LAST_STORM_SCHEMA_VERSION) self.schema_mgr._create = Mock() self.schema_mgr.setup_db() self.assertFalse(self.schema_mgr._create.called) @@ -115,15 +107,7 @@ class TestSchemaManager(unittest.TestCase): def test_old_storm(self): """Existing Storm database in an old version""" Model.metadata.create_all(config.db.engine) - version_table = Table("version", Model.metadata, - Column("id", Integer, primary_key=True), - Column("component", Unicode), - Column("version", Unicode), - ) - version_table.create(config.db.engine) - config.db.store.execute(version_table.insert().values( - component='schema', version='001')) - config.db.commit() + self._create_storm_version_table("001") self.schema_mgr._create = Mock() self.assertRaises(RuntimeError, self.schema_mgr.setup_db) self.assertFalse(self.schema_mgr._create.called) diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py index 304f9a891..68cb07f8c 100644 --- a/src/mailman/testing/layers.py +++ b/src/mailman/testing/layers.py @@ -49,6 +49,7 @@ from lazr.config import as_boolean from pkg_resources import resource_string from sqlalchemy import MetaData from textwrap import dedent +from zope import event from zope.component import getUtility from mailman.config import config @@ -100,7 +101,9 @@ class ConfigLayer(MockAndMonkeyLayer): # Set up the basic configuration stuff. Turn off path creation until # we've pushed the testing config. config.create_paths = False - initialize.initialize_1(INHIBIT_CONFIG_FILE) + if not event.subscribers: + # only if not yet initialized by another layer + initialize.initialize_1(INHIBIT_CONFIG_FILE) assert cls.var_dir is None, 'Layer already set up' # Calculate a temporary VAR_DIR directory so that run-time artifacts # of the tests won't tread on the installation's data. This also @@ -312,9 +315,11 @@ class RESTLayer(SMTPLayer): -class DatabaseLayer(MockAndMonkeyLayer): +class DatabaseLayer: """Layer for database tests""" + var_dir = None + @classmethod def _drop_all_tables(cls): Model.metadata.drop_all(config.db.engine) @@ -327,12 +332,37 @@ class DatabaseLayer(MockAndMonkeyLayer): def setUp(cls): # Set up the basic configuration stuff. Turn off path creation. config.create_paths = False - initialize.initialize_1(INHIBIT_CONFIG_FILE) + if not event.subscribers: + # only if not yet initialized by another layer + initialize.initialize_1(INHIBIT_CONFIG_FILE) # Don't initialize the database. + cls.var_dir = tempfile.mkdtemp() + test_config = dedent(""" + [mailman] + layout: testing + [paths.testing] + var_dir: {0} + [devmode] + testing: yes + """.format(cls.var_dir)) + # Read the testing config and push it. + test_config += resource_string('mailman.testing', 'testing.cfg') + config.create_paths = True + config.push('test config', test_config) + # Write the configuration file for subprocesses and set up the config + # object to pass that properly on the -C option. + config_file = os.path.join(cls.var_dir, 'test.cfg') + with open(config_file, 'w') as fp: + fp.write(test_config) + print(file=fp) + config.filename = config_file @classmethod def tearDown(cls): - cls._drop_all_tables() + assert cls.var_dir is not None, 'Layer not set up' + shutil.rmtree(cls.var_dir) + config.pop('test config') + cls.var_dir = None @classmethod def testTearDown(cls): -- cgit v1.3.1 From b023009538019927b5fe67f129469ee8d4951f91 Mon Sep 17 00:00:00 2001 From: Aurélien Bompard Date: Mon, 6 Oct 2014 19:17:50 +0200 Subject: Don't use a testing layer for database tests --- src/mailman/database/tests/test_factory.py | 15 +++++--- src/mailman/testing/layers.py | 55 ------------------------------ 2 files changed, 10 insertions(+), 60 deletions(-) (limited to 'src/mailman/testing') diff --git a/src/mailman/database/tests/test_factory.py b/src/mailman/database/tests/test_factory.py index 81febbde5..a87bca7be 100644 --- a/src/mailman/database/tests/test_factory.py +++ b/src/mailman/database/tests/test_factory.py @@ -32,7 +32,7 @@ from mock import Mock from sqlalchemy import MetaData, Table, Column, Integer, Unicode from mailman.config import config -from mailman.testing.layers import DatabaseLayer +from mailman.testing.layers import ConfigLayer from mailman.database.factory import SchemaManager, _reset from mailman.database.sqlite import SQLiteDatabase from mailman.database.alembic import alembic_cfg @@ -42,12 +42,15 @@ from mailman.database.model import Model class TestSchemaManager(unittest.TestCase): - layer = DatabaseLayer + layer = ConfigLayer def setUp(self): - config.db = SQLiteDatabase() - config.db.initialize() - config.db._reset = types.MethodType(_reset, config.db) + # Drop the existing database + Model.metadata.drop_all(config.db.engine) + md = MetaData() + md.reflect(bind=config.db.engine) + if "alembic_version" in md.tables: + md.tables["alembic_version"].drop(config.db.engine) self.schema_mgr = SchemaManager(config.db) def tearDown(self): @@ -55,6 +58,8 @@ class TestSchemaManager(unittest.TestCase): version = Model.metadata.tables["version"] version.drop(config.db.engine, checkfirst=True) Model.metadata.remove(version) + # Restore a virgin DB + Model.metadata.create_all(config.db.engine) def _table_exists(self, tablename): diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py index c1346b29f..99852e135 100644 --- a/src/mailman/testing/layers.py +++ b/src/mailman/testing/layers.py @@ -314,61 +314,6 @@ class RESTLayer(SMTPLayer): cls.server = None - -class DatabaseLayer: - """Layer for database tests""" - - var_dir = None - - @classmethod - def _drop_all_tables(cls): - Model.metadata.drop_all(config.db.engine) - md = MetaData() - md.reflect(bind=config.db.engine) - if "alembic_version" in md.tables: - md.tables["alembic_version"].drop(config.db.engine) - - @classmethod - def setUp(cls): - # Set up the basic configuration stuff. Turn off path creation. - config.create_paths = False - if not event.subscribers: - # only if not yet initialized by another layer - initialize.initialize_1(INHIBIT_CONFIG_FILE) - # Don't initialize the database. - cls.var_dir = tempfile.mkdtemp() - test_config = dedent(""" - [mailman] - layout: testing - [paths.testing] - var_dir: {0} - [devmode] - testing: yes - """.format(cls.var_dir)) - # Read the testing config and push it. - test_config += resource_string('mailman.testing', 'testing.cfg') - config.create_paths = True - config.push('test config', test_config) - # Write the configuration file for subprocesses and set up the config - # object to pass that properly on the -C option. - config_file = os.path.join(cls.var_dir, 'test.cfg') - with open(config_file, 'w') as fp: - fp.write(test_config) - print(file=fp) - config.filename = config_file - - @classmethod - def tearDown(cls): - assert cls.var_dir is not None, 'Layer not set up' - shutil.rmtree(cls.var_dir) - config.pop('test config') - cls.var_dir = None - - @classmethod - def testTearDown(cls): - cls._drop_all_tables() - - def is_testing(): """Return a 'testing' flag for use with the predictable factories. -- cgit v1.3.1 From 9cc31df3831298fcc1d698306a74cad6f82a1dc1 Mon Sep 17 00:00:00 2001 From: Abhilash Raj Date: Fri, 10 Oct 2014 10:29:43 +0530 Subject: * remove migrate command * remove alembic.cfg, move contents to schema.cfg * fix import errors in src/mailman/model/language.py * add indexes * change the previously wrong written tablename autoresponserecord * change alembic_cfg to use schema.cfg instead of alembic.cfg --- src/mailman/commands/cli_migrate.py | 62 ---------------------- src/mailman/commands/docs/conf.rst | 2 +- src/mailman/config/alembic.cfg | 21 -------- src/mailman/config/schema.cfg | 4 ++ src/mailman/database/alembic/__init__.py | 2 +- src/mailman/database/alembic/env.py | 3 ++ .../alembic/versions/429e08420177_initial.py | 29 ---------- .../alembic/versions/51b7f92bd06c_initial.py | 32 +++++++++++ src/mailman/model/address.py | 4 +- src/mailman/model/autorespond.py | 6 +-- src/mailman/model/language.py | 4 +- src/mailman/model/mailinglist.py | 12 +++-- src/mailman/model/mime.py | 2 +- src/mailman/model/pending.py | 2 +- src/mailman/model/requests.py | 4 +- src/mailman/model/uid.py | 2 +- src/mailman/model/user.py | 4 +- src/mailman/testing/testing.cfg | 6 +-- 18 files changed, 66 insertions(+), 135 deletions(-) delete mode 100644 src/mailman/commands/cli_migrate.py delete mode 100644 src/mailman/config/alembic.cfg delete mode 100644 src/mailman/database/alembic/versions/429e08420177_initial.py create mode 100644 src/mailman/database/alembic/versions/51b7f92bd06c_initial.py (limited to 'src/mailman/testing') diff --git a/src/mailman/commands/cli_migrate.py b/src/mailman/commands/cli_migrate.py deleted file mode 100644 index 82bf4a708..000000000 --- a/src/mailman/commands/cli_migrate.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright (C) 2010-2014 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 . - -"""bin/mailman migrate.""" - -from __future__ import absolute_import, print_function, unicode_literals - -__metaclass__ = type -__all__ = [ - 'Migrate', - ] - - -from alembic import command -from zope.interface import implementer - -from mailman.config import config -from mailman.core.i18n import _ -from mailman.database.alembic import alembic_cfg -from mailman.interfaces.command import ICLISubCommand -from mailman.utilities.modules import expand_path - - - -@implementer(ICLISubCommand) -class Migrate: - """Migrate the Mailman database to the latest schema.""" - - name = 'migrate' - - def add(self, parser, command_parser): - """See `ICLISubCommand`.""" - command_parser.add_argument( - '-a', '--autogenerate', - action='store_true', help=_("""\ - Autogenerate the migration script using Alembic.""")) - command_parser.add_argument( - '-q', '--quiet', - action='store_true', default=False, - help=('Produce less output.')) - - def process(self, args): - if args.autogenerate: - command.revision(alembic_cfg, autogenerate=True) - else: - command.upgrade(alembic_cfg, 'head') - if not args.quiet: - print('Updated the database schema.') diff --git a/src/mailman/commands/docs/conf.rst b/src/mailman/commands/docs/conf.rst index b3fef9db7..fb4c3eeed 100644 --- a/src/mailman/commands/docs/conf.rst +++ b/src/mailman/commands/docs/conf.rst @@ -22,7 +22,7 @@ To get a list of all key-value pairs of any section, you need to call the command without any options. >>> command.process(FakeArgs) - [logging.dbmigration] path: mailman.log + [alembic] script_location: mailman.database:alembic ... [passwords] password_length: 8 ... diff --git a/src/mailman/config/alembic.cfg b/src/mailman/config/alembic.cfg deleted file mode 100644 index 78c047379..000000000 --- a/src/mailman/config/alembic.cfg +++ /dev/null @@ -1,21 +0,0 @@ -# A generic, single database configuration. - -[alembic] -# path to migration scripts -script_location = mailman.database:alembic - -# template used to generate migration files -# file_template = %%(rev)s_%%(slug)s - -# max length of characters to apply to the -# "slug" field -#truncate_slug_length = 40 - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - -# set to 'true' to allow .pyc and .pyo files without -# a source .py file to be detected as revisions in the -# versions/ directory -# sourceless = false diff --git a/src/mailman/config/schema.cfg b/src/mailman/config/schema.cfg index 3316f54e0..3e628fa47 100644 --- a/src/mailman/config/schema.cfg +++ b/src/mailman/config/schema.cfg @@ -645,3 +645,7 @@ rewrite_duplicate_headers: CC X-Original-CC Content-Transfer-Encoding X-Original-Content-Transfer-Encoding MIME-Version X-MIME-Version + +[alembic] +# path to migration scripts +script_location = mailman.database:alembic diff --git a/src/mailman/database/alembic/__init__.py b/src/mailman/database/alembic/__init__.py index 73f30832e..a2f7418ba 100644 --- a/src/mailman/database/alembic/__init__.py +++ b/src/mailman/database/alembic/__init__.py @@ -29,4 +29,4 @@ from alembic.config import Config from mailman.utilities.modules import expand_path -alembic_cfg=Config(expand_path("python:mailman.config.alembic")) +alembic_cfg=Config(expand_path("python:mailman.config.schema")) diff --git a/src/mailman/database/alembic/env.py b/src/mailman/database/alembic/env.py index 56f673803..d1a1c557c 100644 --- a/src/mailman/database/alembic/env.py +++ b/src/mailman/database/alembic/env.py @@ -36,6 +36,9 @@ from mailman.database.alembic import alembic_cfg from mailman.database.model import Model from mailman.utilities.string import expand +if not config.initialized: + initialize.initialize_1(context.config.config_file_name) + def run_migrations_offline(): diff --git a/src/mailman/database/alembic/versions/429e08420177_initial.py b/src/mailman/database/alembic/versions/429e08420177_initial.py deleted file mode 100644 index e8d612676..000000000 --- a/src/mailman/database/alembic/versions/429e08420177_initial.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Initial migration - -This empty migration file makes sure there is always an alembic_version in the -database. As a consequence, if the DB version is reported as None, it means the -database needs to be created from scratch with SQLAlchemy itself. - -It also removes the "version" table left over from Storm (if it exists). - - -Revision ID: 429e08420177 -Revises: None -Create Date: 2014-10-02 10:18:17.333354 - -""" - -# revision identifiers, used by Alembic. -revision = '429e08420177' -down_revision = None - -from alembic import op -import sqlalchemy as sa - - -def upgrade(): - op.drop_table('version') - - -def downgrade(): - pass diff --git a/src/mailman/database/alembic/versions/51b7f92bd06c_initial.py b/src/mailman/database/alembic/versions/51b7f92bd06c_initial.py new file mode 100644 index 000000000..c5b3e01c5 --- /dev/null +++ b/src/mailman/database/alembic/versions/51b7f92bd06c_initial.py @@ -0,0 +1,32 @@ +"""initial + +Revision ID: 51b7f92bd06c +Revises: None +Create Date: 2014-10-10 09:53:35.624472 + +""" + +# revision identifiers, used by Alembic. +revision = '51b7f92bd06c' +down_revision = None + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.drop_table('version') + op.drop_column('mailinglist', 'acceptable_aliases_id') + op.create_index(op.f('ix_user__user_id'), 'user', ['_user_id'], unique=False) + op.drop_index('ix_user_user_id', table_name='user') + ### end Alembic commands ### + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.create_table('version') + op.create_index('ix_user_user_id', 'user', ['_user_id'], unique=False) + op.drop_index(op.f('ix_user__user_id'), table_name='user') + op.add_column('mailinglist', sa.Column('acceptable_aliases_id', sa.INTEGER(), nullable=True)) + ### end Alembic commands ### diff --git a/src/mailman/model/address.py b/src/mailman/model/address.py index 20bd631f5..cc4ab6fd0 100644 --- a/src/mailman/model/address.py +++ b/src/mailman/model/address.py @@ -53,9 +53,9 @@ class Address(Model): _verified_on = Column('verified_on', DateTime) registered_on = Column(DateTime) - user_id = Column(Integer, ForeignKey('user.id')) + user_id = Column(Integer, ForeignKey('user.id'), index=True) - preferences_id = Column(Integer, ForeignKey('preferences.id')) + preferences_id = Column(Integer, ForeignKey('preferences.id'), index=True) preferences = relationship( 'Preferences', backref=backref('address', uselist=False)) diff --git a/src/mailman/model/autorespond.py b/src/mailman/model/autorespond.py index c74434f7b..2293f5dcd 100644 --- a/src/mailman/model/autorespond.py +++ b/src/mailman/model/autorespond.py @@ -44,14 +44,14 @@ from mailman.utilities.datetime import today class AutoResponseRecord(Model): """See `IAutoResponseRecord`.""" - __tablename__ = 'autorespondrecord' + __tablename__ = 'autoresponserecord' id = Column(Integer, primary_key=True) - address_id = Column(Integer, ForeignKey('address.id')) + address_id = Column(Integer, ForeignKey('address.id'), index=True) address = relationship('Address') - mailing_list_id = Column(Integer, ForeignKey('mailinglist.id')) + mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'), index=True) mailing_list = relationship('MailingList') response_type = Column(Enum(Response)) diff --git a/src/mailman/model/language.py b/src/mailman/model/language.py index 15450c936..f4d48fc97 100644 --- a/src/mailman/model/language.py +++ b/src/mailman/model/language.py @@ -28,8 +28,8 @@ __all__ = [ from sqlalchemy import Column, Integer, Unicode from zope.interface import implementer -from mailman.database import Model -from mailman.interfaces import ILanguage +from mailman.database.model import Model +from mailman.interfaces.languages import ILanguage diff --git a/src/mailman/model/mailinglist.py b/src/mailman/model/mailinglist.py index d00cf3d31..fe84ff5b5 100644 --- a/src/mailman/model/mailinglist.py +++ b/src/mailman/model/mailinglist.py @@ -504,9 +504,12 @@ class AcceptableAlias(Model): id = Column(Integer, primary_key=True) - mailing_list_id = Column(Integer, ForeignKey('mailinglist.id')) + mailing_list_id = Column(Integer, + ForeignKey('mailinglist.id'), + index=True, + nullable=False) mailing_list = relationship('MailingList', backref='acceptable_alias') - alias = Column(Unicode) + alias = Column(Unicode, index=True, nullable=False) def __init__(self, mailing_list, alias): self.mailing_list = mailing_list @@ -558,9 +561,10 @@ class ListArchiver(Model): id = Column(Integer, primary_key=True) - mailing_list_id = Column(Integer, ForeignKey('mailinglist.id')) + mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'), + index=True, nullable=False) mailing_list = relationship('MailingList') - name = Column(Unicode) + name = Column(Unicode, nullable=False) _is_enabled = Column(Boolean) def __init__(self, mailing_list, archiver_name, system_archiver): diff --git a/src/mailman/model/mime.py b/src/mailman/model/mime.py index 906af91ea..dc6a54437 100644 --- a/src/mailman/model/mime.py +++ b/src/mailman/model/mime.py @@ -43,7 +43,7 @@ class ContentFilter(Model): id = Column(Integer, primary_key=True) - mailing_list_id = Column(Integer, ForeignKey('mailinglist.id')) + mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'), index=True) mailing_list = relationship('MailingList') filter_type = Column(Enum(FilterType)) diff --git a/src/mailman/model/pending.py b/src/mailman/model/pending.py index 691e94fd9..49b12c16a 100644 --- a/src/mailman/model/pending.py +++ b/src/mailman/model/pending.py @@ -56,7 +56,7 @@ class PendedKeyValue(Model): id = Column(Integer, primary_key=True) key = Column(Unicode) value = Column(Unicode) - pended_id = Column(Integer, ForeignKey('pended.id')) + pended_id = Column(Integer, ForeignKey('pended.id'), index=True) def __init__(self, key, value): self.key = key diff --git a/src/mailman/model/requests.py b/src/mailman/model/requests.py index 7f996dded..6b130196d 100644 --- a/src/mailman/model/requests.py +++ b/src/mailman/model/requests.py @@ -149,14 +149,14 @@ class ListRequests: class _Request(Model): """Table for mailing list hold requests.""" - __tablename__ = 'request' + __tablename__ = '_request' id = Column(Integer, primary_key=True) key = Column(Unicode) request_type = Column(Enum(RequestType)) data_hash = Column(LargeBinary) - mailing_list_id = Column(Integer, ForeignKey('mailinglist.id')) + mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'), index=True) mailing_list = relationship('MailingList') def __init__(self, key, request_type, mailing_list, data_hash): diff --git a/src/mailman/model/uid.py b/src/mailman/model/uid.py index 29d8e7021..72ddd7b5a 100644 --- a/src/mailman/model/uid.py +++ b/src/mailman/model/uid.py @@ -50,7 +50,7 @@ class UID(Model): __tablename__ = 'uid' id = Column(Integer, primary_key=True) - uid = Column(UUID) + uid = Column(UUID, index=True) @dbconnection def __init__(self, store, uid): diff --git a/src/mailman/model/user.py b/src/mailman/model/user.py index ffd52fdfb..5fe61ddd4 100644 --- a/src/mailman/model/user.py +++ b/src/mailman/model/user.py @@ -57,7 +57,7 @@ class User(Model): id = Column(Integer, primary_key=True) display_name = Column(Unicode) _password = Column('password', LargeBinary) # TODO : was RawStr() - _user_id = Column(UUID) + _user_id = Column(UUID, index=True) _created_on = Column(DateTime) addresses = relationship( @@ -74,7 +74,7 @@ class User(Model): 'Address', primaryjoin=(_preferred_address_id==Address.id), post_update=True) - preferences_id = Column(Integer, ForeignKey('preferences.id')) + preferences_id = Column(Integer, ForeignKey('preferences.id'), index=True) preferences = relationship( 'Preferences', backref=backref('user', uselist=False)) diff --git a/src/mailman/testing/testing.cfg b/src/mailman/testing/testing.cfg index eb9de5513..ea2df6988 100644 --- a/src/mailman/testing/testing.cfg +++ b/src/mailman/testing/testing.cfg @@ -18,9 +18,9 @@ # A testing configuration. # For testing against PostgreSQL. -[database] -class: mailman.database.postgresql.PostgreSQLDatabase -url: postgresql://maxking:maxking@localhost/mailman_test +# [database] +# class: mailman.database.postgresql.PostgreSQLDatabase +# url: postgresql://maxking:maxking@localhost/mailman_test [mailman] site_owner: noreply@example.com -- cgit v1.3.1 From 24e0cf6a5971454c21145d7af319fe5e15de7839 Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Sat, 11 Oct 2014 22:43:07 -0400 Subject: Merge Abhilash's latest revisions. --- MANIFEST.in | 5 +- src/mailman/commands/cli_migrate.py | 2 - src/mailman/config/config.py | 4 +- src/mailman/database/alembic/__init__.py | 8 +-- src/mailman/database/alembic/env.py | 3 +- .../alembic/versions/429e08420177_initial.py | 43 +++++++++--- src/mailman/database/base.py | 1 - src/mailman/database/factory.py | 79 +++++++++++----------- src/mailman/testing/testing.cfg | 2 +- 9 files changed, 81 insertions(+), 66 deletions(-) (limited to 'src/mailman/testing') diff --git a/MANIFEST.in b/MANIFEST.in index c119f141e..75488eb77 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include *.py *.rc +include *.py *.rc *.mako include COPYING recursive-include .buildout * recursive-include contrib * @@ -6,12 +6,9 @@ recursive-include cron * recursive-include data * global-include *.txt *.rst *.po *.mo *.cfg *.sql *.zcml *.html global-exclude *.egg-info -exclude MANIFEST.in prune src/attic prune src/web prune eggs prune parts include MANIFEST.in include src/mailman/testing/config.pck -include src/mailman/databases/alembic/scripts.py.mako -include src/mailman/databases/alembic/versions/*.py diff --git a/src/mailman/commands/cli_migrate.py b/src/mailman/commands/cli_migrate.py index 82bf4a708..d51aaa209 100644 --- a/src/mailman/commands/cli_migrate.py +++ b/src/mailman/commands/cli_migrate.py @@ -28,11 +28,9 @@ __all__ = [ from alembic import command from zope.interface import implementer -from mailman.config import config from mailman.core.i18n import _ from mailman.database.alembic import alembic_cfg from mailman.interfaces.command import ICLISubCommand -from mailman.utilities.modules import expand_path diff --git a/src/mailman/config/config.py b/src/mailman/config/config.py index 0ba88e089..d8b844f65 100644 --- a/src/mailman/config/config.py +++ b/src/mailman/config/config.py @@ -87,7 +87,7 @@ class Configuration: self.pipelines = {} self.commands = {} self.password_context = None - self.initialized = False + #self.initialized = False def _clear(self): """Clear the cached configuration variables.""" @@ -137,7 +137,7 @@ class Configuration: # Expand and set up all directories. self._expand_paths() self.ensure_directories_exist() - self.initialized = True + #self.initialized = True notify(ConfigurationUpdatedEvent(self)) def _expand_paths(self): diff --git a/src/mailman/database/alembic/__init__.py b/src/mailman/database/alembic/__init__.py index 73f30832e..ffd3af6df 100644 --- a/src/mailman/database/alembic/__init__.py +++ b/src/mailman/database/alembic/__init__.py @@ -15,18 +15,18 @@ # You should have received a copy of the GNU General Public License along with # GNU Mailman. If not, see . -"Alembic config init." +"""Alembic configuration initization.""" from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ - 'alembic_cfg' -] + 'alembic_cfg', + ] from alembic.config import Config from mailman.utilities.modules import expand_path -alembic_cfg=Config(expand_path("python:mailman.config.alembic")) +alembic_cfg = Config(expand_path('python:mailman.config.alembic')) diff --git a/src/mailman/database/alembic/env.py b/src/mailman/database/alembic/env.py index d1caec58d..1f4722038 100644 --- a/src/mailman/database/alembic/env.py +++ b/src/mailman/database/alembic/env.py @@ -31,16 +31,15 @@ from contextlib import closing from logging.config import fileConfig from sqlalchemy import create_engine -from mailman.core import initialize from mailman.config import config from mailman.database.alembic import alembic_cfg from mailman.database.model import Model -from mailman.utilities.modules import expand_path from mailman.utilities.string import expand fileConfig(alembic_cfg.config_file_name) + def run_migrations_offline(): """Run migrations in 'offline' mode. diff --git a/src/mailman/database/alembic/versions/429e08420177_initial.py b/src/mailman/database/alembic/versions/429e08420177_initial.py index e8d612676..14f22079b 100644 --- a/src/mailman/database/alembic/versions/429e08420177_initial.py +++ b/src/mailman/database/alembic/versions/429e08420177_initial.py @@ -1,24 +1,45 @@ -"""Initial migration - -This empty migration file makes sure there is always an alembic_version in the -database. As a consequence, if the DB version is reported as None, it means the -database needs to be created from scratch with SQLAlchemy itself. - -It also removes the "version" table left over from Storm (if it exists). - +# Copyright (C) 2014 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 . + +"""Initial migration. + +This empty migration file makes sure there is always an alembic_version +in the database. As a consequence, if the database version is reported +as None, it means the database needs to be created from scratch with +SQLAlchemy itself. + +It also removes the `version` table left over from Storm (if it exists). Revision ID: 429e08420177 Revises: None Create Date: 2014-10-02 10:18:17.333354 - """ -# revision identifiers, used by Alembic. +from __future__ import absolute_import, print_function, unicode_literals + +__metaclass__ = type +__all__ = [ + ] + +# Revision identifiers, used by Alembic. revision = '429e08420177' down_revision = None from alembic import op -import sqlalchemy as sa def upgrade(): diff --git a/src/mailman/database/base.py b/src/mailman/database/base.py index dcaedade0..2f69ed6ad 100644 --- a/src/mailman/database/base.py +++ b/src/mailman/database/base.py @@ -99,7 +99,6 @@ class SABaseDatabase: # alembic_version table. command.stamp(alembic_cfg, 'head') - def initialize(self, debug=None): """See `IDatabase`.""" # Calculate the engine url. diff --git a/src/mailman/database/factory.py b/src/mailman/database/factory.py index 469ed5d18..9cbe1088f 100644 --- a/src/mailman/database/factory.py +++ b/src/mailman/database/factory.py @@ -38,10 +38,14 @@ from zope.interface import implementer from zope.interface.verify import verifyObject from mailman.config import config -from mailman.database.model import Model from mailman.database.alembic import alembic_cfg -from mailman.interfaces.database import IDatabase, IDatabaseFactory -from mailman.utilities.modules import call_name, expand_path +from mailman.database.model import Model +from mailman.interfaces.database import ( + DatabaseError, IDatabase, IDatabaseFactory) +from mailman.utilities.modules import call_name + + +LAST_STORM_SCHEMA_VERSION = '20130406000000' @@ -57,58 +61,55 @@ class DatabaseFactory: database = call_name(database_class) verifyObject(IDatabase, database) database.initialize() - schema_mgr = SchemaManager(database) - schema_mgr.setup_db() + SchemaManager(database).setup_database() database.commit() return database class SchemaManager: - - LAST_STORM_SCHEMA_VERSION = '20130406000000' + "Manage schema migrations.""" def __init__(self, database): - self.database = database - self.alembic_cfg = alembic_cfg - self.script = ScriptDirectory.from_config(self.alembic_cfg) - - def get_storm_schema_version(self): - md = MetaData() - md.reflect(bind=self.database.engine) - if "version" not in md.tables: + self._database = database + self._script = ScriptDirectory.from_config(alembic_cfg) + + def _get_storm_schema_version(self): + metadata = MetaData() + metadata.reflect(bind=self._database.engine) + if 'version' not in metadata.tables: + # There are no Storm artifacts left. return None - Version = md.tables["version"] - last_version = self.database.store.query(Version.c.version).filter( - Version.c.component == "schema" - ).order_by(Version.c.version.desc()).first() + Version = metadata.tables['version'] + last_version = self._database.store.query(Version.c.version).filter( + Version.c.component == 'schema' + ).order_by(Version.c.version.desc()).first() return last_version - def setup_db(self): - context = MigrationContext.configure(self.database.store.connection()) + def setup_database(self): + context = MigrationContext.configure(self._database.store.connection()) current_rev = context.get_current_revision() - head_rev = self.script.get_current_head() + head_rev = self._script.get_current_head() if current_rev == head_rev: - return head_rev # already at the latest revision, nothing to do - if current_rev == None: - # no alembic information - storm_version = self.get_storm_schema_version() + # We're already at the latest revision so there's nothing to do. + return head_rev + if current_rev is None: + # No Alembic information is available. + storm_version = self._get_storm_schema_version() if storm_version is None: - # initial DB creation - Model.metadata.create_all(self.database.engine) - command.stamp(self.alembic_cfg, "head") + # Initial database creation. + Model.metadata.create_all(self._database.engine) + command.stamp(alembic_cfg, 'head') else: - # DB from a previous version managed by Storm - if storm_version.version < self.LAST_STORM_SCHEMA_VERSION: - raise RuntimeError( - "Upgrading while skipping beta version is " - "unsupported, please install the previous " - "Mailman beta release") - # Run migrations to remove the Storm-specific table and - # upgrade to SQLAlchemy & Alembic - command.upgrade(self.alembic_cfg, "head") + # The database was previously managed by Storm. + if storm_version.version < LAST_STORM_SCHEMA_VERSION: + raise DatabaseError( + 'Upgrades skipping beta versions is not supported.') + # Run migrations to remove the Storm-specific table and upgrade + # to SQLAlchemy and Alembic. + command.upgrade(alembic_cfg, 'head') elif current_rev != head_rev: - command.upgrade(self.alembic_cfg, "head") + command.upgrade(alembic_cfg, 'head') return head_rev diff --git a/src/mailman/testing/testing.cfg b/src/mailman/testing/testing.cfg index 39d1d922a..eba10dd3b 100644 --- a/src/mailman/testing/testing.cfg +++ b/src/mailman/testing/testing.cfg @@ -20,7 +20,7 @@ # For testing against PostgreSQL. # [database] # class: mailman.database.postgresql.PostgreSQLDatabase -# url: postgres://maxking:maxking@localhost/mailman_test +# url: postgres://$USER:$USER@localhost/mailman_test [mailman] site_owner: noreply@example.com -- cgit v1.3.1 From d20af910b287f8a7f13b51ee07c89e79af35f8a4 Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Mon, 13 Oct 2014 17:13:38 -0400 Subject: Remove some unnecessary code. --- src/mailman/testing/layers.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'src/mailman/testing') diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py index 38aa3907f..4a9841fc2 100644 --- a/src/mailman/testing/layers.py +++ b/src/mailman/testing/layers.py @@ -48,7 +48,6 @@ import tempfile from lazr.config import as_boolean from pkg_resources import resource_string from textwrap import dedent -from zope import event from zope.component import getUtility from mailman.config import config @@ -99,9 +98,7 @@ class ConfigLayer(MockAndMonkeyLayer): # Set up the basic configuration stuff. Turn off path creation until # we've pushed the testing config. config.create_paths = False - if not event.subscribers: - # Only if not yet initialized by another layer. - initialize.initialize_1(INHIBIT_CONFIG_FILE) + initialize.initialize_1(INHIBIT_CONFIG_FILE) assert cls.var_dir is None, 'Layer already set up' # Calculate a temporary VAR_DIR directory so that run-time artifacts # of the tests won't tread on the installation's data. This also -- cgit v1.3.1 From 4e19b227ab4c29e59801b38f12f80e1948817053 Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Thu, 30 Oct 2014 23:12:00 -0400 Subject: Merge abompard's fixes to the Postgres test suite. --- src/mailman/app/docs/moderator.rst | 77 +++++++++++++++------------------- src/mailman/commands/docs/withlist.rst | 4 +- src/mailman/database/base.py | 6 +++ src/mailman/interfaces/domain.py | 2 + src/mailman/interfaces/listmanager.py | 6 ++- src/mailman/model/docs/mailinglist.rst | 15 ++++--- src/mailman/model/domain.py | 4 +- src/mailman/model/listmanager.py | 3 +- src/mailman/rest/docs/moderation.rst | 32 +++++++------- src/mailman/testing/layers.py | 7 ++-- src/mailman/testing/testing.cfg | 6 +-- 11 files changed, 83 insertions(+), 79 deletions(-) (limited to 'src/mailman/testing') diff --git a/src/mailman/app/docs/moderator.rst b/src/mailman/app/docs/moderator.rst index ee9df8eb5..490c9630a 100644 --- a/src/mailman/app/docs/moderator.rst +++ b/src/mailman/app/docs/moderator.rst @@ -211,9 +211,9 @@ moderators. ... ... Here's something important about our mailing list. ... """) - >>> hold_message(mlist, msg, {}, 'Needs approval') - 2 - >>> handle_message(mlist, 2, Action.discard, forward=['zack@example.com']) + >>> req_id = hold_message(mlist, msg, {}, 'Needs approval') + >>> handle_message(mlist, req_id, Action.discard, + ... forward=['zack@example.com']) The forwarded message is in the virgin queue, destined for the moderator. :: @@ -243,10 +243,9 @@ choosing and their preferred language. >>> from mailman.app.moderator import hold_subscription >>> from mailman.interfaces.member import DeliveryMode - >>> hold_subscription(mlist, - ... 'fred@example.org', 'Fred Person', + >>> req_id = hold_subscription( + ... mlist, 'fred@example.org', 'Fred Person', ... '{NONE}abcxyz', DeliveryMode.regular, 'en') - 2 Disposing of membership change requests @@ -257,26 +256,27 @@ dispositions for this membership change request. The most trivial is to simply defer a decision for now. >>> from mailman.app.moderator import handle_subscription - >>> handle_subscription(mlist, 2, Action.defer) - >>> requests.get_request(2) is not None + >>> handle_subscription(mlist, req_id, Action.defer) + >>> requests.get_request(req_id) is not None True The held subscription can also be discarded. - >>> handle_subscription(mlist, 2, Action.discard) - >>> print(requests.get_request(2)) + >>> handle_subscription(mlist, req_id, Action.discard) + >>> print(requests.get_request(req_id)) None Gwen tries to subscribe to the mailing list, but... - >>> hold_subscription(mlist, - ... 'gwen@example.org', 'Gwen Person', + >>> req_id = hold_subscription( + ... mlist, 'gwen@example.org', 'Gwen Person', ... '{NONE}zyxcba', DeliveryMode.regular, 'en') - 2 + ...her request is rejected... - >>> handle_subscription(mlist, 2, Action.reject, 'This is a closed list') + >>> handle_subscription( + ... mlist, req_id, Action.reject, 'This is a closed list') >>> messages = get_queue_messages('virgin') >>> len(messages) 1 @@ -304,14 +304,13 @@ The subscription can also be accepted. This subscribes the address to the mailing list. >>> mlist.send_welcome_message = False - >>> hold_subscription(mlist, - ... 'herb@example.org', 'Herb Person', + >>> req_id = hold_subscription( + ... mlist, 'herb@example.org', 'Herb Person', ... 'abcxyz', DeliveryMode.regular, 'en') - 2 The moderators accept the subscription request. - >>> handle_subscription(mlist, 2, Action.accept) + >>> handle_subscription(mlist, req_id, Action.accept) And now Herb is a member of the mailing list. @@ -328,29 +327,27 @@ the unsubscribing address is required. Herb now wants to leave the mailing list, but his request must be approved. >>> from mailman.app.moderator import hold_unsubscription - >>> hold_unsubscription(mlist, 'herb@example.org') - 2 + >>> req_id = hold_unsubscription(mlist, 'herb@example.org') As with subscription requests, the unsubscription request can be deferred. >>> from mailman.app.moderator import handle_unsubscription - >>> handle_unsubscription(mlist, 2, Action.defer) + >>> handle_unsubscription(mlist, req_id, Action.defer) >>> print(mlist.members.get_member('herb@example.org').address) Herb Person The held unsubscription can also be discarded, and the member will remain subscribed. - >>> handle_unsubscription(mlist, 2, Action.discard) + >>> handle_unsubscription(mlist, req_id, Action.discard) >>> print(mlist.members.get_member('herb@example.org').address) Herb Person The request can be rejected, in which case a message is sent to the member, and the person remains a member of the mailing list. - >>> hold_unsubscription(mlist, 'herb@example.org') - 2 - >>> handle_unsubscription(mlist, 2, Action.reject, 'No can do') + >>> req_id = hold_unsubscription(mlist, 'herb@example.org') + >>> handle_unsubscription(mlist, req_id, Action.reject, 'No can do') >>> print(mlist.members.get_member('herb@example.org').address) Herb Person @@ -381,10 +378,9 @@ Herb gets a rejection notice. The unsubscription request can also be accepted. This removes the member from the mailing list. - >>> hold_unsubscription(mlist, 'herb@example.org') - 2 + >>> req_id = hold_unsubscription(mlist, 'herb@example.org') >>> mlist.send_goodbye_message = False - >>> handle_unsubscription(mlist, 2, Action.accept) + >>> handle_unsubscription(mlist, req_id, Action.accept) >>> print(mlist.members.get_member('herb@example.org')) None @@ -403,9 +399,8 @@ list is configured to send them. Iris tries to subscribe to the mailing list. - >>> hold_subscription(mlist, 'iris@example.org', 'Iris Person', + >>> req_id = hold_subscription(mlist, 'iris@example.org', 'Iris Person', ... 'password', DeliveryMode.regular, 'en') - 2 There's now a message in the virgin queue, destined for the list owner. @@ -429,8 +424,7 @@ There's now a message in the virgin queue, destined for the list owner. Similarly, the administrator gets notifications on unsubscription requests. Jeff is a member of the mailing list, and chooses to unsubscribe. - >>> hold_unsubscription(mlist, 'jeff@example.org') - 3 + >>> unsub_req_id = hold_unsubscription(mlist, 'jeff@example.org') >>> messages = get_queue_messages('virgin') >>> len(messages) 1 @@ -457,7 +451,7 @@ receive a membership change notice. >>> mlist.admin_notify_mchanges = True >>> mlist.admin_immed_notify = False - >>> handle_subscription(mlist, 2, Action.accept) + >>> handle_subscription(mlist, req_id, Action.accept) >>> messages = get_queue_messages('virgin') >>> len(messages) 1 @@ -474,9 +468,8 @@ receive a membership change notice. Similarly when an unsubscription request is accepted, the administrators can get a notification. - >>> hold_unsubscription(mlist, 'iris@example.org') - 4 - >>> handle_unsubscription(mlist, 4, Action.accept) + >>> req_id = hold_unsubscription(mlist, 'iris@example.org') + >>> handle_unsubscription(mlist, req_id, Action.accept) >>> messages = get_queue_messages('virgin') >>> len(messages) 1 @@ -498,10 +491,9 @@ can get a welcome message. >>> mlist.admin_notify_mchanges = False >>> mlist.send_welcome_message = True - >>> hold_subscription(mlist, 'kate@example.org', 'Kate Person', - ... 'password', DeliveryMode.regular, 'en') - 4 - >>> handle_subscription(mlist, 4, Action.accept) + >>> req_id = hold_subscription(mlist, 'kate@example.org', 'Kate Person', + ... 'password', DeliveryMode.regular, 'en') + >>> handle_subscription(mlist, req_id, Action.accept) >>> messages = get_queue_messages('virgin') >>> len(messages) 1 @@ -523,9 +515,8 @@ Similarly, when the member's unsubscription request is approved, she'll get a goodbye message. >>> mlist.send_goodbye_message = True - >>> hold_unsubscription(mlist, 'kate@example.org') - 4 - >>> handle_unsubscription(mlist, 4, Action.accept) + >>> req_id = hold_unsubscription(mlist, 'kate@example.org') + >>> handle_unsubscription(mlist, req_id, Action.accept) >>> messages = get_queue_messages('virgin') >>> len(messages) 1 diff --git a/src/mailman/commands/docs/withlist.rst b/src/mailman/commands/docs/withlist.rst index 827d246cd..e915eb04c 100644 --- a/src/mailman/commands/docs/withlist.rst +++ b/src/mailman/commands/docs/withlist.rst @@ -90,13 +90,13 @@ must start with a caret. >>> args.listname = '^.*example.com' >>> command.process(args) The list's display name is Aardvark - The list's display name is Badger The list's display name is Badboys + The list's display name is Badger >>> args.listname = '^bad.*' >>> command.process(args) - The list's display name is Badger The list's display name is Badboys + The list's display name is Badger >>> args.listname = '^foo' >>> command.process(args) diff --git a/src/mailman/database/base.py b/src/mailman/database/base.py index e360dcedf..beb9c260d 100644 --- a/src/mailman/database/base.py +++ b/src/mailman/database/base.py @@ -114,3 +114,9 @@ class SABaseDatabase: session = sessionmaker(bind=self.engine) self.store = session() self.store.commit() + + # XXX BAW Why doesn't model.py _reset() do this? + def destroy(self): + """Drop all database tables""" + from mailman.database.model import Model + Model.metadata.drop_all(self.engine) diff --git a/src/mailman/interfaces/domain.py b/src/mailman/interfaces/domain.py index e8610fd76..e7cb0d901 100644 --- a/src/mailman/interfaces/domain.py +++ b/src/mailman/interfaces/domain.py @@ -166,6 +166,8 @@ class IDomainManager(Interface): def __iter__(): """An iterator over all the domains. + Domains are returned sorted by `mail_host`. + :return: iterator over `IDomain`. """ diff --git a/src/mailman/interfaces/listmanager.py b/src/mailman/interfaces/listmanager.py index 22d7b3418..0c641fb91 100644 --- a/src/mailman/interfaces/listmanager.py +++ b/src/mailman/interfaces/listmanager.py @@ -130,8 +130,10 @@ class IListManager(Interface): """ mailing_lists = Attribute( - """An iterator over all the mailing list objects managed by this list - manager.""") + """An iterator over all the mailing list objects. + + The mailing lists are returned in order sorted by `list_id`. + """) def __iter__(): """An iterator over all the mailing lists. diff --git a/src/mailman/model/docs/mailinglist.rst b/src/mailman/model/docs/mailinglist.rst index 53ba99575..3d01710c5 100644 --- a/src/mailman/model/docs/mailinglist.rst +++ b/src/mailman/model/docs/mailinglist.rst @@ -50,7 +50,10 @@ receive a copy of any message sent to the mailing list. Both addresses appear on the roster of members. - >>> for member in mlist.members.members: + >>> from operator import attrgetter + >>> sort_key = attrgetter('address.email') + + >>> for member in sorted(mlist.members.members, key=sort_key): ... print(member) @@ -72,7 +75,7 @@ A Person is now both a member and an owner of the mailing list. C Person is an owner and a moderator. :: - >>> for member in mlist.owners.members: + >>> for member in sorted(mlist.owners.members, key=sort_key): ... print(member) @@ -87,13 +90,13 @@ All rosters can also be accessed indirectly. :: >>> roster = mlist.get_roster(MemberRole.member) - >>> for member in roster.members: + >>> for member in sorted(roster.members, key=sort_key): ... print(member) >>> roster = mlist.get_roster(MemberRole.owner) - >>> for member in roster.members: + >>> for member in sorted(roster.members, key=sort_key): ... print(member) @@ -122,7 +125,7 @@ just by changing their preferred address. >>> mlist.subscribe(user) on aardvark@example.com as MemberRole.member> - >>> for member in mlist.members.members: + >>> for member in sorted(mlist.members.members, key=sort_key): ... print(member) @@ -133,7 +136,7 @@ just by changing their preferred address. >>> new_address.verified_on = now() >>> user.preferred_address = new_address - >>> for member in mlist.members.members: + >>> for member in sorted(mlist.members.members, key=sort_key): ... print(member) diff --git a/src/mailman/model/domain.py b/src/mailman/model/domain.py index 083e1cf51..ef8b1f761 100644 --- a/src/mailman/model/domain.py +++ b/src/mailman/model/domain.py @@ -48,7 +48,7 @@ class Domain(Model): id = Column(Integer, primary_key=True) - mail_host = Column(Unicode) + mail_host = Column(Unicode) # TODO: add index? base_url = Column(Unicode) description = Column(Unicode) contact_address = Column(Unicode) @@ -170,7 +170,7 @@ class DomainManager: @dbconnection def __iter__(self, store): """See `IDomainManager`.""" - for domain in store.query(Domain).all(): + for domain in store.query(Domain).order_by(Domain.mail_host).all(): yield domain @dbconnection diff --git a/src/mailman/model/listmanager.py b/src/mailman/model/listmanager.py index 43a2b8f2a..261490a92 100644 --- a/src/mailman/model/listmanager.py +++ b/src/mailman/model/listmanager.py @@ -86,7 +86,8 @@ class ListManager: @dbconnection def mailing_lists(self, store): """See `IListManager`.""" - for mlist in store.query(MailingList).all(): + for mlist in store.query(MailingList).order_by( + MailingList._list_id).all(): yield mlist @dbconnection diff --git a/src/mailman/rest/docs/moderation.rst b/src/mailman/rest/docs/moderation.rst index 44182eb23..6e2dbb43c 100644 --- a/src/mailman/rest/docs/moderation.rst +++ b/src/mailman/rest/docs/moderation.rst @@ -226,10 +226,9 @@ moderator approval. >>> from mailman.app.moderator import hold_subscription >>> from mailman.interfaces.member import DeliveryMode - >>> hold_subscription( + >>> sub_req_id = hold_subscription( ... ant, 'anne@example.com', 'Anne Person', ... 'password', DeliveryMode.regular, 'en') - 1 >>> transaction.commit() The subscription request is available from the mailing list. @@ -242,7 +241,7 @@ The subscription request is available from the mailing list. http_etag: "..." language: en password: password - request_id: 1 + request_id: ... type: subscription when: 2005-08-01T07:49:23 http_etag: "..." @@ -259,8 +258,7 @@ Bart tries to leave a mailing list, but he may not be allowed to. >>> from mailman.app.moderator import hold_unsubscription >>> bart = add_member(ant, 'bart@example.com', 'Bart Person', ... 'password', DeliveryMode.regular, 'en') - >>> hold_unsubscription(ant, 'bart@example.com') - 2 + >>> unsub_req_id = hold_unsubscription(ant, 'bart@example.com') >>> transaction.commit() The unsubscription request is also available from the mailing list. @@ -273,13 +271,13 @@ The unsubscription request is also available from the mailing list. http_etag: "..." language: en password: password - request_id: 1 + request_id: ... type: subscription when: 2005-08-01T07:49:23 entry 1: address: bart@example.com http_etag: "..." - request_id: 2 + request_id: ... type: unsubscription http_etag: "..." start: 0 @@ -292,23 +290,25 @@ Viewing individual requests You can view an individual membership change request by providing the request id. Anne's subscription request looks like this. - >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/requests/1') + >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/' + ... 'requests/{}'.format(sub_req_id)) address: anne@example.com delivery_mode: regular display_name: Anne Person http_etag: "..." language: en password: password - request_id: 1 + request_id: ... type: subscription when: 2005-08-01T07:49:23 Bart's unsubscription request looks like this. - >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/requests/2') + >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/' + ... 'requests/{}'.format(unsub_req_id)) address: bart@example.com http_etag: "..." - request_id: 2 + request_id: ... type: unsubscription @@ -328,9 +328,8 @@ data requires an action of one of the following: Anne's subscription request is accepted. >>> dump_json('http://localhost:9001/3.0/lists/' - ... 'ant@example.com/requests/1', { - ... 'action': 'accept', - ... }) + ... 'ant@example.com/requests/{}'.format(sub_req_id), + ... {'action': 'accept'}) content-length: 0 date: ... server: ... @@ -347,9 +346,8 @@ Anne is now a member of the mailing list. Bart's unsubscription request is discarded. >>> dump_json('http://localhost:9001/3.0/lists/' - ... 'ant@example.com/requests/2', { - ... 'action': 'discard', - ... }) + ... 'ant@example.com/requests/{}'.format(unsub_req_id), + ... {'action': 'discard'}) content-length: 0 date: ... server: ... diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py index 4a9841fc2..6104e64f7 100644 --- a/src/mailman/testing/layers.py +++ b/src/mailman/testing/layers.py @@ -190,10 +190,11 @@ class ConfigLayer(MockAndMonkeyLayer): @classmethod def tearDown(cls): assert cls.var_dir is not None, 'Layer not set up' - # Reset the test database after the tests are done so that there is no - # data in case the tests are rerun with a database layer like mysql or - # postgresql which are not deleted in teardown. reset_the_world() + # Destroy the test database after the tests are done so that there is + # no data in case the tests are rerun with a database layer like mysql + # or postgresql which are not deleted in teardown. + config.db.destroy() config.pop('test config') shutil.rmtree(cls.var_dir) cls.var_dir = None diff --git a/src/mailman/testing/testing.cfg b/src/mailman/testing/testing.cfg index 43ae6e67e..ff2034069 100644 --- a/src/mailman/testing/testing.cfg +++ b/src/mailman/testing/testing.cfg @@ -18,9 +18,9 @@ # A testing configuration. # For testing against PostgreSQL. -# [database] -# class: mailman.database.postgresql.PostgreSQLDatabase -# url: postgresql://$USER:$USER@localhost/mailman_test +[database] +class: mailman.database.postgresql.PostgreSQLDatabase +url: postgresql://barry:barry@localhost:5433/mailman [mailman] site_owner: noreply@example.com -- cgit v1.3.1 From 98ddb4d8ec662bcff039d60b4b5afd4279231b97 Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Fri, 31 Oct 2014 23:35:02 -0400 Subject: Merge in the last of Aurelien's changes, and make the test suite pass with PostgreSQL. --- src/mailman/config/config.py | 2 +- src/mailman/core/docs/runner.rst | 4 ++++ src/mailman/interfaces/domain.py | 5 ++++- src/mailman/model/domain.py | 3 ++- src/mailman/testing/testing.cfg | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) (limited to 'src/mailman/testing') diff --git a/src/mailman/config/config.py b/src/mailman/config/config.py index 52cac414f..649d6c5e1 100644 --- a/src/mailman/config/config.py +++ b/src/mailman/config/config.py @@ -141,7 +141,7 @@ class Configuration: def _expand_paths(self): """Expand all configuration paths.""" # Set up directories. - bin_dir = os.path.abspath(os.path.dirname(sys.argv[0])) + bin_dir = os.path.abspath(os.path.dirname(sys.executable)) # Now that we've loaded all the configuration files we're going to # load, set up some useful directories based on the settings in the # configuration file. diff --git a/src/mailman/core/docs/runner.rst b/src/mailman/core/docs/runner.rst index 28eab9203..e9fd21c57 100644 --- a/src/mailman/core/docs/runner.rst +++ b/src/mailman/core/docs/runner.rst @@ -73,3 +73,7 @@ on instance variables. version : 3 XXX More of the Runner API should be tested. + +.. + Clean up. + >>> config.pop('test-runner') diff --git a/src/mailman/interfaces/domain.py b/src/mailman/interfaces/domain.py index e7cb0d901..d51b1a702 100644 --- a/src/mailman/interfaces/domain.py +++ b/src/mailman/interfaces/domain.py @@ -97,7 +97,10 @@ class IDomain(Interface): E.g. postmaster@example.com""") mailing_lists = Attribute( - 'All mailing lists for this domain.') + """All mailing lists for this domain. + + The mailing lists are returned in order sorted by list-id. + """) def confirm_url(token=''): """The url used for various forms of confirmation. diff --git a/src/mailman/model/domain.py b/src/mailman/model/domain.py index ef8b1f761..8290cb755 100644 --- a/src/mailman/model/domain.py +++ b/src/mailman/model/domain.py @@ -95,7 +95,8 @@ class Domain(Model): def mailing_lists(self, store): """See `IDomain`.""" mailing_lists = store.query(MailingList).filter( - MailingList.mail_host == self.mail_host) + MailingList.mail_host == self.mail_host + ).order_by(MailingList._list_id) for mlist in mailing_lists: yield mlist diff --git a/src/mailman/testing/testing.cfg b/src/mailman/testing/testing.cfg index ff2034069..12646c680 100644 --- a/src/mailman/testing/testing.cfg +++ b/src/mailman/testing/testing.cfg @@ -20,7 +20,7 @@ # For testing against PostgreSQL. [database] class: mailman.database.postgresql.PostgreSQLDatabase -url: postgresql://barry:barry@localhost:5433/mailman +url: postgresql://barry:barry@localhost:5432/mailman [mailman] site_owner: noreply@example.com -- cgit v1.3.1 From fb38e482aa42edd4032a23e7c1f727066991fa62 Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Fri, 31 Oct 2014 23:48:13 -0400 Subject: SQLite by default --- src/mailman/testing/testing.cfg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/mailman/testing') diff --git a/src/mailman/testing/testing.cfg b/src/mailman/testing/testing.cfg index 12646c680..b61b36604 100644 --- a/src/mailman/testing/testing.cfg +++ b/src/mailman/testing/testing.cfg @@ -18,9 +18,9 @@ # A testing configuration. # For testing against PostgreSQL. -[database] -class: mailman.database.postgresql.PostgreSQLDatabase -url: postgresql://barry:barry@localhost:5432/mailman +#[database] +#class: mailman.database.postgresql.PostgreSQLDatabase +#url: postgresql://barry:barry@localhost:5432/mailman [mailman] site_owner: noreply@example.com -- cgit v1.3.1 From 1d9f6970b9a26ee576838b53f485b96365e3a6c2 Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Sat, 1 Nov 2014 13:46:36 -0400 Subject: Remove some unnecessary code, and revert back to SQLite by default for the test suite. --- src/mailman/database/base.py | 6 ------ src/mailman/testing/layers.py | 3 --- src/mailman/testing/testing.cfg | 6 +++--- 3 files changed, 3 insertions(+), 12 deletions(-) (limited to 'src/mailman/testing') diff --git a/src/mailman/database/base.py b/src/mailman/database/base.py index 2b86899bc..55edf6005 100644 --- a/src/mailman/database/base.py +++ b/src/mailman/database/base.py @@ -114,9 +114,3 @@ class SABaseDatabase: session = sessionmaker(bind=self.engine) self.store = session() self.store.commit() - - # XXX BAW Why doesn't model.py _reset() do this? - def destroy(self): - """Drop all database tables""" - from mailman.database.model import Model - Model.metadata.drop_all(self.engine) diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py index 006feef9c..74ad99dc8 100644 --- a/src/mailman/testing/layers.py +++ b/src/mailman/testing/layers.py @@ -194,9 +194,6 @@ class ConfigLayer(MockAndMonkeyLayer): # Destroy the test database after the tests are done so that there is # no data in case the tests are rerun with a database layer like mysql # or postgresql which are not deleted in teardown. - # - # XXX 2014-11-01 BAW: Shouldn't reset_the_world() take care of this? - config.db.destroy() config.pop('test config') shutil.rmtree(cls.var_dir) cls.var_dir = None diff --git a/src/mailman/testing/testing.cfg b/src/mailman/testing/testing.cfg index 12646c680..b61b36604 100644 --- a/src/mailman/testing/testing.cfg +++ b/src/mailman/testing/testing.cfg @@ -18,9 +18,9 @@ # A testing configuration. # For testing against PostgreSQL. -[database] -class: mailman.database.postgresql.PostgreSQLDatabase -url: postgresql://barry:barry@localhost:5432/mailman +#[database] +#class: mailman.database.postgresql.PostgreSQLDatabase +#url: postgresql://barry:barry@localhost:5432/mailman [mailman] site_owner: noreply@example.com -- cgit v1.3.1