commit e59de80010d1894c5eecb7d4b9d953ee195a5259
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sun Dec 2 03:00:51 2012 -0800
Testing for STATUS_* events
I had grand plans to have a StatusEvent subclass for each of the causes.
However, on reflection that would encompass a bundle of work that's almost as
large as all of the other events put together. It might still be a neat thing
to do someday, but there's plenty of higher priority things to do first.
Adding tests for all of the event examples that I collected earlier. This
includes a bit of a hack to make quoted key/value mappings work. Previously
we've only parsed quoted mappings if they belonged to the _QUOTED listing, but
STATUS_* events sprinkle them all around so for forward compatability we can't
simply enumerate them.
---
stem/response/events.py | 31 ++++++--
test/unit/response/events.py | 159 +++++++++++++++++++++++++++++++++++++++++-
2 files changed, 182 insertions(+), 8 deletions(-)
diff --git a/stem/response/events.py b/stem/response/events.py
index d580829..96764a0 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -34,6 +34,11 @@ class Event(stem.response.ControlMessage):
_QUOTED = ()
_SKIP_PARSING = False
+ # If set then we'll parse anything that looks like a quoted key/value
+ # mapping, reguardless of if it shows up in _QUOTED.
+
+ _PERMISSIVE_QUOTED_MAPPINGS = False
+
def _parse_message(self, arrived_at):
if not str(self).strip():
raise stem.ProtocolError("Received a blank tor event. Events must at the very least have a type.")
@@ -73,13 +78,24 @@ class Event(stem.response.ControlMessage):
content = str(self)
- for keyword in set(self._QUOTED).intersection(set(self._KEYWORD_ARGS.keys())):
- match = re.match("^(.*) %s=\"(.*)\"(.*)$" % keyword, content)
-
- if match:
- prefix, value, suffix = match.groups()
- content = prefix + suffix
- self.keyword_args[keyword] = value
+ if self._PERMISSIVE_QUOTED_MAPPINGS:
+ while True:
+ match = re.match("^(.*) (\S*)=\"(.*)\"(.*)$", content)
+
+ if match:
+ prefix, keyword, value, suffix = match.groups()
+ content = prefix + suffix
+ self.keyword_args[keyword] = value
+ else:
+ break
+ else:
+ for keyword in set(self._QUOTED).intersection(set(self._KEYWORD_ARGS.keys())):
+ match = re.match("^(.*) %s=\"(.*)\"(.*)$" % keyword, content)
+
+ if match:
+ prefix, value, suffix = match.groups()
+ content = prefix + suffix
+ self.keyword_args[keyword] = value
fields = content.split()[1:]
@@ -423,6 +439,7 @@ class StatusEvent(Event):
"""
_POSITIONAL_ARGS = ("runlevel", "action")
+ _PERMISSIVE_QUOTED_MAPPINGS = True
def _parse(self):
if self.type == 'STATUS_GENERAL':
diff --git a/test/unit/response/events.py b/test/unit/response/events.py
index fc796a2..54d4777 100644
--- a/test/unit/response/events.py
+++ b/test/unit/response/events.py
@@ -378,7 +378,7 @@ class TestEvents(unittest.TestCase):
self.assertEqual(ORClosureReason.DONE, event.reason)
self.assertEqual(None, event.circ_count)
- def test_status_event(self):
+ def test_status_event_consensus_arrived(self):
event = _get_event(STATUS_CLIENT_CONSENSUS_ARRIVED)
self.assertTrue(isinstance(event, stem.response.events.StatusEvent))
@@ -387,6 +387,163 @@ class TestEvents(unittest.TestCase):
self.assertEqual(Runlevel.NOTICE, event.runlevel)
self.assertEqual("CONSENSUS_ARRIVED", event.action)
+ def test_status_event_enough_dir_info(self):
+ event = _get_event(STATUS_CLIENT_ENOUGH_DIR_INFO)
+
+ self.assertEqual(StatusType.CLIENT, event.status_type)
+ self.assertEqual(Runlevel.NOTICE, event.runlevel)
+ self.assertEqual("ENOUGH_DIR_INFO", event.action)
+
+ def test_status_event_circuit_established(self):
+ event = _get_event(STATUS_CLIENT_CIRC_ESTABLISHED)
+
+ self.assertEqual(StatusType.CLIENT, event.status_type)
+ self.assertEqual(Runlevel.NOTICE, event.runlevel)
+ self.assertEqual("CIRCUIT_ESTABLISHED", event.action)
+
+ def test_status_event_bootstrap_descriptors(self):
+ event = _get_event(STATUS_CLIENT_BOOTSTRAP_DESCRIPTORS)
+
+ self.assertEqual(StatusType.CLIENT, event.status_type)
+ self.assertEqual(Runlevel.NOTICE, event.runlevel)
+ self.assertEqual("BOOTSTRAP", event.action)
+
+ expected_attr = {
+ 'PROGRESS': '53',
+ 'TAG': 'loading_descriptors',
+ 'SUMMARY': 'Loading relay descriptors',
+ }
+
+ self.assertEqual(expected_attr, event.keyword_args)
+
+ def test_status_event_bootstrap_stuck(self):
+ event = _get_event(STATUS_CLIENT_BOOTSTRAP_STUCK)
+
+ self.assertEqual(StatusType.CLIENT, event.status_type)
+ self.assertEqual(Runlevel.WARN, event.runlevel)
+ self.assertEqual("BOOTSTRAP", event.action)
+
+ expected_attr = {
+ 'PROGRESS': '80',
+ 'TAG': 'conn_or',
+ 'SUMMARY': 'Connecting to the Tor network',
+ 'WARNING': 'Network is unreachable',
+ 'REASON': 'NOROUTE',
+ 'COUNT': '5',
+ 'RECOMMENDATION': 'warn',
+ }
+
+ self.assertEqual(expected_attr, event.keyword_args)
+
+ def test_status_event_bootstrap_connecting(self):
+ event = _get_event(STATUS_CLIENT_BOOTSTRAP_CONNECTING)
+
+ self.assertEqual(StatusType.CLIENT, event.status_type)
+ self.assertEqual(Runlevel.NOTICE, event.runlevel)
+ self.assertEqual("BOOTSTRAP", event.action)
+
+ expected_attr = {
+ 'PROGRESS': '80',
+ 'TAG': 'conn_or',
+ 'SUMMARY': 'Connecting to the Tor network',
+ }
+
+ self.assertEqual(expected_attr, event.keyword_args)
+
+ def test_status_event_bootstrap_first_handshake(self):
+ event = _get_event(STATUS_CLIENT_BOOTSTRAP_FIRST_HANDSHAKE)
+
+ self.assertEqual(StatusType.CLIENT, event.status_type)
+ self.assertEqual(Runlevel.NOTICE, event.runlevel)
+ self.assertEqual("BOOTSTRAP", event.action)
+
+ expected_attr = {
+ 'PROGRESS': '85',
+ 'TAG': 'handshake_or',
+ 'SUMMARY': 'Finishing handshake with first hop',
+ }
+
+ self.assertEqual(expected_attr, event.keyword_args)
+
+ def test_status_event_bootstrap_established(self):
+ event = _get_event(STATUS_CLIENT_BOOTSTRAP_ESTABLISHED)
+
+ self.assertEqual(StatusType.CLIENT, event.status_type)
+ self.assertEqual(Runlevel.NOTICE, event.runlevel)
+ self.assertEqual("BOOTSTRAP", event.action)
+
+ expected_attr = {
+ 'PROGRESS': '90',
+ 'TAG': 'circuit_create',
+ 'SUMMARY': 'Establishing a Tor circuit',
+ }
+
+ self.assertEqual(expected_attr, event.keyword_args)
+
+ def test_status_event_bootstrap_done(self):
+ event = _get_event(STATUS_CLIENT_BOOTSTRAP_DONE)
+
+ self.assertEqual(StatusType.CLIENT, event.status_type)
+ self.assertEqual(Runlevel.NOTICE, event.runlevel)
+ self.assertEqual("BOOTSTRAP", event.action)
+
+ expected_attr = {
+ 'PROGRESS': '100',
+ 'TAG': 'done',
+ 'SUMMARY': 'Done',
+ }
+
+ self.assertEqual(expected_attr, event.keyword_args)
+
+ def test_status_event_bootstrap_check_reachability(self):
+ event = _get_event(STATUS_SERVER_CHECK_REACHABILITY)
+
+ self.assertEqual(StatusType.SERVER, event.status_type)
+ self.assertEqual(Runlevel.NOTICE, event.runlevel)
+ self.assertEqual("CHECKING_REACHABILITY", event.action)
+
+ expected_attr = {
+ 'ORADDRESS': '71.35.143.230:9050',
+ }
+
+ self.assertEqual(expected_attr, event.keyword_args)
+
+ def test_status_event_dns_timeout(self):
+ event = _get_event(STATUS_SERVER_DNS_TIMEOUT)
+
+ self.assertEqual(StatusType.SERVER, event.status_type)
+ self.assertEqual(Runlevel.NOTICE, event.runlevel)
+ self.assertEqual("NAMESERVER_STATUS", event.action)
+
+ expected_attr = {
+ 'NS': '205.171.3.25',
+ 'STATUS': 'DOWN',
+ 'ERR': 'request timed out.',
+ }
+
+ self.assertEqual(expected_attr, event.keyword_args)
+
+ def test_status_event_dns_down(self):
+ event = _get_event(STATUS_SERVER_DNS_DOWN)
+
+ self.assertEqual(StatusType.SERVER, event.status_type)
+ self.assertEqual(Runlevel.WARN, event.runlevel)
+ self.assertEqual("NAMESERVER_ALL_DOWN", event.action)
+
+ def test_status_event_dns_up(self):
+ event = _get_event(STATUS_SERVER_DNS_UP)
+
+ self.assertEqual(StatusType.SERVER, event.status_type)
+ self.assertEqual(Runlevel.NOTICE, event.runlevel)
+ self.assertEqual("NAMESERVER_STATUS", event.action)
+
+ expected_attr = {
+ 'NS': '205.171.3.25',
+ 'STATUS': 'UP',
+ }
+
+ self.assertEqual(expected_attr, event.keyword_args)
+
def test_stream_event(self):
event = _get_event(STREAM_NEW)