[tor-commits] [stem/master] Running integ tests multiple connections methods

atagar at torproject.org atagar at torproject.org
Sun Nov 20 23:57:22 UTC 2011


commit 3d14e1bd1076bdfd6fd109be0b76f71b12fbebaa
Author: Damian Johnson <atagar at torproject.org>
Date:   Sun Nov 20 15:51:29 2011 -0800

    Running integ tests multiple connections methods
    
    Adding a 'CONNECTION' target that, if set, will run integration tests with
    multiple connection and authentication methods...
    - no control connection
    - control port with no auth
    - control port with an authentication cookie
    - control port with a password
    - control port with both an authtication cookie and password
    - control socket
    
    This means running through the integ tests six times which currently results in
    a runtime of arond fourty sectons, so this isn't the default.
    
    The primary purpose for doing this is to exercise the PROTOCOLINFO parsing and
    upcoming connecion methods with all of these tor configurations. The
    ProtocolInfoResponse integ test doesn't yet actually test all of these - fixing
    that is next.
---
 run_tests.py                                   |  114 +++++++++-------
 stem/types.py                                  |    1 +
 stem/util/term.py                              |    4 +-
 test/integ/connection/protocolinfo_response.py |    8 +-
 test/integ/types/control_message.py            |   64 ++++-----
 test/integ/util/conf.py                        |    2 +
 test/integ/util/system.py                      |   35 ++++--
 test/runner.py                                 |  177 ++++++++++++------------
 test/testrc.sample                             |    9 +-
 9 files changed, 218 insertions(+), 196 deletions(-)

diff --git a/run_tests.py b/run_tests.py
index 05aa848..9189908 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -4,6 +4,7 @@
 Runs unit and integration tests.
 """
 
+import os
 import sys
 import time
 import getopt
@@ -45,18 +46,12 @@ INTEG_TESTS = (("stem.types.ControlMessage", test.integ.types.control_message.Te
                ("stem.util.system", test.integ.util.system.TestSystem),
               )
 
-# TODO: drop targets?
-# Configurations that the intergration tests can be ran with. Attributs are
-# tuples of the test runner and description.
-TARGETS = stem.util.enum.Enum(*[(v, v) for v in ("NONE", "NO_CONTROL", "NO_AUTH", "COOKIE", "PASSWORD", "SOCKET")])
+# Integration tests above the basic suite.
+TARGETS = stem.util.enum.Enum(*[(v, v) for v in ("ONLINE", "CONNECTION")])
 
 TARGET_ATTR = {
-  TARGETS.NONE: (None, "No running tor instance."),
-  TARGETS.NO_CONTROL: (None, "Basic client, no control port or socket."),
-  TARGETS.NO_AUTH: (None, "Basic client, control port with no authenticaion."),
-  TARGETS.COOKIE: (None, "Basic client, control port with cookie authenticaion."),
-  TARGETS.PASSWORD: (None, "Basic client, control port wiht password authentication."),
-  TARGETS.SOCKET: (None, "Basic client, control socket."),
+  TARGETS.ONLINE: ("test.integ.target.online", "Includes tests that require network activity."),
+  TARGETS.CONNECTION: ("test.integ.target.connection", "Runs the suite over multiple connection configurations."),
 }
 
 HELP_MSG = """Usage runTests.py [OPTION]
@@ -65,8 +60,7 @@ Runs tests for the stem library.
   -u, --unit            runs unit tests
   -i, --integ           runs integration tests
   -c, --config PATH     path to a custom test configuration
-  -t, --target TARGET   comma separated list of tor configurations to use for
-                        the integration tests (all are used by default)
+  -t, --target TARGET   comma separated list of extra targets for integ tests
   -h, --help            presents this help
 
   Integration targets:
@@ -108,7 +102,7 @@ if __name__ == '__main__':
   run_unit_tests = False
   run_integ_tests = False
   config_path = None
-  integ_targets = TARGETS.values()
+  test_config = stem.util.conf.get_config("test")
   
   # parses user input, noting any issues
   try:
@@ -120,7 +114,7 @@ if __name__ == '__main__':
   for opt, arg in opts:
     if opt in ("-u", "--unit"): run_unit_tests = True
     elif opt in ("-i", "--integ"): run_integ_tests = True
