Commits

Olemis Lang committed 0c0ae42 Merge

BH Multiproduct #463 : Merge patches for #387 back into ticket:463 branch

Comments (0)

Files changed (9)

 t387/t387_r1485255_functional_test_infra.diff
 t387/t387_r1495238_attach_btn_value.diff
 #t577/t577_r1496958_newticket_id.diff
-t387/t387_r149918_global_timeline.diff
+#t387/t387_r149918_global_timeline.diff
 #t387/t387_r149918_global_attachments.diff
 #t576/t576_r1495238_report_id_col.diff
 t387/t387_r1495238_functional_test.2.diff
 t387/t387_r1495238_functional_test.3.diff
 #t621/t621_r1508753_ticket_version_label.diff
-t575/t575_r1496958_product_title_index.diff
-t575/t575_r1496958_product_recent_changes.diff
+t387/t387_r1514232_functional_test.4.diff
+t387/t387_r1514860_functional_test.5.diff
+t598/t598_r1510975_functional_tester_product.diff
+t387/t387_r1515319_functional_test.6.diff
+t387/t387_r1515319_functional_test.7.diff
+#t575/t575_r1496958_product_title_index.diff
+#t575/t575_r1496958_product_recent_changes.diff
 t598/t598_r1510975_new_product_intertracwiki.diff
-t598/t598_r1510975_functional_tester_product.diff
 t598/t598_r1510975_test_new_product.diff
 t463/t463_r1513332_test_unicode_norm.diff
 t463/t463_r1513332_unicode_product_prefix.diff
+t387/t387_r1524640_no_r5994.diff

t387/t387_r1514232_functional_test.4.diff

