text: # -*- 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)
  