[tor-commits] [stem/master] Only doing raw controller requests via the prompt

atagar at torproject.org atagar at torproject.org
Mon May 12 06:01:26 UTC 2014


commit 32b9f75825d0c67639ded301f76d8c599031a616
Author: Damian Johnson <atagar at 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'))



More information about the tor-commits mailing list