[tor-commits] [stem/master] Implementing and testing connect_* functions

atagar at torproject.org atagar at torproject.org
Tue Dec 27 18:25:28 UTC 2011


commit a9a240c553cc154f8019c6332ece686202398c34
Author: Damian Johnson <atagar at torproject.org>
Date:   Tue Dec 27 09:58:00 2011 -0800

    Implementing and testing connect_* functions
    
    Similiar to TorCtl.connect(), stem's connect_port and connect_socket_file are
    convenience functions for trivially getting an authenticated connection. This
    isn't ideal for applications since it hijacks stdin/stdout and lacks
    exceptions, however for CLI apps and the interactive interpretor it's very nice
    to have.
---
 run_tests.py                            |    2 +
 stem/connection.py                      |   87 +++++++++++++++++++++++++++++++
 test/integ/connection/__init__.py       |    2 +-
 test/integ/connection/authentication.py |   25 +++------
 test/integ/connection/connect.py        |   69 ++++++++++++++++++++++++
 test/runner.py                          |   15 +++++
 6 files changed, 181 insertions(+), 19 deletions(-)

diff --git a/run_tests.py b/run_tests.py
index 73cfb9c..7247dd7 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -24,6 +24,7 @@ import test.integ.socket.control_message
 import test.integ.util.conf
 import test.integ.util.system
 import test.integ.connection.authentication
+import test.integ.connection.connect
 import test.integ.connection.protocolinfo
 
 import stem.util.enum
