| Index: third_party/logilab/common/testlib.py
 | 
| ===================================================================
 | 
| --- third_party/logilab/common/testlib.py	(revision 293047)
 | 
| +++ third_party/logilab/common/testlib.py	(working copy)
 | 
| @@ -1,5 +1,5 @@
 | 
|  # -*- coding: utf-8 -*-
 | 
| -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 | 
| +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 | 
|  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 | 
|  #
 | 
|  # This file is part of logilab-common.
 | 
| @@ -36,6 +36,9 @@
 | 
|  'regrtest', 'smoketest' and 'unittest'.
 | 
|  
 | 
|  """
 | 
| +
 | 
| +from __future__ import print_function
 | 
| +
 | 
|  __docformat__ = "restructuredtext en"
 | 
|  # modified copy of some functions from test/regrtest.py from PyXml
 | 
|  # disable camel case warning
 | 
| @@ -52,10 +55,14 @@
 | 
|  import warnings
 | 
|  from shutil import rmtree
 | 
|  from operator import itemgetter
 | 
| -from ConfigParser import ConfigParser
 | 
| -from logilab.common.deprecation import deprecated
 | 
|  from itertools import dropwhile
 | 
| +from inspect import isgeneratorfunction
 | 
|  
 | 
| +from six import string_types
 | 
| +from six.moves import builtins, range, configparser, input
 | 
| +
 | 
| +from logilab.common.deprecation import deprecated
 | 
| +
 | 
|  import unittest as unittest_legacy
 | 
|  if not getattr(unittest_legacy, "__package__", None):
 | 
|      try:
 | 
| @@ -62,31 +69,13 @@
 | 
|          import unittest2 as unittest
 | 
|          from unittest2 import SkipTest
 | 
|      except ImportError:
 | 
| -        sys.exit("You have to install python-unittest2 to use this module")
 | 
| +        raise ImportError("You have to install python-unittest2 to use %s" % __name__)
 | 
|  else:
 | 
|      import unittest
 | 
|      from unittest import SkipTest
 | 
|  
 | 
| -try:
 | 
| -    from functools import wraps
 | 
| -except ImportError:
 | 
| -    def wraps(wrapped):
 | 
| -        def proxy(callable):
 | 
| -            callable.__name__ = wrapped.__name__
 | 
| -            return callable
 | 
| -        return proxy
 | 
| -try:
 | 
| -    from test import test_support
 | 
| -except ImportError:
 | 
| -    # not always available
 | 
| -    class TestSupport:
 | 
| -        def unload(self, test):
 | 
| -            pass
 | 
| -    test_support = TestSupport()
 | 
| +from functools import wraps
 | 
|  
 | 
| -# pylint: disable=W0622
 | 
| -from logilab.common.compat import any, InheritableSet, callable
 | 
| -# pylint: enable=W0622
 | 
|  from logilab.common.debugger import Debugger, colorize_source
 | 
|  from logilab.common.decorators import cached, classproperty
 | 
|  from logilab.common import textutils
 | 
| @@ -97,24 +86,8 @@
 | 
|  DEFAULT_PREFIXES = ('test', 'regrtest', 'smoketest', 'unittest',
 | 
|                      'func', 'validation')
 | 
|  
 | 
| +is_generator = deprecated('[lgc 0.63] use inspect.isgeneratorfunction')(isgeneratorfunction)
 | 
|  
 | 
| -if sys.version_info >= (2, 6):
 | 
| -    # FIXME : this does not work as expected / breaks tests on testlib
 | 
| -    # however testlib does not work on py3k for many reasons ...
 | 
| -    from inspect import CO_GENERATOR
 | 
| -else:
 | 
| -    from compiler.consts import CO_GENERATOR
 | 
| -
 | 
| -if sys.version_info >= (3, 0):
 | 
| -    def is_generator(function):
 | 
| -        flags = function.__code__.co_flags
 | 
| -        return flags & CO_GENERATOR
 | 
| -
 | 
| -else:
 | 
| -    def is_generator(function):
 | 
| -        flags = function.func_code.co_flags
 | 
| -        return flags & CO_GENERATOR
 | 
| -
 | 
|  # used by unittest to count the number of relevant levels in the traceback
 | 
|  __unittest = 1
 | 
|  
 | 
| @@ -122,6 +95,21 @@
 | 
|  def with_tempdir(callable):
 | 
|      """A decorator ensuring no temporary file left when the function return
 | 
