commit 5b1a7e8f2d375db5ad4d3c212fbae93ed4724277 Author: Damian Johnson atagar@torproject.org Date: Mon Oct 10 21:24:58 2011 -0700
Basic unit tests for ControlMessage
Just some sanity checks that ControlMessage parses common GETINFO responses. I'll expand on these including some error cases next. --- run_tests.py | 32 ++++++++++++++--- stem/connection.py | 9 +++-- stem/types.py | 9 +++-- test/unit/__init__.py | 2 +- test/unit/message.py | 87 +++++++++++++++++++++++++++++++++++++++++++++++++ test/unit/version.py | 2 +- 6 files changed, 125 insertions(+), 16 deletions(-)
diff --git a/run_tests.py b/run_tests.py index e1c0811..78f392d 100755 --- a/run_tests.py +++ b/run_tests.py @@ -7,13 +7,19 @@ Runs unit and integration tests. import sys import getopt import unittest +import test.unit.message import test.unit.version
from stem.util import enum, term
OPT = "uit:h" OPT_EXPANDED = ["unit", "integ", "targets=", "help"] -DIVIDER = "=" * 80 +DIVIDER = "=" * 70 + +# (name, class) tuples for all of our unit tests +UNIT_TESTS = (("stem.types.ControlMessage", test.unit.message.TestMessageFunctions), + ("stem.types.Version", test.unit.version.TestVerionFunctions), + )
# Configurations that the intergration tests can be ran with. Attributs are # tuples of the test runner and description. @@ -87,15 +93,29 @@ if __name__ == '__main__': sys.exit()
if run_unit_tests: - print "%s\nUnit Tests\n%s\n" % (DIVIDER, DIVIDER) + print "%s\n%s\n%s\n" % (DIVIDER, "UNIT TESTS".center(70), DIVIDER) + + for name, test_class in UNIT_TESTS: + print "%s\n%s\n%s\n" % (DIVIDER, name, DIVIDER) + #print name + suite = unittest.TestLoader().loadTestsFromTestCase(test_class) + unittest.TextTestRunner(verbosity=2).run(suite) + print + + #import test.unit + #suite = unittest.TestLoader().loadTestsFromTestCase(test.unit.version.TestVerionFunctions) + #suite = unittest.TestLoader().discover("test/unit/", "*.py") + #suite.addTests(unittest.loader.loadTestsFromTestCase(test.unit.message.TestMessageFunctions))
- suite = unittest.TestLoader().loadTestsFromTestCase(test.unit.version.TestVerionFunctions) - unittest.TextTestRunner(verbosity=2).run(suite) + #suite = unittest.TestLoader() + #suite.loadTestsFromTestCase(test.unit.message.TestMessageFunctions) + #suite.loadTestsFromTestCase(test.unit.version.TestVerionFunctions) + #unittest.TextTestRunner(verbosity=2).run(suite)
- print "" + print
if run_integ_tests: - print "%s\nIntegration Tests\n%s\n" % (DIVIDER, DIVIDER) + print "%s\n%s\n%s\n" % (DIVIDER, "INTEGRATION TESTS".center(70), DIVIDER)
for target in integ_targets: runner, description = TARGET_ATTR[target] diff --git a/stem/connection.py b/stem/connection.py index 5e1fe5e..4853864 100644 --- a/stem/connection.py +++ b/stem/connection.py @@ -14,7 +14,7 @@ class ControlConnection: """ Connection to a Tor control port. This is a very lightweight wrapper around the socket, providing basic process communication and event listening. Don't - use this directly - subclasses provide frendlier controller access. + use this directly - subclasses provide friendlier controller access. """
def __init__(self, control_socket): @@ -59,7 +59,8 @@ class ControlConnection: whenever we receive an event from the control socket.
Arguments: - event_message (ControlMessage) - message received from the control socket + event_message (types.ControlMessage) - message received from the control + socket """
pass @@ -72,7 +73,7 @@ class ControlConnection: message (str) - message to be sent to the control socket
Returns: - ControlMessage with the response from the control socket + types.ControlMessage with the response from the control socket """
# makes sure that the message ends with a CRLF @@ -88,7 +89,7 @@ class ControlConnection: def _event_loop(self): """ Continually pulls messages from the _event_thread and sends them to - handle_event. This is done via its own thread so subclasses with a lenghty + handle_event. This is done via its own thread so subclasses with a lengthy handle_event implementation don't block further reading from the socket. """
diff --git a/stem/types.py b/stem/types.py index c73a769..d37f9a1 100644 --- a/stem/types.py +++ b/stem/types.py @@ -1,5 +1,6 @@ """ -Classes for miscellaneous tor object. This includes... +Class representations for a variety of tor objects. These are most commonly +return values rather than being instantiated by users directly.
ProtocolError - Malformed socket data. ControlSocketClosed - Socket terminated. @@ -92,12 +93,12 @@ def read_message(control_file):
line = line[:-2] # strips off the CRLF
- # lines starting with a pariod are escaped by a second period (as per + # lines starting with a period are escaped by a second period (as per # section 2.4 of the control-spec) if line.startswith(".."): line = line[1:]
# appends to previous content, using a newline rather than CRLF - # separator (more contentional for multi-line string content outside + # separator (more conventional for multi-line string content outside # the windows world)
content += "\n" + line @@ -247,7 +248,7 @@ class Version:
def __cmp__(self, other): """ - Simple comparision of versions. An undefined patch level is treated as zero + Simple comparison of versions. An undefined patch level is treated as zero and status tags are compared lexically (as per the version spec). """
diff --git a/test/unit/__init__.py b/test/unit/__init__.py index d7db1bd..52a6924 100644 --- a/test/unit/__init__.py +++ b/test/unit/__init__.py @@ -2,5 +2,5 @@ Unit tests for the stem library. """
-__all__ = ["version"] +__all__ = ["message", "version"]
diff --git a/test/unit/message.py b/test/unit/message.py new file mode 100644 index 0000000..dde2c9c --- /dev/null +++ b/test/unit/message.py @@ -0,0 +1,87 @@ +""" +Unit tests for the types.ControlMessage parsing and class. +""" + +import StringIO +import unittest +import stem.types + +GETINFO_VERSION_REPLY = """250-version=0.2.2.23-alpha (git-b85eb949b528f4d7)\r +250 OK\r +""" + +GETINFO_INFONAMES_REPLY = """250+info/names=\r +accounting/bytes -- Number of bytes read/written so far in the accounting interval.\r +accounting/bytes-left -- Number of bytes left to write/read so far in the accounting interval.\r +accounting/enabled -- Is accounting currently enabled?\r +accounting/hibernating -- Are we hibernating or awake?\r +stream-status -- List of current streams.\r +version -- The current version of Tor.\r +.\r +250 OK\r +""" + +class TestMessageFunctions(unittest.TestCase): + """ + Tests methods and functions related to 'types.ControlMessage'. This uses + StringIO to make 'files' to mock socket input. + """ + + def test_getinfo_results(self): + """ + Checks parsing against some actual GETINFO responses. + """ + + # GETINFO version (basic single-line results) + message = self.assert_message_parses(GETINFO_VERSION_REPLY) + self.assertEquals(2, len(list(message))) + self.assertEquals(2, len(str(message).split("\n"))) + + # manually checks the contents + contents = message.content() + self.assertEquals(2, len(contents)) + self.assertEquals(("250", "-", "version=0.2.2.23-alpha (git-b85eb949b528f4d7)"), contents[0]) + self.assertEquals(("250", " ", "OK"), contents[1]) + + # GETINFO info/names (data entry) + message = self.assert_message_parses(GETINFO_INFONAMES_REPLY) + self.assertEquals(2, len(list(message))) + self.assertEquals(8, len(str(message).split("\n"))) + + # manually checks the contents + contents = message.content() + self.assertEquals(2, len(contents)) + + first_entry = (contents[0][0], contents[0][1], contents[0][2][:contents[0][2].find("\n")]) + self.assertEquals(("250", "+", "info/names="), first_entry) + self.assertEquals(("250", " ", "OK"), contents[1]) + + def assert_message_parses(self, controller_reply): + """ + Performs some basic sanity checks that a reply mirrors its parsed result. + + Returns: + types.ControlMessage for the given input + """ + + message = stem.types.read_message(StringIO.StringIO(controller_reply)) + + # checks that the raw_content equals the input value + self.assertEqual(controller_reply, message.raw_content()) + + # checks that the contents match the input + message_lines = str(message).split("\n") + controller_lines = controller_reply.split("\r\n") + controller_lines.pop() # the ControlMessage won't have a trailing newline + + while controller_lines: + line = controller_lines.pop(0) + + # mismatching lines with just a period are probably data termination + if line == "." and (not message_lines or line != message_lines[0]): + continue + + self.assertTrue(line.endswith(message_lines.pop(0))) + + return message + diff --git a/test/unit/version.py b/test/unit/version.py index f241a8c..0c4a34a 100644 --- a/test/unit/version.py +++ b/test/unit/version.py @@ -1,5 +1,5 @@ """ -Unit tests for types functions and classes. +Unit tests for the types.Version parsing and class. """
import unittest
tor-commits@lists.torproject.org