+# HG changeset patch
+# Parent e6080cbd5c664ded30fd0d08a123b85b03f9bb41
+BH Multiproduct #387 : Refactor test code to include BH test cases together with Trac's
+
+diff -r e6080cbd5c66 bloodhound_multiproduct/tests/functional/__init__.py
+--- a/bloodhound_multiproduct/tests/functional/__init__.py	Fri Aug 16 15:07:02 2013 -0500
++++ b/bloodhound_multiproduct/tests/functional/__init__.py	Fri Aug 16 18:13:42 2013 -0500
+@@ -352,6 +352,20 @@
+     As a consequence some methods of Trac functional tester have to be updated.
+     """
+ 
++    def __init__(self, url, skiplogin=False):
++        """Create a :class:`BloodhoundFunctionalTester` for the given 
++        environment URL and Subversion URL
++        
++        :param skiplogin:   Skip admin user login
++        """
++        self.url = url
++        self.ticketcount = 0
++
++        # Connect, and login so we can run tests.
++        self.go_to_front()
++        if not skiplogin:
++            self.login('admin')
++
+     def login(self, username):
+         """Login as the given user
+ 
+@@ -561,6 +575,29 @@
+         tc.find(self.regex_query_column_selector(fieldname, fieldlbl), 's')
+ 
+ 
++    class in_product(object):
++        """Context manager temporarily switching to product URL
++        """
++        def __init__(self, tester, url=None):
++            self.tester = tester
++            self.prev_url = None
++            self.url = url
++
++        def __enter__(self):
++            """Replace tester base URL with default product's URL
++            """
++            self.prev_url = self.tester.url
++            self.tester.url = self.url if self.url else \
++                              getattr(self.tester, 'default_product_url',
++                                      self.tester.url)
++            return self.tester
++
++        def __exit__(self, exc_type, exc_value, traceback):
++            """Restore tester URL poiting at global environment
++            """
++            self.tester.url = self.prev_url 
++
++
+ class BloodhoundGlobalEnvFunctionalTester(BloodhoundFunctionalTester):
+     """Library of higher-level operations for interacting with
+     a global Apache(TM) Bloodhound test environment enabled with automatic
+@@ -582,31 +619,24 @@
+         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
++    class in_product(BloodhoundFunctionalTester.in_product):
++        """Context manager temporarily switching to 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 __init__(self, tester, url=None):
++            if url is not None and \
++                    isinstance(tester, BloodhoundGlobalEnvFunctionalTester):
++                # Create a regular functional tester instance, no redirections
++                default_product_url = tester.default_product_url
++                tester = BloodhoundFunctionalTester(tester.url, skiplogin=True)
++                tester.default_product_url = default_product_url 
++            super(self.__class__, self).__init__(tester, 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):
++        with self.in_product(self):
+             return superobj._post_create_ticket()
+ 
+     def create_milestone(self, name=None, due=None):
+@@ -616,7 +646,7 @@
+         ... executed in default product context 
+         """
+         superobj = super(BloodhoundGlobalEnvFunctionalTester, self)
+-        with self.in_defaut_product(self):
++        with self.in_product(self):
+             return superobj.create_milestone(name, due)
+ 
+     def create_component(self, name=None, user=None):
+@@ -626,7 +656,7 @@
+         ... executed in default product context 
+         """
+         superobj = super(BloodhoundGlobalEnvFunctionalTester, self)
+-        with self.in_defaut_product(self):
++        with self.in_product(self):
+             return superobj.create_component(name, user)
+ 
+     def create_enum(self, kind, name=None):
+@@ -638,7 +668,7 @@
+ 
+         """
+         superobj = super(BloodhoundGlobalEnvFunctionalTester, self)
+-        with self.in_defaut_product(self):
++        with self.in_product(self):
+             return superobj.create_enum(kind, name)
+ 
+     def create_version(self, name=None, releasetime=None):
+@@ -648,7 +678,7 @@
+         ... executed in default product context 
+         """
+         superobj = super(BloodhoundGlobalEnvFunctionalTester, self)
+-        with self.in_defaut_product(self):
++        with self.in_product(self):
+             return superobj.create_version(name, releasetime)
+ 
+ #----------------
+@@ -711,8 +741,8 @@
+             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):
++                    hasattr(self._tester, 'in_product'):
++                with self._tester.in_product(self._tester):
+                     test(result)
+             else:
+                 test(result)
+@@ -723,6 +753,22 @@
+         print "\nStopping web server...\n"
+         functional.FunctionalTestSuite.tearDown(self)
+ 
++class MultiproductFunctionalTestCase(object):
++    """Mixin extending functional test case setup classes with multi-product
++    test methods.
++    """
++    def in_product(self, prefix=None):
++        """Switch the functional tester to work in product context.
++
++        :param prefix:  target product prefix
++        :return:        context manager object
++        """
++        if prefix is None:
++            return self._tester.in_product(self._tester)
++        else:
++            product_href = self._testenv.get_env_href(prefix=prefix)
++            return self._tester.in_product(self._tester, product_href())
++
+ # Mark some test cases to be run against default product
+ import trac.ticket.tests.functional
+ import trac.admin.tests.functional
+diff -r e6080cbd5c66 bloodhound_multiproduct/tests/functional/prefs.py
+--- a/bloodhound_multiproduct/tests/functional/prefs.py	Fri Aug 16 15:07:02 2013 -0500
++++ b/bloodhound_multiproduct/tests/functional/prefs.py	Fri Aug 16 18:13:42 2013 -0500
+@@ -81,14 +81,28 @@
+         tc.submit()
+         tc.notfind('name="accesskeys".*checked="checked"')
+ 
++def trac_functionalSuite(suite=None):
++    suite.addTest(TestPreferences())
++    suite.addTest(RegressionTestRev5785())
++    suite.addTest(RegressionTestTicket5765())
++
++
++#--------------
++# Multiproduct test cases
++#--------------
++
++
++
+ def functionalSuite(suite=None):
+     if not suite:
+         import tests.functional
+         suite = tests.functional.functionalSuite()
+ 
+-    suite.addTest(TestPreferences())
+-    suite.addTest(RegressionTestRev5785())
+-    suite.addTest(RegressionTestTicket5765())
++    trac_functionalSuite(suite)
++
++    return suite
++
+ 
+ if __name__ == '__main__':
++    import unittest
+     unittest.main(defaultTest='functionalSuite')
+diff -r e6080cbd5c66 bloodhound_multiproduct/tests/functional/ticket.py
+--- a/bloodhound_multiproduct/tests/functional/ticket.py	Fri Aug 16 15:07:02 2013 -0500
++++ b/bloodhound_multiproduct/tests/functional/ticket.py	Fri Aug 16 18:13:42 2013 -0500
+@@ -741,12 +741,9 @@
+         # Verify that their order has changed.
+         tc.find(name + '2.*' + name + '1', 's')
+ 
++
+ # 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()
+-
++def trac_functionalSuite(suite=None):
+     suite.addTest(TestTickets())
+ 
+     # [BLOODHOUND] there's no such thing like ticket preview
+@@ -851,5 +848,23 @@
+     return suite
+ 
+ 
++#--------------
++# Multiproduct test cases
++#--------------
++
++
++
++def functionalSuite(suite=None):
++    if not suite:
++        from tests import functional
++        suite = functional.functionalSuite()
++
++    trac_functionalSuite(suite)
++
++    return suite
++
++
+ if __name__ == '__main__':
++    import unittest
+     unittest.main(defaultTest='functionalSuite')
++

