diff options
| author | Robert Collins <robertc@robertcollins.net> | 2009-09-08 22:08:23 +1000 |
|---|---|---|
| committer | Robert Collins <robertc@robertcollins.net> | 2009-09-08 22:08:23 +1000 |
| commit | 3b11398f6c6247545d846c2bb607b6ff526ecbc1 (patch) | |
| tree | 45511636f6568dd6ea0f0279d282665cd456f0cb /python | |
| parent | 56f366ba26b9bd81079f2ce77c851b0324ef28f6 (diff) | |
| parent | 95a5816c1bb8808141416005c5a203bc4d7f8599 (diff) | |
| download | subunit-git-3b11398f6c6247545d846c2bb607b6ff526ecbc1.tar.gz | |
Merge python API changes for tagging.
Diffstat (limited to 'python')
| -rw-r--r-- | python/subunit/__init__.py | 80 | ||||
| -rw-r--r-- | python/subunit/test_results.py | 119 | ||||
| -rw-r--r-- | python/subunit/tests/test_subunit_stats.py | 6 | ||||
| -rw-r--r-- | python/subunit/tests/test_subunit_tags.py | 1 | ||||
| -rw-r--r-- | python/subunit/tests/test_test_protocol.py | 55 |
5 files changed, 189 insertions, 72 deletions
diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 5407f2a..58d345b 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -100,7 +100,6 @@ class TestProtocolServer(object): if stream is None: stream = sys.stdout self._stream = stream - self.tags = set() def _addError(self, offset, line): if (self.state == TestProtocolServer.TEST_STARTED and @@ -246,12 +245,9 @@ class TestProtocolServer(object): """Process a tags command.""" tags = line[offset:].split() new_tags, gone_tags = tags_to_new_gone(tags) - if self.state == TestProtocolServer.OUTSIDE_TEST: - update_tags = self.tags - else: - update_tags = self._current_test.tags - update_tags.update(new_tags) - update_tags.difference_update(gone_tags) + tags_method = getattr(self.client, 'tags', None) + if tags_method is not None: + tags_method(new_tags, gone_tags) def _handleTime(self, offset, line): # Accept it, but do not do anything with it yet. @@ -339,7 +335,6 @@ class TestProtocolServer(object): self._current_test = RemotedTestCase(line[offset:-1]) self.current_test_description = line[offset:-1] self.client.startTest(self._current_test) - self._current_test.tags = set(self.tags) else: self.stdOutLineReceived(line) @@ -765,7 +760,7 @@ class TestResultStats(unittest.TestResult): :ivar total_tests: The total tests seen. :ivar passed_tests: The tests that passed. :ivar failed_tests: The tests that failed. - :ivar tags: The tags seen across all tests. + :ivar seen_tags: The tags seen across all tests. """ def __init__(self, stream): @@ -774,7 +769,7 @@ class TestResultStats(unittest.TestResult): self._stream = stream self.failed_tests = 0 self.skipped_tests = 0 - self.tags = set() + self.seen_tags = set() @property def total_tests(self): @@ -794,16 +789,16 @@ class TestResultStats(unittest.TestResult): self._stream.write("Passed tests: %5d\n" % self.passed_tests) self._stream.write("Failed tests: %5d\n" % self.failed_tests) self._stream.write("Skipped tests: %5d\n" % self.skipped_tests) - tags = sorted(self.tags) - self._stream.write("Tags: %s\n" % (", ".join(tags))) + tags = sorted(self.seen_tags) + self._stream.write("Seen tags: %s\n" % (", ".join(tags))) @property def passed_tests(self): return self.total_tests - self.failed_tests - self.skipped_tests - def stopTest(self, test): - unittest.TestResult.stopTest(self, test) - self.tags.update(test.tags) + def tags(self, new_tags, gone_tags): + """Accumulate the seen tags.""" + self.seen_tags.update(new_tags) def wasSuccessful(self): """Tells whether or not this result was a success""" @@ -844,18 +839,22 @@ class TestResultFilter(unittest.TestResult): if filter_predicate is None: filter_predicate = lambda test, err: True self.filter_predicate = filter_predicate + # The current test (for filtering tags) + self._current_test = None + # Has the current test been filtered (for outputting test tags) + self._current_test_filtered = None + # The (new, gone) tags for the current test. + self._current_test_tags = None def addError(self, test, err): if not self._filter_error and self.filter_predicate(test, err): self.result.startTest(test) self.result.addError(test, err) - self.result.stopTest(test) def addFailure(self, test, err): if not self._filter_failure and self.filter_predicate(test, err): self.result.startTest(test) self.result.addFailure(test, err) - self.result.stopTest(test) def addSkip(self, test, reason): if not self._filter_skip and self.filter_predicate(test, reason): @@ -867,13 +866,58 @@ class TestResultFilter(unittest.TestResult): self.result.addError(test, RemoteError(reason)) else: self.result.addSkip(test, reason) - self.result.stopTest(test) def addSuccess(self, test): if not self._filter_success and self.filter_predicate(test, None): self.result.startTest(test) self.result.addSuccess(test) + + def startTest(self, test): + """Start a test. + + Not directly passed to the client, but used for handling of tags + correctly. + """ + self._current_test = test + self._current_test_filtered = False + self._current_test_tags = set(), set() + + def stopTest(self, test): + """Stop a test. + + Not directly passed to the client, but used for handling of tags + correctly. + """ + if not self._current_test_filtered: + # Tags to output for this test. + if self._current_test_tags[0] or self._current_test_tags[1]: + tags_method = getattr(self.result, 'tags', None) + if callable(tags_method): + self.result.tags(*self._current_test_tags) self.result.stopTest(test) + self._current_test = None + self._current_test_filtered = None + self._current_test_tags = None + + def tags(self, new_tags, gone_tags): + """Handle tag instructions. + + Adds and removes tags as appropriate. If a test is currently running, + tags are not affected for subsequent tests. + + :param new_tags: Tags to add, + :param gone_tags: Tags to remove. + """ + if self._current_test is not None: + # gather the tags until the test stops. + self._current_test_tags[0].update(new_tags) + self._current_test_tags[0].difference_update(gone_tags) + self._current_test_tags[1].update(gone_tags) + self._current_test_tags[1].difference_update(new_tags) + tags_method = getattr(self.result, 'tags', None) + if tags_method is None: + return + return tags_method(new_tags, gone_tags) def id_to_orig_id(self, id): if id.startswith("subunit.RemotedTestCase."): diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index a5811aa..8f653ef 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -23,13 +23,23 @@ import datetime import iso8601 -class HookedTestResultDecorator(object): - """A TestResult which calls a hook on every event.""" + +# NOT a TestResult, because we are implementing the interface, not inheriting +# it. +class TestResultDecorator(object): + """General pass-through decorator. + + This provides a base that other TestResults can inherit from to + gain basic forwarding functionality. It also takes care of + handling the case where the target doesn't support newer methods + or features by degrading them. + """ def __init__(self, decorated): + """Create a TestResultDecorator forwarding to decorated.""" self.decorated = decorated - def _call_maybe(self, method_name, *params): + def _call_maybe(self, method_name, fallback, *params): """Call method_name on self.decorated, if present. This is used to guard newer methods which older pythons do not @@ -38,75 +48,139 @@ class HookedTestResultDecorator(object): the one to filter them out. :param method_name: The name of the method to call. + :param fallback: If not None, the fallback to call to handle downgrading + this method. Otherwise when method_name is not available, no + exception is raised and None is returned. :param *params: Parameters to pass to method_name. :return: The result of self.decorated.method_name(*params), if it exists, and None otherwise. """ method = getattr(self.decorated, method_name, None) if method is None: + if fallback is not None: + return fallback(*params) return return method(*params) def startTest(self, test): - self._before_event() return self.decorated.startTest(test) def startTestRun(self): + return self._call_maybe("startTestRun", None) + + def stopTest(self, test): + return self.decorated.stopTest(test) + + def stopTestRun(self): + return self._call_maybe("stopTestRun", None) + + def addError(self, test, err): + return self.decorated.addError(test, err) + + def addFailure(self, test, err): + return self.decorated.addFailure(test, err) + + def addSuccess(self, test): + return self.decorated.addSuccess(test) + + def addSkip(self, test, reason): + return self._call_maybe("addSkip", self._degrade_skip, test, reason) + + def _degrade_skip(self, test, reason): + return self.decorated.addSuccess(test) + + def addExpectedFailure(self, test, err): + return self._call_maybe("addExpectedFailure", + self.decorated.addFailure, test, err) + + def addUnexpectedSuccess(self, test): + return self._call_maybe("addUnexpectedSuccess", + self.decorated.addSuccess, test) + + def progress(self, offset, whence): + return self._call_maybe("progress", None, offset, whence) + + def wasSuccessful(self): + return self.decorated.wasSuccessful() + + @property + def shouldStop(self): + return self.decorated.shouldStop + + def stop(self): + return self.decorated.stop() + + def time(self, a_datetime): + return self._call_maybe("time", None, a_datetime) + + +class HookedTestResultDecorator(TestResultDecorator): + """A TestResult which calls a hook on every event.""" + + def __init__(self, decorated): + self.super = super(HookedTestResultDecorator, self) + self.super.__init__(decorated) + + def startTest(self, test): self._before_event() - return self._call_maybe("startTestRun") + return self.super.startTest(test) + + def startTestRun(self): + self._before_event() + return self.super.startTestRun() def stopTest(self, test): self._before_event() - return self.decorated.stopTest(test) + return self.super.stopTest(test) def stopTestRun(self): self._before_event() - return self._call_maybe("stopTestRun") + return self.super.stopTestRun() def addError(self, test, err): self._before_event() - return self.decorated.addError(test, err) + return self.super.addError(test, err) def addFailure(self, test, err): self._before_event() - return self.decorated.addFailure(test, err) + return self.super.addFailure(test, err) def addSuccess(self, test): self._before_event() - return self.decorated.addSuccess(test) + return self.super.addSuccess(test) def addSkip(self, test, reason): self._before_event() - return self._call_maybe("addSkip", test, reason) + return self.super.addSkip(test, reason) def addExpectedFailure(self, test, err): self._before_event() - return self._call_maybe("addExpectedFailure", test, err) + return self.super.addExpectedFailure(test, err) def addUnexpectedSuccess(self, test): self._before_event() - return self._call_maybe("addUnexpectedSuccess", test) + return self.super.addUnexpectedSuccess(test) def progress(self, offset, whence): self._before_event() - return self._call_maybe("progress", offset, whence) + return self.super.progress(offset, whence) def wasSuccessful(self): self._before_event() - return self.decorated.wasSuccessful() + return self.super.wasSuccessful() @property def shouldStop(self): self._before_event() - return self.decorated.shouldStop + return self.super.shouldStop def stop(self): self._before_event() - return self.decorated.stop() + return self.super.stop() def time(self, a_datetime): self._before_event() - return self._call_maybe("time", a_datetime) + return self.super.time(a_datetime) class AutoTimingTestResultDecorator(HookedTestResultDecorator): @@ -126,10 +200,10 @@ class AutoTimingTestResultDecorator(HookedTestResultDecorator): if time is not None: return time = datetime.datetime.utcnow().replace(tzinfo=iso8601.Utc()) - self._call_maybe("time", time) + self._call_maybe("time", None, time) def progress(self, offset, whence): - return self._call_maybe("progress", offset, whence) + return self._call_maybe("progress", None, offset, whence) @property def shouldStop(self): @@ -144,7 +218,4 @@ class AutoTimingTestResultDecorator(HookedTestResultDecorator): result object and disable automatic timestamps. """ self._time = a_datetime - return self._call_maybe("time", a_datetime) - - def done(self): - """Transition function until stopTestRun is used.""" + return self._call_maybe("time", None, a_datetime) diff --git a/python/subunit/tests/test_subunit_stats.py b/python/subunit/tests/test_subunit_stats.py index e2f30fc..a8540bb 100644 --- a/python/subunit/tests/test_subunit_stats.py +++ b/python/subunit/tests/test_subunit_stats.py @@ -39,7 +39,7 @@ class TestTestResultStats(unittest.TestCase): self.assertEqual(0, self.result.total_tests) self.assertEqual(0, self.result.passed_tests) self.assertEqual(0, self.result.failed_tests) - self.assertEqual(set(), self.result.tags) + self.assertEqual(set(), self.result.seen_tags) def setUpUsedStream(self): self.input_stream.write("""tags: global @@ -65,7 +65,7 @@ xfail todo self.assertEqual(2, self.result.passed_tests) self.assertEqual(2, self.result.failed_tests) self.assertEqual(1, self.result.skipped_tests) - self.assertEqual(set(["global", "local"]), self.result.tags) + self.assertEqual(set(["global", "local"]), self.result.seen_tags) def test_stat_formatting(self): expected = (""" @@ -73,7 +73,7 @@ Total tests: 5 Passed tests: 2 Failed tests: 2 Skipped tests: 1 -Tags: global, local +Seen tags: global, local """)[1:] self.setUpUsedStream() self.result.formatStats() diff --git a/python/subunit/tests/test_subunit_tags.py b/python/subunit/tests/test_subunit_tags.py index 66cff68..c23e810 100644 --- a/python/subunit/tests/test_subunit_tags.py +++ b/python/subunit/tests/test_subunit_tags.py @@ -23,6 +23,7 @@ import unittest from StringIO import StringIO import subunit +import subunit.test_results class TestSubUnitTags(unittest.TestCase): diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index 2b90b9e..9a39c6b 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -71,6 +71,19 @@ class MockTestProtocolServerClient(object): self._time = time +class MockExtendedTestProtocolServerClient(MockTestProtocolServerClient): + """An extended TestResult for testing which implements tags() etc.""" + + def __init__(self): + MockTestProtocolServerClient.__init__(self) + self.new_tags = set() + self.gone_tags = set() + + def tags(self, new_tags, gone_tags): + self.new_tags = new_tags + self.gone_tags = gone_tags + + class TestMockTestProtocolServer(unittest.TestCase): def test_start_test(self): @@ -806,53 +819,41 @@ class TestTestProtocolServerStreamTags(unittest.TestCase): """Test managing tags on the protocol level.""" def setUp(self): - self.client = MockTestProtocolServerClient() + self.client = MockExtendedTestProtocolServerClient() self.protocol = subunit.TestProtocolServer(self.client) def test_initial_tags(self): self.protocol.lineReceived("tags: foo bar:baz quux\n") self.assertEqual(set(["foo", "bar:baz", "quux"]), - self.protocol.tags) + self.client.new_tags) + self.assertEqual(set(), self.client.gone_tags) def test_minus_removes_tags(self): self.protocol.lineReceived("tags: foo bar\n") + self.assertEqual(set(["foo", "bar"]), + self.client.new_tags) + self.assertEqual(set(), self.client.gone_tags) self.protocol.lineReceived("tags: -bar quux\n") - self.assertEqual(set(["foo", "quux"]), - self.protocol.tags) + self.assertEqual(set(["quux"]), self.client.new_tags) + self.assertEqual(set(["bar"]), self.client.gone_tags) - def test_tags_get_set_on_test_no_tags(self): + def test_tags_do_not_get_set_on_test(self): self.protocol.lineReceived("test mcdonalds farm\n") test = self.client.start_calls[-1] - self.assertEqual(set(), test.tags) + self.assertEqual(None, getattr(test, 'tags', None)) - def test_tags_get_set_on_test_protocol_tags_only(self): + def test_tags_do_not_get_set_on_global_tags(self): self.protocol.lineReceived("tags: foo bar\n") self.protocol.lineReceived("test mcdonalds farm\n") test = self.client.start_calls[-1] - self.assertEqual(set(["foo", "bar"]), test.tags) - - def test_tags_get_set_on_test_simple(self): - self.protocol.lineReceived("test mcdonalds farm\n") - test = self.client.start_calls[-1] - self.protocol.lineReceived("tags: foo bar\n") - self.assertEqual(set(["foo", "bar"]), test.tags) - self.assertEqual(set(), self.protocol.tags) + self.assertEqual(None, getattr(test, 'tags', None)) - def test_tags_get_set_on_test_minus_removes(self): + def test_tags_get_set_on_test_tags(self): self.protocol.lineReceived("test mcdonalds farm\n") test = self.client.start_calls[-1] self.protocol.lineReceived("tags: foo bar\n") - self.protocol.lineReceived("tags: -bar quux\n") - self.assertEqual(set(["foo", "quux"]), test.tags) - self.assertEqual(set(), self.protocol.tags) - - def test_test_tags_inherit_protocol_tags(self): - self.protocol.lineReceived("tags: foo bar\n") - self.protocol.lineReceived("test mcdonalds farm\n") - test = self.client.start_calls[-1] - self.protocol.lineReceived("tags: -bar quux\n") - self.assertEqual(set(["foo", "quux"]), test.tags) - self.assertEqual(set(["foo", "bar"]), self.protocol.tags) + self.protocol.lineReceived("success mcdonalds farm\n") + self.assertEqual(None, getattr(test, 'tags', None)) class TestTestProtocolServerStreamTime(unittest.TestCase): |
