1"""Utility functions and classes used by nose internally. 2""" 3import inspect 4import itertools 5import logging 6import stat 7import os 8import re 9import sys 10import types 11import unittest 12from nose.pyversion import ClassType, TypeType, isgenerator, ismethod 13 14 15log = logging.getLogger('nose') 16 17ident_re = re.compile(r'^[A-Za-z_][A-Za-z0-9_.]*$') 18class_types = (ClassType, TypeType) 19skip_pattern = r"(?:\.svn)|(?:[^.]+\.py[co])|(?:.*~)|(?:.*\$py\.class)|(?:__pycache__)" 20 21try: 22 set() 23 set = set # make from nose.util import set happy 24except NameError: 25 try: 26 from sets import Set as set 27 except ImportError: 28 pass 29 30 31def ls_tree(dir_path="", 32 skip_pattern=skip_pattern, 33 indent="|-- ", branch_indent="| ", 34 last_indent="`-- ", last_branch_indent=" "): 35 # TODO: empty directories look like non-directory files 36 return "\n".join(_ls_tree_lines(dir_path, skip_pattern, 37 indent, branch_indent, 38 last_indent, last_branch_indent)) 39 40 41def _ls_tree_lines(dir_path, skip_pattern, 42 indent, branch_indent, last_indent, last_branch_indent): 43 if dir_path == "": 44 dir_path = os.getcwd() 45 46 lines = [] 47 48 names = os.listdir(dir_path) 49 names.sort() 50 dirs, nondirs = [], [] 51 for name in names: 52 if re.match(skip_pattern, name): 53 continue 54 if os.path.isdir(os.path.join(dir_path, name)): 55 dirs.append(name) 56 else: 57 nondirs.append(name) 58 59 # list non-directories first 60 entries = list(itertools.chain([(name, False) for name in nondirs], 61 [(name, True) for name in dirs])) 62 def ls_entry(name, is_dir, ind, branch_ind): 63 if not is_dir: 64 yield ind + name 65 else: 66 path = os.path.join(dir_path, name) 67 if not os.path.islink(path): 68 yield ind + name 69 subtree = _ls_tree_lines(path, skip_pattern, 70 indent, branch_indent, 71 last_indent, last_branch_indent) 72 for x in subtree: 73 yield branch_ind + x 74 for name, is_dir in entries[:-1]: 75 for line in ls_entry(name, is_dir, indent, branch_indent): 76 yield line 77 if entries: 78 name, is_dir = entries[-1] 79 for line in ls_entry(name, is_dir, last_indent, last_branch_indent): 80 yield line 81 82 83def absdir(path): 84 """Return absolute, normalized path to directory, if it exists; None 85 otherwise. 86 """ 87 if not os.path.isabs(path): 88 path = os.path.normpath(os.path.abspath(os.path.join(os.getcwd(), 89 path))) 90 if path is None or not os.path.isdir(path): 91 return None 92 return path 93 94 95def absfile(path, where=None): 96 """Return absolute, normalized path to file (optionally in directory 97 where), or None if the file can't be found either in where or the current 98 working directory. 99 """ 100 orig = path 101 if where is None: 102 where = os.getcwd() 103 if isinstance(where, list) or isinstance(where, tuple): 104 for maybe_path in where: 105 maybe_abs = absfile(path, maybe_path) 106 if maybe_abs is not None: 107 return maybe_abs 108 return None 109 if not os.path.isabs(path): 110 path = os.path.normpath(os.path.abspath(os.path.join(where, path))) 111 if path is None or not os.path.exists(path): 112 if where != os.getcwd(): 113 # try the cwd instead 114 path = os.path.normpath(os.path.abspath(os.path.join(os.getcwd(), 115 orig))) 116 if path is None or not os.path.exists(path): 117 return None 118 if os.path.isdir(path): 119 # might want an __init__.py from pacakge 120 init = os.path.join(path,'__init__.py') 121 if os.path.isfile(init): 122 return init 123 elif os.path.isfile(path): 124 return path 125 return None 126 127 128def anyp(predicate, iterable): 129 for item in iterable: 130 if predicate(item): 131 return True 132 return False 133 134 135def file_like(name): 136 """A name is file-like if it is a path that exists, or it has a 137 directory part, or it ends in .py, or it isn't a legal python 138 identifier. 139 """ 140 return (os.path.exists(name) 141 or os.path.dirname(name) 142 or name.endswith('.py') 143 or not ident_re.match(os.path.splitext(name)[0])) 144 145 146def func_lineno(func): 147 """Get the line number of a function. First looks for 148 compat_co_firstlineno, then func_code.co_first_lineno. 149 """ 150 try: 151 return func.compat_co_firstlineno 152 except AttributeError: 153 try: 154 return func.func_code.co_firstlineno 155 except AttributeError: 156 return -1 157 158 159def isclass(obj): 160 """Is obj a class? Inspect's isclass is too liberal and returns True 161 for objects that can't be subclasses of anything. 162 """ 163 obj_type = type(obj) 164 return obj_type in class_types or issubclass(obj_type, type) 165 166 167# backwards compat (issue #64) 168is_generator = isgenerator 169 170 171def ispackage(path): 172 """ 173 Is this path a package directory? 174 175 >>> ispackage('nose') 176 True 177 >>> ispackage('unit_tests') 178 False 179 >>> ispackage('nose/plugins') 180 True 181 >>> ispackage('nose/loader.py') 182 False 183 """ 184 if os.path.isdir(path): 185 # at least the end of the path must be a legal python identifier 186 # and __init__.py[co] must exist 187 end = os.path.basename(path) 188 if ident_re.match(end): 189 for init in ('__init__.py', '__init__.pyc', '__init__.pyo'): 190 if os.path.isfile(os.path.join(path, init)): 191 return True 192 if sys.platform.startswith('java') and \ 193 os.path.isfile(os.path.join(path, '__init__$py.class')): 194 return True 195 return False 196 197 198def isproperty(obj): 199 """ 200 Is this a property? 201 202 >>> class Foo: 203 ... def got(self): 204 ... return 2 205 ... def get(self): 206 ... return 1 207 ... get = property(get) 208 209 >>> isproperty(Foo.got) 210 False 211 >>> isproperty(Foo.get) 212 True 213 """ 214 return type(obj) == property 215 216 217def getfilename(package, relativeTo=None): 218 """Find the python source file for a package, relative to a 219 particular directory (defaults to current working directory if not 220 given). 221 """ 222 if relativeTo is None: 223 relativeTo = os.getcwd() 224 path = os.path.join(relativeTo, os.sep.join(package.split('.'))) 225 if os.path.exists(path + '/__init__.py'): 226 return path 227 filename = path + '.py' 228 if os.path.exists(filename): 229 return filename 230 return None 231 232 233def getpackage(filename): 234 """ 235 Find the full dotted package name for a given python source file 236 name. Returns None if the file is not a python source file. 237 238 >>> getpackage('foo.py') 239 'foo' 240 >>> getpackage('biff/baf.py') 241 'baf' 242 >>> getpackage('nose/util.py') 243 'nose.util' 244 245 Works for directories too. 246 247 >>> getpackage('nose') 248 'nose' 249 >>> getpackage('nose/plugins') 250 'nose.plugins' 251 252 And __init__ files stuck onto directories 253 254 >>> getpackage('nose/plugins/__init__.py') 255 'nose.plugins' 256 257 Absolute paths also work. 258 259 >>> path = os.path.abspath(os.path.join('nose', 'plugins')) 260 >>> getpackage(path) 261 'nose.plugins' 262 """ 263 src_file = src(filename) 264 if (os.path.isdir(src_file) or not src_file.endswith('.py')) and not ispackage(src_file): 265 return None 266 base, ext = os.path.splitext(os.path.basename(src_file)) 267 if base == '__init__': 268 mod_parts = [] 269 else: 270 mod_parts = [base] 271 path, part = os.path.split(os.path.split(src_file)[0]) 272 while part: 273 if ispackage(os.path.join(path, part)): 274 mod_parts.append(part) 275 else: 276 break 277 path, part = os.path.split(path) 278 mod_parts.reverse() 279 return '.'.join(mod_parts) 280 281 282def ln(label): 283 """Draw a 70-char-wide divider, with label in the middle. 284 285 >>> ln('hello there') 286 '---------------------------- hello there -----------------------------' 287 """ 288 label_len = len(label) + 2 289 chunk = (70 - label_len) // 2 290 out = '%s %s %s' % ('-' * chunk, label, '-' * chunk) 291 pad = 70 - len(out) 292 if pad > 0: 293 out = out + ('-' * pad) 294 return out 295 296 297def resolve_name(name, module=None): 298 """Resolve a dotted name to a module and its parts. This is stolen 299 wholesale from unittest.TestLoader.loadTestByName. 300 301 >>> resolve_name('nose.util') #doctest: +ELLIPSIS 302 <module 'nose.util' from...> 303 >>> resolve_name('nose.util.resolve_name') #doctest: +ELLIPSIS 304 <function resolve_name at...> 305 """ 306 parts = name.split('.') 307 parts_copy = parts[:] 308 if module is None: 309 while parts_copy: 310 try: 311 log.debug("__import__ %s", name) 312 module = __import__('.'.join(parts_copy)) 313 break 314 except ImportError: 315 del parts_copy[-1] 316 if not parts_copy: 317 raise 318 parts = parts[1:] 319 obj = module 320 log.debug("resolve: %s, %s, %s, %s", parts, name, obj, module) 321 for part in parts: 322 obj = getattr(obj, part) 323 return obj 324 325 326def split_test_name(test): 327 """Split a test name into a 3-tuple containing file, module, and callable 328 names, any of which (but not all) may be blank. 329 330 Test names are in the form: 331 332 file_or_module:callable 333 334 Either side of the : may be dotted. To change the splitting behavior, you 335 can alter nose.util.split_test_re. 336 """ 337 norm = os.path.normpath 338 file_or_mod = test 339 fn = None 340 if not ':' in test: 341 # only a file or mod part 342 if file_like(test): 343 return (norm(test), None, None) 344 else: 345 return (None, test, None) 346 347 # could be path|mod:callable, or a : in the file path someplace 348 head, tail = os.path.split(test) 349 if not head: 350 # this is a case like 'foo:bar' -- generally a module 351 # name followed by a callable, but also may be a windows 352 # drive letter followed by a path 353 try: 354 file_or_mod, fn = test.split(':') 355 if file_like(fn): 356 # must be a funny path 357 file_or_mod, fn = test, None 358 except ValueError: 359 # more than one : in the test 360 # this is a case like c:\some\path.py:a_test 361 parts = test.split(':') 362 if len(parts[0]) == 1: 363 file_or_mod, fn = ':'.join(parts[:-1]), parts[-1] 364 else: 365 # nonsense like foo:bar:baz 366 raise ValueError("Test name '%s' could not be parsed. Please " 367 "format test names as path:callable or " 368 "module:callable." % (test,)) 369 elif not tail: 370 # this is a case like 'foo:bar/' 371 # : must be part of the file path, so ignore it 372 file_or_mod = test 373 else: 374 if ':' in tail: 375 file_part, fn = tail.split(':') 376 else: 377 file_part = tail 378 file_or_mod = os.sep.join([head, file_part]) 379 if file_or_mod: 380 if file_like(file_or_mod): 381 return (norm(file_or_mod), None, fn) 382 else: 383 return (None, file_or_mod, fn) 384 else: 385 return (None, None, fn) 386split_test_name.__test__ = False # do not collect 387 388 389def test_address(test): 390 """Find the test address for a test, which may be a module, filename, 391 class, method or function. 392 """ 393 if hasattr(test, "address"): 394 return test.address() 395 # type-based polymorphism sucks in general, but I believe is 396 # appropriate here 397 t = type(test) 398 file = module = call = None 399 if t == types.ModuleType: 400 file = getattr(test, '__file__', None) 401 module = getattr(test, '__name__', None) 402 return (src(file), module, call) 403 if t == types.FunctionType or issubclass(t, type) or t == types.ClassType: 404 module = getattr(test, '__module__', None) 405 if module is not None: 406 m = sys.modules[module] 407 file = getattr(m, '__file__', None) 408 if file is not None: 409 file = os.path.abspath(file) 410 call = getattr(test, '__name__', None) 411 return (src(file), module, call) 412 if t == types.MethodType: 413 cls_adr = test_address(test.im_class) 414 return (src(cls_adr[0]), cls_adr[1], 415 "%s.%s" % (cls_adr[2], test.__name__)) 416 # handle unittest.TestCase instances 417 if isinstance(test, unittest.TestCase): 418 if (hasattr(test, '_FunctionTestCase__testFunc') # pre 2.7 419 or hasattr(test, '_testFunc')): # 2.7 420 # unittest FunctionTestCase 421 try: 422 return test_address(test._FunctionTestCase__testFunc) 423 except AttributeError: 424 return test_address(test._testFunc) 425 # regular unittest.TestCase 426 cls_adr = test_address(test.__class__) 427 # 2.5 compat: __testMethodName changed to _testMethodName 428 try: 429 method_name = test._TestCase__testMethodName 430 except AttributeError: 431 method_name = test._testMethodName 432 return (src(cls_adr[0]), cls_adr[1], 433 "%s.%s" % (cls_adr[2], method_name)) 434 if (hasattr(test, '__class__') and 435 test.__class__.__module__ not in ('__builtin__', 'builtins')): 436 return test_address(test.__class__) 437 raise TypeError("I don't know what %s is (%s)" % (test, t)) 438test_address.__test__ = False # do not collect 439 440 441def try_run(obj, names): 442 """Given a list of possible method names, try to run them with the 443 provided object. Keep going until something works. Used to run 444 setup/teardown methods for module, package, and function tests. 445 """ 446 for name in names: 447 func = getattr(obj, name, None) 448 if func is not None: 449 if type(obj) == types.ModuleType: 450 # py.test compatibility 451 if isinstance(func, types.FunctionType): 452 args, varargs, varkw, defaults = \ 453 inspect.getargspec(func) 454 else: 455 # Not a function. If it's callable, call it anyway 456 if hasattr(func, '__call__') and not inspect.ismethod(func): 457 func = func.__call__ 458 try: 459 args, varargs, varkw, defaults = \ 460 inspect.getargspec(func) 461 args.pop(0) # pop the self off 462 except TypeError: 463 raise TypeError("Attribute %s of %r is not a python " 464 "function. Only functions or callables" 465 " may be used as fixtures." % 466 (name, obj)) 467 if len(args): 468 log.debug("call fixture %s.%s(%s)", obj, name, obj) 469 return func(obj) 470 log.debug("call fixture %s.%s", obj, name) 471 return func() 472 473 474def src(filename): 475 """Find the python source file for a .pyc, .pyo or $py.class file on 476 jython. Returns the filename provided if it is not a python source 477 file. 478 """ 479 if filename is None: 480 return filename 481 if sys.platform.startswith('java') and filename.endswith('$py.class'): 482 return '.'.join((filename[:-9], 'py')) 483 base, ext = os.path.splitext(filename) 484 if ext in ('.pyc', '.pyo', '.py'): 485 return '.'.join((base, 'py')) 486 return filename 487 488 489def regex_last_key(regex): 490 """Sort key function factory that puts items that match a 491 regular expression last. 492 493 >>> from nose.config import Config 494 >>> from nose.pyversion import sort_list 495 >>> c = Config() 496 >>> regex = c.testMatch 497 >>> entries = ['.', '..', 'a_test', 'src', 'lib', 'test', 'foo.py'] 498 >>> sort_list(entries, regex_last_key(regex)) 499 >>> entries 500 ['.', '..', 'foo.py', 'lib', 'src', 'a_test', 'test'] 501 """ 502 def k(obj): 503 if regex.search(obj): 504 return (1, obj) 505 return (0, obj) 506 return k 507 508 509def tolist(val): 510 """Convert a value that may be a list or a (possibly comma-separated) 511 string into a list. The exception: None is returned as None, not [None]. 512 513 >>> tolist(["one", "two"]) 514 ['one', 'two'] 515 >>> tolist("hello") 516 ['hello'] 517 >>> tolist("separate,values, with, commas, spaces , are ,ok") 518 ['separate', 'values', 'with', 'commas', 'spaces', 'are', 'ok'] 519 """ 520 if val is None: 521 return None 522 try: 523 # might already be a list 524 val.extend([]) 525 return val 526 except AttributeError: 527 pass 528 # might be a string 529 try: 530 return re.split(r'\s*,\s*', val) 531 except TypeError: 532 # who knows... 533 return list(val) 534 535 536class odict(dict): 537 """Simple ordered dict implementation, based on: 538 539 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/107747 540 """ 541 def __init__(self, *arg, **kw): 542 self._keys = [] 543 super(odict, self).__init__(*arg, **kw) 544 545 def __delitem__(self, key): 546 super(odict, self).__delitem__(key) 547 self._keys.remove(key) 548 549 def __setitem__(self, key, item): 550 super(odict, self).__setitem__(key, item) 551 if key not in self._keys: 552 self._keys.append(key) 553 554 def __str__(self): 555 return "{%s}" % ', '.join(["%r: %r" % (k, v) for k, v in self.items()]) 556 557 def clear(self): 558 super(odict, self).clear() 559 self._keys = [] 560 561 def copy(self): 562 d = super(odict, self).copy() 563 d._keys = self._keys[:] 564 return d 565 566 def items(self): 567 return zip(self._keys, self.values()) 568 569 def keys(self): 570 return self._keys[:] 571 572 def setdefault(self, key, failobj=None): 573 item = super(odict, self).setdefault(key, failobj) 574 if key not in self._keys: 575 self._keys.append(key) 576 return item 577 578 def update(self, dict): 579 super(odict, self).update(dict) 580 for key in dict.keys(): 581 if key not in self._keys: 582 self._keys.append(key) 583 584 def values(self): 585 return map(self.get, self._keys) 586 587 588def transplant_func(func, module): 589 """ 590 Make a function imported from module A appear as if it is located 591 in module B. 592 593 >>> from pprint import pprint 594 >>> pprint.__module__ 595 'pprint' 596 >>> pp = transplant_func(pprint, __name__) 597 >>> pp.__module__ 598 'nose.util' 599 600 The original function is not modified. 601 602 >>> pprint.__module__ 603 'pprint' 604 605 Calling the transplanted function calls the original. 606 607 >>> pp([1, 2]) 608 [1, 2] 609 >>> pprint([1,2]) 610 [1, 2] 611 612 """ 613 from nose.tools import make_decorator 614 if isgenerator(func): 615 def newfunc(*arg, **kw): 616 for v in func(*arg, **kw): 617 yield v 618 else: 619 def newfunc(*arg, **kw): 620 return func(*arg, **kw) 621 622 newfunc = make_decorator(func)(newfunc) 623 newfunc.__module__ = module 624 return newfunc 625 626 627def transplant_class(cls, module): 628 """ 629 Make a class appear to reside in `module`, rather than the module in which 630 it is actually defined. 631 632 >>> from nose.failure import Failure 633 >>> Failure.__module__ 634 'nose.failure' 635 >>> Nf = transplant_class(Failure, __name__) 636 >>> Nf.__module__ 637 'nose.util' 638 >>> Nf.__name__ 639 'Failure' 640 641 """ 642 class C(cls): 643 pass 644 C.__module__ = module 645 C.__name__ = cls.__name__ 646 return C 647 648 649def safe_str(val, encoding='utf-8'): 650 try: 651 return str(val) 652 except UnicodeEncodeError: 653 if isinstance(val, Exception): 654 return ' '.join([safe_str(arg, encoding) 655 for arg in val]) 656 return unicode(val).encode(encoding) 657 658 659def is_executable(file): 660 if not os.path.exists(file): 661 return False 662 st = os.stat(file) 663 return bool(st.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)) 664 665 666if __name__ == '__main__': 667 import doctest 668 doctest.testmod() 669