-    elif opt in ("-c", "--config"): config_path = arg
+    elif opt in ("-c", "--config"): config_path = os.path.abspath(arg)
     elif opt in ("-t", "--targets"):
       integ_targets = arg.split(",")
       
@@ -130,19 +124,23 @@ if __name__ == '__main__':
         sys.exit(1)
       
       for target in integ_targets:
-        if not target in TARGETS.values():
+        if not target in TARGETS:
           print "Invalid integration target: %s" % target
           sys.exit(1)
+        else:
+          # sets the configuration flag
+          config_flag = TARGET_ATTR[target][0]
+          test_config.set(config_flag, "true")
     elif opt in ("-h", "--help"):
       # Prints usage information and quits. This includes a listing of the
       # valid integration targets.
       
       # gets the longest target length so we can show the entries in columns
-      target_name_length = max([len(name) for name in TARGETS.values()])
+      target_name_length = max([len(name) for name in TARGETS])
       description_format = "%%-%is - %%s" % target_name_length
       
       target_lines = []
-      for target in TARGETS.values():
+      for target in TARGETS:
         target_lines.append(description_format % (target, TARGET_ATTR[target][1]))
       
       print HELP_MSG % "\n    ".join(target_lines)
@@ -152,6 +150,28 @@ if __name__ == '__main__':
     print "Nothing to run (for usage provide --help)\n"
     sys.exit()
   
+  if config_path:
+    print_divider("TESTING CONFIG", True)
+    print
+    
+    try:
+      sys.stdout.write(term.format("Loading test configuration (%s)... " % config_path, term.Color.BLUE, term.Attr.BOLD))
+      test_config.load(config_path)
+      sys.stdout.write(term.format("done\n", term.Color.BLUE, term.Attr.BOLD))
+      
+      for config_key in test_config.keys():
+        key_entry = "  %s => " % config_key
+        
+        # if there's multiple values then list them on separate lines
+        value_div = ",\n" + (" " * len(key_entry))
+        value_entry = value_div.join(test_config.get_value(config_key, multiple = True))
+        
+        sys.stdout.write(term.format(key_entry + value_entry + "\n", term.Color.BLUE))
+    except IOError, exc:
+      sys.stdout.write(term.format("failed (%s)\n" % exc, term.Color.RED, term.Attr.BOLD))
+    
+    print
+  
   if run_unit_tests:
     print_divider("UNIT TESTS", True)
     
@@ -167,44 +187,38 @@ if __name__ == '__main__':
   
   if run_integ_tests:
     print_divider("INTEGRATION TESTS", True)
-    
     integ_runner = test.runner.get_runner()
     stem_logger = logging.getLogger("stem")
     
-    try:
-      # TODO: note unused config options afterward
-      integ_runner.start(config_path = config_path)
-      
-      print term.format("Running tests...", term.Color.BLUE, term.Attr.BOLD)
-      print
-      
-      for name, test_class in INTEG_TESTS:
-        print_divider(name)
-        stem_logger.info("STARTING INTEGRATION TEST => %s" % name)
-        suite = unittest.TestLoader().loadTestsFromTestCase(test_class)
-        test_results = StringIO.StringIO()
-        unittest.TextTestRunner(test_results, verbosity=2).run(suite)
-        print_test_results(test_results)
+    # just a single integ run with the default connection method unless we've
+    # set the 'CONNECTION' target, in which case we run 'em all
+    
+    if test_config.get("test.integ.target.connection", False):
+      connection_types = list(test.runner.TorConnection)
+    else:
+      connection_types = [test.runner.DEFAULT_TOR_CONNECTION]
+    
+    for connection_type in connection_types:
+      try:
+        integ_runner.start(connection_type = connection_type)
+        
+        print term.format("Running tests...", term.Color.BLUE, term.Attr.BOLD)
         print
-    except OSError:
-      pass
-    finally:
-      integ_runner.stop()
+        
+        for name, test_class in INTEG_TESTS:
+          print_divider(name)
+          stem_logger.info("STARTING INTEGRATION TEST => %s" % name)
+          suite = unittest.TestLoader().loadTestsFromTestCase(test_class)
+          test_results = StringIO.StringIO()
+          unittest.TextTestRunner(test_results, verbosity=2).run(suite)
+          print_test_results(test_results)
+          print
+      except OSError:
+        pass
+      finally:
+        integ_runner.stop()
     