|      Work only for temporary file create with the tempfile module"""
 | 
| +    if isgeneratorfunction(callable):
 | 
| +        def proxy(*args, **kwargs):
 | 
| +            old_tmpdir = tempfile.gettempdir()
 | 
| +            new_tmpdir = tempfile.mkdtemp(prefix="temp-lgc-")
 | 
| +            tempfile.tempdir = new_tmpdir
 | 
| +            try:
 | 
| +                for x in callable(*args, **kwargs):
 | 
| +                    yield x
 | 
| +            finally:
 | 
| +                try:
 | 
| +                    rmtree(new_tmpdir, ignore_errors=True)
 | 
| +                finally:
 | 
| +                    tempfile.tempdir = old_tmpdir
 | 
| +        return proxy
 | 
| +
 | 
|      @wraps(callable)
 | 
|      def proxy(*args, **kargs):
 | 
|  
 | 
| @@ -190,16 +178,16 @@
 | 
|      else:
 | 
|          while True:
 | 
|              testindex = 0
 | 
| -            print "Choose a test to debug:"
 | 
| +            print("Choose a test to debug:")
 | 
|              # order debuggers in the same way than errors were printed
 | 
| -            print "\n".join(['\t%s : %s' % (i, descr) for i, (_, descr)
 | 
| -                in enumerate(descrs)])
 | 
| -            print "Type 'exit' (or ^D) to quit"
 | 
| -            print
 | 
| +            print("\n".join(['\t%s : %s' % (i, descr) for i, (_, descr)
 | 
| +                  in enumerate(descrs)]))
 | 
| +            print("Type 'exit' (or ^D) to quit")
 | 
| +            print()
 | 
|              try:
 | 
| -                todebug = raw_input('Enter a test name: ')
 | 
| +                todebug = input('Enter a test name: ')
 | 
|                  if todebug.strip().lower() == 'exit':
 | 
| -                    print
 | 
| +                    print()
 | 
|                      break
 | 
|                  else:
 | 
|                      try:
 | 
| @@ -206,11 +194,11 @@
 | 
|                          testindex = int(todebug)
 | 
|                          debugger = debuggers[descrs[testindex][0]]
 | 
|                      except (ValueError, IndexError):
 | 
| -                        print "ERROR: invalid test number %r" % (todebug, )
 | 
| +                        print("ERROR: invalid test number %r" % (todebug, ))
 | 
|                      else:
 | 
|                          debugger.start()
 | 
|              except (EOFError, KeyboardInterrupt):
 | 
| -                print
 | 
| +                print()
 | 
|                  break
 | 
|  
 | 
|  
 | 
| @@ -364,7 +352,7 @@
 | 
|      if tearDownModule is not None:
 | 
|          try:
 | 
|              tearDownModule()
 | 
| -        except Exception, e:
 | 
| +        except Exception as e:
 | 
|              if isinstance(result, _DebugResult):
 | 
|                  raise
 | 
|              errorName = 'tearDownModule (%s)' % previousModule
 | 
| @@ -392,7 +380,7 @@
 | 
|      if setUpModule is not None:
 | 
|          try:
 | 
|              setUpModule()
 | 
| -        except Exception, e:
 | 
| +        except Exception as e:
 | 
|              if isinstance(result, _DebugResult):
 | 
|                  raise
 | 
|              result._moduleSetUpFailed = True
 | 
| @@ -448,7 +436,7 @@
 | 
|          instance.name = name
 | 
|          return instance
 | 
|  
 | 
| -class Tags(InheritableSet): # 2.4 compat
 | 
| +class Tags(set):
 | 
|      """A set of tag able validate an expression"""
 | 
|  
 | 
|      def __init__(self, *tags, **kwargs):
 | 
| @@ -456,7 +444,7 @@
 | 
|          if kwargs:
 | 
|             raise TypeError("%s are an invalid keyword argument for this function" % kwargs.keys())
 | 
|  
 | 
| -        if len(tags) == 1 and not isinstance(tags[0], basestring):
 | 
| +        if len(tags) == 1 and not isinstance(tags[0], string_types):
 | 
|              tags = tags[0]
 | 
|          super(Tags, self).__init__(tags, **kwargs)
 | 
|  
 | 
| @@ -484,14 +472,8 @@
 | 
|  
 | 
|      def __init__(self, methodName='runTest'):
 | 
|          super(TestCase, self).__init__(methodName)
 | 
| -        # internal API changed in python2.4 and needed by DocTestCase
 | 
| -        if sys.version_info >= (2, 4):
 | 
| -            self.__exc_info = sys.exc_info
 | 
| -            self.__testMethodName = self._testMethodName
 | 
| -        else:
 | 
| -            # let's give easier access to _testMethodName to every subclasses
 | 
| -            if hasattr(self, "__testMethodName"):
 | 
| -                self._testMethodName = self.__testMethodName
 | 
| +        self.__exc_info = sys.exc_info
 | 
| +        self.__testMethodName = self._testMethodName
 | 
|          self._current_test_descr = None
 | 
|          self._options_ = None
 | 
|  
 | 
| @@ -533,6 +515,14 @@
 | 
|              func(*args, **kwargs)
 | 
|          except (KeyboardInterrupt, SystemExit):
 | 
|              raise
 | 
| +        except unittest.SkipTest as e:
 | 
| +            if hasattr(result, 'addSkip'):
 | 
| +                result.addSkip(self, str(e))
 | 
| +            else:
 | 
| +                warnings.warn("TestResult has no addSkip method, skips not reported",
 | 
| +                              RuntimeWarning, 2)
 | 
| +                result.addSuccess(self)
 | 
| +            return False
 | 
|          except:
 | 
|              result.addError(self, self.__exc_info())
 | 
|              return False
 | 
| @@ -559,6 +549,16 @@
 | 
|          # if result.cvg:
 | 
|          #     result.cvg.start()
 | 
|          testMethod = self._get_test_method()
 | 
| +        if (getattr(self.__class__, "__unittest_skip__", False) or
 | 
| +            getattr(testMethod, "__unittest_skip__", False)):
 | 
| +            # If the class or method was skipped.
 | 
| +            try:
 | 
| +                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
 | 
| +                            or getattr(testMethod, '__unittest_skip_why__', ''))
 | 
| +                self._addSkip(result, skip_why)
 | 
| +            finally:
 | 
| +                result.stopTest(self)
 | 
| +            return
 | 
|          if runcondition and not runcondition(testMethod):
 | 
|              return # test is skipped
 | 
|          result.startTest(self)
 | 
| @@ -565,7 +565,7 @@
 | 
|          try:
 | 
|              if not self.quiet_run(result, self.setUp):
 | 
|                  return
 | 
| -            generative = is_generator(testMethod.im_func)
 | 
| +            generative = isgeneratorfunction(testMethod)
 | 
|              # generative tests
 | 
|              if generative:
 | 
|                  self._proceed_generative(result, testMethod,
 | 
| @@ -587,10 +587,11 @@
 | 
|                              restartfile.write(descr+os.linesep)
 | 
|                          finally:
 | 
|                              restartfile.close()
 | 
| -                    except Exception, ex:
 | 
| -                        print >> sys.__stderr__, "Error while saving \
 | 
| -succeeded test into", osp.join(os.getcwd(), FILE_RESTART)
 | 
| -                        raise ex
 | 
| +                    except Exception:
 | 
| +                        print("Error while saving succeeded test into",
 | 
| +                              osp.join(os.getcwd(), FILE_RESTART),
 | 
| +                              file=sys.__stderr__)
 | 
| +                        raise
 | 
|                  result.addSuccess(self)
 | 
|          finally:
 | 
|              # if result.cvg:
 | 
| @@ -647,10 +648,10 @@
 | 
|              return 1
 | 
|          except KeyboardInterrupt:
 | 
|              raise
 | 
| -        except InnerTestSkipped, e:
 | 
| +        except InnerTestSkipped as e:
 | 
|              result.addSkip(self, e)
 | 
|              return 1
 | 
| -        except SkipTest, e:
 | 
| +        except SkipTest as e:
 | 
|              result.addSkip(self, e)
 | 
|              return 0
 | 
|          except:
 | 
| @@ -704,7 +705,7 @@
 | 
|                  base = ''
 | 
|              self.fail(base + '\n'.join(msgs))
 | 
|  
 | 
| -    @deprecated('Please use assertItemsEqual instead.')
 | 
| +    @deprecated('Please use assertCountEqual instead.')
 | 
|      def assertUnorderedIterableEquals(self, got, expected, msg=None):
 | 
|          """compares two iterable and shows difference between both
 | 
