commit 32b9f75825d0c67639ded301f76d8c599031a616 Author: Damian Johnson atagar@torproject.org Date: Sun May 11 22:21:09 2014 -0700
Only doing raw controller requests via the prompt
I was using our Controller's high level methods (get_info(), get_conf(), add_event_listeners()) for a couple reasons. First they provide caching and second because they yeild more succinct output. But on refection those are both terrible reasons. Caching is worthless with such a low call volume, and it's actually *better* if we print raw Tor responses. This is supposed to provide raw controller accesss, right? :)
If users want the behavior of the high level methods then they can now call those easy enough. --- stem/control.py | 2 + stem/interpretor/commands.py | 88 +++++-------------------------------- stem/interpretor/settings.cfg | 7 +-- test/mocking.py | 3 +- test/unit/interpretor/commands.py | 50 +++++++-------------- 5 files changed, 32 insertions(+), 118 deletions(-)
diff --git a/stem/control.py b/stem/control.py index 02c9fa5..27cce01 100644 --- a/stem/control.py +++ b/stem/control.py @@ -1915,6 +1915,7 @@ class Controller(BaseController): """
# first checking that tor supports these event types + with self._event_listeners_lock: if self.is_authenticated(): for event_type in events: @@ -1929,6 +1930,7 @@ class Controller(BaseController): failed_events = self._attach_listeners()[1]
# restricted the failures to just things we requested + failed_events = set(failed_events).intersection(set(events))
if failed_events: diff --git a/stem/interpretor/commands.py b/stem/interpretor/commands.py index 4b90fce..b66c87b 100644 --- a/stem/interpretor/commands.py +++ b/stem/interpretor/commands.py @@ -3,7 +3,6 @@ Handles making requests and formatting the responses. """
import code -import re
import stem import stem.control @@ -103,12 +102,16 @@ class ControlInterpretor(code.InteractiveConsole):
self.is_multiline_context = False
- def register_event(self, event): - """ - Adds the event to our buffer so it'll be in '/events' output. - """ + # Intercept events our controller hears about at a pretty low level since + # the user will likely be requesting them by direct 'SETEVENTS' calls. + + handle_event_real = self._controller._handle_event + + def handle_event_wrapper(event_message): + handle_event_real(event_message) + self._received_events.append(event_message)
- self._received_events.append(event) + self._controller._handle_event = handle_event_wrapper
def do_help(self, arg): """ @@ -270,76 +273,7 @@ class ControlInterpretor(code.InteractiveConsole): else: cmd = cmd.upper() # makes commands uppercase to match the spec
- if cmd == 'GETINFO': - try: - response = self._controller.get_info(arg.split()) - output = format('\n'.join(response.values()), *STANDARD_OUTPUT) - except stem.ControllerError as exc: - output = format(str(exc), *ERROR_OUTPUT) - elif cmd == 'GETCONF': - try: - response = self._controller.get_conf_map(arg.split()) - - for arg in response: - output += format(arg, *BOLD_OUTPUT) + format(' => ' + ', '.join(response[arg]), *STANDARD_OUTPUT) + '\n' - except stem.ControllerError as exc: - output = format(str(exc), *ERROR_OUTPUT) - elif cmd in ('SETCONF', 'RESETCONF'): - # arguments can either be '<param>', '<param>=<value>', or - # '<param>="<value>"' entries - - param_list = [] - - while arg: - # TODO: I'm a little dubious of this for LineList values (like the - # ExitPolicy) since they're parsed as a single value. However, tor - # seems to be happy to get a single comma separated string (though it - # echos back faithfully rather than being parsed) so leaving this - # alone for now. - - quoted_match = re.match(r'^(\S+)="([^"]+)"', arg) - nonquoted_match = re.match(r'^(\S+)=(\S+)', arg) - - if quoted_match: - # we're dealing with a '<param>="<value>"' entry - param, value = quoted_match.groups() - - param_list.append((param, value)) - arg = arg[len(param) + len(value) + 3:].strip() - elif nonquoted_match: - # we're dealing with a '<param>=<value>' entry - param, value = nonquoted_match.groups() - - param_list.append((param, value)) - arg = arg[len(param) + len(value) + 1:].strip() - else: - # starts with just a param - param = arg.split()[0] - param_list.append((param, None)) - arg = arg[len(param):].strip() - - try: - is_reset = cmd == 'RESETCONF' - self._controller.set_options(param_list, is_reset) - except stem.ControllerError as exc: - output = format(str(exc), *ERROR_OUTPUT) - elif cmd == 'SETEVENTS': - try: - # first discontinue listening to prior events - - self._controller.remove_event_listener(self.register_event) - - # attach listeners for the given group of events - - if arg: - events = arg.split() - self._controller.add_event_listener(self.register_event, *events) - output = format(msg('msg.listening_to_events', events = ', '.join(events)), *STANDARD_OUTPUT) - else: - output = format('Disabled event listening', *STANDARD_OUTPUT) - except stem.ControllerError as exc: - output = format(str(exc), *ERROR_OUTPUT) - elif cmd.replace('+', '') in ('LOADCONF', 'POSTDESCRIPTOR'): + if cmd.replace('+', '') in ('LOADCONF', 'POSTDESCRIPTOR'): # provides a notice that multi-line controller input isn't yet implemented output = format(msg('msg.multiline_unimplemented_notice'), *ERROR_OUTPUT) elif cmd == 'QUIT': @@ -353,7 +287,7 @@ class ControlInterpretor(code.InteractiveConsole): return else: try: - output = format(str(self._controller.msg(command)), *STANDARD_OUTPUT) + output = format(self._controller.msg(command).raw_content().strip(), *STANDARD_OUTPUT) except stem.ControllerError as exc: if isinstance(exc, stem.SocketClosed): raise exc diff --git a/stem/interpretor/settings.cfg b/stem/interpretor/settings.cfg index b8d39dc..028188f 100644 --- a/stem/interpretor/settings.cfg +++ b/stem/interpretor/settings.cfg @@ -34,7 +34,8 @@ msg.startup_banner |You can also issue requests directly to Tor... | | >>> GETINFO version -| 0.2.5.1-alpha-dev (git-245ecfff36c0cecc) +| 250-version=0.2.5.1-alpha-dev (git-245ecfff36c0cecc) +| 250 OK | |For more information run '/help'. | @@ -49,10 +50,6 @@ msg.starting_tor |-------------------------------------------------------------------------------- |
-msg.listening_to_events -|Listening for {events} events. You can print events we've received with -|'/events', and also interact with them via the 'events' variable. - ################# # OUTPUT OF /HELP # ################# diff --git a/test/mocking.py b/test/mocking.py index bfd68f6..f139548 100644 --- a/test/mocking.py +++ b/test/mocking.py @@ -38,6 +38,7 @@ Helper functions for creating mock objects. import base64 import hashlib import itertools +import re
import stem.descriptor.extrainfo_descriptor import stem.descriptor.microdescriptor @@ -227,7 +228,7 @@ def get_message(content, reformat = True): if not content.endswith('\n'): content += '\n'
- content = content.replace('\n', '\r\n') + content = re.sub('([\r]?)\n', '\r\n', content)
return stem.response.ControlMessage.from_str(content)
diff --git a/test/unit/interpretor/commands.py b/test/unit/interpretor/commands.py index 1d03779..dfdbddb 100644 --- a/test/unit/interpretor/commands.py +++ b/test/unit/interpretor/commands.py @@ -1,4 +1,3 @@ -import collections import datetime import unittest
@@ -130,7 +129,7 @@ class TestInterpretorCommands(unittest.TestCase): for content in event_contents: event = mocking.get_message(content) stem.response.convert('EVENT', event) - interpretor.register_event(event) + interpretor._received_events.append(event)
self.assertEqual(EXPECTED_EVENTS_RESPONSE, interpretor.run_command('/events'))
@@ -166,53 +165,34 @@ class TestInterpretorCommands(unittest.TestCase): self.assertEqual(expected, interpretor.run_command('/unrecognized'))
def test_getinfo(self): - controller, getinfo = Mock(), collections.OrderedDict() - controller.get_info.return_value = getinfo + response = '250-version=0.2.5.1-alpha-dev (git-245ecfff36c0cecc)\r\n250 OK'
- interpretor = ControlInterpretor(controller) + controller = Mock() + controller.msg.return_value = mocking.get_message(response)
- getinfo['version'] = '0.2.5.1-alpha-dev (git-245ecfff36c0cecc)' - self.assertEqual('\x1b[34m0.2.5.1-alpha-dev (git-245ecfff36c0cecc)\x1b[0m\n', interpretor.run_command('GETINFO version')) - controller.get_info.assert_called_with(['version']) + interpretor = ControlInterpretor(controller)
- getinfo['process/user'] = 'atagar' - self.assertEqual('\x1b[34m0.2.5.1-alpha-dev (git-245ecfff36c0cecc)\natagar\x1b[0m\n', interpretor.run_command('getinfo version process/user')) - controller.get_info.assert_called_with(['version', 'process/user']) + self.assertEqual('\x1b[34m%s\x1b[0m\n' % response, interpretor.run_command('GETINFO version')) + controller.msg.assert_called_with('GETINFO version')
- controller.get_info.side_effect = stem.ControllerError('kaboom!') + controller.msg.side_effect = stem.ControllerError('kaboom!') self.assertEqual('\x1b[1;31mkaboom!\x1b[0m\n', interpretor.run_command('getinfo process/user'))
def test_getconf(self): - controller, getconf = Mock(), collections.OrderedDict() - controller.get_conf_map.return_value = getconf - - interpretor = ControlInterpretor(controller) - - getconf['log'] = ['notice stdout'] - getconf['address'] = [''] + response = '250-Log=notice stdout\r\n250 Address'
- self.assertEqual(EXPECTED_GETCONF_RESPONSE, interpretor.run_command('GETCONF log address')) - controller.get_conf_map.assert_called_with(['log', 'address']) - - def test_setconf(self): controller = Mock() + controller.msg.return_value = mocking.get_message(response) + interpretor = ControlInterpretor(controller)
- self.assertEqual('\n', interpretor.run_command('SETCONF ControlPort=9051')) - controller.set_options.assert_called_with([('ControlPort', '9051')], False) + self.assertEqual('\x1b[34m%s\x1b[0m\n' % response, interpretor.run_command('GETCONF log address')) + controller.msg.assert_called_with('GETCONF log address')
def test_setevents(self): controller = Mock() - interpretor = ControlInterpretor(controller) + controller.msg.return_value = mocking.get_message('250 OK')
- self.assertEqual("\x1b[34mListening for BW events. You can print events we've received with\n'/events', and also interact with them via the 'events' variable.\x1b[0m\n", interpretor.run_command('SETEVENTS BW')) - controller.add_event_listener.assert_called_with(interpretor.register_event, 'BW') - - def test_raw_commands(self): - controller = Mock() - controller.msg.return_value = 'response' interpretor = ControlInterpretor(controller) - interpretor.do_python('disable')
- self.assertEqual('\x1b[34mresponse\x1b[0m\n', interpretor.run_command('NEW_COMMAND spiffyness')) - controller.msg.assert_called_with('NEW_COMMAND spiffyness') + self.assertEqual('\x1b[34m250 OK\x1b[0m\n', interpretor.run_command('SETEVENTS BW'))
tor-commits@lists.torproject.org