commit d02aa3e9128e92982e6f2760594523556bd45e85
Author: Damian Johnson <atagar(a)torproject.org>
Date: Wed Dec 28 09:24:35 2011 -0800
Moving python testing output filters to module
Rewriting the hacks I was applying to test output for better readability as a
proper module. Nicer code and now much easier to add more filters.
---
run_tests.py | 34 ++++++--------------
test/__init__.py | 2 +-
test/output.py | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 104 insertions(+), 25 deletions(-)
diff --git a/run_tests.py b/run_tests.py
index 7247dd7..7e3dadb 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -12,6 +12,7 @@ import logging
import unittest
import StringIO
+import test.output
import test.runner
import test.unit.version
import test.unit.socket.control_message
@@ -94,30 +95,10 @@ TEST_OUTPUT_ATTR = {
"... skipped": (term.Color.BLUE,),
}
-# lines that print_test_results have processed which contained a test failure
-PRINTED_ERRORS = []
-
def print_divider(msg, is_header = False):
attr = HEADER_ATTR if is_header else CATEGORY_ATTR
print term.format("%s\n%s\n%s\n" % (DIVIDER, msg.center(70), DIVIDER), *attr)
-def print_test_results(test_results):
- test_results.seek(0)
- for line in test_results.readlines():
- line_attr = DEFAULT_TEST_ATTR
-
- for result in TEST_OUTPUT_ATTR:
- if result in line:
- line_attr = TEST_OUTPUT_ATTR[result]
-
- if result in ("... FAIL", "... ERROR"):
- PRINTED_ERRORS.append(line)
-
- break
-
- if line_attr: line = term.format(line, *line_attr)
- sys.stdout.write(line)
-
if __name__ == '__main__':
start_time = time.time()
run_unit_tests = False
@@ -193,6 +174,9 @@ if __name__ == '__main__':
print
+ error_tracker = test.output.ErrorTracker()
+ output_filters = (error_tracker.get_filter(), test.output.colorize, )
+
if run_unit_tests:
print_divider("UNIT TESTS", True)
@@ -201,7 +185,8 @@ if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(test_class)
test_results = StringIO.StringIO()
unittest.TextTestRunner(test_results, verbosity=2).run(suite)
- print_test_results(test_results)
+
+ sys.stdout.write(test.output.apply_filters(test_results.getvalue(), *output_filters))
print
print
@@ -251,7 +236,8 @@ if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(test_class)
test_results = StringIO.StringIO()
unittest.TextTestRunner(test_results, verbosity=2).run(suite)
- print_test_results(test_results)
+
+ sys.stdout.write(test.output.apply_filters(test_results.getvalue(), *output_filters))
print
except OSError:
pass
@@ -262,10 +248,10 @@ if __name__ == '__main__':
runtime_label = "(%i seconds)" % (time.time() - start_time)
- if PRINTED_ERRORS:
+ if error_tracker.has_error_occured():
print term.format("TESTING FAILED %s" % runtime_label, term.Color.RED, term.Attr.BOLD)
- for line in PRINTED_ERRORS:
+ for line in error_tracker:
print term.format(" %s" % line, term.Color.RED, term.Attr.BOLD)
else:
print term.format("TESTING PASSED %s" % runtime_label, term.Color.GREEN, term.Attr.BOLD)
diff --git a/test/__init__.py b/test/__init__.py
index 9afce0a..a663300 100644
--- a/test/__init__.py
+++ b/test/__init__.py
@@ -2,5 +2,5 @@
Unit and integration tests for the stem library.
"""
-__all__ = ["runner"]
+__all__ = ["output", "runner"]
diff --git a/test/output.py b/test/output.py
new file mode 100644
index 0000000..4b53e52
--- /dev/null
+++ b/test/output.py
@@ -0,0 +1,93 @@
+"""
+Variety of filters for the python unit testing output, which can be chained
+together for improved readability.
+"""
+
+import stem.util.enum
+import stem.util.term as term
+
+LineType = stem.util.enum.Enum("OK", "FAIL", "ERROR", "SKIPPED", "CONTENT")
+
+LINE_ENDINGS = {
+ "... ok": LineType.OK,
+ "... FAIL": LineType.FAIL,
+ "... ERROR": LineType.ERROR,
+ "... skipped": LineType.SKIPPED,
+}
+
+LINE_ATTR = {
+ LineType.OK: (term.Color.GREEN,),
+ LineType.FAIL: (term.Color.RED, term.Attr.BOLD),
+ LineType.ERROR: (term.Color.RED, term.Attr.BOLD),
+ LineType.SKIPPED: (term.Color.BLUE,),
+ LineType.CONTENT: (term.Color.CYAN,),
+}
+
+def apply_filters(testing_output, *filters):
+ """
+ Gets the tests results, possably processed through a series of filters. The
+ filters are applied in order, each getting the output of the previous.
+
+ A filter's input arguments should be the line's (type, content) and the
+ output is either a string with the new content or None if the line should be
+ omitted.
+
+ Arguments:
+ testing_output (str) - output from the unit testing
+ filters (list) - functors to be applied to each line of the results
+
+ Returns:
+ str with the processed test results
+ """
+
+ results = []
+
+ for line in testing_output.split("\n"):
+ # determine the type of the line
+ line_type = LineType.CONTENT
+
+ for ending in LINE_ENDINGS:
+ if line.endswith(ending):
+ line_type = LINE_ENDINGS[ending]
+ break
+
+ for result_filter in filters:
+ line = result_filter(line_type, line)
+ if line == None: break
+
+ if line != None:
+ results.append(line)
+
+ return "\n".join(results)
+
+def colorize(line_type, line_content):
+ """
+ Applies escape sequences so each line is colored according to its type.
+ """
+
+ return term.format(line_content, *LINE_ATTR[line_type])
+
+class ErrorTracker:
+ """
+ Stores any failure or error results we've encountered.
+ """
+
+ def __init__(self):
+ self._errors = []
+
+ def has_error_occured(self):
+ return bool(self._errors)
+
+ def get_filter(self):
+ def _error_tracker(line_type, line_content):
+ if line_type in (LineType.FAIL, LineType.ERROR):
+ self._errors.append(line_content)
+
+ return line_content
+
+ return _error_tracker
+
+ def __iter__(self):
+ for error_line in self._errors:
+ yield error_line
+