-    # TODO: we might do target selection later but for now we should simply
-    # work with a single simple tor instance and see how it works out
-    #
-    #for target in integ_targets:
-    #  runner, description = TARGET_ATTR[target]
-    #  
-    #  print "Configuration: %s - %s" % (target, description)
-    #  
-    #  if runner:
-    #    pass # TODO: implement
-    #  else:
-    #    print "  %s" % term.format("Unimplemented", term.Color.RED, term.Attr.BOLD)
-    #  
-    #  print ""
+    # TODO: note unused config options afterward?
   
   print term.format("Testing Completed (%i seconds)" % (time.time() - start_time), term.Color.GREEN, term.Attr.BOLD)
   print
diff --git a/stem/types.py b/stem/types.py
index 45b040f..4415598 100644
--- a/stem/types.py
+++ b/stem/types.py
@@ -537,4 +537,5 @@ class Version:
 
 # TODO: version requirements will probably be moved to another module later
 REQ_GETINFO_CONFIG_TEXT = Version("0.2.2.7-alpha")
+REQ_CONTROL_SOCKET = Version("0.2.0.30")
 
diff --git a/stem/util/term.py b/stem/util/term.py
index 99c37fc..ef8d1b6 100644
--- a/stem/util/term.py
+++ b/stem/util/term.py
@@ -11,8 +11,8 @@ BgColor = stem.util.enum.Enum(*["BG_" + color for color in TERM_COLORS])
 Attr = stem.util.enum.Enum("BOLD", "UNDERLINE", "HILIGHT")
 
 # mappings of terminal attribute enums to their ANSI escape encoding
-FG_ENCODING = dict([(Color.values()[i], str(30 + i)) for i in range(8)])
-BG_ENCODING = dict([(BgColor.values()[i], str(40 + i)) for i in range(8)])
+FG_ENCODING = dict([(list(Color)[i], str(30 + i)) for i in range(8)])
+BG_ENCODING = dict([(list(BgColor)[i], str(40 + i)) for i in range(8)])
 ATTR_ENCODING = {Attr.BOLD: "1", Attr.UNDERLINE: "4", Attr.HILIGHT: "7"}
 
 CSI = "\x1B[%sm"
diff --git a/test/integ/connection/protocolinfo_response.py b/test/integ/connection/protocolinfo_response.py
index 67c0582..dfb5566 100644
--- a/test/integ/connection/protocolinfo_response.py
+++ b/test/integ/connection/protocolinfo_response.py
@@ -21,9 +21,8 @@ class TestProtocolInfoResponse(unittest.TestCase):
     """
     
     runner = test.runner.get_runner()
-    
-    control_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-    control_socket.connect(("127.0.0.1", runner.get_control_port()))
+    control_socket = runner.get_tor_socket(False)
+    if not control_socket: self.skipTest("(no control socket)")
     control_socket_file = control_socket.makefile()
     
     control_socket_file.write("PROTOCOLINFO\r\n")
@@ -39,6 +38,9 @@ class TestProtocolInfoResponse(unittest.TestCase):
     self.assertNotEqual(None, protocolinfo_response.tor_version)
     self.assertNotEqual(None, protocolinfo_response.auth_methods)
     
+    if runner.get_connection_type() != test.runner.TorConnection.NO_AUTH:
+      self.skipTest("(haven't yet implemented...)") # TODO: implement
+    
     # TODO: The following is for the default integ test configuration. We
     # should run tests that exercise all of tor's startup configs
     # (password/cookie auth and control sockets)
diff --git a/test/integ/types/control_message.py b/test/integ/types/control_message.py
index 07259e7..4aa878f 100644
--- a/test/integ/types/control_message.py
+++ b/test/integ/types/control_message.py
@@ -19,7 +19,9 @@ class TestControlMessage(unittest.TestCase):
     Checks message parsing when we have a valid but unauthenticated socket.
     """
     
-    control_socket, control_socket_file = self._get_control_socket(False)
+    control_socket = test.runner.get_runner().get_tor_socket(False)
+    if not control_socket: self.skipTest("(no control socket)")
+    control_socket_file = control_socket.makefile()
     
     # If an unauthenticated connection gets a message besides AUTHENTICATE or
     # PROTOCOLINFO then tor will give an 'Authentication required.' message and
