[tor-commits] [stem/master] Running tests based on input arguments

atagar at torproject.org atagar at torproject.org
Sat Oct 8 23:33:50 UTC 2011


commit 3dd9ea0d9222ddf22e63c7f5dcf8e7032e6fb494
Author: Damian Johnson <atagar at torproject.org>
Date:   Sat Oct 8 16:04:08 2011 -0700

    Running tests based on input arguments
    
    Making the test runner accept arguments for the type of tests to be ran. The
    integration tests especially will take a while when they're implemented so
    letting the user specify the use cases for those.
    
    This included copying and refactoring some basic utilities from arm for
    enumerations and terminal text attributes.
---
 run_tests.py          |  102 ++++++++++++++++++++++++++++++++-
 stem/types.py         |    2 +-
 stem/util/__init__.py |    6 ++
 stem/util/enum.py     |  150 +++++++++++++++++++++++++++++++++++++++++++++++++
 stem/util/term.py     |   57 +++++++++++++++++++
 5 files changed, 314 insertions(+), 3 deletions(-)

diff --git a/run_tests.py b/run_tests.py
index 9debfc2..e1c0811 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -4,10 +4,108 @@
 Runs unit and integration tests.
 """
 
+import sys
+import getopt
 import unittest
 import test.unit.version
 
+from stem.util import enum, term
+
+OPT = "uit:h"
+OPT_EXPANDED = ["unit", "integ", "targets=", "help"]
+DIVIDER = "=" * 80
+
+# Configurations that the intergration tests can be ran with. Attributs are
+# tuples of the test runner and description.
+TARGETS = enum.Enum(*[(v, v) for v in ("NONE", "NO_CONTROL", "NO_AUTH", "COOKIE", "PASSWORD", "SOCKET")])
+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."),
+}
+
+HELP_MSG = """Usage runTests.py [OPTION]
+Runs tests for the stem library.
+
+  -u, --unit      runs unit tests
+  -i, --integ     runs integration tests
+  -t, --target    comma separated list of tor configurations to use for the
+                    integration tests (all are used by default)
+  -h, --help      presents this help
+
+  Integration targets:
+    %s
+"""
+
 if __name__ == '__main__':
-  suite = unittest.TestLoader().loadTestsFromTestCase(test.unit.version.TestVerionFunctions)
-  unittest.TextTestRunner(verbosity=2).run(suite)
+  run_unit_tests = False
+  run_integ_tests = False
+  integ_targets = TARGETS.values()
+  
+  # parses user input, noting any issues
+  try:
+    opts, args = getopt.getopt(sys.argv[1:], OPT, OPT_EXPANDED)
+  except getopt.GetoptError, exc:
+    print str(exc) + " (for usage provide --help)"
+    sys.exit(1)
+  
+  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 ("-t", "--targets"):
+      integ_targets = arg.split(",")
+      
+      # validates the targets
+      if not integ_targets:
+        print "No targets provided"
+        sys.exit(1)
+      
+      for target in integ_targets:
+        if not target in TARGETS.values():
+          print "Invalid integration target: %s" % target
+          sys.exit(1)
+    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()])
+      description_format = "%%-%is - %%s" % target_name_length
+      
+      target_lines = []
+      for target in TARGETS.values():
+        target_lines.append(description_format % (target, TARGET_ATTR[target][1]))
+      
+      print HELP_MSG % "\n    ".join(target_lines)
+      sys.exit()
+  
+  if not run_unit_tests and not run_integ_tests:
+    print "Nothing to run (for usage provide --help)\n"
+    sys.exit()
+  
+  if run_unit_tests:
+    print "%s\nUnit Tests\n%s\n" % (DIVIDER, DIVIDER)
+    
+    suite = unittest.TestLoader().loadTestsFromTestCase(test.unit.version.TestVerionFunctions)
+    unittest.TextTestRunner(verbosity=2).run(suite)
+    
+    print ""
+  
+  if run_integ_tests:
+    print "%s\nIntegration Tests\n%s\n" % (DIVIDER, DIVIDER)
+    
+    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 ""
 
diff --git a/stem/types.py b/stem/types.py
index 58845fe..f4583e3 100644
--- a/stem/types.py
+++ b/stem/types.py
@@ -76,7 +76,7 @@ def get_version(version_str):
   Returns:
     types.Version instance
   
-  Throws:
+  Raises:
     ValueError if input isn't a valid tor version
   """
   
