main.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. # -*- coding: utf-8 -*-
  2. """!@package grass.gunittest.main
  3. @brief GRASS Python testing framework module for running from command line
  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 sys
  12. import argparse
  13. from unittest.main import TestProgram, USAGE_AS_MAIN
  14. TestProgram.USAGE = USAGE_AS_MAIN
  15. from .loader import GrassTestLoader
  16. from .runner import (GrassTestRunner, MultiTestResult,
  17. TextTestResult, KeyValueTestResult)
  18. from .invoker import GrassTestFilesInvoker
  19. from .utils import silent_rmtree
  20. from .reporters import FileAnonymizer
  21. import grass.script.core as gcore
  22. class GrassTestProgram(TestProgram):
  23. """A class to be used by individual test files (wrapped in the function)"""
  24. def __init__(self, exit_at_end, grass_location, clean_outputs=True,
  25. unittest_argv=None, module=None,
  26. verbosity=1,
  27. failfast=None, catchbreak=None):
  28. """Prepares the tests in GRASS way and then runs the tests.
  29. :param bool clean_outputs: if outputs in mapset and in ?
  30. """
  31. self.test = None
  32. self.grass_location = grass_location
  33. # it is unclear what the exact behavior is in unittest
  34. # buffer stdout and stderr during tests
  35. buffer_stdout_stderr = False
  36. grass_loader = GrassTestLoader(grass_location=self.grass_location)
  37. text_result = TextTestResult(stream=sys.stderr,
  38. descriptions=True,
  39. verbosity=verbosity)
  40. keyval_file = open('test_keyvalue_result.txt', 'w')
  41. keyval_result = KeyValueTestResult(stream=keyval_file)
  42. result = MultiTestResult(results=[text_result, keyval_result])
  43. grass_runner = GrassTestRunner(verbosity=verbosity,
  44. failfast=failfast,
  45. buffer=buffer_stdout_stderr,
  46. result=result)
  47. super(GrassTestProgram, self).__init__(module=module,
  48. argv=unittest_argv,
  49. testLoader=grass_loader,
  50. testRunner=grass_runner,
  51. exit=exit_at_end,
  52. verbosity=verbosity,
  53. failfast=failfast,
  54. catchbreak=catchbreak,
  55. buffer=buffer_stdout_stderr)
  56. keyval_file.close()
  57. def test():
  58. """Run a test of a module.
  59. """
  60. # TODO: put the link to to the report only if available
  61. # TODO: how to disable Python code coverage for module and C tests?
  62. # TODO: we probably need to have different test functions for C, Python modules, and Python code
  63. # TODO: combine the results using python -m coverage --help | grep combine
  64. # TODO: function to anonymize/beautify file names (in content and actual filenames)
  65. # TODO: implement coverage but only when requested by invoker and only if
  66. # it makes sense for tests (need to know what is tested)
  67. # doing_coverage = False
  68. #try:
  69. # import coverage
  70. # doing_coverage = True
  71. # cov = coverage.coverage(omit="*testsuite*")
  72. # cov.start()
  73. #except ImportError:
  74. # pass
  75. # TODO: add some message somewhere
  76. # TODO: enable passing omit to exclude also gunittest or nothing
  77. program = GrassTestProgram(module='__main__', exit_at_end=False, grass_location='all')
  78. # TODO: check if we are in the directory where the test file is
  79. # this will ensure that data directory is available when it is requested
  80. #if doing_coverage:
  81. # cov.stop()
  82. # cov.html_report(directory='testcodecoverage')
  83. # TODO: is sys.exit the right thing here
  84. sys.exit(not program.result.wasSuccessful())
  85. def discovery():
  86. """Recursively find all tests in testsuite directories and run them
  87. Everything is imported and runs in this process.
  88. Runs using::
  89. python main.py discovery [start_directory]
  90. """
  91. program = GrassTestProgram(grass_location='nc', exit_at_end=False)
  92. sys.exit(not program.result.wasSuccessful())
  93. # TODO: makefile rule should depend on the whole build
  94. # TODO: create a full interface (using grass parser or argparse)
  95. def main():
  96. parser = argparse.ArgumentParser(
  97. description='Run test files in all testsuite directories starting'
  98. ' from the current one'
  99. ' (runs on active GRASS session)')
  100. parser.add_argument('--location', dest='location', action='store',
  101. help='Name of location where to perform test', required=True)
  102. parser.add_argument('--location-type', dest='location_type', action='store',
  103. default='nc',
  104. help='Type of tests which should be run'
  105. ' (tag corresponding to location)')
  106. parser.add_argument('--grassdata', dest='gisdbase', action='store',
  107. default=None,
  108. help='GRASS data(base) (GISDBASE) directory'
  109. ' (current GISDBASE by default)')
  110. parser.add_argument('--output', dest='output', action='store',
  111. default='testreport',
  112. help='Output directory')
  113. args = parser.parse_args()
  114. gisdbase = args.gisdbase
  115. if gisdbase is None:
  116. # here we already rely on being in GRASS session
  117. gisdbase = gcore.gisenv()['GISDBASE']
  118. location = args.location
  119. location_type = args.location_type
  120. if not gisdbase:
  121. sys.stderr.write("GISDBASE (grassdata directory)"
  122. " cannot be empty string\n" % gisdbase)
  123. sys.exit(1)
  124. if not os.path.exists(gisdbase):
  125. sys.stderr.write("GISDBASE (grassdata directory) <%s>"
  126. " does not exist\n" % gisdbase)
  127. sys.exit(1)
  128. results_dir = args.output
  129. silent_rmtree(results_dir) # TODO: too brute force?
  130. start_dir = '.'
  131. abs_start_dir = os.path.abspath(start_dir)
  132. invoker = GrassTestFilesInvoker(
  133. start_dir=start_dir,
  134. file_anonymizer=FileAnonymizer(paths_to_remove=[abs_start_dir]))
  135. # TODO: remove also results dir from files
  136. # as an enhancemnt
  137. # we can just iterate over all locations available in database
  138. # but the we don't know the right location type (category, label, shortcut)
  139. invoker.run_in_location(gisdbase=gisdbase,
  140. location=location,
  141. location_type=location_type,
  142. results_dir=results_dir)
  143. return 0
  144. if __name__ == '__main__':
  145. sys.exit(main())