@@ -45,6 +46,7 @@ UNIT_TESTS = (("stem.socket.ControlMessage", test.unit.socket.control_message.Te
 
 INTEG_TESTS = (("stem.socket.ControlMessage", test.integ.socket.control_message.TestControlMessage),
               ("stem.connection.authenticate", test.integ.connection.authentication.TestAuthenticate),
+              ("stem.connection.connect_*", test.integ.connection.connect.TestConnect),
               ("stem.connection.get_protocolinfo", test.integ.connection.protocolinfo.TestProtocolInfo),
                ("stem.util.conf", test.integ.util.conf.TestConf),
                ("stem.util.system", test.integ.util.system.TestSystem),
diff --git a/stem/connection.py b/stem/connection.py
index 3758187..25779f5 100644
--- a/stem/connection.py
+++ b/stem/connection.py
@@ -1,6 +1,9 @@
 """
 Functions for connecting and authenticating to the tor process.
 
+connect_port - Convenience method to get an authenticated control connection.
+connect_socket_file - Similar to connect_port, but for control socket files.
+
 authenticate - Main method for authenticating to a control socket.
 authenticate_none - Authenticates to an open control socket.
 authenticate_password - Authenticates to a socket supporting password auth.
@@ -40,6 +43,7 @@ AuthenticationFailure - Base exception raised for authentication failures.
 """
 
 import os
+import getpass
 import logging
 import binascii
 
@@ -50,6 +54,9 @@ import stem.util.system
 
 LOGGER = logging.getLogger("stem")
 
+# enums representing classes that the connect_* methods can return
+Controller = stem.util.enum.Enum("NONE")
+
 # Methods by which a controller can authenticate to the control port. Tor gives
 # a list of all the authentication methods it will accept in response to
 # PROTOCOLINFO queries.
@@ -147,6 +154,86 @@ AUTHENTICATE_EXCEPTIONS = (
   AuthenticationFailure,
 )
 
+def connect_port(control_addr = "127.0.0.1", control_port = 9051, password = None, controller = Controller.NONE):
+  """
+  Convenience function for quickly getting a control connection. This is very
+  handy for debugging or CLI setup, handling setup and prompting for a password
+  if necessary (and none is provided). If any issues arise this prints a
+  description of the problem and returns None.
+  
+  Arguments:
+    control_addr (str)      - ip address of the controller
+    control_port (int)      - port number of the controller
+    password (str)          - passphrase to authenticate to the socket
+    controller (Controller) - controller type to be returned
+  
+  Returns:
+    Authenticated control connection, the type based on the controller enum...
+      Controller.NONE => stem.socket.ControlPort
+  """
+  
+  # TODO: replace the controller arg's default when we have something better
+  
+  try:
+    control_port = stem.socket.ControlPort(control_addr, control_port)
+  except stem.socket.SocketError, exc:
+    print exc
+    return None
+  
+  return _connect(control_port, password, controller)
+
+def connect_socket_file(socket_path = "/var/run/tor/control", password = None, controller = Controller.NONE):
+  """
+  Convenience function for quickly getting a control connection. For more
+  information see the connect_port function.
+  
+  Arguments:
+    socket_path (str)       - path where the control socket is located
+    password (str)          - passphrase to authenticate to the socket
+    controller (Controller) - controller type to be returned
+  
+  Returns:
+    Authenticated control connection, the type based on the controller enum.
+  """
+  
+  try:
+    control_socket = stem.socket.ControlSocketFile(socket_path)
+  except stem.socket.SocketError, exc:
+    print exc
+    return None
+  
+  return _connect(control_socket, password, controller)
+
+def _connect(control_socket, password, controller):
+  """
+  Common implementation for the connect_* functions.
+  
+  Arguments:
+    control_socket (stem.socket.ControlSocket) - socket being authenticated to
+    password (str)          - passphrase to authenticate to the socket
+    controller (Controller) - controller type to be returned
+  
+  Returns:
+    Authenticated control connection with a type based on the controller enum.
+  """
+  
+  try:
+    authenticate(control_socket, password)
+    
+    if controller == Controller.NONE:
+      return control_socket
+  except MissingPassword:
+    assert password == None, "BUG: authenticate raised MissingPassword despite getting one"
+    
+    try: password = getpass.getpass("Controller password: ")
+    except KeyboardInterrupt: return None
+    
+    return _connect(control_socket, password, controller)
+  except AuthenticationFailure, exc:
+    control_socket.close()
+    print "Unable to authenticate: %s" % exc
+    return None
+
 def authenticate(control_socket, password = None, protocolinfo_response = None):
   """
   Authenticates to a control socket using the information provided by a
diff --git a/test/integ/connection/__init__.py b/test/integ/connection/__init__.py
index d570669..90471c0 100644
--- a/test/integ/connection/__init__.py
+++ b/test/integ/connection/__init__.py
@@ -2,5 +2,5 @@
 Integration tests for stem.connection.
 """
 
-__all__ = ["authenticate", "protocolinfo"]
+__all__ = ["authenticate", "connect", "protocolinfo"]
 
diff --git a/test/integ/connection/authentication.py b/test/integ/connection/authentication.py
index a79cbb0..7ac745b 100644
--- a/test/integ/connection/authentication.py
+++ b/test/integ/connection/authentication.py
@@ -43,7 +43,7 @@ class TestAuthenticate(unittest.TestCase):
     
     control_socket = test.runner.get_runner().get_tor_socket(False)
     stem.connection.authenticate(control_socket, test.runner.CONTROL_PASSWORD)
-    self._exercise_socket(control_socket)
+    test.runner.exercise_socket(self, control_socket)
     control_socket.close()
   
   def test_authenticate_general_example(self):
@@ -64,7 +64,7 @@ class TestAuthenticate(unittest.TestCase):
     try:
       # this authenticate call should work for everything but password-only auth
       stem.connection.authenticate(control_socket)
-      self._exercise_socket(control_socket)
+      test.runner.exercise_socket(self, control_socket)
     except stem.connection.IncorrectSocketType:
       self.fail()
     except stem.connection.MissingPassword:
@@ -73,7 +73,7 @@ class TestAuthenticate(unittest.TestCase):
       
       try:
         stem.connection.authenticate_password(control_socket, controller_password)
-        self._exercise_socket(control_socket)
+        test.runner.exercise_socket(self, control_socket)
       except stem.connection.PasswordAuthFailed:
         self.fail()
     except stem.connection.AuthenticationFailure:
@@ -101,7 +101,7 @@ class TestAuthenticate(unittest.TestCase):
       self.assertRaises(stem.connection.MissingPassword, auth_function)
     else:
       auth_function()
-      self._exercise_socket(control_socket)
+      test.runner.exercise_socket(self, control_socket)
     
     control_socket.close()
     
@@ -113,14 +113,14 @@ class TestAuthenticate(unittest.TestCase):
       self.assertRaises(stem.connection.IncorrectPassword, auth_function)
     else:
       auth_function()
-      self._exercise_socket(control_socket)
+      test.runner.exercise_socket(self, control_socket)
     
     control_socket.close()
     
     # tests with the right password
     control_socket = runner.get_tor_socket(False)
     stem.connection.authenticate(control_socket, test.runner.CONTROL_PASSWORD)
-    self._exercise_socket(control_socket)
+    test.runner.exercise_socket(self, control_socket)
     control_socket.close()
   
   def test_authenticate_none(self):
@@ -364,17 +364,6 @@ class TestAuthenticate(unittest.TestCase):
       control_socket.close()
       raise exc
     
-    self._exercise_socket(control_socket)
+    test.runner.exercise_socket(self, control_socket)
     control_socket.close()
-  
-  def _exercise_socket(self, control_socket):
-    """
-    Checks that we can now use the socket by issuing a 'GETINFO config-file'
-    query.
-    """
-    
-    torrc_path = test.runner.get_runner().get_torrc_path()
-    control_socket.send("GETINFO config-file")
-    config_file_response = control_socket.recv()
-    self.assertEquals("config-file=%s\nOK" % torrc_path, str(config_file_response))
 
diff --git a/test/integ/connection/connect.py b/test/integ/connection/connect.py
new file mode 100644
index 0000000..6b0c16e
--- /dev/null
+++ b/test/integ/connection/connect.py
@@ -0,0 +1,69 @@
+"""
+Integration tests for the connect_* convenience functions.
+"""
+
+import sys
+import unittest
+import StringIO
+
+import stem.connection
+import test.runner
+
+class TestConnect(unittest.TestCase):
+  """
+  Tests the connection methods. This should be run with the 'CONN_ALL' integ
+  target to exercise the widest range of use cases.
+  """
+  
+  def setUp(self):
+    connection_type = test.runner.get_runner().get_connection_type()
+    
+    # none of these tests apply if there's no control connection
+    if connection_type == test.runner.TorConnection.NONE:
+      self.skipTest("(no connection)")
+  
+  def test_connect_port(self):
+    """
+    Basic sanity checks for the connect_port function.
+    """
+    
+    self._test_connect(True)
+  
+  def test_connect_socket_file(self):
+    """
+    Basic sanity checks for the connect_socket_file function.
+    """
+    
+    self._test_connect(False)
+  
+  def _test_connect(self, is_port):
+    """
+    Common implementations for the test_connect_* functions.
+    """
+    
+    # prevents the function from printing to the real stdout
+    original_stdout = sys.stdout
+    sys.stdout = StringIO.StringIO()
+    
+    try:
+      connection_type = test.runner.get_runner().get_connection_type()
+      ctl_pw = test.runner.CONTROL_PASSWORD
+      controller = stem.connection.Controller.NONE
+      
+      if is_port:
+        opt_type = test.runner.OPT_PORT
+        ctl_port = test.runner.CONTROL_PORT
+        control_socket = stem.connection.connect_port(control_port = ctl_port, password = ctl_pw, controller = controller)
+      else:
+        opt_type = test.runner.OPT_SOCKET
+        ctl_socket = test.runner.CONTROL_SOCKET_PATH
+        control_socket = stem.connection.connect_socket_file(socket_path = ctl_socket, password = ctl_pw, controller = controller)
+      
+      if opt_type in test.runner.CONNECTION_OPTS[connection_type]:
+        test.runner.exercise_socket(self, control_socket)
+        control_socket.close()
+      else:
+        self.assertEquals(control_socket, None)
+    finally:
+      sys.stdout = original_stdout
+
diff --git a/test/runner.py b/test/runner.py
index 6b5f6ea..ed92126 100644
--- a/test/runner.py
+++ b/test/runner.py
@@ -106,6 +106,21 @@ def get_torrc(connection_type = DEFAULT_TOR_CONNECTION):
     return torrc + "\n".join(connection_opt) + "\n"
   else: return torrc
 
+def exercise_socket(test_case, control_socket):
+  """
+  Checks that we can now use the socket by issuing a 'GETINFO config-file'
+  query.
+  
+  Arguments:
+    test_case (unittest.TestCase) - unit testing case being ran
+    control_socket (stem.socket.ControlSocket) - socket to be tested
+  """
+  
+  torrc_path = get_runner().get_torrc_path()
+  control_socket.send("GETINFO config-file")
+  config_file_response = control_socket.recv()
+  test_case.assertEquals("config-file=%s\nOK" % torrc_path, str(config_file_response))
+
 class RunnerStopped(Exception):
   "Raised when we try to use a Runner that doesn't have an active tor instance"
   pass





More information about the tor-commits mailing list