Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

teach testify how to use alternative exception tracebacks

  • Loading branch information...
commit 71477b4c79ad601ea29b9d1befe973496dc2c55e 1 parent 869ca14
@eklitzke eklitzke authored
View
8 debian/changelog
@@ -1,7 +1,13 @@
+python-testify (0.1.11) unstable; urgency=low
+
+ * Teach testify how to use alternate exception tracebacks
+
+ -- Evan Klitzke <evan@eklitzke.org> Mon, 02 May 2011 17:48:43 -0700
+
python-testify (0.1.10) unstable; urgency=low
* Fix JSON logging to include proper test method class. Fixes #13 (sumeet)
- * Fix setup.py to not import testify, preventing some installation
+ * Fix setup.py to not import testify, preventing some installation
issues (rhettg)
* Remove required dependency on IPython. Fixes #9 (rhettg)
* Autodetect color support for test output. Fixes #4 (rhettg)
View
2  setup.py
@@ -2,7 +2,7 @@
setup(
name="testify",
- version="0.1.10",
+ version="0.1.11",
provides=["testify"],
author="Yelp",
author_email="yelplabs@yelp.com",
View
2  testify/__init__.py
@@ -25,7 +25,7 @@
to kindly execute themselves.
"""
__testify = 1
-__version__ = "0.1.10"
+__version__ = "0.1.11"
import sys
View
54 testify/test_case.py
@@ -21,12 +21,10 @@
__testify = 1
from collections import defaultdict
-import datetime
import inspect
import logging
from new import instancemethod
import sys
-import traceback
import types
from test_result import TestResult
@@ -36,21 +34,21 @@
# just a useful list to have
fixture_types = ['class_setup', 'setup', 'teardown', 'class_teardown']
deprecated_fixture_type_map = {
- 'classSetUp': 'class_setup',
- 'setUp': 'setup',
- 'tearDown': 'teardown',
+ 'classSetUp': 'class_setup',
+ 'setUp': 'setup',
+ 'tearDown': 'teardown',
'classTearDown': 'class_teardown'}
-class TwistedFailureError(Exception):
+class TwistedFailureError(Exception):
"""Exception that indicates the value is an instance of twisted.python.failure.Failure
-
+
This is part of the magic that's required to get a proper stack trace out of twisted applications
"""
pass
class MetaTestCase(type):
"""This base metaclass is used to collect each TestCase's decorated fixture methods at
- runtime. It is implemented as a metaclass so we can determine the order in which
+ runtime. It is implemented as a metaclass so we can determine the order in which
fixture methods are defined.
"""
__test__ = False
@@ -76,7 +74,7 @@ def _cmp_str(cls, instance):
def __cmp__(self, other):
"""Sort TestCases by a particular string representation."""
return cmp(MetaTestCase._cmp_str(self), MetaTestCase._cmp_str(other))
-
+
def bucket(self, bucket_count, bucket_salt=None):
"""Bucket a TestCase using a relatively consistant hash - for dividing tests across runners."""
if bucket_salt:
@@ -89,12 +87,12 @@ def discovered_test_cases():
class TestCase(object):
"""The TestCase class defines test methods and fixture methods; it is the meat and potatoes of testing.
-
- QuickStart:
+
+ QuickStart:
define a test method, instantiate an instance and call test_case.run()
Extended information:
- TestCases can contain any number of test methods, as well as class-level
+ TestCases can contain any number of test methods, as well as class-level
setup/teardown methods and setup/teardowns to be wrapped around each test
method. These are defined by decorators.
@@ -109,7 +107,7 @@ class TestCase(object):
class_teardown
The results of test methods are stored in TestResult objects.
-
+
Additional behavior beyond running tests, such as logging results, is achieved
by registered callbacks. For more information see the docstrings for:
register_on_complete_test_method_callback
@@ -123,7 +121,7 @@ class TestCase(object):
STAGE_TEST_METHOD = 3
STAGE_TEARDOWN = 4
STAGE_CLASS_TEARDOWN = 5
-
+
log = class_logger.ClassLogger()
def __init__(self, *args, **kwargs):
@@ -149,7 +147,7 @@ def __init__(self, *args, **kwargs):
# callbacks for various stages of execution, used for stuff like logging
self.__on_run_test_method_callbacks = []
self.__on_complete_test_method_callbacks = []
-
+
# one of these will later be populated with exception info if there's an
# exception in the class_setup/class_teardown stage
self.__class_level_failure = None
@@ -254,7 +252,7 @@ def __run_class_setup_fixtures(self):
def __run_class_teardown_fixtures(self):
"""End the process of running tests. Run the class's class_teardown methods"""
self._stage = self.STAGE_CLASS_TEARDOWN
-
+
self.__run_deprecated_fixture_method('classTearDown')
for fixture_method in self.class_teardown_fixtures:
@@ -292,8 +290,8 @@ def method_excluded(self, method):
def __run_test_methods(self):
"""Run this class's setup fixtures / test methods / teardown fixtures.
-
- These are run in the obvious order - setup and teardown go before and after,
+
+ These are run in the obvious order - setup and teardown go before and after,
respectively, every test method. If there was a failure in the class_setup
phase, no method-level fixtures or test methods will be run, and we'll eventually
skip all the way to the class_teardown phase. If a given test method is marked
@@ -369,7 +367,7 @@ def register_callback(self, event, callback):
def __execute_block_recording_exceptions(self, block_fxn, result, is_class_level=False):
"""Excerpted code for executing a block of code that might except and cause us to update a result object.
-
+
Return value is a boolean describing whether the block was successfully executed without exceptions.
"""
try:
@@ -379,21 +377,29 @@ def __execute_block_recording_exceptions(self, block_fxn, result, is_class_level
except TwistedFailureError, exception:
# We provide special support for handling the failures that are generated from twisted.
# Due to complexities in error handling and cleanup, it's difficult to get the raw exception
- # data from an asynchcronous failure, so we really get a pseudo traceback object.
+ # data from an asynchcronous failure, so we really get a pseudo traceback object.
failure = exception.args[0]
exc_info = (failure.type, failure.value, failure.getTracebackObject())
result.end_in_error(exc_info)
if is_class_level:
self.__class_level_failure = exc_info
except Exception, exception:
+ # some code may want to use an alternative exc_info for an exception
+ # (for instance, in an event loop). You can signal an alternative
+ # stack to use by adding a _testify_exc_tb attribute to the
+ # exception object
+ if hasattr(exception, '_testify_exc_tb'):
+ exc_info = (type(exception), exception, exception._testify_exc_tb)
+ else:
+ exc_info = sys.exc_info()
if isinstance(exception, AssertionError):
- result.end_in_failure(sys.exc_info())
+ result.end_in_failure(exc_info)
if is_class_level:
- self.__class_level_failure = sys.exc_info()
+ self.__class_level_failure = exc_info
else:
- result.end_in_error(sys.exc_info())
+ result.end_in_error(exc_info)
if is_class_level:
- self.__class_level_error = sys.exc_info()
+ self.__class_level_error = exc_info
return False
else:
return True
View
16 testify/test_logger.py
@@ -64,7 +64,7 @@ def test_complete(self, test_case, result):
self.report_test_result(result)
self.results.append(result)
elif result.test_method._fixture_type == 'class_teardown' and (result.failure or result.error):
- # For a class_teardown failure, log the name too (since it wouldn't have
+ # For a class_teardown failure, log the name too (since it wouldn't have
# already been logged by on_run_test_method).
self.report_test_name(result.test_method)
self.report_test_result(result)
@@ -96,7 +96,7 @@ def report(self):
return bool((len(results_by_status['failed']) + len(results_by_status['unknown'])) == 0)
- def report_test_name(self, test_name):
+ def report_test_name(self, test_name):
pass
def report_test_result(self, result):
pass
@@ -106,7 +106,7 @@ def report_failures(self, failed_results):
'FAILURES': [],
'EXPECTED_FAILURES': []
}
-
+
[results['EXPECTED_FAILURES'].append(result) if result.expected_failure else results['FAILURES'].append(result) for result in failed_results]
if results['EXPECTED_FAILURES']:
@@ -123,8 +123,8 @@ def report_failures(self, failed_results):
# output won't have to scroll up to figure out whether failures
# were expected or not.
self.heading('FAILURES', 'None!')
-
- def report_failure(self, result):
+
+ def report_failure(self, result):
pass
def report_stats(self, test_case_count, all_results, failed_results, unknown_results):
@@ -137,7 +137,7 @@ def _format_test_method_name(self, test_method):
out.append("%s " % test_method.im_class.__module__)
out.append("%s.%s" % (test_method.im_class.__name__, test_method.__name__))
- return''.join(out)
+ return ''.join(out)
# Helper methods for extracting relevant entries from a stack trace
def _format_exception_info(self, exception_info_tuple):
@@ -310,8 +310,8 @@ def report_stats(self, test_case_count, **results):
self.write("%s, %s. " % (passed_string, failed_string))
total_test_time = reduce(
- operator.add,
- (result.run_time for result in (successful+unexpected_success+failed+incomplete)),
+ operator.add,
+ (result.run_time for result in (successful+unexpected_success+failed+incomplete)),
datetime.timedelta())
self.writeln("(Total test time %.2fs)" % (total_test_time.seconds + total_test_time.microseconds / 1000000.0))
View
19 testify/test_runner.py
@@ -18,15 +18,8 @@
__testify = 1
from collections import defaultdict
-import datetime
-import logging
import functools
-from optparse import OptionParser
-import os
import pprint
-import sys
-import traceback
-import types
from test_case import MetaTestCase, TestCase
@@ -34,9 +27,9 @@
from test_logger import _log, TextTestLogger, VERBOSITY_SILENT, VERBOSITY_NORMAL, VERBOSITY_VERBOSE
class TestRunner(object):
- """TestRunner is the controller class of the testify suite.
+ """TestRunner is the controller class of the testify suite.
- It is responsible for collecting a list of TestCase subclasses, instantiating and
+ It is responsible for collecting a list of TestCase subclasses, instantiating and
running them, delegating the collection of results and printing of statistics.
"""
@@ -81,10 +74,10 @@ def run(self):
We use this opportunity to apply any test method name overrides that were parsed
from the command line (or rather, passed in on initialization).
-
- Logging of individual results is accomplished by registering callbacks for
+
+ Logging of individual results is accomplished by registering callbacks for
the TestCase instances to call when they begin and finish running each test.
-
+
At its conclusion, we pass our collected results and to our TestLogger to get
testing exceptions and summaries printed out.
"""
@@ -98,7 +91,7 @@ def run(self):
suites_require=self.suites_require,
name_overrides=name_overrides)
- # We allow our plugins to mutate the test case prior to execution
+ # We allow our plugins to mutate the test case prior to execution
for plugin_mod in self.plugin_modules:
if hasattr(plugin_mod, "prepare_test_case"):
plugin_mod.prepare_test_case(self.options, test_case)
Please sign in to comment.
Something went wrong with that request. Please try again.