@@ -35,10 +37,14 @@ class TestControlMessage(unittest.TestCase):
     self.assertEquals([("514", " ", "Authentication required.")], auth_required_response.content())
     
     # The socket's broken but doesn't realize it yet. Send another message and
-    # it should fail with a closed exception.
+    # it should fail with a closed exception. With a control port we won't get
+    # an error until we read from the socket. However, with a control socket
+    # the flush will raise a socket.error.
     
-    control_socket_file.write("GETINFO version\r\n")
-    control_socket_file.flush()
+    try:
+      control_socket_file.write("GETINFO version\r\n")
+      control_socket_file.flush()
+    except: pass
     
     self.assertRaises(stem.types.SocketClosed, stem.types.read_message, control_socket_file)
     
@@ -80,7 +86,9 @@ class TestControlMessage(unittest.TestCase):
     Parses the response for a command which doesn't exist.
     """
     
-    control_socket, control_socket_file = self._get_control_socket()
+    control_socket = test.runner.get_runner().get_tor_socket()
+    if not control_socket: self.skipTest("(no control socket)")
+    control_socket_file = control_socket.makefile()
     
     control_socket_file.write("blarg\r\n")
     control_socket_file.flush()
@@ -99,7 +107,9 @@ class TestControlMessage(unittest.TestCase):
     Parses the response for a GETINFO query which doesn't exist.
     """
     
-    control_socket, control_socket_file = self._get_control_socket()
+    control_socket = test.runner.get_runner().get_tor_socket()
+    if not control_socket: self.skipTest("(no control socket)")
+    control_socket_file = control_socket.makefile()
     
     control_socket_file.write("GETINFO blarg\r\n")
     control_socket_file.flush()
@@ -121,7 +131,9 @@ class TestControlMessage(unittest.TestCase):
     runner = test.runner.get_runner()
     torrc_dst = runner.get_torrc_path()
     
-    control_socket, control_socket_file = self._get_control_socket()
+    control_socket = runner.get_tor_socket()
+    if not control_socket: self.skipTest("(no control socket)")
+    control_socket_file = control_socket.makefile()
     
     control_socket_file.write("GETINFO config-file\r\n")
     control_socket_file.flush()
@@ -158,7 +170,9 @@ class TestControlMessage(unittest.TestCase):
       if line and not line.startswith("#"):
         torrc_contents.append(line)
     
-    control_socket, control_socket_file = self._get_control_socket()
+    control_socket = runner.get_tor_socket()
+    if not control_socket: self.skipTest("(no control socket)")
+    control_socket_file = control_socket.makefile()
     
     control_socket_file.write("GETINFO config-text\r\n")
     control_socket_file.flush()
@@ -188,7 +202,9 @@ class TestControlMessage(unittest.TestCase):
     Issues 'SETEVENTS BW' and parses a few events.
     """
     
-    control_socket, control_socket_file = self._get_control_socket()
+    control_socket = test.runner.get_runner().get_tor_socket()
+    if not control_socket: self.skipTest("(no control socket)")
+    control_socket_file = control_socket.makefile()
     
     control_socket_file.write("SETEVENTS BW\r\n")
     control_socket_file.flush()
@@ -209,34 +225,4 @@ class TestControlMessage(unittest.TestCase):
     
     control_socket.close()
     control_socket_file.close()
-  
-  def _get_control_socket(self, authenticate = True):
-    """
-    Provides a socket connected to the tor test instance's control port.
-    
-    Arguments:
-      authenticate (bool) - if True then the socket is authenticated
-    
-    Returns:
-      (socket.socket, file) tuple with the control socket and its file
-    """
-    
-    runner = test.runner.get_runner()
-    
-    control_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-    control_socket.connect(("127.0.0.1", runner.get_control_port()))
-    control_socket_file = control_socket.makefile()
-    
-    if authenticate:
-      control_socket_file.write("AUTHENTICATE\r\n")
-      control_socket_file.flush()
-      
-      authenticate_response = stem.types.read_message(control_socket_file)
-      
-      self.assertEquals("OK", str(authenticate_response))
-      self.assertEquals(["OK"], list(authenticate_response))
-      self.assertEquals("250 OK\r\n", authenticate_response.raw_content())
-      self.assertEquals([("250", " ", "OK")], authenticate_response.content())
-    
-    return (control_socket, control_socket_file)
 
diff --git a/test/integ/util/conf.py b/test/integ/util/conf.py
index a74406f..0b4cbc8 100644
--- a/test/integ/util/conf.py
+++ b/test/integ/util/conf.py
@@ -55,4 +55,6 @@ class TestConf(unittest.TestCase):
     self.assertEquals("1.2.3.4", ssh_config["destination.ip"])
     self.assertEquals(22, ssh_config["destination.port"])
     self.assertEquals(["export PATH=$PATH:~/bin", "alias l=ls"], ssh_config["startup.run"])
+    
+    user_config.clear()
 
diff --git a/test/integ/util/system.py b/test/integ/util/system.py
index c246d55..9785400 100644
--- a/test/integ/util/system.py
+++ b/test/integ/util/system.py
@@ -111,8 +111,10 @@ class TestSystem(unittest.TestCase):
     Checks general usage of the stem.util.system.get_pid_by_port function.
     """
     