diff --git a/stem/util/__init__.py b/stem/util/__init__.py
new file mode 100644
index 0000000..e079d62
--- /dev/null
+++ b/stem/util/__init__.py
@@ -0,0 +1,6 @@
+"""
+Utility functions used by the stem library.
+"""
+
+__all__ = ["enum", "term"]
+
diff --git a/stem/util/enum.py b/stem/util/enum.py
new file mode 100644
index 0000000..d7745ec
--- /dev/null
+++ b/stem/util/enum.py
@@ -0,0 +1,150 @@
+"""
+Basic enumeration, providing ordered types for collections. These can be
+constructed as simple type listings, ie:
+>>> insects = Enum("ANT", "WASP", "LADYBUG", "FIREFLY")
+>>> insects.ANT
+'Ant'
+>>> insects.values()
+['Ant', 'Wasp', 'Ladybug', 'Firefly']
+
+with overwritten string counterparts:
+>>> pets = Enum(("DOG", "Skippy"), "CAT", ("FISH", "Nemo"))
+>>> pets.DOG
+'Skippy'
+>>> pets.CAT
+'Cat'
+
+or with entirely custom string components as an unordered enum with:
+>>> pets = LEnum(DOG="Skippy", CAT="Kitty", FISH="Nemo")
+>>> pets.CAT
+'Kitty'
+"""
+
+def to_camel_case(label, word_divider = " "):
+  """
+  Converts the given string to camel case, ie:
+  >>> to_camel_case("I_LIKE_PEPPERJACK!")
+  'I Like Pepperjack!'
+  
+  Arguments:
+    label (str)        - input string to be converted
+    word_divider (str) - string used to replace underscores
+  """
+  
+  words = []
+  for entry in label.split("_"):
+    if len(entry) == 0: words.append("")
+    elif len(entry) == 1: words.append(entry.upper())
+    else: words.append(entry[0].upper() + entry[1:].lower())
+  
+  return word_divider.join(words)
+
+class Enum:
+  """
+  Basic enumeration.
+  """
+  
+  def __init__(self, *args):
+    # ordered listings of our keys and values
+    keys, values = [], []
+    
+    for entry in args:
+      if isinstance(entry, str):
+        key, val = entry, to_camel_case(entry)
+      elif isinstance(entry, tuple) and len(entry) == 2:
+        key, val = entry
+      else: raise ValueError("Unrecognized input: %s" % args)
+      
+      keys.append(key)
+      values.append(val)
+      self.__dict__[key] = val
+    
+    self._keys = tuple(keys)
+    self._values = tuple(values)
+  
+  def keys(self):
+    """
+    Provides an ordered listing of the enumeration keys in this set.
+    
+    Returns:
+      tuple with our enum keys
+    """
+    
+    return self._keys
+  
+  def values(self):
+    """
+    Provides an ordered listing of the enumerations in this set.
+    
+    Returns:
+      tuple with our enum values
+    """
+    
+    return self._values
+  
+  def index_of(self, value):
+    """
+    Provides the index of the given value in the collection.
+    
+    Arguments:
+      value - entry to be looked up
+    
+    Returns:
+      integer index of the given entry
+    
+    Raises:
+      ValueError if no such element exists
+    """
+    
+    return self._values.index(value)
+  
+  def next(self, value):
+    """
+    Provides the next enumeration after the given value.
+    
+    Arguments:
+      value - enumeration for which to get the next entry
+    
+    Returns:
+      enum value following the given entry
+    
+    Raises:
+      ValueError if no such element exists
+    """
+    
+    if not value in self._values:
+      raise ValueError("No such enumeration exists: %s (options: %s)" % (value, ", ".join(self._values)))
+    
+    next_index = (self._values.index(value) + 1) % len(self._values)
+    return self._values[next_index]
+  
+  def previous(self, value):
+    """
+    Provides the previous enumeration before the given value.
+    
+    Arguments:
+      value - enumeration for which to get the previous entry
+    
+    Returns:
+      enum value proceeding the given entry
+    
+    Raises:
+      ValueError if no such element exists
+    """
+    
+    if not value in self._values:
+      raise ValueError("No such enumeration exists: %s (options: %s)" % (value, ", ".join(self._values)))
+    
+    prev_index = (self._values.index(value) - 1) % len(self._values)
+    return self._values[prev_index]
+
+class LEnum(Enum):
+  """
+  Enumeration that accepts custom string mappings.
+  """
+  
+  def __init__(self, **args):
+    Enum.__init__(self)
+    self.__dict__.update(args)
+    self._values = sorted(args.values())
+
diff --git a/stem/util/term.py b/stem/util/term.py
new file mode 100644
index 0000000..7f3449e
--- /dev/null
+++ b/stem/util/term.py
@@ -0,0 +1,57 @@
+"""
+Utilities for working with the terminal.
+"""
+
+import enum
+
+TERM_COLORS = ("BLACK", "RED", "GREEN", "YELLOW", "BLUE", "MAGENTA", "CYAN", "WHITE")
+
+Color = enum.Enum(*TERM_COLORS)
+BgColor = enum.Enum(*["BG_" + color for color in TERM_COLORS])
+Attr = 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)])
+ATTR_ENCODING = {Attr.BOLD: "1", Attr.UNDERLINE: "4", Attr.HILIGHT: "7"}
+
+CSI = "\x1B[%sm"
+RESET = CSI % "0"
+
+def format(msg, *attr):
+  """
+  Simple terminal text formatting, using ANSI escape sequences from:
+  https://secure.wikimedia.org/wikipedia/en/wiki/ANSI_escape_code#CSI_codes
+  
+  toolkits providing similar capabilities:
+  * django.utils.termcolors
+    https://code.djangoproject.com/browser/django/trunk/django/utils/termcolors.py
+  
+  * termcolor
+    http://pypi.python.org/pypi/termcolor
+  
+  * colorama
+    http://pypi.python.org/pypi/colorama
+  
+  Arguments:
+    msg (str)  - string to be formatted
+    attr (str) - text attributes, this can be Color, BgColor, or Attr enums and
+                 are case insensitive (so strings like "red" are fine)
+  
+  Returns:
+    string wrapped with ANSI escape encodings, starting with the given
+    attributes and ending with a reset
+  """
+  
+  encodings = []
+  for text_attr in attr:
+    text_attr, encoding = enum.to_camel_case(text_attr), None
+    encoding = FG_ENCODING.get(text_attr, encoding)
+    encoding = BG_ENCODING.get(text_attr, encoding)
+    encoding = ATTR_ENCODING.get(text_attr, encoding)
+    if encoding: encodings.append(encoding)
+  
+  if encodings:
+    return (CSI % ";".join(encodings)) + msg + RESET
+  else: return msg
+





More information about the tor-commits mailing list