testlib.py 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389
  1. # -*- coding: utf-8 -*-
  2. # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
  3. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
  4. #
  5. # This file is part of logilab-common.
  6. #
  7. # logilab-common is free software: you can redistribute it and/or modify it under
  8. # the terms of the GNU Lesser General Public License as published by the Free
  9. # Software Foundation, either version 2.1 of the License, or (at your option) any
  10. # later version.
  11. #
  12. # logilab-common is distributed in the hope that it will be useful, but WITHOUT
  13. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  14. # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
  15. # details.
  16. #
  17. # You should have received a copy of the GNU Lesser General Public License along
  18. # with logilab-common. If not, see <http://www.gnu.org/licenses/>.
  19. """Run tests.
  20. This will find all modules whose name match a given prefix in the test
  21. directory, and run them. Various command line options provide
  22. additional facilities.
  23. Command line options:
  24. -v verbose -- run tests in verbose mode with output to stdout
  25. -q quiet -- don't print anything except if a test fails
  26. -t testdir -- directory where the tests will be found
  27. -x exclude -- add a test to exclude
  28. -p profile -- profiled execution
  29. -d dbc -- enable design-by-contract
  30. -m match -- only run test matching the tag pattern which follow
  31. If no non-option arguments are present, prefixes used are 'test',
  32. 'regrtest', 'smoketest' and 'unittest'.
  33. """
  34. __docformat__ = "restructuredtext en"
  35. # modified copy of some functions from test/regrtest.py from PyXml
  36. # disable camel case warning
  37. # pylint: disable=C0103
  38. import sys
  39. import os, os.path as osp
  40. import re
  41. import traceback
  42. import inspect
  43. import difflib
  44. import tempfile
  45. import math
  46. import warnings
  47. from shutil import rmtree
  48. from operator import itemgetter
  49. from ConfigParser import ConfigParser
  50. from logilab.common.deprecation import deprecated
  51. from itertools import dropwhile
  52. import unittest as unittest_legacy
  53. if not getattr(unittest_legacy, "__package__", None):
  54. try:
  55. import unittest2 as unittest
  56. from unittest2 import SkipTest
  57. except ImportError:
  58. raise ImportError("You have to install python-unittest2 to use %s" % __name__)
  59. else:
  60. import unittest
  61. from unittest import SkipTest
  62. try:
  63. from functools import wraps
  64. except ImportError:
  65. def wraps(wrapped):
  66. def proxy(callable):
  67. callable.__name__ = wrapped.__name__
  68. return callable
  69. return proxy
  70. try:
  71. from test import test_support
  72. except ImportError:
  73. # not always available
  74. class TestSupport:
  75. def unload(self, test):
  76. pass
  77. test_support = TestSupport()
  78. # pylint: disable=W0622
  79. from logilab.common.compat import any, InheritableSet, callable
  80. # pylint: enable=W0622
  81. from logilab.common.debugger import Debugger, colorize_source
  82. from logilab.common.decorators import cached, classproperty
  83. from logilab.common import textutils
  84. __all__ = ['main', 'unittest_main', 'find_tests', 'run_test', 'spawn']
  85. DEFAULT_PREFIXES = ('test', 'regrtest', 'smoketest', 'unittest',
  86. 'func', 'validation')
  87. if sys.version_info >= (2, 6):
  88. # FIXME : this does not work as expected / breaks tests on testlib
  89. # however testlib does not work on py3k for many reasons ...
  90. from inspect import CO_GENERATOR
  91. else:
  92. from compiler.consts import CO_GENERATOR
  93. if sys.version_info >= (3, 0):
  94. def is_generator(function):
  95. flags = function.__code__.co_flags
  96. return flags & CO_GENERATOR
  97. else:
  98. def is_generator(function):
  99. flags = function.func_code.co_flags
  100. return flags & CO_GENERATOR
  101. # used by unittest to count the number of relevant levels in the traceback
  102. __unittest = 1
  103. def with_tempdir(callable):
  104. """A decorator ensuring no temporary file left when the function return
  105. Work only for temporary file create with the tempfile module"""
  106. @wraps(callable)
  107. def proxy(*args, **kargs):
  108. old_tmpdir = tempfile.gettempdir()
  109. new_tmpdir = tempfile.mkdtemp(prefix="temp-lgc-")
  110. tempfile.tempdir = new_tmpdir
  111. try:
  112. return callable(*args, **kargs)
  113. finally:
  114. try:
  115. rmtree(new_tmpdir, ignore_errors=True)
  116. finally:
  117. tempfile.tempdir = old_tmpdir
  118. return proxy
  119. def in_tempdir(callable):
  120. """A decorator moving the enclosed function inside the tempfile.tempfdir
  121. """
  122. @wraps(callable)
  123. def proxy(*args, **kargs):
  124. old_cwd = os.getcwd()
  125. os.chdir(tempfile.tempdir)
  126. try:
  127. return callable(*args, **kargs)
  128. finally:
  129. os.chdir(old_cwd)
  130. return proxy
  131. def within_tempdir(callable):
  132. """A decorator run the enclosed function inside a tmpdir removed after execution
  133. """
  134. proxy = with_tempdir(in_tempdir(callable))
  135. proxy.__name__ = callable.__name__
  136. return proxy
  137. def find_tests(testdir,
  138. prefixes=DEFAULT_PREFIXES, suffix=".py",
  139. excludes=(),
  140. remove_suffix=True):
  141. """
  142. Return a list of all applicable test modules.
  143. """
  144. tests = []
  145. for name in os.listdir(testdir):
  146. if not suffix or name.endswith(suffix):
  147. for prefix in prefixes:
  148. if name.startswith(prefix):
  149. if remove_suffix and name.endswith(suffix):
  150. name = name[:-len(suffix)]
  151. if name not in excludes:
  152. tests.append(name)
  153. tests.sort()
  154. return tests
  155. ## PostMortem Debug facilities #####
  156. def start_interactive_mode(result):
  157. """starts an interactive shell so that the user can inspect errors
  158. """
  159. debuggers = result.debuggers
  160. descrs = result.error_descrs + result.fail_descrs
  161. if len(debuggers) == 1:
  162. # don't ask for test name if there's only one failure
  163. debuggers[0].start()
  164. else:
  165. while True:
  166. testindex = 0
  167. print "Choose a test to debug:"
  168. # order debuggers in the same way than errors were printed
  169. print "\n".join(['\t%s : %s' % (i, descr) for i, (_, descr)
  170. in enumerate(descrs)])
  171. print "Type 'exit' (or ^D) to quit"
  172. print
  173. try:
  174. todebug = raw_input('Enter a test name: ')
  175. if todebug.strip().lower() == 'exit':
  176. print
  177. break
  178. else:
  179. try:
  180. testindex = int(todebug)
  181. debugger = debuggers[descrs[testindex][0]]
  182. except (ValueError, IndexError):
  183. print "ERROR: invalid test number %r" % (todebug, )
  184. else:
  185. debugger.start()
  186. except (EOFError, KeyboardInterrupt):
  187. print
  188. break
  189. # test utils ##################################################################
  190. class SkipAwareTestResult(unittest._TextTestResult):
  191. def __init__(self, stream, descriptions, verbosity,
  192. exitfirst=False, pdbmode=False, cvg=None, colorize=False):
  193. super(SkipAwareTestResult, self).__init__(stream,
  194. descriptions, verbosity)
  195. self.skipped = []
  196. self.debuggers = []
  197. self.fail_descrs = []
  198. self.error_descrs = []
  199. self.exitfirst = exitfirst
  200. self.pdbmode = pdbmode
  201. self.cvg = cvg
  202. self.colorize = colorize
  203. self.pdbclass = Debugger
  204. self.verbose = verbosity > 1
  205. def descrs_for(self, flavour):
  206. return getattr(self, '%s_descrs' % flavour.lower())
  207. def _create_pdb(self, test_descr, flavour):
  208. self.descrs_for(flavour).append( (len(self.debuggers), test_descr) )
  209. if self.pdbmode:
  210. self.debuggers.append(self.pdbclass(sys.exc_info()[2]))
  211. def _iter_valid_frames(self, frames):
  212. """only consider non-testlib frames when formatting traceback"""
  213. lgc_testlib = osp.abspath(__file__)
  214. std_testlib = osp.abspath(unittest.__file__)
  215. invalid = lambda fi: osp.abspath(fi[1]) in (lgc_testlib, std_testlib)
  216. for frameinfo in dropwhile(invalid, frames):
  217. yield frameinfo
  218. def _exc_info_to_string(self, err, test):
  219. """Converts a sys.exc_info()-style tuple of values into a string.
  220. This method is overridden here because we want to colorize
  221. lines if --color is passed, and display local variables if
  222. --verbose is passed
  223. """
  224. exctype, exc, tb = err
  225. output = ['Traceback (most recent call last)']
  226. frames = inspect.getinnerframes(tb)
  227. colorize = self.colorize
  228. frames = enumerate(self._iter_valid_frames(frames))
  229. for index, (frame, filename, lineno, funcname, ctx, ctxindex) in frames:
  230. filename = osp.abspath(filename)
  231. if ctx is None: # pyc files or C extensions for instance
  232. source = '<no source available>'
  233. else:
  234. source = ''.join(ctx)
  235. if colorize:
  236. filename = textutils.colorize_ansi(filename, 'magenta')
  237. source = colorize_source(source)
  238. output.append(' File "%s", line %s, in %s' % (filename, lineno, funcname))
  239. output.append(' %s' % source.strip())
  240. if self.verbose:
  241. output.append('%r == %r' % (dir(frame), test.__module__))
  242. output.append('')
  243. output.append(' ' + ' local variables '.center(66, '-'))
  244. for varname, value in sorted(frame.f_locals.items()):
  245. output.append(' %s: %r' % (varname, value))
  246. if varname == 'self': # special handy processing for self
  247. for varname, value in sorted(vars(value).items()):
  248. output.append(' self.%s: %r' % (varname, value))
  249. output.append(' ' + '-' * 66)
  250. output.append('')
  251. output.append(''.join(traceback.format_exception_only(exctype, exc)))
  252. return '\n'.join(output)
  253. def addError(self, test, err):
  254. """err -> (exc_type, exc, tcbk)"""
  255. exc_type, exc, _ = err
  256. if isinstance(exc, SkipTest):
  257. assert exc_type == SkipTest
  258. self.addSkip(test, exc)
  259. else:
  260. if self.exitfirst:
  261. self.shouldStop = True
  262. descr = self.getDescription(test)
  263. super(SkipAwareTestResult, self).addError(test, err)
  264. self._create_pdb(descr, 'error')
  265. def addFailure(self, test, err):
  266. if self.exitfirst:
  267. self.shouldStop = True
  268. descr = self.getDescription(test)
  269. super(SkipAwareTestResult, self).addFailure(test, err)
  270. self._create_pdb(descr, 'fail')
  271. def addSkip(self, test, reason):
  272. self.skipped.append((test, reason))
  273. if self.showAll:
  274. self.stream.writeln("SKIPPED")
  275. elif self.dots:
  276. self.stream.write('S')
  277. def printErrors(self):
  278. super(SkipAwareTestResult, self).printErrors()
  279. self.printSkippedList()
  280. def printSkippedList(self):
  281. # format (test, err) compatible with unittest2
  282. for test, err in self.skipped:
  283. descr = self.getDescription(test)
  284. self.stream.writeln(self.separator1)
  285. self.stream.writeln("%s: %s" % ('SKIPPED', descr))
  286. self.stream.writeln("\t%s" % err)
  287. def printErrorList(self, flavour, errors):
  288. for (_, descr), (test, err) in zip(self.descrs_for(flavour), errors):
  289. self.stream.writeln(self.separator1)
  290. self.stream.writeln("%s: %s" % (flavour, descr))
  291. self.stream.writeln(self.separator2)
  292. self.stream.writeln(err)
  293. self.stream.writeln('no stdout'.center(len(self.separator2)))
  294. self.stream.writeln('no stderr'.center(len(self.separator2)))
  295. # Add deprecation warnings about new api used by module level fixtures in unittest2
  296. # http://www.voidspace.org.uk/python/articles/unittest2.shtml#setupmodule-and-teardownmodule
  297. class _DebugResult(object): # simplify import statement among unittest flavors..
  298. "Used by the TestSuite to hold previous class when running in debug."
  299. _previousTestClass = None
  300. _moduleSetUpFailed = False
  301. shouldStop = False
  302. from logilab.common.decorators import monkeypatch
  303. @monkeypatch(unittest.TestSuite)
  304. def _handleModuleTearDown(self, result):
  305. previousModule = self._get_previous_module(result)
  306. if previousModule is None:
  307. return
  308. if result._moduleSetUpFailed:
  309. return
  310. try:
  311. module = sys.modules[previousModule]
  312. except KeyError:
  313. return
  314. # add testlib specific deprecation warning and switch to new api
  315. if hasattr(module, 'teardown_module'):
  316. warnings.warn('Please rename teardown_module() to tearDownModule() instead.',
  317. DeprecationWarning)
  318. setattr(module, 'tearDownModule', module.teardown_module)
  319. # end of monkey-patching
  320. tearDownModule = getattr(module, 'tearDownModule', None)
  321. if tearDownModule is not None:
  322. try:
  323. tearDownModule()
  324. except Exception, e:
  325. if isinstance(result, _DebugResult):
  326. raise
  327. errorName = 'tearDownModule (%s)' % previousModule
  328. self._addClassOrModuleLevelException(result, e, errorName)
  329. @monkeypatch(unittest.TestSuite)
  330. def _handleModuleFixture(self, test, result):
  331. previousModule = self._get_previous_module(result)
  332. currentModule = test.__class__.__module__
  333. if currentModule == previousModule:
  334. return
  335. self._handleModuleTearDown(result)
  336. result._moduleSetUpFailed = False
  337. try:
  338. module = sys.modules[currentModule]
  339. except KeyError:
  340. return
  341. # add testlib specific deprecation warning and switch to new api
  342. if hasattr(module, 'setup_module'):
  343. warnings.warn('Please rename setup_module() to setUpModule() instead.',
  344. DeprecationWarning)
  345. setattr(module, 'setUpModule', module.setup_module)
  346. # end of monkey-patching
  347. setUpModule = getattr(module, 'setUpModule', None)
  348. if setUpModule is not None:
  349. try:
  350. setUpModule()
  351. except Exception, e:
  352. if isinstance(result, _DebugResult):
  353. raise
  354. result._moduleSetUpFailed = True
  355. errorName = 'setUpModule (%s)' % currentModule
  356. self._addClassOrModuleLevelException(result, e, errorName)
  357. # backward compatibility: TestSuite might be imported from lgc.testlib
  358. TestSuite = unittest.TestSuite
  359. class keywords(dict):
  360. """Keyword args (**kwargs) support for generative tests."""
  361. class starargs(tuple):
  362. """Variable arguments (*args) for generative tests."""
  363. def __new__(cls, *args):
  364. return tuple.__new__(cls, args)
  365. unittest_main = unittest.main
  366. class InnerTestSkipped(SkipTest):
  367. """raised when a test is skipped"""
  368. pass
  369. def parse_generative_args(params):
  370. args = []
  371. varargs = ()
  372. kwargs = {}
  373. flags = 0 # 2 <=> starargs, 4 <=> kwargs
  374. for param in params:
  375. if isinstance(param, starargs):
  376. varargs = param
  377. if flags:
  378. raise TypeError('found starargs after keywords !')
  379. flags |= 2
  380. args += list(varargs)
  381. elif isinstance(param, keywords):
  382. kwargs = param
  383. if flags & 4:
  384. raise TypeError('got multiple keywords parameters')
  385. flags |= 4
  386. elif flags & 2 or flags & 4:
  387. raise TypeError('found parameters after kwargs or args')
  388. else:
  389. args.append(param)
  390. return args, kwargs
  391. class InnerTest(tuple):
  392. def __new__(cls, name, *data):
  393. instance = tuple.__new__(cls, data)
  394. instance.name = name
  395. return instance
  396. class Tags(InheritableSet): # 2.4 compat
  397. """A set of tag able validate an expression"""
  398. def __init__(self, *tags, **kwargs):
  399. self.inherit = kwargs.pop('inherit', True)
  400. if kwargs:
  401. raise TypeError("%s are an invalid keyword argument for this function" % kwargs.keys())
  402. if len(tags) == 1 and not isinstance(tags[0], basestring):
  403. tags = tags[0]
  404. super(Tags, self).__init__(tags, **kwargs)
  405. def __getitem__(self, key):
  406. return key in self
  407. def match(self, exp):
  408. return eval(exp, {}, self)
  409. # duplicate definition from unittest2 of the _deprecate decorator
  410. def _deprecate(original_func):
  411. def deprecated_func(*args, **kwargs):
  412. warnings.warn(
  413. ('Please use %s instead.' % original_func.__name__),
  414. DeprecationWarning, 2)
  415. return original_func(*args, **kwargs)
  416. return deprecated_func
  417. class TestCase(unittest.TestCase):
  418. """A unittest.TestCase extension with some additional methods."""
  419. maxDiff = None
  420. pdbclass = Debugger
  421. tags = Tags()
  422. def __init__(self, methodName='runTest'):
  423. super(TestCase, self).__init__(methodName)
  424. # internal API changed in python2.4 and needed by DocTestCase
  425. if sys.version_info >= (2, 4):
  426. self.__exc_info = sys.exc_info
  427. self.__testMethodName = self._testMethodName
  428. else:
  429. # let's give easier access to _testMethodName to every subclasses
  430. if hasattr(self, "__testMethodName"):
  431. self._testMethodName = self.__testMethodName
  432. self._current_test_descr = None
  433. self._options_ = None
  434. @classproperty
  435. @cached
  436. def datadir(cls): # pylint: disable=E0213
  437. """helper attribute holding the standard test's data directory
  438. NOTE: this is a logilab's standard
  439. """
  440. mod = __import__(cls.__module__)
  441. return osp.join(osp.dirname(osp.abspath(mod.__file__)), 'data')
  442. # cache it (use a class method to cache on class since TestCase is
  443. # instantiated for each test run)
  444. @classmethod
  445. def datapath(cls, *fname):
  446. """joins the object's datadir and `fname`"""
  447. return osp.join(cls.datadir, *fname)
  448. def set_description(self, descr):
  449. """sets the current test's description.
  450. This can be useful for generative tests because it allows to specify
  451. a description per yield
  452. """
  453. self._current_test_descr = descr
  454. # override default's unittest.py feature
  455. def shortDescription(self):
  456. """override default unittest shortDescription to handle correctly
  457. generative tests
  458. """
  459. if self._current_test_descr is not None:
  460. return self._current_test_descr
  461. return super(TestCase, self).shortDescription()
  462. def quiet_run(self, result, func, *args, **kwargs):
  463. try:
  464. func(*args, **kwargs)
  465. except (KeyboardInterrupt, SystemExit):
  466. raise
  467. except:
  468. result.addError(self, self.__exc_info())
  469. return False
  470. return True
  471. def _get_test_method(self):
  472. """return the test method"""
  473. return getattr(self, self._testMethodName)
  474. def optval(self, option, default=None):
  475. """return the option value or default if the option is not define"""
  476. return getattr(self._options_, option, default)
  477. def __call__(self, result=None, runcondition=None, options=None):
  478. """rewrite TestCase.__call__ to support generative tests
  479. This is mostly a copy/paste from unittest.py (i.e same
  480. variable names, same logic, except for the generative tests part)
  481. """
  482. from logilab.common.pytest import FILE_RESTART
  483. if result is None:
  484. result = self.defaultTestResult()
  485. result.pdbclass = self.pdbclass
  486. self._options_ = options
  487. # if result.cvg:
  488. # result.cvg.start()
  489. testMethod = self._get_test_method()
  490. if runcondition and not runcondition(testMethod):
  491. return # test is skipped
  492. result.startTest(self)
  493. try:
  494. if not self.quiet_run(result, self.setUp):
  495. return
  496. generative = is_generator(testMethod.im_func)
  497. # generative tests
  498. if generative:
  499. self._proceed_generative(result, testMethod,
  500. runcondition)
  501. else:
  502. status = self._proceed(result, testMethod)
  503. success = (status == 0)
  504. if not self.quiet_run(result, self.tearDown):
  505. return
  506. if not generative and success:
  507. if hasattr(options, "exitfirst") and options.exitfirst:
  508. # add this test to restart file
  509. try:
  510. restartfile = open(FILE_RESTART, 'a')
  511. try:
  512. descr = '.'.join((self.__class__.__module__,
  513. self.__class__.__name__,
  514. self._testMethodName))
  515. restartfile.write(descr+os.linesep)
  516. finally:
  517. restartfile.close()
  518. except Exception, ex:
  519. print >> sys.__stderr__, "Error while saving \
  520. succeeded test into", osp.join(os.getcwd(), FILE_RESTART)
  521. raise ex
  522. result.addSuccess(self)
  523. finally:
  524. # if result.cvg:
  525. # result.cvg.stop()
  526. result.stopTest(self)
  527. def _proceed_generative(self, result, testfunc, runcondition=None):
  528. # cancel startTest()'s increment
  529. result.testsRun -= 1
  530. success = True
  531. try:
  532. for params in testfunc():
  533. if runcondition and not runcondition(testfunc,
  534. skipgenerator=False):
  535. if not (isinstance(params, InnerTest)
  536. and runcondition(params)):
  537. continue
  538. if not isinstance(params, (tuple, list)):
  539. params = (params, )
  540. func = params[0]
  541. args, kwargs = parse_generative_args(params[1:])
  542. # increment test counter manually
  543. result.testsRun += 1
  544. status = self._proceed(result, func, args, kwargs)
  545. if status == 0:
  546. result.addSuccess(self)
  547. success = True
  548. else:
  549. success = False
  550. # XXX Don't stop anymore if an error occured
  551. #if status == 2:
  552. # result.shouldStop = True
  553. if result.shouldStop: # either on error or on exitfirst + error
  554. break
  555. except:
  556. # if an error occurs between two yield
  557. result.addError(self, self.__exc_info())
  558. success = False
  559. return success
  560. def _proceed(self, result, testfunc, args=(), kwargs=None):
  561. """proceed the actual test
  562. returns 0 on success, 1 on failure, 2 on error
  563. Note: addSuccess can't be called here because we have to wait
  564. for tearDown to be successfully executed to declare the test as
  565. successful
  566. """
  567. kwargs = kwargs or {}
  568. try:
  569. testfunc(*args, **kwargs)
  570. except self.failureException:
  571. result.addFailure(self, self.__exc_info())
  572. return 1
  573. except KeyboardInterrupt:
  574. raise
  575. except InnerTestSkipped, e:
  576. result.addSkip(self, e)
  577. return 1
  578. except SkipTest, e:
  579. result.addSkip(self, e)
  580. return 0
  581. except:
  582. result.addError(self, self.__exc_info())
  583. return 2
  584. return 0
  585. def defaultTestResult(self):
  586. """return a new instance of the defaultTestResult"""
  587. return SkipAwareTestResult()
  588. skip = _deprecate(unittest.TestCase.skipTest)
  589. assertEquals = _deprecate(unittest.TestCase.assertEqual)
  590. assertNotEquals = _deprecate(unittest.TestCase.assertNotEqual)
  591. assertAlmostEquals = _deprecate(unittest.TestCase.assertAlmostEqual)
  592. assertNotAlmostEquals = _deprecate(unittest.TestCase.assertNotAlmostEqual)
  593. def innerSkip(self, msg=None):
  594. """mark a generative test as skipped for the <msg> reason"""
  595. msg = msg or 'test was skipped'
  596. raise InnerTestSkipped(msg)
  597. @deprecated('Please use assertDictEqual instead.')
  598. def assertDictEquals(self, dict1, dict2, msg=None, context=None):
  599. """compares two dicts
  600. If the two dict differ, the first difference is shown in the error
  601. message
  602. :param dict1: a Python Dictionary
  603. :param dict2: a Python Dictionary
  604. :param msg: custom message (String) in case of failure
  605. """
  606. dict1 = dict(dict1)
  607. msgs = []
  608. for key, value in dict2.items():
  609. try:
  610. if dict1[key] != value:
  611. msgs.append('%r != %r for key %r' % (dict1[key], value,
  612. key))
  613. del dict1[key]
  614. except KeyError:
  615. msgs.append('missing %r key' % key)
  616. if dict1:
  617. msgs.append('dict2 is lacking %r' % dict1)
  618. if msg:
  619. self.failureException(msg)
  620. elif msgs:
  621. if context is not None:
  622. base = '%s\n' % context
  623. else:
  624. base = ''
  625. self.fail(base + '\n'.join(msgs))
  626. @deprecated('Please use assertItemsEqual instead.')
  627. def assertUnorderedIterableEquals(self, got, expected, msg=None):
  628. """compares two iterable and shows difference between both
  629. :param got: the unordered Iterable that we found
  630. :param expected: the expected unordered Iterable
  631. :param msg: custom message (String) in case of failure
  632. """
  633. got, expected = list(got), list(expected)
  634. self.assertSetEqual(set(got), set(expected), msg)
  635. if len(got) != len(expected):
  636. if msg is None:
  637. msg = ['Iterable have the same elements but not the same number',
  638. '\t<element>\t<expected>i\t<got>']
  639. got_count = {}
  640. expected_count = {}
  641. for element in got:
  642. got_count[element] = got_count.get(element, 0) + 1
  643. for element in expected:
  644. expected_count[element] = expected_count.get(element, 0) + 1
  645. # we know that got_count.key() == expected_count.key()
  646. # because of assertSetEqual
  647. for element, count in got_count.iteritems():
  648. other_count = expected_count[element]
  649. if other_count != count:
  650. msg.append('\t%s\t%s\t%s' % (element, other_count, count))
  651. self.fail(msg)
  652. assertUnorderedIterableEqual = assertUnorderedIterableEquals
  653. assertUnordIterEquals = assertUnordIterEqual = assertUnorderedIterableEqual
  654. @deprecated('Please use assertSetEqual instead.')
  655. def assertSetEquals(self,got,expected, msg=None):
  656. """compares two sets and shows difference between both
  657. Don't use it for iterables other than sets.
  658. :param got: the Set that we found
  659. :param expected: the second Set to be compared to the first one
  660. :param msg: custom message (String) in case of failure
  661. """
  662. if not(isinstance(got, set) and isinstance(expected, set)):
  663. warnings.warn("the assertSetEquals function if now intended for set only."\
  664. "use assertUnorderedIterableEquals instead.",
  665. DeprecationWarning, 2)
  666. return self.assertUnorderedIterableEquals(got, expected, msg)
  667. items={}
  668. items['missing'] = expected - got
  669. items['unexpected'] = got - expected
  670. if any(items.itervalues()):
  671. if msg is None:
  672. msg = '\n'.join('%s:\n\t%s' % (key, "\n\t".join(str(value) for value in values))
  673. for key, values in items.iteritems() if values)
  674. self.fail(msg)
  675. @deprecated('Please use assertListEqual instead.')
  676. def assertListEquals(self, list_1, list_2, msg=None):
  677. """compares two lists
  678. If the two list differ, the first difference is shown in the error
  679. message
  680. :param list_1: a Python List
  681. :param list_2: a second Python List
  682. :param msg: custom message (String) in case of failure
  683. """
  684. _l1 = list_1[:]
  685. for i, value in enumerate(list_2):
  686. try:
  687. if _l1[0] != value:
  688. from pprint import pprint
  689. pprint(list_1)
  690. pprint(list_2)
  691. self.fail('%r != %r for index %d' % (_l1[0], value, i))
  692. del _l1[0]
  693. except IndexError:
  694. if msg is None:
  695. msg = 'list_1 has only %d elements, not %s '\
  696. '(at least %r missing)'% (i, len(list_2), value)
  697. self.fail(msg)
  698. if _l1:
  699. if msg is None:
  700. msg = 'list_2 is lacking %r' % _l1
  701. self.fail(msg)
  702. @deprecated('Non-standard. Please use assertMultiLineEqual instead.')
  703. def assertLinesEquals(self, string1, string2, msg=None, striplines=False):
  704. """compare two strings and assert that the text lines of the strings
  705. are equal.
  706. :param string1: a String
  707. :param string2: a String
  708. :param msg: custom message (String) in case of failure
  709. :param striplines: Boolean to trigger line stripping before comparing
  710. """
  711. lines1 = string1.splitlines()
  712. lines2 = string2.splitlines()
  713. if striplines:
  714. lines1 = [l.strip() for l in lines1]
  715. lines2 = [l.strip() for l in lines2]
  716. self.assertListEqual(lines1, lines2, msg)
  717. assertLineEqual = assertLinesEquals
  718. @deprecated('Non-standard: please copy test method to your TestCase class')
  719. def assertXMLWellFormed(self, stream, msg=None, context=2):
  720. """asserts the XML stream is well-formed (no DTD conformance check)
  721. :param context: number of context lines in standard message
  722. (show all data if negative).
  723. Only available with element tree
  724. """
  725. try:
  726. from xml.etree.ElementTree import parse
  727. self._assertETXMLWellFormed(stream, parse, msg)
  728. except ImportError:
  729. from xml.sax import make_parser, SAXParseException
  730. parser = make_parser()
  731. try:
  732. parser.parse(stream)
  733. except SAXParseException, ex:
  734. if msg is None:
  735. stream.seek(0)
  736. for _ in xrange(ex.getLineNumber()):
  737. line = stream.readline()
  738. pointer = ('' * (ex.getLineNumber() - 1)) + '^'
  739. msg = 'XML stream not well formed: %s\n%s%s' % (ex, line, pointer)
  740. self.fail(msg)
  741. @deprecated('Non-standard: please copy test method to your TestCase class')
  742. def assertXMLStringWellFormed(self, xml_string, msg=None, context=2):
  743. """asserts the XML string is well-formed (no DTD conformance check)
  744. :param context: number of context lines in standard message
  745. (show all data if negative).
  746. Only available with element tree
  747. """
  748. try:
  749. from xml.etree.ElementTree import fromstring
  750. except ImportError:
  751. from elementtree.ElementTree import fromstring
  752. self._assertETXMLWellFormed(xml_string, fromstring, msg)
  753. def _assertETXMLWellFormed(self, data, parse, msg=None, context=2):
  754. """internal function used by /assertXML(String)?WellFormed/ functions
  755. :param data: xml_data
  756. :param parse: appropriate parser function for this data
  757. :param msg: error message
  758. :param context: number of context lines in standard message
  759. (show all data if negative).
  760. Only available with element tree
  761. """
  762. from xml.parsers.expat import ExpatError
  763. try:
  764. from xml.etree.ElementTree import ParseError
  765. except ImportError:
  766. # compatibility for <python2.7
  767. ParseError = ExpatError
  768. try:
  769. parse(data)
  770. except (ExpatError, ParseError), ex:
  771. if msg is None:
  772. if hasattr(data, 'readlines'): #file like object
  773. data.seek(0)
  774. lines = data.readlines()
  775. else:
  776. lines = data.splitlines(True)
  777. nb_lines = len(lines)
  778. context_lines = []
  779. # catch when ParseError doesn't set valid lineno
  780. if ex.lineno is not None:
  781. if context < 0:
  782. start = 1
  783. end = nb_lines
  784. else:
  785. start = max(ex.lineno-context, 1)
  786. end = min(ex.lineno+context, nb_lines)
  787. line_number_length = len('%i' % end)
  788. line_pattern = " %%%ii: %%s" % line_number_length
  789. for line_no in xrange(start, ex.lineno):
  790. context_lines.append(line_pattern % (line_no, lines[line_no-1]))
  791. context_lines.append(line_pattern % (ex.lineno, lines[ex.lineno-1]))
  792. context_lines.append('%s^\n' % (' ' * (1 + line_number_length + 2 +ex.offset)))
  793. for line_no in xrange(ex.lineno+1, end+1):
  794. context_lines.append(line_pattern % (line_no, lines[line_no-1]))
  795. rich_context = ''.join(context_lines)
  796. msg = 'XML stream not well formed: %s\n%s' % (ex, rich_context)
  797. self.fail(msg)
  798. @deprecated('Non-standard: please copy test method to your TestCase class')
  799. def assertXMLEqualsTuple(self, element, tup):
  800. """compare an ElementTree Element to a tuple formatted as follow:
  801. (tagname, [attrib[, children[, text[, tail]]]])"""
  802. # check tag
  803. self.assertTextEquals(element.tag, tup[0])
  804. # check attrib
  805. if len(element.attrib) or len(tup)>1:
  806. if len(tup)<=1:
  807. self.fail( "tuple %s has no attributes (%s expected)"%(tup,
  808. dict(element.attrib)))
  809. self.assertDictEqual(element.attrib, tup[1])
  810. # check children
  811. if len(element) or len(tup)>2:
  812. if len(tup)<=2:
  813. self.fail( "tuple %s has no children (%i expected)"%(tup,
  814. len(element)))
  815. if len(element) != len(tup[2]):
  816. self.fail( "tuple %s has %i children%s (%i expected)"%(tup,
  817. len(tup[2]),
  818. ('', 's')[len(tup[2])>1], len(element)))
  819. for index in xrange(len(tup[2])):
  820. self.assertXMLEqualsTuple(element[index], tup[2][index])
  821. #check text
  822. if element.text or len(tup)>3:
  823. if len(tup)<=3:
  824. self.fail( "tuple %s has no text value (%r expected)"%(tup,
  825. element.text))
  826. self.assertTextEquals(element.text, tup[3])
  827. #check tail
  828. if element.tail or len(tup)>4:
  829. if len(tup)<=4:
  830. self.fail( "tuple %s has no tail value (%r expected)"%(tup,
  831. element.tail))
  832. self.assertTextEquals(element.tail, tup[4])
  833. def _difftext(self, lines1, lines2, junk=None, msg_prefix='Texts differ'):
  834. junk = junk or (' ', '\t')
  835. # result is a generator
  836. result = difflib.ndiff(lines1, lines2, charjunk=lambda x: x in junk)
  837. read = []
  838. for line in result:
  839. read.append(line)
  840. # lines that don't start with a ' ' are diff ones
  841. if not line.startswith(' '):
  842. self.fail('\n'.join(['%s\n'%msg_prefix]+read + list(result)))
  843. @deprecated('Non-standard. Please use assertMultiLineEqual instead.')
  844. def assertTextEquals(self, text1, text2, junk=None,
  845. msg_prefix='Text differ', striplines=False):
  846. """compare two multiline strings (using difflib and splitlines())
  847. :param text1: a Python BaseString
  848. :param text2: a second Python Basestring
  849. :param junk: List of Caracters
  850. :param msg_prefix: String (message prefix)
  851. :param striplines: Boolean to trigger line stripping before comparing
  852. """
  853. msg = []
  854. if not isinstance(text1, basestring):
  855. msg.append('text1 is not a string (%s)'%(type(text1)))
  856. if not isinstance(text2, basestring):
  857. msg.append('text2 is not a string (%s)'%(type(text2)))
  858. if msg:
  859. self.fail('\n'.join(msg))
  860. lines1 = text1.strip().splitlines(True)
  861. lines2 = text2.strip().splitlines(True)
  862. if striplines:
  863. lines1 = [line.strip() for line in lines1]
  864. lines2 = [line.strip() for line in lines2]
  865. self._difftext(lines1, lines2, junk, msg_prefix)
  866. assertTextEqual = assertTextEquals
  867. @deprecated('Non-standard: please copy test method to your TestCase class')
  868. def assertStreamEquals(self, stream1, stream2, junk=None,
  869. msg_prefix='Stream differ'):
  870. """compare two streams (using difflib and readlines())"""
  871. # if stream2 is stream2, readlines() on stream1 will also read lines
  872. # in stream2, so they'll appear different, although they're not
  873. if stream1 is stream2:
  874. return
  875. # make sure we compare from the beginning of the stream
  876. stream1.seek(0)
  877. stream2.seek(0)
  878. # compare
  879. self._difftext(stream1.readlines(), stream2.readlines(), junk,
  880. msg_prefix)
  881. assertStreamEqual = assertStreamEquals
  882. @deprecated('Non-standard: please copy test method to your TestCase class')
  883. def assertFileEquals(self, fname1, fname2, junk=(' ', '\t')):
  884. """compares two files using difflib"""
  885. self.assertStreamEqual(open(fname1), open(fname2), junk,
  886. msg_prefix='Files differs\n-:%s\n+:%s\n'%(fname1, fname2))
  887. assertFileEqual = assertFileEquals
  888. @deprecated('Non-standard: please copy test method to your TestCase class')
  889. def assertDirEquals(self, path_a, path_b):
  890. """compares two files using difflib"""
  891. assert osp.exists(path_a), "%s doesn't exists" % path_a
  892. assert osp.exists(path_b), "%s doesn't exists" % path_b
  893. all_a = [ (ipath[len(path_a):].lstrip('/'), idirs, ifiles)
  894. for ipath, idirs, ifiles in os.walk(path_a)]
  895. all_a.sort(key=itemgetter(0))
  896. all_b = [ (ipath[len(path_b):].lstrip('/'), idirs, ifiles)
  897. for ipath, idirs, ifiles in os.walk(path_b)]
  898. all_b.sort(key=itemgetter(0))
  899. iter_a, iter_b = iter(all_a), iter(all_b)
  900. partial_iter = True
  901. ipath_a, idirs_a, ifiles_a = data_a = None, None, None
  902. while True:
  903. try:
  904. ipath_a, idirs_a, ifiles_a = datas_a = iter_a.next()
  905. partial_iter = False
  906. ipath_b, idirs_b, ifiles_b = datas_b = iter_b.next()
  907. partial_iter = True
  908. self.assert_(ipath_a == ipath_b,
  909. "unexpected %s in %s while looking %s from %s" %
  910. (ipath_a, path_a, ipath_b, path_b))
  911. errors = {}
  912. sdirs_a = set(idirs_a)
  913. sdirs_b = set(idirs_b)
  914. errors["unexpected directories"] = sdirs_a - sdirs_b
  915. errors["missing directories"] = sdirs_b - sdirs_a
  916. sfiles_a = set(ifiles_a)
  917. sfiles_b = set(ifiles_b)
  918. errors["unexpected files"] = sfiles_a - sfiles_b
  919. errors["missing files"] = sfiles_b - sfiles_a
  920. msgs = [ "%s: %s"% (name, items)
  921. for name, items in errors.iteritems() if items]
  922. if msgs:
  923. msgs.insert(0, "%s and %s differ :" % (
  924. osp.join(path_a, ipath_a),
  925. osp.join(path_b, ipath_b),
  926. ))
  927. self.fail("\n".join(msgs))
  928. for files in (ifiles_a, ifiles_b):
  929. files.sort()
  930. for index, path in enumerate(ifiles_a):
  931. self.assertFileEquals(osp.join(path_a, ipath_a, path),
  932. osp.join(path_b, ipath_b, ifiles_b[index]))
  933. except StopIteration:
  934. break
  935. assertDirEqual = assertDirEquals
  936. def assertIsInstance(self, obj, klass, msg=None, strict=False):
  937. """check if an object is an instance of a class
  938. :param obj: the Python Object to be checked
  939. :param klass: the target class
  940. :param msg: a String for a custom message
  941. :param strict: if True, check that the class of <obj> is <klass>;
  942. else check with 'isinstance'
  943. """
  944. if strict:
  945. warnings.warn('[API] Non-standard. Strict parameter has vanished',
  946. DeprecationWarning, stacklevel=2)
  947. if msg is None:
  948. if strict:
  949. msg = '%r is not of class %s but of %s'
  950. else:
  951. msg = '%r is not an instance of %s but of %s'
  952. msg = msg % (obj, klass, type(obj))
  953. if strict:
  954. self.assert_(obj.__class__ is klass, msg)
  955. else:
  956. self.assert_(isinstance(obj, klass), msg)
  957. @deprecated('Please use assertIsNone instead.')
  958. def assertNone(self, obj, msg=None):
  959. """assert obj is None
  960. :param obj: Python Object to be tested
  961. """
  962. if msg is None:
  963. msg = "reference to %r when None expected"%(obj,)
  964. self.assert_( obj is None, msg )
  965. @deprecated('Please use assertIsNotNone instead.')
  966. def assertNotNone(self, obj, msg=None):
  967. """assert obj is not None"""
  968. if msg is None:
  969. msg = "unexpected reference to None"
  970. self.assert_( obj is not None, msg )
  971. @deprecated('Non-standard. Please use assertAlmostEqual instead.')
  972. def assertFloatAlmostEquals(self, obj, other, prec=1e-5,
  973. relative=False, msg=None):
  974. """compares if two floats have a distance smaller than expected
  975. precision.
  976. :param obj: a Float
  977. :param other: another Float to be comparted to <obj>
  978. :param prec: a Float describing the precision
  979. :param relative: boolean switching to relative/absolute precision
  980. :param msg: a String for a custom message
  981. """
  982. if msg is None:
  983. msg = "%r != %r" % (obj, other)
  984. if relative:
  985. prec = prec*math.fabs(obj)
  986. self.assert_(math.fabs(obj - other) < prec, msg)
  987. def failUnlessRaises(self, excClass, callableObj=None, *args, **kwargs):
  988. """override default failUnlessRaises method to return the raised
  989. exception instance.
  990. Fail unless an exception of class excClass is thrown
  991. by callableObj when invoked with arguments args and keyword
  992. arguments kwargs. If a different type of exception is
  993. thrown, it will not be caught, and the test case will be
  994. deemed to have suffered an error, exactly as for an
  995. unexpected exception.
  996. CAUTION! There are subtle differences between Logilab and unittest2
  997. - exc is not returned in standard version
  998. - context capabilities in standard version
  999. - try/except/else construction (minor)
  1000. :param excClass: the Exception to be raised
  1001. :param callableObj: a callable Object which should raise <excClass>
  1002. :param args: a List of arguments for <callableObj>
  1003. :param kwargs: a List of keyword arguments for <callableObj>
  1004. """
  1005. # XXX cube vcslib : test_branches_from_app
  1006. if callableObj is None:
  1007. _assert = super(TestCase, self).assertRaises
  1008. return _assert(excClass, callableObj, *args, **kwargs)
  1009. try:
  1010. callableObj(*args, **kwargs)
  1011. except excClass, exc:
  1012. class ProxyException:
  1013. def __init__(self, obj):
  1014. self._obj = obj
  1015. def __getattr__(self, attr):
  1016. warn_msg = ("This exception was retrieved with the old testlib way "
  1017. "`exc = self.assertRaises(Exc, callable)`, please use "
  1018. "the context manager instead'")
  1019. warnings.warn(warn_msg, DeprecationWarning, 2)
  1020. return self._obj.__getattribute__(attr)
  1021. return ProxyException(exc)
  1022. else:
  1023. if hasattr(excClass, '__name__'):
  1024. excName = excClass.__name__
  1025. else:
  1026. excName = str(excClass)
  1027. raise self.failureException("%s not raised" % excName)
  1028. assertRaises = failUnlessRaises
  1029. import doctest
  1030. class SkippedSuite(unittest.TestSuite):
  1031. def test(self):
  1032. """just there to trigger test execution"""
  1033. self.skipped_test('doctest module has no DocTestSuite class')
  1034. class DocTestFinder(doctest.DocTestFinder):
  1035. def __init__(self, *args, **kwargs):
  1036. self.skipped = kwargs.pop('skipped', ())
  1037. doctest.DocTestFinder.__init__(self, *args, **kwargs)
  1038. def _get_test(self, obj, name, module, globs, source_lines):
  1039. """override default _get_test method to be able to skip tests
  1040. according to skipped attribute's value
  1041. Note: Python (<=2.4) use a _name_filter which could be used for that
  1042. purpose but it's no longer available in 2.5
  1043. Python 2.5 seems to have a [SKIP] flag
  1044. """
  1045. if getattr(obj, '__name__', '') in self.skipped:
  1046. return None
  1047. return doctest.DocTestFinder._get_test(self, obj, name, module,
  1048. globs, source_lines)
  1049. class DocTest(TestCase):
  1050. """trigger module doctest
  1051. I don't know how to make unittest.main consider the DocTestSuite instance
  1052. without this hack
  1053. """
  1054. skipped = ()
  1055. def __call__(self, result=None, runcondition=None, options=None):\
  1056. # pylint: disable=W0613
  1057. try:
  1058. finder = DocTestFinder(skipped=self.skipped)
  1059. if sys.version_info >= (2, 4):
  1060. suite = doctest.DocTestSuite(self.module, test_finder=finder)
  1061. if sys.version_info >= (2, 5):
  1062. # XXX iirk
  1063. doctest.DocTestCase._TestCase__exc_info = sys.exc_info
  1064. else:
  1065. suite = doctest.DocTestSuite(self.module)
  1066. except AttributeError:
  1067. suite = SkippedSuite()
  1068. # doctest may gork the builtins dictionnary
  1069. # This happen to the "_" entry used by gettext
  1070. old_builtins = __builtins__.copy()
  1071. try:
  1072. return suite.run(result)
  1073. finally:
  1074. __builtins__.clear()
  1075. __builtins__.update(old_builtins)
  1076. run = __call__
  1077. def test(self):
  1078. """just there to trigger test execution"""
  1079. MAILBOX = None
  1080. class MockSMTP:
  1081. """fake smtplib.SMTP"""
  1082. def __init__(self, host, port):
  1083. self.host = host
  1084. self.port = port
  1085. global MAILBOX
  1086. self.reveived = MAILBOX = []
  1087. def set_debuglevel(self, debuglevel):
  1088. """ignore debug level"""
  1089. def sendmail(self, fromaddr, toaddres, body):
  1090. """push sent mail in the mailbox"""
  1091. self.reveived.append((fromaddr, toaddres, body))
  1092. def quit(self):
  1093. """ignore quit"""
  1094. class MockConfigParser(ConfigParser):
  1095. """fake ConfigParser.ConfigParser"""
  1096. def __init__(self, options):
  1097. ConfigParser.__init__(self)
  1098. for section, pairs in options.iteritems():
  1099. self.add_section(section)
  1100. for key, value in pairs.iteritems():
  1101. self.set(section, key, value)
  1102. def write(self, _):
  1103. raise NotImplementedError()
  1104. class MockConnection:
  1105. """fake DB-API 2.0 connexion AND cursor (i.e. cursor() return self)"""
  1106. def __init__(self, results):
  1107. self.received = []
  1108. self.states = []
  1109. self.results = results
  1110. def cursor(self):
  1111. """Mock cursor method"""
  1112. return self
  1113. def execute(self, query, args=None):
  1114. """Mock execute method"""
  1115. self.received.append( (query, args) )
  1116. def fetchone(self):
  1117. """Mock fetchone method"""
  1118. return self.results[0]
  1119. def fetchall(self):
  1120. """Mock fetchall method"""
  1121. return self.results
  1122. def commit(self):
  1123. """Mock commiy method"""
  1124. self.states.append( ('commit', len(self.received)) )
  1125. def rollback(self):
  1126. """Mock rollback method"""
  1127. self.states.append( ('rollback', len(self.received)) )
  1128. def close(self):
  1129. """Mock close method"""
  1130. pass
  1131. def mock_object(**params):
  1132. """creates an object using params to set attributes
  1133. >>> option = mock_object(verbose=False, index=range(5))
  1134. >>> option.verbose
  1135. False
  1136. >>> option.index
  1137. [0, 1, 2, 3, 4]
  1138. """
  1139. return type('Mock', (), params)()
  1140. def create_files(paths, chroot):
  1141. """Creates directories and files found in <path>.
  1142. :param paths: list of relative paths to files or directories
  1143. :param chroot: the root directory in which paths will be created
  1144. >>> from os.path import isdir, isfile
  1145. >>> isdir('/tmp/a')
  1146. False
  1147. >>> create_files(['a/b/foo.py', 'a/b/c/', 'a/b/c/d/e.py'], '/tmp')
  1148. >>> isdir('/tmp/a')
  1149. True
  1150. >>> isdir('/tmp/a/b/c')
  1151. True
  1152. >>> isfile('/tmp/a/b/c/d/e.py')
  1153. True
  1154. >>> isfile('/tmp/a/b/foo.py')
  1155. True
  1156. """
  1157. dirs, files = set(), set()
  1158. for path in paths:
  1159. path = osp.join(chroot, path)
  1160. filename = osp.basename(path)
  1161. # path is a directory path
  1162. if filename == '':
  1163. dirs.add(path)
  1164. # path is a filename path
  1165. else:
  1166. dirs.add(osp.dirname(path))
  1167. files.add(path)
  1168. for dirpath in dirs:
  1169. if not osp.isdir(dirpath):
  1170. os.makedirs(dirpath)
  1171. for filepath in files:
  1172. open(filepath, 'w').close()
  1173. class AttrObject: # XXX cf mock_object
  1174. def __init__(self, **kwargs):
  1175. self.__dict__.update(kwargs)
  1176. def tag(*args, **kwargs):
  1177. """descriptor adding tag to a function"""
  1178. def desc(func):
  1179. assert not hasattr(func, 'tags')
  1180. func.tags = Tags(*args, **kwargs)
  1181. return func
  1182. return desc
  1183. def require_version(version):
  1184. """ Compare version of python interpreter to the given one. Skip the test
  1185. if older.
  1186. """
  1187. def check_require_version(f):
  1188. version_elements = version.split('.')
  1189. try:
  1190. compare = tuple([int(v) for v in version_elements])
  1191. except ValueError:
  1192. raise ValueError('%s is not a correct version : should be X.Y[.Z].' % version)
  1193. current = sys.version_info[:3]
  1194. if current < compare:
  1195. def new_f(self, *args, **kwargs):
  1196. self.skipTest('Need at least %s version of python. Current version is %s.' % (version, '.'.join([str(element) for element in current])))
  1197. new_f.__name__ = f.__name__
  1198. return new_f
  1199. else:
  1200. return f
  1201. return check_require_version
  1202. def require_module(module):
  1203. """ Check if the given module is loaded. Skip the test if not.
  1204. """
  1205. def check_require_module(f):
  1206. try:
  1207. __import__(module)
  1208. return f
  1209. except ImportError:
  1210. def new_f(self, *args, **kwargs):
  1211. self.skipTest('%s can not be imported.' % module)
  1212. new_f.__name__ = f.__name__
  1213. return new_f
  1214. return check_require_module