File: //usr/lib/python2.6/site-packages/nose/util.py
"""Utility functions and classes used by nose internally.
"""
import inspect
import itertools
import logging
import os
import re
import sys
import types
import unittest
from compiler.consts import CO_GENERATOR
from types import ClassType, TypeType
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)"
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 cmp_lineno(a, b):
"""Compare functions by their line numbers.
>>> cmp_lineno(isgenerator, ispackage)
-1
>>> cmp_lineno(ispackage, isgenerator)
1
>>> cmp_lineno(isgenerator, isgenerator)
0
"""
return cmp(func_lineno(a), func_lineno(b))
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)
def isgenerator(func):
try:
return func.func_code.co_flags & CO_GENERATOR != 0
except AttributeError:
return False
# 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 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('.')))
suffixes = ('/__init__.py', '.py')
for suffix in suffixes:
filename = path + suffix
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 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.")
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 (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 (file, module, call)
if t == types.InstanceType:
return test_address(test.__class__)
if t == types.MethodType:
cls_adr = test_address(test.im_class)
return (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'):
# unittest FunctionTestCase
return test_address(test._FunctionTestCase__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 (cls_adr[0], cls_adr[1],
"%s.%s" % (cls_adr[2], method_name))
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
try:
args, varargs, varkw, defaults = inspect.getargspec(func)
except TypeError:
# Not a function. If it's callable, call it anyway
if hasattr(func, '__call__'):
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 match_last(a, b, regex):
"""Sort compare function that puts items that match a
regular expression last.
>>> from nose.config import Config
>>> c = Config()
>>> regex = c.testMatch
>>> entries = ['.', '..', 'a_test', 'src', 'lib', 'test', 'foo.py']
>>> entries.sort(lambda a, b: match_last(a, b, regex))
>>> entries
['.', '..', 'foo.py', 'lib', 'src', 'a_test', 'test']
"""
if regex.search(a) and not regex.search(b):
return 1
elif regex.search(b) and not regex.search(a):
return -1
return cmp(a, b)
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
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
if __name__ == '__main__':
import doctest
doctest.testmod()