|  
 | 
| @@ -826,10 +827,10 @@
 | 
|              parser = make_parser()
 | 
|              try:
 | 
|                  parser.parse(stream)
 | 
| -            except SAXParseException, ex:
 | 
| +            except SAXParseException as ex:
 | 
|                  if msg is None:
 | 
|                      stream.seek(0)
 | 
| -                    for _ in xrange(ex.getLineNumber()):
 | 
| +                    for _ in range(ex.getLineNumber()):
 | 
|                          line = stream.readline()
 | 
|                      pointer = ('' * (ex.getLineNumber() - 1)) + '^'
 | 
|                      msg = 'XML stream not well formed: %s\n%s%s' % (ex, line, pointer)
 | 
| @@ -867,7 +868,7 @@
 | 
|              ParseError = ExpatError
 | 
|          try:
 | 
|              parse(data)
 | 
| -        except (ExpatError, ParseError), ex:
 | 
| +        except (ExpatError, ParseError) as ex:
 | 
|              if msg is None:
 | 
|                  if hasattr(data, 'readlines'): #file like object
 | 
|                      data.seek(0)
 | 
| @@ -888,11 +889,11 @@
 | 
|                      line_number_length = len('%i' % end)
 | 
|                      line_pattern = " %%%ii: %%s" % line_number_length
 | 
