You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@bloodhound.apache.org by Ryan Ollos <ry...@wandisco.com> on 2013/07/16 08:44:57 UTC

Re: svn commit: r1476118 - in /bloodhound/trunk/bloodhound_multiproduct: multiproduct/api.py tests/upgrade.py

On Fri, Apr 26, 2013 at 2:10 AM, <as...@apache.org> wrote:

> Author: astaric
> Date: Fri Apr 26 09:10:14 2013
> New Revision: 1476118
>
> URL: http://svn.apache.org/r1476118
> Log:
> Added more tests for multiproduct upgrade, refactoring.
>
> Modified:
>     bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py
>     bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py
>
> Modified: bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py
> URL:
> http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py?rev=1476118&r1=1476117&r2=1476118&view=diff
>
> ==============================================================================
> --- bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py (original)
> +++ bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py Fri Apr
> 26 09:10:14 2013
> @@ -30,6 +30,7 @@ from trac.attachment import Attachment
>  from trac.config import Option, PathOption
>  from trac.core import Component, TracError, implements, Interface
>  from trac.db import Table, Column, DatabaseManager, Index
> +import trac.db_default
>  from trac.env import IEnvironmentSetupParticipant, Environment
>  from trac.perm import IPermissionRequestor, PermissionCache
>  from trac.resource import IExternalResourceConnector,
> IResourceChangeListener,\
> @@ -97,7 +98,7 @@ class MultiProductSystem(Component):
>          global environment configuration.
>          """)
>
> -    SCHEMA = [mcls._get_schema() \
> +    SCHEMA = [mcls._get_schema()
>                for mcls in (Product, ProductResourceMap)]
>
>      # Tables which should be migrated (extended with 'product' column)
> @@ -202,171 +203,207 @@ class MultiProductSystem(Component):
>          db_installed_version = self.get_version()
>          with self.env.db_direct_transaction as db:
>              if db_installed_version < 1:
> -                # Initial installation
> -                db("ALTER TABLE ticket ADD COLUMN product TEXT")
> -                self.log.debug("creating initial db tables for %s
> plugin." %
> -                               PLUGIN_NAME)
> -                db_connector, dummy =
> DatabaseManager(self.env)._get_connector()
> -                for table in self.SCHEMA:
> -                    for statement in db_connector.to_sql(table):
> -                        db(statement)
> +                self._add_column_product_to_ticket(db)
> +                self._create_multiproduct_tables(db)
>                  db_installed_version = self._update_db_version(db, 1)
>
>              if db_installed_version < 2:
> -                from multiproduct.model import Product
> -                products = Product.select(self.env)
> -                for prod in products:
> -                    db("""UPDATE ticket SET product=%s
> -                          WHERE product=%s""", (prod.prefix, prod.name))
> +                self._replace_product_on_ticket_with_product_prefix(db)
>                  db_installed_version = self._update_db_version(db, 2)
>
>              if db_installed_version < 3:
> -                from multiproduct.model import Product
> -                import trac.db_default
> -
> -                def create_temp_table(table):
> -                    """creates temporary table with the new schema and
> -                    drops original table"""
> -                    table_temp_name = '%s_temp' % table
> -                    if table == 'report':
> -                        cols = ','.join([c for c in table_columns[table]
> if c != 'id'])
> -                    else:
> -                        cols = ','.join(table_columns[table])
> -                    self.log.info("Migrating table '%s' to a new
> schema", table)
> -                    db("""CREATE TABLE %s AS SELECT %s FROM %s""" %
> -                          (table_temp_name, cols, table))
> -                    db("""DROP TABLE %s""" % table)
> -                    db_connector, _ =
> DatabaseManager(self.env)._get_connector()
> -                    table_schema = [t for t in table_defs if t.name ==
> table][0]
> -                    for sql in db_connector.to_sql(table_schema):
> -                        db(sql)
> -                    return table_temp_name, cols
> -
> -                def drop_temp_table(table):
> -                    """drops specified temporary table"""
> -                    db("""DROP TABLE %s""" % table)
> -
> -                TICKET_TABLES = ['ticket_change', 'ticket_custom',
> -                                 'attachment',
> -                                ]
>                  SYSTEM_TABLES = ['system']
> +                TICKET_TABLES = [
> +                    'ticket_change', 'ticket_custom', 'attachment',
> +                ]
> +                table_defs = self._add_product_column_to_tables(
> +                    self.MIGRATE_TABLES + TICKET_TABLES + SYSTEM_TABLES,
> +                    db_installed_version)
> +                table_columns = self._get_table_columns(table_defs)
> +                create_temp_table = lambda table: self._create_temp_table(
> +                    db, table, table_columns, table_defs)
> +
> +                self._insert_default_product(db)
> +                self._upgrade_tickets(db, TICKET_TABLES,
> create_temp_table)
> +                self._upgrade_wikis(db, create_temp_table, table_columns)
> +                self._upgrade_system_tables(db, create_temp_table)
> +                self._soft_link_repositories_to_default_product(db)
> +                self._upgrade_table_system(SYSTEM_TABLES,
> create_temp_table, db)
> +                self._enable_multiproduct_hooks()
>
> -                # extend trac default schema by adding product column and
> extending key with product
> -                table_defs = [copy.deepcopy(t) for t in
> trac.db_default.schema
> -                                                    if t.name in
> self.MIGRATE_TABLES + TICKET_TABLES + SYSTEM_TABLES]
> -                for t in table_defs:
> -                    t.columns.append(Column('product'))
> -                    if isinstance(t.key, list):
> -                        t.key = tuple(t.key) + tuple(['product'])
> -                    elif isinstance(t.key, tuple):
> -                        t.key = t.key + tuple(['product'])
> -                    else:
> -                        raise TracError("Invalid table '%s' schema key
> '%s' while upgrading "
> -                                        "plugin '%s' from version %d to
> %d'" %
> -                                        (t.name, t.key, PLUGIN_NAME,
> db_installed_version, 3))
> -                table_columns = dict()
> -                for table in table_defs:
> -                    table_columns[table.name] = [c for c in [column.namefor column in
> -                                                                [t for t
> in table_defs if t.name == table.name][0].columns]
> -                                                                    if c
> != 'product']
> -                # create default product
> -                self.log.info("Creating default product")
> -                db("""INSERT INTO bloodhound_product (prefix, name,
> description, owner)
> -                      VALUES ('%s', '%s', '%s', '')""" %
> -                      (DEFAULT_PRODUCT, 'Default', 'Default product'))
> -
> -                # fetch all products
> -                all_products = Product.select(self.env)
> -
> -                # migrate tickets that don't have product assigned to
> default product
> -                # - update ticket table product column
> -                # - update ticket related tables by:
> -                #   - upgrading schema
> -                #   - update product column to match ticket's product
> -                self.log.info("Migrating tickets w/o product to default
> product")
> -                db("""UPDATE ticket SET product='%s'
> -                      WHERE (product IS NULL OR product='')""" %
> DEFAULT_PRODUCT)
> -                self._migrate_attachments(
> -                    db("""SELECT a.type, a.id, a.filename
> +                db_installed_version = self._update_db_version(db, 3)
> +
> +            if db_installed_version < 4:
> +                self._create_product_tables_for_plugins(db)
> +                db_installed_version = self._update_db_version(db, 4)
> +
> +            self.env.enable_multiproduct_schema(True)
> +
> +    def _add_column_product_to_ticket(self, db):
> +        self.log.debug("Adding field product to ticket table")
> +        db("ALTER TABLE ticket ADD COLUMN product TEXT")
> +
> +    def _create_multiproduct_tables(self, db):
> +        self.log.debug("Creating initial db tables for %s plugin." %
> +                       PLUGIN_NAME)
> +        db_connector, dummy = DatabaseManager(self.env)._get_connector()
> +        for table in self.SCHEMA:
> +            for statement in db_connector.to_sql(table):
> +                db(statement)
> +
> +    def _replace_product_on_ticket_with_product_prefix(self, db):
> +        for prod in Product.select(self.env):
> +            db("""UPDATE ticket SET product=%s
> +                          WHERE product=%s""", (prod.prefix, prod.name))
> +
> +    def _create_temp_table(self, db, table, table_columns, table_defs):
> +        """creates temporary table with the new schema and
> +        drops original table"""
> +        table_temp_name = '%s_temp' % table
> +        if table == 'report':
> +            cols = ','.join([c for c in table_columns[table] if c !=
> 'id'])
> +        else:
> +            cols = ','.join(table_columns[table])
> +        self.log.info("Migrating table '%s' to a new schema", table)
> +        db("""CREATE TABLE %s AS SELECT %s FROM %s""" %
> +              (table_temp_name, cols, table))
> +        db("""DROP TABLE %s""" % table)
> +        db_connector, _ = DatabaseManager(self.env)._get_connector()
> +        table_schema = [t for t in table_defs if t.name == table][0]
> +        for sql in db_connector.to_sql(table_schema):
> +            db(sql)
> +        return table_temp_name, cols
> +
> +    def _drop_temp_table(self, db, table):
> +        db("""DROP TABLE %s""" % table)
> +
> +    def _add_product_column_to_tables(self, tables, current_version):
> +        """Extend trac default schema by adding product column
> +        and extending key with product.
> +        """
> +        table_defs = [copy.deepcopy(t) for t in trac.db_default.schema
> +                      if
> +                      t.name in tables]
> +        for t in table_defs:
> +            t.columns.append(Column('product'))
> +            if isinstance(t.key, list):
> +                t.key = tuple(t.key) + tuple(['product'])
> +            elif isinstance(t.key, tuple):
> +                t.key = t.key + tuple(['product'])
> +            else:
> +                raise TracError(
> +                    "Invalid table '%s' schema key '%s' while upgrading "
> +                    "plugin '%s' from version %d to %d'" %
> +                    (t.name, t.key, PLUGIN_NAME, current_version, 3))
> +        return table_defs
> +
> +    def _get_table_columns(self, table_defs):
> +        table_columns = dict()
> +        for table in table_defs:
> +            table_definition = \
> +                [t for t in table_defs if t.name == table.name][0]
> +            column_names = \
> +                [column.name for column in table_definition.columns]
> +            table_columns[table.name] = \
> +                [c for c in column_names if c != 'product']
> +        return table_columns
> +
> +    def _insert_default_product(self, db):
> +        self.log.info("Creating default product")
> +        db("""INSERT INTO bloodhound_product (prefix, name, description,
> owner)
> +              VALUES ('%s', '%s', '%s', '')
> +           """ % (DEFAULT_PRODUCT, 'Default', 'Default product'))
> +
> +    def _upgrade_tickets(self, db, TICKET_TABLES, create_temp_table):
> +        # migrate tickets that don't have product assigned to default
> product
> +        # - update ticket table product column
> +        # - update ticket related tables by:
> +        #   - upgrading schema
> +        #   - update product column to match ticket's product
> +        self.log.info("Migrating tickets w/o product to default product")
> +        db("""UPDATE ticket SET product='%s'
> +                      WHERE (product IS NULL OR product='')
> +           """ % DEFAULT_PRODUCT)
> +        self._migrate_attachments(
> +            db("""SELECT a.type, a.id, a.filename
>                              FROM attachment a
>                        INNER JOIN ticket t ON a.id = %(t.id)s
>                             WHERE a.type='ticket'
> -                       """ % {'t.id':  db.cast('t.id', 'text')}),
> -                    to_product=DEFAULT_PRODUCT
> -                )
> -
> -                self.log.info("Migrating ticket tables to a new schema")
> -                for table in TICKET_TABLES:
> -                    temp_table_name, cols = create_temp_table(table)
> -                    db("""INSERT INTO %s (%s, product)
> +                       """ % {'t.id': db.cast('t.id', 'text')}),
> +            to_product=DEFAULT_PRODUCT
> +        )
> +        self.log.info("Migrating ticket tables to a new schema")
> +        for table in TICKET_TABLES:
> +            temp_table_name, cols = create_temp_table(table)
> +            db("""INSERT INTO %s (%s, product)
>                            SELECT %s, '' FROM %s""" %
> -                          (table, cols, cols, temp_table_name))
> -                    drop_temp_table(temp_table_name)
> -
> -                for table in TICKET_TABLES:
> -                    if table == 'attachment':
> -                        db("""
> -                            UPDATE attachment
> -                               SET product=(SELECT ticket.product
> -                                              FROM ticket
> -                                             WHERE %(ticket_id)s=
> attachment.id
> -                                             LIMIT 1)
> -                             WHERE attachment.type='ticket'
> -                               AND EXISTS(SELECT ticket.product
> -                                            FROM ticket
> -                                           WHERE %(ticket_id)s=
> attachment.id)
> -                           """ % dict(
> -                            ticket_id=db.cast('attachment.id', 'text')))
> -                    else:
> -                        db("""UPDATE %s
> -                              SET product=(SELECT ticket.product FROM
> ticket WHERE ticket.id=%s.ticket)""" %
> -                           (table, table))
> -
> -                # migrate system table (except wiki which is handled
> separately) to a
> -                # new schema
> -                # - create tables with the new schema
> -                # - populate system tables with global configuration for
> each product
> -                # - exception is permission table where permissions are
> also populated in
> -                #   global scope
> -                self.log.info("Migrating system tables to a new schema")
> -                for table in self.MIGRATE_TABLES:
> -                    if table == 'wiki':
> -                        continue
> -                    temp_table_name, cols = create_temp_table(table)
> -                    for product in all_products:
> -                        self.log.info("Populating table '%s' for product
> '%s' ('%s')",
> -                                      table, product.name,
> product.prefix)
> -                        db("""INSERT INTO %s (%s, product) SELECT %s,'%s'
> FROM %s""" %
> -                              (table, cols, cols, product.prefix,
> temp_table_name))
> -                    if table == 'permission':
> -                        self.log.info("Populating table '%s' for global
> scope", table)
> -                        db("""INSERT INTO %s (%s, product) SELECT %s,'%s'
> FROM %s""" %
> -                              (table, cols, cols, '', temp_table_name))
> -                    drop_temp_table(temp_table_name)
> -
> -                # migrate wiki table
> -                # - populate system wikis to all products + global scope
> -                # - update wiki attachment product to match wiki product
> -                table = 'wiki'
> -                temp_table_name, cols = create_temp_table(table)
> -                self.log.info("Migrating wikis to global context")
> -                db("""INSERT INTO %s (%s, product) SELECT %s, '' FROM
> %s""" %
> -                   (table, cols, cols, temp_table_name))
> +               (table, cols, cols, temp_table_name))
> +            self._drop_temp_table(db, temp_table_name)
> +            if table == 'attachment':
>                  db("""UPDATE attachment
> -                         SET product=''
> -                       WHERE attachment.type='wiki'""")
> -
> -                for wiki_name, wiki_version, wiki_product in db("""
> +                         SET product=(SELECT ticket.product
> +                                        FROM ticket
> +                                       WHERE %(ticket.id)s=attachment.id
> +                                       LIMIT 1)
> +                       WHERE attachment.type='ticket'
> +                         AND EXISTS(SELECT ticket.product
> +                                      FROM ticket
> +                                     WHERE %(ticket.id)s=attachment.id)
> +                   """ % {'ticket.id': db.cast('ticket.id', 'text')})
> +            else:
> +                db("""UPDATE %(table)s
> +                         SET product=(SELECT ticket.product
> +                                        FROM ticket
> +                                       WHERE ticket.id=%(table)s.ticket)
> +                   """ % {'table': table})
> +
> +    def _upgrade_system_tables(self, db, create_temp_table):
> +        # migrate system table (except wiki which is handled separately)
> +        # to a new schema
> +        # - create tables with the new schema
> +        # - populate system tables with global configuration for each
> product
> +        # - exception is permission table where permissions
> +        #   are also populated in global scope
> +        self.log.info("Migrating system tables to a new schema")
> +        for table in self.MIGRATE_TABLES:
> +            if table == 'wiki':
> +                continue
> +            temp_table_name, cols = create_temp_table(table)
> +            for product in Product.select(self.env):
> +                self.log.info("Populating table '%s' for product '%s'
> ('%s')",
> +                              table, product.name, product.prefix)
> +                db("""INSERT INTO %s (%s, product) SELECT %s,'%s' FROM
> %s""" %
> +                   (table, cols, cols, product.prefix, temp_table_name))
> +            if table == 'permission':
> +                self.log.info("Populating table '%s' for global scope",
> table)
> +                db("""INSERT INTO %s (%s, product) SELECT %s,'%s' FROM
> %s""" %
> +                   (table, cols, cols, '', temp_table_name))
> +            self._drop_temp_table(db, temp_table_name)
> +
> +    def _upgrade_wikis(self, db, create_temp_table,
> +                       table_columns):
> +        # migrate wiki table
> +        # - populate system wikis to all products + global scope
> +        # - update wiki attachment product to match wiki product
> +        table = 'wiki'
> +        temp_table_name, cols = create_temp_table(table)
> +        self.log.info("Migrating wikis to global context")
> +        db("""INSERT INTO %s (%s, product) SELECT %s, '' FROM %s""" %
> +           (table, cols, cols, temp_table_name))
> +        db("""UPDATE attachment
> +                 SET product=''
> +               WHERE attachment.type='wiki'""")
> +        for wiki_name, wiki_version, wiki_product in db("""
>                          SELECT name, version, product FROM %s""" % table):
> -                    attachment_cols =
> ','.join(table_columns['attachment'])
> -                    if wiki_name in self.system_wiki_list:
> -                        for product in all_products:
> -                            db("""INSERT INTO %s (%s, product)
> +            attachment_cols = ','.join(table_columns['attachment'])
> +            if wiki_name in self.system_wiki_list:
> +                for product in Product.select(self.env):
> +                    db("""INSERT INTO %s (%s, product)
>                                    SELECT %s, '%s' FROM %s
>                                    WHERE name='%s' AND version=%s AND
> product='%s'""" %
> -                                  (table, cols, cols, product.prefix,
> table,
> -                                   wiki_name, wiki_version, wiki_product))
> -                            db("""INSERT INTO attachment (%(cols)s,
> product)
> +                       (table, cols, cols, product.prefix, table,
> +                        wiki_name, wiki_version, wiki_product))
> +                    db("""INSERT INTO attachment (%(cols)s, product)
>                                    SELECT %(cols)s, '%(new_product)s'
>                                      FROM attachment a
>                                     WHERE type='wiki'
> @@ -380,89 +417,40 @@ class MultiProductSystem(Component):
>                                            wiki_name=wiki_name,
>                                            old_product=wiki_product,
>                                            new_product=product.prefix))
> -                            self._migrate_attachments(
> -                                db("""SELECT type, id, filename
> +                    self._migrate_attachments(
> +                        db("""SELECT type, id, filename
>                                          FROM attachment
>                                         WHERE type='wiki'
>                                           AND id='%s'
>                                           AND product='%s'
>                                     """ % (wiki_name, DEFAULT_PRODUCT)),
> -                                    to_product=DEFAULT_PRODUCT,
> -                                    copy=True
> -                            )
> -                    else:
> -                        self.log.info("Moving wiki page '%s' to default
> product", wiki_name)
> -                        db("""UPDATE wiki
> +                        to_product=DEFAULT_PRODUCT,
> +                        copy=True
> +                    )
> +            else:
> +                self.log.info("Moving wiki page '%s' to default product",
> +                              wiki_name)
> +                db("""UPDATE wiki
>                                SET product='%s'
>                                WHERE name='%s' AND version=%s AND
> product='%s'
>                             """ % (DEFAULT_PRODUCT,
>                                    wiki_name, wiki_version, wiki_product))
> -                        db("""UPDATE attachment
> +                db("""UPDATE attachment
>                                   SET product='%s'
>                                 WHERE type='wiki'
>                                   AND id='%s'
>                                   AND product='%s'
>                             """ % (DEFAULT_PRODUCT, wiki_name,
> wiki_product))
> -                        self._migrate_attachments(
> -                            db("""SELECT type, id, filename
> +                self._migrate_attachments(
> +                    db("""SELECT type, id, filename
>                                      FROM attachment
>                                     WHERE type='wiki'
>                                       AND id='%s'
>                                       AND product='%s'
>                                 """ % (wiki_name, DEFAULT_PRODUCT)),
> -                            to_product=DEFAULT_PRODUCT,
> -                        )
> -
> -                drop_temp_table(temp_table_name)
> -
> -                # soft link existing repositories to default product
> -                repositories_linked = []
> -                for id, name in db("""SELECT id, value FROM repository
> -                                      WHERE name='name'"""):
> -                    if id in repositories_linked:
> -                        continue
> -                    db("""INSERT INTO repository (id, name, value)
> -                          VALUES (%s, 'product', '%s')""" %
> -                       (id, DEFAULT_PRODUCT))
> -                    repositories_linked.append(id)
> -                    self.log.info("Repository '%s' (%s) soft linked to
> default product", name, id)
> -
> -                # Update system tables
> -                # Upgrade schema
> -                self.log.info("Migrating system tables to a new schema")
> -                for table in SYSTEM_TABLES:
> -                    temp_table_name, cols = create_temp_table(table)
> -                    db("""INSERT INTO %s (%s, product)
> -                          SELECT %s,'' FROM %s""" %
> -                       (table, cols, cols, temp_table_name))
> -                    drop_temp_table(temp_table_name)
> -
> -                # enable multi product hooks in environment configuration
> -                import multiproduct.hooks
> -                import inspect
> -                config_update = False
> -                hook_path =
> os.path.realpath(inspect.getsourcefile(multiproduct.hooks))
> -                if not 'environment_factory' in self.env.config['trac']:
> -                    self.env.config['trac'].set('environment_factory',
> hook_path)
> -                    config_update = True
> -                if not 'request_factory' in self.env.config['trac']:
> -                    self.env.config['trac'].set('request_factory',
> hook_path)
> -                    config_update = True
> -                if config_update:
> -                    self.log.info("Enabling multi product hooks in
> environment configuration")
> -                    self.env.config.save()
> -
> -                db_installed_version = self._update_db_version(db, 3)
> -
> -            if db_installed_version < 4:
> -                self.log.debug("creating additional db tables for %s
> plugin." %
> -                               PLUGIN_NAME)
> -                db_connector, dummy =
> DatabaseManager(self.env)._get_connector()
> -                for statement in
> db_connector.to_sql(ProductSetting._get_schema()):
> -                    db(statement)
> -                db_installed_version = self._update_db_version(db, 4)
> -
> -            self.env.enable_multiproduct_schema(True)
> +                    to_product=DEFAULT_PRODUCT,
> +                )
> +        self._drop_temp_table(db, temp_table_name)
>
>      def _migrate_attachments(self, attachments, to_product=None,
> copy=False):
>          for type, id, filename in attachments:
> @@ -498,6 +486,56 @@ class MultiProductSystem(Component):
>                      filename, type, id, str(err)
>                  )
>
> +    def _soft_link_repositories_to_default_product(self, db):
> +        # soft link existing repositories to default product
> +        repositories_linked = []
> +        for id, name in db("""SELECT id, value FROM repository
> +                                      WHERE name='name'"""):
> +            if id in repositories_linked:
> +                continue
> +            db("""INSERT INTO repository (id, name, value)
> +                          VALUES (%s, 'product', '%s')""" %
> +               (id, DEFAULT_PRODUCT))
> +            repositories_linked.append(id)
> +            self.log.info("Repository '%s' (%s) soft linked to default
> product",
> +                          name, id)
> +
> +    def _upgrade_table_system(self, SYSTEM_TABLES, create_temp_table, db):
> +        # Update system tables
> +        # Upgrade schema
> +        self.log.info("Migrating system tables to a new schema")
> +        for table in SYSTEM_TABLES:
> +            temp_table_name, cols = create_temp_table(table)
> +            db("""INSERT INTO %s (%s, product)
> +                          SELECT %s,'' FROM %s""" %
> +               (table, cols, cols, temp_table_name))
> +            self._drop_temp_table(db, temp_table_name)
> +
> +    def _enable_multiproduct_hooks(self):
> +        # enable multi product hooks in environment configuration
> +        import multiproduct.hooks
> +        import inspect
> +
> +        config_update = False
> +        hook_path =
> os.path.realpath(inspect.getsourcefile(multiproduct.hooks))
> +        if not 'environment_factory' in self.env.config['trac']:
> +            self.env.config['trac'].set('environment_factory', hook_path)
> +            config_update = True
> +        if not 'request_factory' in self.env.config['trac']:
> +            self.env.config['trac'].set('request_factory', hook_path)
> +            config_update = True
> +        if config_update:
> +            self.log.info(
> +                "Enabling multi product hooks in environment
> configuration")
> +            self.env.config.save()
> +
> +    def _create_product_tables_for_plugins(self, db):
> +        self.log.debug("creating additional db tables for %s plugin." %
> +                       PLUGIN_NAME)
> +        db_connector, dummy = DatabaseManager(self.env)._get_connector()
> +        for statement in
> db_connector.to_sql(ProductSetting._get_schema()):
> +            db(statement)
> +
>      # IResourceChangeListener methods
>      def match_resource(self, resource):
>          return isinstance(resource, Product)
>
> Modified: bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py
> URL:
> http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py?rev=1476118&r1=1476117&r2=1476118&view=diff
>
> ==============================================================================
> --- bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py (original)
> +++ bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py Fri Apr 26
> 09:10:14 2013
> @@ -33,6 +33,7 @@ from trac.test import Environment
>  from trac.ticket import Ticket
>  from trac.wiki import WikiPage
>
> +from multiproduct.api import MultiProductSystem
>  from multiproduct.env import ProductEnvironment
>  from multiproduct.model import Product
>
> @@ -43,13 +44,9 @@ BLOODHOUND_TABLES = (
>  )
>
>  TABLES_WITH_PRODUCT_FIELD = (
> -    'component',
> -    'milestone',
> -    'version',
> -    'enum',
> -    'permission',
> -    'wiki',
> -    'report',
> +    'ticket', 'ticket_change', 'ticket_custom', 'attachment', 'component',
> +    'milestone', 'wiki', 'report',
> +    'version', 'enum', 'permission', 'system',
>  )
>
>
> @@ -57,12 +54,12 @@ class EnvironmentUpgradeTestCase(unittes
>      def setUp(self):
>          self.env_path = tempfile.mkdtemp('multiproduct-tempenv')
>          self.env = Environment(self.env_path, create=True)
> -        self.enabled_components = []
>          DummyPlugin.version = 1
>
> -    def test_upgrade_environment(self):
> +    def test_can_upgrade_environment_with_multi_product_disabled(self):
>          self.env.upgrade()
>
> +        # Multiproduct was not enabled so multiproduct tables should not
> exist
>          with self.env.db_direct_transaction as db:
>              for table in BLOODHOUND_TABLES:
>                  with self.assertFailsWithMissingTable():
> @@ -72,7 +69,7 @@ class EnvironmentUpgradeTestCase(unittes
>                  with self.assertFailsWithMissingColumn():
>                      db("SELECT product FROM %s" % table)
>
> -    def test_upgrade_environment_to_multiproduct(self):
> +    def
> test_upgrade_creates_multi_product_tables_and_adds_product_column(self):
>          self._enable_multiproduct()
>          self.env.upgrade()
>
> @@ -83,89 +80,38 @@ class EnvironmentUpgradeTestCase(unittes
>              for table in TABLES_WITH_PRODUCT_FIELD:
>                  db("SELECT product FROM %s" % table)
>
> -    def test_upgrade_plugin(self):
> -        self._enable_component(DummyPlugin)
> -        self.env.upgrade()
> -
> -        with self.env.db_direct_transaction as db:
> -            db("SELECT v1 FROM dummy_table")
> -            with self.assertFailsWithMissingColumn():
> -                db("SELECT v2 FROM dummy_table")
> -
> -        DummyPlugin.version = 2
> -        self.env.upgrade()
> -
> -        with self.env.db_direct_transaction as db:
> -            db("SELECT v2 FROM dummy_table")
> -
> -    def test_upgrade_plugin_to_multiproduct(self):
> +    def test_upgrade_creates_default_product(self):
>          self._enable_multiproduct()
> -        self._enable_component(DummyPlugin)
>          self.env.upgrade()
>
> -        with self.env.db_direct_transaction as db:
> -            db("SELECT * FROM dummy_table")
> -            db("""SELECT * FROM "@_dummy_table" """)
> +        products = Product.select(self.env)
> +        self.assertEqual(len(products), 1)
>
> -    def test_upgrade_existing_plugin_to_multiproduct(self):
> -        self._enable_component(DummyPlugin)
> -        self.env.upgrade()
> -        with self.env.db_direct_transaction as db:
> -            with self.assertFailsWithMissingTable():
> -                db("""SELECT * FROM "@_dummy_table" """)
> -
> -        self._enable_multiproduct()
> -        self.env.upgrade()
> -        with self.env.db_direct_transaction as db:
> -            db("SELECT * FROM dummy_table")
> -            db("""SELECT * FROM "@_dummy_table" """)
> -
> -    def test_upgrading_existing_plugin_leaves_data_in_global_env(self):
> -        self._enable_component(DummyPlugin)
> -        self.env.upgrade()
> -        with self.env.db_direct_transaction as db:
> -            for i in range(5):
> -                db("INSERT INTO dummy_table (v1) VALUES ('%d')" % i)
> -            self.assertEqual(
> -                len(db("SELECT * FROM dummy_table")), 5)
> -
> -        self._enable_multiproduct()
> -        self.env.upgrade()
> -        with self.env.db_direct_transaction as db:
> -            self.assertEqual(
> -                len(db('SELECT * FROM "dummy_table"')), 5)
> -            self.assertEqual(
> -                len(db('SELECT * FROM "@_dummy_table"')), 0)
> -
> -    def test_creating_new_product_calls_environment_created(self):
> -        self._enable_component(DummyPlugin)
> -        self._enable_multiproduct()
> -        self.env.upgrade()
> -
> -        prod = Product(self.env)
> -        prod.update_field_dict(dict(prefix='p1'))
> -        ProductEnvironment(self.env, prod, create=True)
> -        with self.env.db_direct_transaction as db:
> -            db('SELECT * FROM "p1_dummy_table"')
> -
> -    def test_upgrade_moves_tickets_to_default_product(self):
> +    def
> test_upgrade_moves_tickets_and_related_objects_to_default_prod(self):
> +        self._add_custom_field('custom_field')
>          with self.env.db_direct_transaction as db:
>              db("""INSERT INTO ticket (id) VALUES (1)""")
>              db("""INSERT INTO attachment (type, id)
> -                         VALUES ('ticket', '1')""")
> +                       VALUES ('ticket', '1')""")
> +            db("""INSERT INTO ticket_custom (ticket, name, value)
> +                       VALUES (1, 'custom_field', '42')""")
> +            db("""INSERT INTO ticket_change (ticket, time, field)
> +                       VALUES (1, 42, 'summary')""")
>
>          self._enable_multiproduct()
>          self.env.upgrade()
>
> -        with self.env.db_direct_transaction as db:
> -            self.assertEqual(
> -                len(db("""SELECT * FROM ticket WHERE product='@'""")), 1)
> -            self.assertEqual(
> -                len(db("""SELECT * FROM attachment
> -                          WHERE product='@'
> -                            AND type='ticket'""")), 1)
> +        with self.product('@'):
> +            ticket = Ticket(self.env, 1)
> +            attachments = list(Attachment.select(self.env,
> +                                                 ticket.resource.realm,
> +                                                 ticket.resource.id))
> +            self.assertEqual(len(attachments), 1)
> +            self.assertEqual(ticket['custom_field'], '42')
> +            changes = ticket.get_changelog()
> +            self.assertEqual(len(changes), 3)
>
> -    def test_upgrade_moves_wikis_to_default_product(self):
> +    def test_upgrade_moves_custom_wikis_to_default_product(self):
>          with self.env.db_direct_transaction as db:
>              db("""INSERT INTO wiki (name, version) VALUES ('MyPage',
> 1)""")
>              db("""INSERT INTO attachment (type, id)
> @@ -205,23 +151,44 @@ class EnvironmentUpgradeTestCase(unittes
>                             WHERE product=''
>                               AND type='wiki'""")), 1)
>
> -    def test_can_upgrade_database_with_orphaned_attachments(self):
> +    def
> test_upgrade_copies_content_of_system_tables_to_all_products(self):
> +        mp = MultiProductSystem(self.env)
>          with self.env.db_direct_transaction as db:
> -            db("""INSERT INTO attachment (id, type)
> -                       VALUES ('5', 'ticket')""")
> -            db("""INSERT INTO attachment (id, type)
> -                       VALUES ('MyWiki', 'wiki')""")
> +            mp._add_column_product_to_ticket(db)
> +            mp._create_multiproduct_tables(db)
> +            mp._update_db_version(db, 1)
> +            for i in range(1, 6):
> +                db("""INSERT INTO bloodhound_product (prefix, name)
> +                           VALUES ('p%d', 'Product 1')""" % i)
> +            for table in ('component', 'milestone', 'enum', 'version',
> +                          'permission', 'report'):
> +                db("""DELETE FROM %s""" % table)
> +            db("""INSERT INTO component (name) VALUES ('foobar')""")
> +            db("""INSERT INTO milestone (name) VALUES ('foobar')""")
> +            db("""INSERT INTO version (name) VALUES ('foobar')""")
> +            db("""INSERT INTO enum (type, name) VALUES ('a', 'b')""")
> +            db("""INSERT INTO permission VALUES ('x', 'TICKET_VIEW')""")
> +            db("""INSERT INTO wiki (name, version) VALUES ('WikiStart',
> 1)""")
> +            db("""INSERT INTO report (title) VALUES ('x')""")
>
>          self._enable_multiproduct()
>          self.env.upgrade()
>
> -    def test_can_upgrade_database_with_text_attachment_ids(self):
>          with self.env.db_direct_transaction as db:
> -            db("""INSERT INTO attachment (id, type)
> -                       VALUES ('abc', 'ticket')""")
> -
> -        self._enable_multiproduct()
> -        self.env.upgrade()
> +            for table in ('component', 'milestone', 'version', 'enum',
> +                          'report'):
> +                rows = db("SELECT * FROM %s" % table)
> +                self.assertEqual(
> +                    len(rows), 6,
> +                    "Wrong number of lines in %s (%d instead of %d)\n%s"
> +                    % (table, len(rows), 6, rows))
> +            for table in ('wiki', 'permission'):
> +                # Permissions and wikis also hold rows for global product.
> +                rows = db("SELECT * FROM %s" % table)
> +                self.assertEqual(
> +                    len(rows), 7,
> +                    "Wrong number of lines in %s (%d instead of %d)\n%s"
> +                    % (table, len(rows), 7, rows))
>
>      def test_upgrading_database_moves_attachment_to_correct_product(self):
>          ticket = self.insert_ticket('ticket')
> @@ -258,26 +225,148 @@ class EnvironmentUpgradeTestCase(unittes
>          for attachment in attachments:
>              self.assertEqual(attachment.open().read(), 'Hello World!')
>
> +    def
> test_can_upgrade_database_with_ticket_attachment_with_text_ids(self):
> +        with self.env.db_direct_transaction as db:
> +            db("""INSERT INTO attachment (id, type)
> +                       VALUES ('abc', 'ticket')""")
> +
> +        self._enable_multiproduct()
> +        self.env.upgrade()
> +
> +    def test_can_upgrade_database_with_orphaned_attachments(self):
> +        with self.env.db_direct_transaction as db:
> +            db("""INSERT INTO attachment (id, type)
> +                       VALUES ('5', 'ticket')""")
> +            db("""INSERT INTO attachment (id, type)
> +                       VALUES ('MyWiki', 'wiki')""")
> +
> +        self._enable_multiproduct()
> +        self.env.upgrade()
> +
> +    def test_can_upgrade_multi_product_from_v1(self):
> +        mp = MultiProductSystem(self.env)
> +        with self.env.db_direct_transaction as db:
> +            mp._add_column_product_to_ticket(db)
> +            mp._create_multiproduct_tables(db)
> +            mp._update_db_version(db, 1)
> +
> +            db("""INSERT INTO bloodhound_product (prefix, name)
> +                       VALUES ('p1', 'Product 1')""")
> +            db("""INSERT INTO ticket (id, product)
> +                       VALUES (1, 'Product 1')""")
> +
> +        self._enable_multiproduct()
> +        self.env.upgrade()
> +
> +        with self.product('p1'):
> +            Ticket(self.env, 1)
> +
> +    def test_can_upgrade_multi_product_from_v2(self):
> +        mp = MultiProductSystem(self.env)
> +        with self.env.db_direct_transaction as db:
> +            mp._add_column_product_to_ticket(db)
> +            mp._create_multiproduct_tables(db)
> +            mp._replace_product_on_ticket_with_product_prefix(db)
> +            mp._update_db_version(db, 2)
> +
> +            db("""INSERT INTO bloodhound_product (prefix, name)
> +                       VALUES ('p1', 'Product 1')""")
> +            db("""INSERT INTO ticket (id, product)
> +                       VALUES (1, 'p1')""")
> +            db("""INSERT INTO ticket (id)
> +                       VALUES (2)""")
> +
> +        self._enable_multiproduct()
> +        self.env.upgrade()
> +
> +        with self.product('p1'):
> +            Ticket(self.env, 1)
> +        with self.product('@'):
> +            Ticket(self.env, 2)
> +
> +    def test_upgrade_plugin(self):
> +        self._enable_component(DummyPlugin)
> +        self.env.upgrade()
> +
> +        with self.env.db_direct_transaction as db:
> +            db("SELECT v1 FROM dummy_table")
> +            with self.assertFailsWithMissingColumn():
> +                db("SELECT v2 FROM dummy_table")
> +
> +        DummyPlugin.version = 2
> +        self.env.upgrade()
> +
> +        with self.env.db_direct_transaction as db:
> +            db("SELECT v2 FROM dummy_table")
> +
> +    def test_upgrade_plugin_to_multiproduct(self):
> +        self._enable_multiproduct()
> +        self._enable_component(DummyPlugin)
> +        self.env.upgrade()
> +
> +        with self.env.db_direct_transaction as db:
> +            db("SELECT * FROM dummy_table")
> +            db("""SELECT * FROM "@_dummy_table" """)
> +
> +    def test_upgrade_existing_plugin_to_multiproduct(self):
> +        self._enable_component(DummyPlugin)
> +        self.env.upgrade()
> +        with self.env.db_direct_transaction as db:
> +            with self.assertFailsWithMissingTable():
> +                db("""SELECT * FROM "@_dummy_table" """)
> +
> +        self._enable_multiproduct()
> +        self.env.upgrade()
> +        with self.env.db_direct_transaction as db:
> +            db("SELECT * FROM dummy_table")
> +            db("""SELECT * FROM "@_dummy_table" """)
> +
> +    def test_upgrading_existing_plugin_leaves_data_in_global_env(self):
> +        self._enable_component(DummyPlugin)
> +        self.env.upgrade()
> +        with self.env.db_direct_transaction as db:
> +            for i in range(5):
> +                db("INSERT INTO dummy_table (v1) VALUES ('%d')" % i)
> +            self.assertEqual(
> +                len(db("SELECT * FROM dummy_table")), 5)
> +
> +        self._enable_multiproduct()
> +        self.env.upgrade()
> +        with self.env.db_direct_transaction as db:
> +            self.assertEqual(
> +                len(db('SELECT * FROM "dummy_table"')), 5)
> +            self.assertEqual(
> +                len(db('SELECT * FROM "@_dummy_table"')), 0)
> +
> +    def test_creating_new_product_calls_environment_created(self):
> +        self._enable_component(DummyPlugin)
> +        self._enable_multiproduct()
> +        self.env.upgrade()
> +
> +        prod = Product(self.env)
> +        prod.update_field_dict(dict(prefix='p1'))
> +        ProductEnvironment(self.env, prod, create=True)
> +        with self.env.db_direct_transaction as db:
> +            db('SELECT * FROM "p1_dummy_table"')
> +
>      def _enable_multiproduct(self):
> -        self.env.config.set('components', 'multiproduct.*', 'enabled')
> -        self.env.config.save()
> -        self._reload_environment()
> -        self._reenable_components()
> +        self._update_config('components', 'multiproduct.*', 'enabled')
> +
> +    def _add_custom_field(self, field_name):
> +        self._update_config('ticket-custom', field_name, 'text')
>
>      def _enable_component(self, cls):
> -        self.env.config.set('components',
> -                            '%s.%s' % (cls.__module__, cls.__name__),
> -                            'enabled')
> -        self.enabled_components.append(cls)
> -        self.env.compmgr.enabled[cls] = True
> +        self._update_config(
> +            'components',
> +            '%s.%s' % (cls.__module__, cls.__name__),
> +            'enabled'
> +        )
>
> -    def _reload_environment(self):
> +    def _update_config(self, section, key, value):
> +        self.env.config.set(section, key, value)
> +        self.env.config.save()
>          self.env = Environment(self.env_path)
>
> -    def _reenable_components(self):
> -        for cls in self.enabled_components:
> -            self.env.compmgr.enabled[cls] = True
> -
>      def _create_file_with_content(self, content):
>          filename = str(uuid.uuid4())[:6]
>          path = os.path.join(self.env_path, filename)
>
>
>

One of the unit tests added in this changeset is now failing on the trunk.
It looks like the test is only correct for the number of permissions
associated with "anonymous" and doesn't take into account the permissions
assigned to "authenticated". Just wanted to ask what is the best way to fix
this rather than guessing at the intention. Thanks!

======================================================================
FAIL: test_upgrade_copies_content_of_system_tables_to_all_products
(tests.upgrade.EnvironmentUpgradeTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File
"/home/user/Workspace/bh579/bloodhound/bloodhound_multiproduct/tests/upgrade.py",
line 197, in test_upgrade_copies_content_of_system_tables_to_all_products
    % (table, len(rows), 7, rows))
AssertionError: Wrong number of lines in permission (21 instead of 7)
[(u'anonymous', u'PRODUCT_VIEW', u''), (u'anonymous', u'PRODUCT_VIEW', u'@'),
(u'anonymous', u'PRODUCT_VIEW', u'p1'), (u'anonymous', u'PRODUCT_VIEW',
u'p2'), (u'anonymous', u'PRODUCT_VIEW', u'p3'), (u'anonymous',
u'PRODUCT_VIEW', u'p4'), (u'anonymous', u'PRODUCT_VIEW', u'p5'),
(u'authenticated', u'PRODUCT_VIEW', u''), (u'authenticated',
u'PRODUCT_VIEW', u'@'), (u'authenticated', u'PRODUCT_VIEW', u'p1'),
(u'authenticated', u'PRODUCT_VIEW', u'p2'), (u'authenticated',
u'PRODUCT_VIEW', u'p3'), (u'authenticated', u'PRODUCT_VIEW', u'p4'),
(u'authenticated', u'PRODUCT_VIEW', u'p5'), (u'x', u'TICKET_VIEW', u''),
(u'x', u'TICKET_VIEW', u'@'), (u'x', u'TICKET_VIEW', u'p1'), (u'x',
u'TICKET_VIEW', u'p2'), (u'x', u'TICKET_VIEW', u'p3'), (u'x',
u'TICKET_VIEW', u'p4'), (u'x', u'TICKET_VIEW', u'p5')]

Re: svn commit: r1476118 - in /bloodhound/trunk/bloodhound_multiproduct: multiproduct/api.py tests/upgrade.py

Posted by Anze Staric <an...@gmail.com>.
Should we break the test case in two, keep the part that is passing
and skip the part that is failing?

Im thinking of something along the lines of:

Index: trac/trac/wiki/tests/wiki-tests.txt
===================================================================
--- trac/trac/wiki/tests/wiki-tests.txt (revision 1503571)
+++ trac/trac/wiki/tests/wiki-tests.txt (working copy)
@@ -474,22 +474,27 @@
 ============================== Another arbitrary protocol Link
 svn+ssh://secureserver.org
 [svn+ssh://secureserver.org SVN link]
-rfc-2396.compatible://link
 [rfc-2396.compatible://link RFC 2396]
-rfc-2396+under_score://link
 [rfc-2396+under_score://link underscore]
 unsafe://scheme is not rendered
 ------------------------------
 <p>
 <a class="ext-link" href="svn+ssh://secureserver.org"><span
class="icon"></span>svn+ssh://secureserver.org</a>
 <a class="ext-link" href="svn+ssh://secureserver.org"><span
class="icon"></span>SVN link</a>
-<a class="ext-link" href="rfc-2396.compatible://link"><span
class="icon"></span>rfc-2396.compatible://link</a>
 <a class="ext-link" href="rfc-2396.compatible://link"><span
class="icon"></span>RFC 2396</a>
-<a class="ext-link" href="rfc-2396+under_score://link"><span
class="icon"></span>rfc-2396+under_score://link</a>
 <a class="ext-link" href="rfc-2396+under_score://link"><span
class="icon"></span>underscore</a>
 unsafe://scheme is not rendered
 </p>
 ------------------------------
+============================== Another arbitrary protocol Link
without [] - SKIP as it clashes with Jira ticket syntax
+rfc-2396.compatible://link
+rfc-2396+under_score://link
+------------------------------
+<p>
+<a class="ext-link" href="rfc-2396.compatible://link"><span
class="icon"></span>rfc-2396.compatible://link</a>
+<a class="ext-link" href="rfc-2396+under_score://link"><span
class="icon"></span>rfc-2396+under_score://link</a>
+</p>
+------------------------------


On Tue, Jul 16, 2013 at 9:49 AM, Ryan Ollos <ry...@wandisco.com> wrote:
> On Tue, Jul 16, 2013 at 12:26 AM, Anze Staric <an...@gmail.com> wrote:
>
>> The test is actually only correct for the number of permissions
>> associated with user "x".
>>
>> Test deletes all default permissions and creates a custom set for the
>> user x. Upgrading multiproduct to version 2+ was supposed to copy
>> existing permissions for each of the created products. Since r1489442,
>> upgrade also creates PRODUCT_VIEW for anonymous and authenticated
>> users to all products, even if they had no permissions before the
>> upgrade.
>>
>> If this is the desired behaviour, I would fix the test to only count
>> permissions of user x:
>>
>> Index: bloodhound_multiproduct/tests/upgrade.py
>> ===================================================================
>> --- bloodhound_multiproduct/tests/upgrade.py (revision 1503125)
>> +++ bloodhound_multiproduct/tests/upgrade.py (working copy)
>> @@ -190,7 +190,7 @@
>>                  # Permissions also hold rows for global product.
>> -                rows = db("SELECT * FROM %s" % table)
>> +                rows = db("SELECT * FROM %s WHERE username='x'" % table)
>>                  self.assertEqual(
>>
>>
>>
>> On Tue, Jul 16, 2013 at 8:44 AM, Ryan Ollos <ry...@wandisco.com>
>> wrote:
>> > On Fri, Apr 26, 2013 at 2:10 AM, <as...@apache.org> wrote:
>> >
>> >> Author: astaric
>> >> Date: Fri Apr 26 09:10:14 2013
>> >> New Revision: 1476118
>> >>
>> >> URL: http://svn.apache.org/r1476118
>> >> Log:
>> >> Added more tests for multiproduct upgrade, refactoring.
>> >>
>> >> Modified:
>> >>     bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py
>> >>     bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py
>> >>
>> >> Modified: bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py
>> >> URL:
>> >>
>> http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py?rev=1476118&r1=1476117&r2=1476118&view=diff
>> >>
>> >>
>> ==============================================================================
>> >> --- bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py
>> (original)
>> >> +++ bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py Fri Apr
>> >> 26 09:10:14 2013
>> >> @@ -30,6 +30,7 @@ from trac.attachment import Attachment
>> >>  from trac.config import Option, PathOption
>> >>  from trac.core import Component, TracError, implements, Interface
>> >>  from trac.db import Table, Column, DatabaseManager, Index
>> >> +import trac.db_default
>> >>  from trac.env import IEnvironmentSetupParticipant, Environment
>> >>  from trac.perm import IPermissionRequestor, PermissionCache
>> >>  from trac.resource import IExternalResourceConnector,
>> >> IResourceChangeListener,\
>> >> @@ -97,7 +98,7 @@ class MultiProductSystem(Component):
>> >>          global environment configuration.
>> >>          """)
>> >>
>> >> -    SCHEMA = [mcls._get_schema() \
>> >> +    SCHEMA = [mcls._get_schema()
>> >>                for mcls in (Product, ProductResourceMap)]
>> >>
>> >>      # Tables which should be migrated (extended with 'product' column)
>> >> @@ -202,171 +203,207 @@ class MultiProductSystem(Component):
>> >>          db_installed_version = self.get_version()
>> >>          with self.env.db_direct_transaction as db:
>> >>              if db_installed_version < 1:
>> >> -                # Initial installation
>> >> -                db("ALTER TABLE ticket ADD COLUMN product TEXT")
>> >> -                self.log.debug("creating initial db tables for %s
>> >> plugin." %
>> >> -                               PLUGIN_NAME)
>> >> -                db_connector, dummy =
>> >> DatabaseManager(self.env)._get_connector()
>> >> -                for table in self.SCHEMA:
>> >> -                    for statement in db_connector.to_sql(table):
>> >> -                        db(statement)
>> >> +                self._add_column_product_to_ticket(db)
>> >> +                self._create_multiproduct_tables(db)
>> >>                  db_installed_version = self._update_db_version(db, 1)
>> >>
>> >>              if db_installed_version < 2:
>> >> -                from multiproduct.model import Product
>> >> -                products = Product.select(self.env)
>> >> -                for prod in products:
>> >> -                    db("""UPDATE ticket SET product=%s
>> >> -                          WHERE product=%s""", (prod.prefix, prod.name
>> ))
>> >> +                self._replace_product_on_ticket_with_product_prefix(db)
>> >>                  db_installed_version = self._update_db_version(db, 2)
>> >>
>> >>              if db_installed_version < 3:
>> >> -                from multiproduct.model import Product
>> >> -                import trac.db_default
>> >> -
>> >> -                def create_temp_table(table):
>> >> -                    """creates temporary table with the new schema and
>> >> -                    drops original table"""
>> >> -                    table_temp_name = '%s_temp' % table
>> >> -                    if table == 'report':
>> >> -                        cols = ','.join([c for c in
>> table_columns[table]
>> >> if c != 'id'])
>> >> -                    else:
>> >> -                        cols = ','.join(table_columns[table])
>> >> -                    self.log.info("Migrating table '%s' to a new
>> >> schema", table)
>> >> -                    db("""CREATE TABLE %s AS SELECT %s FROM %s""" %
>> >> -                          (table_temp_name, cols, table))
>> >> -                    db("""DROP TABLE %s""" % table)
>> >> -                    db_connector, _ =
>> >> DatabaseManager(self.env)._get_connector()
>> >> -                    table_schema = [t for t in table_defs if t.name ==
>> >> table][0]
>> >> -                    for sql in db_connector.to_sql(table_schema):
>> >> -                        db(sql)
>> >> -                    return table_temp_name, cols
>> >> -
>> >> -                def drop_temp_table(table):
>> >> -                    """drops specified temporary table"""
>> >> -                    db("""DROP TABLE %s""" % table)
>> >> -
>> >> -                TICKET_TABLES = ['ticket_change', 'ticket_custom',
>> >> -                                 'attachment',
>> >> -                                ]
>> >>                  SYSTEM_TABLES = ['system']
>> >> +                TICKET_TABLES = [
>> >> +                    'ticket_change', 'ticket_custom', 'attachment',
>> >> +                ]
>> >> +                table_defs = self._add_product_column_to_tables(
>> >> +                    self.MIGRATE_TABLES + TICKET_TABLES +
>> SYSTEM_TABLES,
>> >> +                    db_installed_version)
>> >> +                table_columns = self._get_table_columns(table_defs)
>> >> +                create_temp_table = lambda table:
>> self._create_temp_table(
>> >> +                    db, table, table_columns, table_defs)
>> >> +
>> >> +                self._insert_default_product(db)
>> >> +                self._upgrade_tickets(db, TICKET_TABLES,
>> >> create_temp_table)
>> >> +                self._upgrade_wikis(db, create_temp_table,
>> table_columns)
>> >> +                self._upgrade_system_tables(db, create_temp_table)
>> >> +                self._soft_link_repositories_to_default_product(db)
>> >> +                self._upgrade_table_system(SYSTEM_TABLES,
>> >> create_temp_table, db)
>> >> +                self._enable_multiproduct_hooks()
>> >>
>> >> -                # extend trac default schema by adding product column
>> and
>> >> extending key with product
>> >> -                table_defs = [copy.deepcopy(t) for t in
>> >> trac.db_default.schema
>> >> -                                                    if t.name in
>> >> self.MIGRATE_TABLES + TICKET_TABLES + SYSTEM_TABLES]
>> >> -                for t in table_defs:
>> >> -                    t.columns.append(Column('product'))
>> >> -                    if isinstance(t.key, list):
>> >> -                        t.key = tuple(t.key) + tuple(['product'])
>> >> -                    elif isinstance(t.key, tuple):
>> >> -                        t.key = t.key + tuple(['product'])
>> >> -                    else:
>> >> -                        raise TracError("Invalid table '%s' schema key
>> >> '%s' while upgrading "
>> >> -                                        "plugin '%s' from version %d to
>> >> %d'" %
>> >> -                                        (t.name, t.key, PLUGIN_NAME,
>> >> db_installed_version, 3))
>> >> -                table_columns = dict()
>> >> -                for table in table_defs:
>> >> -                    table_columns[table.name] = [c for c in
>> [column.namefor column in
>> >> -                                                                [t for
>> t
>> >> in table_defs if t.name == table.name][0].columns]
>> >> -                                                                    if
>> c
>> >> != 'product']
>> >> -                # create default product
>> >> -                self.log.info("Creating default product")
>> >> -                db("""INSERT INTO bloodhound_product (prefix, name,
>> >> description, owner)
>> >> -                      VALUES ('%s', '%s', '%s', '')""" %
>> >> -                      (DEFAULT_PRODUCT, 'Default', 'Default product'))
>> >> -
>> >> -                # fetch all products
>> >> -                all_products = Product.select(self.env)
>> >> -
>> >> -                # migrate tickets that don't have product assigned to
>> >> default product
>> >> -                # - update ticket table product column
>> >> -                # - update ticket related tables by:
>> >> -                #   - upgrading schema
>> >> -                #   - update product column to match ticket's product
>> >> -                self.log.info("Migrating tickets w/o product to
>> default
>> >> product")
>> >> -                db("""UPDATE ticket SET product='%s'
>> >> -                      WHERE (product IS NULL OR product='')""" %
>> >> DEFAULT_PRODUCT)
>> >> -                self._migrate_attachments(
>> >> -                    db("""SELECT a.type, a.id, a.filename
>> >> +                db_installed_version = self._update_db_version(db, 3)
>> >> +
>> >> +            if db_installed_version < 4:
>> >> +                self._create_product_tables_for_plugins(db)
>> >> +                db_installed_version = self._update_db_version(db, 4)
>> >> +
>> >> +            self.env.enable_multiproduct_schema(True)
>> >> +
>> >> +    def _add_column_product_to_ticket(self, db):
>> >> +        self.log.debug("Adding field product to ticket table")
>> >> +        db("ALTER TABLE ticket ADD COLUMN product TEXT")
>> >> +
>> >> +    def _create_multiproduct_tables(self, db):
>> >> +        self.log.debug("Creating initial db tables for %s plugin." %
>> >> +                       PLUGIN_NAME)
>> >> +        db_connector, dummy =
>> DatabaseManager(self.env)._get_connector()
>> >> +        for table in self.SCHEMA:
>> >> +            for statement in db_connector.to_sql(table):
>> >> +                db(statement)
>> >> +
>> >> +    def _replace_product_on_ticket_with_product_prefix(self, db):
>> >> +        for prod in Product.select(self.env):
>> >> +            db("""UPDATE ticket SET product=%s
>> >> +                          WHERE product=%s""", (prod.prefix, prod.name
>> ))
>> >> +
>> >> +    def _create_temp_table(self, db, table, table_columns, table_defs):
>> >> +        """creates temporary table with the new schema and
>> >> +        drops original table"""
>> >> +        table_temp_name = '%s_temp' % table
>> >> +        if table == 'report':
>> >> +            cols = ','.join([c for c in table_columns[table] if c !=
>> >> 'id'])
>> >> +        else:
>> >> +            cols = ','.join(table_columns[table])
>> >> +        self.log.info("Migrating table '%s' to a new schema", table)
>> >> +        db("""CREATE TABLE %s AS SELECT %s FROM %s""" %
>> >> +              (table_temp_name, cols, table))
>> >> +        db("""DROP TABLE %s""" % table)
>> >> +        db_connector, _ = DatabaseManager(self.env)._get_connector()
>> >> +        table_schema = [t for t in table_defs if t.name == table][0]
>> >> +        for sql in db_connector.to_sql(table_schema):
>> >> +            db(sql)
>> >> +        return table_temp_name, cols
>> >> +
>> >> +    def _drop_temp_table(self, db, table):
>> >> +        db("""DROP TABLE %s""" % table)
>> >> +
>> >> +    def _add_product_column_to_tables(self, tables, current_version):
>> >> +        """Extend trac default schema by adding product column
>> >> +        and extending key with product.
>> >> +        """
>> >> +        table_defs = [copy.deepcopy(t) for t in trac.db_default.schema
>> >> +                      if
>> >> +                      t.name in tables]
>> >> +        for t in table_defs:
>> >> +            t.columns.append(Column('product'))
>> >> +            if isinstance(t.key, list):
>> >> +                t.key = tuple(t.key) + tuple(['product'])
>> >> +            elif isinstance(t.key, tuple):
>> >> +                t.key = t.key + tuple(['product'])
>> >> +            else:
>> >> +                raise TracError(
>> >> +                    "Invalid table '%s' schema key '%s' while
>> upgrading "
>> >> +                    "plugin '%s' from version %d to %d'" %
>> >> +                    (t.name, t.key, PLUGIN_NAME, current_version, 3))
>> >> +        return table_defs
>> >> +
>> >> +    def _get_table_columns(self, table_defs):
>> >> +        table_columns = dict()
>> >> +        for table in table_defs:
>> >> +            table_definition = \
>> >> +                [t for t in table_defs if t.name == table.name][0]
>> >> +            column_names = \
>> >> +                [column.name for column in table_definition.columns]
>> >> +            table_columns[table.name] = \
>> >> +                [c for c in column_names if c != 'product']
>> >> +        return table_columns
>> >> +
>> >> +    def _insert_default_product(self, db):
>> >> +        self.log.info("Creating default product")
>> >> +        db("""INSERT INTO bloodhound_product (prefix, name,
>> description,
>> >> owner)
>> >> +              VALUES ('%s', '%s', '%s', '')
>> >> +           """ % (DEFAULT_PRODUCT, 'Default', 'Default product'))
>> >> +
>> >> +    def _upgrade_tickets(self, db, TICKET_TABLES, create_temp_table):
>> >> +        # migrate tickets that don't have product assigned to default
>> >> product
>> >> +        # - update ticket table product column
>> >> +        # - update ticket related tables by:
>> >> +        #   - upgrading schema
>> >> +        #   - update product column to match ticket's product
>> >> +        self.log.info("Migrating tickets w/o product to default
>> product")
>> >> +        db("""UPDATE ticket SET product='%s'
>> >> +                      WHERE (product IS NULL OR product='')
>> >> +           """ % DEFAULT_PRODUCT)
>> >> +        self._migrate_attachments(
>> >> +            db("""SELECT a.type, a.id, a.filename
>> >>                              FROM attachment a
>> >>                        INNER JOIN ticket t ON a.id = %(t.id)s
>> >>                             WHERE a.type='ticket'
>> >> -                       """ % {'t.id':  db.cast('t.id', 'text')}),
>> >> -                    to_product=DEFAULT_PRODUCT
>> >> -                )
>> >> -
>> >> -                self.log.info("Migrating ticket tables to a new
>> schema")
>> >> -                for table in TICKET_TABLES:
>> >> -                    temp_table_name, cols = create_temp_table(table)
>> >> -                    db("""INSERT INTO %s (%s, product)
>> >> +                       """ % {'t.id': db.cast('t.id', 'text')}),
>> >> +            to_product=DEFAULT_PRODUCT
>> >> +        )
>> >> +        self.log.info("Migrating ticket tables to a new schema")
>> >> +        for table in TICKET_TABLES:
>> >> +            temp_table_name, cols = create_temp_table(table)
>> >> +            db("""INSERT INTO %s (%s, product)
>> >>                            SELECT %s, '' FROM %s""" %
>> >> -                          (table, cols, cols, temp_table_name))
>> >> -                    drop_temp_table(temp_table_name)
>> >> -
>> >> -                for table in TICKET_TABLES:
>> >> -                    if table == 'attachment':
>> >> -                        db("""
>> >> -                            UPDATE attachment
>> >> -                               SET product=(SELECT ticket.product
>> >> -                                              FROM ticket
>> >> -                                             WHERE %(ticket_id)s=
>> >> attachment.id
>> >> -                                             LIMIT 1)
>> >> -                             WHERE attachment.type='ticket'
>> >> -                               AND EXISTS(SELECT ticket.product
>> >> -                                            FROM ticket
>> >> -                                           WHERE %(ticket_id)s=
>> >> attachment.id)
>> >> -                           """ % dict(
>> >> -                            ticket_id=db.cast('attachment.id',
>> 'text')))
>> >> -                    else:
>> >> -                        db("""UPDATE %s
>> >> -                              SET product=(SELECT ticket.product FROM
>> >> ticket WHERE ticket.id=%s.ticket)""" %
>> >> -                           (table, table))
>> >> -
>> >> -                # migrate system table (except wiki which is handled
>> >> separately) to a
>> >> -                # new schema
>> >> -                # - create tables with the new schema
>> >> -                # - populate system tables with global configuration
>> for
>> >> each product
>> >> -                # - exception is permission table where permissions are
>> >> also populated in
>> >> -                #   global scope
>> >> -                self.log.info("Migrating system tables to a new
>> schema")
>> >> -                for table in self.MIGRATE_TABLES:
>> >> -                    if table == 'wiki':
>> >> -                        continue
>> >> -                    temp_table_name, cols = create_temp_table(table)
>> >> -                    for product in all_products:
>> >> -                        self.log.info("Populating table '%s' for
>> product
>> >> '%s' ('%s')",
>> >> -                                      table, product.name,
>> >> product.prefix)
>> >> -                        db("""INSERT INTO %s (%s, product) SELECT
>> %s,'%s'
>> >> FROM %s""" %
>> >> -                              (table, cols, cols, product.prefix,
>> >> temp_table_name))
>> >> -                    if table == 'permission':
>> >> -                        self.log.info("Populating table '%s' for
>> global
>> >> scope", table)
>> >> -                        db("""INSERT INTO %s (%s, product) SELECT
>> %s,'%s'
>> >> FROM %s""" %
>> >> -                              (table, cols, cols, '', temp_table_name))
>> >> -                    drop_temp_table(temp_table_name)
>> >> -
>> >> -                # migrate wiki table
>> >> -                # - populate system wikis to all products + global
>> scope
>> >> -                # - update wiki attachment product to match wiki
>> product
>> >> -                table = 'wiki'
>> >> -                temp_table_name, cols = create_temp_table(table)
>> >> -                self.log.info("Migrating wikis to global context")
>> >> -                db("""INSERT INTO %s (%s, product) SELECT %s, '' FROM
>> >> %s""" %
>> >> -                   (table, cols, cols, temp_table_name))
>> >> +               (table, cols, cols, temp_table_name))
>> >> +            self._drop_temp_table(db, temp_table_name)
>> >> +            if table == 'attachment':
>> >>                  db("""UPDATE attachment
>> >> -                         SET product=''
>> >> -                       WHERE attachment.type='wiki'""")
>> >> -
>> >> -                for wiki_name, wiki_version, wiki_product in db("""
>> >> +                         SET product=(SELECT ticket.product
>> >> +                                        FROM ticket
>> >> +                                       WHERE %(ticket.id)s=
>> attachment.id
>> >> +                                       LIMIT 1)
>> >> +                       WHERE attachment.type='ticket'
>> >> +                         AND EXISTS(SELECT ticket.product
>> >> +                                      FROM ticket
>> >> +                                     WHERE %(ticket.id)s=attachment.id
>> )
>> >> +                   """ % {'ticket.id': db.cast('ticket.id', 'text')})
>> >> +            else:
>> >> +                db("""UPDATE %(table)s
>> >> +                         SET product=(SELECT ticket.product
>> >> +                                        FROM ticket
>> >> +                                       WHERE ticket.id
>> =%(table)s.ticket)
>> >> +                   """ % {'table': table})
>> >> +
>> >> +    def _upgrade_system_tables(self, db, create_temp_table):
>> >> +        # migrate system table (except wiki which is handled
>> separately)
>> >> +        # to a new schema
>> >> +        # - create tables with the new schema
>> >> +        # - populate system tables with global configuration for each
>> >> product
>> >> +        # - exception is permission table where permissions
>> >> +        #   are also populated in global scope
>> >> +        self.log.info("Migrating system tables to a new schema")
>> >> +        for table in self.MIGRATE_TABLES:
>> >> +            if table == 'wiki':
>> >> +                continue
>> >> +            temp_table_name, cols = create_temp_table(table)
>> >> +            for product in Product.select(self.env):
>> >> +                self.log.info("Populating table '%s' for product '%s'
>> >> ('%s')",
>> >> +                              table, product.name, product.prefix)
>> >> +                db("""INSERT INTO %s (%s, product) SELECT %s,'%s' FROM
>> >> %s""" %
>> >> +                   (table, cols, cols, product.prefix,
>> temp_table_name))
>> >> +            if table == 'permission':
>> >> +                self.log.info("Populating table '%s' for global
>> scope",
>> >> table)
>> >> +                db("""INSERT INTO %s (%s, product) SELECT %s,'%s' FROM
>> >> %s""" %
>> >> +                   (table, cols, cols, '', temp_table_name))
>> >> +            self._drop_temp_table(db, temp_table_name)
>> >> +
>> >> +    def _upgrade_wikis(self, db, create_temp_table,
>> >> +                       table_columns):
>> >> +        # migrate wiki table
>> >> +        # - populate system wikis to all products + global scope
>> >> +        # - update wiki attachment product to match wiki product
>> >> +        table = 'wiki'
>> >> +        temp_table_name, cols = create_temp_table(table)
>> >> +        self.log.info("Migrating wikis to global context")
>> >> +        db("""INSERT INTO %s (%s, product) SELECT %s, '' FROM %s""" %
>> >> +           (table, cols, cols, temp_table_name))
>> >> +        db("""UPDATE attachment
>> >> +                 SET product=''
>> >> +               WHERE attachment.type='wiki'""")
>> >> +        for wiki_name, wiki_version, wiki_product in db("""
>> >>                          SELECT name, version, product FROM %s""" %
>> table):
>> >> -                    attachment_cols =
>> >> ','.join(table_columns['attachment'])
>> >> -                    if wiki_name in self.system_wiki_list:
>> >> -                        for product in all_products:
>> >> -                            db("""INSERT INTO %s (%s, product)
>> >> +            attachment_cols = ','.join(table_columns['attachment'])
>> >> +            if wiki_name in self.system_wiki_list:
>> >> +                for product in Product.select(self.env):
>> >> +                    db("""INSERT INTO %s (%s, product)
>> >>                                    SELECT %s, '%s' FROM %s
>> >>                                    WHERE name='%s' AND version=%s AND
>> >> product='%s'""" %
>> >> -                                  (table, cols, cols, product.prefix,
>> >> table,
>> >> -                                   wiki_name, wiki_version,
>> wiki_product))
>> >> -                            db("""INSERT INTO attachment (%(cols)s,
>> >> product)
>> >> +                       (table, cols, cols, product.prefix, table,
>> >> +                        wiki_name, wiki_version, wiki_product))
>> >> +                    db("""INSERT INTO attachment (%(cols)s, product)
>> >>                                    SELECT %(cols)s, '%(new_product)s'
>> >>                                      FROM attachment a
>> >>                                     WHERE type='wiki'
>> >> @@ -380,89 +417,40 @@ class MultiProductSystem(Component):
>> >>                                            wiki_name=wiki_name,
>> >>                                            old_product=wiki_product,
>> >>                                            new_product=product.prefix))
>> >> -                            self._migrate_attachments(
>> >> -                                db("""SELECT type, id, filename
>> >> +                    self._migrate_attachments(
>> >> +                        db("""SELECT type, id, filename
>> >>                                          FROM attachment
>> >>                                         WHERE type='wiki'
>> >>                                           AND id='%s'
>> >>                                           AND product='%s'
>> >>                                     """ % (wiki_name, DEFAULT_PRODUCT)),
>> >> -                                    to_product=DEFAULT_PRODUCT,
>> >> -                                    copy=True
>> >> -                            )
>> >> -                    else:
>> >> -                        self.log.info("Moving wiki page '%s' to
>> default
>> >> product", wiki_name)
>> >> -                        db("""UPDATE wiki
>> >> +                        to_product=DEFAULT_PRODUCT,
>> >> +                        copy=True
>> >> +                    )
>> >> +            else:
>> >> +                self.log.info("Moving wiki page '%s' to default
>> product",
>> >> +                              wiki_name)
>> >> +                db("""UPDATE wiki
>> >>                                SET product='%s'
>> >>                                WHERE name='%s' AND version=%s AND
>> >> product='%s'
>> >>                             """ % (DEFAULT_PRODUCT,
>> >>                                    wiki_name, wiki_version,
>> wiki_product))
>> >> -                        db("""UPDATE attachment
>> >> +                db("""UPDATE attachment
>> >>                                   SET product='%s'
>> >>                                 WHERE type='wiki'
>> >>                                   AND id='%s'
>> >>                                   AND product='%s'
>> >>                             """ % (DEFAULT_PRODUCT, wiki_name,
>> >> wiki_product))
>> >> -                        self._migrate_attachments(
>> >> -                            db("""SELECT type, id, filename
>> >> +                self._migrate_attachments(
>> >> +                    db("""SELECT type, id, filename
>> >>                                      FROM attachment
>> >>                                     WHERE type='wiki'
>> >>                                       AND id='%s'
>> >>                                       AND product='%s'
>> >>                                 """ % (wiki_name, DEFAULT_PRODUCT)),
>> >> -                            to_product=DEFAULT_PRODUCT,
>> >> -                        )
>> >> -
>> >> -                drop_temp_table(temp_table_name)
>> >> -
>> >> -                # soft link existing repositories to default product
>> >> -                repositories_linked = []
>> >> -                for id, name in db("""SELECT id, value FROM repository
>> >> -                                      WHERE name='name'"""):
>> >> -                    if id in repositories_linked:
>> >> -                        continue
>> >> -                    db("""INSERT INTO repository (id, name, value)
>> >> -                          VALUES (%s, 'product', '%s')""" %
>> >> -                       (id, DEFAULT_PRODUCT))
>> >> -                    repositories_linked.append(id)
>> >> -                    self.log.info("Repository '%s' (%s) soft linked to
>> >> default product", name, id)
>> >> -
>> >> -                # Update system tables
>> >> -                # Upgrade schema
>> >> -                self.log.info("Migrating system tables to a new
>> schema")
>> >> -                for table in SYSTEM_TABLES:
>> >> -                    temp_table_name, cols = create_temp_table(table)
>> >> -                    db("""INSERT INTO %s (%s, product)
>> >> -                          SELECT %s,'' FROM %s""" %
>> >> -                       (table, cols, cols, temp_table_name))
>> >> -                    drop_temp_table(temp_table_name)
>> >> -
>> >> -                # enable multi product hooks in environment
>> configuration
>> >> -                import multiproduct.hooks
>> >> -                import inspect
>> >> -                config_update = False
>> >> -                hook_path =
>> >> os.path.realpath(inspect.getsourcefile(multiproduct.hooks))
>> >> -                if not 'environment_factory' in
>> self.env.config['trac']:
>> >> -                    self.env.config['trac'].set('environment_factory',
>> >> hook_path)
>> >> -                    config_update = True
>> >> -                if not 'request_factory' in self.env.config['trac']:
>> >> -                    self.env.config['trac'].set('request_factory',
>> >> hook_path)
>> >> -                    config_update = True
>> >> -                if config_update:
>> >> -                    self.log.info("Enabling multi product hooks in
>> >> environment configuration")
>> >> -                    self.env.config.save()
>> >> -
>> >> -                db_installed_version = self._update_db_version(db, 3)
>> >> -
>> >> -            if db_installed_version < 4:
>> >> -                self.log.debug("creating additional db tables for %s
>> >> plugin." %
>> >> -                               PLUGIN_NAME)
>> >> -                db_connector, dummy =
>> >> DatabaseManager(self.env)._get_connector()
>> >> -                for statement in
>> >> db_connector.to_sql(ProductSetting._get_schema()):
>> >> -                    db(statement)
>> >> -                db_installed_version = self._update_db_version(db, 4)
>> >> -
>> >> -            self.env.enable_multiproduct_schema(True)
>> >> +                    to_product=DEFAULT_PRODUCT,
>> >> +                )
>> >> +        self._drop_temp_table(db, temp_table_name)
>> >>
>> >>      def _migrate_attachments(self, attachments, to_product=None,
>> >> copy=False):
>> >>          for type, id, filename in attachments:
>> >> @@ -498,6 +486,56 @@ class MultiProductSystem(Component):
>> >>                      filename, type, id, str(err)
>> >>                  )
>> >>
>> >> +    def _soft_link_repositories_to_default_product(self, db):
>> >> +        # soft link existing repositories to default product
>> >> +        repositories_linked = []
>> >> +        for id, name in db("""SELECT id, value FROM repository
>> >> +                                      WHERE name='name'"""):
>> >> +            if id in repositories_linked:
>> >> +                continue
>> >> +            db("""INSERT INTO repository (id, name, value)
>> >> +                          VALUES (%s, 'product', '%s')""" %
>> >> +               (id, DEFAULT_PRODUCT))
>> >> +            repositories_linked.append(id)
>> >> +            self.log.info("Repository '%s' (%s) soft linked to default
>> >> product",
>> >> +                          name, id)
>> >> +
>> >> +    def _upgrade_table_system(self, SYSTEM_TABLES, create_temp_table,
>> db):
>> >> +        # Update system tables
>> >> +        # Upgrade schema
>> >> +        self.log.info("Migrating system tables to a new schema")
>> >> +        for table in SYSTEM_TABLES:
>> >> +            temp_table_name, cols = create_temp_table(table)
>> >> +            db("""INSERT INTO %s (%s, product)
>> >> +                          SELECT %s,'' FROM %s""" %
>> >> +               (table, cols, cols, temp_table_name))
>> >> +            self._drop_temp_table(db, temp_table_name)
>> >> +
>> >> +    def _enable_multiproduct_hooks(self):
>> >> +        # enable multi product hooks in environment configuration
>> >> +        import multiproduct.hooks
>> >> +        import inspect
>> >> +
>> >> +        config_update = False
>> >> +        hook_path =
>> >> os.path.realpath(inspect.getsourcefile(multiproduct.hooks))
>> >> +        if not 'environment_factory' in self.env.config['trac']:
>> >> +            self.env.config['trac'].set('environment_factory',
>> hook_path)
>> >> +            config_update = True
>> >> +        if not 'request_factory' in self.env.config['trac']:
>> >> +            self.env.config['trac'].set('request_factory', hook_path)
>> >> +            config_update = True
>> >> +        if config_update:
>> >> +            self.log.info(
>> >> +                "Enabling multi product hooks in environment
>> >> configuration")
>> >> +            self.env.config.save()
>> >> +
>> >> +    def _create_product_tables_for_plugins(self, db):
>> >> +        self.log.debug("creating additional db tables for %s plugin." %
>> >> +                       PLUGIN_NAME)
>> >> +        db_connector, dummy =
>> DatabaseManager(self.env)._get_connector()
>> >> +        for statement in
>> >> db_connector.to_sql(ProductSetting._get_schema()):
>> >> +            db(statement)
>> >> +
>> >>      # IResourceChangeListener methods
>> >>      def match_resource(self, resource):
>> >>          return isinstance(resource, Product)
>> >>
>> >> Modified: bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py
>> >> URL:
>> >>
>> http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py?rev=1476118&r1=1476117&r2=1476118&view=diff
>> >>
>> >>
>> ==============================================================================
>> >> --- bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py (original)
>> >> +++ bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py Fri Apr 26
>> >> 09:10:14 2013
>> >> @@ -33,6 +33,7 @@ from trac.test import Environment
>> >>  from trac.ticket import Ticket
>> >>  from trac.wiki import WikiPage
>> >>
>> >> +from multiproduct.api import MultiProductSystem
>> >>  from multiproduct.env import ProductEnvironment
>> >>  from multiproduct.model import Product
>> >>
>> >> @@ -43,13 +44,9 @@ BLOODHOUND_TABLES = (
>> >>  )
>> >>
>> >>  TABLES_WITH_PRODUCT_FIELD = (
>> >> -    'component',
>> >> -    'milestone',
>> >> -    'version',
>> >> -    'enum',
>> >> -    'permission',
>> >> -    'wiki',
>> >> -    'report',
>> >> +    'ticket', 'ticket_change', 'ticket_custom', 'attachment',
>> 'component',
>> >> +    'milestone', 'wiki', 'report',
>> >> +    'version', 'enum', 'permission', 'system',
>> >>  )
>> >>
>> >>
>> >> @@ -57,12 +54,12 @@ class EnvironmentUpgradeTestCase(unittes
>> >>      def setUp(self):
>> >>          self.env_path = tempfile.mkdtemp('multiproduct-tempenv')
>> >>          self.env = Environment(self.env_path, create=True)
>> >> -        self.enabled_components = []
>> >>          DummyPlugin.version = 1
>> >>
>> >> -    def test_upgrade_environment(self):
>> >> +    def test_can_upgrade_environment_with_multi_product_disabled(self):
>> >>          self.env.upgrade()
>> >>
>> >> +        # Multiproduct was not enabled so multiproduct tables should
>> not
>> >> exist
>> >>          with self.env.db_direct_transaction as db:
>> >>              for table in BLOODHOUND_TABLES:
>> >>                  with self.assertFailsWithMissingTable():
>> >> @@ -72,7 +69,7 @@ class EnvironmentUpgradeTestCase(unittes
>> >>                  with self.assertFailsWithMissingColumn():
>> >>                      db("SELECT product FROM %s" % table)
>> >>
>> >> -    def test_upgrade_environment_to_multiproduct(self):
>> >> +    def
>> >> test_upgrade_creates_multi_product_tables_and_adds_product_column(self):
>> >>          self._enable_multiproduct()
>> >>          self.env.upgrade()
>> >>
>> >> @@ -83,89 +80,38 @@ class EnvironmentUpgradeTestCase(unittes
>> >>              for table in TABLES_WITH_PRODUCT_FIELD:
>> >>                  db("SELECT product FROM %s" % table)
>> >>
>> >> -    def test_upgrade_plugin(self):
>> >> -        self._enable_component(DummyPlugin)
>> >> -        self.env.upgrade()
>> >> -
>> >> -        with self.env.db_direct_transaction as db:
>> >> -            db("SELECT v1 FROM dummy_table")
>> >> -            with self.assertFailsWithMissingColumn():
>> >> -                db("SELECT v2 FROM dummy_table")
>> >> -
>> >> -        DummyPlugin.version = 2
>> >> -        self.env.upgrade()
>> >> -
>> >> -        with self.env.db_direct_transaction as db:
>> >> -            db("SELECT v2 FROM dummy_table")
>> >> -
>> >> -    def test_upgrade_plugin_to_multiproduct(self):
>> >> +    def test_upgrade_creates_default_product(self):
>> >>          self._enable_multiproduct()
>> >> -        self._enable_component(DummyPlugin)
>> >>          self.env.upgrade()
>> >>
>> >> -        with self.env.db_direct_transaction as db:
>> >> -            db("SELECT * FROM dummy_table")
>> >> -            db("""SELECT * FROM "@_dummy_table" """)
>> >> +        products = Product.select(self.env)
>> >> +        self.assertEqual(len(products), 1)
>> >>
>> >> -    def test_upgrade_existing_plugin_to_multiproduct(self):
>> >> -        self._enable_component(DummyPlugin)
>> >> -        self.env.upgrade()
>> >> -        with self.env.db_direct_transaction as db:
>> >> -            with self.assertFailsWithMissingTable():
>> >> -                db("""SELECT * FROM "@_dummy_table" """)
>> >> -
>> >> -        self._enable_multiproduct()
>> >> -        self.env.upgrade()
>> >> -        with self.env.db_direct_transaction as db:
>> >> -            db("SELECT * FROM dummy_table")
>> >> -            db("""SELECT * FROM "@_dummy_table" """)
>> >> -
>> >> -    def test_upgrading_existing_plugin_leaves_data_in_global_env(self):
>> >> -        self._enable_component(DummyPlugin)
>> >> -        self.env.upgrade()
>> >> -        with self.env.db_direct_transaction as db:
>> >> -            for i in range(5):
>> >> -                db("INSERT INTO dummy_table (v1) VALUES ('%d')" % i)
>> >> -            self.assertEqual(
>> >> -                len(db("SELECT * FROM dummy_table")), 5)
>> >> -
>> >> -        self._enable_multiproduct()
>> >> -        self.env.upgrade()
>> >> -        with self.env.db_direct_transaction as db:
>> >> -            self.assertEqual(
>> >> -                len(db('SELECT * FROM "dummy_table"')), 5)
>> >> -            self.assertEqual(
>> >> -                len(db('SELECT * FROM "@_dummy_table"')), 0)
>> >> -
>> >> -    def test_creating_new_product_calls_environment_created(self):
>> >> -        self._enable_component(DummyPlugin)
>> >> -        self._enable_multiproduct()
>> >> -        self.env.upgrade()
>> >> -
>> >> -        prod = Product(self.env)
>> >> -        prod.update_field_dict(dict(prefix='p1'))
>> >> -        ProductEnvironment(self.env, prod, create=True)
>> >> -        with self.env.db_direct_transaction as db:
>> >> -            db('SELECT * FROM "p1_dummy_table"')
>> >> -
>> >> -    def test_upgrade_moves_tickets_to_default_product(self):
>> >> +    def
>> >> test_upgrade_moves_tickets_and_related_objects_to_default_prod(self):
>> >> +        self._add_custom_field('custom_field')
>> >>          with self.env.db_direct_transaction as db:
>> >>              db("""INSERT INTO ticket (id) VALUES (1)""")
>> >>              db("""INSERT INTO attachment (type, id)
>> >> -                         VALUES ('ticket', '1')""")
>> >> +                       VALUES ('ticket', '1')""")
>> >> +            db("""INSERT INTO ticket_custom (ticket, name, value)
>> >> +                       VALUES (1, 'custom_field', '42')""")
>> >> +            db("""INSERT INTO ticket_change (ticket, time, field)
>> >> +                       VALUES (1, 42, 'summary')""")
>> >>
>> >>          self._enable_multiproduct()
>> >>          self.env.upgrade()
>> >>
>> >> -        with self.env.db_direct_transaction as db:
>> >> -            self.assertEqual(
>> >> -                len(db("""SELECT * FROM ticket WHERE product='@'""")),
>> 1)
>> >> -            self.assertEqual(
>> >> -                len(db("""SELECT * FROM attachment
>> >> -                          WHERE product='@'
>> >> -                            AND type='ticket'""")), 1)
>> >> +        with self.product('@'):
>> >> +            ticket = Ticket(self.env, 1)
>> >> +            attachments = list(Attachment.select(self.env,
>> >> +                                                 ticket.resource.realm,
>> >> +                                                 ticket.resource.id))
>> >> +            self.assertEqual(len(attachments), 1)
>> >> +            self.assertEqual(ticket['custom_field'], '42')
>> >> +            changes = ticket.get_changelog()
>> >> +            self.assertEqual(len(changes), 3)
>> >>
>> >> -    def test_upgrade_moves_wikis_to_default_product(self):
>> >> +    def test_upgrade_moves_custom_wikis_to_default_product(self):
>> >>          with self.env.db_direct_transaction as db:
>> >>              db("""INSERT INTO wiki (name, version) VALUES ('MyPage',
>> >> 1)""")
>> >>              db("""INSERT INTO attachment (type, id)
>> >> @@ -205,23 +151,44 @@ class EnvironmentUpgradeTestCase(unittes
>> >>                             WHERE product=''
>> >>                               AND type='wiki'""")), 1)
>> >>
>> >> -    def test_can_upgrade_database_with_orphaned_attachments(self):
>> >> +    def
>> >> test_upgrade_copies_content_of_system_tables_to_all_products(self):
>> >> +        mp = MultiProductSystem(self.env)
>> >>          with self.env.db_direct_transaction as db:
>> >> -            db("""INSERT INTO attachment (id, type)
>> >> -                       VALUES ('5', 'ticket')""")
>> >> -            db("""INSERT INTO attachment (id, type)
>> >> -                       VALUES ('MyWiki', 'wiki')""")
>> >> +            mp._add_column_product_to_ticket(db)
>> >> +            mp._create_multiproduct_tables(db)
>> >> +            mp._update_db_version(db, 1)
>> >> +            for i in range(1, 6):
>> >> +                db("""INSERT INTO bloodhound_product (prefix, name)
>> >> +                           VALUES ('p%d', 'Product 1')""" % i)
>> >> +            for table in ('component', 'milestone', 'enum', 'version',
>> >> +                          'permission', 'report'):
>> >> +                db("""DELETE FROM %s""" % table)
>> >> +            db("""INSERT INTO component (name) VALUES ('foobar')""")
>> >> +            db("""INSERT INTO milestone (name) VALUES ('foobar')""")
>> >> +            db("""INSERT INTO version (name) VALUES ('foobar')""")
>> >> +            db("""INSERT INTO enum (type, name) VALUES ('a', 'b')""")
>> >> +            db("""INSERT INTO permission VALUES ('x',
>> 'TICKET_VIEW')""")
>> >> +            db("""INSERT INTO wiki (name, version) VALUES ('WikiStart',
>> >> 1)""")
>> >> +            db("""INSERT INTO report (title) VALUES ('x')""")
>> >>
>> >>          self._enable_multiproduct()
>> >>          self.env.upgrade()
>> >>
>> >> -    def test_can_upgrade_database_with_text_attachment_ids(self):
>> >>          with self.env.db_direct_transaction as db:
>> >> -            db("""INSERT INTO attachment (id, type)
>> >> -                       VALUES ('abc', 'ticket')""")
>> >> -
>> >> -        self._enable_multiproduct()
>> >> -        self.env.upgrade()
>> >> +            for table in ('component', 'milestone', 'version', 'enum',
>> >> +                          'report'):
>> >> +                rows = db("SELECT * FROM %s" % table)
>> >> +                self.assertEqual(
>> >> +                    len(rows), 6,
>> >> +                    "Wrong number of lines in %s (%d instead of
>> %d)\n%s"
>> >> +                    % (table, len(rows), 6, rows))
>> >> +            for table in ('wiki', 'permission'):
>> >> +                # Permissions and wikis also hold rows for global
>> product.
>> >> +                rows = db("SELECT * FROM %s" % table)
>> >> +                self.assertEqual(
>> >> +                    len(rows), 7,
>> >> +                    "Wrong number of lines in %s (%d instead of
>> %d)\n%s"
>> >> +                    % (table, len(rows), 7, rows))
>> >>
>> >>      def
>> test_upgrading_database_moves_attachment_to_correct_product(self):
>> >>          ticket = self.insert_ticket('ticket')
>> >> @@ -258,26 +225,148 @@ class EnvironmentUpgradeTestCase(unittes
>> >>          for attachment in attachments:
>> >>              self.assertEqual(attachment.open().read(), 'Hello World!')
>> >>
>> >> +    def
>> >> test_can_upgrade_database_with_ticket_attachment_with_text_ids(self):
>> >> +        with self.env.db_direct_transaction as db:
>> >> +            db("""INSERT INTO attachment (id, type)
>> >> +                       VALUES ('abc', 'ticket')""")
>> >> +
>> >> +        self._enable_multiproduct()
>> >> +        self.env.upgrade()
>> >> +
>> >> +    def test_can_upgrade_database_with_orphaned_attachments(self):
>> >> +        with self.env.db_direct_transaction as db:
>> >> +            db("""INSERT INTO attachment (id, type)
>> >> +                       VALUES ('5', 'ticket')""")
>> >> +            db("""INSERT INTO attachment (id, type)
>> >> +                       VALUES ('MyWiki', 'wiki')""")
>> >> +
>> >> +        self._enable_multiproduct()
>> >> +        self.env.upgrade()
>> >> +
>> >> +    def test_can_upgrade_multi_product_from_v1(self):
>> >> +        mp = MultiProductSystem(self.env)
>> >> +        with self.env.db_direct_transaction as db:
>> >> +            mp._add_column_product_to_ticket(db)
>> >> +            mp._create_multiproduct_tables(db)
>> >> +            mp._update_db_version(db, 1)
>> >> +
>> >> +            db("""INSERT INTO bloodhound_product (prefix, name)
>> >> +                       VALUES ('p1', 'Product 1')""")
>> >> +            db("""INSERT INTO ticket (id, product)
>> >> +                       VALUES (1, 'Product 1')""")
>> >> +
>> >> +        self._enable_multiproduct()
>> >> +        self.env.upgrade()
>> >> +
>> >> +        with self.product('p1'):
>> >> +            Ticket(self.env, 1)
>> >> +
>> >> +    def test_can_upgrade_multi_product_from_v2(self):
>> >> +        mp = MultiProductSystem(self.env)
>> >> +        with self.env.db_direct_transaction as db:
>> >> +            mp._add_column_product_to_ticket(db)
>> >> +            mp._create_multiproduct_tables(db)
>> >> +            mp._replace_product_on_ticket_with_product_prefix(db)
>> >> +            mp._update_db_version(db, 2)
>> >> +
>> >> +            db("""INSERT INTO bloodhound_product (prefix, name)
>> >> +                       VALUES ('p1', 'Product 1')""")
>> >> +            db("""INSERT INTO ticket (id, product)
>> >> +                       VALUES (1, 'p1')""")
>> >> +            db("""INSERT INTO ticket (id)
>> >> +                       VALUES (2)""")
>> >> +
>> >> +        self._enable_multiproduct()
>> >> +        self.env.upgrade()
>> >> +
>> >> +        with self.product('p1'):
>> >> +            Ticket(self.env, 1)
>> >> +        with self.product('@'):
>> >> +            Ticket(self.env, 2)
>> >> +
>> >> +    def test_upgrade_plugin(self):
>> >> +        self._enable_component(DummyPlugin)
>> >> +        self.env.upgrade()
>> >> +
>> >> +        with self.env.db_direct_transaction as db:
>> >> +            db("SELECT v1 FROM dummy_table")
>> >> +            with self.assertFailsWithMissingColumn():
>> >> +                db("SELECT v2 FROM dummy_table")
>> >> +
>> >> +        DummyPlugin.version = 2
>> >> +        self.env.upgrade()
>> >> +
>> >> +        with self.env.db_direct_transaction as db:
>> >> +            db("SELECT v2 FROM dummy_table")
>> >> +
>> >> +    def test_upgrade_plugin_to_multiproduct(self):
>> >> +        self._enable_multiproduct()
>> >> +        self._enable_component(DummyPlugin)
>> >> +        self.env.upgrade()
>> >> +
>> >> +        with self.env.db_direct_transaction as db:
>> >> +            db("SELECT * FROM dummy_table")
>> >> +            db("""SELECT * FROM "@_dummy_table" """)
>> >> +
>> >> +    def test_upgrade_existing_plugin_to_multiproduct(self):
>> >> +        self._enable_component(DummyPlugin)
>> >> +        self.env.upgrade()
>> >> +        with self.env.db_direct_transaction as db:
>> >> +            with self.assertFailsWithMissingTable():
>> >> +                db("""SELECT * FROM "@_dummy_table" """)
>> >> +
>> >> +        self._enable_multiproduct()
>> >> +        self.env.upgrade()
>> >> +        with self.env.db_direct_transaction as db:
>> >> +            db("SELECT * FROM dummy_table")
>> >> +            db("""SELECT * FROM "@_dummy_table" """)
>> >> +
>> >> +    def test_upgrading_existing_plugin_leaves_data_in_global_env(self):
>> >> +        self._enable_component(DummyPlugin)
>> >> +        self.env.upgrade()
>> >> +        with self.env.db_direct_transaction as db:
>> >> +            for i in range(5):
>> >> +                db("INSERT INTO dummy_table (v1) VALUES ('%d')" % i)
>> >> +            self.assertEqual(
>> >> +                len(db("SELECT * FROM dummy_table")), 5)
>> >> +
>> >> +        self._enable_multiproduct()
>> >> +        self.env.upgrade()
>> >> +        with self.env.db_direct_transaction as db:
>> >> +            self.assertEqual(
>> >> +                len(db('SELECT * FROM "dummy_table"')), 5)
>> >> +            self.assertEqual(
>> >> +                len(db('SELECT * FROM "@_dummy_table"')), 0)
>> >> +
>> >> +    def test_creating_new_product_calls_environment_created(self):
>> >> +        self._enable_component(DummyPlugin)
>> >> +        self._enable_multiproduct()
>> >> +        self.env.upgrade()
>> >> +
>> >> +        prod = Product(self.env)
>> >> +        prod.update_field_dict(dict(prefix='p1'))
>> >> +        ProductEnvironment(self.env, prod, create=True)
>> >> +        with self.env.db_direct_transaction as db:
>> >> +            db('SELECT * FROM "p1_dummy_table"')
>> >> +
>> >>      def _enable_multiproduct(self):
>> >> -        self.env.config.set('components', 'multiproduct.*', 'enabled')
>> >> -        self.env.config.save()
>> >> -        self._reload_environment()
>> >> -        self._reenable_components()
>> >> +        self._update_config('components', 'multiproduct.*', 'enabled')
>> >> +
>> >> +    def _add_custom_field(self, field_name):
>> >> +        self._update_config('ticket-custom', field_name, 'text')
>> >>
>> >>      def _enable_component(self, cls):
>> >> -        self.env.config.set('components',
>> >> -                            '%s.%s' % (cls.__module__, cls.__name__),
>> >> -                            'enabled')
>> >> -        self.enabled_components.append(cls)
>> >> -        self.env.compmgr.enabled[cls] = True
>> >> +        self._update_config(
>> >> +            'components',
>> >> +            '%s.%s' % (cls.__module__, cls.__name__),
>> >> +            'enabled'
>> >> +        )
>> >>
>> >> -    def _reload_environment(self):
>> >> +    def _update_config(self, section, key, value):
>> >> +        self.env.config.set(section, key, value)
>> >> +        self.env.config.save()
>> >>          self.env = Environment(self.env_path)
>> >>
>> >> -    def _reenable_components(self):
>> >> -        for cls in self.enabled_components:
>> >> -            self.env.compmgr.enabled[cls] = True
>> >> -
>> >>      def _create_file_with_content(self, content):
>> >>          filename = str(uuid.uuid4())[:6]
>> >>          path = os.path.join(self.env_path, filename)
>> >>
>> >>
>> >>
>> >
>> > One of the unit tests added in this changeset is now failing on the
>> trunk.
>> > It looks like the test is only correct for the number of permissions
>> > associated with "anonymous" and doesn't take into account the permissions
>> > assigned to "authenticated". Just wanted to ask what is the best way to
>> fix
>> > this rather than guessing at the intention. Thanks!
>> >
>> > ======================================================================
>> > FAIL: test_upgrade_copies_content_of_system_tables_to_all_products
>> > (tests.upgrade.EnvironmentUpgradeTestCase)
>> > ----------------------------------------------------------------------
>> > Traceback (most recent call last):
>> >   File
>> >
>> "/home/user/Workspace/bh579/bloodhound/bloodhound_multiproduct/tests/upgrade.py",
>> > line 197, in test_upgrade_copies_content_of_system_tables_to_all_products
>> >     % (table, len(rows), 7, rows))
>> > AssertionError: Wrong number of lines in permission (21 instead of 7)
>> > [(u'anonymous', u'PRODUCT_VIEW', u''), (u'anonymous', u'PRODUCT_VIEW',
>> u'@'),
>> > (u'anonymous', u'PRODUCT_VIEW', u'p1'), (u'anonymous', u'PRODUCT_VIEW',
>> > u'p2'), (u'anonymous', u'PRODUCT_VIEW', u'p3'), (u'anonymous',
>> > u'PRODUCT_VIEW', u'p4'), (u'anonymous', u'PRODUCT_VIEW', u'p5'),
>> > (u'authenticated', u'PRODUCT_VIEW', u''), (u'authenticated',
>> > u'PRODUCT_VIEW', u'@'), (u'authenticated', u'PRODUCT_VIEW', u'p1'),
>> > (u'authenticated', u'PRODUCT_VIEW', u'p2'), (u'authenticated',
>> > u'PRODUCT_VIEW', u'p3'), (u'authenticated', u'PRODUCT_VIEW', u'p4'),
>> > (u'authenticated', u'PRODUCT_VIEW', u'p5'), (u'x', u'TICKET_VIEW', u''),
>> > (u'x', u'TICKET_VIEW', u'@'), (u'x', u'TICKET_VIEW', u'p1'), (u'x',
>> > u'TICKET_VIEW', u'p2'), (u'x', u'TICKET_VIEW', u'p3'), (u'x',
>> > u'TICKET_VIEW', u'p4'), (u'x', u'TICKET_VIEW', u'p5')]
>>
>
>
> Thanks! If you push that change, then we'll only have two test failures in
> the MultiProduct test cases. It looks like the two failures may have to do
> with the ambiguity between MultiProduct TracLinks and InterTracLinks, which
> is an issue that I believe you've already raised on the list. If that is
> the case, maybe we just want to mark these as "skip" for now? What do you
> think?
>
> ======================================================================
> FAIL: test (tests.wiki.formatter.ProductWikiTestCase)
> Test Another arbitrary protocol Link
> ----------------------------------------------------------------------
> Traceback (most recent call last):
>   File
> "/home/user/Workspace/bh579/bloodhound/trac/trac/wiki/tests/formatter.py",
> line 209, in test
>     % (msg, self.file, self.line, self.title, formatter.flavor))
> AssertionError:
> ========== expected: ==========
> <p>
> <a class="ext-link" href="svn+ssh://secureserver.org"><span
> class="icon"></span>svn+ssh://secureserver.org</a>
> <a class="ext-link" href="svn+ssh://secureserver.org"><span
> class="icon"></span>SVN link</a>
> <a class="ext-link" href="rfc-2396.compatible://link"><span
> class="icon"></span>rfc-2396.compatible://link</a>
> <a class="ext-link" href="rfc-2396.compatible://link"><span
> class="icon"></span>RFC 2396</a>
> <a class="ext-link" href="rfc-2396+under_score://link"><span
> class="icon"></span>rfc-2396+under_score://link</a>
> <a class="ext-link" href="rfc-2396+under_score://link"><span
> class="icon"></span>underscore</a>
> unsafe://scheme is not rendered
> </p>
>
>
> ========== actual: ==========
> <p>
> <a class="ext-link" href="svn+ssh://secureserver.org"><span
> class="icon"></span>svn+ssh://secureserver.org</a>
> <a class="ext-link" href="svn+ssh://secureserver.org"><span
> class="icon"></span>SVN link</a>
> <a class="missing product">rfc-2396</a>.compatible://link
> <a class="ext-link" href="rfc-2396.compatible://link"><span
> class="icon"></span>RFC 2396</a>
> <a class="missing product">rfc-2396</a>+under_score://link
> <a class="ext-link" href="rfc-2396+under_score://link"><span
> class="icon"></span>underscore</a>
> unsafe://scheme is not rendered
> </p>
>
>
> ========== wiki: ==========
> u'
> svn+ssh://secureserver.org
> [svn+ssh://secureserver.org SVN link]
> rfc-2396.compatible://link
> [rfc-2396.compatible://link RFC 2396]
> rfc-2396+under_score://link
> [rfc-2396+under_score://link underscore]
> unsafe://scheme is not rendered
> '
> ========== diff: ==========
> --- expected
> +++ actual
> @@ -1,9 +1,9 @@
>  <p>
>  <a class="ext-link" href="svn+ssh://secureserver.org"><span
> class="icon"></span>svn+ssh://secureserver.org</a>
>  <a class="ext-link" href="svn+ssh://secureserver.org"><span
> class="icon"></span>SVN link</a>
> -<a class="ext-link" href="rfc-2396.compatible://link"><span
> class="icon"></span>rfc-2396.compatible://link</a>
> +<a class="missing product">rfc-2396</a>.compatible://link
>  <a class="ext-link" href="rfc-2396.compatible://link"><span
> class="icon"></span>RFC 2396</a>
> -<a class="ext-link" href="rfc-2396+under_score://link"><span
> class="icon"></span>rfc-2396+under_score://link</a>
> +<a class="missing product">rfc-2396</a>+under_score://link
>  <a class="ext-link" href="rfc-2396+under_score://link"><span
> class="icon"></span>underscore</a>
>  unsafe://scheme is not rendered
>  </p>
>
>
> /home/user/Workspace/bh579/bloodhound/trac/trac/wiki/tests/wiki-tests.txt:474:
> "Another arbitrary protocol Link" (default flavor)
>
> ----------------------------------------------------------------------

Re: svn commit: r1476118 - in /bloodhound/trunk/bloodhound_multiproduct: multiproduct/api.py tests/upgrade.py

Posted by Ryan Ollos <ry...@wandisco.com>.
On Tue, Jul 16, 2013 at 12:26 AM, Anze Staric <an...@gmail.com> wrote:

> The test is actually only correct for the number of permissions
> associated with user "x".
>
> Test deletes all default permissions and creates a custom set for the
> user x. Upgrading multiproduct to version 2+ was supposed to copy
> existing permissions for each of the created products. Since r1489442,
> upgrade also creates PRODUCT_VIEW for anonymous and authenticated
> users to all products, even if they had no permissions before the
> upgrade.
>
> If this is the desired behaviour, I would fix the test to only count
> permissions of user x:
>
> Index: bloodhound_multiproduct/tests/upgrade.py
> ===================================================================
> --- bloodhound_multiproduct/tests/upgrade.py (revision 1503125)
> +++ bloodhound_multiproduct/tests/upgrade.py (working copy)
> @@ -190,7 +190,7 @@
>                  # Permissions also hold rows for global product.
> -                rows = db("SELECT * FROM %s" % table)
> +                rows = db("SELECT * FROM %s WHERE username='x'" % table)
>                  self.assertEqual(
>
>
>
> On Tue, Jul 16, 2013 at 8:44 AM, Ryan Ollos <ry...@wandisco.com>
> wrote:
> > On Fri, Apr 26, 2013 at 2:10 AM, <as...@apache.org> wrote:
> >
> >> Author: astaric
> >> Date: Fri Apr 26 09:10:14 2013
> >> New Revision: 1476118
> >>
> >> URL: http://svn.apache.org/r1476118
> >> Log:
> >> Added more tests for multiproduct upgrade, refactoring.
> >>
> >> Modified:
> >>     bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py
> >>     bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py
> >>
> >> Modified: bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py
> >> URL:
> >>
> http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py?rev=1476118&r1=1476117&r2=1476118&view=diff
> >>
> >>
> ==============================================================================
> >> --- bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py
> (original)
> >> +++ bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py Fri Apr
> >> 26 09:10:14 2013
> >> @@ -30,6 +30,7 @@ from trac.attachment import Attachment
> >>  from trac.config import Option, PathOption
> >>  from trac.core import Component, TracError, implements, Interface
> >>  from trac.db import Table, Column, DatabaseManager, Index
> >> +import trac.db_default
> >>  from trac.env import IEnvironmentSetupParticipant, Environment
> >>  from trac.perm import IPermissionRequestor, PermissionCache
> >>  from trac.resource import IExternalResourceConnector,
> >> IResourceChangeListener,\
> >> @@ -97,7 +98,7 @@ class MultiProductSystem(Component):
> >>          global environment configuration.
> >>          """)
> >>
> >> -    SCHEMA = [mcls._get_schema() \
> >> +    SCHEMA = [mcls._get_schema()
> >>                for mcls in (Product, ProductResourceMap)]
> >>
> >>      # Tables which should be migrated (extended with 'product' column)
> >> @@ -202,171 +203,207 @@ class MultiProductSystem(Component):
> >>          db_installed_version = self.get_version()
> >>          with self.env.db_direct_transaction as db:
> >>              if db_installed_version < 1:
> >> -                # Initial installation
> >> -                db("ALTER TABLE ticket ADD COLUMN product TEXT")
> >> -                self.log.debug("creating initial db tables for %s
> >> plugin." %
> >> -                               PLUGIN_NAME)
> >> -                db_connector, dummy =
> >> DatabaseManager(self.env)._get_connector()
> >> -                for table in self.SCHEMA:
> >> -                    for statement in db_connector.to_sql(table):
> >> -                        db(statement)
> >> +                self._add_column_product_to_ticket(db)
> >> +                self._create_multiproduct_tables(db)
> >>                  db_installed_version = self._update_db_version(db, 1)
> >>
> >>              if db_installed_version < 2:
> >> -                from multiproduct.model import Product
> >> -                products = Product.select(self.env)
> >> -                for prod in products:
> >> -                    db("""UPDATE ticket SET product=%s
> >> -                          WHERE product=%s""", (prod.prefix, prod.name
> ))
> >> +                self._replace_product_on_ticket_with_product_prefix(db)
> >>                  db_installed_version = self._update_db_version(db, 2)
> >>
> >>              if db_installed_version < 3:
> >> -                from multiproduct.model import Product
> >> -                import trac.db_default
> >> -
> >> -                def create_temp_table(table):
> >> -                    """creates temporary table with the new schema and
> >> -                    drops original table"""
> >> -                    table_temp_name = '%s_temp' % table
> >> -                    if table == 'report':
> >> -                        cols = ','.join([c for c in
> table_columns[table]
> >> if c != 'id'])
> >> -                    else:
> >> -                        cols = ','.join(table_columns[table])
> >> -                    self.log.info("Migrating table '%s' to a new
> >> schema", table)
> >> -                    db("""CREATE TABLE %s AS SELECT %s FROM %s""" %
> >> -                          (table_temp_name, cols, table))
> >> -                    db("""DROP TABLE %s""" % table)
> >> -                    db_connector, _ =
> >> DatabaseManager(self.env)._get_connector()
> >> -                    table_schema = [t for t in table_defs if t.name ==
> >> table][0]
> >> -                    for sql in db_connector.to_sql(table_schema):
> >> -                        db(sql)
> >> -                    return table_temp_name, cols
> >> -
> >> -                def drop_temp_table(table):
> >> -                    """drops specified temporary table"""
> >> -                    db("""DROP TABLE %s""" % table)
> >> -
> >> -                TICKET_TABLES = ['ticket_change', 'ticket_custom',
> >> -                                 'attachment',
> >> -                                ]
> >>                  SYSTEM_TABLES = ['system']
> >> +                TICKET_TABLES = [
> >> +                    'ticket_change', 'ticket_custom', 'attachment',
> >> +                ]
> >> +                table_defs = self._add_product_column_to_tables(
> >> +                    self.MIGRATE_TABLES + TICKET_TABLES +
> SYSTEM_TABLES,
> >> +                    db_installed_version)
> >> +                table_columns = self._get_table_columns(table_defs)
> >> +                create_temp_table = lambda table:
> self._create_temp_table(
> >> +                    db, table, table_columns, table_defs)
> >> +
> >> +                self._insert_default_product(db)
> >> +                self._upgrade_tickets(db, TICKET_TABLES,
> >> create_temp_table)
> >> +                self._upgrade_wikis(db, create_temp_table,
> table_columns)
> >> +                self._upgrade_system_tables(db, create_temp_table)
> >> +                self._soft_link_repositories_to_default_product(db)
> >> +                self._upgrade_table_system(SYSTEM_TABLES,
> >> create_temp_table, db)
> >> +                self._enable_multiproduct_hooks()
> >>
> >> -                # extend trac default schema by adding product column
> and
> >> extending key with product
> >> -                table_defs = [copy.deepcopy(t) for t in
> >> trac.db_default.schema
> >> -                                                    if t.name in
> >> self.MIGRATE_TABLES + TICKET_TABLES + SYSTEM_TABLES]
> >> -                for t in table_defs:
> >> -                    t.columns.append(Column('product'))
> >> -                    if isinstance(t.key, list):
> >> -                        t.key = tuple(t.key) + tuple(['product'])
> >> -                    elif isinstance(t.key, tuple):
> >> -                        t.key = t.key + tuple(['product'])
> >> -                    else:
> >> -                        raise TracError("Invalid table '%s' schema key
> >> '%s' while upgrading "
> >> -                                        "plugin '%s' from version %d to
> >> %d'" %
> >> -                                        (t.name, t.key, PLUGIN_NAME,
> >> db_installed_version, 3))
> >> -                table_columns = dict()
> >> -                for table in table_defs:
> >> -                    table_columns[table.name] = [c for c in
> [column.namefor column in
> >> -                                                                [t for
> t
> >> in table_defs if t.name == table.name][0].columns]
> >> -                                                                    if
> c
> >> != 'product']
> >> -                # create default product
> >> -                self.log.info("Creating default product")
> >> -                db("""INSERT INTO bloodhound_product (prefix, name,
> >> description, owner)
> >> -                      VALUES ('%s', '%s', '%s', '')""" %
> >> -                      (DEFAULT_PRODUCT, 'Default', 'Default product'))
> >> -
> >> -                # fetch all products
> >> -                all_products = Product.select(self.env)
> >> -
> >> -                # migrate tickets that don't have product assigned to
> >> default product
> >> -                # - update ticket table product column
> >> -                # - update ticket related tables by:
> >> -                #   - upgrading schema
> >> -                #   - update product column to match ticket's product
> >> -                self.log.info("Migrating tickets w/o product to
> default
> >> product")
> >> -                db("""UPDATE ticket SET product='%s'
> >> -                      WHERE (product IS NULL OR product='')""" %
> >> DEFAULT_PRODUCT)
> >> -                self._migrate_attachments(
> >> -                    db("""SELECT a.type, a.id, a.filename
> >> +                db_installed_version = self._update_db_version(db, 3)
> >> +
> >> +            if db_installed_version < 4:
> >> +                self._create_product_tables_for_plugins(db)
> >> +                db_installed_version = self._update_db_version(db, 4)
> >> +
> >> +            self.env.enable_multiproduct_schema(True)
> >> +
> >> +    def _add_column_product_to_ticket(self, db):
> >> +        self.log.debug("Adding field product to ticket table")
> >> +        db("ALTER TABLE ticket ADD COLUMN product TEXT")
> >> +
> >> +    def _create_multiproduct_tables(self, db):
> >> +        self.log.debug("Creating initial db tables for %s plugin." %
> >> +                       PLUGIN_NAME)
> >> +        db_connector, dummy =
> DatabaseManager(self.env)._get_connector()
> >> +        for table in self.SCHEMA:
> >> +            for statement in db_connector.to_sql(table):
> >> +                db(statement)
> >> +
> >> +    def _replace_product_on_ticket_with_product_prefix(self, db):
> >> +        for prod in Product.select(self.env):
> >> +            db("""UPDATE ticket SET product=%s
> >> +                          WHERE product=%s""", (prod.prefix, prod.name
> ))
> >> +
> >> +    def _create_temp_table(self, db, table, table_columns, table_defs):
> >> +        """creates temporary table with the new schema and
> >> +        drops original table"""
> >> +        table_temp_name = '%s_temp' % table
> >> +        if table == 'report':
> >> +            cols = ','.join([c for c in table_columns[table] if c !=
> >> 'id'])
> >> +        else:
> >> +            cols = ','.join(table_columns[table])
> >> +        self.log.info("Migrating table '%s' to a new schema", table)
> >> +        db("""CREATE TABLE %s AS SELECT %s FROM %s""" %
> >> +              (table_temp_name, cols, table))
> >> +        db("""DROP TABLE %s""" % table)
> >> +        db_connector, _ = DatabaseManager(self.env)._get_connector()
> >> +        table_schema = [t for t in table_defs if t.name == table][0]
> >> +        for sql in db_connector.to_sql(table_schema):
> >> +            db(sql)
> >> +        return table_temp_name, cols
> >> +
> >> +    def _drop_temp_table(self, db, table):
> >> +        db("""DROP TABLE %s""" % table)
> >> +
> >> +    def _add_product_column_to_tables(self, tables, current_version):
> >> +        """Extend trac default schema by adding product column
> >> +        and extending key with product.
> >> +        """
> >> +        table_defs = [copy.deepcopy(t) for t in trac.db_default.schema
> >> +                      if
> >> +                      t.name in tables]
> >> +        for t in table_defs:
> >> +            t.columns.append(Column('product'))
> >> +            if isinstance(t.key, list):
> >> +                t.key = tuple(t.key) + tuple(['product'])
> >> +            elif isinstance(t.key, tuple):
> >> +                t.key = t.key + tuple(['product'])
> >> +            else:
> >> +                raise TracError(
> >> +                    "Invalid table '%s' schema key '%s' while
> upgrading "
> >> +                    "plugin '%s' from version %d to %d'" %
> >> +                    (t.name, t.key, PLUGIN_NAME, current_version, 3))
> >> +        return table_defs
> >> +
> >> +    def _get_table_columns(self, table_defs):
> >> +        table_columns = dict()
> >> +        for table in table_defs:
> >> +            table_definition = \
> >> +                [t for t in table_defs if t.name == table.name][0]
> >> +            column_names = \
> >> +                [column.name for column in table_definition.columns]
> >> +            table_columns[table.name] = \
> >> +                [c for c in column_names if c != 'product']
> >> +        return table_columns
> >> +
> >> +    def _insert_default_product(self, db):
> >> +        self.log.info("Creating default product")
> >> +        db("""INSERT INTO bloodhound_product (prefix, name,
> description,
> >> owner)
> >> +              VALUES ('%s', '%s', '%s', '')
> >> +           """ % (DEFAULT_PRODUCT, 'Default', 'Default product'))
> >> +
> >> +    def _upgrade_tickets(self, db, TICKET_TABLES, create_temp_table):
> >> +        # migrate tickets that don't have product assigned to default
> >> product
> >> +        # - update ticket table product column
> >> +        # - update ticket related tables by:
> >> +        #   - upgrading schema
> >> +        #   - update product column to match ticket's product
> >> +        self.log.info("Migrating tickets w/o product to default
> product")
> >> +        db("""UPDATE ticket SET product='%s'
> >> +                      WHERE (product IS NULL OR product='')
> >> +           """ % DEFAULT_PRODUCT)
> >> +        self._migrate_attachments(
> >> +            db("""SELECT a.type, a.id, a.filename
> >>                              FROM attachment a
> >>                        INNER JOIN ticket t ON a.id = %(t.id)s
> >>                             WHERE a.type='ticket'
> >> -                       """ % {'t.id':  db.cast('t.id', 'text')}),
> >> -                    to_product=DEFAULT_PRODUCT
> >> -                )
> >> -
> >> -                self.log.info("Migrating ticket tables to a new
> schema")
> >> -                for table in TICKET_TABLES:
> >> -                    temp_table_name, cols = create_temp_table(table)
> >> -                    db("""INSERT INTO %s (%s, product)
> >> +                       """ % {'t.id': db.cast('t.id', 'text')}),
> >> +            to_product=DEFAULT_PRODUCT
> >> +        )
> >> +        self.log.info("Migrating ticket tables to a new schema")
> >> +        for table in TICKET_TABLES:
> >> +            temp_table_name, cols = create_temp_table(table)
> >> +            db("""INSERT INTO %s (%s, product)
> >>                            SELECT %s, '' FROM %s""" %
> >> -                          (table, cols, cols, temp_table_name))
> >> -                    drop_temp_table(temp_table_name)
> >> -
> >> -                for table in TICKET_TABLES:
> >> -                    if table == 'attachment':
> >> -                        db("""
> >> -                            UPDATE attachment
> >> -                               SET product=(SELECT ticket.product
> >> -                                              FROM ticket
> >> -                                             WHERE %(ticket_id)s=
> >> attachment.id
> >> -                                             LIMIT 1)
> >> -                             WHERE attachment.type='ticket'
> >> -                               AND EXISTS(SELECT ticket.product
> >> -                                            FROM ticket
> >> -                                           WHERE %(ticket_id)s=
> >> attachment.id)
> >> -                           """ % dict(
> >> -                            ticket_id=db.cast('attachment.id',
> 'text')))
> >> -                    else:
> >> -                        db("""UPDATE %s
> >> -                              SET product=(SELECT ticket.product FROM
> >> ticket WHERE ticket.id=%s.ticket)""" %
> >> -                           (table, table))
> >> -
> >> -                # migrate system table (except wiki which is handled
> >> separately) to a
> >> -                # new schema
> >> -                # - create tables with the new schema
> >> -                # - populate system tables with global configuration
> for
> >> each product
> >> -                # - exception is permission table where permissions are
> >> also populated in
> >> -                #   global scope
> >> -                self.log.info("Migrating system tables to a new
> schema")
> >> -                for table in self.MIGRATE_TABLES:
> >> -                    if table == 'wiki':
> >> -                        continue
> >> -                    temp_table_name, cols = create_temp_table(table)
> >> -                    for product in all_products:
> >> -                        self.log.info("Populating table '%s' for
> product
> >> '%s' ('%s')",
> >> -                                      table, product.name,
> >> product.prefix)
> >> -                        db("""INSERT INTO %s (%s, product) SELECT
> %s,'%s'
> >> FROM %s""" %
> >> -                              (table, cols, cols, product.prefix,
> >> temp_table_name))
> >> -                    if table == 'permission':
> >> -                        self.log.info("Populating table '%s' for
> global
> >> scope", table)
> >> -                        db("""INSERT INTO %s (%s, product) SELECT
> %s,'%s'
> >> FROM %s""" %
> >> -                              (table, cols, cols, '', temp_table_name))
> >> -                    drop_temp_table(temp_table_name)
> >> -
> >> -                # migrate wiki table
> >> -                # - populate system wikis to all products + global
> scope
> >> -                # - update wiki attachment product to match wiki
> product
> >> -                table = 'wiki'
> >> -                temp_table_name, cols = create_temp_table(table)
> >> -                self.log.info("Migrating wikis to global context")
> >> -                db("""INSERT INTO %s (%s, product) SELECT %s, '' FROM
> >> %s""" %
> >> -                   (table, cols, cols, temp_table_name))
> >> +               (table, cols, cols, temp_table_name))
> >> +            self._drop_temp_table(db, temp_table_name)
> >> +            if table == 'attachment':
> >>                  db("""UPDATE attachment
> >> -                         SET product=''
> >> -                       WHERE attachment.type='wiki'""")
> >> -
> >> -                for wiki_name, wiki_version, wiki_product in db("""
> >> +                         SET product=(SELECT ticket.product
> >> +                                        FROM ticket
> >> +                                       WHERE %(ticket.id)s=
> attachment.id
> >> +                                       LIMIT 1)
> >> +                       WHERE attachment.type='ticket'
> >> +                         AND EXISTS(SELECT ticket.product
> >> +                                      FROM ticket
> >> +                                     WHERE %(ticket.id)s=attachment.id
> )
> >> +                   """ % {'ticket.id': db.cast('ticket.id', 'text')})
> >> +            else:
> >> +                db("""UPDATE %(table)s
> >> +                         SET product=(SELECT ticket.product
> >> +                                        FROM ticket
> >> +                                       WHERE ticket.id
> =%(table)s.ticket)
> >> +                   """ % {'table': table})
> >> +
> >> +    def _upgrade_system_tables(self, db, create_temp_table):
> >> +        # migrate system table (except wiki which is handled
> separately)
> >> +        # to a new schema
> >> +        # - create tables with the new schema
> >> +        # - populate system tables with global configuration for each
> >> product
> >> +        # - exception is permission table where permissions
> >> +        #   are also populated in global scope
> >> +        self.log.info("Migrating system tables to a new schema")
> >> +        for table in self.MIGRATE_TABLES:
> >> +            if table == 'wiki':
> >> +                continue
> >> +            temp_table_name, cols = create_temp_table(table)
> >> +            for product in Product.select(self.env):
> >> +                self.log.info("Populating table '%s' for product '%s'
> >> ('%s')",
> >> +                              table, product.name, product.prefix)
> >> +                db("""INSERT INTO %s (%s, product) SELECT %s,'%s' FROM
> >> %s""" %
> >> +                   (table, cols, cols, product.prefix,
> temp_table_name))
> >> +            if table == 'permission':
> >> +                self.log.info("Populating table '%s' for global
> scope",
> >> table)
> >> +                db("""INSERT INTO %s (%s, product) SELECT %s,'%s' FROM
> >> %s""" %
> >> +                   (table, cols, cols, '', temp_table_name))
> >> +            self._drop_temp_table(db, temp_table_name)
> >> +
> >> +    def _upgrade_wikis(self, db, create_temp_table,
> >> +                       table_columns):
> >> +        # migrate wiki table
> >> +        # - populate system wikis to all products + global scope
> >> +        # - update wiki attachment product to match wiki product
> >> +        table = 'wiki'
> >> +        temp_table_name, cols = create_temp_table(table)
> >> +        self.log.info("Migrating wikis to global context")
> >> +        db("""INSERT INTO %s (%s, product) SELECT %s, '' FROM %s""" %
> >> +           (table, cols, cols, temp_table_name))
> >> +        db("""UPDATE attachment
> >> +                 SET product=''
> >> +               WHERE attachment.type='wiki'""")
> >> +        for wiki_name, wiki_version, wiki_product in db("""
> >>                          SELECT name, version, product FROM %s""" %
> table):
> >> -                    attachment_cols =
> >> ','.join(table_columns['attachment'])
> >> -                    if wiki_name in self.system_wiki_list:
> >> -                        for product in all_products:
> >> -                            db("""INSERT INTO %s (%s, product)
> >> +            attachment_cols = ','.join(table_columns['attachment'])
> >> +            if wiki_name in self.system_wiki_list:
> >> +                for product in Product.select(self.env):
> >> +                    db("""INSERT INTO %s (%s, product)
> >>                                    SELECT %s, '%s' FROM %s
> >>                                    WHERE name='%s' AND version=%s AND
> >> product='%s'""" %
> >> -                                  (table, cols, cols, product.prefix,
> >> table,
> >> -                                   wiki_name, wiki_version,
> wiki_product))
> >> -                            db("""INSERT INTO attachment (%(cols)s,
> >> product)
> >> +                       (table, cols, cols, product.prefix, table,
> >> +                        wiki_name, wiki_version, wiki_product))
> >> +                    db("""INSERT INTO attachment (%(cols)s, product)
> >>                                    SELECT %(cols)s, '%(new_product)s'
> >>                                      FROM attachment a
> >>                                     WHERE type='wiki'
> >> @@ -380,89 +417,40 @@ class MultiProductSystem(Component):
> >>                                            wiki_name=wiki_name,
> >>                                            old_product=wiki_product,
> >>                                            new_product=product.prefix))
> >> -                            self._migrate_attachments(
> >> -                                db("""SELECT type, id, filename
> >> +                    self._migrate_attachments(
> >> +                        db("""SELECT type, id, filename
> >>                                          FROM attachment
> >>                                         WHERE type='wiki'
> >>                                           AND id='%s'
> >>                                           AND product='%s'
> >>                                     """ % (wiki_name, DEFAULT_PRODUCT)),
> >> -                                    to_product=DEFAULT_PRODUCT,
> >> -                                    copy=True
> >> -                            )
> >> -                    else:
> >> -                        self.log.info("Moving wiki page '%s' to
> default
> >> product", wiki_name)
> >> -                        db("""UPDATE wiki
> >> +                        to_product=DEFAULT_PRODUCT,
> >> +                        copy=True
> >> +                    )
> >> +            else:
> >> +                self.log.info("Moving wiki page '%s' to default
> product",
> >> +                              wiki_name)
> >> +                db("""UPDATE wiki
> >>                                SET product='%s'
> >>                                WHERE name='%s' AND version=%s AND
> >> product='%s'
> >>                             """ % (DEFAULT_PRODUCT,
> >>                                    wiki_name, wiki_version,
> wiki_product))
> >> -                        db("""UPDATE attachment
> >> +                db("""UPDATE attachment
> >>                                   SET product='%s'
> >>                                 WHERE type='wiki'
> >>                                   AND id='%s'
> >>                                   AND product='%s'
> >>                             """ % (DEFAULT_PRODUCT, wiki_name,
> >> wiki_product))
> >> -                        self._migrate_attachments(
> >> -                            db("""SELECT type, id, filename
> >> +                self._migrate_attachments(
> >> +                    db("""SELECT type, id, filename
> >>                                      FROM attachment
> >>                                     WHERE type='wiki'
> >>                                       AND id='%s'
> >>                                       AND product='%s'
> >>                                 """ % (wiki_name, DEFAULT_PRODUCT)),
> >> -                            to_product=DEFAULT_PRODUCT,
> >> -                        )
> >> -
> >> -                drop_temp_table(temp_table_name)
> >> -
> >> -                # soft link existing repositories to default product
> >> -                repositories_linked = []
> >> -                for id, name in db("""SELECT id, value FROM repository
> >> -                                      WHERE name='name'"""):
> >> -                    if id in repositories_linked:
> >> -                        continue
> >> -                    db("""INSERT INTO repository (id, name, value)
> >> -                          VALUES (%s, 'product', '%s')""" %
> >> -                       (id, DEFAULT_PRODUCT))
> >> -                    repositories_linked.append(id)
> >> -                    self.log.info("Repository '%s' (%s) soft linked to
> >> default product", name, id)
> >> -
> >> -                # Update system tables
> >> -                # Upgrade schema
> >> -                self.log.info("Migrating system tables to a new
> schema")
> >> -                for table in SYSTEM_TABLES:
> >> -                    temp_table_name, cols = create_temp_table(table)
> >> -                    db("""INSERT INTO %s (%s, product)
> >> -                          SELECT %s,'' FROM %s""" %
> >> -                       (table, cols, cols, temp_table_name))
> >> -                    drop_temp_table(temp_table_name)
> >> -
> >> -                # enable multi product hooks in environment
> configuration
> >> -                import multiproduct.hooks
> >> -                import inspect
> >> -                config_update = False
> >> -                hook_path =
> >> os.path.realpath(inspect.getsourcefile(multiproduct.hooks))
> >> -                if not 'environment_factory' in
> self.env.config['trac']:
> >> -                    self.env.config['trac'].set('environment_factory',
> >> hook_path)
> >> -                    config_update = True
> >> -                if not 'request_factory' in self.env.config['trac']:
> >> -                    self.env.config['trac'].set('request_factory',
> >> hook_path)
> >> -                    config_update = True
> >> -                if config_update:
> >> -                    self.log.info("Enabling multi product hooks in
> >> environment configuration")
> >> -                    self.env.config.save()
> >> -
> >> -                db_installed_version = self._update_db_version(db, 3)
> >> -
> >> -            if db_installed_version < 4:
> >> -                self.log.debug("creating additional db tables for %s
> >> plugin." %
> >> -                               PLUGIN_NAME)
> >> -                db_connector, dummy =
> >> DatabaseManager(self.env)._get_connector()
> >> -                for statement in
> >> db_connector.to_sql(ProductSetting._get_schema()):
> >> -                    db(statement)
> >> -                db_installed_version = self._update_db_version(db, 4)
> >> -
> >> -            self.env.enable_multiproduct_schema(True)
> >> +                    to_product=DEFAULT_PRODUCT,
> >> +                )
> >> +        self._drop_temp_table(db, temp_table_name)
> >>
> >>      def _migrate_attachments(self, attachments, to_product=None,
> >> copy=False):
> >>          for type, id, filename in attachments:
> >> @@ -498,6 +486,56 @@ class MultiProductSystem(Component):
> >>                      filename, type, id, str(err)
> >>                  )
> >>
> >> +    def _soft_link_repositories_to_default_product(self, db):
> >> +        # soft link existing repositories to default product
> >> +        repositories_linked = []
> >> +        for id, name in db("""SELECT id, value FROM repository
> >> +                                      WHERE name='name'"""):
> >> +            if id in repositories_linked:
> >> +                continue
> >> +            db("""INSERT INTO repository (id, name, value)
> >> +                          VALUES (%s, 'product', '%s')""" %
> >> +               (id, DEFAULT_PRODUCT))
> >> +            repositories_linked.append(id)
> >> +            self.log.info("Repository '%s' (%s) soft linked to default
> >> product",
> >> +                          name, id)
> >> +
> >> +    def _upgrade_table_system(self, SYSTEM_TABLES, create_temp_table,
> db):
> >> +        # Update system tables
> >> +        # Upgrade schema
> >> +        self.log.info("Migrating system tables to a new schema")
> >> +        for table in SYSTEM_TABLES:
> >> +            temp_table_name, cols = create_temp_table(table)
> >> +            db("""INSERT INTO %s (%s, product)
> >> +                          SELECT %s,'' FROM %s""" %
> >> +               (table, cols, cols, temp_table_name))
> >> +            self._drop_temp_table(db, temp_table_name)
> >> +
> >> +    def _enable_multiproduct_hooks(self):
> >> +        # enable multi product hooks in environment configuration
> >> +        import multiproduct.hooks
> >> +        import inspect
> >> +
> >> +        config_update = False
> >> +        hook_path =
> >> os.path.realpath(inspect.getsourcefile(multiproduct.hooks))
> >> +        if not 'environment_factory' in self.env.config['trac']:
> >> +            self.env.config['trac'].set('environment_factory',
> hook_path)
> >> +            config_update = True
> >> +        if not 'request_factory' in self.env.config['trac']:
> >> +            self.env.config['trac'].set('request_factory', hook_path)
> >> +            config_update = True
> >> +        if config_update:
> >> +            self.log.info(
> >> +                "Enabling multi product hooks in environment
> >> configuration")
> >> +            self.env.config.save()
> >> +
> >> +    def _create_product_tables_for_plugins(self, db):
> >> +        self.log.debug("creating additional db tables for %s plugin." %
> >> +                       PLUGIN_NAME)
> >> +        db_connector, dummy =
> DatabaseManager(self.env)._get_connector()
> >> +        for statement in
> >> db_connector.to_sql(ProductSetting._get_schema()):
> >> +            db(statement)
> >> +
> >>      # IResourceChangeListener methods
> >>      def match_resource(self, resource):
> >>          return isinstance(resource, Product)
> >>
> >> Modified: bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py
> >> URL:
> >>
> http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py?rev=1476118&r1=1476117&r2=1476118&view=diff
> >>
> >>
> ==============================================================================
> >> --- bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py (original)
> >> +++ bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py Fri Apr 26
> >> 09:10:14 2013
> >> @@ -33,6 +33,7 @@ from trac.test import Environment
> >>  from trac.ticket import Ticket
> >>  from trac.wiki import WikiPage
> >>
> >> +from multiproduct.api import MultiProductSystem
> >>  from multiproduct.env import ProductEnvironment
> >>  from multiproduct.model import Product
> >>
> >> @@ -43,13 +44,9 @@ BLOODHOUND_TABLES = (
> >>  )
> >>
> >>  TABLES_WITH_PRODUCT_FIELD = (
> >> -    'component',
> >> -    'milestone',
> >> -    'version',
> >> -    'enum',
> >> -    'permission',
> >> -    'wiki',
> >> -    'report',
> >> +    'ticket', 'ticket_change', 'ticket_custom', 'attachment',
> 'component',
> >> +    'milestone', 'wiki', 'report',
> >> +    'version', 'enum', 'permission', 'system',
> >>  )
> >>
> >>
> >> @@ -57,12 +54,12 @@ class EnvironmentUpgradeTestCase(unittes
> >>      def setUp(self):
> >>          self.env_path = tempfile.mkdtemp('multiproduct-tempenv')
> >>          self.env = Environment(self.env_path, create=True)
> >> -        self.enabled_components = []
> >>          DummyPlugin.version = 1
> >>
> >> -    def test_upgrade_environment(self):
> >> +    def test_can_upgrade_environment_with_multi_product_disabled(self):
> >>          self.env.upgrade()
> >>
> >> +        # Multiproduct was not enabled so multiproduct tables should
> not
> >> exist
> >>          with self.env.db_direct_transaction as db:
> >>              for table in BLOODHOUND_TABLES:
> >>                  with self.assertFailsWithMissingTable():
> >> @@ -72,7 +69,7 @@ class EnvironmentUpgradeTestCase(unittes
> >>                  with self.assertFailsWithMissingColumn():
> >>                      db("SELECT product FROM %s" % table)
> >>
> >> -    def test_upgrade_environment_to_multiproduct(self):
> >> +    def
> >> test_upgrade_creates_multi_product_tables_and_adds_product_column(self):
> >>          self._enable_multiproduct()
> >>          self.env.upgrade()
> >>
> >> @@ -83,89 +80,38 @@ class EnvironmentUpgradeTestCase(unittes
> >>              for table in TABLES_WITH_PRODUCT_FIELD:
> >>                  db("SELECT product FROM %s" % table)
> >>
> >> -    def test_upgrade_plugin(self):
> >> -        self._enable_component(DummyPlugin)
> >> -        self.env.upgrade()
> >> -
> >> -        with self.env.db_direct_transaction as db:
> >> -            db("SELECT v1 FROM dummy_table")
> >> -            with self.assertFailsWithMissingColumn():
> >> -                db("SELECT v2 FROM dummy_table")
> >> -
> >> -        DummyPlugin.version = 2
> >> -        self.env.upgrade()
> >> -
> >> -        with self.env.db_direct_transaction as db:
> >> -            db("SELECT v2 FROM dummy_table")
> >> -
> >> -    def test_upgrade_plugin_to_multiproduct(self):
> >> +    def test_upgrade_creates_default_product(self):
> >>          self._enable_multiproduct()
> >> -        self._enable_component(DummyPlugin)
> >>          self.env.upgrade()
> >>
> >> -        with self.env.db_direct_transaction as db:
> >> -            db("SELECT * FROM dummy_table")
> >> -            db("""SELECT * FROM "@_dummy_table" """)
> >> +        products = Product.select(self.env)
> >> +        self.assertEqual(len(products), 1)
> >>
> >> -    def test_upgrade_existing_plugin_to_multiproduct(self):
> >> -        self._enable_component(DummyPlugin)
> >> -        self.env.upgrade()
> >> -        with self.env.db_direct_transaction as db:
> >> -            with self.assertFailsWithMissingTable():
> >> -                db("""SELECT * FROM "@_dummy_table" """)
> >> -
> >> -        self._enable_multiproduct()
> >> -        self.env.upgrade()
> >> -        with self.env.db_direct_transaction as db:
> >> -            db("SELECT * FROM dummy_table")
> >> -            db("""SELECT * FROM "@_dummy_table" """)
> >> -
> >> -    def test_upgrading_existing_plugin_leaves_data_in_global_env(self):
> >> -        self._enable_component(DummyPlugin)
> >> -        self.env.upgrade()
> >> -        with self.env.db_direct_transaction as db:
> >> -            for i in range(5):
> >> -                db("INSERT INTO dummy_table (v1) VALUES ('%d')" % i)
> >> -            self.assertEqual(
> >> -                len(db("SELECT * FROM dummy_table")), 5)
> >> -
> >> -        self._enable_multiproduct()
> >> -        self.env.upgrade()
> >> -        with self.env.db_direct_transaction as db:
> >> -            self.assertEqual(
> >> -                len(db('SELECT * FROM "dummy_table"')), 5)
> >> -            self.assertEqual(
> >> -                len(db('SELECT * FROM "@_dummy_table"')), 0)
> >> -
> >> -    def test_creating_new_product_calls_environment_created(self):
> >> -        self._enable_component(DummyPlugin)
> >> -        self._enable_multiproduct()
> >> -        self.env.upgrade()
> >> -
> >> -        prod = Product(self.env)
> >> -        prod.update_field_dict(dict(prefix='p1'))
> >> -        ProductEnvironment(self.env, prod, create=True)
> >> -        with self.env.db_direct_transaction as db:
> >> -            db('SELECT * FROM "p1_dummy_table"')
> >> -
> >> -    def test_upgrade_moves_tickets_to_default_product(self):
> >> +    def
> >> test_upgrade_moves_tickets_and_related_objects_to_default_prod(self):
> >> +        self._add_custom_field('custom_field')
> >>          with self.env.db_direct_transaction as db:
> >>              db("""INSERT INTO ticket (id) VALUES (1)""")
> >>              db("""INSERT INTO attachment (type, id)
> >> -                         VALUES ('ticket', '1')""")
> >> +                       VALUES ('ticket', '1')""")
> >> +            db("""INSERT INTO ticket_custom (ticket, name, value)
> >> +                       VALUES (1, 'custom_field', '42')""")
> >> +            db("""INSERT INTO ticket_change (ticket, time, field)
> >> +                       VALUES (1, 42, 'summary')""")
> >>
> >>          self._enable_multiproduct()
> >>          self.env.upgrade()
> >>
> >> -        with self.env.db_direct_transaction as db:
> >> -            self.assertEqual(
> >> -                len(db("""SELECT * FROM ticket WHERE product='@'""")),
> 1)
> >> -            self.assertEqual(
> >> -                len(db("""SELECT * FROM attachment
> >> -                          WHERE product='@'
> >> -                            AND type='ticket'""")), 1)
> >> +        with self.product('@'):
> >> +            ticket = Ticket(self.env, 1)
> >> +            attachments = list(Attachment.select(self.env,
> >> +                                                 ticket.resource.realm,
> >> +                                                 ticket.resource.id))
> >> +            self.assertEqual(len(attachments), 1)
> >> +            self.assertEqual(ticket['custom_field'], '42')
> >> +            changes = ticket.get_changelog()
> >> +            self.assertEqual(len(changes), 3)
> >>
> >> -    def test_upgrade_moves_wikis_to_default_product(self):
> >> +    def test_upgrade_moves_custom_wikis_to_default_product(self):
> >>          with self.env.db_direct_transaction as db:
> >>              db("""INSERT INTO wiki (name, version) VALUES ('MyPage',
> >> 1)""")
> >>              db("""INSERT INTO attachment (type, id)
> >> @@ -205,23 +151,44 @@ class EnvironmentUpgradeTestCase(unittes
> >>                             WHERE product=''
> >>                               AND type='wiki'""")), 1)
> >>
> >> -    def test_can_upgrade_database_with_orphaned_attachments(self):
> >> +    def
> >> test_upgrade_copies_content_of_system_tables_to_all_products(self):
> >> +        mp = MultiProductSystem(self.env)
> >>          with self.env.db_direct_transaction as db:
> >> -            db("""INSERT INTO attachment (id, type)
> >> -                       VALUES ('5', 'ticket')""")
> >> -            db("""INSERT INTO attachment (id, type)
> >> -                       VALUES ('MyWiki', 'wiki')""")
> >> +            mp._add_column_product_to_ticket(db)
> >> +            mp._create_multiproduct_tables(db)
> >> +            mp._update_db_version(db, 1)
> >> +            for i in range(1, 6):
> >> +                db("""INSERT INTO bloodhound_product (prefix, name)
> >> +                           VALUES ('p%d', 'Product 1')""" % i)
> >> +            for table in ('component', 'milestone', 'enum', 'version',
> >> +                          'permission', 'report'):
> >> +                db("""DELETE FROM %s""" % table)
> >> +            db("""INSERT INTO component (name) VALUES ('foobar')""")
> >> +            db("""INSERT INTO milestone (name) VALUES ('foobar')""")
> >> +            db("""INSERT INTO version (name) VALUES ('foobar')""")
> >> +            db("""INSERT INTO enum (type, name) VALUES ('a', 'b')""")
> >> +            db("""INSERT INTO permission VALUES ('x',
> 'TICKET_VIEW')""")
> >> +            db("""INSERT INTO wiki (name, version) VALUES ('WikiStart',
> >> 1)""")
> >> +            db("""INSERT INTO report (title) VALUES ('x')""")
> >>
> >>          self._enable_multiproduct()
> >>          self.env.upgrade()
> >>
> >> -    def test_can_upgrade_database_with_text_attachment_ids(self):
> >>          with self.env.db_direct_transaction as db:
> >> -            db("""INSERT INTO attachment (id, type)
> >> -                       VALUES ('abc', 'ticket')""")
> >> -
> >> -        self._enable_multiproduct()
> >> -        self.env.upgrade()
> >> +            for table in ('component', 'milestone', 'version', 'enum',
> >> +                          'report'):
> >> +                rows = db("SELECT * FROM %s" % table)
> >> +                self.assertEqual(
> >> +                    len(rows), 6,
> >> +                    "Wrong number of lines in %s (%d instead of
> %d)\n%s"
> >> +                    % (table, len(rows), 6, rows))
> >> +            for table in ('wiki', 'permission'):
> >> +                # Permissions and wikis also hold rows for global
> product.
> >> +                rows = db("SELECT * FROM %s" % table)
> >> +                self.assertEqual(
> >> +                    len(rows), 7,
> >> +                    "Wrong number of lines in %s (%d instead of
> %d)\n%s"
> >> +                    % (table, len(rows), 7, rows))
> >>
> >>      def
> test_upgrading_database_moves_attachment_to_correct_product(self):
> >>          ticket = self.insert_ticket('ticket')
> >> @@ -258,26 +225,148 @@ class EnvironmentUpgradeTestCase(unittes
> >>          for attachment in attachments:
> >>              self.assertEqual(attachment.open().read(), 'Hello World!')
> >>
> >> +    def
> >> test_can_upgrade_database_with_ticket_attachment_with_text_ids(self):
> >> +        with self.env.db_direct_transaction as db:
> >> +            db("""INSERT INTO attachment (id, type)
> >> +                       VALUES ('abc', 'ticket')""")
> >> +
> >> +        self._enable_multiproduct()
> >> +        self.env.upgrade()
> >> +
> >> +    def test_can_upgrade_database_with_orphaned_attachments(self):
> >> +        with self.env.db_direct_transaction as db:
> >> +            db("""INSERT INTO attachment (id, type)
> >> +                       VALUES ('5', 'ticket')""")
> >> +            db("""INSERT INTO attachment (id, type)
> >> +                       VALUES ('MyWiki', 'wiki')""")
> >> +
> >> +        self._enable_multiproduct()
> >> +        self.env.upgrade()
> >> +
> >> +    def test_can_upgrade_multi_product_from_v1(self):
> >> +        mp = MultiProductSystem(self.env)
> >> +        with self.env.db_direct_transaction as db:
> >> +            mp._add_column_product_to_ticket(db)
> >> +            mp._create_multiproduct_tables(db)
> >> +            mp._update_db_version(db, 1)
> >> +
> >> +            db("""INSERT INTO bloodhound_product (prefix, name)
> >> +                       VALUES ('p1', 'Product 1')""")
> >> +            db("""INSERT INTO ticket (id, product)
> >> +                       VALUES (1, 'Product 1')""")
> >> +
> >> +        self._enable_multiproduct()
> >> +        self.env.upgrade()
> >> +
> >> +        with self.product('p1'):
> >> +            Ticket(self.env, 1)
> >> +
> >> +    def test_can_upgrade_multi_product_from_v2(self):
> >> +        mp = MultiProductSystem(self.env)
> >> +        with self.env.db_direct_transaction as db:
> >> +            mp._add_column_product_to_ticket(db)
> >> +            mp._create_multiproduct_tables(db)
> >> +            mp._replace_product_on_ticket_with_product_prefix(db)
> >> +            mp._update_db_version(db, 2)
> >> +
> >> +            db("""INSERT INTO bloodhound_product (prefix, name)
> >> +                       VALUES ('p1', 'Product 1')""")
> >> +            db("""INSERT INTO ticket (id, product)
> >> +                       VALUES (1, 'p1')""")
> >> +            db("""INSERT INTO ticket (id)
> >> +                       VALUES (2)""")
> >> +
> >> +        self._enable_multiproduct()
> >> +        self.env.upgrade()
> >> +
> >> +        with self.product('p1'):
> >> +            Ticket(self.env, 1)
> >> +        with self.product('@'):
> >> +            Ticket(self.env, 2)
> >> +
> >> +    def test_upgrade_plugin(self):
> >> +        self._enable_component(DummyPlugin)
> >> +        self.env.upgrade()
> >> +
> >> +        with self.env.db_direct_transaction as db:
> >> +            db("SELECT v1 FROM dummy_table")
> >> +            with self.assertFailsWithMissingColumn():
> >> +                db("SELECT v2 FROM dummy_table")
> >> +
> >> +        DummyPlugin.version = 2
> >> +        self.env.upgrade()
> >> +
> >> +        with self.env.db_direct_transaction as db:
> >> +            db("SELECT v2 FROM dummy_table")
> >> +
> >> +    def test_upgrade_plugin_to_multiproduct(self):
> >> +        self._enable_multiproduct()
> >> +        self._enable_component(DummyPlugin)
> >> +        self.env.upgrade()
> >> +
> >> +        with self.env.db_direct_transaction as db:
> >> +            db("SELECT * FROM dummy_table")
> >> +            db("""SELECT * FROM "@_dummy_table" """)
> >> +
> >> +    def test_upgrade_existing_plugin_to_multiproduct(self):
> >> +        self._enable_component(DummyPlugin)
> >> +        self.env.upgrade()
> >> +        with self.env.db_direct_transaction as db:
> >> +            with self.assertFailsWithMissingTable():
> >> +                db("""SELECT * FROM "@_dummy_table" """)
> >> +
> >> +        self._enable_multiproduct()
> >> +        self.env.upgrade()
> >> +        with self.env.db_direct_transaction as db:
> >> +            db("SELECT * FROM dummy_table")
> >> +            db("""SELECT * FROM "@_dummy_table" """)
> >> +
> >> +    def test_upgrading_existing_plugin_leaves_data_in_global_env(self):
> >> +        self._enable_component(DummyPlugin)
> >> +        self.env.upgrade()
> >> +        with self.env.db_direct_transaction as db:
> >> +            for i in range(5):
> >> +                db("INSERT INTO dummy_table (v1) VALUES ('%d')" % i)
> >> +            self.assertEqual(
> >> +                len(db("SELECT * FROM dummy_table")), 5)
> >> +
> >> +        self._enable_multiproduct()
> >> +        self.env.upgrade()
> >> +        with self.env.db_direct_transaction as db:
> >> +            self.assertEqual(
> >> +                len(db('SELECT * FROM "dummy_table"')), 5)
> >> +            self.assertEqual(
> >> +                len(db('SELECT * FROM "@_dummy_table"')), 0)
> >> +
> >> +    def test_creating_new_product_calls_environment_created(self):
> >> +        self._enable_component(DummyPlugin)
> >> +        self._enable_multiproduct()
> >> +        self.env.upgrade()
> >> +
> >> +        prod = Product(self.env)
> >> +        prod.update_field_dict(dict(prefix='p1'))
> >> +        ProductEnvironment(self.env, prod, create=True)
> >> +        with self.env.db_direct_transaction as db:
> >> +            db('SELECT * FROM "p1_dummy_table"')
> >> +
> >>      def _enable_multiproduct(self):
> >> -        self.env.config.set('components', 'multiproduct.*', 'enabled')
> >> -        self.env.config.save()
> >> -        self._reload_environment()
> >> -        self._reenable_components()
> >> +        self._update_config('components', 'multiproduct.*', 'enabled')
> >> +
> >> +    def _add_custom_field(self, field_name):
> >> +        self._update_config('ticket-custom', field_name, 'text')
> >>
> >>      def _enable_component(self, cls):
> >> -        self.env.config.set('components',
> >> -                            '%s.%s' % (cls.__module__, cls.__name__),
> >> -                            'enabled')
> >> -        self.enabled_components.append(cls)
> >> -        self.env.compmgr.enabled[cls] = True
> >> +        self._update_config(
> >> +            'components',
> >> +            '%s.%s' % (cls.__module__, cls.__name__),
> >> +            'enabled'
> >> +        )
> >>
> >> -    def _reload_environment(self):
> >> +    def _update_config(self, section, key, value):
> >> +        self.env.config.set(section, key, value)
> >> +        self.env.config.save()
> >>          self.env = Environment(self.env_path)
> >>
> >> -    def _reenable_components(self):
> >> -        for cls in self.enabled_components:
> >> -            self.env.compmgr.enabled[cls] = True
> >> -
> >>      def _create_file_with_content(self, content):
> >>          filename = str(uuid.uuid4())[:6]
> >>          path = os.path.join(self.env_path, filename)
> >>
> >>
> >>
> >
> > One of the unit tests added in this changeset is now failing on the
> trunk.
> > It looks like the test is only correct for the number of permissions
> > associated with "anonymous" and doesn't take into account the permissions
> > assigned to "authenticated". Just wanted to ask what is the best way to
> fix
> > this rather than guessing at the intention. Thanks!
> >
> > ======================================================================
> > FAIL: test_upgrade_copies_content_of_system_tables_to_all_products
> > (tests.upgrade.EnvironmentUpgradeTestCase)
> > ----------------------------------------------------------------------
> > Traceback (most recent call last):
> >   File
> >
> "/home/user/Workspace/bh579/bloodhound/bloodhound_multiproduct/tests/upgrade.py",
> > line 197, in test_upgrade_copies_content_of_system_tables_to_all_products
> >     % (table, len(rows), 7, rows))
> > AssertionError: Wrong number of lines in permission (21 instead of 7)
> > [(u'anonymous', u'PRODUCT_VIEW', u''), (u'anonymous', u'PRODUCT_VIEW',
> u'@'),
> > (u'anonymous', u'PRODUCT_VIEW', u'p1'), (u'anonymous', u'PRODUCT_VIEW',
> > u'p2'), (u'anonymous', u'PRODUCT_VIEW', u'p3'), (u'anonymous',
> > u'PRODUCT_VIEW', u'p4'), (u'anonymous', u'PRODUCT_VIEW', u'p5'),
> > (u'authenticated', u'PRODUCT_VIEW', u''), (u'authenticated',
> > u'PRODUCT_VIEW', u'@'), (u'authenticated', u'PRODUCT_VIEW', u'p1'),
> > (u'authenticated', u'PRODUCT_VIEW', u'p2'), (u'authenticated',
> > u'PRODUCT_VIEW', u'p3'), (u'authenticated', u'PRODUCT_VIEW', u'p4'),
> > (u'authenticated', u'PRODUCT_VIEW', u'p5'), (u'x', u'TICKET_VIEW', u''),
> > (u'x', u'TICKET_VIEW', u'@'), (u'x', u'TICKET_VIEW', u'p1'), (u'x',
> > u'TICKET_VIEW', u'p2'), (u'x', u'TICKET_VIEW', u'p3'), (u'x',
> > u'TICKET_VIEW', u'p4'), (u'x', u'TICKET_VIEW', u'p5')]
>


Thanks! If you push that change, then we'll only have two test failures in
the MultiProduct test cases. It looks like the two failures may have to do
with the ambiguity between MultiProduct TracLinks and InterTracLinks, which
is an issue that I believe you've already raised on the list. If that is
the case, maybe we just want to mark these as "skip" for now? What do you
think?

======================================================================
FAIL: test (tests.wiki.formatter.ProductWikiTestCase)
Test Another arbitrary protocol Link
----------------------------------------------------------------------
Traceback (most recent call last):
  File
"/home/user/Workspace/bh579/bloodhound/trac/trac/wiki/tests/formatter.py",
line 209, in test
    % (msg, self.file, self.line, self.title, formatter.flavor))
AssertionError:
========== expected: ==========
<p>
<a class="ext-link" href="svn+ssh://secureserver.org"><span
class="icon"></span>svn+ssh://secureserver.org</a>
<a class="ext-link" href="svn+ssh://secureserver.org"><span
class="icon"></span>SVN link</a>
<a class="ext-link" href="rfc-2396.compatible://link"><span
class="icon"></span>rfc-2396.compatible://link</a>
<a class="ext-link" href="rfc-2396.compatible://link"><span
class="icon"></span>RFC 2396</a>
<a class="ext-link" href="rfc-2396+under_score://link"><span
class="icon"></span>rfc-2396+under_score://link</a>
<a class="ext-link" href="rfc-2396+under_score://link"><span
class="icon"></span>underscore</a>
unsafe://scheme is not rendered
</p>


========== actual: ==========
<p>
<a class="ext-link" href="svn+ssh://secureserver.org"><span
class="icon"></span>svn+ssh://secureserver.org</a>
<a class="ext-link" href="svn+ssh://secureserver.org"><span
class="icon"></span>SVN link</a>
<a class="missing product">rfc-2396</a>.compatible://link
<a class="ext-link" href="rfc-2396.compatible://link"><span
class="icon"></span>RFC 2396</a>
<a class="missing product">rfc-2396</a>+under_score://link
<a class="ext-link" href="rfc-2396+under_score://link"><span
class="icon"></span>underscore</a>
unsafe://scheme is not rendered
</p>


========== wiki: ==========
u'
svn+ssh://secureserver.org
[svn+ssh://secureserver.org SVN link]
rfc-2396.compatible://link
[rfc-2396.compatible://link RFC 2396]
rfc-2396+under_score://link
[rfc-2396+under_score://link underscore]
unsafe://scheme is not rendered
'
========== diff: ==========
--- expected
+++ actual
@@ -1,9 +1,9 @@
 <p>
 <a class="ext-link" href="svn+ssh://secureserver.org"><span
class="icon"></span>svn+ssh://secureserver.org</a>
 <a class="ext-link" href="svn+ssh://secureserver.org"><span
class="icon"></span>SVN link</a>
-<a class="ext-link" href="rfc-2396.compatible://link"><span
class="icon"></span>rfc-2396.compatible://link</a>
+<a class="missing product">rfc-2396</a>.compatible://link
 <a class="ext-link" href="rfc-2396.compatible://link"><span
class="icon"></span>RFC 2396</a>
-<a class="ext-link" href="rfc-2396+under_score://link"><span
class="icon"></span>rfc-2396+under_score://link</a>
+<a class="missing product">rfc-2396</a>+under_score://link
 <a class="ext-link" href="rfc-2396+under_score://link"><span
class="icon"></span>underscore</a>
 unsafe://scheme is not rendered
 </p>


/home/user/Workspace/bh579/bloodhound/trac/trac/wiki/tests/wiki-tests.txt:474:
"Another arbitrary protocol Link" (default flavor)

----------------------------------------------------------------------

Re: svn commit: r1476118 - in /bloodhound/trunk/bloodhound_multiproduct: multiproduct/api.py tests/upgrade.py

Posted by Anze Staric <an...@gmail.com>.
The test is actually only correct for the number of permissions
associated with user "x".

Test deletes all default permissions and creates a custom set for the
user x. Upgrading multiproduct to version 2+ was supposed to copy
existing permissions for each of the created products. Since r1489442,
upgrade also creates PRODUCT_VIEW for anonymous and authenticated
users to all products, even if they had no permissions before the
upgrade.

If this is the desired behaviour, I would fix the test to only count
permissions of user x:

Index: bloodhound_multiproduct/tests/upgrade.py
===================================================================
--- bloodhound_multiproduct/tests/upgrade.py (revision 1503125)
+++ bloodhound_multiproduct/tests/upgrade.py (working copy)
@@ -190,7 +190,7 @@
                 # Permissions also hold rows for global product.
-                rows = db("SELECT * FROM %s" % table)
+                rows = db("SELECT * FROM %s WHERE username='x'" % table)
                 self.assertEqual(



On Tue, Jul 16, 2013 at 8:44 AM, Ryan Ollos <ry...@wandisco.com> wrote:
> On Fri, Apr 26, 2013 at 2:10 AM, <as...@apache.org> wrote:
>
>> Author: astaric
>> Date: Fri Apr 26 09:10:14 2013
>> New Revision: 1476118
>>
>> URL: http://svn.apache.org/r1476118
>> Log:
>> Added more tests for multiproduct upgrade, refactoring.
>>
>> Modified:
>>     bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py
>>     bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py
>>
>> Modified: bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py
>> URL:
>> http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py?rev=1476118&r1=1476117&r2=1476118&view=diff
>>
>> ==============================================================================
>> --- bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py (original)
>> +++ bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py Fri Apr
>> 26 09:10:14 2013
>> @@ -30,6 +30,7 @@ from trac.attachment import Attachment
>>  from trac.config import Option, PathOption
>>  from trac.core import Component, TracError, implements, Interface
>>  from trac.db import Table, Column, DatabaseManager, Index
>> +import trac.db_default
>>  from trac.env import IEnvironmentSetupParticipant, Environment
>>  from trac.perm import IPermissionRequestor, PermissionCache
>>  from trac.resource import IExternalResourceConnector,
>> IResourceChangeListener,\
>> @@ -97,7 +98,7 @@ class MultiProductSystem(Component):
>>          global environment configuration.
>>          """)
>>
>> -    SCHEMA = [mcls._get_schema() \
>> +    SCHEMA = [mcls._get_schema()
>>                for mcls in (Product, ProductResourceMap)]
>>
>>      # Tables which should be migrated (extended with 'product' column)
>> @@ -202,171 +203,207 @@ class MultiProductSystem(Component):
>>          db_installed_version = self.get_version()
>>          with self.env.db_direct_transaction as db:
>>              if db_installed_version < 1:
>> -                # Initial installation
>> -                db("ALTER TABLE ticket ADD COLUMN product TEXT")
>> -                self.log.debug("creating initial db tables for %s
>> plugin." %
>> -                               PLUGIN_NAME)
>> -                db_connector, dummy =
>> DatabaseManager(self.env)._get_connector()
>> -                for table in self.SCHEMA:
>> -                    for statement in db_connector.to_sql(table):
>> -                        db(statement)
>> +                self._add_column_product_to_ticket(db)
>> +                self._create_multiproduct_tables(db)
>>                  db_installed_version = self._update_db_version(db, 1)
>>
>>              if db_installed_version < 2:
>> -                from multiproduct.model import Product
>> -                products = Product.select(self.env)
>> -                for prod in products:
>> -                    db("""UPDATE ticket SET product=%s
>> -                          WHERE product=%s""", (prod.prefix, prod.name))
>> +                self._replace_product_on_ticket_with_product_prefix(db)
>>                  db_installed_version = self._update_db_version(db, 2)
>>
>>              if db_installed_version < 3:
>> -                from multiproduct.model import Product
>> -                import trac.db_default
>> -
>> -                def create_temp_table(table):
>> -                    """creates temporary table with the new schema and
>> -                    drops original table"""
>> -                    table_temp_name = '%s_temp' % table
>> -                    if table == 'report':
>> -                        cols = ','.join([c for c in table_columns[table]
>> if c != 'id'])
>> -                    else:
>> -                        cols = ','.join(table_columns[table])
>> -                    self.log.info("Migrating table '%s' to a new
>> schema", table)
>> -                    db("""CREATE TABLE %s AS SELECT %s FROM %s""" %
>> -                          (table_temp_name, cols, table))
>> -                    db("""DROP TABLE %s""" % table)
>> -                    db_connector, _ =
>> DatabaseManager(self.env)._get_connector()
>> -                    table_schema = [t for t in table_defs if t.name ==
>> table][0]
>> -                    for sql in db_connector.to_sql(table_schema):
>> -                        db(sql)
>> -                    return table_temp_name, cols
>> -
>> -                def drop_temp_table(table):
>> -                    """drops specified temporary table"""
>> -                    db("""DROP TABLE %s""" % table)
>> -
>> -                TICKET_TABLES = ['ticket_change', 'ticket_custom',
>> -                                 'attachment',
>> -                                ]
>>                  SYSTEM_TABLES = ['system']
>> +                TICKET_TABLES = [
>> +                    'ticket_change', 'ticket_custom', 'attachment',
>> +                ]
>> +                table_defs = self._add_product_column_to_tables(
>> +                    self.MIGRATE_TABLES + TICKET_TABLES + SYSTEM_TABLES,
>> +                    db_installed_version)
>> +                table_columns = self._get_table_columns(table_defs)
>> +                create_temp_table = lambda table: self._create_temp_table(
>> +                    db, table, table_columns, table_defs)
>> +
>> +                self._insert_default_product(db)
>> +                self._upgrade_tickets(db, TICKET_TABLES,
>> create_temp_table)
>> +                self._upgrade_wikis(db, create_temp_table, table_columns)
>> +                self._upgrade_system_tables(db, create_temp_table)
>> +                self._soft_link_repositories_to_default_product(db)
>> +                self._upgrade_table_system(SYSTEM_TABLES,
>> create_temp_table, db)
>> +                self._enable_multiproduct_hooks()
>>
>> -                # extend trac default schema by adding product column and
>> extending key with product
>> -                table_defs = [copy.deepcopy(t) for t in
>> trac.db_default.schema
>> -                                                    if t.name in
>> self.MIGRATE_TABLES + TICKET_TABLES + SYSTEM_TABLES]
>> -                for t in table_defs:
>> -                    t.columns.append(Column('product'))
>> -                    if isinstance(t.key, list):
>> -                        t.key = tuple(t.key) + tuple(['product'])
>> -                    elif isinstance(t.key, tuple):
>> -                        t.key = t.key + tuple(['product'])
>> -                    else:
>> -                        raise TracError("Invalid table '%s' schema key
>> '%s' while upgrading "
>> -                                        "plugin '%s' from version %d to
>> %d'" %
>> -                                        (t.name, t.key, PLUGIN_NAME,
>> db_installed_version, 3))
>> -                table_columns = dict()
>> -                for table in table_defs:
>> -                    table_columns[table.name] = [c for c in [column.namefor column in
>> -                                                                [t for t
>> in table_defs if t.name == table.name][0].columns]
>> -                                                                    if c
>> != 'product']
>> -                # create default product
>> -                self.log.info("Creating default product")
>> -                db("""INSERT INTO bloodhound_product (prefix, name,
>> description, owner)
>> -                      VALUES ('%s', '%s', '%s', '')""" %
>> -                      (DEFAULT_PRODUCT, 'Default', 'Default product'))
>> -
>> -                # fetch all products
>> -                all_products = Product.select(self.env)
>> -
>> -                # migrate tickets that don't have product assigned to
>> default product
>> -                # - update ticket table product column
>> -                # - update ticket related tables by:
>> -                #   - upgrading schema
>> -                #   - update product column to match ticket's product
>> -                self.log.info("Migrating tickets w/o product to default
>> product")
>> -                db("""UPDATE ticket SET product='%s'
>> -                      WHERE (product IS NULL OR product='')""" %
>> DEFAULT_PRODUCT)
>> -                self._migrate_attachments(
>> -                    db("""SELECT a.type, a.id, a.filename
>> +                db_installed_version = self._update_db_version(db, 3)
>> +
>> +            if db_installed_version < 4:
>> +                self._create_product_tables_for_plugins(db)
>> +                db_installed_version = self._update_db_version(db, 4)
>> +
>> +            self.env.enable_multiproduct_schema(True)
>> +
>> +    def _add_column_product_to_ticket(self, db):
>> +        self.log.debug("Adding field product to ticket table")
>> +        db("ALTER TABLE ticket ADD COLUMN product TEXT")
>> +
>> +    def _create_multiproduct_tables(self, db):
>> +        self.log.debug("Creating initial db tables for %s plugin." %
>> +                       PLUGIN_NAME)
>> +        db_connector, dummy = DatabaseManager(self.env)._get_connector()
>> +        for table in self.SCHEMA:
>> +            for statement in db_connector.to_sql(table):
>> +                db(statement)
>> +
>> +    def _replace_product_on_ticket_with_product_prefix(self, db):
>> +        for prod in Product.select(self.env):
>> +            db("""UPDATE ticket SET product=%s
>> +                          WHERE product=%s""", (prod.prefix, prod.name))
>> +
>> +    def _create_temp_table(self, db, table, table_columns, table_defs):
>> +        """creates temporary table with the new schema and
>> +        drops original table"""
>> +        table_temp_name = '%s_temp' % table
>> +        if table == 'report':
>> +            cols = ','.join([c for c in table_columns[table] if c !=
>> 'id'])
>> +        else:
>> +            cols = ','.join(table_columns[table])
>> +        self.log.info("Migrating table '%s' to a new schema", table)
>> +        db("""CREATE TABLE %s AS SELECT %s FROM %s""" %
>> +              (table_temp_name, cols, table))
>> +        db("""DROP TABLE %s""" % table)
>> +        db_connector, _ = DatabaseManager(self.env)._get_connector()
>> +        table_schema = [t for t in table_defs if t.name == table][0]
>> +        for sql in db_connector.to_sql(table_schema):
>> +            db(sql)
>> +        return table_temp_name, cols
>> +
>> +    def _drop_temp_table(self, db, table):
>> +        db("""DROP TABLE %s""" % table)
>> +
>> +    def _add_product_column_to_tables(self, tables, current_version):
>> +        """Extend trac default schema by adding product column
>> +        and extending key with product.
>> +        """
>> +        table_defs = [copy.deepcopy(t) for t in trac.db_default.schema
>> +                      if
>> +                      t.name in tables]
>> +        for t in table_defs:
>> +            t.columns.append(Column('product'))
>> +            if isinstance(t.key, list):
>> +                t.key = tuple(t.key) + tuple(['product'])
>> +            elif isinstance(t.key, tuple):
>> +                t.key = t.key + tuple(['product'])
>> +            else:
>> +                raise TracError(
>> +                    "Invalid table '%s' schema key '%s' while upgrading "
>> +                    "plugin '%s' from version %d to %d'" %
>> +                    (t.name, t.key, PLUGIN_NAME, current_version, 3))
>> +        return table_defs
>> +
>> +    def _get_table_columns(self, table_defs):
>> +        table_columns = dict()
>> +        for table in table_defs:
>> +            table_definition = \
>> +                [t for t in table_defs if t.name == table.name][0]
>> +            column_names = \
>> +                [column.name for column in table_definition.columns]
>> +            table_columns[table.name] = \
>> +                [c for c in column_names if c != 'product']
>> +        return table_columns
>> +
>> +    def _insert_default_product(self, db):
>> +        self.log.info("Creating default product")
>> +        db("""INSERT INTO bloodhound_product (prefix, name, description,
>> owner)
>> +              VALUES ('%s', '%s', '%s', '')
>> +           """ % (DEFAULT_PRODUCT, 'Default', 'Default product'))
>> +
>> +    def _upgrade_tickets(self, db, TICKET_TABLES, create_temp_table):
>> +        # migrate tickets that don't have product assigned to default
>> product
>> +        # - update ticket table product column
>> +        # - update ticket related tables by:
>> +        #   - upgrading schema
>> +        #   - update product column to match ticket's product
>> +        self.log.info("Migrating tickets w/o product to default product")
>> +        db("""UPDATE ticket SET product='%s'
>> +                      WHERE (product IS NULL OR product='')
>> +           """ % DEFAULT_PRODUCT)
>> +        self._migrate_attachments(
>> +            db("""SELECT a.type, a.id, a.filename
>>                              FROM attachment a
>>                        INNER JOIN ticket t ON a.id = %(t.id)s
>>                             WHERE a.type='ticket'
>> -                       """ % {'t.id':  db.cast('t.id', 'text')}),
>> -                    to_product=DEFAULT_PRODUCT
>> -                )
>> -
>> -                self.log.info("Migrating ticket tables to a new schema")
>> -                for table in TICKET_TABLES:
>> -                    temp_table_name, cols = create_temp_table(table)
>> -                    db("""INSERT INTO %s (%s, product)
>> +                       """ % {'t.id': db.cast('t.id', 'text')}),
>> +            to_product=DEFAULT_PRODUCT
>> +        )
>> +        self.log.info("Migrating ticket tables to a new schema")
>> +        for table in TICKET_TABLES:
>> +            temp_table_name, cols = create_temp_table(table)
>> +            db("""INSERT INTO %s (%s, product)
>>                            SELECT %s, '' FROM %s""" %
>> -                          (table, cols, cols, temp_table_name))
>> -                    drop_temp_table(temp_table_name)
>> -
>> -                for table in TICKET_TABLES:
>> -                    if table == 'attachment':
>> -                        db("""
>> -                            UPDATE attachment
>> -                               SET product=(SELECT ticket.product
>> -                                              FROM ticket
>> -                                             WHERE %(ticket_id)s=
>> attachment.id
>> -                                             LIMIT 1)
>> -                             WHERE attachment.type='ticket'
>> -                               AND EXISTS(SELECT ticket.product
>> -                                            FROM ticket
>> -                                           WHERE %(ticket_id)s=
>> attachment.id)
>> -                           """ % dict(
>> -                            ticket_id=db.cast('attachment.id', 'text')))
>> -                    else:
>> -                        db("""UPDATE %s
>> -                              SET product=(SELECT ticket.product FROM
>> ticket WHERE ticket.id=%s.ticket)""" %
>> -                           (table, table))
>> -
>> -                # migrate system table (except wiki which is handled
>> separately) to a
>> -                # new schema
>> -                # - create tables with the new schema
>> -                # - populate system tables with global configuration for
>> each product
>> -                # - exception is permission table where permissions are
>> also populated in
>> -                #   global scope
>> -                self.log.info("Migrating system tables to a new schema")
>> -                for table in self.MIGRATE_TABLES:
>> -                    if table == 'wiki':
>> -                        continue
>> -                    temp_table_name, cols = create_temp_table(table)
>> -                    for product in all_products:
>> -                        self.log.info("Populating table '%s' for product
>> '%s' ('%s')",
>> -                                      table, product.name,
>> product.prefix)
>> -                        db("""INSERT INTO %s (%s, product) SELECT %s,'%s'
>> FROM %s""" %
>> -                              (table, cols, cols, product.prefix,
>> temp_table_name))
>> -                    if table == 'permission':
>> -                        self.log.info("Populating table '%s' for global
>> scope", table)
>> -                        db("""INSERT INTO %s (%s, product) SELECT %s,'%s'
>> FROM %s""" %
>> -                              (table, cols, cols, '', temp_table_name))
>> -                    drop_temp_table(temp_table_name)
>> -
>> -                # migrate wiki table
>> -                # - populate system wikis to all products + global scope
>> -                # - update wiki attachment product to match wiki product
>> -                table = 'wiki'
>> -                temp_table_name, cols = create_temp_table(table)
>> -                self.log.info("Migrating wikis to global context")
>> -                db("""INSERT INTO %s (%s, product) SELECT %s, '' FROM
>> %s""" %
>> -                   (table, cols, cols, temp_table_name))
>> +               (table, cols, cols, temp_table_name))
>> +            self._drop_temp_table(db, temp_table_name)
>> +            if table == 'attachment':
>>                  db("""UPDATE attachment
>> -                         SET product=''
>> -                       WHERE attachment.type='wiki'""")
>> -
>> -                for wiki_name, wiki_version, wiki_product in db("""
>> +                         SET product=(SELECT ticket.product
>> +                                        FROM ticket
>> +                                       WHERE %(ticket.id)s=attachment.id
>> +                                       LIMIT 1)
>> +                       WHERE attachment.type='ticket'
>> +                         AND EXISTS(SELECT ticket.product
>> +                                      FROM ticket
>> +                                     WHERE %(ticket.id)s=attachment.id)
>> +                   """ % {'ticket.id': db.cast('ticket.id', 'text')})
>> +            else:
>> +                db("""UPDATE %(table)s
>> +                         SET product=(SELECT ticket.product
>> +                                        FROM ticket
>> +                                       WHERE ticket.id=%(table)s.ticket)
>> +                   """ % {'table': table})
>> +
>> +    def _upgrade_system_tables(self, db, create_temp_table):
>> +        # migrate system table (except wiki which is handled separately)
>> +        # to a new schema
>> +        # - create tables with the new schema
>> +        # - populate system tables with global configuration for each
>> product
>> +        # - exception is permission table where permissions
>> +        #   are also populated in global scope
>> +        self.log.info("Migrating system tables to a new schema")
>> +        for table in self.MIGRATE_TABLES:
>> +            if table == 'wiki':
>> +                continue
>> +            temp_table_name, cols = create_temp_table(table)
>> +            for product in Product.select(self.env):
>> +                self.log.info("Populating table '%s' for product '%s'
>> ('%s')",
>> +                              table, product.name, product.prefix)
>> +                db("""INSERT INTO %s (%s, product) SELECT %s,'%s' FROM
>> %s""" %
>> +                   (table, cols, cols, product.prefix, temp_table_name))
>> +            if table == 'permission':
>> +                self.log.info("Populating table '%s' for global scope",
>> table)
>> +                db("""INSERT INTO %s (%s, product) SELECT %s,'%s' FROM
>> %s""" %
>> +                   (table, cols, cols, '', temp_table_name))
>> +            self._drop_temp_table(db, temp_table_name)
>> +
>> +    def _upgrade_wikis(self, db, create_temp_table,
>> +                       table_columns):
>> +        # migrate wiki table
>> +        # - populate system wikis to all products + global scope
>> +        # - update wiki attachment product to match wiki product
>> +        table = 'wiki'
>> +        temp_table_name, cols = create_temp_table(table)
>> +        self.log.info("Migrating wikis to global context")
>> +        db("""INSERT INTO %s (%s, product) SELECT %s, '' FROM %s""" %
>> +           (table, cols, cols, temp_table_name))
>> +        db("""UPDATE attachment
>> +                 SET product=''
>> +               WHERE attachment.type='wiki'""")
>> +        for wiki_name, wiki_version, wiki_product in db("""
>>                          SELECT name, version, product FROM %s""" % table):
>> -                    attachment_cols =
>> ','.join(table_columns['attachment'])
>> -                    if wiki_name in self.system_wiki_list:
>> -                        for product in all_products:
>> -                            db("""INSERT INTO %s (%s, product)
>> +            attachment_cols = ','.join(table_columns['attachment'])
>> +            if wiki_name in self.system_wiki_list:
>> +                for product in Product.select(self.env):
>> +                    db("""INSERT INTO %s (%s, product)
>>                                    SELECT %s, '%s' FROM %s
>>                                    WHERE name='%s' AND version=%s AND
>> product='%s'""" %
>> -                                  (table, cols, cols, product.prefix,
>> table,
>> -                                   wiki_name, wiki_version, wiki_product))
>> -                            db("""INSERT INTO attachment (%(cols)s,
>> product)
>> +                       (table, cols, cols, product.prefix, table,
>> +                        wiki_name, wiki_version, wiki_product))
>> +                    db("""INSERT INTO attachment (%(cols)s, product)
>>                                    SELECT %(cols)s, '%(new_product)s'
>>                                      FROM attachment a
>>                                     WHERE type='wiki'
>> @@ -380,89 +417,40 @@ class MultiProductSystem(Component):
>>                                            wiki_name=wiki_name,
>>                                            old_product=wiki_product,
>>                                            new_product=product.prefix))
>> -                            self._migrate_attachments(
>> -                                db("""SELECT type, id, filename
>> +                    self._migrate_attachments(
>> +                        db("""SELECT type, id, filename
>>                                          FROM attachment
>>                                         WHERE type='wiki'
>>                                           AND id='%s'
>>                                           AND product='%s'
>>                                     """ % (wiki_name, DEFAULT_PRODUCT)),
>> -                                    to_product=DEFAULT_PRODUCT,
>> -                                    copy=True
>> -                            )
>> -                    else:
>> -                        self.log.info("Moving wiki page '%s' to default
>> product", wiki_name)
>> -                        db("""UPDATE wiki
>> +                        to_product=DEFAULT_PRODUCT,
>> +                        copy=True
>> +                    )
>> +            else:
>> +                self.log.info("Moving wiki page '%s' to default product",
>> +                              wiki_name)
>> +                db("""UPDATE wiki
>>                                SET product='%s'
>>                                WHERE name='%s' AND version=%s AND
>> product='%s'
>>                             """ % (DEFAULT_PRODUCT,
>>                                    wiki_name, wiki_version, wiki_product))
>> -                        db("""UPDATE attachment
>> +                db("""UPDATE attachment
>>                                   SET product='%s'
>>                                 WHERE type='wiki'
>>                                   AND id='%s'
>>                                   AND product='%s'
>>                             """ % (DEFAULT_PRODUCT, wiki_name,
>> wiki_product))
>> -                        self._migrate_attachments(
>> -                            db("""SELECT type, id, filename
>> +                self._migrate_attachments(
>> +                    db("""SELECT type, id, filename
>>                                      FROM attachment
>>                                     WHERE type='wiki'
>>                                       AND id='%s'
>>                                       AND product='%s'
>>                                 """ % (wiki_name, DEFAULT_PRODUCT)),
>> -                            to_product=DEFAULT_PRODUCT,
>> -                        )
>> -
>> -                drop_temp_table(temp_table_name)
>> -
>> -                # soft link existing repositories to default product
>> -                repositories_linked = []
>> -                for id, name in db("""SELECT id, value FROM repository
>> -                                      WHERE name='name'"""):
>> -                    if id in repositories_linked:
>> -                        continue
>> -                    db("""INSERT INTO repository (id, name, value)
>> -                          VALUES (%s, 'product', '%s')""" %
>> -                       (id, DEFAULT_PRODUCT))
>> -                    repositories_linked.append(id)
>> -                    self.log.info("Repository '%s' (%s) soft linked to
>> default product", name, id)
>> -
>> -                # Update system tables
>> -                # Upgrade schema
>> -                self.log.info("Migrating system tables to a new schema")
>> -                for table in SYSTEM_TABLES:
>> -                    temp_table_name, cols = create_temp_table(table)
>> -                    db("""INSERT INTO %s (%s, product)
>> -                          SELECT %s,'' FROM %s""" %
>> -                       (table, cols, cols, temp_table_name))
>> -                    drop_temp_table(temp_table_name)
>> -
>> -                # enable multi product hooks in environment configuration
>> -                import multiproduct.hooks
>> -                import inspect
>> -                config_update = False
>> -                hook_path =
>> os.path.realpath(inspect.getsourcefile(multiproduct.hooks))
>> -                if not 'environment_factory' in self.env.config['trac']:
>> -                    self.env.config['trac'].set('environment_factory',
>> hook_path)
>> -                    config_update = True
>> -                if not 'request_factory' in self.env.config['trac']:
>> -                    self.env.config['trac'].set('request_factory',
>> hook_path)
>> -                    config_update = True
>> -                if config_update:
>> -                    self.log.info("Enabling multi product hooks in
>> environment configuration")
>> -                    self.env.config.save()
>> -
>> -                db_installed_version = self._update_db_version(db, 3)
>> -
>> -            if db_installed_version < 4:
>> -                self.log.debug("creating additional db tables for %s
>> plugin." %
>> -                               PLUGIN_NAME)
>> -                db_connector, dummy =
>> DatabaseManager(self.env)._get_connector()
>> -                for statement in
>> db_connector.to_sql(ProductSetting._get_schema()):
>> -                    db(statement)
>> -                db_installed_version = self._update_db_version(db, 4)
>> -
>> -            self.env.enable_multiproduct_schema(True)
>> +                    to_product=DEFAULT_PRODUCT,
>> +                )
>> +        self._drop_temp_table(db, temp_table_name)
>>
>>      def _migrate_attachments(self, attachments, to_product=None,
>> copy=False):
>>          for type, id, filename in attachments:
>> @@ -498,6 +486,56 @@ class MultiProductSystem(Component):
>>                      filename, type, id, str(err)
>>                  )
>>
>> +    def _soft_link_repositories_to_default_product(self, db):
>> +        # soft link existing repositories to default product
>> +        repositories_linked = []
>> +        for id, name in db("""SELECT id, value FROM repository
>> +                                      WHERE name='name'"""):
>> +            if id in repositories_linked:
>> +                continue
>> +            db("""INSERT INTO repository (id, name, value)
>> +                          VALUES (%s, 'product', '%s')""" %
>> +               (id, DEFAULT_PRODUCT))
>> +            repositories_linked.append(id)
>> +            self.log.info("Repository '%s' (%s) soft linked to default
>> product",
>> +                          name, id)
>> +
>> +    def _upgrade_table_system(self, SYSTEM_TABLES, create_temp_table, db):
>> +        # Update system tables
>> +        # Upgrade schema
>> +        self.log.info("Migrating system tables to a new schema")
>> +        for table in SYSTEM_TABLES:
>> +            temp_table_name, cols = create_temp_table(table)
>> +            db("""INSERT INTO %s (%s, product)
>> +                          SELECT %s,'' FROM %s""" %
>> +               (table, cols, cols, temp_table_name))
>> +            self._drop_temp_table(db, temp_table_name)
>> +
>> +    def _enable_multiproduct_hooks(self):
>> +        # enable multi product hooks in environment configuration
>> +        import multiproduct.hooks
>> +        import inspect
>> +
>> +        config_update = False
>> +        hook_path =
>> os.path.realpath(inspect.getsourcefile(multiproduct.hooks))
>> +        if not 'environment_factory' in self.env.config['trac']:
>> +            self.env.config['trac'].set('environment_factory', hook_path)
>> +            config_update = True
>> +        if not 'request_factory' in self.env.config['trac']:
>> +            self.env.config['trac'].set('request_factory', hook_path)
>> +            config_update = True
>> +        if config_update:
>> +            self.log.info(
>> +                "Enabling multi product hooks in environment
>> configuration")
>> +            self.env.config.save()
>> +
>> +    def _create_product_tables_for_plugins(self, db):
>> +        self.log.debug("creating additional db tables for %s plugin." %
>> +                       PLUGIN_NAME)
>> +        db_connector, dummy = DatabaseManager(self.env)._get_connector()
>> +        for statement in
>> db_connector.to_sql(ProductSetting._get_schema()):
>> +            db(statement)
>> +
>>      # IResourceChangeListener methods
>>      def match_resource(self, resource):
>>          return isinstance(resource, Product)
>>
>> Modified: bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py
>> URL:
>> http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py?rev=1476118&r1=1476117&r2=1476118&view=diff
>>
>> ==============================================================================
>> --- bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py (original)
>> +++ bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py Fri Apr 26
>> 09:10:14 2013
>> @@ -33,6 +33,7 @@ from trac.test import Environment
>>  from trac.ticket import Ticket
>>  from trac.wiki import WikiPage
>>
>> +from multiproduct.api import MultiProductSystem
>>  from multiproduct.env import ProductEnvironment
>>  from multiproduct.model import Product
>>
>> @@ -43,13 +44,9 @@ BLOODHOUND_TABLES = (
>>  )
>>
>>  TABLES_WITH_PRODUCT_FIELD = (
>> -    'component',
>> -    'milestone',
>> -    'version',
>> -    'enum',
>> -    'permission',
>> -    'wiki',
>> -    'report',
>> +    'ticket', 'ticket_change', 'ticket_custom', 'attachment', 'component',
>> +    'milestone', 'wiki', 'report',
>> +    'version', 'enum', 'permission', 'system',
>>  )
>>
>>
>> @@ -57,12 +54,12 @@ class EnvironmentUpgradeTestCase(unittes
>>      def setUp(self):
>>          self.env_path = tempfile.mkdtemp('multiproduct-tempenv')
>>          self.env = Environment(self.env_path, create=True)
>> -        self.enabled_components = []
>>          DummyPlugin.version = 1
>>
>> -    def test_upgrade_environment(self):
>> +    def test_can_upgrade_environment_with_multi_product_disabled(self):
>>          self.env.upgrade()
>>
>> +        # Multiproduct was not enabled so multiproduct tables should not
>> exist
>>          with self.env.db_direct_transaction as db:
>>              for table in BLOODHOUND_TABLES:
>>                  with self.assertFailsWithMissingTable():
>> @@ -72,7 +69,7 @@ class EnvironmentUpgradeTestCase(unittes
>>                  with self.assertFailsWithMissingColumn():
>>                      db("SELECT product FROM %s" % table)
>>
>> -    def test_upgrade_environment_to_multiproduct(self):
>> +    def
>> test_upgrade_creates_multi_product_tables_and_adds_product_column(self):
>>          self._enable_multiproduct()
>>          self.env.upgrade()
>>
>> @@ -83,89 +80,38 @@ class EnvironmentUpgradeTestCase(unittes
>>              for table in TABLES_WITH_PRODUCT_FIELD:
>>                  db("SELECT product FROM %s" % table)
>>
>> -    def test_upgrade_plugin(self):
>> -        self._enable_component(DummyPlugin)
>> -        self.env.upgrade()
>> -
>> -        with self.env.db_direct_transaction as db:
>> -            db("SELECT v1 FROM dummy_table")
>> -            with self.assertFailsWithMissingColumn():
>> -                db("SELECT v2 FROM dummy_table")
>> -
>> -        DummyPlugin.version = 2
>> -        self.env.upgrade()
>> -
>> -        with self.env.db_direct_transaction as db:
>> -            db("SELECT v2 FROM dummy_table")
>> -
>> -    def test_upgrade_plugin_to_multiproduct(self):
>> +    def test_upgrade_creates_default_product(self):
>>          self._enable_multiproduct()
>> -        self._enable_component(DummyPlugin)
>>          self.env.upgrade()
>>
>> -        with self.env.db_direct_transaction as db:
>> -            db("SELECT * FROM dummy_table")
>> -            db("""SELECT * FROM "@_dummy_table" """)
>> +        products = Product.select(self.env)
>> +        self.assertEqual(len(products), 1)
>>
>> -    def test_upgrade_existing_plugin_to_multiproduct(self):
>> -        self._enable_component(DummyPlugin)
>> -        self.env.upgrade()
>> -        with self.env.db_direct_transaction as db:
>> -            with self.assertFailsWithMissingTable():
>> -                db("""SELECT * FROM "@_dummy_table" """)
>> -
>> -        self._enable_multiproduct()
>> -        self.env.upgrade()
>> -        with self.env.db_direct_transaction as db:
>> -            db("SELECT * FROM dummy_table")
>> -            db("""SELECT * FROM "@_dummy_table" """)
>> -
>> -    def test_upgrading_existing_plugin_leaves_data_in_global_env(self):
>> -        self._enable_component(DummyPlugin)
>> -        self.env.upgrade()
>> -        with self.env.db_direct_transaction as db:
>> -            for i in range(5):
>> -                db("INSERT INTO dummy_table (v1) VALUES ('%d')" % i)
>> -            self.assertEqual(
>> -                len(db("SELECT * FROM dummy_table")), 5)
>> -
>> -        self._enable_multiproduct()
>> -        self.env.upgrade()
>> -        with self.env.db_direct_transaction as db:
>> -            self.assertEqual(
>> -                len(db('SELECT * FROM "dummy_table"')), 5)
>> -            self.assertEqual(
>> -                len(db('SELECT * FROM "@_dummy_table"')), 0)
>> -
>> -    def test_creating_new_product_calls_environment_created(self):
>> -        self._enable_component(DummyPlugin)
>> -        self._enable_multiproduct()
>> -        self.env.upgrade()
>> -
>> -        prod = Product(self.env)
>> -        prod.update_field_dict(dict(prefix='p1'))
>> -        ProductEnvironment(self.env, prod, create=True)
>> -        with self.env.db_direct_transaction as db:
>> -            db('SELECT * FROM "p1_dummy_table"')
>> -
>> -    def test_upgrade_moves_tickets_to_default_product(self):
>> +    def
>> test_upgrade_moves_tickets_and_related_objects_to_default_prod(self):
>> +        self._add_custom_field('custom_field')
>>          with self.env.db_direct_transaction as db:
>>              db("""INSERT INTO ticket (id) VALUES (1)""")
>>              db("""INSERT INTO attachment (type, id)
>> -                         VALUES ('ticket', '1')""")
>> +                       VALUES ('ticket', '1')""")
>> +            db("""INSERT INTO ticket_custom (ticket, name, value)
>> +                       VALUES (1, 'custom_field', '42')""")
>> +            db("""INSERT INTO ticket_change (ticket, time, field)
>> +                       VALUES (1, 42, 'summary')""")
>>
>>          self._enable_multiproduct()
>>          self.env.upgrade()
>>
>> -        with self.env.db_direct_transaction as db:
>> -            self.assertEqual(
>> -                len(db("""SELECT * FROM ticket WHERE product='@'""")), 1)
>> -            self.assertEqual(
>> -                len(db("""SELECT * FROM attachment
>> -                          WHERE product='@'
>> -                            AND type='ticket'""")), 1)
>> +        with self.product('@'):
>> +            ticket = Ticket(self.env, 1)
>> +            attachments = list(Attachment.select(self.env,
>> +                                                 ticket.resource.realm,
>> +                                                 ticket.resource.id))
>> +            self.assertEqual(len(attachments), 1)
>> +            self.assertEqual(ticket['custom_field'], '42')
>> +            changes = ticket.get_changelog()
>> +            self.assertEqual(len(changes), 3)
>>
>> -    def test_upgrade_moves_wikis_to_default_product(self):
>> +    def test_upgrade_moves_custom_wikis_to_default_product(self):
>>          with self.env.db_direct_transaction as db:
>>              db("""INSERT INTO wiki (name, version) VALUES ('MyPage',
>> 1)""")
>>              db("""INSERT INTO attachment (type, id)
>> @@ -205,23 +151,44 @@ class EnvironmentUpgradeTestCase(unittes
>>                             WHERE product=''
>>                               AND type='wiki'""")), 1)
>>
>> -    def test_can_upgrade_database_with_orphaned_attachments(self):
>> +    def
>> test_upgrade_copies_content_of_system_tables_to_all_products(self):
>> +        mp = MultiProductSystem(self.env)
>>          with self.env.db_direct_transaction as db:
>> -            db("""INSERT INTO attachment (id, type)
>> -                       VALUES ('5', 'ticket')""")
>> -            db("""INSERT INTO attachment (id, type)
>> -                       VALUES ('MyWiki', 'wiki')""")
>> +            mp._add_column_product_to_ticket(db)
>> +            mp._create_multiproduct_tables(db)
>> +            mp._update_db_version(db, 1)
>> +            for i in range(1, 6):
>> +                db("""INSERT INTO bloodhound_product (prefix, name)
>> +                           VALUES ('p%d', 'Product 1')""" % i)
>> +            for table in ('component', 'milestone', 'enum', 'version',
>> +                          'permission', 'report'):
>> +                db("""DELETE FROM %s""" % table)
>> +            db("""INSERT INTO component (name) VALUES ('foobar')""")
>> +            db("""INSERT INTO milestone (name) VALUES ('foobar')""")
>> +            db("""INSERT INTO version (name) VALUES ('foobar')""")
>> +            db("""INSERT INTO enum (type, name) VALUES ('a', 'b')""")
>> +            db("""INSERT INTO permission VALUES ('x', 'TICKET_VIEW')""")
>> +            db("""INSERT INTO wiki (name, version) VALUES ('WikiStart',
>> 1)""")
>> +            db("""INSERT INTO report (title) VALUES ('x')""")
>>
>>          self._enable_multiproduct()
>>          self.env.upgrade()
>>
>> -    def test_can_upgrade_database_with_text_attachment_ids(self):
>>          with self.env.db_direct_transaction as db:
>> -            db("""INSERT INTO attachment (id, type)
>> -                       VALUES ('abc', 'ticket')""")
>> -
>> -        self._enable_multiproduct()
>> -        self.env.upgrade()
>> +            for table in ('component', 'milestone', 'version', 'enum',
>> +                          'report'):
>> +                rows = db("SELECT * FROM %s" % table)
>> +                self.assertEqual(
>> +                    len(rows), 6,
>> +                    "Wrong number of lines in %s (%d instead of %d)\n%s"
>> +                    % (table, len(rows), 6, rows))
>> +            for table in ('wiki', 'permission'):
>> +                # Permissions and wikis also hold rows for global product.
>> +                rows = db("SELECT * FROM %s" % table)
>> +                self.assertEqual(
>> +                    len(rows), 7,
>> +                    "Wrong number of lines in %s (%d instead of %d)\n%s"
>> +                    % (table, len(rows), 7, rows))
>>
>>      def test_upgrading_database_moves_attachment_to_correct_product(self):
>>          ticket = self.insert_ticket('ticket')
>> @@ -258,26 +225,148 @@ class EnvironmentUpgradeTestCase(unittes
>>          for attachment in attachments:
>>              self.assertEqual(attachment.open().read(), 'Hello World!')
>>
>> +    def
>> test_can_upgrade_database_with_ticket_attachment_with_text_ids(self):
>> +        with self.env.db_direct_transaction as db:
>> +            db("""INSERT INTO attachment (id, type)
>> +                       VALUES ('abc', 'ticket')""")
>> +
>> +        self._enable_multiproduct()
>> +        self.env.upgrade()
>> +
>> +    def test_can_upgrade_database_with_orphaned_attachments(self):
>> +        with self.env.db_direct_transaction as db:
>> +            db("""INSERT INTO attachment (id, type)
>> +                       VALUES ('5', 'ticket')""")
>> +            db("""INSERT INTO attachment (id, type)
>> +                       VALUES ('MyWiki', 'wiki')""")
>> +
>> +        self._enable_multiproduct()
>> +        self.env.upgrade()
>> +
>> +    def test_can_upgrade_multi_product_from_v1(self):
>> +        mp = MultiProductSystem(self.env)
>> +        with self.env.db_direct_transaction as db:
>> +            mp._add_column_product_to_ticket(db)
>> +            mp._create_multiproduct_tables(db)
>> +            mp._update_db_version(db, 1)
>> +
>> +            db("""INSERT INTO bloodhound_product (prefix, name)
>> +                       VALUES ('p1', 'Product 1')""")
>> +            db("""INSERT INTO ticket (id, product)
>> +                       VALUES (1, 'Product 1')""")
>> +
>> +        self._enable_multiproduct()
>> +        self.env.upgrade()
>> +
>> +        with self.product('p1'):
>> +            Ticket(self.env, 1)
>> +
>> +    def test_can_upgrade_multi_product_from_v2(self):
>> +        mp = MultiProductSystem(self.env)
>> +        with self.env.db_direct_transaction as db:
>> +            mp._add_column_product_to_ticket(db)
>> +            mp._create_multiproduct_tables(db)
>> +            mp._replace_product_on_ticket_with_product_prefix(db)
>> +            mp._update_db_version(db, 2)
>> +
>> +            db("""INSERT INTO bloodhound_product (prefix, name)
>> +                       VALUES ('p1', 'Product 1')""")
>> +            db("""INSERT INTO ticket (id, product)
>> +                       VALUES (1, 'p1')""")
>> +            db("""INSERT INTO ticket (id)
>> +                       VALUES (2)""")
>> +
>> +        self._enable_multiproduct()
>> +        self.env.upgrade()
>> +
>> +        with self.product('p1'):
>> +            Ticket(self.env, 1)
>> +        with self.product('@'):
>> +            Ticket(self.env, 2)
>> +
>> +    def test_upgrade_plugin(self):
>> +        self._enable_component(DummyPlugin)
>> +        self.env.upgrade()
>> +
>> +        with self.env.db_direct_transaction as db:
>> +            db("SELECT v1 FROM dummy_table")
>> +            with self.assertFailsWithMissingColumn():
>> +                db("SELECT v2 FROM dummy_table")
>> +
>> +        DummyPlugin.version = 2
>> +        self.env.upgrade()
>> +
>> +        with self.env.db_direct_transaction as db:
>> +            db("SELECT v2 FROM dummy_table")
>> +
>> +    def test_upgrade_plugin_to_multiproduct(self):
>> +        self._enable_multiproduct()
>> +        self._enable_component(DummyPlugin)
>> +        self.env.upgrade()
>> +
>> +        with self.env.db_direct_transaction as db:
>> +            db("SELECT * FROM dummy_table")
>> +            db("""SELECT * FROM "@_dummy_table" """)
>> +
>> +    def test_upgrade_existing_plugin_to_multiproduct(self):
>> +        self._enable_component(DummyPlugin)
>> +        self.env.upgrade()
>> +        with self.env.db_direct_transaction as db:
>> +            with self.assertFailsWithMissingTable():
>> +                db("""SELECT * FROM "@_dummy_table" """)
>> +
>> +        self._enable_multiproduct()
>> +        self.env.upgrade()
>> +        with self.env.db_direct_transaction as db:
>> +            db("SELECT * FROM dummy_table")
>> +            db("""SELECT * FROM "@_dummy_table" """)
>> +
>> +    def test_upgrading_existing_plugin_leaves_data_in_global_env(self):
>> +        self._enable_component(DummyPlugin)
>> +        self.env.upgrade()
>> +        with self.env.db_direct_transaction as db:
>> +            for i in range(5):
>> +                db("INSERT INTO dummy_table (v1) VALUES ('%d')" % i)
>> +            self.assertEqual(
>> +                len(db("SELECT * FROM dummy_table")), 5)
>> +
>> +        self._enable_multiproduct()
>> +        self.env.upgrade()
>> +        with self.env.db_direct_transaction as db:
>> +            self.assertEqual(
>> +                len(db('SELECT * FROM "dummy_table"')), 5)
>> +            self.assertEqual(
>> +                len(db('SELECT * FROM "@_dummy_table"')), 0)
>> +
>> +    def test_creating_new_product_calls_environment_created(self):
>> +        self._enable_component(DummyPlugin)
>> +        self._enable_multiproduct()
>> +        self.env.upgrade()
>> +
>> +        prod = Product(self.env)
>> +        prod.update_field_dict(dict(prefix='p1'))
>> +        ProductEnvironment(self.env, prod, create=True)
>> +        with self.env.db_direct_transaction as db:
>> +            db('SELECT * FROM "p1_dummy_table"')
>> +
>>      def _enable_multiproduct(self):
>> -        self.env.config.set('components', 'multiproduct.*', 'enabled')
>> -        self.env.config.save()
>> -        self._reload_environment()
>> -        self._reenable_components()
>> +        self._update_config('components', 'multiproduct.*', 'enabled')
>> +
>> +    def _add_custom_field(self, field_name):
>> +        self._update_config('ticket-custom', field_name, 'text')
>>
>>      def _enable_component(self, cls):
>> -        self.env.config.set('components',
>> -                            '%s.%s' % (cls.__module__, cls.__name__),
>> -                            'enabled')
>> -        self.enabled_components.append(cls)
>> -        self.env.compmgr.enabled[cls] = True
>> +        self._update_config(
>> +            'components',
>> +            '%s.%s' % (cls.__module__, cls.__name__),
>> +            'enabled'
>> +        )
>>
>> -    def _reload_environment(self):
>> +    def _update_config(self, section, key, value):
>> +        self.env.config.set(section, key, value)
>> +        self.env.config.save()
>>          self.env = Environment(self.env_path)
>>
>> -    def _reenable_components(self):
>> -        for cls in self.enabled_components:
>> -            self.env.compmgr.enabled[cls] = True
>> -
>>      def _create_file_with_content(self, content):
>>          filename = str(uuid.uuid4())[:6]
>>          path = os.path.join(self.env_path, filename)
>>
>>
>>
>
> One of the unit tests added in this changeset is now failing on the trunk.
> It looks like the test is only correct for the number of permissions
> associated with "anonymous" and doesn't take into account the permissions
> assigned to "authenticated". Just wanted to ask what is the best way to fix
> this rather than guessing at the intention. Thanks!
>
> ======================================================================
> FAIL: test_upgrade_copies_content_of_system_tables_to_all_products
> (tests.upgrade.EnvironmentUpgradeTestCase)
> ----------------------------------------------------------------------
> Traceback (most recent call last):
>   File
> "/home/user/Workspace/bh579/bloodhound/bloodhound_multiproduct/tests/upgrade.py",
> line 197, in test_upgrade_copies_content_of_system_tables_to_all_products
>     % (table, len(rows), 7, rows))
> AssertionError: Wrong number of lines in permission (21 instead of 7)
> [(u'anonymous', u'PRODUCT_VIEW', u''), (u'anonymous', u'PRODUCT_VIEW', u'@'),
> (u'anonymous', u'PRODUCT_VIEW', u'p1'), (u'anonymous', u'PRODUCT_VIEW',
> u'p2'), (u'anonymous', u'PRODUCT_VIEW', u'p3'), (u'anonymous',
> u'PRODUCT_VIEW', u'p4'), (u'anonymous', u'PRODUCT_VIEW', u'p5'),
> (u'authenticated', u'PRODUCT_VIEW', u''), (u'authenticated',
> u'PRODUCT_VIEW', u'@'), (u'authenticated', u'PRODUCT_VIEW', u'p1'),
> (u'authenticated', u'PRODUCT_VIEW', u'p2'), (u'authenticated',
> u'PRODUCT_VIEW', u'p3'), (u'authenticated', u'PRODUCT_VIEW', u'p4'),
> (u'authenticated', u'PRODUCT_VIEW', u'p5'), (u'x', u'TICKET_VIEW', u''),
> (u'x', u'TICKET_VIEW', u'@'), (u'x', u'TICKET_VIEW', u'p1'), (u'x',
> u'TICKET_VIEW', u'p2'), (u'x', u'TICKET_VIEW', u'p3'), (u'x',
> u'TICKET_VIEW', u'p4'), (u'x', u'TICKET_VIEW', u'p5')]