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