Implement tags support in the protocol server, supporting clients that want to define...
authorRobert Collins <robertc@robertcollins.net>
Sat, 6 Dec 2008 19:57:05 +0000 (06:57 +1100)
committerRobert Collins <robertc@robertcollins.net>
Sat, 6 Dec 2008 19:57:05 +0000 (06:57 +1100)
README
python/subunit/__init__.py
python/subunit/tests/test_test_protocol.py

diff --git a/README b/README
index bf3ada071e94d74253ad1242a2653b0e235ffa29..e916f084a65e8bb6b8d02703beec7c7534df3b9c 100644 (file)
--- a/README
+++ b/README
@@ -156,8 +156,14 @@ failure: test label [
 error: test label
 error: test label [
 ]
 error: test label
 error: test label [
 ]
+tags: [-]TAG ...
 unexpected output on stdout -> stdout.
 exit w/0 or last test -> error
 unexpected output on stdout -> stdout.
 exit w/0 or last test -> error
+tags given outside a test are applied to all following tests
+tags given within a test inherit the current global tags
+
+In python, tags are assigned to the .tags attribute on the RemoteTest objects
+created by the TestProtocolServer.
 
 TODO:
 def run:
 
 TODO:
 def run:
index f0b0987308bbd507f687b1e18af15f40d0da93b3..bf1e0cc1477d2ee8f063a1f27773a34a68b887fc 100644 (file)
@@ -43,7 +43,10 @@ def join_dir(base_path, path):
 
 
 class TestProtocolServer(object):
 
 
 class TestProtocolServer(object):
-    """A class for receiving results from a TestProtocol client."""
+    """A class for receiving results from a TestProtocol client.
+    
+    :ivar tags: The current tags associated with the protocol stream.
+    """
 
     OUTSIDE_TEST = 0
     TEST_STARTED = 1
 
     OUTSIDE_TEST = 0
     TEST_STARTED = 1
@@ -64,6 +67,7 @@ class TestProtocolServer(object):
         self.state = TestProtocolServer.OUTSIDE_TEST
         self.client = client
         self._stream = stream
         self.state = TestProtocolServer.OUTSIDE_TEST
         self.client = client
         self._stream = stream
+        self.tags = set()
 
     def _addError(self, offset, line):
         if (self.state == TestProtocolServer.TEST_STARTED and
 
     def _addError(self, offset, line):
         if (self.state == TestProtocolServer.TEST_STARTED and
@@ -128,6 +132,23 @@ class TestProtocolServer(object):
         else:
             self.stdOutLineReceived(line)
 
         else:
             self.stdOutLineReceived(line)
 
+    def _handleTags(self, offset, line):
+        """Process a tags command."""
+        tags = line[offset:].split()
+        new_tags = set()
+        gone_tags = set()
+        for tag in tags:
+            if tag[0] == '-':
+                gone_tags.add(tag[1:])
+            else:
+                new_tags.add(tag)
+        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)
+
     def lineReceived(self, line):
         """Call the appropriate local method for the received line."""
         if line == "]\n":
     def lineReceived(self, line):
         """Call the appropriate local method for the received line."""
         if line == "]\n":
@@ -149,6 +170,8 @@ class TestProtocolServer(object):
                     self._addFailure(offset, line)
                 elif cmd in ('success', 'successful'):
                     self._addSuccess(offset, line)
                     self._addFailure(offset, line)
                 elif cmd in ('success', 'successful'):
                     self._addSuccess(offset, line)
+                elif cmd in ('tags'):
+                    self._handleTags(offset, line)
                 else:
                     self.stdOutLineReceived(line)
             else:
                 else:
                     self.stdOutLineReceived(line)
             else:
@@ -188,6 +211,7 @@ 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 = 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)
 
         else:
             self.stdOutLineReceived(line)
 
@@ -242,7 +266,14 @@ def RemoteError(description=""):
 
 
 class RemotedTestCase(unittest.TestCase):
 
 
 class RemotedTestCase(unittest.TestCase):
-    """A class to represent test cases run in child processes."""
+    """A class to represent test cases run in child processes.
+    
+    Instances of this class are used to provide the python test API a TestCase
+    that can be printed to the screen, introspected for metadata and so on.
+    However, as they are a simply a memoisation of a test that was actually
+    run in the past by a separate process, they cannot perform any interactive
+    actions.
+    """
 
     def __eq__ (self, other):
         try:
 
     def __eq__ (self, other):
         try:
index 00839548b6bd0261b5eb121ad2e4b1d29f3cf1c2..7f5bbe4bdbb1b4505ed6d2957969a294f0aea621 100644 (file)
@@ -521,6 +521,59 @@ class TestTestProtocolServerAddSuccess(unittest.TestCase):
         self.simple_success_keyword("successful:")
 
 
         self.simple_success_keyword("successful:")
 
 
+class TestTestProtocolServerStreamTags(unittest.TestCase):
+    """Test managing tags on the protocol level."""
+
+    def setUp(self):
+        self.client = MockTestProtocolServerClient()
+        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)
+
+    def test_minus_removes_tags(self):
+        self.protocol.lineReceived("tags: foo bar\n")
+        self.protocol.lineReceived("tags: -bar quux\n")
+        self.assertEqual(set(["foo", "quux"]),
+            self.protocol.tags)
+
+    def test_tags_get_set_on_test_no_tags(self):
+        self.protocol.lineReceived("test mcdonalds farm\n")
+        test = self.client.start_calls[-1]
+        self.assertEqual(set(), test.tags)
+
+    def test_tags_get_set_on_test_protocol_tags_only(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)
+
+    def test_tags_get_set_on_test_minus_removes(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)
+
+
 class TestRemotedTestCase(unittest.TestCase):
 
     def test_simple(self):
 class TestRemotedTestCase(unittest.TestCase):
 
     def test_simple(self):