|  
 | 
| -                    for line_no in xrange(start, ex.lineno):
 | 
| +                    for line_no in range(start, ex.lineno):
 | 
|                          context_lines.append(line_pattern % (line_no, lines[line_no-1]))
 | 
|                      context_lines.append(line_pattern % (ex.lineno, lines[ex.lineno-1]))
 | 
|                      context_lines.append('%s^\n' % (' ' * (1 + line_number_length + 2 +ex.offset)))
 | 
| -                    for line_no in xrange(ex.lineno+1, end+1):
 | 
| +                    for line_no in range(ex.lineno+1, end+1):
 | 
|                          context_lines.append(line_pattern % (line_no, lines[line_no-1]))
 | 
|  
 | 
|                  rich_context = ''.join(context_lines)
 | 
| @@ -920,7 +921,7 @@
 | 
|                  self.fail( "tuple %s has %i children%s (%i expected)"%(tup,
 | 
|                      len(tup[2]),
 | 
|                          ('', 's')[len(tup[2])>1], len(element)))
 | 
| -            for index in xrange(len(tup[2])):
 | 
| +            for index in range(len(tup[2])):
 | 
|                  self.assertXMLEqualsTuple(element[index], tup[2][index])
 | 
|          #check text
 | 
|          if element.text or len(tup)>3:
 | 
| @@ -950,7 +951,7 @@
 | 
|      def assertTextEquals(self, text1, text2, junk=None,
 | 
|              msg_prefix='Text differ', striplines=False):
 | 
|          """compare two multiline strings (using difflib and splitlines())
 | 
| -        
 | 
| +
 | 
|          :param text1: a Python BaseString
 | 
|          :param text2: a second Python Basestring
 | 
|          :param junk: List of Caracters
 | 
| @@ -958,9 +959,9 @@
 | 
|          :param striplines: Boolean to trigger line stripping before comparing
 | 
|          """
 | 
