summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorRobert Collins <robertc@robertcollins.net>2009-09-08 22:08:23 +1000
committerRobert Collins <robertc@robertcollins.net>2009-09-08 22:08:23 +1000
commit3b11398f6c6247545d846c2bb607b6ff526ecbc1 (patch)
tree45511636f6568dd6ea0f0279d282665cd456f0cb /python
parent56f366ba26b9bd81079f2ce77c851b0324ef28f6 (diff)
parent95a5816c1bb8808141416005c5a203bc4d7f8599 (diff)
downloadsubunit-git-3b11398f6c6247545d846c2bb607b6ff526ecbc1.tar.gz
Merge python API changes for tagging.
Diffstat (limited to 'python')
-rw-r--r--python/subunit/__init__.py80
-rw-r--r--python/subunit/test_results.py119
-rw-r--r--python/subunit/tests/test_subunit_stats.py6
-rw-r--r--python/subunit/tests/test_subunit_tags.py1
-rw-r--r--python/subunit/tests/test_test_protocol.py55
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):