blob: 566cfb97f8af0496e63317a3a07ad06ec5f188b3 [file] [log] [blame]
# -*- coding: utf-8 -*-
# test/scaffold.py
# Part of python-daemon, an implementation of PEP 3143.
#
# Copyright © 2007–2010 Ben Finney <ben+python@benfinney.id.au>
# This is free software; you may copy, modify and/or distribute this work
# under the terms of the GNU General Public License, version 2 or later.
# No warranty expressed or implied. See the file LICENSE.GPL-2 for details.
""" Scaffolding for unit test modules.
"""
import unittest
import doctest
import logging
import os
import sys
import operator
import textwrap
from minimock import (
Mock,
TraceTracker as MockTracker,
mock,
restore as mock_restore,
)
test_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(test_dir)
if not test_dir in sys.path:
sys.path.insert(1, test_dir)
if not parent_dir in sys.path:
sys.path.insert(1, parent_dir)
# Disable all but the most critical logging messages
logging.disable(logging.CRITICAL)
def get_python_module_names(file_list, file_suffix='.py'):
""" Return a list of module names from a filename list. """
module_names = [m[:m.rfind(file_suffix)] for m in file_list
if m.endswith(file_suffix)]
return module_names
def get_test_module_names(module_list, module_prefix='test_'):
""" Return the list of module names that qualify as test modules. """
module_names = [m for m in module_list
if m.startswith(module_prefix)]
return module_names
def make_suite(path=test_dir):
""" Create the test suite for the given path. """
loader = unittest.TestLoader()
python_module_names = get_python_module_names(os.listdir(path))
test_module_names = get_test_module_names(python_module_names)
suite = loader.loadTestsFromNames(test_module_names)
return suite
def get_function_signature(func):
""" Get the function signature as a mapping of attributes. """
arg_count = func.func_code.co_argcount
arg_names = func.func_code.co_varnames[:arg_count]
arg_defaults = {}
func_defaults = ()
if func.func_defaults is not None:
func_defaults = func.func_defaults
for (name, value) in zip(arg_names[::-1], func_defaults[::-1]):
arg_defaults[name] = value
signature = {
'name': func.__name__,
'arg_count': arg_count,
'arg_names': arg_names,
'arg_defaults': arg_defaults,
}
non_pos_names = list(func.func_code.co_varnames[arg_count:])
COLLECTS_ARBITRARY_POSITIONAL_ARGS = 0x04
if func.func_code.co_flags & COLLECTS_ARBITRARY_POSITIONAL_ARGS:
signature['var_args'] = non_pos_names.pop(0)
COLLECTS_ARBITRARY_KEYWORD_ARGS = 0x08
if func.func_code.co_flags & COLLECTS_ARBITRARY_KEYWORD_ARGS:
signature['var_kw_args'] = non_pos_names.pop(0)
return signature
def format_function_signature(func):
""" Format the function signature as printable text. """
signature = get_function_signature(func)
args_text = []
for arg_name in signature['arg_names']:
if arg_name in signature['arg_defaults']:
arg_default = signature['arg_defaults'][arg_name]
arg_text_template = "%(arg_name)s=%(arg_default)r"
else:
arg_text_template = "%(arg_name)s"
args_text.append(arg_text_template % vars())
if 'var_args' in signature:
args_text.append("*%(var_args)s" % signature)
if 'var_kw_args' in signature:
args_text.append("**%(var_kw_args)s" % signature)
signature_args_text = ", ".join(args_text)
func_name = signature['name']
signature_text = (
"%(func_name)s(%(signature_args_text)s)" % vars())
return signature_text
class TestCase(unittest.TestCase):
""" Test case behaviour. """
def failUnlessRaises(self, exc_class, func, *args, **kwargs):
""" Fail unless the function call raises the expected exception.
Fail the test if an instance of the exception class
``exc_class`` is not raised when calling ``func`` with the
arguments ``*args`` and ``**kwargs``.
"""
try:
super(TestCase, self).failUnlessRaises(
exc_class, func, *args, **kwargs)
except self.failureException:
exc_class_name = exc_class.__name__
msg = (
"Exception %(exc_class_name)s not raised"
" for function call:"
" func=%(func)r args=%(args)r kwargs=%(kwargs)r"
) % vars()
raise self.failureException(msg)
def failIfIs(self, first, second, msg=None):
""" Fail if the two objects are identical.
Fail the test if ``first`` and ``second`` are identical,
as determined by the ``is`` operator.
"""
if first is second:
if msg is None:
msg = "%(first)r is %(second)r" % vars()
raise self.failureException(msg)
def failUnlessIs(self, first, second, msg=None):
""" Fail unless the two objects are identical.
Fail the test unless ``first`` and ``second`` are
identical, as determined by the ``is`` operator.
"""
if first is not second:
if msg is None:
msg = "%(first)r is not %(second)r" % vars()
raise self.failureException(msg)
assertIs = failUnlessIs
assertNotIs = failIfIs
def failIfIn(self, first, second, msg=None):
""" Fail if the second object is in the first.
Fail the test if ``first`` contains ``second``, as
determined by the ``in`` operator.
"""
if second in first:
if msg is None:
msg = "%(second)r is in %(first)r" % vars()
raise self.failureException(msg)
def failUnlessIn(self, first, second, msg=None):
""" Fail unless the second object is in the first.
Fail the test unless ``first`` contains ``second``, as
determined by the ``in`` operator.
"""
if second not in first:
if msg is None:
msg = "%(second)r is not in %(first)r" % vars()
raise self.failureException(msg)
assertIn = failUnlessIn
assertNotIn = failIfIn
def failUnlessOutputCheckerMatch(self, want, got, msg=None):
""" Fail unless the specified string matches the expected.
Fail the test unless ``want`` matches ``got``, as
determined by a ``doctest.OutputChecker`` instance. This
is not an equality check, but a pattern match according to
the ``OutputChecker`` rules.
"""
checker = doctest.OutputChecker()
want = textwrap.dedent(want)
source = ""
example = doctest.Example(source, want)
got = textwrap.dedent(got)
checker_optionflags = reduce(operator.or_, [
doctest.ELLIPSIS,
])
if not checker.check_output(want, got, checker_optionflags):
if msg is None:
diff = checker.output_difference(
example, got, checker_optionflags)
msg = "\n".join([
"Output received did not match expected output",
"%(diff)s",
]) % vars()
raise self.failureException(msg)
assertOutputCheckerMatch = failUnlessOutputCheckerMatch
def failUnlessMockCheckerMatch(self, want, tracker=None, msg=None):
""" Fail unless the mock tracker matches the wanted output.
Fail the test unless `want` matches the output tracked by
`tracker` (defaults to ``self.mock_tracker``. This is not
an equality check, but a pattern match according to the
``minimock.MinimockOutputChecker`` rules.
"""
if tracker is None:
tracker = self.mock_tracker
if not tracker.check(want):
if msg is None:
diff = tracker.diff(want)
msg = "\n".join([
"Output received did not match expected output",
"%(diff)s",
]) % vars()
raise self.failureException(msg)
def failIfMockCheckerMatch(self, want, tracker=None, msg=None):
""" Fail if the mock tracker matches the specified output.
Fail the test if `want` matches the output tracked by
`tracker` (defaults to ``self.mock_tracker``. This is not
an equality check, but a pattern match according to the
``minimock.MinimockOutputChecker`` rules.
"""
if tracker is None:
tracker = self.mock_tracker
if tracker.check(want):
if msg is None:
diff = tracker.diff(want)
msg = "\n".join([
"Output received matched specified undesired output",
"%(diff)s",
]) % vars()
raise self.failureException(msg)
assertMockCheckerMatch = failUnlessMockCheckerMatch
assertNotMockCheckerMatch = failIfMockCheckerMatch
def failIfIsInstance(self, obj, classes, msg=None):
""" Fail if the object is an instance of the specified classes.
Fail the test if the object ``obj`` is an instance of any
of ``classes``.
"""
if isinstance(obj, classes):
if msg is None:
msg = (
"%(obj)r is an instance of one of %(classes)r"
) % vars()
raise self.failureException(msg)
def failUnlessIsInstance(self, obj, classes, msg=None):
""" Fail unless the object is an instance of the specified classes.
Fail the test unless the object ``obj`` is an instance of
any of ``classes``.
"""
if not isinstance(obj, classes):
if msg is None:
msg = (
"%(obj)r is not an instance of any of %(classes)r"
) % vars()
raise self.failureException(msg)
assertIsInstance = failUnlessIsInstance
assertNotIsInstance = failIfIsInstance
def failUnlessFunctionInTraceback(self, traceback, function, msg=None):
""" Fail if the function is not in the traceback.
Fail the test if the function ``function`` is not at any
of the levels in the traceback object ``traceback``.
"""
func_in_traceback = False
expect_code = function.func_code
current_traceback = traceback
while current_traceback is not None:
if expect_code is current_traceback.tb_frame.f_code:
func_in_traceback = True
break
current_traceback = current_traceback.tb_next
if not func_in_traceback:
if msg is None:
msg = (
"Traceback did not lead to original function"
" %(function)s"
) % vars()
raise self.failureException(msg)
assertFunctionInTraceback = failUnlessFunctionInTraceback
def failUnlessFunctionSignatureMatch(self, first, second, msg=None):
""" Fail if the function signatures do not match.
Fail the test if the function signature does not match
between the ``first`` function and the ``second``
function.
The function signature includes:
* function name,
* count of named parameters,
* sequence of named parameters,
* default values of named parameters,
* collector for arbitrary positional arguments,
* collector for arbitrary keyword arguments.
"""
first_signature = get_function_signature(first)
second_signature = get_function_signature(second)
if first_signature != second_signature:
if msg is None:
first_signature_text = format_function_signature(first)
second_signature_text = format_function_signature(second)
msg = (textwrap.dedent("""\
Function signatures do not match:
%(first_signature)r != %(second_signature)r
Expected:
%(first_signature_text)s
Got:
%(second_signature_text)s""")
) % vars()
raise self.failureException(msg)
assertFunctionSignatureMatch = failUnlessFunctionSignatureMatch
class Exception_TestCase(TestCase):
""" Test cases for exception classes. """
def __init__(self, *args, **kwargs):
""" Set up a new instance """
self.valid_exceptions = NotImplemented
super(Exception_TestCase, self).__init__(*args, **kwargs)
def setUp(self):
""" Set up test fixtures. """
for exc_type, params in self.valid_exceptions.items():
args = (None, ) * params['min_args']
params['args'] = args
instance = exc_type(*args)
params['instance'] = instance
super(Exception_TestCase, self).setUp()
def test_exception_instance(self):
""" Exception instance should be created. """
for params in self.valid_exceptions.values():
instance = params['instance']
self.failIfIs(None, instance)
def test_exception_types(self):
""" Exception instances should match expected types. """
for params in self.valid_exceptions.values():
instance = params['instance']
for match_type in params['types']:
match_type_name = match_type.__name__
fail_msg = (
"%(instance)r is not an instance of"
" %(match_type_name)s"
) % vars()
self.failUnless(
isinstance(instance, match_type),
msg=fail_msg)