scaffold.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. # -*- coding: utf-8 -*-
  2. # test/scaffold.py
  3. # Part of python-daemon, an implementation of PEP 3143.
  4. #
  5. # Copyright © 2007–2009 Ben Finney <ben+python@benfinney.id.au>
  6. # This is free software; you may copy, modify and/or distribute this work
  7. # under the terms of the GNU General Public License, version 2 or later.
  8. # No warranty expressed or implied. See the file LICENSE.GPL-2 for details.
  9. """ Scaffolding for unit test modules.
  10. """
  11. import unittest
  12. import doctest
  13. import logging
  14. import os
  15. import sys
  16. import operator
  17. import textwrap
  18. from minimock import (
  19. Mock,
  20. TraceTracker as MockTracker,
  21. mock,
  22. restore as mock_restore,
  23. )
  24. test_dir = os.path.dirname(os.path.abspath(__file__))
  25. parent_dir = os.path.dirname(test_dir)
  26. if not test_dir in sys.path:
  27. sys.path.insert(1, test_dir)
  28. if not parent_dir in sys.path:
  29. sys.path.insert(1, parent_dir)
  30. # Disable all but the most critical logging messages
  31. logging.disable(logging.CRITICAL)
  32. def get_python_module_names(file_list, file_suffix='.py'):
  33. """ Return a list of module names from a filename list. """
  34. module_names = [m[:m.rfind(file_suffix)] for m in file_list
  35. if m.endswith(file_suffix)]
  36. return module_names
  37. def get_test_module_names(module_list, module_prefix='test_'):
  38. """ Return the list of module names that qualify as test modules. """
  39. module_names = [m for m in module_list
  40. if m.startswith(module_prefix)]
  41. return module_names
  42. def make_suite(path=test_dir):
  43. """ Create the test suite for the given path. """
  44. loader = unittest.TestLoader()
  45. python_module_names = get_python_module_names(os.listdir(path))
  46. test_module_names = get_test_module_names(python_module_names)
  47. suite = loader.loadTestsFromNames(test_module_names)
  48. return suite
  49. def get_function_signature(func):
  50. """ Get the function signature as a mapping of attributes. """
  51. arg_count = func.func_code.co_argcount
  52. arg_names = func.func_code.co_varnames[:arg_count]
  53. arg_defaults = {}
  54. func_defaults = ()
  55. if func.func_defaults is not None:
  56. func_defaults = func.func_defaults
  57. for (name, value) in zip(arg_names[::-1], func_defaults[::-1]):
  58. arg_defaults[name] = value
  59. signature = {
  60. 'name': func.__name__,
  61. 'arg_count': arg_count,
  62. 'arg_names': arg_names,
  63. 'arg_defaults': arg_defaults,
  64. }
  65. non_pos_names = list(func.func_code.co_varnames[arg_count:])
  66. COLLECTS_ARBITRARY_POSITIONAL_ARGS = 0x04
  67. if func.func_code.co_flags & COLLECTS_ARBITRARY_POSITIONAL_ARGS:
  68. signature['var_args'] = non_pos_names.pop(0)
  69. COLLECTS_ARBITRARY_KEYWORD_ARGS = 0x08
  70. if func.func_code.co_flags & COLLECTS_ARBITRARY_KEYWORD_ARGS:
  71. signature['var_kw_args'] = non_pos_names.pop(0)
  72. return signature
  73. def format_function_signature(func):
  74. """ Format the function signature as printable text. """
  75. signature = get_function_signature(func)
  76. args_text = []
  77. for arg_name in signature['arg_names']:
  78. if arg_name in signature['arg_defaults']:
  79. arg_default = signature['arg_defaults'][arg_name]
  80. arg_text_template = "%(arg_name)s=%(arg_default)r"
  81. else:
  82. arg_text_template = "%(arg_name)s"
  83. args_text.append(arg_text_template % vars())
  84. if 'var_args' in signature:
  85. args_text.append("*%(var_args)s" % signature)
  86. if 'var_kw_args' in signature:
  87. args_text.append("**%(var_kw_args)s" % signature)
  88. signature_args_text = ", ".join(args_text)
  89. func_name = signature['name']
  90. signature_text = (
  91. "%(func_name)s(%(signature_args_text)s)" % vars())
  92. return signature_text
  93. class TestCase(unittest.TestCase):
  94. """ Test case behaviour. """
  95. def failUnlessRaises(self, exc_class, func, *args, **kwargs):
  96. """ Fail unless the function call raises the expected exception.
  97. Fail the test if an instance of the exception class
  98. ``exc_class`` is not raised when calling ``func`` with the
  99. arguments ``*args`` and ``**kwargs``.
  100. """
  101. try:
  102. super(TestCase, self).failUnlessRaises(
  103. exc_class, func, *args, **kwargs)
  104. except self.failureException:
  105. exc_class_name = exc_class.__name__
  106. msg = (
  107. "Exception %(exc_class_name)s not raised"
  108. " for function call:"
  109. " func=%(func)r args=%(args)r kwargs=%(kwargs)r"
  110. ) % vars()
  111. raise self.failureException(msg)
  112. def failIfIs(self, first, second, msg=None):
  113. """ Fail if the two objects are identical.
  114. Fail the test if ``first`` and ``second`` are identical,
  115. as determined by the ``is`` operator.
  116. """
  117. if first is second:
  118. if msg is None:
  119. msg = "%(first)r is %(second)r" % vars()
  120. raise self.failureException(msg)
  121. def failUnlessIs(self, first, second, msg=None):
  122. """ Fail unless the two objects are identical.
  123. Fail the test unless ``first`` and ``second`` are
  124. identical, as determined by the ``is`` operator.
  125. """
  126. if first is not second:
  127. if msg is None:
  128. msg = "%(first)r is not %(second)r" % vars()
  129. raise self.failureException(msg)
  130. assertIs = failUnlessIs
  131. assertNotIs = failIfIs
  132. def failIfIn(self, first, second, msg=None):
  133. """ Fail if the second object is in the first.
  134. Fail the test if ``first`` contains ``second``, as
  135. determined by the ``in`` operator.
  136. """
  137. if second in first:
  138. if msg is None:
  139. msg = "%(second)r is in %(first)r" % vars()
  140. raise self.failureException(msg)
  141. def failUnlessIn(self, first, second, msg=None):
  142. """ Fail unless the second object is in the first.
  143. Fail the test unless ``first`` contains ``second``, as
  144. determined by the ``in`` operator.
  145. """
  146. if second not in first:
  147. if msg is None:
  148. msg = "%(second)r is not in %(first)r" % vars()
  149. raise self.failureException(msg)
  150. assertIn = failUnlessIn
  151. assertNotIn = failIfIn
  152. def failUnlessOutputCheckerMatch(self, want, got, msg=None):
  153. """ Fail unless the specified string matches the expected.
  154. Fail the test unless ``want`` matches ``got``, as
  155. determined by a ``doctest.OutputChecker`` instance. This
  156. is not an equality check, but a pattern match according to
  157. the ``OutputChecker`` rules.
  158. """
  159. checker = doctest.OutputChecker()
  160. want = textwrap.dedent(want)
  161. source = ""
  162. example = doctest.Example(source, want)
  163. got = textwrap.dedent(got)
  164. checker_optionflags = reduce(operator.or_, [
  165. doctest.ELLIPSIS,
  166. ])
  167. if not checker.check_output(want, got, checker_optionflags):
  168. if msg is None:
  169. diff = checker.output_difference(
  170. example, got, checker_optionflags)
  171. msg = "\n".join([
  172. "Output received did not match expected output",
  173. "%(diff)s",
  174. ]) % vars()
  175. raise self.failureException(msg)
  176. assertOutputCheckerMatch = failUnlessOutputCheckerMatch
  177. def failUnlessMockCheckerMatch(self, want, tracker=None, msg=None):
  178. """ Fail unless the mock tracker matches the wanted output.
  179. Fail the test unless `want` matches the output tracked by
  180. `tracker` (defaults to ``self.mock_tracker``. This is not
  181. an equality check, but a pattern match according to the
  182. ``minimock.MinimockOutputChecker`` rules.
  183. """
  184. if tracker is None:
  185. tracker = self.mock_tracker
  186. if not tracker.check(want):
  187. if msg is None:
  188. diff = tracker.diff(want)
  189. msg = "\n".join([
  190. "Output received did not match expected output",
  191. "%(diff)s",
  192. ]) % vars()
  193. raise self.failureException(msg)
  194. def failIfMockCheckerMatch(self, want, tracker=None, msg=None):
  195. """ Fail if the mock tracker matches the specified output.
  196. Fail the test if `want` matches the output tracked by
  197. `tracker` (defaults to ``self.mock_tracker``. This is not
  198. an equality check, but a pattern match according to the
  199. ``minimock.MinimockOutputChecker`` rules.
  200. """
  201. if tracker is None:
  202. tracker = self.mock_tracker
  203. if tracker.check(want):
  204. if msg is None:
  205. diff = tracker.diff(want)
  206. msg = "\n".join([
  207. "Output received matched specified undesired output",
  208. "%(diff)s",
  209. ]) % vars()
  210. raise self.failureException(msg)
  211. assertMockCheckerMatch = failUnlessMockCheckerMatch
  212. assertNotMockCheckerMatch = failIfMockCheckerMatch
  213. def failIfIsInstance(self, obj, classes, msg=None):
  214. """ Fail if the object is an instance of the specified classes.
  215. Fail the test if the object ``obj`` is an instance of any
  216. of ``classes``.
  217. """
  218. if isinstance(obj, classes):
  219. if msg is None:
  220. msg = (
  221. "%(obj)r is an instance of one of %(classes)r"
  222. ) % vars()
  223. raise self.failureException(msg)
  224. def failUnlessIsInstance(self, obj, classes, msg=None):
  225. """ Fail unless the object is an instance of the specified classes.
  226. Fail the test unless the object ``obj`` is an instance of
  227. any of ``classes``.
  228. """
  229. if not isinstance(obj, classes):
  230. if msg is None:
  231. msg = (
  232. "%(obj)r is not an instance of any of %(classes)r"
  233. ) % vars()
  234. raise self.failureException(msg)
  235. assertIsInstance = failUnlessIsInstance
  236. assertNotIsInstance = failIfIsInstance
  237. def failUnlessFunctionInTraceback(self, traceback, function, msg=None):
  238. """ Fail if the function is not in the traceback.
  239. Fail the test if the function ``function`` is not at any
  240. of the levels in the traceback object ``traceback``.
  241. """
  242. func_in_traceback = False
  243. expect_code = function.func_code
  244. current_traceback = traceback
  245. while current_traceback is not None:
  246. if expect_code is current_traceback.tb_frame.f_code:
  247. func_in_traceback = True
  248. break
  249. current_traceback = current_traceback.tb_next
  250. if not func_in_traceback:
  251. if msg is None:
  252. msg = (
  253. "Traceback did not lead to original function"
  254. " %(function)s"
  255. ) % vars()
  256. raise self.failureException(msg)
  257. assertFunctionInTraceback = failUnlessFunctionInTraceback
  258. def failUnlessFunctionSignatureMatch(self, first, second, msg=None):
  259. """ Fail if the function signatures do not match.
  260. Fail the test if the function signature does not match
  261. between the ``first`` function and the ``second``
  262. function.
  263. The function signature includes:
  264. * function name,
  265. * count of named parameters,
  266. * sequence of named parameters,
  267. * default values of named parameters,
  268. * collector for arbitrary positional arguments,
  269. * collector for arbitrary keyword arguments.
  270. """
  271. first_signature = get_function_signature(first)
  272. second_signature = get_function_signature(second)
  273. if first_signature != second_signature:
  274. if msg is None:
  275. first_signature_text = format_function_signature(first)
  276. second_signature_text = format_function_signature(second)
  277. msg = (textwrap.dedent("""\
  278. Function signatures do not match:
  279. %(first_signature)r != %(second_signature)r
  280. Expected:
  281. %(first_signature_text)s
  282. Got:
  283. %(second_signature_text)s""")
  284. ) % vars()
  285. raise self.failureException(msg)
  286. assertFunctionSignatureMatch = failUnlessFunctionSignatureMatch
  287. class Exception_TestCase(TestCase):
  288. """ Test cases for exception classes. """
  289. def __init__(self, *args, **kwargs):
  290. """ Set up a new instance """
  291. self.valid_exceptions = NotImplemented
  292. super(Exception_TestCase, self).__init__(*args, **kwargs)
  293. def setUp(self):
  294. """ Set up test fixtures. """
  295. for exc_type, params in self.valid_exceptions.items():
  296. args = (None, ) * params['min_args']
  297. params['args'] = args
  298. instance = exc_type(*args)
  299. params['instance'] = instance
  300. super(Exception_TestCase, self).setUp()
  301. def test_exception_instance(self):
  302. """ Exception instance should be created. """
  303. for params in self.valid_exceptions.values():
  304. instance = params['instance']
  305. self.failIfIs(None, instance)
  306. def test_exception_types(self):
  307. """ Exception instances should match expected types. """
  308. for params in self.valid_exceptions.values():
  309. instance = params['instance']
  310. for match_type in params['types']:
  311. match_type_name = match_type.__name__
  312. fail_msg = (
  313. "%(instance)r is not an instance of"
  314. " %(match_type_name)s"
  315. ) % vars()
  316. self.failUnless(
  317. isinstance(instance, match_type),
  318. msg=fail_msg)