[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