Author: rjollos
Date: Mon Sep 30 05:31:52 2013
New Revision: 1527447

URL: http://svn.apache.org/r1527447
Log:
0.8dev: Added Bloodhound infrastructure to run functional test cases, and test 
modules for  prefs and ticket. Refs #387.

Patch by Olemis Lang.

Added:
    bloodhound/trunk/bloodhound_multiproduct/tests/functional/
    bloodhound/trunk/bloodhound_multiproduct/tests/functional/__init__.py
    bloodhound/trunk/bloodhound_multiproduct/tests/functional/prefs.py
    bloodhound/trunk/bloodhound_multiproduct/tests/functional/ticket.py
Modified:
    bloodhound/trunk/trac/trac/tests/functional/__init__.py

Added: bloodhound/trunk/bloodhound_multiproduct/tests/functional/__init__.py
URL: 
http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_multiproduct/tests/functional/__init__.py?rev=1527447&view=auto
==============================================================================
--- bloodhound/trunk/bloodhound_multiproduct/tests/functional/__init__.py 
(added)
+++ bloodhound/trunk/bloodhound_multiproduct/tests/functional/__init__.py Mon 
Sep 30 05:31:52 2013
@@ -0,0 +1,743 @@
+# -*- coding: utf-8 -*-
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+
+import imp
+from inspect import isclass
+import os
+from subprocess import call, Popen
+import sys
+import time
+import urllib
+import urllib2
+
+from trac.tests.contentgen import random_sentence, random_page,\
+    random_unique_camel
+from trac.tests import functional
+from trac.tests.functional.svntestenv import SvnFunctionalTestEnvironment
+from trac.tests.functional.testenv import FunctionalTestEnvironment, 
ConnectError
+from trac.tests.functional.tester import b, FunctionalTester, internal_error, 
tc
+from trac.util.compat import close_fds
+from trac.util.text import unicode_quote
+from trac.web.href import Href
+from multiproduct.api import MultiProductSystem
+from multiproduct import hooks
+
+from tests import unittest
+
+#----------------
+# Product-aware classes for functional tests
+#----------------
+
+class MultiproductFunctionalMixin(object):
+    """Mixin class applying multi-product upgrade path upon a given
+    functional Trac test environment. Access to the global environment
+    is provided at testing time. In order to obtain a compatible test
+    environment for a given product @see: `product_test_env` method 
+
+    @attention: This class must precede functional test environment class in
+                class declaration because it overrides some methods
+    """
+
+    def init(self):
+        """Determine the location of Trac source code
+        """
+        self.bhmp_upgrade = False
+        self.trac_src = os.path.realpath(os.path.join( 
+                __import__('trac', []).__file__, '..' , '..'))
+        self.bh_src = os.path.realpath(os.path.join( 
+                __import__('multiproduct', []).__file__, '..' , '..', '..'))
+        self.htdigest = os.path.join(self.dirname, "htdigest")
+        self.htdigest_realm = 'bloodhound'
+        print "\nFound Trac source: %s" \
+              "\nFound Bloodhound source: %s" % (self.trac_src, self.bh_src)
+        super(MultiproductFunctionalMixin, self).init()
+
+    def create(self):
+        """Create a new test environment.
+        This will set up Bloodhound and authentication by invoking installer
+        script, then call :meth:`create_repo`.
+        """
+        os.mkdir(self.dirname)
+        self.create_repo()
+
+        self._bloodhound_install()
+        if call([sys.executable,
+                 os.path.join(self.trac_src, 'contrib', 'htpasswd.py'), "-c",
+                 "-b", self.htpasswd, "admin", "admin"], close_fds=close_fds,
+                 cwd=self.command_cwd):
+            raise Exception('Unable to setup admin password')
+        self.adduser('user')
+        self._tracadmin('permission', 'add', 'admin', 'TRAC_ADMIN')
+
+        # Setup Trac logging
+        env = self.get_trac_environment()
+        env.config.set('logging', 'log_type', 'file')
+        for component in self.get_enabled_components():
+            env.config.set('components', component, 'enabled')
+        env.config.save()
+        self.post_create(env)
+
+    def adduser_htpasswd(self, user):
+        """Add a user to the environment.  The password will be set 
+        in htpasswd file to the same as username.
+        """
+        return super(MultiproductFunctionalMixin, self).adduser(user)  
+
+    def adduser_htdigest(self, user):
+        """Add a user to the environment.  The password will be set 
+        in htdigest file to the same as username.
+        """
+        if call([sys.executable, os.path.join(self.trac_src, 'contrib',
+                 'htdigest.py'), '-b', self.htdigest, self.htdigest_realm,
+                 user, user], close_fds=close_fds, cwd=self.command_cwd):
+            raise Exception('Unable to setup password for user "%s"' % user)
+
+    adduser = adduser_htdigest
+
+    def get_env_href(self, user=None, prefix=None, envname=None):
+        """Default implementation just returning href object for global
+        environment and failing if product prefix is specified.
+        """
+        if envname not in ('trac', None):
+            raise LookupError('Unknown environment ' + repr(envname))
+        if prefix is not None:
+            self._fail_no_mp_setup()
+        parts = urllib2.urlparse.urlsplit(self.url)
+        if not user or user == 'anonymous':
+            return Href('%s://%s/' % (parts[0], parts[1]))
+        else:
+            return Href('%s://%s:%s@%s/' % (parts[0], user, user, parts[1]))
+
+    def get_enabled_components(self):
+        """Also enable Bloodhound multiproduct plugin. 
+        """
+        return super(MultiproductFunctionalMixin, 
self).get_enabled_components() + \
+                ['multiproduct.*']
+
+    def post_create(self, env):
+        self.getLogger = lambda : env.log
+
+        print "Created test environment: %s" % self.dirname
+
+        # Setup URL generation for product environments
+        self.get_env_href = self.configure_web_hooks()
+
+        super(MultiproductFunctionalMixin, self).post_create(env)
+
+    def _tracadmin(self, *args, **kwargs): 
+        """Execute trac-admin command in product or (by default) global context
+        """
+        do_wait = kwargs.pop('wait', False)
+        product_id = kwargs.pop('product', None)
+        if product_id:
+            if self.bhmp_upgrade and \
+                    args[0] not in ProductAdminModule.GLOBAL_COMMANDS:
+                args = ('product', 'admin', product_id) + args
+            elif not self.bhmp_upgrade:
+                self._fail_no_mp_setup()
+
+        super(MultiproductFunctionalMixin, self)._tracadmin(*args, **kwargs)
+        if do_wait: # Delay to ensure command executes and caches resets
+            time.sleep(5)
+
+    def start(self):
+        """Starts the webserver, and waits for it to come up.
+        
+        Notice: Same as inherited method but without basic auth by default
+        """
+        if 'FIGLEAF' in os.environ:
+            exe = os.environ['FIGLEAF']
+            if ' ' in exe: # e.g. 'coverage run'
+                args = exe.split()
+            else:
+                args = [exe]
+        else:
+            args = [sys.executable]
+        options = ["--port=%s" % self.port, "-s", "--hostname=127.0.0.1"]
+        if 'TRAC_TEST_TRACD_OPTIONS' in os.environ:
+            options += os.environ['TRAC_TEST_TRACD_OPTIONS'].split()
+        args.append(os.path.join(self.trac_src, 'trac', 'web',
+                                 'standalone.py'))
+        server = Popen(args + options + [self.tracdir],
+                       stdout=functional.logfile, stderr=functional.logfile,
+                       close_fds=close_fds,
+                       cwd=self.command_cwd,
+                      )
+        self.pid = server.pid
+        # Verify that the url is ok
+        timeout = 30
+        while timeout:
+            try:
+                tc.go(self.url)
+                break
+            except ConnectError:
+                time.sleep(1)
+            timeout -= 1
+        else:
+            raise Exception('Timed out waiting for server to start.')
+        tc.url(self.url)
+
+    def restart(self):
+        """Restarts the webserver"""
+        self.stop()
+        self.start()
+
+        # Reload components e.g. those in /plugins folder
+        from trac.loader import load_components
+
+        global_env = self.get_trac_environment()
+        plugins_dir = global_env.shared_plugins_dir
+        load_components(global_env, plugins_dir and (plugins_dir,))
+
+    def product_test_env(self, product_id):
+        """Functional test environment for product
+
+        @param product_id: target product prefix
+        @return: an object reusing resources in target functional test
+                 environment to implement a compatible interface for
+                 a given product environment
+        @raise LookupError: if there's no product for given prefix
+        """
+        raise NotImplementedError()
+
+    def configure_web_hooks(self):
+        """Setup web bootstrap_handlers and generation of product and global
+        base URLs for a given user
+
+        :return: a function used to generate base URL for product and 
+                 global environments . It will satisfy the following signature
+                 `base_url(user=None, prefix=None, envname=None)` where::
+
+                 @param user: username used to construct URLs for authenticated
+                              requests
+                 @param prefix: product prefix ; global environment selected
+                                if missing
+                 @param envname: environment name , useful in functional setup
+                                 running sibling Trac environments under
+                                 parent directory
+
+        Generated URLs must be consistent with web hooks configuration
+        @see: `_configure_web_hooks` method . By default `envname` is ignored 
+        and product base URL will be at /products under URL namespace of the
+        global environment.
+        """
+        def _default_base_href(user=None, prefix=None, envname=None):
+            # TODO: Does not generate /login ? Should it ?
+            parts = urllib2.urlparse.urlsplit(self.url)
+            if not user or user == 'anonymous':
+                global_href = Href('%s://%s/' % (parts[0], parts[1]))
+            else:
+                global_href = Href('%s://%s:%s@%s/' % 
+                                   (parts[0], user, user, parts[1]))
+            return global_href if not prefix \
+                               else Href(global_href('products', prefix))
+
+        return _default_base_href
+
+    # Protected methods
+
+    def _bloodhound_install(self):
+        """Execute Bloodhound installer script
+        """
+        cwd = os.getcwdu()
+
+        try:
+            os.chdir(os.path.join(self.bh_src, 'installer'))
+            create_digest = imp.load_source('bloodhound_setup',
+                                            os.path.join(self.bh_src, 
'installer',
+                                                         'createdigest.py'))
+            sys.modules['createdigest'] = create_digest
+            bhsetup = imp.load_source('bloodhound_setup',
+                                      os.path.join(self.bh_src, 'installer',
+                                                   'bloodhound_setup.py'))
+
+            #FIXME: Account manager's store will not work even after this
+            # Prepare installer for HTTP basic authentication store
+#            bhsetup.ACCOUNTS_CONFIG['account-manager'].update(
+#                          {'htpasswd_file' : self.htpasswd,
+#                           'password_store' : 'HtPasswdStore'})
+
+            # Enable timeline and roadmap views; needed in functional tests
+            bhsetup.BASE_CONFIG['mainnav'].update({'timeline': 'enabled',
+                                                   'roadmap': 'enabled'})
+
+            bhsetup = bhsetup.BloodhoundSetup({'project' : 'trac',
+                                               'envsdir' : self.dirname})
+
+            # Do not perform Bloodhound-specific wiki upgrades
+            bhsetup.apply_bhwiki_upgrades = False
+
+            bhsetup.setup(adminuser='admin', adminpass='admin', 
+                          dbstring=self.dburi, default_product_prefix='test',
+                          digestfile=self.htdigest, realm=self.htdigest_realm,
+                          repo_type=self.repotype,
+                          repo_path=self.repo_path_for_initenv(),
+                          sourcedir=self.bh_src)
+
+        finally:
+            os.chdir(cwd)
+
+    def _fail_no_mp_setup(self):
+        raise EnvironmentError('Product admin executed before upgrade')
+
+    def _default_product(self, envname=None):
+        """Default product configured for a given environment
+
+        @raise LookupError: if no environment matching `envname` can be found
+        """
+        if envname not in ('trac', None):
+            raise LookupError('Unable to open environment ' + envname)
+        env = self.get_trac_environment()
+        return MultiProductSystem(env).default_product_prefix
+
+
+class BloodhoundFunctionalTester(FunctionalTester):
+    """Leverages Trac library of higher-level operations for interacting with
+    a fully featured Apache(TM) Bloodhound test environment.
+
+    Many things have changed in recent versions of Apache(TM) Bloodhound
+    user interface once theme and dashboard are both installed:
+
+    - 'New Ticket' link has been phased out in favor of 'More fields' link in
+      quick create ticket shortcut menu.
+    - New helper method `quick_create_ticket` has been added to create a 
+      new (random) ticket via quick create ticket shortcut menu.
+    - 'logged in as user' label replaced by '<i class="icon-user"></i>user'
+    - By using account manager plugin a web form must be submitted to login  
+    - As a consequence of default hooks new tickets in global scope are
+      always bound to default product
+    - Timeline module is disabled; frequently used along functional tests
+    - View Tickets renamed to Tickets pointing at dashboard
+    - Milestones `No date set` label replaced by `Unscheduled`
+    - There's no actual button to submit `propertyform` in new ticket page
+    - Different markup used to render ticket fields
+
+    Other notable differences not solved by this class (target test cases
+    should be rewritten?)
+
+    - Preferences link removed in Bloodhound UI
+    - There's no such thing like ticket preview in Bloodhound UI
+    - 'Create New Ticket' label in new ticket page replaced by 'New Ticket' 
+
+    As a consequence some methods of Trac functional tester have to be updated.
+    """
+
+    def login(self, username):
+        """Login as the given user
+
+        Consider that 'logged in as user' label has been replaced by
+        '<i class="icon-user"></i>user'
+        """
+        #FIXME: Keep/remove this ?
+        #tc.add_auth("", self.url, username, username)
+        self.go_to_front()
+        tc.find("Login")
+        tc.follow("Login")
+
+        # Submit user + password via account manager login form
+        tc.formvalue('acctmgr_loginform', 'user', username)
+        tc.formvalue('acctmgr_loginform', 'password', username)
+        tc.submit()
+        self.go_to_front()
+
+        tc.find(r'<i class="icon-user"></i>\s*%s' % username)
+        tc.find("Logout")
+        tc.url(self.url)
+        tc.notfind(internal_error)
+
+    def _post_create_ticket(self):
+        """Look at the newly created ticket page after creating it
+        """
+        # we should be looking at the newly created ticket
+        tc.url(self.url + '/ticket/%s' % (self.ticketcount + 1))
+        # Increment self.ticketcount /after/ we've verified that the ticket
+        # was created so a failure does not trigger spurious later
+        # failures.
+        self.ticketcount += 1
+
+        # verify the ticket creation event shows up in the timeline
+        self.go_to_timeline()
+        tc.formvalue('prefs', 'ticket', True)
+        tc.submit()
+        tc.find('Ticket.*#%s.*created' % self.ticketcount)
+
+    def create_ticket(self, summary=None, info=None):
+        """Create a new (random) ticket in the test environment.  Returns
+        the new ticket number.
+
+        :param summary:
+            may optionally be set to the desired summary
+        :param info:
+            may optionally be set to a dictionary of field value pairs for
+            populating the ticket.  ``info['summary']`` overrides summary.
+
+        `summary` and `description` default to randomly-generated values.
+        """
+        # [BLOODHOUND] New Ticket => More fields (in create ticket menu)
+        self.go_to_newticket()
+
+        tc.notfind(internal_error)
+        if summary == None:
+            summary = random_sentence(4)
+        tc.formvalue('propertyform', 'field_summary', summary)
+        tc.formvalue('propertyform', 'field_description', random_page())
+        if info:
+            for field, value in info.items():
+                tc.formvalue('propertyform', 'field_%s' % field, value)
+        
+        # [BLOODHOUND] no actual button to submit /newticket `propertyform` 
+        tc.submit()
+
+        self._post_create_ticket()
+        return self.ticketcount
+
+    def create_report(self, title, query, description):
+        """Create a new report with the given title, query, and description
+        """
+        self.go_to_front()
+        # [BLOODHOUND] View Tickets renamed to Tickets pointing at dashboard
+        tc.follow('Tickets')
+        tc.notfind(internal_error)
+        tc.follow('Reports')
+        tc.notfind(internal_error)
+        tc.formvalue('create_report', 'action', 'new') # select new report form
+        tc.submit()
+        tc.find('New Report')
+        tc.notfind(internal_error)
+        tc.formvalue('edit_report', 'title', title)
+        tc.formvalue('edit_report', 'description', description)
+        tc.formvalue('edit_report', 'query', query)
+        tc.submit()
+        reportnum = b.get_url().split('/')[-1]
+        # TODO: verify the url is correct
+        # TODO: verify the report number is correct
+        # TODO: verify the report does not cause an internal error
+        # TODO: verify the title appears on the report list
+        return reportnum
+
+    def create_milestone(self, name=None, due=None):
+        """Creates the specified milestone, with a random name if none is
+        provided.  Returns the name of the milestone.
+        """
+        if name == None:
+            name = random_unique_camel()
+        milestone_url = self.url + "/admin/ticket/milestones"
+        tc.go(milestone_url)
+        tc.url(milestone_url)
+        tc.formvalue('addmilestone', 'name', name)
+        if due:
+            # TODO: How should we deal with differences in date formats?
+            tc.formvalue('addmilestone', 'duedate', due)
+        tc.submit()
+        tc.notfind(internal_error)
+        tc.notfind('Milestone .* already exists')
+        tc.url(milestone_url)
+        tc.find(name)
+
+        # Make sure it's on the roadmap.
+        tc.follow('Roadmap')
+        tc.url(self.url + "/roadmap")
+        tc.find('Milestone:.*%s' % name)
+        tc.follow(name)
+        tc.url('%s/milestone/%s' % (self.url, unicode_quote(name)))
+        if not due:
+            # [BLOODHOUND] No date set => Unscheduled
+            tc.find('Unscheduled')
+
+        return name
+
+    def go_to_query(self):
+        """Surf to the custom query page.
+        """
+        self.go_to_front()
+        # [BLOODHOUND] View Tickets (reports list) => Tickets (dashboard)
+        tc.follow('^Tickets$')
+        tc.notfind(internal_error)
+        tc.url(self.url + '/dashboard')
+        tc.follow('Custom Query')
+        tc.url(self.url + '/query')
+
+    # Bloodhound functional tester extensions
+
+    def go_to_newticket(self):
+        self.go_to_front()
+
+        tc.follow('More fields')
+
+    def quick_create_ticket(self, summary=None, info=None):
+        """Create a new (random) ticket in the test environment via quick
+        create ticket shortcut. Returns the new ticket number.
+
+        :param summary:
+            may optionally be set to the desired summary
+        :param info:
+            may optionally be set to a dictionary of field value pairs for
+            populating the ticket.  Fields are populated afterwards by
+            navigating to ticket page, thereby ``info['summary']``overrides
+            ``summary``.
+
+        `summary` and `description` default to randomly-generated values.
+        """
+        self.go_to_front()
+        tc.notfind(internal_error)
+
+        if summary == None:
+            summary = random_sentence(4)
+        tc.formvalue('qct-form', 'field_summary', summary)
+        tc.formvalue('qct-form', 'field_description', random_page())
+        self._post_create_ticket()
+
+        if info:
+            # Second pass to update ticket fields
+            tc.url(self.url + '/ticket/%s' % (self.ticketcount + 1))
+            tc.notfind(internal_error)
+            for field, value in info.items():
+                tc.formvalue('inplace-propertyform', 'field_%s' % field, value)
+            tc.submit('submit')
+
+        return self.ticketcount
+
+    def find_ticket_field(self, fieldname, fieldval):
+        """Assert that expected value (pattern) matches value in ticket view
+        """
+        tc.find(r'<td [^>]*\bid="vc-%s"[^>]*>\s*%s\s*</td>' % (fieldname, 
fieldval))
+
+
+class BloodhoundGlobalEnvFunctionalTester(BloodhoundFunctionalTester):
+    """Library of higher-level operations for interacting with
+    a global Apache(TM) Bloodhound test environment enabled with automatic
+    redirects from global environment to resources in default product.
+
+    Many things have changed in recent versions of Apache(TM) Bloodhound
+    user interface once theme and dashboard are both installed. Beyond common
+    differences this functional tester also deals with :
+
+    - Tickets are created in default product context
+    - Admin panels for ticket fields are only accessible in default product
+      context
+    - Reports are created in default product context
+
+    As a consequence some methods of Trac functional tester have to be
+    executed in special ways.
+    """
+    def __init__(self, url, default_product_url=None):
+        super(BloodhoundGlobalEnvFunctionalTester, self).__init__(url)
+        self.default_product_url = default_product_url
+
+    class in_defaut_product(object):
+        """Context manager temporarily switching to default product URL
+        """
+        def __init__(self, tester):
+            self.tester = tester
+            self.global_url = None
+
+        def __enter__(self):
+            """Replace tester base URL with default product's URL
+            """
+            self.global_url = self.tester.url
+            self.tester.url = self.tester.default_product_url
+            return self.tester
+
+        def __exit__(self, exc_type, exc_value, traceback):
+            """Restore tester URL poiting at global environment
+            """
+            self.tester.url = self.global_url 
+
+    def _post_create_ticket(self):
+        """Look at the newly created ticket page after creating it
+        ... but in default product context ...
+        """
+        superobj = super(BloodhoundGlobalEnvFunctionalTester, self)
+        with self.in_defaut_product(self):
+            return superobj._post_create_ticket()
+
+    def create_milestone(self, name=None, due=None):
+        """Creates the specified milestone, with a random name if none is
+        provided.  Returns the name of the milestone.
+
+        ... executed in default product context 
+        """
+        superobj = super(BloodhoundGlobalEnvFunctionalTester, self)
+        with self.in_defaut_product(self):
+            return superobj.create_milestone(name, due)
+
+    def create_component(self, name=None, user=None):
+        """Creates the specified component, with a random camel-cased name if
+        none is provided.  Returns the name.
+
+        ... executed in default product context 
+        """
+        superobj = super(BloodhoundGlobalEnvFunctionalTester, self)
+        with self.in_defaut_product(self):
+            return superobj.create_component(name, user)
+
+    def create_enum(self, kind, name=None):
+        """Helper to create the specified enum (used for ``priority``,
+        ``severity``, etc). If no name is given, a unique random word is used.
+        The name is returned.
+
+        ... executed in default product context 
+
+        """
+        superobj = super(BloodhoundGlobalEnvFunctionalTester, self)
+        with self.in_defaut_product(self):
+            return superobj.create_enum(kind, name)
+
+    def create_version(self, name=None, releasetime=None):
+        """Create a new version.  The name defaults to a random camel-cased
+        word if not provided.
+
+        ... executed in default product context 
+        """
+        superobj = super(BloodhoundGlobalEnvFunctionalTester, self)
+        with self.in_defaut_product(self):
+            return superobj.create_version(name, releasetime)
+
+
+#----------------
+# Product-aware functional setup
+#----------------
+
+class MultiproductFunctionalTestSuite(functional.FunctionalTestSuite):
+    """TestSuite that leverages a test fixture containing a
+    FunctionalTestEnvironment and a FunctionalTester by also
+    upgrading them to multi-product support after activating Bloodhound theme
+    and dashboard plugins.
+    """
+
+    class env_class(MultiproductFunctionalMixin, 
+                    functional.FunctionalTestSuite.env_class):
+        pass
+
+    tester_class = BloodhoundGlobalEnvFunctionalTester
+
+    def setUp(self, port=None):
+        print "Starting web server ..."
+        try:
+            functional.FunctionalTestSuite.setUp(self)
+        except:
+            # Ensure tracd process is killed on failure
+            print "Stopping web server...\n"
+            testenv = getattr(self, '_testenv', None)
+            if testenv:
+                testenv.stop()
+            raise
+        else:
+            prefix = self._testenv._default_product()
+            default_product_base = self._testenv.get_env_href(user=None,
+                                                              prefix=prefix)
+            self._tester.default_product_url = default_product_base()
+            print "Started web server: %s" % self._testenv.url
+
+    def run(self, result):
+        """Setup the fixture (self.setUp), call .setFixture on all the tests,
+        and tear down the fixture (self.tearDown).
+
+        Ensure marked tests will be wrapped by specialized context manager in
+        order access default product URL namespace instead of global.
+        """
+        self.setUp()
+        if hasattr(self, 'fixture'):
+            for test in self._tests:
+                if hasattr(test, 'setFixture'):
+                    test.setFixture(self.fixture)
+        # Override unittest loop
+        for test in self._tests:
+            if result.shouldStop:
+                break
+            if getattr(test, 'BH_IN_DEFAULT_PRODUCT', False) and \
+                    hasattr(self._tester, 'in_defaut_product'):
+                with self._tester.in_defaut_product(self._tester):
+                    test(result)
+            else:
+                test(result)
+        self.tearDown()
+        return result
+
+    def tearDown(self):
+        print "\nStopping web server...\n"
+        functional.FunctionalTestSuite.tearDown(self)
+
+# Mark some test cases to be run against default product
+import trac.ticket.tests.functional
+import trac.admin.tests.functional
+for mdl in (trac.ticket.tests.functional, trac.admin.tests.functional):
+    for attr in dir(mdl):
+        attr = getattr(mdl, attr)
+        if isclass(attr) and issubclass(attr, 
+                                        
functional.FunctionalTwillTestCaseSetup):
+            attr.BH_IN_DEFAULT_PRODUCT = True
+del attr, mdl
+
+def trac_functionalSuite(suite):
+    from trac.tests.functional import testcases
+    suite.addTest(testcases.RegressionTestRev6017())
+    suite.addTest(testcases.RegressionTestTicket3833a())
+    suite.addTest(testcases.RegressionTestTicket3833b())
+    suite.addTest(testcases.RegressionTestTicket3833c())
+    suite.addTest(testcases.RegressionTestTicket5572())
+    suite.addTest(testcases.RegressionTestTicket7209())
+    suite.addTest(testcases.RegressionTestTicket9880())
+    suite.addTest(testcases.ErrorPageValidation())
+    suite.addTest(testcases.RegressionTestTicket3663())
+
+    import trac.versioncontrol.tests
+    trac.versioncontrol.tests.functionalSuite(suite)
+
+    # import trac.ticket.tests
+    # trac.ticket.tests.functionalSuite(suite)
+    import tests.functional.ticket
+    tests.functional.ticket.functionalSuite(suite)
+
+    # import trac.prefs.tests
+    # trac.prefs.tests.functionalSuite(suite)
+    import tests.functional.prefs
+    tests.functional.prefs.functionalSuite(suite)
+
+    import trac.wiki.tests
+    trac.wiki.tests.functionalSuite(suite)
+    import trac.timeline.tests
+    trac.timeline.tests.functionalSuite(suite)
+    import trac.admin.tests
+    trac.admin.tests.functionalSuite(suite)
+    # The db tests should be last since the backup test occurs there.
+    import trac.db.tests
+    trac.db.tests.functionalSuite(suite)
+
+
+def functionalSuite():
+    suite = MultiproductFunctionalTestSuite()
+    return suite
+
+
+def test_suite():
+    suite = functionalSuite()
+
+    # TODO: Load Bloodhound-specific functional test cases
+
+    #from tests import TestLoader
+    # FIXME: Does this work for functional tests suite ?
+    # bhsuite = TestLoader().discover_package('tests.functional', 
pattern='*.py')
+
+    trac_functionalSuite(suite)
+
+    return suite
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: bloodhound/trunk/bloodhound_multiproduct/tests/functional/prefs.py
URL: 
http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_multiproduct/tests/functional/prefs.py?rev=1527447&view=auto
==============================================================================
--- bloodhound/trunk/bloodhound_multiproduct/tests/functional/prefs.py (added)
+++ bloodhound/trunk/bloodhound_multiproduct/tests/functional/prefs.py Mon Sep 
30 05:31:52 2013
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+
+from trac.tests import functional
+from trac.tests.functional.tester import internal_error, tc
+
+#----------------
+# Functional test cases for preferences (rewritten) 
+#----------------
+
+# TODO: These classes are almost a copycat of Trac's. Beware of license header
+
+class TestPreferences(functional.FunctionalTwillTestCaseSetup):
+    def runTest(self):
+        """Set preferences for admin user"""
+        prefs_url = self._tester.url + "/prefs"
+        # [BLOODHOUND] Preferences link removed
+        tc.follow('/prefs')
+        tc.url(prefs_url)
+        tc.notfind('Your preferences have been saved.')
+        tc.formvalue('userprefs', 'name', ' System Administrator ')
+        tc.formvalue('userprefs', 'email', ' [email protected] ')
+        tc.submit()
+        tc.find('Your preferences have been saved.')
+        tc.follow('Date & Time')
+        tc.url(prefs_url + '/datetime')
+        tc.formvalue('userprefs', 'tz', 'GMT -10:00')
+        tc.submit()
+        tc.find('Your preferences have been saved.')
+        tc.follow('General')
+        tc.url(prefs_url)
+        tc.notfind('Your preferences have been saved.')
+        tc.find('value="System Administrator"')
+        tc.find(r'value="admin@example\.com"')
+        tc.follow('Date & Time')
+        tc.url(prefs_url + '/datetime')
+        tc.find('GMT -10:00')
+
+
+class RegressionTestRev5785(functional.FunctionalTwillTestCaseSetup):
+    def runTest(self):
+        """Test for regression of the fix in r5785"""
+        prefs_url = self._tester.url + "/prefs"
+        # [BLOODHOUND] Preferences link removed
+        tc.follow('/prefs')
+        tc.url(prefs_url)
+        tc.follow('Logout')
+        tc.notfind(internal_error) # See [5785]
+        tc.follow('Login')
+
+
+class RegressionTestTicket5765(functional.FunctionalTwillTestCaseSetup):
+    def runTest(self):
+        """Test for regression of http://trac.edgewall.org/ticket/5765
+        Unable to turn off 'Enable access keys' in Preferences
+        """
+        self._tester.go_to_front()
+        # [BLOODHOUND] Preferences link removed
+        tc.follow('/prefs')
+        tc.follow('Keyboard Shortcuts')
+        tc.formvalue('userprefs', 'accesskeys', True)
+        tc.submit()
+        tc.find('name="accesskeys".*checked="checked"')
+        tc.formvalue('userprefs', 'accesskeys', False)
+        tc.submit()
+        tc.notfind('name="accesskeys".*checked="checked"')
+
+def functionalSuite(suite=None):
+    if not suite:
+        import tests.functional
+        suite = tests.functional.functionalSuite()
+
+    suite.addTest(TestPreferences())
+    suite.addTest(RegressionTestRev5785())
+    suite.addTest(RegressionTestTicket5765())
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='functionalSuite')

