unittest framework kickoff
svn: r9301
This commit is contained in:
parent
3a9ef9d71f
commit
84a7a512f8
@ -1,3 +1,9 @@
|
||||
2007-11-05 Jim Sack <jgsack@san.rr.com>
|
||||
* src/test directory added
|
||||
* src/test/{__init__,regrtest,test_init}.py added
|
||||
* src/test/test/test_util_test.py added
|
||||
First steps in a new unittest framework
|
||||
|
||||
2007-11-05 Jim Sack <jgsack@san.rr.com>
|
||||
* remove 2 files from version control: config.guess and config.sub
|
||||
These are generated by configure (via autogen.sh).
|
||||
|
46
src/test/__init__.py
Normal file
46
src/test/__init__.py
Normal file
@ -0,0 +1,46 @@
|
||||
#
|
||||
# Gramps - a GTK+/GNOME based genealogy program
|
||||
#
|
||||
# Copyright (C) 2004-2006 Donald N. Allingham
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
# $Id:$
|
||||
|
||||
"""
|
||||
This package implements unittest support for GRAMPS
|
||||
|
||||
It includes a test-running program regrtest.py,
|
||||
and various test support utility modules
|
||||
(first one created being test_util.py)
|
||||
|
||||
Also includes tests for code in the parent (src) directory
|
||||
and other tests that may be considered top-level tests
|
||||
|
||||
Note: tests for utility code in this directory would be in a
|
||||
subdirectory also named test by convention for gramps testing.
|
||||
|
||||
Note: test subdirectories do not normally need to have a
|
||||
package-enabling module __init__.py, but this one (src/test) does
|
||||
because it contains utilities used by other test modules.
|
||||
Thus, this package would allow test code like
|
||||
from test import test_util
|
||||
|
||||
"""
|
||||
|
||||
# This file does not presently contain any code.
|
||||
|
||||
#===eof===
|
155
src/test/regrtest.py
Executable file
155
src/test/regrtest.py
Executable file
@ -0,0 +1,155 @@
|
||||
#! /usr/bin/env python
|
||||
#
|
||||
# Gramps - a GTK+/GNOME based genealogy program
|
||||
#
|
||||
# Copyright (C) 2000-2005 Donald N. Allingham
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Original: RunAllTests.py Written by Richard Taylor
|
||||
# (jgs: revised for embedded "test" subdirs as regrtest.py )
|
||||
|
||||
"""
|
||||
Testing framework for performing a variety of unttests for GRAMPS.
|
||||
"""
|
||||
|
||||
# TODO: review whether logging is really useful for unittest
|
||||
# it does seem to work .. try -v5
|
||||
import logging
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from optparse import OptionParser
|
||||
|
||||
from test import test_util as tu
|
||||
gramps_root=tu.path_append_parent()
|
||||
|
||||
def make_parser():
|
||||
usage = "usage: %prog [options]"
|
||||
parser = OptionParser(usage)
|
||||
parser.add_option("-v", "--verbosity", type="int",
|
||||
dest="verbose_level", default=0,
|
||||
help="Level of verboseness")
|
||||
parser.add_option("-p", "--performance", action="store_true",
|
||||
dest="performance", default=False,
|
||||
help="Run the performance tests.")
|
||||
return parser
|
||||
|
||||
|
||||
def getTestSuites(loc=gramps_root):
|
||||
# in a developer's checkout, it is worth filtering-out .svn
|
||||
# and we only need to look inside test subdirs
|
||||
# (this might change)
|
||||
# this is not so performance critical that we can't afford
|
||||
# a couple of function calls to make it readable
|
||||
# TODO: handle parts of a module (see unittest.py)
|
||||
|
||||
ldr= unittest.defaultTestLoader
|
||||
|
||||
test_dirname = "test"
|
||||
test_suffix = "_test.py"
|
||||
def test_mod(p,ds):
|
||||
""" test for path p=test dir; removes a dir '.svn' in ds """
|
||||
if ".svn" in ds:
|
||||
ds.remove(".svn")
|
||||
return os.path.basename(p) == test_dirname
|
||||
def match_mod(fs):
|
||||
""" test for any test modules; deletes all non-tests """
|
||||
# NB: do not delete fs elements within a "for f in fs"
|
||||
dels= [f for f in fs if not f.endswith(test_suffix)]
|
||||
for f in dels:
|
||||
fs.remove(f)
|
||||
return len(fs) > 0
|
||||
|
||||
test_suites = []
|
||||
perf_suites = []
|
||||
# note that test_mod and match_mod modify passed-in lists
|
||||
paths = [(path,files) for path,dirs,files in os.walk(loc) \
|
||||
if test_mod(path,dirs) and match_mod(files)]
|
||||
|
||||
oldpath = list(sys.path)
|
||||
for (dir,test_modules) in paths:
|
||||
sys.path.append(dir)
|
||||
|
||||
for module in test_modules:
|
||||
if not module.endswith(test_suffix):
|
||||
raise ValueError
|
||||
mod = __import__(module[:-len(".py")])
|
||||
if getattr(mod,"suite",None):
|
||||
test_suites.append(mod.suite())
|
||||
else:
|
||||
test_suites.append(ldr.loadTestsFromModule(mod))
|
||||
try:
|
||||
perf_suites.append(mod.perfSuite())
|
||||
except:
|
||||
pass
|
||||
# remove temporary paths added
|
||||
sys.path = list(oldpath)
|
||||
return (test_suites,perf_suites)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
def logging_init():
|
||||
global logger
|
||||
global console
|
||||
console = logging.StreamHandler()
|
||||
console.setLevel(logging.INFO)
|
||||
console.setFormatter(logging.Formatter(
|
||||
'%(name)-12s: %(levelname)-8s %(message)s'))
|
||||
logger = logging.getLogger('Gramps')
|
||||
logger.addHandler(console)
|
||||
return console, logger
|
||||
|
||||
def logging_adjust(verbose_level):
|
||||
if verbose_level == 1:
|
||||
logger.setLevel(logging.INFO)
|
||||
console.setLevel(logging.INFO)
|
||||
elif verbose_level == 2:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
console.setLevel(logging.DEBUG)
|
||||
elif verbose_level == 3:
|
||||
logger.setLevel(logging.NOTSET)
|
||||
console.setLevel(logging.NOTSET)
|
||||
elif verbose_level >= 4:
|
||||
logger.setLevel(logging.NOTSET)
|
||||
console.setLevel(logging.NOTSET)
|
||||
os.environ['GRAMPS_SIGNAL'] = "1"
|
||||
else:
|
||||
logger.setLevel(logging.ERROR)
|
||||
console.setLevel(logging.ERROR)
|
||||
|
||||
console,logger = logging_init()
|
||||
options,args = make_parser().parse_args()
|
||||
logging_adjust(options.verbose_level)
|
||||
|
||||
# TODO allow multiple subdirs, modules, or testnames
|
||||
# (see unittest.py)
|
||||
# hmmm this is starting to look like a unittest.Testprog
|
||||
# (maybe with a custom TestLoader)
|
||||
if args and os.path.isdir(args[0]):
|
||||
loc = args[0]
|
||||
else:
|
||||
loc = gramps_root
|
||||
|
||||
utests, ptests = getTestSuites(loc)
|
||||
if options.performance:
|
||||
suite = unittest.TestSuite(ptests)
|
||||
else:
|
||||
suite = unittest.TestSuite(utests)
|
||||
|
||||
unittest.TextTestRunner(verbosity=options.verbose_level).run(suite)
|
||||
|
||||
#===eof===
|
167
src/test/test/test_util_test.py
Normal file
167
src/test/test/test_util_test.py
Normal file
@ -0,0 +1,167 @@
|
||||
"""unittest (test_util_test.py) for test_util.py"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import tempfile
|
||||
import unittest as U
|
||||
|
||||
usage_note="""
|
||||
**************************************************************
|
||||
Testing (and runing) Gramps requires that PYTHONPATH include
|
||||
the path to the top Gramps directory (where gramps.py resides).
|
||||
|
||||
For example, in bash, a shell export would look like
|
||||
export PYTHONPATH=/.../src
|
||||
with the ... filled in appropriately.
|
||||
**************************************************************
|
||||
"""
|
||||
|
||||
# **************************************************************
|
||||
#
|
||||
# Since this module is used by other test modules, it is
|
||||
# strongly advised to test this module to 100% coverage,
|
||||
# and in all calling variations, eg:
|
||||
# run directly, from this dir with and without ./ prefix
|
||||
# run from other dirs (with path prefix)
|
||||
# run from within regrtest.py
|
||||
# run from regrtest.py with other test modules present
|
||||
# which use the test_util module itself
|
||||
#
|
||||
# **************************************************************
|
||||
|
||||
try:
|
||||
from test import test_util as tu
|
||||
##here = tu.absdir()
|
||||
except ImportError:
|
||||
print "Cannot import 'test_util'from package 'test'" + usage_note
|
||||
exit(1)
|
||||
|
||||
|
||||
# grouping into multiple TestCases (classes) is not required,
|
||||
# but may be useful for various reasons, such as collecting
|
||||
# tests that share a setUp/tearDown mechanism or that share
|
||||
# some test data, or just because they're related.
|
||||
#
|
||||
# The test function name should not have docstrings, but should
|
||||
# have names which add to the value of failure reporting, and
|
||||
# which make it easy to find them within the source.
|
||||
|
||||
|
||||
# some enabling infrastructure features
|
||||
class Test1(U.TestCase):
|
||||
def test1a_custom_exception(s):
|
||||
tmsg = "testing"
|
||||
try:
|
||||
err = None
|
||||
raise tu.TestError(tmsg)
|
||||
except tu.TestError,e:
|
||||
emsg = e.value
|
||||
s.assertEqual(emsg, tmsg,
|
||||
"raising TestError: g=%r e=%r" % (emsg, tmsg))
|
||||
|
||||
def test1b_msg_reporting_utility(s):
|
||||
g,e = "got this", "expected that"
|
||||
m,p = "your message here", "pfx"
|
||||
tmsg0 = m + "\n .....got:'" + g + \
|
||||
"'\n expected:'" + e +"'"
|
||||
tmsg1 = p + ": " + tmsg0
|
||||
s.assertEqual(tu.msg(g,e,m), tmsg0, "non-prefix message")
|
||||
s.assertEqual(tu.msg(g,e,m,p), tmsg1, "prefix message")
|
||||
|
||||
|
||||
# path-related features (note use of tu.msg tested above)
|
||||
class Test2(U.TestCase):
|
||||
def test2a_context_via_traceback(s):
|
||||
e = __file__.rstrip(".co") # eg in *.py[co]
|
||||
g = tu._caller_context()[0]
|
||||
g.rstrip('c')
|
||||
s.assertEqual(g,e, tu.msg(g,e, "_caller_context"))
|
||||
|
||||
def test2b_absdir(s):
|
||||
here = tu.absdir();
|
||||
g=tu.absdir(__file__)
|
||||
s.assertEqual(g,here, tu.msg(g,here, "absdir"))
|
||||
|
||||
def test2c_path_append_parent(s):
|
||||
here = tu.absdir();
|
||||
par = os.path.dirname(here)
|
||||
was_there = par in sys.path
|
||||
if was_there:
|
||||
while par in sys.path:
|
||||
sys.path.remove(par)
|
||||
np = len(sys.path)
|
||||
|
||||
for p in (None, __file__):
|
||||
s.assertFalse(par in sys.path, "par not in initial path")
|
||||
if not p:
|
||||
g = tu.path_append_parent()
|
||||
else:
|
||||
g = tu.path_append_parent(p)
|
||||
s.assertEqual(g,par, tu.msg(g,par, "path_append_parent return"))
|
||||
s.assertTrue(par in sys.path, "actually appends")
|
||||
sys.path.remove(par)
|
||||
l= len(sys.path)
|
||||
s.assertEqual(l,np, tu.msg(l,np,"numpaths"))
|
||||
if was_there:
|
||||
# restore entry state (but no multiples needed!)
|
||||
sys.path.append(par)
|
||||
|
||||
# make and remove test dirs
|
||||
class Test3(U.TestCase):
|
||||
here = tu.absdir()
|
||||
bases = (here, tempfile.gettempdir())
|
||||
asubs = map(lambda b: os.path.join(b,"test_sub"), bases)
|
||||
home= os.environ["HOME"]
|
||||
if home:
|
||||
home_junk = os.path.join(home,"test_junk")
|
||||
def _rmsubs(s):
|
||||
import shutil
|
||||
for sub in s.asubs:
|
||||
if os.path.isdir(sub):
|
||||
shutil.rmtree(sub)
|
||||
|
||||
def setUp(s):
|
||||
s._rmsubs()
|
||||
if s.home and not os.path.isdir(s.home_junk):
|
||||
os.mkdir(s.home_junk)
|
||||
|
||||
def tearDown(s):
|
||||
s._rmsubs()
|
||||
if s.home and os.path.isdir(s.home_junk):
|
||||
os.rmdir(s.home_junk)
|
||||
|
||||
def test3a_subdir(s):
|
||||
for sub in s.asubs:
|
||||
s.assertFalse(os.path.isdir(sub), "init: no dir %r" % sub)
|
||||
b,d = os.path.dirname(sub), os.path.basename(sub)
|
||||
md = tu.make_subdir(d, b)
|
||||
s.assertTrue(os.path.isdir(sub), "made dir %r" % sub)
|
||||
s.assertEqual(md,sub, tu.msg(md,sub,
|
||||
"make_subdir returns path"))
|
||||
|
||||
s2 = os.path.join(sub,"sub2")
|
||||
tu.make_subdir("sub2", sub)
|
||||
s.assertTrue(os.path.isdir(s2), "made dir %r" % s2)
|
||||
f = os.path.join(s2,"test_file")
|
||||
|
||||
open(f,"w").write("testing..")
|
||||
s.assertTrue(os.path.isfile(f), "file %r exists" % f)
|
||||
tu.delete_tree(sub)
|
||||
s.assertFalse(os.path.isdir(sub),
|
||||
"delete_tree removes subdir %r" % sub )
|
||||
|
||||
def test3b_delete_tree_constraint(s):
|
||||
if s.home:
|
||||
err = None
|
||||
try:
|
||||
tu.delete_tree(s.home_junk)
|
||||
except tu.TestError, e:
|
||||
err = e.value
|
||||
s.assertFalse(err is None,
|
||||
"deltree on %r raises TestError" % (s.home_junk))
|
||||
else:
|
||||
s.fail("Skip deltree constraint test, no '$HOME' var")
|
||||
|
||||
if __name__ == "__main__":
|
||||
U.main()
|
||||
#===eof===
|
143
src/test/test_util.py
Normal file
143
src/test/test_util.py
Normal file
@ -0,0 +1,143 @@
|
||||
"""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===
|
Loading…
Reference in New Issue
Block a user