-    runner = test.runner.get_runner()
-    tor_pid, tor_port = runner.get_pid(), runner.get_control_port()
+    if not self._has_port():
+      self.skipTest("(test instance has no port)")
+    
+    tor_pid, tor_port = test.runner.get_runner().get_pid(), test.runner.CONTROL_PORT
     self.assertEquals(tor_pid, stem.util.system.get_pid_by_port(tor_port))
     self.assertEquals(None, stem.util.system.get_pid_by_port(99999))
   
@@ -121,38 +123,41 @@ class TestSystem(unittest.TestCase):
     Tests the get_pid_by_port function with a netstat response.
     """
     
-    if not stem.util.system.is_available("netstat"):
+    if not self._has_port():
+      self.skipTest("(test instance has no port)")
+    elif not stem.util.system.is_available("netstat"):
       self.skipTest("(netstat unavailable)")
     elif stem.util.system.is_bsd(): self.skipTest("(linux only)")
     
     netstat_cmd = stem.util.system.GET_PID_BY_PORT_NETSTAT
-    runner_port = test.runner.get_runner().get_control_port()
-    self._run_pid_test(netstat_cmd, stem.util.system.get_pid_by_port, runner_port)
+    self._run_pid_test(netstat_cmd, stem.util.system.get_pid_by_port, test.runner.CONTROL_PORT)
   
   def test_get_pid_by_port_sockstat(self):
     """
     Tests the get_pid_by_port function with a sockstat response.
     """
     
-    if not stem.util.system.is_available("sockstat"):
+    if not self._has_port():
+      self.skipTest("(test instance has no port)")
+    elif not stem.util.system.is_available("sockstat"):
       self.skipTest("(sockstat unavailable)")
     elif not stem.util.system.is_bsd(): self.skipTest("(bsd only)")
     
     sockstat_prefix = stem.util.system.GET_PID_BY_PORT_SOCKSTAT % ""
-    runner_port = test.runner.get_runner().get_control_port()
-    self._run_pid_test(sockstat_prefix, stem.util.system.get_pid_by_port, runner_port)
+    self._run_pid_test(sockstat_prefix, stem.util.system.get_pid_by_port, test.runner.CONTROL_PORT)
   
   def test_get_pid_by_port_lsof(self):
     """
     Tests the get_pid_by_port function with a lsof response.
     """
     
-    if not stem.util.system.is_available("lsof"):
+    if not self._has_port():
+      self.skipTest("(test instance has no port)")
+    elif not stem.util.system.is_available("lsof"):
       self.skipTest("(lsof unavailable)")
     
     lsof_cmd = stem.util.system.GET_PID_BY_PORT_LSOF
-    runner_port = test.runner.get_runner().get_control_port()
-    self._run_pid_test(lsof_cmd, stem.util.system.get_pid_by_port, runner_port)
+    self._run_pid_test(lsof_cmd, stem.util.system.get_pid_by_port, test.runner.CONTROL_PORT)
   
   def test_get_pid_by_open_file(self):
     """
