144 lines
4.1 KiB
Python
144 lines
4.1 KiB
Python
|
"""unittest support utility module"""
|
||
|
|
||
|
import os
|
||
|
import sys
|
||
|
import traceback
|
||
|
import tempfile
|
||
|
import shutil
|
||
|
|
||
|
# _caller_context is primarily here to support and document the process
|
||
|
# of determining the test-module's directory.
|
||
|
#
|
||
|
# NB: the traceback 0-element is 'limit'-levels back, or earliest calling
|
||
|
# context if that is less than limit.
|
||
|
# The -1 element is this very function; -2 is its caller, etc.
|
||
|
# A traceback context tuple is:
|
||
|
# (file, line, active function, text of the call-line)
|
||
|
def _caller_context():
|
||
|
"""Return context of first caller outside this module"""
|
||
|
lim = 5 # 1 for this function, plus futrher chain within this module
|
||
|
st = traceback.extract_stack(limit=lim)
|
||
|
thisfile = __file__.rstrip("co") # eg, in ".py[co]
|
||
|
while st and st[-1][0] == thisfile:
|
||
|
del(st[-1])
|
||
|
if not st:
|
||
|
raise TestError("Unexpected function call chain length!")
|
||
|
return st[-1]
|
||
|
|
||
|
|
||
|
# NB: tb[0] differs between running 'XYZ_test.py' and './XYZ_test.py'
|
||
|
# so, always take the abspath.
|
||
|
def _caller_dir():
|
||
|
"""Return directory of caller function (caller outside this module)"""
|
||
|
tb = _caller_context()
|
||
|
return os.path.dirname(os.path.abspath(tb[0]))
|
||
|
|
||
|
|
||
|
class TestError(Exception):
|
||
|
"""Exception for use by test modules
|
||
|
|
||
|
Use this, for example, to distuinguish testing errors.
|
||
|
|
||
|
"""
|
||
|
def __init__(self, value):
|
||
|
self.value = value
|
||
|
def __str__(self):
|
||
|
return repr(self.value)
|
||
|
|
||
|
|
||
|
def msg(got, exp, msg, pfx=""):
|
||
|
"""Error-report message formatting utility
|
||
|
|
||
|
This improves unittest failure messages by showing data values
|
||
|
Usage:
|
||
|
assertEqual(got,exp, msg(got,exp,"mess" [,prefix])
|
||
|
The failure message will show as
|
||
|
[prefix: ] mess
|
||
|
.....got:repr(value-of-got)
|
||
|
expected:repr(value-of-exp)
|
||
|
|
||
|
"""
|
||
|
if pfx:
|
||
|
pfx += ": "
|
||
|
return "%s%s\n .....got:%r\n expected:%r" % (pfx, msg, got, exp)
|
||
|
|
||
|
|
||
|
def absdir(path=None):
|
||
|
"""Return absolute dir of the specified path
|
||
|
|
||
|
The path parm may be dir or file or missing.
|
||
|
If a file, the dir of the file is used.
|
||
|
If missing, the dir of test-module caller is used
|
||
|
|
||
|
Common usage is
|
||
|
here = absdir()
|
||
|
here = absdir(__file__)
|
||
|
These 2 return the same result
|
||
|
|
||
|
"""
|
||
|
if not path:
|
||
|
path = _caller_dir()
|
||
|
loc = os.path.abspath(path)
|
||
|
if os.path.isfile(loc):
|
||
|
loc = os.path.dirname(loc)
|
||
|
return loc
|
||
|
|
||
|
|
||
|
def path_append_parent(path=None):
|
||
|
"""Append (if required) the parent of a path to the python system path,
|
||
|
and return the abspath to the parent as a possible convenience
|
||
|
|
||
|
The path parm may be a dir or a file or missing.
|
||
|
If a file, the the dir of the file is used.
|
||
|
If missing the test-module caller's dir is used.
|
||
|
And then the parent of that dir is appended (if not already present)
|
||
|
|
||
|
Common usage is
|
||
|
path_append_parent()
|
||
|
path_append_parent(__file__)
|
||
|
These 2 produce the same result
|
||
|
|
||
|
"""
|
||
|
pdir = os.path.dirname(absdir(path))
|
||
|
if not pdir in sys.path:
|
||
|
sys.path.append(pdir)
|
||
|
return pdir
|
||
|
|
||
|
|
||
|
def make_subdir(dir, parent=None):
|
||
|
"""Make (if required) a subdir to a given parent and return its path
|
||
|
|
||
|
The parent parm may be dir or file or missing
|
||
|
If a file, the dir of the file us used
|
||
|
If missing, the test-module caller's dir is used
|
||
|
Then the subdir dir in the parent dir is created if not already present
|
||
|
|
||
|
"""
|
||
|
if not parent:
|
||
|
parent = _caller_dir()
|
||
|
sdir = os.path.join(parent,dir)
|
||
|
if not os.path.exists(sdir):
|
||
|
os.mkdir(sdir)
|
||
|
return sdir
|
||
|
|
||
|
def delete_tree(dir):
|
||
|
"""Recursively delete directory and content
|
||
|
|
||
|
WARNING: this is clearly dangerous
|
||
|
it will only operate on subdirs of the test module dir or of /tmp
|
||
|
|
||
|
Test writers may explicitly use shutil.rmtree if really needed
|
||
|
"""
|
||
|
|
||
|
if not os.path.isdir(dir):
|
||
|
raise TestError("%r is not a dir" % dir)
|
||
|
sdir = os.path.abspath(dir)
|
||
|
here = _caller_dir() + os.path.sep
|
||
|
tmp = tempfile.gettempdir() + os.path.sep
|
||
|
if not (sdir.startswith(here) or sdir.startswith(tmp)):
|
||
|
raise TestError("%r is not a subdir of here (%r) or %r"
|
||
|
% (dir, here, tmp))
|
||
|
shutil.rmtree(sdir)
|
||
|
|
||
|
#===eof===
|