loader.py 7.6 KB

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