[tor-commits] [stem/master] Unit testing for stem.types.ControlLine

atagar at torproject.org atagar at torproject.org
Sun Nov 6 00:20:59 UTC 2011


commit 40b7ab1b36dd4bd0ce455714ea8a9dfd4d3bb76b
Author: Damian Johnson <atagar at torproject.org>
Date:   Sat Nov 5 17:18:10 2011 -0700

    Unit testing for stem.types.ControlLine
    
    General unit tests to exercise the ControlLine class with PROTOCOLINFO output.
    This also has a minor fix so we throw an IndexError rather than ValueError when
    pop_mapping() is called while empty.
---
 stem/types.py                   |    8 ++
 test/unit/types/control_line.py |  135 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 143 insertions(+), 0 deletions(-)

diff --git a/stem/types.py b/stem/types.py
index 1dc03e2..d152542 100644
--- a/stem/types.py
+++ b/stem/types.py
@@ -34,6 +34,13 @@ from stem.util import log
 KEY_ARG = re.compile("^(\S+)=")
 
 # Escape sequences from the 'esc_for_log' function of tor's 'common/util.c'.
+# It's hard to tell what controller functions use this in practice, but direct
+# users are...
+# - 'COOKIEFILE' field of PROTOCOLINFO responses
+# - logged messages about bugs
+# - the 'getinfo_helper_listeners' function of control.c which looks to be dead
+#   code
+
 CONTROL_ESCAPES = {r"\\": "\\",  r"\"": "\"",   r"\'": "'",
                    r"\r": "\r",  r"\n": "\n",   r"\t": "\t"}
 
@@ -350,6 +357,7 @@ class ControlLine(str):
     
     try:
       self._remainder_lock.acquire()
+      if self.is_empty(): raise IndexError("no remaining content to parse")
       key_match = KEY_ARG.match(self._remainder)
       
       if not key_match:
diff --git a/test/unit/types/control_line.py b/test/unit/types/control_line.py
index d6bda8d..7f7349c 100644
--- a/test/unit/types/control_line.py
+++ b/test/unit/types/control_line.py
@@ -5,6 +5,14 @@ Unit tests for the types.ControlLine class.
 import unittest
 import stem.types
 
