123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 |
- # -*- coding: utf-8 -*-
- """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
- # TODO: resolve test file versus test module
- GrassTestPythonModule = collections.namedtuple(
- "GrassTestPythonModule",
- ["name", "module", "file_type", "tested_dir", "file_dir", "abs_file_path"],
- )
- # TODO: implement loading without the import
- 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,
- ):
- """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)]
- 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 == "__init__.py":
- # we always ignore __init__.py
- 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_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
- class GrassTestLoader(unittest.TestLoader):
- """Class handles GRASS-specific loading of test modules."""
- skip_dirs = [".svn", "dist.*", "bin.*", "OBJ.*"]
- testsuite_dir = "testsuite"
- files_in_testsuite = "*.py"
- all_tests_value = "all"
- universal_tests_value = "universal"
- def __init__(self, grass_location):
- 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
- 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()
|