#!/usr/bin/python
"""
Regression testing framework
Author: Ernesto Posse
Description:
This script runs a collection of unit tests in a given directory tree. It's
purpose is to easily perform regression testing.
It collects all test cases (subclasses of unittest.TestCase) found in the
directory tree in every python file whose name satisfies a given pattern,
and only in directories which satisfy the given pattern under the given base
directory.
Usage: runtests [options] [base_dir]
Options:
-h, --help show this help message and exit
-f FILE_NAME_PATTERN, --files=FILE_NAME_PATTERN
Specify the files to include by name pattern (a
regular expression).
The default is 'test[0-9]*.py$'
-d DIR_NAME_PATTERN, --dirs=DIR_NAME_PATTERN
Specify the directories to include by name pattern (a
regular expression).
The default is '.+$'
-p ADDITIONAL_PATHS, --path=ADDITIONAL_PATHS
Specify additional paths to include in the PYTHONPATH.
The default is '.'
-v VERBOSITY, --verbosity=VERBOSITY
Specify the verbosity of the test run.
The default is '2'
"""
import logging
#import logging.config
import optparse
import os.path
import pprint
import re
import sys
import unittest
import libutilitaspy.general.utils as utils
DEFAULT_FILENAME_PATTERN = "test[0-9]*.py$"
DEFAULT_DIRNAME_PATTERN = ".+$"
DEFAULT_VERBOSITY = 2
COMMAND_LINE_OPTIONS = \
[
optparse.make_option("-f", "--files", action="store", type="string",
dest="file_name_pattern", default=DEFAULT_FILENAME_PATTERN,
help="Specify the files to include by name pattern (a regular expression). The default is '%s'" % DEFAULT_FILENAME_PATTERN),
optparse.make_option("-d", "--dirs", action="store", type="string",
dest="dir_name_pattern", default=DEFAULT_DIRNAME_PATTERN,
help="Specify the directories to include by name pattern (a regular expression). The default is '%s'" % DEFAULT_DIRNAME_PATTERN),
optparse.make_option("-p", "--path", action="append", type="string",
dest="additional_paths", default=[],
help="Specify additional paths to include in the PYTHONPATH. The default is '.'" ),
optparse.make_option("-v", "--verbosity", action="store", type="int",
dest="verbosity", default=DEFAULT_VERBOSITY,
help="Specify the verbosity of the test run. The default is '%s'" % DEFAULT_VERBOSITY)
]
#logging.config.fileConfig(libutilitaspy.get_config_file()) # Commented: logger configuration done by libutilitaspy.__init__
logger = logging.getLogger(__name__)
pp = pprint.PrettyPrinter(indent=2)
[docs]def get_cmd_line_opts():
"""Returns the result of parsing the command-line options."""
global COMMAND_LINE_OPTIONS
cmd_line_parser = optparse.OptionParser(usage="runtests [options] [base_dir]")
for option in COMMAND_LINE_OPTIONS:
cmd_line_parser.add_option(option)
options, args = cmd_line_parser.parse_args()
if len(args) == 1:
base_dir = args[0]
elif len(args) == 0:
base_dir = os.getcwd()
else:
cmd_line_parser.error("incorrect number of arguments")
return base_dir, options
#def get_paths_and_modules(base_dir=os.getcwd(),
# file_name_pattern=DEFAULT_FILENAME_PATTERN,
# dir_name_pattern=DEFAULT_DIRNAME_PATTERN):
# """Traverse a directory tree beginning at base_dir and return a list of
# module names to import and a list of their paths to add to the PYTHONPATH.
# It considers only directories and files which match the given patterns."""
# files_re = re.compile(file_name_pattern, re.IGNORECASE)
# dirs_re = re.compile(dir_name_pattern, re.IGNORECASE)
# paths = set()
# module_names = []
# for root, subdirs, files in os.walk(base_dir):
# logger.info("current dir = %s" % root)
# if dirs_re.search(root):
# logger.info("current dir matched pattern")
# for file_name in files:
# logger.info("current file = %s" % file_name)
# if files_re.match(file_name):
# logger.info("current file matched pattern")
# module_name = os.path.splitext(os.path.split(file_name)[1])[0]
# module_names.append(module_name)
# paths.add(os.path.abspath(root))
# paths = list(paths)
# return paths, module_names
[docs]def get_module_names(base_dir=os.getcwd(),
file_name_pattern=DEFAULT_FILENAME_PATTERN,
dir_name_pattern=DEFAULT_DIRNAME_PATTERN):
"""Returns the list of qualified module names for modules that meet the
criteria that:
1) are defined inside a package within the base_dir directory
2) their file name matches the given pattern
3) their containing directory is a package that matches the given pattern.
"""
files_re = re.compile(file_name_pattern, re.IGNORECASE)
dirs_re = re.compile(dir_name_pattern, re.IGNORECASE)
abs_path = os.path.abspath(base_dir)
path, base_package = os.path.split(abs_path)
path_list = path.split('/')
logger.info("containing dir = '%s'; base package = '%s'" % (path, base_package))
packages = [base_package]
module_names = []
for root, subdirs, files in os.walk(abs_path):
logger.info("current dir = %s" % root)
if not '__init__.py' in files:
logger.info("current dir is not a package; ignoring sub-directories")
del subdirs[:]
continue
subpackage = '.'.join(utils.postfix_difference(path_list, root.split('/'))[1])
logger.info("current dir contains a package named '%s'" % subpackage)
if dirs_re.search(root):
logger.info("current dir matched pattern")
packages.append(subpackage)
for file_name in files:
logger.info("current file = %s" % file_name)
if files_re.match(file_name):
logger.info("current file matched pattern")
module_name = os.path.splitext(os.path.split(file_name)[1])[0]
module_qual_name = subpackage + '.' + module_name
module_names.append(module_qual_name)
logger.info("qualified module name = '%s'" % module_qual_name)
return path, module_names
def main():
try:
base_dir, options = get_cmd_line_opts()
logger.info("base_dir = '%s', options = [%s]" % (base_dir, options))
test_path, module_names = get_module_names(base_dir, options.file_name_pattern, options.dir_name_pattern)
additional_paths = [os.path.abspath(path) for path in options.additional_paths]
paths = [test_path] + additional_paths
logger.info("\npaths = \n%s\nmodule_names = \n%s" % (pp.pformat(paths), pp.pformat(module_names)))
# Add the paths to the PYTHONPATH
for path in paths:
sys.path.append(path)
logger.info("\nsys.path = \n%s\n" % (pp.pformat(sys.path)))
# Import the modules
logger.info("\nsys.modules = \n%s\n" % (pp.pformat(sys.modules)))
modules = []
for module_name in module_names:
try:
logger.info("importing module %s" % (module_name))
__import__(module_name)
logger.info("imported module %s" % (module_name))
module = sys.modules[module_name]
logger.info("dir('%s') = %s" % (module_name, dir(module)))
modules.append(module)
except ImportError:
logger.warning("failed to import module %s" % (module_name))
logger.info("All test modules imported.")
# Load the test modules and build the full test suite
load = unittest.defaultTestLoader.loadTestsFromModule
full_test_suite = unittest.TestSuite()
for module in modules:
test_suite = load(module)
full_test_suite.addTests(test_suite)
logger.info("All test modules loaded and added to the combined test suite.")
# Run the full test suite
unittest.TextTestRunner(verbosity=options.verbosity).run(full_test_suite)
logger.info("All tests done.")
except Exception, e:
logger.critical("An unexpected exception was raised: \n%s\n" % (str(e)))
raise
if __name__ == "__main__":
main()