123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668 |
- """Utility functions and classes used by nose internally.
- """
- import inspect
- import itertools
- import logging
- import stat
- import os
- import re
- import sys
- import types
- import unittest
- from nose.pyversion import ClassType, TypeType, isgenerator, ismethod
- log = logging.getLogger('nose')
- ident_re = re.compile(r'^[A-Za-z_][A-Za-z0-9_.]*$')
- class_types = (ClassType, TypeType)
- skip_pattern = r"(?:\.svn)|(?:[^.]+\.py[co])|(?:.*~)|(?:.*\$py\.class)|(?:__pycache__)"
- try:
- set()
- set = set # make from nose.util import set happy
- except NameError:
- try:
- from sets import Set as set
- except ImportError:
- pass
- def ls_tree(dir_path="",
- skip_pattern=skip_pattern,
- indent="|-- ", branch_indent="| ",
- last_indent="`-- ", last_branch_indent=" "):
- # TODO: empty directories look like non-directory files
- return "\n".join(_ls_tree_lines(dir_path, skip_pattern,
- indent, branch_indent,
- last_indent, last_branch_indent))
- def _ls_tree_lines(dir_path, skip_pattern,
- indent, branch_indent, last_indent, last_branch_indent):
- if dir_path == "":
- dir_path = os.getcwd()
- lines = []
- names = os.listdir(dir_path)
- names.sort()
- dirs, nondirs = [], []
- for name in names:
- if re.match(skip_pattern, name):
- continue
- if os.path.isdir(os.path.join(dir_path, name)):
- dirs.append(name)
- else:
- nondirs.append(name)
- # list non-directories first
- entries = list(itertools.chain([(name, False) for name in nondirs],
- [(name, True) for name in dirs]))
- def ls_entry(name, is_dir, ind, branch_ind):
- if not is_dir:
- yield ind + name
- else:
- path = os.path.join(dir_path, name)
- if not os.path.islink(path):
- yield ind + name
- subtree = _ls_tree_lines(path, skip_pattern,
- indent, branch_indent,
- last_indent, last_branch_indent)
- for x in subtree:
- yield branch_ind + x
- for name, is_dir in entries[:-1]:
- for line in ls_entry(name, is_dir, indent, branch_indent):
- yield line
- if entries:
- name, is_dir = entries[-1]
- for line in ls_entry(name, is_dir, last_indent, last_branch_indent):
- yield line
- def absdir(path):
- """Return absolute, normalized path to directory, if it exists; None
- otherwise.
- """
- if not os.path.isabs(path):
- path = os.path.normpath(os.path.abspath(os.path.join(os.getcwd(),
- path)))
- if path is None or not os.path.isdir(path):
- return None
- return path
- def absfile(path, where=None):
- """Return absolute, normalized path to file (optionally in directory
- where), or None if the file can't be found either in where or the current
- working directory.
- """
- orig = path
- if where is None:
- where = os.getcwd()
- if isinstance(where, list) or isinstance(where, tuple):
- for maybe_path in where:
- maybe_abs = absfile(path, maybe_path)
- if maybe_abs is not None:
- return maybe_abs
- return None
- if not os.path.isabs(path):
- path = os.path.normpath(os.path.abspath(os.path.join(where, path)))
- if path is None or not os.path.exists(path):
- if where != os.getcwd():
- # try the cwd instead
- path = os.path.normpath(os.path.abspath(os.path.join(os.getcwd(),
- orig)))
- if path is None or not os.path.exists(path):
- return None
- if os.path.isdir(path):
- # might want an __init__.py from pacakge
- init = os.path.join(path,'__init__.py')
- if os.path.isfile(init):
- return init
- elif os.path.isfile(path):
- return path
- return None
- def anyp(predicate, iterable):
- for item in iterable:
- if predicate(item):
- return True
- return False
- def file_like(name):
- """A name is file-like if it is a path that exists, or it has a
- directory part, or it ends in .py, or it isn't a legal python
- identifier.
- """
- return (os.path.exists(name)
- or os.path.dirname(name)
- or name.endswith('.py')
- or not ident_re.match(os.path.splitext(name)[0]))
- def func_lineno(func):
- """Get the line number of a function. First looks for
- compat_co_firstlineno, then func_code.co_first_lineno.
- """
- try:
- return func.compat_co_firstlineno
- except AttributeError:
- try:
- return func.func_code.co_firstlineno
- except AttributeError:
- return -1
- def isclass(obj):
- """Is obj a class? Inspect's isclass is too liberal and returns True
- for objects that can't be subclasses of anything.
- """
- obj_type = type(obj)
- return obj_type in class_types or issubclass(obj_type, type)
- # backwards compat (issue #64)
- is_generator = isgenerator
- def ispackage(path):
- """
- Is this path a package directory?
- >>> ispackage('nose')
- True
- >>> ispackage('unit_tests')
- False
- >>> ispackage('nose/plugins')
- True
- >>> ispackage('nose/loader.py')
- False
- """
- if os.path.isdir(path):
- # at least the end of the path must be a legal python identifier
- # and __init__.py[co] must exist
- end = os.path.basename(path)
- if ident_re.match(end):
- for init in ('__init__.py', '__init__.pyc', '__init__.pyo'):
- if os.path.isfile(os.path.join(path, init)):
- return True
- if sys.platform.startswith('java') and \
- os.path.isfile(os.path.join(path, '__init__$py.class')):
- return True
- return False
- def isproperty(obj):
- """
- Is this a property?
- >>> class Foo:
- ... def got(self):
- ... return 2
- ... def get(self):
- ... return 1
- ... get = property(get)
- >>> isproperty(Foo.got)
- False
- >>> isproperty(Foo.get)
- True
- """
- return type(obj) == property
- def getfilename(package, relativeTo=None):
- """Find the python source file for a package, relative to a
- particular directory (defaults to current working directory if not
- given).
- """
- if relativeTo is None:
- relativeTo = os.getcwd()
- path = os.path.join(relativeTo, os.sep.join(package.split('.')))
- if os.path.exists(path + '/__init__.py'):
- return path
- filename = path + '.py'
- if os.path.exists(filename):
- return filename
- return None
- def getpackage(filename):
- """
- Find the full dotted package name for a given python source file
- name. Returns None if the file is not a python source file.
- >>> getpackage('foo.py')
- 'foo'
- >>> getpackage('biff/baf.py')
- 'baf'
- >>> getpackage('nose/util.py')
- 'nose.util'
- Works for directories too.
- >>> getpackage('nose')
- 'nose'
- >>> getpackage('nose/plugins')
- 'nose.plugins'
- And __init__ files stuck onto directories
- >>> getpackage('nose/plugins/__init__.py')
- 'nose.plugins'
- Absolute paths also work.
- >>> path = os.path.abspath(os.path.join('nose', 'plugins'))
- >>> getpackage(path)
- 'nose.plugins'
- """
- src_file = src(filename)
- if (os.path.isdir(src_file) or not src_file.endswith('.py')) and not ispackage(src_file):
- return None
- base, ext = os.path.splitext(os.path.basename(src_file))
- if base == '__init__':
- mod_parts = []
- else:
- mod_parts = [base]
- path, part = os.path.split(os.path.split(src_file)[0])
- while part:
- if ispackage(os.path.join(path, part)):
- mod_parts.append(part)
- else:
- break
- path, part = os.path.split(path)
- mod_parts.reverse()
- return '.'.join(mod_parts)
- def ln(label):
- """Draw a 70-char-wide divider, with label in the middle.
- >>> ln('hello there')
- '---------------------------- hello there -----------------------------'
- """
- label_len = len(label) + 2
- chunk = (70 - label_len) // 2
- out = '%s %s %s' % ('-' * chunk, label, '-' * chunk)
- pad = 70 - len(out)
- if pad > 0:
- out = out + ('-' * pad)
- return out
- def resolve_name(name, module=None):
- """Resolve a dotted name to a module and its parts. This is stolen
- wholesale from unittest.TestLoader.loadTestByName.
- >>> resolve_name('nose.util') #doctest: +ELLIPSIS
- <module 'nose.util' from...>
- >>> resolve_name('nose.util.resolve_name') #doctest: +ELLIPSIS
- <function resolve_name at...>
- """
- parts = name.split('.')
- parts_copy = parts[:]
- if module is None:
- while parts_copy:
- try:
- log.debug("__import__ %s", name)
- module = __import__('.'.join(parts_copy))
- break
- except ImportError:
- del parts_copy[-1]
- if not parts_copy:
- raise
- parts = parts[1:]
- obj = module
- log.debug("resolve: %s, %s, %s, %s", parts, name, obj, module)
- for part in parts:
- obj = getattr(obj, part)
- return obj
- def split_test_name(test):
- """Split a test name into a 3-tuple containing file, module, and callable
- names, any of which (but not all) may be blank.
- Test names are in the form:
- file_or_module:callable
- Either side of the : may be dotted. To change the splitting behavior, you
- can alter nose.util.split_test_re.
- """
- norm = os.path.normpath
- file_or_mod = test
- fn = None
- if not ':' in test:
- # only a file or mod part
- if file_like(test):
- return (norm(test), None, None)
- else:
- return (None, test, None)
- # could be path|mod:callable, or a : in the file path someplace
- head, tail = os.path.split(test)
- if not head:
- # this is a case like 'foo:bar' -- generally a module
- # name followed by a callable, but also may be a windows
- # drive letter followed by a path
- try:
- file_or_mod, fn = test.split(':')
- if file_like(fn):
- # must be a funny path
- file_or_mod, fn = test, None
- except ValueError:
- # more than one : in the test
- # this is a case like c:\some\path.py:a_test
- parts = test.split(':')
- if len(parts[0]) == 1:
- file_or_mod, fn = ':'.join(parts[:-1]), parts[-1]
- else:
- # nonsense like foo:bar:baz
- raise ValueError("Test name '%s' could not be parsed. Please "
- "format test names as path:callable or "
- "module:callable." % (test,))
- elif not tail:
- # this is a case like 'foo:bar/'
- # : must be part of the file path, so ignore it
- file_or_mod = test
- else:
- if ':' in tail:
- file_part, fn = tail.split(':')
- else:
- file_part = tail
- file_or_mod = os.sep.join([head, file_part])
- if file_or_mod:
- if file_like(file_or_mod):
- return (norm(file_or_mod), None, fn)
- else:
- return (None, file_or_mod, fn)
- else:
- return (None, None, fn)
- split_test_name.__test__ = False # do not collect
- def test_address(test):
- """Find the test address for a test, which may be a module, filename,
- class, method or function.
- """
- if hasattr(test, "address"):
- return test.address()
- # type-based polymorphism sucks in general, but I believe is
- # appropriate here
- t = type(test)
- file = module = call = None
- if t == types.ModuleType:
- file = getattr(test, '__file__', None)
- module = getattr(test, '__name__', None)
- return (src(file), module, call)
- if t == types.FunctionType or issubclass(t, type) or t == types.ClassType:
- module = getattr(test, '__module__', None)
- if module is not None:
- m = sys.modules[module]
- file = getattr(m, '__file__', None)
- if file is not None:
- file = os.path.abspath(file)
- call = getattr(test, '__name__', None)
- return (src(file), module, call)
- if t == types.MethodType:
- cls_adr = test_address(test.im_class)
- return (src(cls_adr[0]), cls_adr[1],
- "%s.%s" % (cls_adr[2], test.__name__))
- # handle unittest.TestCase instances
- if isinstance(test, unittest.TestCase):
- if (hasattr(test, '_FunctionTestCase__testFunc') # pre 2.7
- or hasattr(test, '_testFunc')): # 2.7
- # unittest FunctionTestCase
- try:
- return test_address(test._FunctionTestCase__testFunc)
- except AttributeError:
- return test_address(test._testFunc)
- # regular unittest.TestCase
- cls_adr = test_address(test.__class__)
- # 2.5 compat: __testMethodName changed to _testMethodName
- try:
- method_name = test._TestCase__testMethodName
- except AttributeError:
- method_name = test._testMethodName
- return (src(cls_adr[0]), cls_adr[1],
- "%s.%s" % (cls_adr[2], method_name))
- if (hasattr(test, '__class__') and
- test.__class__.__module__ not in ('__builtin__', 'builtins')):
- return test_address(test.__class__)
- raise TypeError("I don't know what %s is (%s)" % (test, t))
- test_address.__test__ = False # do not collect
- def try_run(obj, names):
- """Given a list of possible method names, try to run them with the
- provided object. Keep going until something works. Used to run
- setup/teardown methods for module, package, and function tests.
- """
- for name in names:
- func = getattr(obj, name, None)
- if func is not None:
- if type(obj) == types.ModuleType:
- # py.test compatibility
- if isinstance(func, types.FunctionType):
- args, varargs, varkw, defaults = \
- inspect.getargspec(func)
- else:
- # Not a function. If it's callable, call it anyway
- if hasattr(func, '__call__') and not inspect.ismethod(func):
- func = func.__call__
- try:
- args, varargs, varkw, defaults = \
- inspect.getargspec(func)
- args.pop(0) # pop the self off
- except TypeError:
- raise TypeError("Attribute %s of %r is not a python "
- "function. Only functions or callables"
- " may be used as fixtures." %
- (name, obj))
- if len(args):
- log.debug("call fixture %s.%s(%s)", obj, name, obj)
- return func(obj)
- log.debug("call fixture %s.%s", obj, name)
- return func()
- def src(filename):
- """Find the python source file for a .pyc, .pyo or $py.class file on
- jython. Returns the filename provided if it is not a python source
- file.
- """
- if filename is None:
- return filename
- if sys.platform.startswith('java') and filename.endswith('$py.class'):
- return '.'.join((filename[:-9], 'py'))
- base, ext = os.path.splitext(filename)
- if ext in ('.pyc', '.pyo', '.py'):
- return '.'.join((base, 'py'))
- return filename
- def regex_last_key(regex):
- """Sort key function factory that puts items that match a
- regular expression last.
- >>> from nose.config import Config
- >>> from nose.pyversion import sort_list
- >>> c = Config()
- >>> regex = c.testMatch
- >>> entries = ['.', '..', 'a_test', 'src', 'lib', 'test', 'foo.py']
- >>> sort_list(entries, regex_last_key(regex))
- >>> entries
- ['.', '..', 'foo.py', 'lib', 'src', 'a_test', 'test']
- """
- def k(obj):
- if regex.search(obj):
- return (1, obj)
- return (0, obj)
- return k
- def tolist(val):
- """Convert a value that may be a list or a (possibly comma-separated)
- string into a list. The exception: None is returned as None, not [None].
- >>> tolist(["one", "two"])
- ['one', 'two']
- >>> tolist("hello")
- ['hello']
- >>> tolist("separate,values, with, commas, spaces , are ,ok")
- ['separate', 'values', 'with', 'commas', 'spaces', 'are', 'ok']
- """
- if val is None:
- return None
- try:
- # might already be a list
- val.extend([])
- return val
- except AttributeError:
- pass
- # might be a string
- try:
- return re.split(r'\s*,\s*', val)
- except TypeError:
- # who knows...
- return list(val)
- class odict(dict):
- """Simple ordered dict implementation, based on:
- http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/107747
- """
- def __init__(self, *arg, **kw):
- self._keys = []
- super(odict, self).__init__(*arg, **kw)
- def __delitem__(self, key):
- super(odict, self).__delitem__(key)
- self._keys.remove(key)
- def __setitem__(self, key, item):
- super(odict, self).__setitem__(key, item)
- if key not in self._keys:
- self._keys.append(key)
- def __str__(self):
- return "{%s}" % ', '.join(["%r: %r" % (k, v) for k, v in self.items()])
- def clear(self):
- super(odict, self).clear()
- self._keys = []
- def copy(self):
- d = super(odict, self).copy()
- d._keys = self._keys[:]
- return d
- def items(self):
- return zip(self._keys, self.values())
- def keys(self):
- return self._keys[:]
- def setdefault(self, key, failobj=None):
- item = super(odict, self).setdefault(key, failobj)
- if key not in self._keys:
- self._keys.append(key)
- return item
- def update(self, dict):
- super(odict, self).update(dict)
- for key in dict.keys():
- if key not in self._keys:
- self._keys.append(key)
- def values(self):
- return map(self.get, self._keys)
- def transplant_func(func, module):
- """
- Make a function imported from module A appear as if it is located
- in module B.
- >>> from pprint import pprint
- >>> pprint.__module__
- 'pprint'
- >>> pp = transplant_func(pprint, __name__)
- >>> pp.__module__
- 'nose.util'
- The original function is not modified.
- >>> pprint.__module__
- 'pprint'
- Calling the transplanted function calls the original.
- >>> pp([1, 2])
- [1, 2]
- >>> pprint([1,2])
- [1, 2]
- """
- from nose.tools import make_decorator
- if isgenerator(func):
- def newfunc(*arg, **kw):
- for v in func(*arg, **kw):
- yield v
- else:
- def newfunc(*arg, **kw):
- return func(*arg, **kw)
- newfunc = make_decorator(func)(newfunc)
- newfunc.__module__ = module
- return newfunc
- def transplant_class(cls, module):
- """
- Make a class appear to reside in `module`, rather than the module in which
- it is actually defined.
- >>> from nose.failure import Failure
- >>> Failure.__module__
- 'nose.failure'
- >>> Nf = transplant_class(Failure, __name__)
- >>> Nf.__module__
- 'nose.util'
- >>> Nf.__name__
- 'Failure'
- """
- class C(cls):
- pass
- C.__module__ = module
- C.__name__ = cls.__name__
- return C
- def safe_str(val, encoding='utf-8'):
- try:
- return str(val)
- except UnicodeEncodeError:
- if isinstance(val, Exception):
- return ' '.join([safe_str(arg, encoding)
- for arg in val])
- return unicode(val).encode(encoding)
- def is_executable(file):
- if not os.path.exists(file):
- return False
- st = os.stat(file)
- return bool(st.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH))
- if __name__ == '__main__':
- import doctest
- doctest.testmod()
|