t387/t387_r1514860_functional_test.5.diff

+# HG changeset patch
+# Parent 148450bd9058828f60fa6032a655ceef32978734
+BH Multiproduct #387 : Bloodhound functional test cases. Shared instance state
+
+diff -r 148450bd9058 bloodhound_multiproduct/tests/functional/__init__.py
+--- a/bloodhound_multiproduct/tests/functional/__init__.py	Fri Aug 16 19:31:36 2013 -0500
++++ b/bloodhound_multiproduct/tests/functional/__init__.py	Fri Aug 16 22:19:39 2013 -0500
+@@ -352,20 +352,31 @@
+     As a consequence some methods of Trac functional tester have to be updated.
+     """
+ 
+-    def __init__(self, url, skiplogin=False):
++    def __init__(self, url, skiplogin=False, instance_state=None):
+         """Create a :class:`BloodhoundFunctionalTester` for the given 
+         environment URL and Subversion URL
+         
+         :param skiplogin:   Skip admin user login
+         """
+         self.url = url
+-        self.ticketcount = 0
++        self._state = instance_state or dict(ticketcount=0)
+ 
+         # Connect, and login so we can run tests.
+         self.go_to_front()
+         if not skiplogin:
+             self.login('admin')
+ 
++    @property
++    def ticketcount(self):
++        """Retrieve ticket count from shared instance state.
++        Ticket ID sequence is global.
++        """
++        return self._state.get('ticketcount', 0)
++
++    @ticketcount.setter
++    def ticketcount(self, value):
++        self._state['ticketcount'] = value
++
+     def login(self, username):
+         """Login as the given user
+ 
+@@ -653,7 +664,8 @@
+                     isinstance(tester, BloodhoundGlobalEnvFunctionalTester):
+                 # Create a regular functional tester instance, no redirections
+                 default_product_url = tester.default_product_url
+-                tester = BloodhoundFunctionalTester(tester.url, skiplogin=True)
++                tester = BloodhoundFunctionalTester(tester.url, True,
++                                                    tester._state)
+                 tester.default_product_url = default_product_url 
+             super(self.__class__, self).__init__(tester, url)
+ 

t387/t387_r1515319_functional_test.6.diff