+# response made by having 'DataDirectory /tmp/my data\"dir/' in the torrc
+PROTOCOLINFO_RESPONSE = (
+  'PROTOCOLINFO 1',
+  'AUTH METHODS=COOKIE COOKIEFILE="/tmp/my data\\\\\\"dir//control_auth_cookie"',
+  'VERSION Tor="0.2.1.30"',
+  'OK',
+)
+
 class TestControlLine(unittest.TestCase):
   """
   Tests methods of the types.ControlLine class.
@@ -22,4 +30,131 @@ class TestControlLine(unittest.TestCase):
     
     line = stem.types.ControlLine("\"this has a \\\" and \\\\ in it\" foo=bar more_data")
     self.assertEquals(line.pop(True, True), "this has a \" and \\ in it")
+  
+  def test_string(self):
+    """
+    Basic checks that we behave as a regular immutable string.
+    """
+    
+    line = stem.types.ControlLine(PROTOCOLINFO_RESPONSE[0])
+    self.assertEquals(line, 'PROTOCOLINFO 1')
+    self.assertTrue(line.startswith('PROTOCOLINFO '))
+    
+    # checks that popping items doesn't effect us
+    line.pop()
+    self.assertEquals(line, 'PROTOCOLINFO 1')
+    self.assertTrue(line.startswith('PROTOCOLINFO '))
+  
+  def test_general_usage(self):
+    """
+    Checks a basic use case for the popping entries.
+    """
+    
+    # pops a series of basic, space separated entries
+    line = stem.types.ControlLine(PROTOCOLINFO_RESPONSE[0])
+    self.assertEquals(line.remainder(), 'PROTOCOLINFO 1')
+    self.assertFalse(line.is_empty())
+    self.assertFalse(line.is_next_quoted())
+    self.assertFalse(line.is_next_mapping())
+    
+    self.assertRaises(ValueError, line.pop_mapping)
+    self.assertEquals(line.pop(), 'PROTOCOLINFO')
+    self.assertEquals(line.remainder(), '1')
+    self.assertFalse(line.is_empty())
+    self.assertFalse(line.is_next_quoted())
+    self.assertFalse(line.is_next_mapping())
+    
+    self.assertRaises(ValueError, line.pop_mapping)
+    self.assertEquals(line.pop(), '1')
+    self.assertEquals(line.remainder(), '')
+    self.assertTrue(line.is_empty())
+    self.assertFalse(line.is_next_quoted())
+    self.assertFalse(line.is_next_mapping())
+    
+    self.assertRaises(IndexError, line.pop_mapping)
+    self.assertRaises(IndexError, line.pop)
+    self.assertEquals(line.remainder(), '')
+    self.assertTrue(line.is_empty())
+    self.assertFalse(line.is_next_quoted())
+    self.assertFalse(line.is_next_mapping())
+  
+  def test_pop_mapping(self):
+    """
+    Checks use cases when parsing KEY=VALUE mappings.
+    """
+    
+    # version entry with a space
+    version_entry = 'Tor="0.2.1.30 (0a083b0188cacd2f07838ff0446113bd5211a024)"'
+    
+    line = stem.types.ControlLine(version_entry)
+    self.assertEquals(line.remainder(), version_entry)
+    self.assertFalse(line.is_empty())
+    self.assertFalse(line.is_next_quoted())
+    self.assertTrue(line.is_next_mapping())
+    self.assertTrue(line.is_next_mapping(True))
+    
+    # try popping this as a non-quoted mapping
+    self.assertEquals(line.pop_mapping(), ('Tor', '"0.2.1.30'))
+    self.assertEquals(line.remainder(), '(0a083b0188cacd2f07838ff0446113bd5211a024)"')
+    self.assertFalse(line.is_empty())
+    self.assertFalse(line.is_next_quoted())
+    self.assertFalse(line.is_next_mapping())
+    self.assertRaises(ValueError, line.pop_mapping)
+    
+    # try popping this as a quoted mapping
+    line = stem.types.ControlLine(version_entry)
+    self.assertEquals(line.pop_mapping(True), ('Tor', '0.2.1.30 (0a083b0188cacd2f07838ff0446113bd5211a024)'))
+    self.assertEquals(line.remainder(), '')
+    self.assertTrue(line.is_empty())
+    self.assertFalse(line.is_next_quoted())
+    self.assertFalse(line.is_next_mapping())
+  
+  def test_escapes(self):
+    """
+    Checks that we can parse quoted values with escaped quotes in it. This
+    explicitely comes up with the COOKIEFILE attribute of PROTOCOLINFO
+    responses.
+    """
+    
+    auth_line = PROTOCOLINFO_RESPONSE[1]
+    line = stem.types.ControlLine(auth_line)
+    self.assertEquals(line, auth_line)
+    self.assertEquals(line.remainder(), auth_line)
+    
+    self.assertEquals(line.pop(), "AUTH")
+    self.assertEquals(line.pop_mapping(), ("METHODS", "COOKIE"))
+    
+    self.assertEquals(line.remainder(), r'COOKIEFILE="/tmp/my data\\\"dir//control_auth_cookie"')
+    self.assertTrue(line.is_next_mapping())
+    self.assertTrue(line.is_next_mapping(True))
+    self.assertTrue(line.is_next_mapping(True, True))
+    cookie_file_entry = line.remainder()
+    
+    # try a general pop
+    self.assertEquals(line.pop(), 'COOKIEFILE="/tmp/my')
+    self.assertEquals(line.pop(), r'data\\\"dir//control_auth_cookie"')
+    self.assertTrue(line.is_empty())
+    
+    # try a general pop with escapes
+    line = stem.types.ControlLine(cookie_file_entry)
+    self.assertEquals(line.pop(escaped = True), 'COOKIEFILE="/tmp/my')
+    self.assertEquals(line.pop(escaped = True), r'data\"dir//control_auth_cookie"')
+    self.assertTrue(line.is_empty())
+    
+    # try a mapping pop
+    line = stem.types.ControlLine(cookie_file_entry)
+    self.assertEquals(line.pop_mapping(), ('COOKIEFILE', '"/tmp/my'))
+    self.assertEquals(line.remainder(), r'data\\\"dir//control_auth_cookie"')
+    self.assertFalse(line.is_empty())
+    
+    # try a quoted mapping pop (this should trip up on the escaped quote)
+    line = stem.types.ControlLine(cookie_file_entry)
+    self.assertEquals(line.pop_mapping(True), ('COOKIEFILE', '/tmp/my data\\\\\\'))
+    self.assertEquals(line.remainder(), 'dir//control_auth_cookie"')
+    self.assertFalse(line.is_empty())
+    
+    # try an escaped quoted mapping pop
+    line = stem.types.ControlLine(cookie_file_entry)
+    self.assertEquals(line.pop_mapping(True, True), ('COOKIEFILE', r'/tmp/my data\"dir//control_auth_cookie'))
+    self.assertTrue(line.is_empty())
 



More information about the tor-commits mailing list