commit 40b7ab1b36dd4bd0ce455714ea8a9dfd4d3bb76b Author: Damian Johnson atagar@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())
tor-commits@lists.torproject.org