Added: bloodhound/trunk/bloodhound_multiproduct/tests/functional/ticket.py
URL: 
http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_multiproduct/tests/functional/ticket.py?rev=1527447&view=auto
==============================================================================
--- bloodhound/trunk/bloodhound_multiproduct/tests/functional/ticket.py (added)
+++ bloodhound/trunk/bloodhound_multiproduct/tests/functional/ticket.py Mon Sep 
30 05:31:52 2013
@@ -0,0 +1,415 @@
+# -*- coding: utf-8 -*-
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+
+"""Override a few functional tests for tickets.
+"""
+
+from urlparse import urlsplit
+
+from trac.ticket.tests.functional import *
+
+#----------------
+# Functional test cases for tickets (rewritten) 
+#----------------
+
+# TODO: These classes are almost a copycat of Trac's. Beware of license header
+
+class TestTicketPreview(FunctionalTwillTestCaseSetup):
+    """There's no such thing like ticket preview in Bloodhound but, if it would
+    then the corresponding Trac test case should be rewritten like this.
+    """
+    def runTest(self):
+        """Preview ticket creation
+        """
+        # [BLOODHOUND] New Ticket => More fields (in create ticket menu)
+        self._tester.go_to_newticket()
+
+        summary = random_sentence(5)
+        desc = random_sentence(5)
+        tc.formvalue('propertyform', 'field-summary', summary)
+        tc.formvalue('propertyform', 'field-description', desc)
+        tc.submit('preview')
+        tc.url(self._tester.url + '/newticket$')
+        tc.find('ticket not yet created')
+        tc.find(summary)
+        tc.find(desc)
+
+
+class TestTicketNoSummary(FunctionalTwillTestCaseSetup):
+    def runTest(self):
+        """Creating a ticket without summary should fail
+        """
+        # [BLOODHOUND] New Ticket => More fields (in create ticket menu)
+        self._tester.go_to_newticket()
+
+        desc = random_sentence(5)
+        tc.formvalue('propertyform', 'field-description', desc)
+        # [BLOODHOUND] no actual button to submit /newticket `propertyform` 
+        tc.submit()
+        tc.find(desc)
+        tc.find('Tickets must contain a summary.')
+        # [BLOODHOUND] Create New Ticket => New Ticket 
+        tc.find('New Ticket')
+        tc.find('ticket not yet created')
+
+
+class TestTicketCustomFieldTextNoFormat(FunctionalTwillTestCaseSetup):
+    def runTest(self):
+        """Test custom text field with no format explicitly specified.
+        Its contents should be rendered as plain text.
+        """
+        env = self._testenv.get_trac_environment()
+        env.config.set('ticket-custom', 'newfield', 'text')
+        env.config.set('ticket-custom', 'newfield.label',
+                       'Another Custom Field')
+        env.config.set('ticket-custom', 'newfield.format', '')
+        env.config.save()
+
+        self._testenv.restart()
+        val = "%s %s" % (random_unique_camel(), random_word())
+        ticketid = self._tester.create_ticket(summary=random_sentence(3),
+                                              info={'newfield': val})
+        self._tester.go_to_ticket(ticketid)
+
+        # [BLOODHOUND] Different markup to render field values
+        self._tester.find_ticket_field('newfield', val) 
+
+
+class TestTicketCustomFieldTextAreaNoFormat(FunctionalTwillTestCaseSetup):
+    def runTest(self):
+        """Test custom textarea field with no format explicitly specified,
+        its contents should be rendered as plain text.
+        """
+        env = self._testenv.get_trac_environment()
+        env.config.set('ticket-custom', 'newfield', 'textarea')
+        env.config.set('ticket-custom', 'newfield.label',
+                       'Another Custom Field')
+        env.config.set('ticket-custom', 'newfield.format', '')
+        env.config.save()
+
+        self._testenv.restart()
+        val = "%s %s" % (random_unique_camel(), random_word())
+        ticketid = self._tester.create_ticket(summary=random_sentence(3),
+                                              info={'newfield': val})
+        self._tester.go_to_ticket(ticketid)
+
+        # [BLOODHOUND] Different markup to render field values
+        self._tester.find_ticket_field('newfield', val)
+
+
+class TestTicketCustomFieldTextWikiFormat(FunctionalTwillTestCaseSetup):
+    def runTest(self):
+        """Test custom text field with `wiki` format.
+        Its contents should through the wiki engine, wiki-links and all.
+        Feature added in http://trac.edgewall.org/ticket/1791
+        """
+        env = self._testenv.get_trac_environment()
+        env.config.set('ticket-custom', 'newfield', 'text')
+        env.config.set('ticket-custom', 'newfield.label',
+                       'Another Custom Field')
+        env.config.set('ticket-custom', 'newfield.format', 'wiki')
+        env.config.save()
+
+        self._testenv.restart()
+        word1 = random_unique_camel()
+        word2 = random_word()
+        val = "%s %s" % (word1, word2)
+        ticketid = self._tester.create_ticket(summary=random_sentence(3),
+                                              info={'newfield': val})
+        self._tester.go_to_ticket(ticketid)
+        wiki = '<a [^>]*>%s\??</a> %s' % (word1, word2)
+
+        # [BLOODHOUND] Different markup to render field values
+        self._tester.find_ticket_field('newfield', wiki) 
+
+
+class TestTicketCustomFieldTextAreaWikiFormat(FunctionalTwillTestCaseSetup):
+    def runTest(self):
+        """Test custom textarea field with no format explicitly specified,
+        its contents should be rendered as plain text.
+        """
+        env = self._testenv.get_trac_environment()
+        env.config.set('ticket-custom', 'newfield', 'textarea')
+        env.config.set('ticket-custom', 'newfield.label',
+                       'Another Custom Field')
+        env.config.set('ticket-custom', 'newfield.format', 'wiki')
+        env.config.save()
+
+        self._testenv.restart()
+        word1 = random_unique_camel()
+        word2 = random_word()
+        val = "%s %s" % (word1, word2)
+        ticketid = self._tester.create_ticket(summary=random_sentence(3),
+                                              info={'newfield': val})
+        self._tester.go_to_ticket(ticketid)
+        wiki = '<p>\s*<a [^>]*>%s\??</a> %s<br />\s*</p>' % (word1, word2)
+
+        # [BLOODHOUND] Different markup to render field values
+        self._tester.find_ticket_field('newfield', wiki) 
+
+
+class TestTicketCustomFieldTextReferenceFormat(FunctionalTwillTestCaseSetup):
+    # Run this test case in default product context to keep body agnostic to
+    # context switching
+    BH_IN_DEFAULT_PRODUCT = True
+
+    def runTest(self):
+        """Test custom text field with `reference` format.
+        Its contents are treated as a single value
+        and are rendered as an auto-query link.
+        Feature added in http://trac.edgewall.org/ticket/10643
+        """
+        env = self._testenv.get_trac_environment()
+        env.config.set('ticket-custom', 'newfield', 'text')
+        env.config.set('ticket-custom', 'newfield.label',
+                       'Another Custom Field')
+        env.config.set('ticket-custom', 'newfield.format', 'reference')
+        env.config.save()
+
+        self._testenv.restart()
+        word1 = random_unique_camel()
+        word2 = random_word()
+        val = "%s %s" % (word1, word2)
+        ticketid = self._tester.create_ticket(summary=random_sentence(3),
+                                              info={'newfield': val})
+        self._tester.go_to_ticket(ticketid)
+        query = 'status=!closed&amp;newfield=%s\+%s' % (word1, word2)
+
+        path_prefix = urlsplit(self._tester.url).path
+        querylink = '<a href="%s/query\?%s">%s</a>' % (path_prefix, query, val)
+
+        # [BLOODHOUND] Different markup to render field values
+        self._tester.find_ticket_field('newfield', querylink) 
+
+
+class TestTicketCustomFieldTextListFormat(FunctionalTwillTestCaseSetup):
+    # Run this test case in default product context to keep body agnostic to
+    # context switching
+    BH_IN_DEFAULT_PRODUCT = True
+
+    def runTest(self):
+        """Test custom text field with `list` format.
+        Its contents are treated as a space-separated list of values
+        and are rendered as separate auto-query links per word.
+        Feature added in http://trac.edgewall.org/ticket/10643
+        """
+        env = self._testenv.get_trac_environment()
+        env.config.set('ticket-custom', 'newfield', 'text')
+        env.config.set('ticket-custom', 'newfield.label',
+                       'Another Custom Field')
+        env.config.set('ticket-custom', 'newfield.format', 'list')
+        env.config.save()
+
+        self._testenv.restart()
+        word1 = random_unique_camel()
+        word2 = random_word()
+        val = "%s %s" % (word1, word2)
+        ticketid = self._tester.create_ticket(summary=random_sentence(3),
+                                              info={'newfield': val})
+        self._tester.go_to_ticket(ticketid)
+        query1 = 'status=!closed&amp;newfield=~%s' % word1
+        query2 = 'status=!closed&amp;newfield=~%s' % word2
+
+        path_prefix = urlsplit(self._tester.url).path
+        querylink1 = '<a href="%s/query\?%s">%s</a>' % (path_prefix,
+                                                        query1, word1)
+        querylink2 = '<a href="%s/query\?%s">%s</a>' % (path_prefix,
+                                                        query2, word2)
+        querylinks = '%s %s' % (querylink1, querylink2)
+
+        # [BLOODHOUND] Different markup to render field values
+        self._tester.find_ticket_field('newfield', querylinks) 
+
+
+class RegressionTestTicket10828(FunctionalTwillTestCaseSetup):
+    # Run this test case in default product context to keep body agnostic to
+    # context switching
+    BH_IN_DEFAULT_PRODUCT = True
+
+    def runTest(self):
+        """Test for regression of http://trac.edgewall.org/ticket/10828
+        Rendered property changes should be described as lists of added and
+        removed items, even in the presence of comma and semicolon separators.
+        """
+        env = self._testenv.get_trac_environment()
+        env.config.set('ticket-custom', 'newfield', 'text')
+        env.config.set('ticket-custom', 'newfield.label',
+                       'A Custom Field')
+        env.config.set('ticket-custom', 'newfield.format', 'list')
+        env.config.save()
+
+        self._testenv.restart()
+        ticketid = self._tester.create_ticket(summary=random_sentence(3))
+        self._tester.go_to_ticket(ticketid)
+
+        word1 = random_unique_camel()
+        word2 = random_word()
+        val = "%s %s" % (word1, word2)
+        tc.formvalue('propertyform', 'field-newfield', val)
+        tc.submit('submit')
+        tc.find('<em>%s</em> <em>%s</em> added' % (word1, word2))
+
+        word3 = random_unique_camel()
+        word4 = random_unique_camel()
+        val = "%s,  %s; %s" % (word2, word3, word4)
+        tc.formvalue('propertyform', 'field-newfield', val)
+        tc.submit('submit')
+        tc.find('<em>%s</em> <em>%s</em> added; <em>%s</em> removed'
+                % (word3, word4, word1))
+
+        tc.formvalue('propertyform', 'field-newfield', '')
+        tc.submit('submit')
+        tc.find('<em>%s</em> <em>%s</em> <em>%s</em> removed'
+                % (word2, word3, word4))
+
+        val = "%s %s,%s" % (word1, word2, word3)
+        tc.formvalue('propertyform', 'field-newfield', val)
+        tc.submit('submit')
+        tc.find('<em>%s</em> <em>%s</em> <em>%s</em> added'
+                % (word1, word2, word3))
+        query1 = 'status=!closed&amp;newfield=~%s' % word1
+        query2 = 'status=!closed&amp;newfield=~%s' % word2
+        query3 = 'status=!closed&amp;newfield=~%s' % word3
+
+        
+        path_prefix = urlsplit(self._tester.url).path
+        querylink1 = '<a href="%s/query\?%s">%s</a>' % (path_prefix,
+                                                        query1, word1)
+        querylink2 = '<a href="%s/query\?%s">%s</a>' % (path_prefix,
+                                                        query2, word2)
+        querylink3 = '<a href="%s/query\?%s">%s</a>' % (path_prefix,
+                                                        query3, word3)
+        querylinks = '%s %s, %s' % (querylink1, querylink2, querylink3)
+
+        # [BLOODHOUND] Different markup to render field values
+        self._tester.find_ticket_field('newfield', querylinks) 
+
+
+# Ensure that overridden code will be loaded
+def functionalSuite(suite=None):
+    if not suite:
+        import trac.tests.functional.testcases
+        suite = trac.tests.functional.testcases.functionalSuite()
+
+    suite.addTest(TestTickets())
+
+    # [BLOODHOUND] there's no such thing like ticket preview
+    #suite.addTest(TestTicketPreview())
+
+    suite.addTest(TestTicketNoSummary())
+    suite.addTest(TestTicketAltFormats())
+    suite.addTest(TestTicketCSVFormat())
+    suite.addTest(TestTicketTabFormat())
+    suite.addTest(TestTicketRSSFormat())
+
+    # [BLOODHOUND] TODO: Move to BloodhoundSearch plugin 
+    # suite.addTest(TestTicketSearch())
+    # suite.addTest(TestNonTicketSearch())
+
+    suite.addTest(TestTicketHistory())
+    suite.addTest(TestTicketHistoryDiff())
+    suite.addTest(TestTicketQueryLinks())
+    suite.addTest(TestTicketQueryOrClause())
+    suite.addTest(TestTicketCustomFieldTextNoFormat())
+    suite.addTest(TestTicketCustomFieldTextWikiFormat())
+    suite.addTest(TestTicketCustomFieldTextAreaNoFormat())
+    suite.addTest(TestTicketCustomFieldTextAreaWikiFormat())
+    suite.addTest(TestTicketCustomFieldTextReferenceFormat())
+    suite.addTest(TestTicketCustomFieldTextListFormat())
+    suite.addTest(RegressionTestTicket10828())
+    suite.addTest(TestTimelineTicketDetails())
+    suite.addTest(TestAdminComponent())
+    suite.addTest(TestAdminComponentDuplicates())
+    suite.addTest(TestAdminComponentRemoval())
+    suite.addTest(TestAdminComponentNonRemoval())
+    suite.addTest(TestAdminComponentDefault())
+    suite.addTest(TestAdminComponentDetail())
+    suite.addTest(TestAdminMilestone())
+    suite.addTest(TestAdminMilestoneSpace())
+    suite.addTest(TestAdminMilestoneDuplicates())
+    suite.addTest(TestAdminMilestoneDetail())
+    suite.addTest(TestAdminMilestoneDue())
+    suite.addTest(TestAdminMilestoneDetailDue())
+    suite.addTest(TestAdminMilestoneCompleted())
+    suite.addTest(TestAdminMilestoneCompletedFuture())
+    suite.addTest(TestAdminMilestoneRemove())
+    suite.addTest(TestAdminMilestoneRemoveMulti())
+    suite.addTest(TestAdminMilestoneNonRemoval())
+    suite.addTest(TestAdminMilestoneDefault())
+    suite.addTest(TestAdminPriority())
+    suite.addTest(TestAdminPriorityModify())
+    suite.addTest(TestAdminPriorityRemove())
+    suite.addTest(TestAdminPriorityRemoveMulti())
+    suite.addTest(TestAdminPriorityNonRemoval())
+    suite.addTest(TestAdminPriorityDefault())
+    suite.addTest(TestAdminPriorityDetail())
+    suite.addTest(TestAdminPriorityRenumber())
+    suite.addTest(TestAdminPriorityRenumberDup())
+    suite.addTest(TestAdminResolution())
+    suite.addTest(TestAdminResolutionDuplicates())
+    suite.addTest(TestAdminSeverity())
+    suite.addTest(TestAdminSeverityDuplicates())
+    suite.addTest(TestAdminType())
+    suite.addTest(TestAdminTypeDuplicates())
+    suite.addTest(TestAdminVersion())
+    suite.addTest(TestAdminVersionDuplicates())
+    suite.addTest(TestAdminVersionDetail())
+    suite.addTest(TestAdminVersionDetailTime())
+    suite.addTest(TestAdminVersionDetailCancel())
+    suite.addTest(TestAdminVersionRemove())
+    suite.addTest(TestAdminVersionRemoveMulti())
+    suite.addTest(TestAdminVersionNonRemoval())
+    suite.addTest(TestAdminVersionDefault())
+    suite.addTest(TestNewReport())
+    suite.addTest(TestReportRealmDecoration())
+    suite.addTest(RegressionTestRev5665())
+    suite.addTest(RegressionTestRev5994())
+
+    suite.addTest(RegressionTestTicket4447())
+    suite.addTest(RegressionTestTicket4630a())
+    suite.addTest(RegressionTestTicket4630b())
+    suite.addTest(RegressionTestTicket5022())
+    suite.addTest(RegressionTestTicket5394a())
+    suite.addTest(RegressionTestTicket5394b())
+    suite.addTest(RegressionTestTicket5497prep())
+    suite.addTest(RegressionTestTicket5497a())
+    suite.addTest(RegressionTestTicket5497b())
+    suite.addTest(RegressionTestTicket5497c())
+    suite.addTest(RegressionTestTicket5497d())
+    suite.addTest(RegressionTestTicket5602())
+    suite.addTest(RegressionTestTicket5687())
+    suite.addTest(RegressionTestTicket5930())
+    suite.addTest(RegressionTestTicket6048())
+    suite.addTest(RegressionTestTicket6747())
+    suite.addTest(RegressionTestTicket6879a())
+    suite.addTest(RegressionTestTicket6879b())
+    suite.addTest(RegressionTestTicket6912a())
+    suite.addTest(RegressionTestTicket6912b())
+    suite.addTest(RegressionTestTicket7821group())
+    suite.addTest(RegressionTestTicket7821var())
+    suite.addTest(RegressionTestTicket8247())
+    suite.addTest(RegressionTestTicket8861())
+    suite.addTest(RegressionTestTicket9084())
+    suite.addTest(RegressionTestTicket9981())
+
+    return suite
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='functionalSuite')

Modified: bloodhound/trunk/trac/trac/tests/functional/__init__.py
URL: 
http://svn.apache.org/viewvc/bloodhound/trunk/trac/trac/tests/functional/__init__.py?rev=1527447&r1=1527446&r2=1527447&view=diff
==============================================================================
--- bloodhound/trunk/trac/trac/tests/functional/__init__.py (original)
+++ bloodhound/trunk/trac/trac/tests/functional/__init__.py Mon Sep 30 05:31:52 
2013
@@ -104,6 +104,8 @@ if twill:
         else:
             env_class = FunctionalTestEnvironment
 
+        tester_class = FunctionalTester
+
         def setUp(self, port=None):
             """If no port is specified, use a semi-random port and subdirectory
             'testenv'; but if a port is specified, use that port and
@@ -119,7 +121,7 @@ if twill:
             baseurl = "http://127.0.0.1:%s"; % port
             self._testenv = self.env_class(dirname, port, baseurl)
             self._testenv.start()
-            self._tester = FunctionalTester(baseurl)
+            self._tester = self.tester_class(baseurl)
             self.fixture = (self._testenv, self._tester)
 
         def tearDown(self):


Reply via email to