Merge lp:~yellow/subunit/test-count into lp:~subunit/subunit/trunk

Proposed by Gary Poster
Status: Rejected
Rejected by: Robert Collins
Proposed branch: lp:~yellow/subunit/test-count
Merge into: lp:~subunit/subunit/trunk
Diff against target: 101 lines (+15/-10)
2 files modified
python/subunit/ (+12/-0)
python/subunit/tests/ (+3/-10)
To merge this branch: bzr merge lp:~yellow/subunit/test-count
Reviewer Review Type Date Requested Status
Jonathan Lange Disapprove
Review via email:

Description of the change

This branch changes the semantics of testsRun in a TestResultFilter to count the total number of tests run, not the number of tests filtered.

Whether these semantics are correct is up for debate. We're obviously proposing that they are. That said, these changes primarily exist to make the testrepository change in lp:~yellow/testrepository/bug988481 cleaner. We don't know how to continue to subclass in testrepository.

This is work done by Benji York and Brad Crittenden; I'm just helping out with the MP.

To post a comment you must log in.
Revision history for this message
Jonathan Lange (jml) wrote :

I'm not 100% sure about this change, but I can't think of any actual use case for which this would be a regression.

review: Approve
Revision history for this message
Jonathan Lange (jml) wrote :

Wrong MP sorry.

review: Abstain
Revision history for this message
Jonathan Lange (jml) wrote :

Hey guys,

Thanks for looking into this one. I don't think that this change should go into subunit. The _get_concrete_result thing is just too hideous. I wrote it originally so I hope you aren't offended by me saying so.

What testrepository needs is a way to increment testsRun on its CLITestResult instance even when tests are filtered out of the stream. The right way to do this is to pass that result into TestResultFilter somehow. Here are some options:

  class MyTestResultFilter(TestResultFilter):

    def __init__(self, result, on_filter, **kwargs):
      super(MyTestResultFilter, self).__init__(result, **kwargs)
      self._on_filter = on_filter

    def _filtered(self):
      super(MyTestResultFilter, self)._filtered()

Or you could make the last line:


Instead of writing that as a subclass, it could be a patch to TestResultFilter in subunit. In which case it would have to be an optional argument. Then you could call TestResultFilter(cli_test_result, lambda test: cli_test_result.testsRun += 1) in testrepository. (Except that won't work because of Python's statement / expression hang up.)

Alternatively, you could make a more specific version and have it live in testrepository:

  class MyTestResultFilter(TestResultFilter):

    def __init__(self, result, cli_test_result, **kwargs):
      super(MyTestResultFilter, self).__init__(result, **kwargs)
      self._cli_test_result = cli_test_result

    def _filtered(self):
      super(MyTestResultFilter, self)._filtered()
      self._cli_test_result.testsRun += 1

I'm sorry to bounce back so hard on this, but the getattr() searching is really quite fragile.


review: Disapprove
Revision history for this message
Gary Poster (gary) wrote :

Hi Jono.

While this change is stand-alone from subunit's perspective, we actually were working with .

Without the filter-tags branch, is a sufficient change to get the testrepository tests to pass with subunit's trunk (after your already-completed merge of

However, with the filter-tags branch and the testrepository patch above, we still need to address additional failures: . Those are the failures that this branch tried to fix.

The _PredicateFilter refactoring in the filter-tags branch makes it harder to come up with a solution. The _filtered override changes you suggested will break just as much as the existing _filtered method override does. If instead we override addFailure, addError, and addSkip to increment testsRun there, we double count tests that matched the filter.

I think the best approach will be a subunit change like the one you gave...except that I think the argument will need to be something on the order of "on_filter," because we need to increment testsRun manually only when a test is filtered out in order to get the behavior testrepository desires. I'll make a new branch that does that.



Revision history for this message
Robert Collins (lifeless) wrote :

So, I'm going to disagree with this patch from another angle: its
inappropriate coupling.

