
commit e59de80010d1894c5eecb7d4b9d953ee195a5259 Author: Damian Johnson <atagar@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)