loader.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. """
  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()