Index: htdocs/css/roadmap.css
===================================================================
--- htdocs/css/roadmap.css	(revision 2670)
+++ htdocs/css/roadmap.css	(working copy)
@@ -9,6 +9,7 @@
 }
 div.progress :link:hover, div.progress :visited:hover { background: #fff }
 div.progress .closed:link, div.progress .closed:visited { background: #bae0ba }
+div.progress .testing:link, div.progress .testing:visited { background: #fff788 }
 p.percent { font-size: 10px; line-height: 2.4em; margin: 0.9em 0 0 }
 
 /* Styles for the roadmap view */
Index: trac/db_default.py
===================================================================
--- trac/db_default.py	(revision 2670)
+++ trac/db_default.py	(working copy)
@@ -344,6 +344,7 @@
                 ('status', 'assigned', 2),
                 ('status', 'reopened', 3),
                 ('status', 'closed', 4),
+                ('status', 'testing', 5),
                 ('resolution', 'fixed', 1),
                 ('resolution', 'invalid', 2),
                 ('resolution', 'wontfix', 3),
Index: trac/ticket/api.py
===================================================================
--- trac/ticket/api.py	(revision 2670)
+++ trac/ticket/api.py	(working copy)
@@ -29,12 +29,14 @@
     def get_available_actions(self, ticket, perm_):
         """Returns the actions that can be performed on the ticket."""
         actions = {
-            'new':      ['leave', 'resolve', 'reassign', 'accept'],
-            'assigned': ['leave', 'resolve', 'reassign'          ],
-            'reopened': ['leave', 'resolve', 'reassign'          ],
-            'closed':   ['leave',                        'reopen']
+            'new':      ['leave', 'testing', 'resolve', 'reassign', 'accept'],
+            'assigned': ['leave', 'testing', 'resolve', 'reassign'          ],
+            'reopened': ['leave', 'testing', 'resolve', 'reassign'          ],
+            'testing':  ['leave',            'resolve', 'reassign', 'reopen'],
+            'closed':   ['leave',                                   'reopen']
         }
         perms = {'resolve': 'TICKET_MODIFY', 'reassign': 'TICKET_CHGPROP',
+                 'testing': 'TICKET_CHGPROP',
                  'accept': 'TICKET_CHGPROP', 'reopen': 'TICKET_CREATE'}
         return [action for action in actions.get(ticket['status'], ['leave'])
                 if action not in perms or perm_.has_permission(perms[action])]
Index: trac/ticket/web_ui.py
===================================================================
--- trac/ticket/web_ui.py	(revision 2670)
+++ trac/ticket/web_ui.py	(working copy)
@@ -368,6 +368,9 @@
         elif action == 'reopen':
             ticket['status'] = 'reopened'
             ticket['resolution'] = ''
+        elif action == 'testing':
+            ticket['status'] = 'testing'
+            ticket['resolution'] = ''
 
         now = int(time.time())
         ticket.save_changes(req.args.get('author', req.authname),
Index: trac/ticket/tests/api.py
===================================================================
--- trac/ticket/tests/api.py	(revision 2670)
+++ trac/ticket/tests/api.py	(working copy)
@@ -55,11 +55,11 @@
     def test_available_actions_full_perms(self):
         ts = TicketSystem(self.env)
         perm = Mock(has_permission=lambda x: 1)
-        self.assertEqual(['leave', 'resolve', 'reassign', 'accept'],
+        self.assertEqual(['leave', 'testing', 'resolve', 'reassign', 'accept'],
                          ts.get_available_actions({'status': 'new'}, perm))
-        self.assertEqual(['leave', 'resolve', 'reassign'],
+        self.assertEqual(['leave', 'testing', 'resolve', 'reassign'],
                          ts.get_available_actions({'status': 'assigned'}, perm))
-        self.assertEqual(['leave', 'resolve', 'reassign'],
+        self.assertEqual(['leave', 'testing', 'resolve', 'reassign'],
                          ts.get_available_actions({'status': 'reopened'}, perm))
         self.assertEqual(['leave', 'reopen'],
                          ts.get_available_actions({'status': 'closed'}, perm))
@@ -91,11 +91,11 @@
     def test_available_actions_chgprop_only(self):
         ts = TicketSystem(self.env)
         perm = Mock(has_permission=lambda x: x == 'TICKET_CHGPROP')
-        self.assertEqual(['leave', 'reassign', 'accept'],
+        self.assertEqual(['leave', 'testing', 'reassign', 'accept'],
                          ts.get_available_actions({'status': 'new'}, perm))
-        self.assertEqual(['leave', 'reassign'],
+        self.assertEqual(['leave', 'testing', 'reassign'],
                          ts.get_available_actions({'status': 'assigned'}, perm))
-        self.assertEqual(['leave', 'reassign'],
+        self.assertEqual(['leave', 'testing', 'reassign'],
                          ts.get_available_actions({'status': 'reopened'}, perm))
         self.assertEqual(['leave'],
                          ts.get_available_actions({'status': 'closed'}, perm))
Index: trac/ticket/roadmap.py
===================================================================
--- trac/ticket/roadmap.py	(revision 2670)
+++ trac/ticket/roadmap.py	(working copy)
@@ -49,6 +49,8 @@
         q['all_tickets'] = env.href.query(milestone=milestone)
         q['active_tickets'] = env.href.query(milestone=milestone,
                                              status=('new', 'assigned', 'reopened'))
+        q['testing_tickets'] = env.href.query(milestone=milestone,
+                                             status=('testing'))
         q['closed_tickets'] = env.href.query(milestone=milestone, status='closed')
     else:
         q['all_tickets'] = env.href.query({grouped_by: group},
@@ -56,6 +58,9 @@
         q['active_tickets'] = env.href.query({grouped_by: group},
                                              milestone=milestone,
                                              status=('new', 'assigned', 'reopened'))
+        q['testing_tickets'] = env.href.query({grouped_by: group},
+                                             milestone=milestone,
+                                             status=('testing'))
         q['closed_tickets'] = env.href.query({grouped_by: group},
                                              milestone=milestone,
                                              status='closed')
@@ -63,21 +68,27 @@
 
 def calc_ticket_stats(tickets):
     total_cnt = len(tickets)
-    active = [ticket for ticket in tickets if ticket['status'] != 'closed']
+    active = [ticket for ticket in tickets if ticket['status'] != 'closed' and \
+                    ticket['status'] != 'testing' ]
     active_cnt = len(active)
-    closed_cnt = total_cnt - active_cnt
+    testing = [ticket for ticket in tickets if ticket['status'] == 'testing'] 
+    testing_cnt = len(testing)
+    closed_cnt = total_cnt - active_cnt - testing_cnt
 
-    percent_active, percent_closed = 0, 0
+    percent_active, percent_testing, percent_closed = 0, 0, 0
     if total_cnt > 0:
         percent_active = round(float(active_cnt) / float(total_cnt) * 100)
+        percent_testing = round(float(testing_cnt) / float(total_cnt) * 100)
         percent_closed = round(float(closed_cnt) / float(total_cnt) * 100)
-        if percent_active + percent_closed > 100:
+        if percent_active + percent_testing + percent_closed > 100:
             percent_closed -= 1
 
     return {
         'total_tickets': total_cnt,
         'active_tickets': active_cnt,
         'percent_active': percent_active,
+        'testing_tickets': testing_cnt,
+        'percent_testing': percent_testing,
         'closed_tickets': closed_cnt,
         'percent_closed': percent_closed
     }
@@ -200,7 +211,7 @@
             status = ticket['status']
             if status == 'new' or status == 'reopened' and not ticket['owner']:
                 return 'NEEDS-ACTION'
-            elif status == 'assigned' or status == 'reopened':
+            elif status in [ 'assigned', 'reopened', 'testing']:
                 return 'IN-PROCESS'
             elif status == 'closed':
                 if ticket['resolution'] == 'fixed': return 'COMPLETED'
Index: templates/ticket.cs
===================================================================
--- templates/ticket.cs	(revision 2670)
+++ templates/ticket.cs	(working copy)
@@ -248,6 +248,10 @@
    call:action_radio('reopen') ?>
    <label for="reopen">reopen ticket</label><br /><?cs
   /if ?><?cs
+  if:ticket.actions.testing ?><?cs
+   call:action_radio('testing') ?>
+   <label for="testing">ready for testing</label><br /><?cs
+  /if ?><?cs
   if:ticket.actions.resolve ?><?cs
    call:action_radio('resolve') ?>
    <label for="resolve">resolve</label><?cs
Index: templates/roadmap.cs
===================================================================
--- templates/roadmap.cs	(revision 2670)
+++ templates/roadmap.cs	(working copy)
@@ -44,6 +44,12 @@
          var:#stats.closed_tickets ?> of <?cs
          var:#stats.total_tickets ?> ticket<?cs
          if:#stats.total_tickets != #1 ?>s<?cs /if ?> closed"></a>
+       <a class="testing" href="<?cs
+         var:milestone.queries.testing_tickets ?>" style="width: <?cs
+         var:#stats.percent_testing ?>%" title="<?cs
+         var:#stats.testing_tickets ?> of <?Cs
+         var:#stats.total_tickets ?> ticket<?cs
+         if:#stats.total_tickets != #1 ?>s<?cs /if ?> testing"></a>
        <a class="open" href="<?cs
          var:milestone.queries.active_tickets ?>" style="width: <?cs
          var:#stats.percent_active - 1 ?>%" title="<?cs
@@ -56,6 +62,9 @@
        <dt>Closed tickets:</dt>
        <dd><a href="<?cs var:milestone.queries.closed_tickets ?>"><?cs
          var:stats.closed_tickets ?></a></dd>
+       <dt>Testing tickets:</dt>
+       <dd><a href="<?cs var:milestone.queries.testing_tickets ?>"><?cs
+         var:stats.testing_tickets ?></a></dd>
        <dt>Active tickets:</dt>
        <dd><a href="<?cs var:milestone.queries.active_tickets ?>"><?cs
          var:stats.active_tickets ?></a></dd>
Index: templates/milestone.cs
===================================================================
--- templates/milestone.cs	(revision 2670)
+++ templates/milestone.cs	(working copy)
@@ -124,6 +124,12 @@
         var:#stats.closed_tickets ?> of <?cs
         var:#stats.total_tickets ?> ticket<?cs
         if:#stats.total_tickets != #1 ?>s<?cs /if ?> closed"></a>
+       <a class="testing" href="<?cs
+         var:milestone.queries.testing_tickets ?>" style="width: <?cs
+         var:#stats.percent_testing ?>%" title="<?cs
+         var:#stats.testing_tickets ?> of <?Cs
+         var:#stats.total_tickets ?> ticket<?cs
+         if:#stats.total_tickets != #1 ?>s<?cs /if ?> testing"></a>
       <a class="open" href="<?cs
         var:milestone.queries.active_tickets ?>" style="width: <?cs
         var:#stats.percent_active - 1 ?>%" title="<?cs
@@ -136,6 +142,9 @@
       <dt>Closed tickets:</dt>
       <dd><a href="<?cs var:milestone.queries.closed_tickets ?>"><?cs
         var:stats.closed_tickets ?></a></dd>
+      <dt>Testing tickets:</dt>
+      <dd><a href="<?cs var:milestone.queries.testingtickets ?>"><?cs
+        var:stats.testing_tickets ?></a></dd>
       <dt>Active tickets:</dt>
       <dd><a href="<?cs var:milestone.queries.active_tickets ?>"><?cs
         var:stats.active_tickets ?></a></dd>
@@ -170,6 +179,12 @@
           var:group.closed_tickets ?> of <?cs
           var:group.total_tickets ?> ticket<?cs
           if:group.total_tickets != #1 ?>s<?cs /if ?> closed"></a>
+         <a class="testing" href="<?cs
+           var:group.queries.testing_tickets ?>" style="width: <?cs
+           var:#group.percent_testing ?>%" title="<?cs
+          var:group.testing_tickets ?> of <?cs
+          var:group.total_tickets ?> ticket<?cs
+          if:group.total_tickets != #1 ?>s<?cs /if ?> testing"></a>
          <a class="open" href="<?cs
            var:group.queries.active_tickets ?>" style="width: <?cs
            var:#group.percent_active - 1 ?>%" title="<?cs
Index: contrib/trac-post-commit-hook
===================================================================
--- contrib/trac-post-commit-hook	(revision 2670)
+++ contrib/trac-post-commit-hook	(working copy)
@@ -99,6 +99,10 @@
                   help='The user who is responsible for this action')
 parser.add_option('-m', '--msg', dest='msg',
                   help='The log message to search.')
+parser.add_option('-t', '--testing', dest='testing',
+                  action='store_true', default='False',
+                  help='Set a ticket to testing instead of resolved if the '
+                       'commit message indicates it is fixed.')
 parser.add_option('-s', '--siteurl', dest='url',
                   help='The base URL to the project\'s trac website (to which '
                        '/ticket/## is appended).  If this is not specified, '
@@ -151,8 +155,12 @@
         for tkt_id in tickets:
             try:
                 ticket = Ticket(self.env, tkt_id)
-                ticket['status'] = 'closed'
-                ticket['resolution'] = 'fixed'
+                if options.testing:
+                    ticket['status'] = 'testing'
+                    ticket['resolution'] = ''
+                else:
+                    ticket['status'] = 'closed'
+                    ticket['resolution'] = 'fixed'
                 ticket.save_changes(self.author, self.msg, self.now)
                 tn = TicketNotifyEmail(self.env)
                 tn.notify(ticket, newticket=0, modtime=self.now)