@@ -239,4 +244,12 @@ class TestSystem(unittest.TestCase):
     
     runner_pid = test.runner.get_runner().get_pid()
     self.assertEquals(runner_pid, test_function(arg))
+  
+  def _has_port(self):
+    """
+    True if our test runner has a control port, False otherwise.
+    """
+    
+    connection_type = runner = test.runner.get_runner().get_connection_type()
+    return test.runner.OPT_PORT in test.runner.CONNECTION_OPTS[connection_type]
 
diff --git a/test/runner.py b/test/runner.py
index 80f481b..e39bd92 100644
--- a/test/runner.py
+++ b/test/runner.py
@@ -12,16 +12,19 @@ Runner - Runtime context for our integration tests.
   |- is_running - checks if our tor test instance is running
   |- get_torrc_path - path to our tor instance's torrc
   |- get_torrc_contents - contents of our tor instance's torrc
-  |- get_control_port - port that our tor instance is listening on
-  +- get_pid - process id of our tor process
+  |- get_connection_type - method by which controllers can connect to tor
+  |- get_pid - process id of our tor process
+  +- get_tor_socket - provides a socket to the tor instance
 """
 
 import os
 import sys
 import time
+import socket
 import shutil
 import logging
 import tempfile
+import binascii
 import threading
 
 import stem.process
@@ -32,7 +35,7 @@ import stem.util.term as term
 DEFAULT_CONFIG = {
   "test.integ.test_directory": "./test/data",
   "test.integ.log": "./test/data/log",
-  "test.integ.run.online": False,
+  "test.integ.target.online": False,
 }
 
 # Methods for connecting to tor. General integration tests only run with the
@@ -67,6 +70,17 @@ OPT_COOKIE = "CookieAuthentication 1"
 OPT_PASSWORD = "HashedControlPassword 16:8C423A41EF4A542C6078985270AE28A4E04D056FB63F9F201505DB8E06"
 OPT_SOCKET = "ControlSocket %s" % CONTROL_SOCKET_PATH
 
+# mapping of TorConnection to their options
+
+CONNECTION_OPTS = {
+  TorConnection.NONE: [],
+  TorConnection.NO_AUTH: [OPT_PORT],
+  TorConnection.PASSWORD: [OPT_PORT, OPT_PASSWORD],
+  TorConnection.COOKIE: [OPT_PORT, OPT_COOKIE],
+  TorConnection.MULTIPLE: [OPT_PORT, OPT_PASSWORD, OPT_COOKIE],
+  TorConnection.SOCKET: [OPT_SOCKET],
+}
+
 def get_runner():
   """
   Singleton for the runtime context of integration tests.
@@ -82,25 +96,11 @@ def get_torrc(connection_type = DEFAULT_TOR_CONNECTION):
   for "pw".
   """
   
-  connection_opt, torrc = [], BASE_TORRC
-  
-  if connection_type == TorConnection.NONE:
-    pass
-  elif connection_type == TorConnection.NO_AUTH:
-    connection_opt = [OPT_PORT]
-  elif connection_type == TorConnection.PASSWORD:
-    connection_opt = [OPT_PORT, OPT_PASSWORD]
-  elif connection_type == TorConnection.COOKIE:
-    connection_opt = [OPT_PORT, OPT_COOKIE]
-  elif connection_type == TorConnection.MULTIPLE:
-    connection_opt = [OPT_PORT, OPT_PASSWORD, OPT_COOKIE]
-  elif connection_type == TorConnection.SOCKET:
-    connection_opt = [OPT_SOCKET]
+  connection_opt, torrc = CONNECTION_OPTS[connection_type], BASE_TORRC
   
   if connection_opt:
-    torrc += "\n" + "\n".join(connection_opt)
-  
-  return torrc
+    return torrc + "\n".join(connection_opt) + "\n"
+  else: return torrc
 
 class RunnerStopped(Exception):
   "Raised when we try to use a Runner that doesn't have an active tor instance"
@@ -114,9 +114,10 @@ class Runner:
     # runtime attributes, set by the start method
     self._test_dir = ""
     self._torrc_contents = ""
+    self._connection_type = None
     self._tor_process = None
   
-  def start(self, connection_type = DEFAULT_TOR_CONNECTION, quiet = False, config_path = None):
+  def start(self, connection_type = DEFAULT_TOR_CONNECTION, quiet = False):
     """
     Makes temporary testing resources and starts tor, blocking until it
     completes.
