You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bloodhound.apache.org by rj...@apache.org on 2014/02/13 06:08:08 UTC

svn commit: r1567849 [11/17] - in /bloodhound/vendor/trac: 1.0-stable/ current/ current/contrib/ current/contrib/cgi-bin/ current/contrib/workflow/ current/doc/ current/doc/utils/ current/sample-plugins/ current/sample-plugins/permissions/ current/samp...

Modified: bloodhound/vendor/trac/current/trac/ticket/tests/notification.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/tests/notification.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/ticket/tests/notification.py (original)
+++ bloodhound/vendor/trac/current/trac/ticket/tests/notification.py Thu Feb 13 05:08:02 2014
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 #
-# Copyright (C) 2005-2009 Edgewall Software
+# Copyright (C) 2005-2013 Edgewall Software
 # Copyright (C) 2005-2006 Emmanuel Blot <em...@free.fr>
 # All rights reserved.
 #
@@ -16,21 +16,22 @@
 # (lsmithson@open-networks.co.uk) extensible Python SMTP Server
 #
 
-from trac.util.datefmt import utc
-from trac.ticket.model import Ticket
-from trac.ticket.notification import TicketNotifyEmail
-from trac.test import EnvironmentStub, Mock, MockPerm
-from trac.tests.notification import SMTPThreadedServer, parse_smtp_message, \
-                                    smtp_address
-
 import base64
-from datetime import datetime
 import os
 import quopri
 import re
 import unittest
+from datetime import datetime
+
+from trac.test import EnvironmentStub, Mock, MockPerm
+from trac.tests import compat
+from trac.tests.notification import SMTP_TEST_PORT, SMTPThreadedServer,\
+                                    parse_smtp_message, smtp_address
+from trac.ticket.model import Ticket
+from trac.ticket.notification import TicketNotifyEmail
+from trac.ticket.web_ui import TicketModule
+from trac.util.datefmt import utc
 
-SMTP_TEST_PORT = 7000 + os.getpid() % 1000
 MAXBODYWIDTH = 76
 notifysuite = None
 
@@ -48,7 +49,7 @@ class NotificationTestCase(unittest.Test
                             'joe.user@example.net, joe.bar@example.net')
         self.env.config.set('notification', 'use_public_cc', 'true')
         self.env.config.set('notification', 'smtp_port', str(SMTP_TEST_PORT))
-        self.env.config.set('notification', 'smtp_server','localhost')
+        self.env.config.set('notification', 'smtp_server', 'localhost')
         self.req = Mock(href=self.env.href, abs_href=self.env.abs_href, tz=utc,
                         perm=MockPerm())
 