|          msg = []
 | 
| -        if not isinstance(text1, basestring):
 | 
| +        if not isinstance(text1, string_types):
 | 
|              msg.append('text1 is not a string (%s)'%(type(text1)))
 | 
| -        if not isinstance(text2, basestring):
 | 
| +        if not isinstance(text2, string_types):
 | 
|              msg.append('text2 is not a string (%s)'%(type(text2)))
 | 
|          if msg:
 | 
|              self.fail('\n'.join(msg))
 | 
| @@ -1016,13 +1017,13 @@
 | 
|          ipath_a, idirs_a, ifiles_a = data_a = None, None, None
 | 
|          while True:
 | 
|              try:
 | 
| -                ipath_a, idirs_a, ifiles_a = datas_a = iter_a.next()
 | 
| +                ipath_a, idirs_a, ifiles_a = datas_a = next(iter_a)
 | 
|                  partial_iter = False
 | 
| -                ipath_b, idirs_b, ifiles_b = datas_b = iter_b.next()
 | 
| +                ipath_b, idirs_b, ifiles_b = datas_b = next(iter_b)
 | 
|                  partial_iter = True
 | 
|  
 | 
|  
 | 
| -                self.assert_(ipath_a == ipath_b,
 | 
| +                self.assertTrue(ipath_a == ipath_b,
 | 
|                      "unexpected %s in %s while looking %s from %s" %
 | 
|                      (ipath_a, path_a, ipath_b, path_b))
 | 
|  
 | 
| @@ -1040,7 +1041,7 @@
 | 
|  
 | 
|  
 | 
|                  msgs = [ "%s: %s"% (name, items)
 | 
| -                    for name, items in errors.iteritems() if items]
 | 
| +                    for name, items in errors.items() if items]
 | 
|  
 | 
|                  if msgs:
 | 