@@ -126,7 +127,6 @@ class Runner:
                           to tor
       quiet (bool)      - if False then this prints status information as we
                           start up to stdout
-      config_path (str) - path to a custom test configuration
     
     Raises:
       OSError if unable to run test preparations or start tor
@@ -134,15 +134,15 @@ class Runner:
     
     self._runner_lock.acquire()
     
+    test_config = stem.util.conf.get_config("test")
+    test_config.update(self._config)
+    
     # if we're holding on to a tor process (running or not) then clean up after
     # it so we can start a fresh instance
     if self._tor_process: self.stop(quiet)
     
     _print_status("Setting up a test instance...\n", STATUS_ATTR, quiet)
     
-    # load and apply any custom configurations
-    self._load_config(config_path, quiet)
-    
     # if 'test_directory' is unset then we make a new data directory in /tmp
     # and clean it up when we're done
     
@@ -153,6 +153,7 @@ class Runner:
     else:
       self._test_dir = tempfile.mktemp("-stem-integ")
     
+    self._connection_type = connection_type
     self._torrc_contents = get_torrc(connection_type) % self._test_dir
     
     try:
@@ -185,6 +186,7 @@ class Runner:
     
     self._test_dir = ""
     self._torrc_contents = ""
+    self._connection_type = None
     self._tor_process = None
     
     _print_status("done\n", STATUS_ATTR, quiet)
@@ -241,33 +243,16 @@ class Runner:
     
     return self._get("_torrc_contents")
   
-  def get_control_port(self):
+  def get_connection_type(self):
     """
-    Provides the control port tor is running with.
+    Provides the method we can use for connecting to the tor instance.
     
     Returns:
-      int for the port tor's controller interface is bound to, None if it
-      doesn't have one
-    
-    Raises:
-      RunnerStopped if we aren't running
-      ValueError if our torrc has a malformed ControlPort entry
+      test.runner.TorConnection enumeration for the method we can use for
+      connecting to the tor test instance
     """
     
-    torrc_contents = self.get_torrc_contents()
-    
-    for line in torrc_contents.split("\n"):
-      line_comp = line.strip().split()
-      if not line_comp: continue
-      
-      if line_comp[0] == "ControlPort":
-        if len(line_comp) == 2 and line_comp[1].isdigit():
-          return int(line_comp[1])
-        else:
-          raise ValueError("Malformed ControlPort entry: %s" % line)
-    
-    # torrc doesn't have a ControlPort
-    return None
+    return self._connection_type
   
   def get_pid(self):
     """
@@ -283,6 +268,59 @@ class Runner:
     tor_process = self._get("_tor_process")
     return tor_process.pid
   
+  def get_tor_socket(self, authenticate = True):
+    """
+    Provides a socket connected to the tor test instance's control socket.
+    
+    Arguments:
+      authenticate (bool) - if True then the socket is authenticated
+    
+    Returns:
+      socket.socket connected with our testing instance, returning None if we
+      either don't have a test instance or it can't be connected to
+    """
+    
+    # TODO: replace with higher level connection functions when we have them
+    
+    connection_type, test_dir = self.get_connection_type(), self._get("_test_dir")
+    if connection_type == None: return None
+    
+    conn_opts = CONNECTION_OPTS[connection_type]
+    
+    if OPT_PORT in conn_opts:
+      control_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+      control_socket.connect(("127.0.0.1", CONTROL_PORT))
+    elif OPT_SOCKET in conn_opts:
+      control_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+      control_socket.connect(CONTROL_SOCKET_PATH)
+    else: return None
+    
+    if authenticate:
+      control_socket_file = control_socket.makefile()
+      
+      if OPT_COOKIE in conn_opts:
+        cookie_path = os.path.join(test_dir, "control_auth_cookie")
+        
+        auth_cookie = open(cookie_path, "r")
+        auth_cookie_contents = auth_cookie.read()
+        auth_cookie.close()
+        
+        control_socket_file.write("AUTHENTICATE %s\r\n" % binascii.b2a_hex(auth_cookie_contents))
+      elif OPT_PASSWORD in conn_opts:
+        control_socket_file.write("AUTHENTICATE \"%s\"\r\n" % CONTROL_PASSWORD)
+      else:
+        control_socket_file.write("AUTHENTICATE\r\n")
+      
+      control_socket_file.flush()
+      authenticate_response = stem.types.read_message(control_socket_file)
+      control_socket_file.close()
+      
+      if str(authenticate_response) != "OK":
+        # authentication was rejected
+        logging.error("AUTHENTICATE returned a failure response: %s" % authenticate_response)
+    
+    return control_socket
+  
   def _get(self, attr):
     """
     Fetches one of our attributes in a thread safe manner, raising if we aren't
