Source code for grass.gunittest.loader

GRASS Python testing framework test loading functionality

Copyright (C) 2014 by the GRASS Development Team
This program is free software under the GNU General Public
License (>=v2). Read the file COPYING that comes with GRASS GIS
for details.

:authors: Vaclav Petras

import os
import fnmatch
import unittest
import collections
import re

[docs]def fnmatch_exclude_with_base(files, base, exclude): """Return list of files not matching any exclusion pattern :param files: list of file names :param base: directory (path) where the files are :param exclude: list of fnmatch glob patterns for exclusion """ not_excluded = [] patterns = [] # Make all dir separators slashes and drop leading current dir # for both patterns and (later) for files. for pattern in exclude: pattern = pattern.replace(os.sep, "/") if pattern.startswith("./"): patterns.append(pattern[2:]) else: patterns.append(pattern) for filename in files: full_file_path = os.path.join(base, filename) test_filename = full_file_path.replace(os.sep, "/") if full_file_path.startswith("./"): test_filename = full_file_path[2:] matches = False for pattern in patterns: if fnmatch.fnmatch(test_filename, pattern): matches = True break if not matches: not_excluded.append(filename) return not_excluded
# TODO: resolve test file versus test module GrassTestPythonModule = collections.namedtuple( "GrassTestPythonModule", [ "name", "module", "file_type", "tested_dir", "file_dir", "file_path", "abs_file_path", ], ) # TODO: implement loading without the import
[docs]def discover_modules( start_dir, skip_dirs, testsuite_dir, grass_location, all_locations_value, universal_location_value, import_modules, add_failed_imports=True, file_pattern=None, file_regexp=None, exclude=None, ): """Find all test files (modules) in a directory tree. The function is designed specifically for GRASS testing framework test layout. It expects some directories to have a "testsuite" directory where test files (test modules) are present. Additionally, it also handles loading of test files which specify in which location they can run. :param start_dir: directory to start the search :param file_pattern: pattern of files in a test suite directory (using Unix shell-style wildcards) :param skip_dirs: directories not to recurse to (e.g. ``.svn``) :param testsuite_dir: name of directory where the test files are found, the function will not recurse to this directory :param grass_location: string with an accepted location type (category, shortcut) :param all_locations_value: string used to say that all locations should be loaded (grass_location can be set to this value) :param universal_location_value: string marking a test as location-independent (same as not providing any) :param import_modules: True if files should be imported as modules, False if the files should be just searched for the needed values :returns: a list of GrassTestPythonModule objects .. todo:: Implement import_modules. """ modules = [] for root, dirs, unused_files in os.walk(start_dir, topdown=True): dirs.sort() for dir_pattern in skip_dirs: to_skip = fnmatch.filter(dirs, dir_pattern) for skip in to_skip: dirs.remove(skip) if testsuite_dir in dirs: dirs.remove(testsuite_dir) # do not recurse to testsuite full = os.path.join(root, testsuite_dir) all_files = os.listdir(full) if file_pattern: files = fnmatch.filter(all_files, file_pattern) if file_regexp: files = [f for f in all_files if re.match(file_regexp, f)] if exclude: files = fnmatch_exclude_with_base(files, full, exclude) files = sorted(files) # get test/module name without .py # extpecting all files to end with .py # this will not work for invoking bat files but it works fine # as long as we handle only Python files (and using Python # interpreter for invoking) # TODO: warning about no tests in a testsuite # (in what way?) for file_name in files: # TODO: add also import if requested # (see older versions of this file) # TODO: check if there is some main in .py # otherwise we can have successful test just because # everything was loaded into Python # TODO: check if there is set -e or exit !0 or ? # otherwise we can have successful because nothing was reported abspath = os.path.abspath(full) abs_file_path = os.path.join(abspath, file_name) if file_name.endswith(".py"): if file_name == "": # we always ignore continue file_type = "py" name = file_name[:-3] elif file_name.endswith(".sh"): file_type = "sh" name = file_name[:-3] else: file_type = None # alternative would be '', now equivalent name = file_name add = False try: if grass_location == all_locations_value: add = True else: try: locations = ["nc", "stdmaps", "all"] except AttributeError: add = True # test is universal else: if universal_location_value in locations: add = True # cases when it is explicit if grass_location in locations: add = True # standard case with given location if not locations: add = True # count not specified as universal except ImportError as e: if add_failed_imports: add = True else: raise ImportError( "Cannot import module named" " %s in %s (%s)" % (name, full, e.message) ) # alternative is to create TestClass which will raise # see unittest.loader if add: modules.append( GrassTestPythonModule( name=name, module=None, tested_dir=root, file_dir=full, abs_file_path=abs_file_path, file_path=os.path.join(full, file_name), file_type=file_type, ) ) # in else with some verbose we could tell about skipped test return modules
# TODO: find out if this is useful for us in some way # we are now using only discover_modules directly
[docs]class GrassTestLoader(unittest.TestLoader): """Class handles GRASS-specific loading of test modules.""" skip_dirs = [".git", ".svn", "dist.*", "bin.*", "OBJ.*"] testsuite_dir = "testsuite" files_in_testsuite = "*.py" all_tests_value = "all" universal_tests_value = "universal" def __init__(self, grass_location): super().__init__() self.grass_location = grass_location # TODO: what is the purpose of top_level_dir, can it be useful? # probably yes, we need to know grass src or dist root # TODO: not using pattern here
[docs] def discover(self, start_dir, pattern="test*.py", top_level_dir=None): """Load test modules from in GRASS testing framework way.""" modules = discover_modules( start_dir=start_dir, file_pattern=self.files_in_testsuite, skip_dirs=self.skip_dirs, testsuite_dir=self.testsuite_dir, grass_location=self.grass_location, all_locations_value=self.all_tests_value, universal_location_value=self.universal_tests_value, import_modules=True, ) tests = [] for module in modules: tests.append(self.loadTestsFromModule(module.module)) return self.suiteClass(tests)
if __name__ == "__main__": GrassTestLoader().discover()