TestRepository can just use a Y join to get a separate count vs
filtered tests. (And BTW, the *filtered* count there is appropriate
for UI consumption, so I don't see how this would help with it

Revision history for this message
Jonathan Lange (jml) wrote :

The filtered count (i.e. the number of tests that we display in the output) might be appropriate for UI consumption (e.g. 7 failing tests), but the UI also needs to have the unfiltered count (e.g. 100 tests run).

The UI layer can use a Y join, I guess. I can't fully connect the dots in my head but I can see how it might work.

Revision history for this message
Robert Collins (lifeless) wrote :

To join the dots, see my whiteboard image on the list.

Unmerged revisions

164. By Benji York

make filtered test results report the total number of tests

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'python/subunit/'
2--- python/subunit/ 2012-04-20 11:32:41 +0000
3+++ python/subunit/ 2012-04-26 15:20:26 +0000
4@@ -407,8 +407,20 @@
5 self._buffered_calls.append(
6 ('addUnexpectedSuccess', [test], {'details': details}))
8+ def _get_concrete_result(self):
9+ # This assumes that the test result we actually care about is
10+ # decorated and that we can find our way to the one we care about.
11+ concrete = self
12+ while True:
13+ next = getattr(concrete, 'decorated', None)
14+ if next is None:
15+ return concrete
16+ concrete = next
18 def _filtered(self):
19 self._current_test_filtered = True
20+ concrete = self._get_concrete_result()
21+ concrete.testsRun += 1
23 def _failure_expected(self, test):
24 return ( in self._fixup_expected_failures)
26=== modified file 'python/subunit/tests/'
27--- python/subunit/tests/ 2011-05-09 21:00:42 +0000
28+++ python/subunit/tests/ 2012-04-26 15:20:26 +0000
29@@ -75,7 +75,6 @@
30 self.assertEqual(['failed'],
31 [failure[0].id() for failure in
32 filtered_result.failures])
33- self.assertEqual(4, filtered_result.testsRun)
35 def test_exclude_errors(self):
36 filtered_result = unittest.TestResult()
37@@ -86,7 +85,6 @@
38 self.assertEqual(['failed'],
39 [failure[0].id() for failure in
40 filtered_result.failures])
41- self.assertEqual(3, filtered_result.testsRun)
43 def test_fixup_expected_failures(self):
44 filtered_result = unittest.TestResult()
45@@ -96,7 +94,6 @@
46 self.assertEqual(['failed', 'todo'],
47 [failure[0].id() for failure in filtered_result.expectedFailures])
48 self.assertEqual([], filtered_result.failures)
49- self.assertEqual(4, filtered_result.testsRun)
51 def test_fixup_expected_errors(self):
52 filtered_result = unittest.TestResult()
53@@ -106,7 +103,6 @@
54 self.assertEqual(['error', 'todo'],
55 [failure[0].id() for failure in filtered_result.expectedFailures])
56 self.assertEqual([], filtered_result.errors)
57- self.assertEqual(4, filtered_result.testsRun)
59 def test_fixup_unexpected_success(self):
60 filtered_result = unittest.TestResult()
61@@ -115,7 +111,6 @@
62 self.run_tests(result_filter)
63 self.assertEqual(['passed'],
64 [ for passed in filtered_result.unexpectedSuccesses])
65- self.assertEqual(5, filtered_result.testsRun)
67 def test_exclude_failure(self):
68 filtered_result = unittest.TestResult()
69@@ -126,7 +121,6 @@
70 self.assertEqual([],
71 [failure[0].id() for failure in
72 filtered_result.failures])
73- self.assertEqual(3, filtered_result.testsRun)
75 def test_exclude_skips(self):
76 filtered_result = subunit.TestResultStats(None)
77@@ -134,7 +128,6 @@
78 self.run_tests(result_filter)
79 self.assertEqual(0, filtered_result.skipped_tests)
80 self.assertEqual(2, filtered_result.failed_tests)
81- self.assertEqual(3, filtered_result.testsRun)
83 def test_include_success(self):
84 """Successes can be included if requested."""
85@@ -153,13 +146,13 @@
86 """You can filter by predicate callbacks"""
87 filtered_result = unittest.TestResult()
88 def filter_cb(test, outcome, err, details):
89- return outcome == 'success'
90+ return outcome == 'error'
91 result_filter = TestResultFilter(filtered_result,
92 filter_predicate=filter_cb,
93 filter_success=False)
94 self.run_tests(result_filter)
95- # Only success should pass
96- self.assertEqual(1, filtered_result.testsRun)
97+ # Only errors should pass
98+ self.assertEqual(1, len(filtered_result.errors))
100 def test_time_ordering_preserved(self):
101 # Passing a subunit stream through TestResultFilter preserves the


People subscribed via source and target branches