Author: rjollos
Date: Thu Nov 14 21:18:19 2013
New Revision: 1542087
URL: http://svn.apache.org/r1542087
Log:
0.8dev: Rewrite Trac's functional test cases against multiproduct environment.
Refs #387.
Patches by Olemis Lang.
Modified:
bloodhound/trunk/bloodhound_multiproduct/multiproduct/templates/product_list.html
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/bloodhound_multiproduct/multiproduct/templates/product_list.html
URL:
http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_multiproduct/multiproduct/templates/product_list.html?rev=1542087&r1=1542086&r2=1542087&view=diff
==============================================================================
---
bloodhound/trunk/bloodhound_multiproduct/multiproduct/templates/product_list.html
(original)
+++
bloodhound/trunk/bloodhound_multiproduct/multiproduct/templates/product_list.html
Thu Nov 14 21:18:19 2013
@@ -47,7 +47,7 @@
</div>
<div py:if="'PRODUCT_CREATE' in perm" class="btn-group span8">
- <form method="get" action="${href.products()}">
+ <form name="new" method="get" action="${href.products()}">
<input type="hidden" name="action" value="new" />
<input class="btn" type="submit" value="${_('Add new product')}" />
</form>
Modified: bloodhound/trunk/bloodhound_multiproduct/tests/functional/__init__.py
URL:
http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_multiproduct/tests/functional/__init__.py?rev=1542087&r1=1542086&r2=1542087&view=diff
==============================================================================
--- bloodhound/trunk/bloodhound_multiproduct/tests/functional/__init__.py
(original)
+++ bloodhound/trunk/bloodhound_multiproduct/tests/functional/__init__.py Thu
Nov 14 21:18:19 2013
@@ -17,6 +17,7 @@
# specific language governing permissions and limitations
# under the License.
+import contextlib
import imp
from inspect import isclass
import os
@@ -26,8 +27,8 @@ import time
import urllib
import urllib2
-from trac.tests.contentgen import random_sentence, random_page,\
- random_unique_camel
+from trac.tests.contentgen import random_page, random_sentence, \
+ random_unique_camel, random_word
from trac.tests import functional
from trac.tests.functional.svntestenv import SvnFunctionalTestEnvironment
from trac.tests.functional.testenv import FunctionalTestEnvironment,
ConnectError
@@ -35,15 +36,24 @@ from trac.tests.functional.tester import
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
#----------------
+# Constants
+#----------------
+
+from multiproduct.dbcursor import GLOBAL_PRODUCT as GLOBAL_ENV
+
+#----------------
# 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
@@ -54,9 +64,14 @@ class MultiproductFunctionalMixin(object
class declaration because it overrides some methods
"""
+ @property
+ def parent(self):
+ return None
+
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 +129,7 @@ class MultiproductFunctionalMixin(object
"""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()
@@ -145,7 +160,7 @@ class MultiproductFunctionalMixin(object
"""
do_wait = kwargs.pop('wait', False)
product_id = kwargs.pop('product', None)
- if product_id:
+ if product_id is not None and product_id != GLOBAL_ENV:
if self.bhmp_upgrade and \
args[0] not in ProductAdminModule.GLOBAL_COMMANDS:
args = ('product', 'admin', product_id) + args
@@ -156,6 +171,11 @@ class MultiproductFunctionalMixin(object
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 +189,11 @@ class MultiproductFunctionalMixin(object
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 +227,14 @@ class MultiproductFunctionalMixin(object
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):
+ if product_id == GLOBAL_ENV:
+ return self.parent or self
+ else:
+ 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 +258,8 @@ class MultiproductFunctionalMixin(object
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':
@@ -245,13 +267,25 @@ class MultiproductFunctionalMixin(object
else:
global_href = Href('%s://%s:%s@%s/' %
(parts[0], user, user, parts[1]))
- return global_href if not prefix \
+ # FIXME : Check that prefix is None is correct
+ return global_href if (prefix is None or prefix == GLOBAL_ENV) \
else Href(global_href('products', prefix))
return _default_base_href
# 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 +301,22 @@ class MultiproductFunctionalMixin(object
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 +334,76 @@ class MultiproductFunctionalMixin(object
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.
@@ -333,11 +430,50 @@ class BloodhoundFunctionalTester(Functio
- 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'
+ - 'Create New Ticket' label in new ticket page replaced by 'New Ticket'
+ - Ticket owner label changed from 'Owned by' to 'Assigned to'
+ - Source files (*.py) files copied in /plugins folder not enabled ootb
+ - Twitter Bootstrap class="input-mini" added in 'Max items per page'
+ input control in query view.
+ - Ticket comment header changed
+ - 'Page PageName created' is not shown anymore for new wiki page
+ - Ticket workflow <select /> does not end with `id` attribute
+ - Ticket events in timeline are different i.e. 'by user' outside <a />
+ - Description 'modified' label in ticket comments feed inside <span />
+ - closed: labels in milestone progress reports not shown anymore
+ - active: labels in milestone progress reports not shown anymore
+ - In ticket comments reply form 'Submit changes' => 'Submit'
+ - No preview button for ticket (comments) in BH theme
+ - class="input-mini" appended to priorities admin <select />
As a consequence some methods of Trac functional tester have to be updated.
"""
+ 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._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
@@ -412,9 +548,9 @@ class BloodhoundFunctionalTester(Functio
"""
self.go_to_front()
# [BLOODHOUND] View Tickets renamed to Tickets pointing at dashboard
- tc.follow('Tickets')
+ tc.follow(r'\bTickets\b')
tc.notfind(internal_error)
- tc.follow('Reports')
+ tc.follow(r'\bReports\b')
tc.notfind(internal_error)
tc.formvalue('create_report', 'action', 'new') # select new report form
tc.submit()
@@ -473,6 +609,12 @@ class BloodhoundFunctionalTester(Functio
tc.follow('Custom Query')
tc.url(self.url + '/query')
+ def quickjump(self, search):
+ """Do a quick search to jump to a page."""
+ tc.formvalue('mainsearch', 'q', search)
+ tc.submit()
+ tc.notfind(internal_error)
+
# Bloodhound functional tester extensions
def go_to_newticket(self):
@@ -513,10 +655,108 @@ class BloodhoundFunctionalTester(Functio
return self.ticketcount
+ @staticmethod
+ def regex_ticket_field(fieldname, fieldval):
+ return r'<td [^>]*\bid="vc-%s"[^>]*>\s*%s\s*</td>' % (fieldname,
fieldval)
+
+ @staticmethod
+ def regex_owned_by(username):
+ return '(Assigned to(<[^>]*>|\\n| )*%s)' % username
+
+ @staticmethod
+ def regex_query_column_selector(fieldname, fieldlbl):
+ return r'<label>( |\n)*<input[^<]*value="%s"[^<]*>' \
+ '( |\n)*<[^<]*>( |\n)*%s( |\n)*</[^<]*>' \
+ '(.|\n)*</label>' % (fieldname, fieldlbl)
+
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))
+ tc.find(self.regex_ticket_field(fieldname, fieldval))
+
+ def find_owned_by(self, username):
+ """Assert that a ticket is owned by a given user
+ """
+ tc.find(self.regex_owned_by(username))
+
+ def find_query_column_selector(self, fieldname, fieldlbl):
+ tc.find(self.regex_query_column_selector(fieldname, fieldlbl), 's')
+
+ def as_user(self, user, restore='admin'):
+ """Context manager to track access to the web site
+ as user and restore login afterwards (by default to admin)
+ """
+ @contextlib.contextmanager
+ def user_ctx():
+ try:
+ login_ok = False
+ try:
+ self.logout()
+ except:
+ pass
+ if user:
+ self.login(user)
+ login_ok = True
+ yield self
+ finally:
+ if login_ok:
+ try:
+ self.logout()
+ except:
+ pass
+ if restore:
+ self.login(restore)
+
+ return user_ctx()
+
+
+ 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
+
+ def create_product(self, prefix=None, name=None, desc=None):
+ products_url = self.url + "/products"
+ tc.go(products_url)
+ tc.find('Products')
+ # Touch new product form
+ tc.formvalue('new', 'action', 'new')
+ tc.submit('Add new product')
+ tc.find('New Product')
+
+ prefix = prefix or random_word()
+ name = prefix or random_sentence()
+
+ tc.formvalue('edit', 'prefix', prefix)
+ tc.formvalue('edit', 'name', name)
+ if desc:
+ tc.formvalue('edit', 'description', desc)
+ tc.submit()
+ 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):
@@ -536,35 +776,30 @@ class BloodhoundGlobalEnvFunctionalTeste
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 __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
+ """
+ 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, True,
+ tester._state)
+ 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):
@@ -574,7 +809,7 @@ class BloodhoundGlobalEnvFunctionalTeste
... 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):
@@ -584,7 +819,7 @@ class BloodhoundGlobalEnvFunctionalTeste
... 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):
@@ -596,7 +831,7 @@ class BloodhoundGlobalEnvFunctionalTeste
"""
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):
@@ -606,10 +841,48 @@ class BloodhoundGlobalEnvFunctionalTeste
... 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)
+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, ...)
+ """
+
+ 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()
+ 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 :
+#----------------
+
+regex_owned_by = BloodhoundFunctionalTester.regex_owned_by
+regex_ticket_field = BloodhoundFunctionalTester.regex_ticket_field
+
#----------------
# Product-aware functional setup
#----------------
@@ -627,10 +900,26 @@ class MultiproductFunctionalTestSuite(fu
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"
@@ -653,6 +942,7 @@ class MultiproductFunctionalTestSuite(fu
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'):
@@ -661,10 +951,20 @@ class MultiproductFunctionalTestSuite(fu
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)
+ 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()
@@ -674,16 +974,56 @@ class MultiproductFunctionalTestSuite(fu
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
+ """
+ # 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:
+ 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 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
import trac.admin.tests.functional
+from trac.tests.functional import testcases
+
+ignore_tc = (functional.FunctionalTwillTestCaseSetup,
+ functional.FunctionalTestCaseSetup)
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):
+ if isclass(attr) and attr not in ignore_tc \
+ and issubclass(attr, functional.FunctionalTestCaseSetup):
attr.BH_IN_DEFAULT_PRODUCT = True
-del attr, mdl
+del attr, mdl, ignore_tc
+
+testcases.RegressionTestTicket7209.BH_IN_DEFAULT_PRODUCT = True
+testcases.RegressionTestTicket9880.BH_IN_DEFAULT_PRODUCT = True
+
def trac_functionalSuite(suite):
from trac.tests.functional import testcases
@@ -697,8 +1037,14 @@ def trac_functionalSuite(suite):
suite.addTest(testcases.ErrorPageValidation())
suite.addTest(testcases.RegressionTestTicket3663())
+ import trac.admin.tests
+ trac.admin.tests.functionalSuite(suite)
import trac.versioncontrol.tests
trac.versioncontrol.tests.functionalSuite(suite)
+ import trac.wiki.tests
+ trac.wiki.tests.functionalSuite(suite)
+ import trac.timeline.tests
+ trac.timeline.tests.functionalSuite(suite)
# import trac.ticket.tests
# trac.ticket.tests.functionalSuite(suite)
@@ -710,12 +1056,6 @@ def trac_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)
Modified: bloodhound/trunk/bloodhound_multiproduct/tests/functional/prefs.py
URL:
http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_multiproduct/tests/functional/prefs.py?rev=1542087&r1=1542086&r2=1542087&view=diff
==============================================================================
--- bloodhound/trunk/bloodhound_multiproduct/tests/functional/prefs.py
(original)
+++ bloodhound/trunk/bloodhound_multiproduct/tests/functional/prefs.py Thu Nov
14 21:18:19 2013
@@ -81,14 +81,28 @@ class RegressionTestTicket5765(functiona
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')
Modified: bloodhound/trunk/bloodhound_multiproduct/tests/functional/ticket.py
URL:
http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_multiproduct/tests/functional/ticket.py?rev=1542087&r1=1542086&r2=1542087&view=diff
==============================================================================
--- bloodhound/trunk/bloodhound_multiproduct/tests/functional/ticket.py
(original)
+++ bloodhound/trunk/bloodhound_multiproduct/tests/functional/ticket.py Thu Nov
14 21:18:19 2013
@@ -22,8 +22,13 @@
from urlparse import urlsplit
+from twill.errors import TwillException
+
from trac.ticket.tests.functional import *
+from tests import unittest
+from tests.functional import regex_owned_by
+
#----------------
# Functional test cases for tickets (rewritten)
#----------------
@@ -70,6 +75,8 @@ class TestTicketNoSummary(FunctionalTwil
class TestTicketCustomFieldTextNoFormat(FunctionalTwillTestCaseSetup):
+ BH_IN_DEFAULT_PRODUCT = True
+
def runTest(self):
"""Test custom text field with no format explicitly specified.
Its contents should be rendered as plain text.
@@ -92,6 +99,8 @@ class TestTicketCustomFieldTextNoFormat(
class TestTicketCustomFieldTextAreaNoFormat(FunctionalTwillTestCaseSetup):
+ BH_IN_DEFAULT_PRODUCT = True
+
def runTest(self):
"""Test custom textarea field with no format explicitly specified,
its contents should be rendered as plain text.
@@ -114,6 +123,8 @@ class TestTicketCustomFieldTextAreaNoFor
class TestTicketCustomFieldTextWikiFormat(FunctionalTwillTestCaseSetup):
+ BH_IN_DEFAULT_PRODUCT = True
+
def runTest(self):
"""Test custom text field with `wiki` format.
Its contents should through the wiki engine, wiki-links and all.
@@ -140,6 +151,8 @@ class TestTicketCustomFieldTextWikiForma
class TestTicketCustomFieldTextAreaWikiFormat(FunctionalTwillTestCaseSetup):
+ BH_IN_DEFAULT_PRODUCT = True
+
def runTest(self):
"""Test custom textarea field with no format explicitly specified,
its contents should be rendered as plain text.
@@ -301,12 +314,463 @@ class RegressionTestTicket10828(Function
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()
+class RegressionTestTicket5394a(FunctionalTwillTestCaseSetup):
+ BH_IN_DEFAULT_PRODUCT = True
+
+ def runTest(self):
+ """Test for regression of http://trac.edgewall.org/ticket/5394 a
+ Order user list alphabetically in (re)assign action
+ """
+ # set restrict_owner config
+ env = self._testenv.get_trac_environment()
+ env.config.set('ticket', 'restrict_owner', 'yes')
+ env.config.save()
+ self._testenv.restart()
+
+ self._tester.go_to_front()
+ self._tester.logout()
+
+ test_users = ['alice', 'bob', 'jane', 'john', 'charlie', 'alan',
+ 'zorro']
+ # Apprently it takes a sec for the new user to be recognized by the
+ # environment. So we add all the users, then log in as the users
+ # in a second loop. This should be faster than adding a sleep(1)
+ # between the .adduser and .login steps.
+ for user in test_users:
+ self._testenv.adduser(user)
+ for user in test_users:
+ self._tester.login(user)
+ self._tester.logout()
+
+ self._tester.login('admin')
+
+ ticketid = self._tester.create_ticket("regression test 5394a")
+ self._tester.go_to_ticket(ticketid)
+
+ # [BLOODHOUND] Workflow <select /> does not end with id attribute
+ options = 'id="action_reassign_reassign_owner"[^>]*>' + \
+ ''.join(['<option[^>]*>%s</option>' % user for user in
+ sorted(test_users + ['admin', 'user'])])
+ tc.find(options, 's')
+ # We don't have a good way to fully delete a user from the Trac db.
+ # Once we do, we may want to cleanup our list of users here.
+
+
+class RegressionTestTicket5394b(FunctionalTwillTestCaseSetup):
+ def runTest(self):
+ """Test for regression of http://trac.edgewall.org/ticket/5394 b
+ Order user list alphabetically on new ticket page
+ """
+ #FIXME : Test is missing a lot of context. See
RegressionTestTicket5394a
+
+ # Must run after RegressionTestTicket5394a
+ self._tester.go_to_front()
+
+ # [BLOODHOUND] New Ticket => More fields (in create ticket menu)
+ self._tester.go_to_newticket()
+ # [BLOODHOUND] Create New Ticket => New Ticket
+ tc.find('New Ticket')
+
+ test_users = ['alice', 'bob', 'jane', 'john', 'charlie', 'alan',
+ 'zorro']
+ options = 'id="field-owner"[^>]*>[[:space:]]*<option/>.*' + \
+ '.*'.join(['<option[^>]*>%s</option>' % user for user in
+ sorted(test_users + ['admin', 'user'])])
+ options = '.*'.join(sorted(test_users + ['admin', 'user']))
+ tc.find(options, 's')
+
+# FIXME: Verbatim copy of its peer just to override regex_owned_by
+class RegressionTestTicket5497a(FunctionalTwillTestCaseSetup):
+ BH_IN_DEFAULT_PRODUCT = True
+
+ def runTest(self):
+ """Test for regression of http://trac.edgewall.org/ticket/5497 a
+ Open ticket, component changed, owner not changed"""
+ ticketid = self._tester.create_ticket("regression test 5497a")
+ self._tester.go_to_ticket(ticketid)
+ tc.formvalue('propertyform', 'field-component', 'regression5497')
+ tc.submit('submit')
+ tc.find(regex_owned_by('user'))
+
+
+# FIXME: Verbatim copy of its peer just to override regex_owned_by
+class RegressionTestTicket5497b(FunctionalTwillTestCaseSetup):
+ BH_IN_DEFAULT_PRODUCT = True
+
+ def runTest(self):
+ """Test for regression of http://trac.edgewall.org/ticket/5497 b
+ Open ticket, component changed, owner changed"""
+ ticketid = self._tester.create_ticket("regression test 5497b")
+ self._tester.go_to_ticket(ticketid)
+ tc.formvalue('propertyform', 'field-component', 'regression5497')
+ tc.formvalue('propertyform', 'action', 'reassign')
+ tc.formvalue('propertyform', 'action_reassign_reassign_owner', 'admin')
+ tc.submit('submit')
+ tc.notfind(regex_owned_by('user'))
+ tc.find(regex_owned_by('admin'))
+
+
+# FIXME: Verbatim copy of its peer just to override regex_owned_by
+class RegressionTestTicket5497c(FunctionalTwillTestCaseSetup):
+ BH_IN_DEFAULT_PRODUCT = True
+
+ def runTest(self):
+ """Test for regression of http://trac.edgewall.org/ticket/5497 c
+ New ticket, component changed, owner not changed"""
+ ticketid = self._tester.create_ticket("regression test 5497c",
+ {'component':'regression5497'})
+ self._tester.go_to_ticket(ticketid)
+ tc.find(regex_owned_by('user'))
+
+
+# FIXME: Verbatim copy of its peer just to override regex_owned_by
+class RegressionTestTicket5497d(FunctionalTwillTestCaseSetup):
+ BH_IN_DEFAULT_PRODUCT = True
+
+ def runTest(self):
+ """Test for regression of http://trac.edgewall.org/ticket/5497 d
+ New ticket, component changed, owner changed"""
+ ticketid = self._tester.create_ticket("regression test 5497d",
+ {'component':'regression5497', 'owner':'admin'})
+ self._tester.go_to_ticket(ticketid)
+ tc.find(regex_owned_by('admin'))
+
+class RegressionTestRev5994(FunctionalTwillTestCaseSetup):
+ def runTest(self):
+ """Test for regression of the column label fix in r5994"""
+ env = self._testenv.get_trac_environment()
+ env.config.set('ticket-custom', 'custfield', 'text')
+ env.config.set('ticket-custom', 'custfield.label', 'Custom Field')
+ env.config.save()
+ try:
+ self._testenv.restart()
+ self._tester.go_to_query()
+ self._tester.find_query_column_selector('custfield', 'Custom
Field')
+ finally:
+ pass
+ #env.config.set('ticket', 'restrict_owner', 'no')
+ #env.config.save()
+ #self._testenv.restart()
+
+
+class RegressionTestTicket6048(FunctionalTwillTestCaseSetup):
+ BH_IN_DEFAULT_PRODUCT = True
+
+ def runTest(self):
+ """Test for regression of http://trac.edgewall.org/ticket/6048"""
+ # Setup the DeleteTicket plugin
+ plugin = open(os.path.join(self._testenv.command_cwd, 'sample-plugins',
+ 'workflow', 'DeleteTicket.py')).read()
+ open(os.path.join(self._testenv.tracdir, 'plugins', 'DeleteTicket.py'),
+ 'w').write(plugin)
+ env = self._testenv.get_trac_environment()
+
+ # [BLOODHOUND] Ensure plugin will be enabled in target scope
+ env.config.set('components', 'DeleteTicket.*', 'enabled')
+
+ prevconfig = env.config.get('ticket', 'workflow')
+ env.config.set('ticket', 'workflow',
+ prevconfig + ',DeleteTicketActionController')
+ env.config.save()
+ env = self._testenv.get_trac_environment() # reload environment
+
+ # Create a ticket and delete it
+ ticket_id = self._tester.create_ticket(
+ summary='RegressionTestTicket6048')
+ # (Create a second ticket so that the ticket id does not get reused
+ # and confuse the tester object.)
+ self._tester.create_ticket(summary='RegressionTestTicket6048b')
+ self._tester.go_to_ticket(ticket_id)
+ tc.find('delete ticket')
+ tc.formvalue('propertyform', 'action', 'delete')
+ tc.submit('submit')
+
+ self._tester.go_to_ticket(ticket_id)
+ tc.find('Error: Invalid ticket number')
+ tc.find('Ticket %s does not exist.' % ticket_id)
+
+ # Remove the DeleteTicket plugin
+ env.config.set('ticket', 'workflow', prevconfig)
+ env.config.save()
+ env = self._testenv.get_trac_environment() # reload environment
+ for ext in ('py', 'pyc', 'pyo'):
+ filename = os.path.join(self._testenv.tracdir, 'plugins',
+ 'DeleteTicket.%s' % ext)
+ if os.path.exists(filename):
+ os.unlink(filename)
+
+
+class RegressionTestTicket7821group(FunctionalTwillTestCaseSetup):
+ def runTest(self):
+ """Test for regression of http://trac.edgewall.org/ticket/7821 group"""
+ env = self._testenv.get_trac_environment()
+ saved_default_query = env.config.get('query', 'default_query')
+ default_query = 'status!=closed&order=status&group=status&max=42' \
+ '&desc=1&groupdesc=1&col=summary|status|cc' \
+ '&cc~=$USER'
+ env.config.set('query', 'default_query', default_query)
+ env.config.save()
+ try:
+ self._testenv.restart()
+ self._tester.create_ticket('RegressionTestTicket7821 group')
+ self._tester.go_to_query()
+ # $USER
+ tc.find('<input type="text" name="0_cc" value="admin"'
+ ' size="[0-9]+" />')
+ # col
+ tc.find('<input type="checkbox" name="col" value="summary"'
+ ' checked="checked" />')
+ tc.find('<input type="checkbox" name="col" value="owner" />')
+ tc.find('<input type="checkbox" name="col" value="status"'
+ ' checked="checked" />')
+ tc.find('<input type="checkbox" name="col" value="cc"'
+ ' checked="checked" />')
+ # group
+ tc.find('<option selected="selected" value="status">Status'
+ '</option>')
+ # groupdesc
+ tc.find('<input type="checkbox" name="groupdesc" id="groupdesc"'
+ ' checked="checked" />')
+ # max
+ # [BLOODHOUND] class="input-mini" added (Twitter Bootstrap)
+ tc.find('<input type="text" name="max" id="max" size="[0-9]*?"'
+ ' value="42" [^/]*/>')
+ # col in results
+ tc.find('<a title="Sort by Ticket [(]ascending[)]" ')
+ tc.find('<a title="Sort by Summary [(]ascending[)]" ')
+ tc.find('<a title="Sort by Status [(]ascending[)]" ')
+ tc.find('<a title="Sort by Cc [(]ascending[)]" ')
+ tc.notfind('<a title="Sort by Owner "')
+ finally:
+ env.config.set('query', 'default_query', saved_default_query)
+ env.config.save()
+ self._testenv.restart()
+
+
+class RegressionTestTicket8247(FunctionalTwillTestCaseSetup):
+ BH_IN_DEFAULT_PRODUCT = True
+ def runTest(self):
+ """Test for regression of http://trac.edgewall.org/ticket/8247
+ Author field of ticket comment corresponding to the milestone removal
+ was always 'anonymous'.
+ """
+ name = "MilestoneRemove"
+ self._tester.create_milestone(name)
+ id = self._tester.create_ticket(info={'milestone': name})
+ ticket_url = self._tester.url + "/ticket/%d" % id
+ tc.go(ticket_url)
+ tc.find(name)
+ tc.go(self._tester.url + "/admin/ticket/milestones")
+ tc.formvalue('milestone_table', 'sel', name)
+ tc.submit('remove')
+ tc.go(ticket_url)
+
+ # [BLOODHOUND] Ticket comment header changed
+ tc.find('<strong class="trac-field-milestone">Milestone</strong>'
+ '[ \n\t]*<span>[ \n\t]*<em>%s</em> deleted' % name)
+ tc.find('by admin<span>, <a.* ago</a></span>')
+
+ tc.notfind('anonymous')
+
+
+class TestTimelineTicketDetails(FunctionalTwillTestCaseSetup):
+ BH_IN_DEFAULT_PRODUCT = True
+
+ def runTest(self):
+ """Test ticket details on timeline"""
+ env = self._testenv.get_trac_environment()
+ env.config.set('timeline', 'ticket_show_details', 'yes')
+ env.config.save()
+ summary = random_sentence(5)
+ ticketid = self._tester.create_ticket(summary)
+ self._tester.go_to_ticket(ticketid)
+ self._tester.add_comment(ticketid)
+ self._tester.go_to_timeline()
+ tc.formvalue('prefs', 'ticket_details', True)
+ tc.submit()
+ htmltags = '(<[^>]*>)*'
+
+ # [BLOODHOUND] Ticket events are different i.e. 'by user' outside <a />
+ tc.find(htmltags + 'Ticket ' + htmltags + '#' + str(ticketid) +
+ htmltags + ' \\(' + summary + '\\) updated\\s*' +
+ htmltags + '\\s+by\\s+' + htmltags + 'admin', 's')
+
+
+class TestTicketHistoryDiff(FunctionalTwillTestCaseSetup):
+ BH_IN_DEFAULT_PRODUCT = True
+
+ def runTest(self):
+ """Test ticket history (diff)"""
+ name = 'TestTicketHistoryDiff'
+ ticketid = self._tester.create_ticket(name)
+ self._tester.go_to_ticket(ticketid)
+ tc.formvalue('propertyform', 'description', random_sentence(6))
+ tc.submit('submit')
+
+ # [BLOODHOUND] Description 'modified' in comments feed inside <span />
+ tc.find('Description<[^>]*>\\s*<[^>]*>\\s*modified \\(<[^>]*>diff',
's')
+ tc.follow('diff')
+ tc.find('Changes\\s*between\\s*<[^>]*>Initial Version<[^>]*>\\s*and' \
+ '\\s*<[^>]*>Version 1<[^>]*>\\s*of\\s*<[^>]*>Ticket #' , 's')
+
+
+class RegressionTestTicket5602(FunctionalTwillTestCaseSetup):
+ BH_IN_DEFAULT_PRODUCT = True
+
+ def runTest(self):
+ """Test for regression of http://trac.edgewall.org/ticket/5602"""
+ # Create a set of tickets, and assign them all to a milestone
+ milestone = self._tester.create_milestone()
+ ids = [self._tester.create_ticket() for x in range(5)]
+ [self._tester.ticket_set_milestone(x, milestone) for x in ids]
+ # Need a ticket in each state: new, assigned, accepted, closed,
+ # reopened
+ # leave ids[0] as new
+ # make ids[1] be assigned
+ self._tester.go_to_ticket(ids[1])
+ tc.formvalue('propertyform', 'action', 'reassign')
+ tc.formvalue('propertyform', 'action_reassign_reassign_owner', 'admin')
+ tc.submit('submit')
+ # make ids[2] be accepted
+ self._tester.go_to_ticket(ids[2])
+ tc.formvalue('propertyform', 'action', 'accept')
+ tc.submit('submit')
+ # make ids[3] be closed
+ self._tester.go_to_ticket(ids[3])
+ tc.formvalue('propertyform', 'action', 'resolve')
+ tc.formvalue('propertyform', 'action_resolve_resolve_resolution',
'fixed')
+ tc.submit('submit')
+ # make ids[4] be reopened
+ self._tester.go_to_ticket(ids[4])
+ tc.formvalue('propertyform', 'action', 'resolve')
+ tc.formvalue('propertyform', 'action_resolve_resolve_resolution',
'fixed')
+ tc.submit('submit')
+ # FIXME: we have to wait a second to avoid "IntegrityError: columns
+ # ticket, time, field are not unique"
+ time.sleep(1)
+ tc.formvalue('propertyform', 'action', 'reopen')
+ tc.submit('submit')
+ tc.show()
+ tc.notfind("Python Traceback")
+
+ # Go to the milestone and follow the links to the closed and active
+ # tickets.
+ tc.go(self._tester.url + "/roadmap")
+ tc.follow(milestone)
+
+ # [BLOODHOUND] closed: labels in milestone progress bar removed
+ tc.follow(r"/query\?.*status=closed&.*milestone=%s$" % (milestone,))
+ tc.find("Resolution:[ \t\n]+fixed")
+
+ tc.back()
+ # [BLOODHOUND] active: labels in milestone progress bar removed
+ tc.follow(r"/query\?.*status=new&.*milestone=%s$" % (milestone,))
+ tc.find("Status:[ \t\n]+new")
+ tc.find("Status:[ \t\n]+assigned")
+ tc.find("Status:[ \t\n]+accepted")
+ tc.notfind("Status:[ \t\n]+closed")
+ tc.find("Status:[ \t\n]+reopened")
+
+
+class RegressionTestTicket9084(FunctionalTwillTestCaseSetup):
+ BH_IN_DEFAULT_PRODUCT = True
+
+ def runTest(self):
+ """Test for regression of http://trac.edgewall.org/ticket/9084"""
+ ticketid = self._tester.create_ticket()
+ self._tester.add_comment(ticketid)
+ self._tester.go_to_ticket(ticketid)
+ tc.submit('2', formname='reply-to-comment-1') # '1' hidden, '2' submit
+ tc.formvalue('propertyform', 'comment', random_sentence(3))
+
+ # [BLOODHPUND] In ticket comments reply form 'Submit changes'=>'Submit'
+ tc.submit('Submit')
+ tc.notfind('AssertionError')
+
+
+class RegressionTestTicket6879a(FunctionalTwillTestCaseSetup,
+ unittest.TestCase):
+ BH_IN_DEFAULT_PRODUCT = True
+
+ def runTest(self):
+ """Test for regression of http://trac.edgewall.org/ticket/6879 a
+
+ Make sure that previewing a close does not make the available actions
+ be those for the close status.
+ """
+ # create a ticket, then preview resolving the ticket twice
+ ticket_id = self._tester.create_ticket("RegressionTestTicket6879 a")
+ self._tester.go_to_ticket(ticket_id)
+ tc.formvalue('propertyform', 'action', 'resolve')
+ tc.formvalue('propertyform', 'action_resolve_resolve_resolution',
'fixed')
+
+ # [BLOODHOUND] No preview button for ticket (comments) in BH theme
+ try:
+ tc.submit('preview')
+ except TwillException:
+ self.skipTest('Active theme without ticket preview')
+
+ tc.formvalue('propertyform', 'action', 'resolve')
+ tc.submit('preview')
+
+
+class RegressionTestTicket6879b(FunctionalTwillTestCaseSetup,
+ unittest.TestCase):
+ BH_IN_DEFAULT_PRODUCT = True
+
+ def runTest(self):
+ """Test for regression of http://trac.edgewall.org/ticket/6879 b
+
+ Make sure that previewing a close does not make the available actions
+ be those for the close status.
+ """
+ # create a ticket, then preview resolving the ticket twice
+ ticket_id = self._tester.create_ticket("RegressionTestTicket6879 b")
+ self._tester.go_to_ticket(ticket_id)
+ tc.formvalue('propertyform', 'action', 'resolve')
+ tc.formvalue('propertyform', 'action_resolve_resolve_resolution',
'fixed')
+
+ # [BLOODHOUND] No preview button for ticket (comments) in BH theme
+ try:
+ tc.submit('preview')
+ except TwillException:
+ self.skipTest('Active theme without ticket comment preview')
+
+ tc.formvalue('propertyform', 'action', 'resolve')
+ tc.submit('submit')
+
+
+class TestAdminPriorityRenumber(FunctionalTwillTestCaseSetup):
+ BH_IN_DEFAULT_PRODUCT = True
+
+ def runTest(self):
+ """Admin renumber priorities"""
+
+ # [BLOODHOUND] class="input-mini" appended to priorities <select />
+ valuesRE = re.compile('<select name="value_([0-9]+)".*>', re.M)
+
+ html = b.get_html()
+ max_priority = max([int(x) for x in valuesRE.findall(html)])
+
+ name = "RenumberPriority"
+ self._tester.create_priority(name + '1')
+ self._tester.create_priority(name + '2')
+ priority_url = self._tester.url + '/admin/ticket/priority'
+ tc.go(priority_url)
+ tc.url(priority_url + '$')
+ tc.find(name + '1')
+ tc.find(name + '2')
+ tc.formvalue('enumtable', 'value_%s' % (max_priority + 1),
str(max_priority + 2))
+ tc.formvalue('enumtable', 'value_%s' % (max_priority + 2),
str(max_priority + 1))
+ tc.submit('apply')
+ tc.url(priority_url + '$')
+ # Verify that their order has changed.
+ tc.find(name + '2.*' + name + '1', 's')
+
+
+# Ensure that overridden code will be loaded
+def trac_functionalSuite(suite=None):
suite.addTest(TestTickets())
# [BLOODHOUND] there's no such thing like ticket preview
@@ -411,5 +875,23 @@ def functionalSuite(suite=None):
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')
+