loader.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. # -*- coding: utf-8 -*-
  2. """GRASS Python testing framework test loading functionality
  3. Copyright (C) 2014 by the GRASS Development Team
  4. This program is free software under the GNU General Public
  5. License (>=v2). Read the file COPYING that comes with GRASS GIS
  6. for details.
  7. :authors: Vaclav Petras
  8. """
  9. import os
  10. import fnmatch
  11. import unittest
  12. import collections
  13. import re
  14. # TODO: resolve test file versus test module
  15. GrassTestPythonModule = collections.namedtuple(
  16. "GrassTestPythonModule",
  17. ["name", "module", "file_type", "tested_dir", "file_dir", "abs_file_path"],
  18. )
  19. # TODO: implement loading without the import
  20. def discover_modules(
  21. start_dir,
  22. skip_dirs,
  23. testsuite_dir,
  24. grass_location,
  25. all_locations_value,
  26. universal_location_value,
  27. import_modules,
  28. add_failed_imports=True,
  29. file_pattern=None,
  30. file_regexp=None,
  31. ):
  32. """Find all test files (modules) in a directory tree.
  33. The function is designed specifically for GRASS testing framework
  34. test layout. It expects some directories to have a "testsuite"
  35. directory where test files (test modules) are present.
  36. Additionally, it also handles loading of test files which specify
  37. in which location they can run.
  38. :param start_dir: directory to start the search
  39. :param file_pattern: pattern of files in a test suite directory
  40. (using Unix shell-style wildcards)
  41. :param skip_dirs: directories not to recurse to (e.g. ``.svn``)
  42. :param testsuite_dir: name of directory where the test files are found,
  43. the function will not recurse to this directory
  44. :param grass_location: string with an accepted location type (category, shortcut)
  45. :param all_locations_value: string used to say that all locations
  46. should be loaded (grass_location can be set to this value)
  47. :param universal_location_value: string marking a test as
  48. location-independent (same as not providing any)
  49. :param import_modules: True if files should be imported as modules,
  50. False if the files should be just searched for the needed values
  51. :returns: a list of GrassTestPythonModule objects
  52. .. todo::
  53. Implement import_modules.
  54. """
  55. modules = []
  56. for root, dirs, unused_files in os.walk(start_dir, topdown=True):
  57. dirs.sort()
  58. for dir_pattern in skip_dirs:
  59. to_skip = fnmatch.filter(dirs, dir_pattern)
  60. for skip in to_skip:
  61. dirs.remove(skip)
  62. if testsuite_dir in dirs:
  63. dirs.remove(testsuite_dir) # do not recurse to testsuite
  64. full = os.path.join(root, testsuite_dir)
  65. all_files = os.listdir(full)
  66. if file_pattern:
  67. files = fnmatch.filter(all_files, file_pattern)
  68. if file_regexp:
  69. files = [f for f in all_files if re.match(file_regexp, f)]
  70. files = sorted(files)
  71. # get test/module name without .py
  72. # extpecting all files to end with .py
  73. # this will not work for invoking bat files but it works fine
  74. # as long as we handle only Python files (and using Python
  75. # interpreter for invoking)
  76. # TODO: warning about no tests in a testsuite
  77. # (in what way?)
  78. for file_name in files:
  79. # TODO: add also import if requested
  80. # (see older versions of this file)
  81. # TODO: check if there is some main in .py
  82. # otherwise we can have successful test just because
  83. # everything was loaded into Python
  84. # TODO: check if there is set -e or exit !0 or ?
  85. # otherwise we can have successful because nothing was reported
  86. abspath = os.path.abspath(full)
  87. abs_file_path = os.path.join(abspath, file_name)
  88. if file_name.endswith(".py"):
  89. if file_name == "__init__.py":
  90. # we always ignore __init__.py
  91. continue
  92. file_type = "py"
  93. name = file_name[:-3]
  94. elif file_name.endswith(".sh"):
  95. file_type = "sh"
  96. name = file_name[:-3]
  97. else:
  98. file_type = None # alternative would be '', now equivalent
  99. name = file_name
  100. add = False
  101. try:
  102. if grass_location == all_locations_value:
  103. add = True
  104. else:
  105. try:
  106. locations = ["nc", "stdmaps", "all"]
  107. except AttributeError:
  108. add = True # test is universal
  109. else:
  110. if universal_location_value in locations:
  111. add = True # cases when it is explicit
  112. if grass_location in locations:
  113. add = True # standard case with given location
  114. if not locations:
  115. add = True # count not specified as universal
  116. except ImportError as e:
  117. if add_failed_imports:
  118. add = True
  119. else:
  120. raise ImportError(
  121. "Cannot import module named"
  122. " %s in %s (%s)" % (name, full, e.message)
  123. )
  124. # alternative is to create TestClass which will raise
  125. # see unittest.loader
  126. if add:
  127. modules.append(
  128. GrassTestPythonModule(
  129. name=name,
  130. module=None,
  131. tested_dir=root,
  132. file_dir=full,
  133. abs_file_path=abs_file_path,
  134. file_type=file_type,
  135. )
  136. )
  137. # in else with some verbose we could tell about skipped test
  138. return modules
  139. # TODO: find out if this is useful for us in some way
  140. # we are now using only discover_modules directly
  141. class GrassTestLoader(unittest.TestLoader):
  142. """Class handles GRASS-specific loading of test modules."""
  143. skip_dirs = [".svn", "dist.*", "bin.*", "OBJ.*"]
  144. testsuite_dir = "testsuite"
  145. files_in_testsuite = "*.py"
  146. all_tests_value = "all"
  147. universal_tests_value = "universal"
  148. def __init__(self, grass_location):
  149. self.grass_location = grass_location
  150. # TODO: what is the purpose of top_level_dir, can it be useful?
  151. # probably yes, we need to know grass src or dist root
  152. # TODO: not using pattern here
  153. def discover(self, start_dir, pattern="test*.py", top_level_dir=None):
  154. """Load test modules from in GRASS testing framework way."""
  155. modules = discover_modules(
  156. start_dir=start_dir,
  157. file_pattern=self.files_in_testsuite,
  158. skip_dirs=self.skip_dirs,
  159. testsuite_dir=self.testsuite_dir,
  160. grass_location=self.grass_location,
  161. all_locations_value=self.all_tests_value,
  162. universal_location_value=self.universal_tests_value,
  163. import_modules=True,
  164. )
  165. tests = []
  166. for module in modules:
  167. tests.append(self.loadTestsFromModule(module.module))
  168. return self.suiteClass(tests)
  169. if __name__ == "__main__":
  170. GrassTestLoader().discover()