Source code for libutilitaspy.testing.runtests

#!/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()