Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(337)

Side by Side Diff: third_party/logilab/logilab/common/testlib.py

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

Powered by Google App Engine
This is Rietveld 408576698