|                      msgs.insert(0, "%s and %s differ :" % (
 | 
| @@ -1080,9 +1081,9 @@
 | 
|                  msg = '%r is not an instance of %s but of %s'
 | 
|              msg = msg % (obj, klass, type(obj))
 | 
|          if strict:
 | 
| -            self.assert_(obj.__class__ is klass, msg)
 | 
| +            self.assertTrue(obj.__class__ is klass, msg)
 | 
|          else:
 | 
| -            self.assert_(isinstance(obj, klass), msg)
 | 
| +            self.assertTrue(isinstance(obj, klass), msg)
 | 
|  
 | 
|      @deprecated('Please use assertIsNone instead.')
 | 
|      def assertNone(self, obj, msg=None):
 | 
| @@ -1092,7 +1093,7 @@
 | 
|          """
 | 
|          if msg is None:
 | 
|              msg = "reference to %r when None expected"%(obj,)
 | 
| -        self.assert_( obj is None, msg )
 | 
| +        self.assertTrue( obj is None, msg )
 | 
|  
 | 
|      @deprecated('Please use assertIsNotNone instead.')
 | 
|      def assertNotNone(self, obj, msg=None):
 | 
| @@ -1099,7 +1100,7 @@
 | 
|          """assert obj is not None"""
 | 
|          if msg is None:
 | 
|              msg = "unexpected reference to None"
 | 
| -        self.assert_( obj is not None, msg )
 | 
| +        self.assertTrue( obj is not None, msg )
 | 
|  
 | 
|      @deprecated('Non-standard. Please use assertAlmostEqual instead.')
 | 
|      def assertFloatAlmostEquals(self, obj, other, prec=1e-5,
 | 
| @@ -1117,7 +1118,7 @@
 | 
|              msg = "%r != %r" % (obj, other)
 | 
|          if relative:
 | 
|              prec = prec*math.fabs(obj)
 | 
| -        self.assert_(math.fabs(obj - other) < prec, msg)
 | 
| +        self.assertTrue(math.fabs(obj - other) < prec, msg)
 | 
|  
 | 
|      def failUnlessRaises(self, excClass, callableObj=None, *args, **kwargs):
 | 
|          """override default failUnlessRaises method to return the raised
 | 
| @@ -1146,7 +1147,7 @@
 | 
|              return _assert(excClass, callableObj, *args, **kwargs)
 | 
|          try:
 | 
|              callableObj(*args, **kwargs)
 | 
| -        except excClass, exc:
 | 
| +        except excClass as exc:
 | 
|              class ProxyException:
 | 
|                  def __init__(self, obj):
 | 
|                      self._obj = obj
 | 
| @@ -1166,7 +1167,17 @@
 | 
|  
 | 
|      assertRaises = failUnlessRaises
 | 
|  
 | 
| +    if sys.version_info >= (3,2):
 | 
| +        assertItemsEqual = unittest.TestCase.assertCountEqual
 | 
| +    else:
 | 
| +        assertCountEqual = unittest.TestCase.assertItemsEqual
 | 
| +        if sys.version_info < (2,7):
 | 
| +            def assertIsNotNone(self, value, *args, **kwargs):
 | 
| +                self.assertNotEqual(None, value, *args, **kwargs)
 | 
|  
 | 
| +TestCase.assertItemsEqual = deprecated('assertItemsEqual is deprecated, use assertCountEqual')(
 | 
| +    TestCase.assertItemsEqual)
 | 
| +
 | 
|  import doctest
 | 
|  
 | 
|  class SkippedSuite(unittest.TestSuite):
 | 
| @@ -1184,10 +1195,6 @@
 | 
|      def _get_test(self, obj, name, module, globs, source_lines):
 | 
|          """override default _get_test method to be able to skip tests
 | 
|          according to skipped attribute's value
 | 
| -
 | 
| -        Note: Python (<=2.4) use a _name_filter which could be used for that
 | 
| -              purpose but it's no longer available in 2.5
 | 
| -              Python 2.5 seems to have a [SKIP] flag
 | 
|          """
 | 
|          if getattr(obj, '__name__', '') in self.skipped:
 | 
|              return None
 | 
| @@ -1205,16 +1212,19 @@
 | 
|          # pylint: disable=W0613
 | 
|          try:
 | 
|              finder = DocTestFinder(skipped=self.skipped)
 | 
| -            if sys.version_info >= (2, 4):
 | 
| -                suite = doctest.DocTestSuite(self.module, test_finder=finder)
 | 
| -                if sys.version_info >= (2, 5):
 | 
| -                    # XXX iirk
 | 
| -                    doctest.DocTestCase._TestCase__exc_info = sys.exc_info
 | 
| -            else:
 | 
| -                suite = doctest.DocTestSuite(self.module)
 | 
| +            suite = doctest.DocTestSuite(self.module, test_finder=finder)
 | 
| +            # XXX iirk
 | 
| +            doctest.DocTestCase._TestCase__exc_info = sys.exc_info
 | 
|          except AttributeError:
 | 
|              suite = SkippedSuite()
 | 
| -        return suite.run(result)
 | 
| +        # doctest may gork the builtins dictionnary
 | 
| +        # This happen to the "_" entry used by gettext
 | 
| +        old_builtins = builtins.__dict__.copy()
 | 
| +        try:
 | 
| +            return suite.run(result)
 | 
| +        finally:
 | 
| +            builtins.__dict__.clear()
 | 
| +            builtins.__dict__.update(old_builtins)
 | 
|      run = __call__
 | 
|  
 | 
|      def test(self):
 | 
| @@ -1242,11 +1252,11 @@
 | 
|          """ignore quit"""
 | 
|  
 | 
|  
 | 
| -class MockConfigParser(ConfigParser):
 | 
| +class MockConfigParser(configparser.ConfigParser):
 | 
|      """fake ConfigParser.ConfigParser"""
 | 
|  
 | 
|      def __init__(self, options):
 | 
| -        ConfigParser.__init__(self)
 | 
| +        configparser.ConfigParser.__init__(self)
 | 
|          for section, pairs in options.iteritems():
 | 
|              self.add_section(section)
 | 
|              for key, value in pairs.iteritems():
 | 
| 
 |