+# HG changeset patch
+# Parent 4fe97d0f13df52474be96111b1cc426c56b12b17
+BH Multiproduct #387 : Refactor code working towards more flexible and reusable classes. Minor bugs fixed
+
+diff -r 4fe97d0f13df bloodhound_multiproduct/tests/functional/__init__.py
+--- a/bloodhound_multiproduct/tests/functional/__init__.py	Tue Sep 03 13:50:55 2013 -0500
++++ b/bloodhound_multiproduct/tests/functional/__init__.py	Tue Sep 03 13:51:35 2013 -0500
+@@ -17,6 +17,8 @@
+ #  specific language governing permissions and limitations
+ #  under the License.
+ 
++import abc
++import contextlib
+ import imp
+ from inspect import isclass
+ import os
+@@ -35,15 +37,18 @@
+ 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.env import ProductEnvironment
+ from multiproduct import hooks
+-
++from multiproduct.product_admin import ProductAdminModule
+ from tests import unittest
+ 
+ #----------------
+ # Product-aware classes for functional tests
+ #----------------
+ 
++# TODO: Virtual ABCs for isinstance() checks
+ class MultiproductFunctionalMixin(object):
+     """Mixin class applying multi-product upgrade path upon a given
+     functional Trac test environment. Access to the global environment
+@@ -57,6 +62,7 @@
+     def init(self):
+         """Determine the location of Trac source code
+         """
++        self.bh_install_project = 'trac'
+         self.bhmp_upgrade = False
+         self.trac_src = os.path.realpath(os.path.join( 
+                 __import__('trac', []).__file__, '..' , '..'))
+@@ -114,7 +120,7 @@
+         """Default implementation just returning href object for global
+         environment and failing if product prefix is specified.
+         """
+-        if envname not in ('trac', None):
++        if envname not in (self.bh_install_project, None):
+             raise LookupError('Unknown environment ' + repr(envname))
+         if prefix is not None:
+             self._fail_no_mp_setup()
+@@ -156,6 +162,11 @@
+         if do_wait: # Delay to ensure command executes and caches resets
+             time.sleep(5)
+ 
++    def _tracd_options(self):
++        """List options to run tracd server started for the test run.
++        """
++        return ["--port=%s" % self.port, "-s", "--hostname=127.0.0.1"]
++
+     def start(self):
+         """Starts the webserver, and waits for it to come up.
+         
+@@ -169,9 +180,11 @@
+                 args = [exe]
+         else:
+             args = [sys.executable]
+-        options = ["--port=%s" % self.port, "-s", "--hostname=127.0.0.1"]
++        options = self._tracd_options()
+         if 'TRAC_TEST_TRACD_OPTIONS' in os.environ:
+             options += os.environ['TRAC_TEST_TRACD_OPTIONS'].split()
++        self.get_trac_environment().log.debug('Starting tracd with args ' +
++                                              ' '.join(options))
+         args.append(os.path.join(self.trac_src, 'trac', 'web',
+                                  'standalone.py'))
+         server = Popen(args + options + [self.tracdir],
+@@ -205,16 +218,11 @@
+         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
++    def product_testenv(self, product_id):
++        return FunctionalProductEnvironment(self, product_id)
+ 
+-        @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 product_environment(self, product_id):
++        return ProductEnvironment(self.get_trac_environment(), product_id)
+ 
+     def configure_web_hooks(self):
+         """Setup web bootstrap_handlers and generation of product and global
+@@ -238,6 +246,8 @@
+         global environment.
+         """
+         def _default_base_href(user=None, prefix=None, envname=None):
++            if envname not in (self.bh_install_project, None):
++                raise LookupError('Unknown environment ' + repr(envname))
+             # TODO: Does not generate /login ? Should it ?
+             parts = urllib2.urlparse.urlsplit(self.url)
+             if not user or user == 'anonymous':
+@@ -252,6 +262,17 @@
+ 
+     # Protected methods
+ 
++    @property
++    def _bloodhound_install_args(self):
++        """Determine arguments supplied in to Bloodhound installer.
++        """
++        return dict(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)
++
+     def _bloodhound_install(self):
+         """Execute Bloodhound installer script
+         """
+@@ -267,29 +288,22 @@
+                                       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',
++            bhsetup = bhsetup.BloodhoundSetup({'project' : self.bh_install_project,
+                                                '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)
+-
++            bh_install_args = self._bloodhound_install_args
++            bhsetup.setup(**bh_install_args)
++        except:
++            raise
++        else:
++            self.bhmp_upgrade = True
+         finally:
+             os.chdir(cwd)
+ 
+@@ -307,6 +321,76 @@
+         return MultiProductSystem(env).default_product_prefix
+ 
+ 
++# TODO: Virtual ABCs for isinstance() checks
++# TODO: Assess implications of forwarding methods to global test env
++class FunctionalProductEnvironment(object):
++    """Functional test environment limiting interactions to product context
++    """
++    def __init__(self, testenv, product_id):
++        """Initialize functional product environment
++
++        @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
++        """
++        self.parent = testenv
++        self.prefix = product_id
++        self.url = self.parent.get_env_href(prefix=product_id)
++        ProductEnvironment(testenv.get_trac_environment(), self.prefix)
++
++    def _tracadmin(self, *args, **kwargs): 
++        """Execute trac-admin command in target product context by default
++        """
++        product_id = kwargs.get('product')
++        if product_id is None:
++            kwargs['product'] = self.prefix
++        return self.parent._tracadmin(*args, **kwargs)
++
++    def get_trac_environment(self):
++        return ProductEnvironment(self.parent.get_trac_environment(),
++                                  self.prefix)
++
++    def create(self):
++        raise RuntimeError('Bloodhound test environment already created')
++
++    def _bloodhound_install(self):
++        raise RuntimeError('Bloodhound test environment already created')
++
++    def __getattr__(self, attrnm):
++        try:
++            if attrnm == 'parent':
++                raise AttributeError
++            return getattr(self.parent, attrnm)
++        except AttributeError:
++            raise AttributeError("'%s' object has no attribute '%s'" % 
++                                 (self.__class__.__name__, attrnm))
++
++
++# TODO: Virtual ABCs for isinstance() checks
++class BasicAuthTestEnvironment(object):
++    """Setup tracd for HTTP basic authentication.
++    """
++    def _tracd_options(self):
++        options = super(BasicAuthTestEnvironment, self)._tracd_options()
++        options.append("--basic-auth=%s,%s," % (self.bh_install_project,
++                                                self.htpasswd))
++        return options
++
++
++# TODO: Virtual ABCs for isinstance() checks
++class DigestAuthTestEnvironment(object):
++    """Setup tracd for HTTP digest authentication.
++    """
++    def _tracd_options(self):
++        options = super(DigestAuthTestEnvironment, self)._tracd_options()
++        options.append("--auth=%s,%s,%s" % (self.bh_install_project,
++                                            self.htdigest,
++                                            self.htdigest_realm))
++        return options
++
++
+ class BloodhoundFunctionalTester(FunctionalTester):
+     """Leverages Trac library of higher-level operations for interacting with
+     a fully featured Apache(TM) Bloodhound test environment.
+@@ -652,9 +736,10 @@
+     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
++    def __init__(self, url, *args, **kwargs):
++        super(BloodhoundGlobalEnvFunctionalTester,
++              self).__init__(url, *args, **kwargs)
++        self.default_product_url = None
+ 
+     class in_product(BloodhoundFunctionalTester.in_product):
+         """Context manager temporarily switching to product URL
+@@ -719,6 +804,38 @@
+         with self.in_product(self):
+             return superobj.create_version(name, releasetime)
+ 
++
++class OpenerDirectorMixin(object):
++    """URL opener extensions for functional testers.
++    """
++    def build_opener(self, url, user, passwd=None):
++        """Build an urllib2 OpenerDirector configured to access the web
++        instance on behalf of a given user
++        """
++        return urllib2.build_opener()
++
++
++class HttpAuthTester(OpenerDirectorMixin):
++    """Configure HTTP authentication (basic or digest, proxy, ...)
++    """
++    __metaclass__ = abc.ABCMeta
++
++    @abc.abstractmethod
++    def url_auth_handlers(self, password_mgr):
++        """Return a (list of) instance(s) of urllib2.AbstractBasicAuthHandler,
++        urllib2.AbstractDigestAuthHandler or equivalent.
++        """
++
++    def build_opener(self, url, user, passwd=None):
++        password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
++        handlers = self.url_auth_handlers(password_mgr)
++        if not isinstance(handlers, (tuple, list)):
++            handlers = (handlers,)
++        password_mgr.add_password(realm=None, uri=url, user=user,
++                                  passwd=passwd or user)
++        return urllib2.build_opener(*handlers)
++
++
+ #----------------
+ # Twill's find command accepts regexes; some convenient but complex regexes
+ # & regex factories are provided here :
+@@ -744,10 +861,26 @@
+ 
+     tester_class = BloodhoundGlobalEnvFunctionalTester
+ 
++    def testenv_path(self, port=None):
++        if port is None:
++            dirname = "testenv"
++        else:
++            dirname = "testenv%s" % port
++        return os.path.join(functional.trac_source_tree, dirname)
++
+     def setUp(self, port=None):
+         print "Starting web server ..."
+         try:
+-            functional.FunctionalTestSuite.setUp(self)
++            # Rewrite FunctionalTestSuite.setUp for custom dirname
++            dirname = self.testenv_path(port)
++            if port is None:
++                port = 8000 + os.getpid() % 1000
++
++            baseurl = "http://127.0.0.1:%s" % port
++            self._testenv = self.env_class(dirname, port, baseurl)
++            self._testenv.start()
++            self._tester = self.tester_class(baseurl)
++            self.fixture = (self._testenv, self._tester)
+         except:
+             # Ensure tracd process is killed on failure
+             print "Stopping web server...\n"
+@@ -770,6 +903,7 @@
+         order access default product URL namespace instead of global.
+         """
+         self.setUp()
++        # FIXME: Loop once over test cases
+         if hasattr(self, 'fixture'):
+             for test in self._tests:
+                 if hasattr(test, 'setFixture'):
+@@ -778,10 +912,20 @@
+         for test in self._tests:
+             if result.shouldStop:
+                 break
+-            if getattr(test, 'BH_IN_DEFAULT_PRODUCT', False) and \
+-                    hasattr(self._tester, 'in_product'):
+-                with self._tester.in_product(self._tester):
+-                    test(result)
++            if getattr(test, 'BH_IN_DEFAULT_PRODUCT', False):
++                if hasattr(test, 'in_product'):
++                    with test.in_product():
++                        test(result)
++                elif hasattr(self._tester, 'in_product'):
++                    with self._tester.in_product(self._tester):
++                        test(result)
++                else:
++                    try:
++                        raise RuntimeError('Impossible to run test %s in '
++                                           'default product' % (test,))
++                    except:
++                        err = sys.exc_info()
++                    result.addError(test, err)
+             else:
+                 test(result)
+         self.tearDown()
+@@ -801,11 +945,27 @@
+         :param prefix:  target product prefix
+         :return:        context manager object
+         """
++        # Force setting tester and test environment
++        functional.FunctionalTestCaseSetup.setUp(self)
++
++        @contextlib.contextmanager
++        def in_product_testenv(product_id):
++            try:
++                # Backup active test env
++                original = self._testenv
++                self._testenv = original.product_testenv(product_id)
++                yield self._testenv
++            finally:
++                self._testenv = original
++
+         if prefix is None:
+-            return self._tester.in_product(self._tester)
++            default_product = self._testenv._default_product()
++            return contextlib.nested(in_product_testenv(default_product),
++                                     self._tester.in_product(self._tester))
+         else:
+             product_href = self._testenv.get_env_href(prefix=prefix)
+-            return self._tester.in_product(self._tester, product_href())
++            return contextlib.nested(in_product_testenv(prefix),
++                          self._tester.in_product(self._tester, product_href()))
+ 
+ # Mark some test cases to be run against default product
+ import trac.ticket.tests.functional

t387/t387_r1515319_functional_test.7.diff

+# HG changeset patch
+# Parent bce73ec990901bb2d3e3618659a463c4e74f6e36
+BH Multiproduct #387 : Remove abc module dependency
+
+diff --git a/bloodhound_multiproduct/tests/functional/__init__.py b/bloodhound_multiproduct/tests/functional/__init__.py
+--- a/bloodhound_multiproduct/tests/functional/__init__.py
++++ b/bloodhound_multiproduct/tests/functional/__init__.py
+@@ -17,7 +17,6 @@
+ #  specific language governing permissions and limitations
+ #  under the License.
+ 
+-import abc
+ import contextlib
+ import imp
+ from inspect import isclass
+@@ -818,13 +817,12 @@
+ class HttpAuthTester(OpenerDirectorMixin):
+     """Configure HTTP authentication (basic or digest, proxy, ...)
+     """
+-    __metaclass__ = abc.ABCMeta
+ 
+-    @abc.abstractmethod
+     def url_auth_handlers(self, password_mgr):
+         """Return a (list of) instance(s) of urllib2.AbstractBasicAuthHandler,
+         urllib2.AbstractDigestAuthHandler or equivalent.
+         """
++        raise NotImplementedError("Must override 'url_auth_handlers' method")
+ 
+     def build_opener(self, url, user, passwd=None):
+         password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()

t387/t387_r1524640_no_r5994.diff

+# HG changeset patch
+# Parent ad3d2432b6450da18e624f05dd92718f3b1d045b
+BH Multiproduct #387 : Mute test case for r5665 (taking too long)
+
+diff -r ad3d2432b645 bloodhound_multiproduct/tests/functional/ticket.py
+--- a/bloodhound_multiproduct/tests/functional/ticket.py	Thu Sep 19 17:56:30 2013 -0500
++++ b/bloodhound_multiproduct/tests/functional/ticket.py	Thu Sep 19 20:28:31 2013 -0500
+@@ -816,7 +816,7 @@
+     suite.addTest(TestNewReport())
+     suite.addTest(TestReportRealmDecoration())
+     suite.addTest(RegressionTestRev5665())
+-    suite.addTest(RegressionTestRev5994())
++#    suite.addTest(RegressionTestRev5994())
+ 
+     suite.addTest(RegressionTestTicket4447())
+     suite.addTest(RegressionTestTicket4630a())

t463/t463_r1513332_test_unicode_norm.diff

 # HG changeset patch
-# Parent b92698c1c90520682083375d8eb1c4ef67b5191a
+# Parent b2658b26542d7e2698f927d141e765e68cda8f24
 BH Multiproduct #463 : Test cases for unicode normalization
 
-diff -r b92698c1c905 bloodhound_multiproduct/tests/functional/__init__.py
---- a/bloodhound_multiproduct/tests/functional/__init__.py	Tue Aug 13 17:13:55 2013 -0500
-+++ b/bloodhound_multiproduct/tests/functional/__init__.py	Thu Aug 15 02:04:09 2013 -0500
-@@ -577,9 +577,15 @@
+diff -r b2658b26542d bloodhound_multiproduct/tests/functional/__init__.py
+--- a/bloodhound_multiproduct/tests/functional/__init__.py	Fri Sep 20 10:20:04 2013 -0500
++++ b/bloodhound_multiproduct/tests/functional/__init__.py	Fri Sep 20 10:24:31 2013 -0500
+@@ -708,9 +708,16 @@
          if desc:
              tc.formvalue('edit', 'description', desc)
          tc.submit()
 +        tc.go(base_url())
 +        tc.url(base_url())
 +        tc.notfind(functional.internal_error)
- 
- class BloodhoundGlobalEnvFunctionalTester(BloodhoundFunctionalTester):
-     """Library of higher-level operations for interacting with
-diff -r b92698c1c905 bloodhound_multiproduct/tests/functional/product.py
---- a/bloodhound_multiproduct/tests/functional/product.py	Tue Aug 13 17:13:55 2013 -0500
-+++ b/bloodhound_multiproduct/tests/functional/product.py	Thu Aug 15 02:04:09 2013 -0500
++
+     def go_to_dashboard(self):
+         """Surf to the dashboard page."""
+         self.go_to_front()
+diff -r b2658b26542d bloodhound_multiproduct/tests/functional/product.py
+--- a/bloodhound_multiproduct/tests/functional/product.py	Fri Sep 20 10:20:04 2013 -0500
++++ b/bloodhound_multiproduct/tests/functional/product.py	Fri Sep 20 10:24:31 2013 -0500
 @@ -20,9 +20,14 @@
  from trac.tests import contentgen
  from trac.tests import functional
      return suite
  
  if __name__ == '__main__':
-diff -r b92698c1c905 bloodhound_multiproduct/tests/functional/util.py
+diff -r b2658b26542d bloodhound_multiproduct/tests/functional/util.py
 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-+++ b/bloodhound_multiproduct/tests/functional/util.py	Thu Aug 15 02:04:09 2013 -0500
++++ b/bloodhound_multiproduct/tests/functional/util.py	Fri Sep 20 10:24:31 2013 -0500
 @@ -0,0 +1,110 @@
 +# -*- coding: utf-8 -*-
 +#

t463/t463_r1513332_unicode_product_prefix.diff

 # HG changeset patch
-# Parent 39527b8d5e5ce3b9b31e9ab607ce4cc3b598566e
+# Parent a58e4d68bed9457b9f9511a2e21d955837324390
 BH Multiproduct #463 : Support for unicode product prefix
 
-diff -r 39527b8d5e5c bloodhound_multiproduct/multiproduct/hooks.py
---- a/bloodhound_multiproduct/multiproduct/hooks.py	Thu Aug 15 00:28:07 2013 -0500
-+++ b/bloodhound_multiproduct/multiproduct/hooks.py	Thu Aug 15 00:31:49 2013 -0500
+diff -r a58e4d68bed9 bloodhound_multiproduct/multiproduct/hooks.py
+--- a/bloodhound_multiproduct/multiproduct/hooks.py	Fri Sep 20 10:24:31 2013 -0500
++++ b/bloodhound_multiproduct/multiproduct/hooks.py	Fri Sep 20 10:24:56 2013 -0500
 @@ -28,6 +28,7 @@
  from trac.core import TracError
  from trac.hooks import EnvironmentFactoryBase, RequestFactoryBase
  from trac.web.href import Href
  from trac.web.main import RequestWithSession
  
-@@ -70,8 +71,9 @@
+@@ -65,8 +66,9 @@
              return env
  
          if pid:
 +                                     environ['SCRIPT_NAME'] + u'/products/' +
                                       pid,
                                       m.group('pathinfo') or '')
-         else:
-diff -r 39527b8d5e5c bloodhound_multiproduct/multiproduct/web_ui.py
---- a/bloodhound_multiproduct/multiproduct/web_ui.py	Thu Aug 15 00:28:07 2013 -0500
-+++ b/bloodhound_multiproduct/multiproduct/web_ui.py	Thu Aug 15 00:31:49 2013 -0500
-@@ -21,8 +21,13 @@
- Provides request filtering to capture product related paths
- """
+ 
+diff -r a58e4d68bed9 bloodhound_multiproduct/multiproduct/web_ui.py
+--- a/bloodhound_multiproduct/multiproduct/web_ui.py	Fri Sep 20 10:24:31 2013 -0500
++++ b/bloodhound_multiproduct/multiproduct/web_ui.py	Fri Sep 20 10:24:56 2013 -0500
+@@ -23,8 +23,13 @@
+ 
+ import re
  
 +import unicodedata
 +
  from trac.resource import Neighborhood, Resource, ResourceNotFound
 +from trac.util.text import to_unicode
  from trac.util.translation import _
- from trac.web.api import HTTPNotFound, IRequestHandler
+ from trac.web.api import HTTPNotFound, IRequestHandler, IRequestFilter
  from trac.web.chrome import (
-@@ -47,12 +52,15 @@
+@@ -67,12 +72,15 @@
              req.args['pathinfo'] = m.group('pathinfo')
          return not m is None
  
              req.perm('product', pid).require('PRODUCT_VIEW')
  
          try:
-@@ -126,6 +134,8 @@
+@@ -148,6 +156,8 @@
          
          name = req.args.get('name')
          prefix = req.args.get('prefix')
          description = req.args.get('description', '')
          
          owner = req.args.get('owner') or req.authname
-@@ -158,8 +168,9 @@
+@@ -180,8 +190,9 @@
              if not prefix:
                  warn(_('You must provide a prefix for the product.'))
              elif Product.select(self.env, where={'prefix': prefix}):
              if not name:
                  warn(_('You must provide a name for the product.'))
              elif Product.select(self.env, where={'name': name}):
-@@ -171,8 +182,9 @@
+@@ -193,8 +204,9 @@
                  prod.update_field_dict(keys)
                  prod.update_field_dict(field_data)
                  prod.insert()

t598/t598_r1510975_functional_tester_product.diff

 # HG changeset patch
-# Parent f2c3297f145e9b1c042502303b03091530cacc9e
+# Parent 06e5b23b7c35f6b16205e20509e1d0892a040eee
 BH Multiproduct #598 : Product-specific extensions in functional tester
 
-diff -r f2c3297f145e bloodhound_multiproduct/multiproduct/templates/product_list.html
---- a/bloodhound_multiproduct/multiproduct/templates/product_list.html	Tue Aug 06 18:34:55 2013 -0500
-+++ b/bloodhound_multiproduct/multiproduct/templates/product_list.html	Tue Aug 06 20:35:01 2013 -0500
+diff -r 06e5b23b7c35 bloodhound_multiproduct/multiproduct/templates/product_list.html
+--- a/bloodhound_multiproduct/multiproduct/templates/product_list.html	Fri Aug 16 17:50:15 2013 -0500
++++ b/bloodhound_multiproduct/multiproduct/templates/product_list.html	Fri Aug 16 17:50:57 2013 -0500
 @@ -47,7 +47,7 @@
        </div>
  
            <input type="hidden" name="action" value="new" />
            <input class="btn" type="submit" value="${_('Add new product')}" />
          </form>
-diff -r f2c3297f145e bloodhound_multiproduct/tests/functional/__init__.py
---- a/bloodhound_multiproduct/tests/functional/__init__.py	Tue Aug 06 18:34:55 2013 -0500
-+++ b/bloodhound_multiproduct/tests/functional/__init__.py	Tue Aug 06 20:35:01 2013 -0500
+diff -r 06e5b23b7c35 bloodhound_multiproduct/tests/functional/__init__.py
+--- a/bloodhound_multiproduct/tests/functional/__init__.py	Fri Aug 16 17:50:15 2013 -0500
++++ b/bloodhound_multiproduct/tests/functional/__init__.py	Fri Aug 16 17:50:57 2013 -0500
 @@ -26,8 +26,8 @@
  import urllib
  import urllib2
  from trac.tests import functional
  from trac.tests.functional.svntestenv import SvnFunctionalTestEnvironment
  from trac.tests.functional.testenv import FunctionalTestEnvironment, ConnectError
-@@ -560,6 +560,26 @@
-     def find_query_column_selector(self, fieldname, fieldlbl):
-         tc.find(self.regex_query_column_selector(fieldname, fieldlbl), 's')
+@@ -582,6 +582,32 @@
+             """
+             self.tester.url = self.prev_url 
  
 +    def create_product(self, prefix=None, name=None, desc=None):
 +        products_url = self.url + "/products"
 +        tc.find('The product "%s" has been added' % (prefix,))
 +        return prefix
 +
++    def go_to_dashboard(self):
++        """Surf to the dashboard page."""
++        self.go_to_front()
++        tc.follow('Tickets')
++        tc.url(self.url + '/dashboard')
++
  
  class BloodhoundGlobalEnvFunctionalTester(BloodhoundFunctionalTester):
      """Library of higher-level operations for interacting with
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.