@@ -61,9 +62,9 @@ class NotificationTestCase(unittest.Test
         """To/Cc recipients"""
         ticket = Ticket(self.env)
         ticket['reporter'] = '"Joe User" < joe.user@example.org >'
-        ticket['owner']    = 'joe.user@example.net'
-        ticket['cc']       = 'joe.user@example.com, joe.bar@example.org, ' \
-                             'joe.bar@example.net'
+        ticket['owner'] = 'joe.user@example.net'
+        ticket['cc'] = 'joe.user@example.com, joe.bar@example.org, ' \
+                       'joe.bar@example.net'
         ticket['summary'] = 'Foo'
         ticket.insert()
         tn = TicketNotifyEmail(self.env)
@@ -72,17 +73,17 @@ class NotificationTestCase(unittest.Test
         # checks there is no duplicate in the recipient list
         rcpts = []
         for r in recipients:
-            self.failIf(r in rcpts)
+            self.assertNotIn(r, rcpts)
             rcpts.append(r)
         # checks that all cc recipients have been notified
         cc_list = self.env.config.get('notification', 'smtp_always_cc')
         cc_list = "%s, %s" % (cc_list, ticket['cc'])
         for r in cc_list.replace(',', ' ').split():
-            self.failIf(r not in recipients)
+            self.assertIn(r, recipients)
         # checks that owner has been notified
-        self.failIf(smtp_address(ticket['owner']) not in recipients)
+        self.assertIn(smtp_address(ticket['owner']), recipients)
         # checks that reporter has been notified
-        self.failIf(smtp_address(ticket['reporter']) not in recipients)
+        self.assertIn(smtp_address(ticket['reporter']), recipients)
 
     def test_no_recipient(self):
         """No recipient case"""
@@ -97,9 +98,9 @@ class NotificationTestCase(unittest.Test
         recipients = notifysuite.smtpd.get_recipients()
         message = notifysuite.smtpd.get_message()
         # checks that no message has been sent
-        self.failIf(recipients)
-        self.failIf(sender)
-        self.failIf(message)
+        self.assertEqual([], recipients)
+        self.assertIsNone(sender)
+        self.assertIsNone(message)
 
     def test_cc_only(self):
         """Notification w/o explicit recipients but Cc: (#3101)"""
@@ -112,30 +113,30 @@ class NotificationTestCase(unittest.Test
         # checks that all cc recipients have been notified
         cc_list = self.env.config.get('notification', 'smtp_always_cc')
         for r in cc_list.replace(',', ' ').split():
-            self.failIf(r not in recipients)
+            self.assertIn(r, recipients)
 
     def test_structure(self):
         """Basic SMTP message structure (headers, body)"""
         ticket = Ticket(self.env)
         ticket['reporter'] = '"Joe User" <jo...@example.org>'
-        ticket['owner']    = 'joe.user@example.net'
-        ticket['cc']       = 'joe.user@example.com, joe.bar@example.org, ' \
-                             'joe.bar@example.net'
+        ticket['owner'] = 'joe.user@example.net'
+        ticket['cc'] = 'joe.user@example.com, joe.bar@example.org, ' \
+                       'joe.bar@example.net'
         ticket['summary'] = 'This is a summary'
         ticket.insert()
         tn = TicketNotifyEmail(self.env)
         tn.notify(ticket, newticket=True)
         message = notifysuite.smtpd.get_message()
-        (headers, body) = parse_smtp_message(message)
+        headers, body = parse_smtp_message(message)
         # checks for header existence
-        self.failIf(not headers)
-        # checks for body existance
-        self.failIf(not body)
+        self.assertTrue(headers)
+        # checks for body existence
+        self.assertTrue(body)
         # checks for expected headers
-        self.failIf('Date' not in headers)
-        self.failIf('Subject' not in headers)
-        self.failIf('Message-ID' not in headers)
-        self.failIf('From' not in headers)
+        self.assertIn('Date', headers)
+        self.assertIn('Subject', headers)
+        self.assertIn('Message-ID', headers)
+        self.assertIn('From', headers)
 
     def test_date(self):
         """Date format compliance (RFC822)
@@ -148,7 +149,7 @@ class NotificationTestCase(unittest.Test
         date_re = re.compile(date_str)
         # python time module does not detect incorrect time values
         days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
-        months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', \
+        months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
                   'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
         tz = ['UT', 'GMT', 'EST', 'EDT', 'CST', 'CDT', 'MST', 'MDT',
               'PST', 'PDT']
@@ -159,17 +160,17 @@ class NotificationTestCase(unittest.Test
         tn = TicketNotifyEmail(self.env)
         tn.notify(ticket, newticket=True)
         message = notifysuite.smtpd.get_message()
-        (headers, body) = parse_smtp_message(message)
-        self.failIf('Date' not in headers)
+        headers, body = parse_smtp_message(message)
+        self.assertIn('Date', headers)
         mo = date_re.match(headers['Date'])
-        self.failIf(not mo)
+        self.assertTrue(mo)
         if mo.group('day'):
-            self.failIf(mo.group('day') not in days)
-        self.failIf(int(mo.group('dm')) not in range(1, 32))
-        self.failIf(mo.group('month') not in months)
-        self.failIf(int(mo.group('hour')) not in range(0, 24))
+            self.assertIn(mo.group('day'), days)
+        self.assertIn(int(mo.group('dm')), range(1, 32))
+        self.assertIn(mo.group('month'), months)
+        self.assertIn(int(mo.group('hour')), range(0, 24))
         if mo.group('tz'):
-            self.failIf(mo.group('tz') not in tz)
+            self.assertIn(mo.group('tz'), tz)
 
     def test_bcc_privacy(self):
         """Visibility of recipients"""
@@ -186,15 +187,15 @@ class NotificationTestCase(unittest.Test
             tn = TicketNotifyEmail(self.env)
             tn.notify(ticket, newticket=True)
             message = notifysuite.smtpd.get_message()
-            (headers, body) = parse_smtp_message(message)
+            headers, body = parse_smtp_message(message)
             if public:
                 # Msg should have a To list
-                self.failIf('To' not in headers)
+                self.assertIn('To', headers)
                 # Extract the list of 'To' recipients from the message
                 to = [rcpt.strip() for rcpt in headers['To'].split(',')]
             else:
                 # Msg should not have a To list
-                self.failIf('To' in headers)
+                self.assertNotIn('To', headers)
                 # Extract the list of 'To' recipients from the message
                 to = []
             # Extract the list of 'Cc' recipients from the message
@@ -207,18 +208,18 @@ class NotificationTestCase(unittest.Test
             for rcpt in cclist:
                 # Each recipient of the 'Cc' list should appear
                 # in the 'Cc' header
-                self.failIf(rcpt not in cc)
+                self.assertIn(rcpt, cc)
                 # Check the message has actually been sent to the recipients
-                self.failIf(rcpt not in rcptlist)
+                self.assertIn(rcpt, rcptlist)
             # Build the list of the expected 'Bcc' recipients
             bccrcpt = self.env.config.get('notification', 'smtp_always_bcc')
             bcclist = [bccr.strip() for bccr in bccrcpt.split(',')]
             for rcpt in bcclist:
                 # Check none of the 'Bcc' recipients appears
                 # in the 'To' header
-                self.failIf(rcpt in to)
+                self.assertNotIn(rcpt, to)
                 # Check the message has actually been sent to the recipients
-                self.failIf(rcpt not in rcptlist)
+                self.assertIn(rcpt, rcptlist)
         run_bcc_feature(True)
         run_bcc_feature(False)
 
@@ -238,26 +239,26 @@ class NotificationTestCase(unittest.Test
             tn = TicketNotifyEmail(self.env)
             tn.notify(ticket, newticket=True)
             message = notifysuite.smtpd.get_message()
-            (headers, body) = parse_smtp_message(message)
+            headers, body = parse_smtp_message(message)
             # Msg should not have a 'To' header
             if not enabled:
-                self.failIf('To' in headers)
+                self.assertNotIn('To', headers)
             else:
                 tolist = [addr.strip() for addr in headers['To'].split(',')]
             # Msg should have a 'Cc' field
-            self.failIf('Cc' not in headers)
+            self.assertIn('Cc', headers)
             cclist = [addr.strip() for addr in headers['Cc'].split(',')]
             if enabled:
                 # Msg should be delivered to the reporter
-                self.failIf(ticket['reporter'] not in tolist)
+                self.assertIn(ticket['reporter'], tolist)
             else:
                 # Msg should not be delivered to joeuser
-                self.failIf(ticket['reporter'] in cclist)
+                self.assertNotIn(ticket['reporter'], cclist)
             # Msg should still be delivered to the always_cc list
-            self.failIf(self.env.config.get('notification',
-                        'smtp_always_cc') not in cclist)
+            self.assertIn(self.env.config.get('notification',
+                                              'smtp_always_cc'), cclist)
         # Validate with and without the short addr option enabled
-        for enable in [False, True]:
+        for enable in False, True:
             _test_short_login(enable)
 
     def test_default_domain(self):
@@ -282,21 +283,21 @@ class NotificationTestCase(unittest.Test
             tn = TicketNotifyEmail(self.env)
             tn.notify(ticket, newticket=True)
             message = notifysuite.smtpd.get_message()
-            (headers, body) = parse_smtp_message(message)
+            headers, body = parse_smtp_message(message)
             # Msg should always have a 'Cc' field
-            self.failIf('Cc' not in headers)
+            self.assertIn('Cc', headers)
             cclist = [addr.strip() for addr in headers['Cc'].split(',')]
-            self.failIf('joewithdom@example.com' not in cclist)
-            self.failIf('joe.bar@example.net' not in cclist)
+            self.assertIn('joewithdom@example.com', cclist)
+            self.assertIn('joe.bar@example.net', cclist)
             if not enabled:
-                self.failIf(len(cclist) != 2)
-                self.failIf('joenodom' in cclist)
+                self.assertEqual(2, len(cclist))
+                self.assertNotIn('joenodom', cclist)
             else:
-                self.failIf(len(cclist) != 3)
-                self.failIf('joenodom@example.org' not in cclist)
+                self.assertEqual(3, len(cclist))
+                self.assertIn('joenodom@example.org', cclist)
 
         # Validate with and without a default domain
-        for enable in [False, True]:
+        for enable in False, True:
             _test_default_domain(enable)
 
     def test_email_map(self):
@@ -317,15 +318,15 @@ class NotificationTestCase(unittest.Test
         tn = TicketNotifyEmail(self.env)
         tn.notify(ticket, newticket=True)
         message = notifysuite.smtpd.get_message()
-        (headers, body) = parse_smtp_message(message)
+        headers, body = parse_smtp_message(message)
         # Msg should always have a 'To' field
-        self.failIf('To' not in headers)
+        self.assertIn('To', headers)
         tolist = [addr.strip() for addr in headers['To'].split(',')]
         # 'To' list should have been resolved to the real email address
-        self.failIf('user-joe@example.com' not in tolist)
-        self.failIf('user-jim@example.com' not in tolist)
-        self.failIf('joeuser' in tolist)
-        self.failIf('jim@domain' in tolist)
+        self.assertIn('user-joe@example.com', tolist)
+        self.assertIn('user-jim@example.com', tolist)
+        self.assertNotIn('joeuser', tolist)
+        self.assertNotIn('jim@domain', tolist)
 
     def test_from_author(self):
         """Using the reporter or change author as the notification sender"""
@@ -346,7 +347,7 @@ class NotificationTestCase(unittest.Test
         tn = TicketNotifyEmail(self.env)
         tn.notify(ticket, newticket=True)
         message = notifysuite.smtpd.get_message()
-        (headers, body) = parse_smtp_message(message)
+        headers, body = parse_smtp_message(message)
         self.assertEqual('"Joe User" <us...@example.com>', headers['From'])
         # Ticket change uses the change author
         ticket['summary'] = 'Modified summary'
@@ -354,7 +355,7 @@ class NotificationTestCase(unittest.Test
         tn = TicketNotifyEmail(self.env)
         tn.notify(ticket, newticket=False, modtime=ticket['changetime'])
         message = notifysuite.smtpd.get_message()
-        (headers, body) = parse_smtp_message(message)
+        headers, body = parse_smtp_message(message)
         self.assertEqual('"Jim User" <us...@example.com>', headers['From'])
         # Known author without name uses e-mail address only
         ticket['summary'] = 'Final summary'
@@ -362,7 +363,7 @@ class NotificationTestCase(unittest.Test
         tn = TicketNotifyEmail(self.env)
         tn.notify(ticket, newticket=False, modtime=ticket['changetime'])
         message = notifysuite.smtpd.get_message()
-        (headers, body) = parse_smtp_message(message)
+        headers, body = parse_smtp_message(message)
         self.assertEqual('user-noname@example.com', headers['From'])
         # Known author without e-mail uses smtp_from and smtp_from_name
         ticket['summary'] = 'Other summary'
@@ -370,7 +371,7 @@ class NotificationTestCase(unittest.Test
         tn = TicketNotifyEmail(self.env)
         tn.notify(ticket, newticket=False, modtime=ticket['changetime'])
         message = notifysuite.smtpd.get_message()
-        (headers, body) = parse_smtp_message(message)
+        headers, body = parse_smtp_message(message)
         self.assertEqual('"My Trac" <tr...@example.com>', headers['From'])
         # Unknown author with name and e-mail address
         ticket['summary'] = 'Some summary'
@@ -378,7 +379,7 @@ class NotificationTestCase(unittest.Test
         tn = TicketNotifyEmail(self.env)
         tn.notify(ticket, newticket=False, modtime=ticket['changetime'])
         message = notifysuite.smtpd.get_message()
-        (headers, body) = parse_smtp_message(message)
+        headers, body = parse_smtp_message(message)
         self.assertEqual('"Test User" <te...@example.com>', headers['From'])
         # Unknown author with e-mail address only
         ticket['summary'] = 'Some summary'
@@ -386,7 +387,7 @@ class NotificationTestCase(unittest.Test
         tn = TicketNotifyEmail(self.env)
         tn.notify(ticket, newticket=False, modtime=ticket['changetime'])
         message = notifysuite.smtpd.get_message()
-        (headers, body) = parse_smtp_message(message)
+        headers, body = parse_smtp_message(message)
         self.assertEqual('test@example.com', headers['From'])
         # Unknown author uses smtp_from and smtp_from_name
         ticket['summary'] = 'Better summary'
@@ -394,7 +395,7 @@ class NotificationTestCase(unittest.Test
         tn = TicketNotifyEmail(self.env)
         tn.notify(ticket, newticket=False, modtime=ticket['changetime'])
         message = notifysuite.smtpd.get_message()
-        (headers, body) = parse_smtp_message(message)
+        headers, body = parse_smtp_message(message)
         self.assertEqual('"My Trac" <tr...@example.com>', headers['From'])
 
     def test_ignore_domains(self):
@@ -412,16 +413,16 @@ class NotificationTestCase(unittest.Test
         tn = TicketNotifyEmail(self.env)
         tn.notify(ticket, newticket=True)
         message = notifysuite.smtpd.get_message()
-        (headers, body) = parse_smtp_message(message)
+        headers, body = parse_smtp_message(message)
         # Msg should always have a 'To' field
-        self.failIf('To' not in headers)
+        self.assertIn('To', headers)
         tolist = [addr.strip() for addr in headers['To'].split(',')]
         # 'To' list should not contain addresses with non-SMTP domains
-        self.failIf('kerberos@example.com' in tolist)
-        self.failIf('kerberos@example.org' in tolist)
+        self.assertNotIn('kerberos@example.com', tolist)
+        self.assertNotIn('kerberos@example.org', tolist)
         # 'To' list should have been resolved to the actual email address
-        self.failIf('kerb@example.net' not in tolist)
-        self.failIf(len(tolist) != 1)
+        self.assertIn('kerb@example.net', tolist)
+        self.assertEqual(1, len(tolist))
 
     def test_admit_domains(self):
         """SMTP domain inclusion"""
@@ -436,16 +437,16 @@ class NotificationTestCase(unittest.Test
         tn = TicketNotifyEmail(self.env)
         tn.notify(ticket, newticket=True)
         message = notifysuite.smtpd.get_message()
-        (headers, body) = parse_smtp_message(message)
+        headers, body = parse_smtp_message(message)
         # Msg should always have a 'To' field
-        self.failIf('Cc' not in headers)
+        self.assertIn('Cc', headers)
         cclist = [addr.strip() for addr in headers['Cc'].split(',')]
         # 'Cc' list should contain addresses with SMTP included domains
-        self.failIf('joe.user@localdomain' not in cclist)
-        self.failIf('joe.user@server' not in cclist)
+        self.assertIn('joe.user@localdomain', cclist)
+        self.assertIn('joe.user@server', cclist)
         # 'Cc' list should not contain non-FQDN domains
-        self.failIf('joe.user@unknown' in cclist)
-        self.failIf(len(cclist) != 2+2)
+        self.assertNotIn('joe.user@unknown', cclist)
+        self.assertEqual(4, len(cclist))
 
     def test_multiline_header(self):
         """Encoded headers split into multiple lines"""
@@ -458,11 +459,11 @@ class NotificationTestCase(unittest.Test
         tn = TicketNotifyEmail(self.env)
         tn.notify(ticket, newticket=True)
         message = notifysuite.smtpd.get_message()
-        (headers, body) = parse_smtp_message(message)
+        headers, body = parse_smtp_message(message)
         # Discards the project name & ticket number
         subject = headers['Subject']
         summary = subject[subject.find(':')+2:]
-        self.failIf(ticket['summary'] != summary)
+        self.assertEqual(ticket['summary'], summary)
 
     def test_mimebody_b64(self):
         """MIME Base64/utf-8 encoding"""
@@ -472,8 +473,7 @@ class NotificationTestCase(unittest.Test
         ticket['summary'] = u'This is a long enough summary to cause Trac ' \
                             u'to generate a multi-line (2 lines) súmmäry'
         ticket.insert()
-        self._validate_mimebody((base64, 'base64', 'utf-8'), \
-                                ticket, True)
+        self._validate_mimebody((base64, 'base64', 'utf-8'), ticket, True)
 
     def test_mimebody_qp(self):
         """MIME QP/utf-8 encoding"""
@@ -493,8 +493,7 @@ class NotificationTestCase(unittest.Test
         ticket['reporter'] = 'joe.user'
         ticket['summary'] = u'This is a summary'
         ticket.insert()
-        self._validate_mimebody((None, '7bit', 'utf-8'), \
-                                ticket, True)
+        self._validate_mimebody((None, '7bit', 'utf-8'), ticket, True)
 
     def test_mimebody_none_8bit(self):
         """MIME None encoding resulting in 8bit"""
@@ -503,8 +502,7 @@ class NotificationTestCase(unittest.Test
         ticket['reporter'] = 'joe.user'
         ticket['summary'] = u'This is a summary for Jöe Usèr'
         ticket.insert()
-        self._validate_mimebody((None, '8bit', 'utf-8'), \
-                                ticket, True)
+        self._validate_mimebody((None, '8bit', 'utf-8'), ticket, True)
 
     def test_md5_digest(self):
         """MD5 digest w/ non-ASCII recipient address (#3491)"""
@@ -518,12 +516,12 @@ class NotificationTestCase(unittest.Test
         tn = TicketNotifyEmail(self.env)
         tn.notify(ticket, newticket=True)
         message = notifysuite.smtpd.get_message()
-        (headers, body) = parse_smtp_message(message)
+        headers, body = parse_smtp_message(message)
 
     def test_updater(self):
         """No-self-notification option"""
-        def _test_updater(disable):
-            if disable:
+        def _test_updater(disabled):
+            if disabled:
                 self.env.config.set('notification', 'always_notify_updater',
                                     'false')
             ticket = Ticket(self.env)
@@ -538,19 +536,19 @@ class NotificationTestCase(unittest.Test
             tn = TicketNotifyEmail(self.env)
             tn.notify(ticket, newticket=False, modtime=now)
             message = notifysuite.smtpd.get_message()
-            (headers, body) = parse_smtp_message(message)
+            headers, body = parse_smtp_message(message)
             # checks for header existence
-            self.failIf(not headers)
+            self.assertTrue(headers)
             # checks for updater in the 'To' recipient list
-            self.failIf('To' not in headers)
+            self.assertIn('To', headers)
             tolist = [addr.strip() for addr in headers['To'].split(',')]
-            if disable:
-                self.failIf('joe.bar2@example.com' in tolist)
+            if disabled:
+                self.assertNotIn('joe.bar2@example.com', tolist)
             else:
-                self.failIf('joe.bar2@example.com' not in tolist)
+                self.assertIn('joe.bar2@example.com', tolist)
 
         # Validate with and without a default domain
-        for disable in [False, True]:
+        for disable in False, True:
             _test_updater(disable)
 
     def test_updater_only(self):
@@ -573,9 +571,9 @@ class NotificationTestCase(unittest.Test
         tn = TicketNotifyEmail(self.env)
         tn.notify(ticket, newticket=True)
         recipients = notifysuite.smtpd.get_recipients()
-        self.failIf(recipients is None)
-        self.failIf(len(recipients) != 1)
-        self.failIf(recipients[0] != 'joe@example.com')
+        self.assertIsNotNone(recipients)
+        self.assertEqual(1, len(recipients))
+        self.assertEqual(recipients[0], 'joe@example.com')
 
     def test_updater_is_reporter(self):
         """Notification to reporter w/ updater option disabled (#3780)"""
@@ -598,38 +596,38 @@ class NotificationTestCase(unittest.Test
         tn = TicketNotifyEmail(self.env)
         tn.notify(ticket, newticket=True)
         recipients = notifysuite.smtpd.get_recipients()
-        self.failIf(recipients is None)
-        self.failIf(len(recipients) != 1)
-        self.failIf(recipients[0] != 'joe@example.org')
+        self.assertIsNotNone(recipients)
+        self.assertEqual(1, len(recipients))
+        self.assertEqual('joe@example.org', recipients[0])
 
     def _validate_mimebody(self, mime, ticket, newtk):
         """Body of a ticket notification message"""
-        (mime_decoder, mime_name, mime_charset) = mime
+        mime_decoder, mime_name, mime_charset = mime
         tn = TicketNotifyEmail(self.env)
         tn.notify(ticket, newticket=newtk)
         message = notifysuite.smtpd.get_message()
-        (headers, body) = parse_smtp_message(message)
-        self.failIf('MIME-Version' not in headers)
-        self.failIf('Content-Type' not in headers)
-        self.failIf('Content-Transfer-Encoding' not in headers)
-        self.failIf(not re.compile(r"1.\d").match(headers['MIME-Version']))
+        headers, body = parse_smtp_message(message)
+        self.assertIn('MIME-Version', headers)
+        self.assertIn('Content-Type', headers)
+        self.assertIn('Content-Transfer-Encoding', headers)
+        self.assertTrue(re.compile(r"1.\d").match(headers['MIME-Version']))
         type_re = re.compile(r'^text/plain;\scharset="([\w\-\d]+)"$')
         charset = type_re.match(headers['Content-Type'])
-        self.failIf(not charset)
+        self.assertTrue(charset)
         charset = charset.group(1)
-        self.assertEqual(charset, mime_charset)
+        self.assertEqual(mime_charset, charset)
         self.assertEqual(headers['Content-Transfer-Encoding'], mime_name)
         # checks the width of each body line
         for line in body.splitlines():
-            self.failIf(len(line) > MAXBODYWIDTH)
-        # attempts to decode the body, following the specified MIME endoding
+            self.assertTrue(len(line) <= MAXBODYWIDTH)
+        # attempts to decode the body, following the specified MIME encoding
         # and charset
         try:
             if mime_decoder:
                 body = mime_decoder.decodestring(body)
             body = unicode(body, charset)
         except Exception, e:
-            raise AssertionError, e
+            raise AssertionError(e)
         # now processes each line of the body
         bodylines = body.splitlines()
         # body starts with one of more summary lines, first line is prefixed
@@ -637,25 +635,25 @@ class NotificationTestCase(unittest.Test
         # finds the banner after the summary
         banner_delim_re = re.compile(r'^\-+\+\-+$')
         bodyheader = []
-        while ( not banner_delim_re.match(bodylines[0]) ):
+        while not banner_delim_re.match(bodylines[0]):
             bodyheader.append(bodylines.pop(0))
         # summary should be present
-        self.failIf(not bodyheader)
+        self.assertTrue(bodyheader)
         # banner should not be empty
-        self.failIf(not bodylines)
+        self.assertTrue(bodylines)
         # extracts the ticket ID from the first line
-        (tknum, bodyheader[0]) = bodyheader[0].split(' ', 1)
-        self.assertEqual(tknum[0], '#')
+        tknum, bodyheader[0] = bodyheader[0].split(' ', 1)
+        self.assertEqual('#', tknum[0])
         try:
             tkid = int(tknum[1:-1])
-            self.assertEqual(tkid, 1)
+            self.assertEqual(1, tkid)
         except ValueError:
-            raise AssertionError, "invalid ticket number"
-        self.assertEqual(tknum[-1], ':')
+            raise AssertionError("invalid ticket number")
+        self.assertEqual(':', tknum[-1])
         summary = ' '.join(bodyheader)
         self.assertEqual(summary, ticket['summary'])
         # now checks the banner contents
-        self.failIf(not banner_delim_re.match(bodylines[0]))
+        self.assertTrue(banner_delim_re.match(bodylines[0]))
         banner = True
         footer = None
         props = {}
@@ -667,11 +665,11 @@ class NotificationTestCase(unittest.Test
             if banner:
                 # parse banner and fill in a property dict
                 properties = line.split('|')
-                self.assertEqual(len(properties), 2)
+                self.assertEqual(2, len(properties))
                 for prop in properties:
                     if prop.strip() == '':
                         continue
-                    (k, v) = prop.split(':')
+                    k, v = prop.split(':')
                     props[k.strip().lower()] = v.strip()
             # detect footer marker (weak detection)
             if not footer:
@@ -679,10 +677,10 @@ class NotificationTestCase(unittest.Test
                     footer = 0
                     continue
             # check footer
-            if footer != None:
+            if footer is not None:
                 footer += 1
                 # invalid footer detection
-                self.failIf(footer > 3)
+                self.assertTrue(footer <= 3)
                 # check ticket link
                 if line[:11] == 'Ticket URL:':
                     ticket_link = self.env.abs_href.ticket(ticket.id)
@@ -693,12 +691,13 @@ class NotificationTestCase(unittest.Test
         xlist = ['summary', 'description', 'comment', 'time', 'changetime']
         # check banner content (field exists, msg value matches ticket value)
         for p in [prop for prop in ticket.values.keys() if prop not in xlist]:
-            self.failIf(not props.has_key(p))
+            self.assertIn(p, props)
             # Email addresses might be obfuscated
             if '@' in ticket[p] and '@' in props[p]:
-                self.failIf(props[p].split('@')[0] != ticket[p].split('@')[0])
+                self.assertEqual(props[p].split('@')[0],
+                                 ticket[p].split('@')[0])
             else:
-                self.failIf(props[p] != ticket[p])
+                self.assertEqual(props[p], ticket[p])
 
     def test_props_format_ambiwidth_single(self):
         self.env.config.set('notification', 'mime_encoding', 'none')
@@ -827,7 +826,7 @@ Resolution:  fixed                   |  
       Type:  defect                              |     Status:  new
   Priority:  major                               |  Milestone:  milestone1
  Component:  Lorem ipsum dolor sit amet,         |    Version:  2.0
-  consectetur adipisicing elit, sed do eiusmod   |   Keywords:
+  consectetur adipisicing elit, sed do eiusmod   |
   tempor incididunt ut labore et dolore magna    |
   aliqua. Ut enim ad minim veniam, quis nostrud  |
   exercitation ullamco laboris nisi ut aliquip   |
@@ -837,7 +836,7 @@ Resolution:  fixed                   |  
   Excepteur sint occaecat cupidatat non          |
   proident, sunt in culpa qui officia deserunt   |
   mollit anim id est laborum.                    |
-Resolution:  fixed                               |"""
+Resolution:  fixed                               |   Keywords:"""
         self._validate_props_format(formatted, ticket)
 
     def test_props_format_wrap_leftside_unicode(self):
@@ -865,10 +864,10 @@ Resolution:  fixed                      
       Type:  defect                              |     Status:  new
   Priority:  major                               |  Milestone:  milestone1
  Component:  Trac は BSD ライセンスのもとで配布  |    Version:  2.0
-  されています。[1:]このライセンスの全文は、配   |   Keywords:
+  されています。[1:]このライセンスの全文は、配   |
   布ファイルに含まれている [3:COPYING] ファイル  |
   と同じものが[2:オンライン]で参照できます。     |
-Resolution:  fixed                               |"""
+Resolution:  fixed                               |   Keywords:"""
         self._validate_props_format(formatted, ticket)
 
     def test_props_format_wrap_rightside(self):
@@ -893,7 +892,7 @@ Resolution:  fixed                      
                               u'culpa qui officia deserunt mollit anim id ' \
                               u'est laborum.'
         ticket['component'] = u'component1'
-        ticket['version'] = u'2.0'
+        ticket['version'] = u'2.0 Standard and International Edition'
         ticket['resolution'] = u'fixed'
         ticket['keywords'] = u''
         ticket.insert()
@@ -901,8 +900,8 @@ Resolution:  fixed                      
   Reporter:  anonymous   |      Owner:  somebody
       Type:  defect      |     Status:  new
   Priority:  major       |  Milestone:  Lorem ipsum dolor sit amet,
- Component:  component1  |  consectetur adipisicing elit, sed do eiusmod
-Resolution:  fixed       |  tempor incididunt ut labore et dolore magna
+                         |  consectetur adipisicing elit, sed do eiusmod
+                         |  tempor incididunt ut labore et dolore magna
                          |  aliqua. Ut enim ad minim veniam, quis nostrud
                          |  exercitation ullamco laboris nisi ut aliquip ex
                          |  ea commodo consequat. Duis aute irure dolor in
@@ -911,8 +910,9 @@ Resolution:  fixed       |  tempor incid
                          |  occaecat cupidatat non proident, sunt in culpa
                          |  qui officia deserunt mollit anim id est
                          |  laborum.
-                         |    Version:  2.0
-                         |   Keywords:"""
+ Component:  component1  |    Version:  2.0 Standard and International
+                         |  Edition
+Resolution:  fixed       |   Keywords:"""
         self._validate_props_format(formatted, ticket)
 
     def test_props_format_wrap_rightside_unicode(self):
@@ -937,10 +937,10 @@ Resolution:  fixed       |  tempor incid
   Reporter:  anonymous   |      Owner:  somebody
       Type:  defect      |     Status:  new
   Priority:  major       |  Milestone:  Trac 在经过修改的BSD协议下发布。
- Component:  component1  |  [1:]协议的完整文本可以[2:在线查看]也可在发布版
-Resolution:  fixed       |  的 [3:COPYING] 文件中找到。
-                         |    Version:  2.0
-                         |   Keywords:"""
+                         |  [1:]协议的完整文本可以[2:在线查看]也可在发布版
+                         |  的 [3:COPYING] 文件中找到。
+ Component:  component1  |    Version:  2.0
+Resolution:  fixed       |   Keywords:"""
         self._validate_props_format(formatted, ticket)
 
     def test_props_format_wrap_bothsides(self):
@@ -964,31 +964,33 @@ Resolution:  fixed       |  的 [3:CO
                               u'occaecat cupidatat non proident, sunt in ' \
                               u'culpa qui officia deserunt mollit anim id ' \
                               u'est laborum.'
-        ticket['component'] = ticket['milestone']
+        ticket['component'] = (u'Lorem ipsum dolor sit amet, consectetur '
+                               u'adipisicing elit, sed do eiusmod tempor '
+                               u'incididunt ut labore et dolore magna aliqua.')
         ticket['version'] = u'2.0'
         ticket['resolution'] = u'fixed'
-        ticket['keywords'] = u''
+        ticket['keywords'] = u'Ut enim ad minim veniam, ....'
         ticket.insert()
         formatted = """\
   Reporter:  anonymous               |      Owner:  somebody
       Type:  defect                  |     Status:  new
   Priority:  major                   |  Milestone:  Lorem ipsum dolor sit
- Component:  Lorem ipsum dolor sit   |  amet, consectetur adipisicing elit,
-  amet, consectetur adipisicing      |  sed do eiusmod tempor incididunt ut
-  elit, sed do eiusmod tempor        |  labore et dolore magna aliqua. Ut
-  incididunt ut labore et dolore     |  enim ad minim veniam, quis nostrud
-  magna aliqua. Ut enim ad minim     |  exercitation ullamco laboris nisi
-  veniam, quis nostrud exercitation  |  ut aliquip ex ea commodo consequat.
-  ullamco laboris nisi ut aliquip    |  Duis aute irure dolor in
-  ex ea commodo consequat. Duis      |  reprehenderit in voluptate velit
-  aute irure dolor in reprehenderit  |  esse cillum dolore eu fugiat nulla
-  in voluptate velit esse cillum     |  pariatur. Excepteur sint occaecat
-  dolore eu fugiat nulla pariatur.   |  cupidatat non proident, sunt in
-  Excepteur sint occaecat cupidatat  |  culpa qui officia deserunt mollit
-  non proident, sunt in culpa qui    |  anim id est laborum.
-  officia deserunt mollit anim id    |    Version:  2.0
-  est laborum.                       |   Keywords:
-Resolution:  fixed                   |"""
+                                     |  amet, consectetur adipisicing elit,
+                                     |  sed do eiusmod tempor incididunt ut
+                                     |  labore et dolore magna aliqua. Ut
+                                     |  enim ad minim veniam, quis nostrud
+                                     |  exercitation ullamco laboris nisi
+                                     |  ut aliquip ex ea commodo consequat.
+                                     |  Duis aute irure dolor in
+                                     |  reprehenderit in voluptate velit
+                                     |  esse cillum dolore eu fugiat nulla
+ Component:  Lorem ipsum dolor sit   |  pariatur. Excepteur sint occaecat
+  amet, consectetur adipisicing      |  cupidatat non proident, sunt in
+  elit, sed do eiusmod tempor        |  culpa qui officia deserunt mollit
+  incididunt ut labore et dolore     |  anim id est laborum.
+  magna aliqua.                      |    Version:  2.0
+Resolution:  fixed                   |   Keywords:  Ut enim ad minim
+                                     |  veniam, ...."""
         self._validate_props_format(formatted, ticket)
 
     def test_props_format_wrap_bothsides_unicode(self):
@@ -1011,7 +1013,7 @@ Resolution:  fixed                   |""
                               u'に含まれている[3:CОPYING]ファイ' \
                               u'ルと同じものが[2:オンライン]で' \
                               u'参照できます。'
-        ticket['version'] = u'2.0'
+        ticket['version'] = u'2.0 International Edition'
         ticket['resolution'] = u'fixed'
         ticket['keywords'] = u''
         ticket.insert()
@@ -1022,17 +1024,74 @@ Resolution:  fixed                   |""
  Component:  Trac は BSD ライセンス  |  议下发布。[1:]协议的完整文本可以[2:
   のもとで配布されています。[1:]こ   |  在线查看]也可在发布版的 [3:COPYING]
   のライセンスの全文は、※配布ファ   |  文件中找到。
-  イルに含まれている[3:CОPYING]フ   |    Version:  2.0
-  ァイルと同じものが[2:オンライン]   |   Keywords:
+  イルに含まれている[3:CОPYING]フ   |    Version:  2.0 International
+  ァイルと同じものが[2:オンライン]   |  Edition
   で参照できます。                   |
-Resolution:  fixed                   |"""
+Resolution:  fixed                   |   Keywords:"""
+        self._validate_props_format(formatted, ticket)
+
+    def test_props_format_wrap_ticket_10283(self):
+        self.env.config.set('notification', 'mime_encoding', 'none')
+        for name, value in (('blockedby', 'text'),
+                            ('blockedby.label', 'Blocked by'),
+                            ('blockedby.order', '6'),
+                            ('blocking', 'text'),
+                            ('blocking.label', 'Blocking'),
+                            ('blocking.order', '5'),
+                            ('deployment', 'text'),
+                            ('deployment.label', 'Deployment state'),
+                            ('deployment.order', '1'),
+                            ('nodes', 'text'),
+                            ('nodes.label', 'Related nodes'),
+                            ('nodes.order', '3'),
+                            ('privacy', 'text'),
+                            ('privacy.label', 'Privacy sensitive'),
+                            ('privacy.order', '2'),
+                            ('sensitive', 'text'),
+                            ('sensitive.label', 'Security sensitive'),
+                            ('sensitive.order', '4')):
+            self.env.config.set('ticket-custom', name, value)
+
+        ticket = Ticket(self.env)
+        ticket['summary'] = u'This is a summary'
+        ticket['reporter'] = u'anonymous'
+        ticket['owner'] = u'somebody'
+        ticket['type'] = u'defect'
+        ticket['status'] = u'closed'
+        ticket['priority'] = u'normal'
+        ticket['milestone'] = u'iter_01'
+        ticket['component'] = u'XXXXXXXXXXXXXXXXXXXXXXXXXX'
+        ticket['resolution'] = u'fixed'
+        ticket['keywords'] = u''
+        ticket['deployment'] = ''
+        ticket['privacy'] = '0'
+        ticket['nodes'] = 'XXXXXXXXXX'
+        ticket['sensitive'] = '0'
+        ticket['blocking'] = ''
+        ticket['blockedby'] = ''
+        ticket.insert()
+
+        formatted = """\
+          Reporter:  anonymous                   |             Owner:
+                                                 |  somebody
+              Type:  defect                      |            Status:
+                                                 |  closed
+          Priority:  normal                      |         Milestone:
+                                                 |  iter_01
+         Component:  XXXXXXXXXXXXXXXXXXXXXXXXXX  |        Resolution:
+                                                 |  fixed
+          Keywords:                              |  Deployment state:
+ Privacy sensitive:  0                           |     Related nodes:
+                                                 |  XXXXXXXXXX
+Security sensitive:  0                           |          Blocking:
+        Blocked by:                              |"""
         self._validate_props_format(formatted, ticket)
 
     def _validate_props_format(self, expected, ticket):
         tn = TicketNotifyEmail(self.env)
         tn.notify(ticket, newticket=True)
         message = notifysuite.smtpd.get_message()
-        (headers, body) = parse_smtp_message(message)
+        headers, body = parse_smtp_message(message)
         bodylines = body.splitlines()
         # Extract ticket properties
         delim_re = re.compile(r'^\-+\+\-+$')
@@ -1052,7 +1111,7 @@ Resolution:  fixed                   |""
         ticket.insert()
         tn = TicketNotifyEmail(self.env)
         tn.notify(ticket, newticket=True)
-        self.assertNotEqual(None, notifysuite.smtpd.get_message())
+        self.assertIsNotNone(notifysuite.smtpd.get_message())
         self.assertEqual('My Summary', ticket['summary'])
         self.assertEqual('Some description', ticket['description'])
         valid_fieldnames = set([f['name'] for f in ticket.fields])
@@ -1070,7 +1129,6 @@ Resolution:  fixed                   |""
         tn.get_message_id('foo')
 
 
-
 class NotificationTestSuite(unittest.TestSuite):
     """Thin test suite wrapper to start and stop the SMTP test server"""
 
@@ -1079,18 +1137,19 @@ class NotificationTestSuite(unittest.Tes
         unittest.TestSuite.__init__(self)
         self.smtpd = SMTPThreadedServer(SMTP_TEST_PORT)
         self.smtpd.start()
-        self.addTest(unittest.makeSuite(NotificationTestCase, 'test'))
+        self.addTest(unittest.makeSuite(NotificationTestCase))
         self.remaining = self.countTestCases()
 
     def tear_down(self):
         """Reset the local SMTP test server"""
         self.smtpd.cleanup()
-        self.remaining = self.remaining-1
+        self.remaining -= 1
         if self.remaining > 0:
             return
         # stop the SMTP test server when all tests have been completed
         self.smtpd.stop()
 
+
 def suite():
     global notifysuite
     if not notifysuite:
@@ -1098,4 +1157,4 @@ def suite():
     return notifysuite
 
 if __name__ == '__main__':
-    unittest.TextTestRunner(verbosity=2).run(suite())
+    unittest.main(defaultTest='suite')

Modified: bloodhound/vendor/trac/current/trac/ticket/tests/query.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/tests/query.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/ticket/tests/query.py (original)
+++ bloodhound/vendor/trac/current/trac/ticket/tests/query.py Thu Feb 13 05:08:02 2014
@@ -1,4 +1,18 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2004-2013 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.org/wiki/TracLicense.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
+
 from trac.test import Mock, EnvironmentStub, MockPerm, locale_en
+from trac.ticket.model import Ticket
 from trac.ticket.query import Query, QueryModule, TicketQueryMacro
 from trac.util.datefmt import utc
 from trac.web.chrome import web_context
@@ -257,12 +271,14 @@ ORDER BY COALESCE(t.id,0)=0,t.id""" % {'
         sql, args = query.get_sql()
         foo = self.env.get_read_db().quote('foo')
         self.assertEqualSQL(sql,
-"""SELECT t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.milestone AS milestone,t.time AS time,t.changetime AS changetime,priority.value AS priority_value,%s.value AS %s
-FROM ticket AS t
-  LEFT OUTER JOIN ticket_custom AS %s ON (id=%s.ticket AND %s.name='foo')
+"""SELECT t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.milestone AS milestone,t.time AS time,t.changetime AS changetime,priority.value AS priority_value,t.%s AS %s
+FROM (
+  SELECT t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.milestone AS milestone,t.time AS time,t.changetime AS changetime,
+  (SELECT c.value FROM ticket_custom c WHERE c.ticket=t.id AND c.name='foo') AS %s
+  FROM ticket AS t) AS t
   LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority)
-WHERE ((COALESCE(%s.value,'')=%%s))
-ORDER BY COALESCE(t.id,0)=0,t.id""" % ((foo,) * 6))
+WHERE ((COALESCE(t.%s,'')=%%s))
+ORDER BY COALESCE(t.id,0)=0,t.id""" % ((foo,) * 4))
         self.assertEqual(['something'], args)
         tickets = query.execute(self.req)
 
@@ -272,15 +288,77 @@ ORDER BY COALESCE(t.id,0)=0,t.id""" % ((
         sql, args = query.get_sql()
         foo = self.env.get_read_db().quote('foo')
         self.assertEqualSQL(sql,
-"""SELECT t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.milestone AS milestone,t.time AS time,t.changetime AS changetime,priority.value AS priority_value,%s.value AS %s
-FROM ticket AS t
-  LEFT OUTER JOIN ticket_custom AS %s ON (id=%s.ticket AND %s.name='foo')
+"""SELECT t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.milestone AS milestone,t.time AS time,t.changetime AS changetime,priority.value AS priority_value,t.%s AS %s
+FROM (
+  SELECT t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.milestone AS milestone,t.time AS time,t.changetime AS changetime,
+  (SELECT c.value FROM ticket_custom c WHERE c.ticket=t.id AND c.name='foo') AS %s
+  FROM ticket AS t) AS t
   LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority)
-ORDER BY COALESCE(%s.value,'')='',%s.value,COALESCE(t.id,0)=0,t.id""" %
-        ((foo,) * 7))
+ORDER BY COALESCE(t.%s,'')='',t.%s,COALESCE(t.id,0)=0,t.id""" %
+        ((foo,) * 5))
         self.assertEqual([], args)
         tickets = query.execute(self.req)
 
+    def test_constrained_by_id_ranges(self):
+        query = Query.from_string(self.env, 'id=42,44,51-55&order=id')
+        sql, args = query.get_sql()
+        self.assertEqualSQL(sql,
+"""SELECT t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.milestone AS milestone,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
+FROM ticket AS t
+  LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority)
+WHERE ((t.id BETWEEN %s AND %s OR t.id IN (42,44)))
+ORDER BY COALESCE(t.id,0)=0,t.id""")
+        self.assertEqual([51, 55], args)
+
+    def test_constrained_by_id_and_custom_field(self):
+        self.env.config.set('ticket-custom', 'foo', 'text')
+        ticket = Ticket(self.env)
+        ticket['reporter'] = 'joe'
+        ticket['summary'] = 'Foo'
+        ticket['foo'] = 'blah'
+        ticket.insert()
+
+        query = Query.from_string(self.env, 'id=%d-42&foo=blah' % ticket.id)
+        tickets = query.execute(self.req)
+        self.assertEqual(1, len(tickets))
+        self.assertEqual(ticket.id, tickets[0]['id'])
+
+        query = Query.from_string(self.env, 'id=%d,42&foo=blah' % ticket.id)
+        tickets = query.execute(self.req)
+        self.assertEqual(1, len(tickets))
+        self.assertEqual(ticket.id, tickets[0]['id'])
+
+        query = Query.from_string(self.env, 'id=%d,42,43-84&foo=blah' %
+                                            ticket.id)
+        tickets = query.execute(self.req)
+        self.assertEqual(1, len(tickets))
+        self.assertEqual(ticket.id, tickets[0]['id'])
+
+    def test_too_many_custom_fields(self):
+        fields = ['col_%02d' % i for i in xrange(100)]
+        for f in fields:
+            self.env.config.set('ticket-custom', f, 'text')
+
+        ticket = Ticket(self.env)
+        ticket['reporter'] = 'joe'
+        ticket['summary'] = 'Foo'
+        for idx, f in enumerate(fields):
+            ticket[f] = '%d.%s' % (idx, f)
+        ticket.insert()
+
+        string = 'col_00=0.col_00&order=id&col=id&col=reporter&col=summary' + \
+                 ''.join('&col=' + f for f in fields)
+        query = Query.from_string(self.env, string)
+        tickets = query.execute(self.req)
+        self.assertEqual(ticket.id, tickets[0]['id'])
+        self.assertEqual('joe', tickets[0]['reporter'])
+        self.assertEqual('Foo', tickets[0]['summary'])
+        self.assertEqual('0.col_00', tickets[0]['col_00'])
+        self.assertEqual('99.col_99', tickets[0]['col_99'])
+
+        query = Query.from_string(self.env, 'col_00=notfound')
+        self.assertEqual([], query.execute(self.req))
+
     def test_constrained_by_multiple_owners(self):
         query = Query.from_string(self.env, 'owner=someone|someone_else',
                                   order='id')
@@ -579,9 +657,9 @@ class TicketQueryMacroTestCase(unittest.
 
 def suite():
     suite = unittest.TestSuite()
-    suite.addTest(unittest.makeSuite(QueryTestCase, 'test'))
-    suite.addTest(unittest.makeSuite(QueryLinksTestCase, 'test'))
-    suite.addTest(unittest.makeSuite(TicketQueryMacroTestCase, 'test'))
+    suite.addTest(unittest.makeSuite(QueryTestCase))
+    suite.addTest(unittest.makeSuite(QueryLinksTestCase))
+    suite.addTest(unittest.makeSuite(TicketQueryMacroTestCase))
     return suite
 
 if __name__ == '__main__':

Modified: bloodhound/vendor/trac/current/trac/ticket/tests/report.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/tests/report.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/ticket/tests/report.py (original)
+++ bloodhound/vendor/trac/current/trac/ticket/tests/report.py Thu Feb 13 05:08:02 2014
@@ -1,4 +1,15 @@
 # -*- coding: utf-8 -*-
+#
+# Copyright (C) 2006-2013 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.org/wiki/TracLicense.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
 
 import doctest
 
@@ -103,7 +114,7 @@ class ReportTestCase(unittest.TestCase):
 def suite():
     suite = unittest.TestSuite()
     suite.addTest(doctest.DocTestSuite(trac.ticket.report))
-    suite.addTest(unittest.makeSuite(ReportTestCase, 'test'))
+    suite.addTest(unittest.makeSuite(ReportTestCase))
     return suite
 
 if __name__ == '__main__':

Modified: bloodhound/vendor/trac/current/trac/ticket/tests/roadmap.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/tests/roadmap.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/ticket/tests/roadmap.py (original)
+++ bloodhound/vendor/trac/current/trac/ticket/tests/roadmap.py Thu Feb 13 05:08:02 2014
@@ -1,3 +1,16 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2006-2013 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.org/wiki/TracLicense.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
+
 from trac.test import EnvironmentStub
 from trac.ticket.roadmap import *
 from trac.core import ComponentManager
@@ -10,53 +23,53 @@ class TicketGroupStatsTestCase(unittest.
         self.stats = TicketGroupStats('title', 'units')
 
     def test_init(self):
-        self.assertEquals('title', self.stats.title, 'title incorrect')
-        self.assertEquals('units', self.stats.unit, 'unit incorrect')
-        self.assertEquals(0, self.stats.count, 'count not zero')
-        self.assertEquals(0, len(self.stats.intervals), 'intervals not empty')
+        self.assertEqual('title', self.stats.title, 'title incorrect')
+        self.assertEqual('units', self.stats.unit, 'unit incorrect')
+        self.assertEqual(0, self.stats.count, 'count not zero')
+        self.assertEqual(0, len(self.stats.intervals), 'intervals not empty')
 
     def test_add_iterval(self):
         self.stats.add_interval('intTitle', 3, {'k1': 'v1'}, 'css', 0)
         self.stats.refresh_calcs()
-        self.assertEquals(3, self.stats.count, 'count not incremented')
+        self.assertEqual(3, self.stats.count, 'count not incremented')
         int = self.stats.intervals[0]
-        self.assertEquals('intTitle', int['title'], 'title incorrect')
-        self.assertEquals(3, int['count'], 'count incorrect')
-        self.assertEquals({'k1': 'v1'}, int['qry_args'], 'query args incorrect')
-        self.assertEquals('css', int['css_class'], 'css class incorrect')
-        self.assertEquals(100, int['percent'], 'percent incorrect')
+        self.assertEqual('intTitle', int['title'], 'title incorrect')
+        self.assertEqual(3, int['count'], 'count incorrect')
+        self.assertEqual({'k1': 'v1'}, int['qry_args'], 'query args incorrect')
+        self.assertEqual('css', int['css_class'], 'css class incorrect')
+        self.assertEqual(100, int['percent'], 'percent incorrect')
         self.stats.add_interval('intTitle', 3, {'k1': 'v1'}, 'css', 0)
         self.stats.refresh_calcs()
-        self.assertEquals(50, int['percent'], 'percent not being updated')
+        self.assertEqual(50, int['percent'], 'percent not being updated')
 
     def test_add_interval_no_prog(self):
         self.stats.add_interval('intTitle', 3, {'k1': 'v1'}, 'css', 0)
         self.stats.add_interval('intTitle', 5, {'k1': 'v1'}, 'css', 0)
         self.stats.refresh_calcs()
         interval = self.stats.intervals[1]
-        self.assertEquals(0, self.stats.done_count, 'count added for no prog')
-        self.assertEquals(0, self.stats.done_percent, 'percent incremented')
+        self.assertEqual(0, self.stats.done_count, 'count added for no prog')
+        self.assertEqual(0, self.stats.done_percent, 'percent incremented')
 
     def test_add_interval_prog(self):
         self.stats.add_interval('intTitle', 3, {'k1': 'v1'}, 'css', 0)
         self.stats.add_interval('intTitle', 1, {'k1': 'v1'}, 'css', 1)
         self.stats.refresh_calcs()
-        self.assertEquals(4, self.stats.count, 'count not incremented')
-        self.assertEquals(1, self.stats.done_count, 'count not added to prog')
-        self.assertEquals(25, self.stats.done_percent, 'done percent not incr')
+        self.assertEqual(4, self.stats.count, 'count not incremented')
+        self.assertEqual(1, self.stats.done_count, 'count not added to prog')
+        self.assertEqual(25, self.stats.done_percent, 'done percent not incr')
 
     def test_add_interval_fudging(self):
         self.stats.add_interval('intTitle', 3, {'k1': 'v1'}, 'css', 0)
         self.stats.add_interval('intTitle', 5, {'k1': 'v1'}, 'css', 1)
         self.stats.refresh_calcs()
-        self.assertEquals(8, self.stats.count, 'count not incremented')
-        self.assertEquals(5, self.stats.done_count, 'count not added to prog')
-        self.assertEquals(62, self.stats.done_percent,
-                          'done percnt not fudged downward')
-        self.assertEquals(62, self.stats.intervals[1]['percent'],
-                          'interval percent not fudged downward')
-        self.assertEquals(38, self.stats.intervals[0]['percent'],
-                          'interval percent not fudged upward')
+        self.assertEqual(8, self.stats.count, 'count not incremented')
+        self.assertEqual(5, self.stats.done_count, 'count not added to prog')
+        self.assertEqual(62, self.stats.done_percent,
+                         'done percnt not fudged downward')
+        self.assertEqual(62, self.stats.intervals[1]['percent'],
+                         'interval percent not fudged downward')
+        self.assertEqual(38, self.stats.intervals[0]['percent'],
+                         'interval percent not fudged upward')
 
 
 class DefaultTicketGroupStatsProviderTestCase(unittest.TestCase):
@@ -96,32 +109,32 @@ class DefaultTicketGroupStatsProviderTes
         self.env.reset_db()
 
     def test_stats(self):
-        self.assertEquals(self.stats.title, 'ticket status', 'title incorrect')
-        self.assertEquals(self.stats.unit, 'tickets', 'unit incorrect')
-        self.assertEquals(2, len(self.stats.intervals), 'more than 2 intervals')
+        self.assertEqual(self.stats.title, 'ticket status', 'title incorrect')
+        self.assertEqual(self.stats.unit, 'tickets', 'unit incorrect')
+        self.assertEqual(2, len(self.stats.intervals), 'more than 2 intervals')
 
     def test_closed_interval(self):
         closed = self.stats.intervals[0]
-        self.assertEquals('closed', closed['title'], 'closed title incorrect')
-        self.assertEquals('closed', closed['css_class'], 'closed class incorrect')
-        self.assertEquals(True, closed['overall_completion'],
-                          'closed should contribute to overall completion')
-        self.assertEquals({'status': ['closed'], 'group': ['resolution']},
-                          closed['qry_args'], 'qry_args incorrect')
-        self.assertEquals(1, closed['count'], 'closed count incorrect')
-        self.assertEquals(33, closed['percent'], 'closed percent incorrect')
+        self.assertEqual('closed', closed['title'], 'closed title incorrect')
+        self.assertEqual('closed', closed['css_class'], 'closed class incorrect')
+        self.assertTrue(closed['overall_completion'],
+                        'closed should contribute to overall completion')
+        self.assertEqual({'status': ['closed'], 'group': ['resolution']},
+                         closed['qry_args'], 'qry_args incorrect')
+        self.assertEqual(1, closed['count'], 'closed count incorrect')
+        self.assertEqual(33, closed['percent'], 'closed percent incorrect')
 
     def test_open_interval(self):
         open = self.stats.intervals[1]
-        self.assertEquals('active', open['title'], 'open title incorrect')
-        self.assertEquals('open', open['css_class'], 'open class incorrect')
-        self.assertEquals(False, open['overall_completion'],
-                          "open shouldn't contribute to overall completion")
-        self.assertEquals({'status':
-                           [u'assigned', u'new', u'accepted', u'reopened']},
-                          open['qry_args'], 'qry_args incorrect')
-        self.assertEquals(2, open['count'], 'open count incorrect')
-        self.assertEquals(67, open['percent'], 'open percent incorrect')
+        self.assertEqual('active', open['title'], 'open title incorrect')
+        self.assertEqual('open', open['css_class'], 'open class incorrect')
+        self.assertFalse(open['overall_completion'],
+                         "open shouldn't contribute to overall completion")
+        self.assertEqual({'status':
+                          [u'assigned', u'new', u'accepted', u'reopened']},
+                         open['qry_args'], 'qry_args incorrect')
+        self.assertEqual(2, open['count'], 'open count incorrect')
+        self.assertEqual(67, open['percent'], 'open percent incorrect')
 
 
 def in_tlist(ticket, list):
@@ -129,9 +142,8 @@ def in_tlist(ticket, list):
 
 def suite():
     suite = unittest.TestSuite()
-    suite.addTest(unittest.makeSuite(TicketGroupStatsTestCase, 'test'))
-    suite.addTest(unittest.makeSuite(DefaultTicketGroupStatsProviderTestCase,
-                                      'test'))
+    suite.addTest(unittest.makeSuite(TicketGroupStatsTestCase))
+    suite.addTest(unittest.makeSuite(DefaultTicketGroupStatsProviderTestCase))
     return suite
 
 if __name__ == '__main__':

Modified: bloodhound/vendor/trac/current/trac/ticket/tests/wikisyntax.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/tests/wikisyntax.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/ticket/tests/wikisyntax.py (original)
+++ bloodhound/vendor/trac/current/trac/ticket/tests/wikisyntax.py Thu Feb 13 05:08:02 2014
@@ -1,11 +1,28 @@
 # -*- coding: utf-8 -*-
+#
+# Copyright (C) 2006-2013 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.org/wiki/TracLicense.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
 
 import unittest
+from datetime import datetime, timedelta
 
-from trac.ticket.model import Ticket
-from trac.ticket.roadmap import Milestone
+from trac.test import locale_en
+from trac.ticket.query import QueryModule
+from trac.ticket.report import ReportModule
+from trac.ticket.roadmap import RoadmapModule
+from trac.ticket.model import Milestone, Ticket
+from trac.util.datefmt import format_datetime, pretty_timedelta, utc
 from trac.wiki.tests import formatter
 
+
 TICKET_TEST_CASES = u"""
 ============================== ticket: link resolver
 ticket:1
@@ -109,6 +126,9 @@ trac:#2041
 """ # "
 
 def ticket_setup(tc):
+    config = tc.env.config
+    config.set('ticket-custom', 'custom1', 'text')
+    config.save()
     ticket = Ticket(tc.env)
     ticket.values.update({'reporter': 'santa',
                           'summary': 'This is the summary',
@@ -116,6 +136,9 @@ def ticket_setup(tc):
     ticket.insert()
 
 def ticket_teardown(tc):
+    config = tc.env.config
+    config.remove('ticket-custom', 'custom1')
+    config.save()
     tc.env.reset_db()
 
 
@@ -127,7 +150,7 @@ REPORT_TEST_CASES = u"""
 ------------------------------
 <p>
 <a class="report" href="/report/1">{1}</a>, <a class="report" href="/report/2">{2}</a>
-<a class="report" href="/report/12">{12}</a>, {abc}
+<a class="missing report" title="report does not exist">{12}</a>, {abc}
 </p>
 ------------------------------
 ============================== escaping the above
@@ -175,7 +198,16 @@ trac:report:1
 """ # '
 
 def report_setup(tc):
-    pass # TBD
+    def create_report(tc, id):
+        tc.env.db_transaction("""
+            INSERT INTO report (id,title,query,description)
+            VALUES (%s,%s,'SELECT 1','')""", (id, 'Report %s' % id))
+    create_report(tc, 1)
+    create_report(tc, 2)
+
+
+dt_past = datetime.now(utc) - timedelta(days=1)
+dt_future = datetime.now(utc) + timedelta(days=1)
 
 
 MILESTONE_TEST_CASES = u"""
@@ -183,11 +215,15 @@ MILESTONE_TEST_CASES = u"""
 milestone:foo
 [milestone:boo Milestone Boo]
 [milestone:roo Milestone Roo]
+[milestone:woo Milestone Woo]
+[milestone:zoo Milestone Zoo]
 ------------------------------
 <p>
 <a class="missing milestone" href="/milestone/foo" rel="nofollow">milestone:foo</a>
-<a class="milestone" href="/milestone/boo">Milestone Boo</a>
-<a class="closed milestone" href="/milestone/roo">Milestone Roo</a>
+<a class="milestone" href="/milestone/boo" title="No date set">Milestone Boo</a>
+<a class="closed milestone" href="/milestone/roo" title="Completed %(dt_past)s ago (%(datestr_past)s)">Milestone Roo</a>
+<a class="milestone" href="/milestone/woo" title="Due in %(dt_future)s (%(datestr_future)s)">Milestone Woo</a>
+<a class="milestone" href="/milestone/zoo" title="%(dt_past)s late (%(datestr_past)s)">Milestone Zoo</a>
 </p>
 ------------------------------
 ============================== milestone: link resolver + arguments
@@ -196,23 +232,35 @@ milestone:?action=new
 ------------------------------
 <p>
 <a class="missing milestone" href="/milestone/?action=new" rel="nofollow">milestone:?action=new</a>
-<a class="milestone" href="/milestone/boo#KnownIssues">Known Issues for 1.0</a>
+<a class="milestone" href="/milestone/boo#KnownIssues" title="No date set">Known Issues for 1.0</a>
 </p>
 ------------------------------
-""" #"
+""" % {'dt_past': pretty_timedelta(dt_past),
+       'dt_future': pretty_timedelta(dt_future),
+       'datestr_past': format_datetime(dt_past, locale=locale_en, tzinfo=utc),
+       'datestr_future': format_datetime(dt_future, locale=locale_en,
+                                         tzinfo=utc)} #"
 
 def milestone_setup(tc):
-    from datetime import datetime
-    from trac.util.datefmt import utc
     boo = Milestone(tc.env)
     boo.name = 'boo'
     boo.completed = boo.due = None
     boo.insert()
     roo = Milestone(tc.env)
     roo.name = 'roo'
-    roo.completed = datetime.now(utc)
+    roo.completed = dt_past
     roo.due = None
     roo.insert()
+    woo = Milestone(tc.env)
+    woo.name = 'woo'
+    woo.completed = None
+    woo.due = dt_future
+    woo.insert()
+    zoo = Milestone(tc.env)
+    zoo.name = 'zoo'
+    zoo.completed = None
+    zoo.due = dt_past
+    zoo.insert()
 
 def milestone_teardown(tc):
     tc.env.reset_db()
@@ -309,6 +357,20 @@ New tickets: [[TicketQuery(status=new, f
 New tickets: <span><a class="new" href="/ticket/1" title="This is the summary">#1</a></span>
 </p>
 ------------------------------
+============================== TicketQuery macro: duplicated fields
+New tickets: [[TicketQuery(status=new, format=compact, col=summary|status|status)]]
+------------------------------
+<p>
+New tickets: <span><a class="new" href="/ticket/1" title="This is the summary">#1</a></span>
+</p>
+------------------------------
+============================== TicketQuery macro: duplicated custom fields
+New tickets: [[TicketQuery(status=new, format=compact, col=summary|custom1|custom1)]]
+------------------------------
+<p>
+New tickets: <span><a class="new" href="/ticket/1" title="This is the summary">#1</a></span>
+</p>
+------------------------------
 """
 
 QUERY2_TEST_CASES = u"""
@@ -353,25 +415,88 @@ def query2_teardown(tc):
 
 COMMENT_TEST_CASES = u"""
 ============================== comment: link resolver (deprecated)
-comment:ticket:123:2 (deprecated)
-[comment:ticket:123:2 see above] (deprecated)
-[comment:ticket:123:description see descr] (deprecated)
-------------------------------
-<p>
-<a href="/ticket/123#comment:2" title="Comment 2 for Ticket #123">comment:ticket:123:2</a> (deprecated)
-<a href="/ticket/123#comment:2" title="Comment 2 for Ticket #123">see above</a> (deprecated)
-<a href="/ticket/123#comment:description" title="Comment description for Ticket #123">see descr</a> (deprecated)
+comment:ticket:1:1 (deprecated)
+[comment:ticket:1:1 see above] (deprecated)
+comment:ticket:1:description (deprecated)
+[comment:ticket:1:description see descr] (deprecated)
+comment:ticket:2:1 (deprecated)
+comment:ticket:2:3 (deprecated)
+comment:ticket:3:1 (deprecated)
+comment:tiket:2:1 (deprecated)
+comment:ticket:two:1 (deprecated)
+comment:ticket:2:1a (deprecated)
+comment:ticket:2:one (deprecated)
+comment:ticket:1: (deprecated)
+comment:ticket::2 (deprecated)
+comment:ticket:: (deprecated)
+------------------------------
+<p>
+<a class="new ticket" href="/ticket/1#comment:1" title="Comment 1 for Ticket #1">comment:ticket:1:1</a> (deprecated)
+<a class="new ticket" href="/ticket/1#comment:1" title="Comment 1 for Ticket #1">see above</a> (deprecated)
+<a class="new ticket" href="/ticket/1#comment:description" title="Description for Ticket #1">comment:ticket:1:description</a> (deprecated)
+<a class="new ticket" href="/ticket/1#comment:description" title="Description for Ticket #1">see descr</a> (deprecated)
+<a class="ticket" href="/ticket/2#comment:1" title="Comment 1">comment:ticket:2:1</a> (deprecated)
+<a class="missing ticket" title="ticket comment does not exist">comment:ticket:2:3</a> (deprecated)
+<a class="missing ticket" title="ticket does not exist">comment:ticket:3:1</a> (deprecated)
+comment:tiket:2:1 (deprecated)
+comment:ticket:two:1 (deprecated)
+comment:ticket:2:1a (deprecated)
+comment:ticket:2:one (deprecated)
+comment:ticket:1: (deprecated)
+comment:ticket::2 (deprecated)
+comment:ticket:: (deprecated)
 </p>
 ------------------------------
 ============================== comment: link resolver
-comment:2:ticket:123
-[comment:2:ticket:123 see above]
-[comment:description:ticket:123 see descr]
-------------------------------
-<p>
-<a href="/ticket/123#comment:2" title="Comment 2 for Ticket #123">comment:2:ticket:123</a>
-<a href="/ticket/123#comment:2" title="Comment 2 for Ticket #123">see above</a>
-<a href="/ticket/123#comment:description" title="Comment description for Ticket #123">see descr</a>
+comment:1
+[comment:1 see above]
+comment:description
+[comment:description see descr]
+comment:
+comment:one
+comment:1a
+------------------------------
+<p>
+<a class="ticket" href="/ticket/2#comment:1" title="Comment 1">comment:1</a>
+<a class="ticket" href="/ticket/2#comment:1" title="Comment 1">see above</a>
+<a class="ticket" href="/ticket/2#comment:description" title="Description">comment:description</a>
+<a class="ticket" href="/ticket/2#comment:description" title="Description">see descr</a>
+comment:
+comment:one
+comment:1a
+</p>
+------------------------------
+============================== comment: link resolver with ticket number
+comment:1:ticket:1
+[comment:1:ticket:1 see above]
+comment:description:ticket:1
+[comment:description:ticket:1 see descr]
+comment:1:ticket:2
+comment:3:ticket:2
+comment:1:ticket:3
+comment:2:tiket:1
+comment:1:ticket:two
+comment:one:ticket:1
+comment:1a:ticket:1
+comment:ticket:1
+comment:2:ticket:
+comment::ticket:
+------------------------------
+<p>
+<a class="new ticket" href="/ticket/1#comment:1" title="Comment 1 for Ticket #1">comment:1:ticket:1</a>
+<a class="new ticket" href="/ticket/1#comment:1" title="Comment 1 for Ticket #1">see above</a>
+<a class="new ticket" href="/ticket/1#comment:description" title="Description for Ticket #1">comment:description:ticket:1</a>
+<a class="new ticket" href="/ticket/1#comment:description" title="Description for Ticket #1">see descr</a>
+<a class="ticket" href="/ticket/2#comment:1" title="Comment 1">comment:1:ticket:2</a>
+<a class="missing ticket" title="ticket comment does not exist">comment:3:ticket:2</a>
+<a class="missing ticket" title="ticket does not exist">comment:1:ticket:3</a>
+comment:2:tiket:1
+comment:1:ticket:two
+comment:one:ticket:1
+comment:1a:ticket:1
+comment:ticket:1
+comment:2:ticket:
+comment::ticket:
 </p>
 ------------------------------
 """ # "
@@ -385,6 +510,24 @@ comment:2:ticket:123
 # As it's a problem with a temp workaround, I think there's no need
 # to fix it for now.
 
+def comment_setup(tc):
+    ticket1 = Ticket(tc.env)
+    ticket1.values.update({'reporter': 'santa',
+                            'summary': 'This is the summary for ticket 1',
+                            'status': 'new'})
+    ticket1.insert()
+    ticket1.save_changes(comment='This is the comment for ticket 1')
+    ticket2 = Ticket(tc.env)
+    ticket2.values.update({'reporter': 'claws',
+                           'summary': 'This is the summary for ticket 2',
+                           'status': 'closed'})
+    ticket2.insert()
+    ticket2.save_changes(comment='This is the comment for ticket 2')
+
+def comment_teardown(tc):
+    tc.env.reset_db()
+
+
 def suite():
     suite = unittest.TestSuite()
     suite.addTest(formatter.suite(TICKET_TEST_CASES, ticket_setup, __file__,
@@ -396,9 +539,9 @@ def suite():
                                   ticket_teardown))
     suite.addTest(formatter.suite(QUERY2_TEST_CASES, query2_setup, __file__,
                                   query2_teardown))
-    suite.addTest(formatter.suite(COMMENT_TEST_CASES, file=__file__))
+    suite.addTest(formatter.suite(COMMENT_TEST_CASES, comment_setup, __file__,
+                                  comment_teardown, ('ticket', 2)))
     return suite
 
 if __name__ == '__main__':
     unittest.main(defaultTest='suite')
-

Modified: bloodhound/vendor/trac/current/trac/ticket/web_ui.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/web_ui.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/ticket/web_ui.py (original)
+++ bloodhound/vendor/trac/current/trac/ticket/web_ui.py Thu Feb 13 05:08:02 2014
@@ -42,6 +42,7 @@ from trac.util import as_bool, as_int, g
 from trac.util.datefmt import (
     format_datetime, from_utimestamp, to_utimestamp, utc
 )
+from trac.util.html import to_fragment
 from trac.util.text import (
     exception_to_unicode, empty, obfuscate_email_address, shorten_line,
     to_unicode
@@ -77,12 +78,14 @@ class TicketModule(Component):
         open / close operations (''since 0.9'').""")
 
     max_description_size = IntOption('ticket', 'max_description_size', 262144,
-        """Don't accept tickets with a too big description.
-        (''since 0.11'').""")
+        """Maximum allowed description size in characters.
+        (//since 0.11//).""")
 
     max_comment_size = IntOption('ticket', 'max_comment_size', 262144,
-        """Don't accept tickets with a too big comment.
-        (''since 0.11.2'')""")
+        """Maximum allowed comment size in characters. (//since 0.11.2//).""")
+
+    max_summary_size = IntOption('ticket', 'max_summary_size', 262144,
+        """Maximum allowed summary size in characters. (//since 1.0.2//).""")
 
     timeline_newticket_formatter = Option('timeline', 'newticket_formatter',
                                           'oneliner',
@@ -296,7 +299,7 @@ class TicketModule(Component):
                     FROM ticket_change tc
                         INNER JOIN ticket t ON t.id = tc.ticket
                             AND tc.time>=%s AND tc.time<=%s
-                    ORDER BY tc.time
+                    ORDER BY tc.time, tc.ticket
                     """ % (ts_start, ts_stop)):
                 if not (oldvalue or newvalue):
                     # ignore empty change corresponding to custom field
@@ -307,7 +310,7 @@ class TicketModule(Component):
                         ev = produce_event(data, status, fields, comment,
                                            cid)
                         if ev:
-                             yield (ev, data[1])
+                            yield (ev, data[1])
                     status, fields, comment, cid = 'edit', {}, '', None
                     data = (id, t, author, type, summary, None)
                 if field == 'comment':
@@ -1263,6 +1266,13 @@ class TicketModule(Component):
                                num=self.max_comment_size))
             valid = False
 
+        # Validate summary length
+        if len(ticket['summary']) > self.max_summary_size:
+            add_warning(req, _("Ticket summary is too long (must be less "
+                               "than %(num)s characters)",
+                               num=self.max_summary_size))
+            valid = False
+
         # Validate comment numbering
         try:
             # replyto must be 'description' or a number
@@ -1295,9 +1305,9 @@ class TicketModule(Component):
         except Exception, e:
             self.log.error("Failure sending notification on creation of "
                     "ticket #%s: %s", ticket.id, exception_to_unicode(e))
-            add_warning(req, _("The ticket has been created, but an error "
-                               "occurred while sending notifications: "
-                               "%(message)s", message=to_unicode(e)))
+            add_warning(req, tag_("The ticket has been created, but an error "
+                                  "occurred while sending notifications: "
+                                  "%(message)s", message=to_fragment(e)))
 
         # Redirect the user to the newly created ticket or add attachment
         ticketref=tag.a('#', ticket.id, href=req.href.ticket(ticket.id))
@@ -1341,7 +1351,7 @@ class TicketModule(Component):
                 add_warning(req, tag_("The %(change)s has been saved, but an "
                                       "error occurred while sending "
                                       "notifications: %(message)s",
-                                      change=change, message=to_unicode(e)))
+                                      change=change, message=to_fragment(e)))
                 fragment = ''
 
         # After saving the changes, apply the side-effects.
@@ -1401,6 +1411,9 @@ class TicketModule(Component):
 
     def _query_link(self, req, name, value, text=None):
         """Return a link to /query with the appropriate name and value"""
+        from trac.ticket.query import QueryModule
+        if not self.env.is_component_enabled(QueryModule):
+            return text or value
         default_query = self.ticketlink_query.lstrip('?')
         args = arg_list_to_args(parse_arg_list(default_query))
         args[name] = value
@@ -1410,7 +1423,9 @@ class TicketModule(Component):
 
     def _query_link_words(self, context, name, value):
         """Splits a list of words and makes a query link to each separately"""
-        if not isinstance(value, basestring): # None or other non-splitable
+        from trac.ticket.query import QueryModule
+        if not (isinstance(value, basestring) and  # None or other non-splitable
+                self.env.is_component_enabled(QueryModule)):
             return value
         default_query = self.ticketlink_query.startswith('?') and \
                         self.ticketlink_query[1:] or self.ticketlink_query

Modified: bloodhound/vendor/trac/current/trac/timeline/__init__.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/timeline/__init__.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/timeline/__init__.py (original)
+++ bloodhound/vendor/trac/current/trac/timeline/__init__.py Thu Feb 13 05:08:02 2014
@@ -1 +1,14 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2006-2013 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.org/wiki/TracLicense.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
+
 from trac.timeline.api import *

Modified: bloodhound/vendor/trac/current/trac/timeline/api.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/timeline/api.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/timeline/api.py (original)
+++ bloodhound/vendor/trac/current/trac/timeline/api.py Thu Feb 13 05:08:02 2014
@@ -69,5 +69,3 @@ class ITimelineEventProvider(Interface):
                       the 'url'
         :param event: the event tuple, as returned by `get_timeline_events`
         """
-
-

Modified: bloodhound/vendor/trac/current/trac/timeline/templates/timeline.rss
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/timeline/templates/timeline.rss?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/timeline/templates/timeline.rss (original)
+++ bloodhound/vendor/trac/current/trac/timeline/templates/timeline.rss Thu Feb 13 05:08:02 2014
@@ -6,9 +6,7 @@
     <title>${project.name}</title>
     <link>${abs_href.timeline()}</link>
     <description>Trac Timeline</description>
-    <language>${req.locale and \
-                '%s-%s' % (req.locale.language, req.locale.territory) or \
-                'en-US'}</language>
+    <language>${str(locale).replace('_', '-') if locale else 'en-US'}</language>
     <generator>Trac ${trac.version}</generator>
     <image py:if="chrome.logo.src_abs">
       <title>${project.name}</title>

Modified: bloodhound/vendor/trac/current/trac/timeline/tests/__init__.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/timeline/tests/__init__.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/timeline/tests/__init__.py (original)
+++ bloodhound/vendor/trac/current/trac/timeline/tests/__init__.py Thu Feb 13 05:08:02 2014
@@ -1 +1,28 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2013 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.org/wiki/TracLicense.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
+
+import unittest
+
+from trac.timeline.tests import web_ui
+from trac.timeline.tests import wikisyntax
 from trac.timeline.tests.functional import functionalSuite
+
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(web_ui.suite())
+    suite.addTest(wikisyntax.suite())
+    return suite
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')

Modified: bloodhound/vendor/trac/current/trac/timeline/tests/functional.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/timeline/tests/functional.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/timeline/tests/functional.py (original)
+++ bloodhound/vendor/trac/current/trac/timeline/tests/functional.py Thu Feb 13 05:08:02 2014
@@ -1,4 +1,17 @@
-#!/usr/bin/python
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2013 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.org/wiki/TracLicense.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
+
 from trac.tests.functional import *
 
 
@@ -29,8 +42,8 @@ class RegressionTestRev5883(FunctionalTw
 
 def functionalSuite(suite=None):
     if not suite:
-        import trac.tests.functional.testcases
-        suite = trac.tests.functional.testcases.functionalSuite()
+        import trac.tests.functional
+        suite = trac.tests.functional.functionalSuite()
     suite.addTest(RegressionTestRev5883())
     return suite