* Tags can now be filtered. (Jonathan Lange, #664171)
authorRobert Collins <robertc@robertcollins.net>
Wed, 2 May 2012 09:45:14 +0000 (21:45 +1200)
committerRobert Collins <robertc@robertcollins.net>
Wed, 2 May 2012 09:45:14 +0000 (21:45 +1200)
1  2 
NEWS
python/subunit/test_results.py
python/subunit/tests/test_subunit_filter.py

diff --cc NEWS
--- 1/NEWS
--- 2/NEWS
+++ b/NEWS
@@@ -34,6 -31,6 +34,8 @@@ BUG FIXE
  * Tag support has been implemented for TestProtocolClient.
    (Robert Collins, #518016)
  
++* Tags can now be filtered. (Jonathan Lange, #664171)
++
  * Test suite works with latest testtools (but not older ones - formatting
    changes only). (Robert Collins)
  
@@@ -447,6 -431,93 +431,95 @@@ class _PredicateFilter(TestResultDecora
          return id
  
  
 -            details) and returning True if the result should be passed
+ class TestResultFilter(TestResultDecorator):
+     """A pyunit TestResult interface implementation which filters tests.
+     Tests that pass the filter are handed on to another TestResult instance
+     for further processing/reporting. To obtain the filtered results,
+     the other instance must be interrogated.
+     :ivar result: The result that tests are passed to after filtering.
+     :ivar filter_predicate: The callback run to decide whether to pass
+         a result.
+     """
+     def __init__(self, result, filter_error=False, filter_failure=False,
+         filter_success=True, filter_skip=False, filter_xfail=False,
+         filter_predicate=None, fixup_expected_failures=None):
+         """Create a FilterResult object filtering to result.
+         :param filter_error: Filter out errors.
+         :param filter_failure: Filter out failures.
+         :param filter_success: Filter out successful tests.
+         :param filter_skip: Filter out skipped tests.
+         :param filter_xfail: Filter out expected failure tests.
+         :param filter_predicate: A callable taking (test, outcome, err,
 -            as 'success' or 'failure'.
++            details, tags) and returning True if the result should be passed
+             through.  err and details may be none if no error or extra
+             metadata is available. outcome is the name of the outcome such
++            as 'success' or 'failure'. tags is new in 0.0.8; 0.0.7 filters
++            are still supported but should be updated to accept the tags
++            parameter for efficiency.
+         :param fixup_expected_failures: Set of test ids to consider known
+             failing.
+         """
+         predicates = []
+         if filter_error:
+             predicates.append(
+                 lambda t, outcome, e, d, tags: outcome != 'error')
+         if filter_failure:
+             predicates.append(
+                 lambda t, outcome, e, d, tags: outcome != 'failure')
+         if filter_success:
+             predicates.append(
+                 lambda t, outcome, e, d, tags: outcome != 'success')
+         if filter_skip:
+             predicates.append(
+                 lambda t, outcome, e, d, tags: outcome != 'skip')
+         if filter_xfail:
+             predicates.append(
+                 lambda t, outcome, e, d, tags: outcome != 'expectedfailure')
+         if filter_predicate is not None:
+             def compat(test, outcome, error, details, tags):
+                 # 0.0.7 and earlier did not support the 'tags' parameter.
+                 try:
+                     return filter_predicate(
+                         test, outcome, error, details, tags)
+                 except TypeError:
+                     return filter_predicate(test, outcome, error, details)
+             predicates.append(compat)
+         predicate = and_predicates(predicates)
+         super(TestResultFilter, self).__init__(
+             _PredicateFilter(result, predicate))
+         if fixup_expected_failures is None:
+             self._fixup_expected_failures = frozenset()
+         else:
+             self._fixup_expected_failures = fixup_expected_failures
+     def addError(self, test, err=None, details=None):
+         if self._failure_expected(test):
+             self.addExpectedFailure(test, err=err, details=details)
+         else:
+             super(TestResultFilter, self).addError(
+                 test, err=err, details=details)
+     def addFailure(self, test, err=None, details=None):
+         if self._failure_expected(test):
+             self.addExpectedFailure(test, err=err, details=details)
+         else:
+             super(TestResultFilter, self).addFailure(
+                 test, err=err, details=details)
+     def addSuccess(self, test, details=None):
+         if self._failure_expected(test):
+             self.addUnexpectedSuccess(test, details=details)
+         else:
+             super(TestResultFilter, self).addSuccess(test, details=details)
+     def _failure_expected(self, test):
+         return (test.id() in self._fixup_expected_failures)
  class TestIdPrintingResult(testtools.TestResult):
  
      def __init__(self, stream, show_times=False):
@@@ -179,11 -230,10 +230,11 @@@ xfail tod
          result_filter = TestResultFilter(result)
          self.run_tests(result_filter, subunit_stream)
          foo = subunit.RemotedTestCase('foo')
 -        self.assertEquals(
 +        self.maxDiff = None
 +        self.assertSequenceEqual(
              [('time', date_a),
-              ('time', date_b),
               ('startTest', foo),
+              ('time', date_b),
               ('addError', foo, {}),
               ('stopTest', foo),
               ('time', date_c)], result._events)