@@ -307,45 +345,6 @@ class Runner:
     finally:
       self._runner_lock.release()
   
-  def _load_config(self, config_path, quiet):
-    """
-    Loads the given configuration file, printing the contents if successful and
-    the exception if not.
-    
-    Arguments:
-      config_path (str) - path to custom testing configuration options, skipped
-                          if None
-      quiet (bool)      - prints status information to stdout if False
-    """
-    
-    if not config_path:
-      _print_status("  loading test configuration... skipped\n", STATUS_ATTR, quiet)
-    else:
-      # loads custom testing configuration
-      test_config = stem.util.conf.get_config("test")
-      config_path = os.path.abspath(config_path)
-      
-      try:
-        _print_status("  loading test configuration (%s)... " % config_path, STATUS_ATTR, quiet)
-        
-        test_config.load(config_path)
-        test_config.update(self._config)
-        
-        _print_status("done\n", STATUS_ATTR, quiet)
-        
-        for config_key in test_config.keys():
-          key_entry = "    %s => " % config_key
-          
-          # if there's multiple values then list them on separate lines
-          value_div = ",\n" + (" " * len(key_entry))
-          value_entry = value_div.join(test_config.get_value(config_key, multiple = True))
-          
-          _print_status(key_entry + value_entry + "\n", SUBSTATUS_ATTR, quiet)
-        
-        _print_status("\n", (), quiet)
-      except IOError, exc:
-        _print_status("failed (%s)\n" % exc, ERROR_ATTR, quiet)
-  
   def _run_setup(self, quiet):
     """
     Makes a temporary runtime resources of our integration test instance.
@@ -378,7 +377,8 @@ class Runner:
       _print_status("  configuring logger (%s)... " % logging_path, STATUS_ATTR, quiet)
       
       # delete the old log
-      if os.path.exists: os.remove(logging_path)
+      if os.path.exists(logging_path):
+        os.remove(logging_path)
       
       logging.basicConfig(
         filename = logging_path,
@@ -389,7 +389,6 @@ class Runner:
       
       stem_logger = logging.getLogger("stem")
       stem_logger.info("Logging opened for integration test run")
-      #datefmt='%m/%d/%Y %I:%M:%S %p',
       
       _print_status("done\n", STATUS_ATTR, quiet)
     else:
@@ -433,7 +432,7 @@ class Runner:
     try:
       # wait to fully complete if we're running tests with network activity,
       # otherwise finish after local bootstraping
-      complete_percent = 100 if self._config["test.integ.run.online"] else 5
+      complete_percent = 100 if self._config["test.integ.target.online"] else 5
       
       # prints output from tor's stdout while it starts up
       print_init_line = lambda line: _print_status("  %s\n" % line, SUBSTATUS_ATTR, quiet)
diff --git a/test/testrc.sample b/test/testrc.sample
index 47e3862..83f0861 100644
--- a/test/testrc.sample
+++ b/test/testrc.sample
@@ -12,11 +12,16 @@
 #   Path runtime logs are placed. Relative paths are expanded in reference to
 #   'run_tests.py'. Logging is disabled if set ot an empty value.
 #
-# test.integ.run.online
+# test.integ.target.online
 #   Runs tests with network activity. If set then we'll wait for tor to fully
 #   bootstrap when starting, which won't happen without a network connection.
+#
+# test.integ.target.connection
+#   Runs integration tests for multiple connection and authentiction methods if
+#   true. Otherwise they'll only be run for a single connection type.
 
 test.integ.test_directory ./test/data
 test.integ.log ./test/data/log
-test.integ.run.online false
+test.integ.target.online false
+test.integ.target.connection false
 



More information about the tor-commits mailing list