runner.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. # -*- coding: utf-8 -*-
  2. """!@package grass.gunittest.runner
  3. @brief Testing framework module for running tests in Python unittest fashion
  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. File content taken from Python's ``unittest.runner``, it will be used as
  10. a template. It is not expected that something will left.
  11. """
  12. import sys
  13. import time
  14. import unittest.result
  15. from unittest.signals import registerResult
  16. __unittest = True
  17. class _WritelnDecorator(object):
  18. """Used to decorate file-like objects with a handy 'writeln' method"""
  19. def __init__(self,stream):
  20. self.stream = stream
  21. def __getattr__(self, attr):
  22. if attr in ('stream', '__getstate__'):
  23. raise AttributeError(attr)
  24. return getattr(self.stream,attr)
  25. def writeln(self, arg=None):
  26. if arg:
  27. self.write(arg)
  28. self.write('\n') # text-mode streams translate to \r\n if needed
  29. class TestResult(unittest.result.TestResult):
  30. # descriptions and verbosity unused
  31. # included for compatibility with unittest's TestResult
  32. # where are also unused, so perhaps we can remove them
  33. # stream set to None and not included in interface, it would not make sense
  34. def __init__(self, stream=None, descriptions=None, verbosity=None):
  35. super(TestResult, self).__init__(
  36. stream=stream, descriptions=descriptions, verbosity=verbosity)
  37. self.successes = []
  38. def addSuccess(self, test):
  39. super(TestResult, self).addSuccess(test)
  40. self.successes.append(test)
  41. # TODO: better would be to pass start at the beginning
  42. # alternative is to leave counting time on class
  43. # TODO: document: we expect all grass classes to have setTimes
  44. # TODO: alternatively, be more forgiving for non-unittest methods
  45. def setTimes(self, start_time, end_time, time_taken):
  46. pass
  47. # TODO: implement this
  48. class TextTestResult(TestResult):
  49. """A test result class that can print formatted text results to a stream.
  50. Used by TextTestRunner.
  51. """
  52. separator1 = '=' * 70
  53. separator2 = '-' * 70
  54. def __init__(self, stream, descriptions, verbosity):
  55. super(TextTestResult, self).__init__(
  56. stream=stream, descriptions=descriptions, verbosity=verbosity)
  57. self.stream = _WritelnDecorator(stream)
  58. self.showAll = verbosity > 1
  59. self.dots = verbosity == 1
  60. self.descriptions = descriptions
  61. self.start_time = None
  62. self.end_time = None
  63. self.time_taken = None
  64. def getDescription(self, test):
  65. doc_first_line = test.shortDescription()
  66. if self.descriptions and doc_first_line:
  67. return '\n'.join((str(test), doc_first_line))
  68. else:
  69. return str(test)
  70. def startTest(self, test):
  71. super(TextTestResult, self).startTest(test)
  72. if self.showAll:
  73. self.stream.write(self.getDescription(test))
  74. self.stream.write(" ... ")
  75. self.stream.flush()
  76. def addSuccess(self, test):
  77. super(TextTestResult, self).addSuccess(test)
  78. if self.showAll:
  79. self.stream.writeln("ok")
  80. elif self.dots:
  81. self.stream.write('.')
  82. self.stream.flush()
  83. def addError(self, test, err):
  84. super(TextTestResult, self).addError(test, err)
  85. if self.showAll:
  86. self.stream.writeln("ERROR")
  87. elif self.dots:
  88. self.stream.write('E')
  89. self.stream.flush()
  90. def addFailure(self, test, err):
  91. super(TextTestResult, self).addFailure(test, err)
  92. if self.showAll:
  93. self.stream.writeln("FAIL")
  94. elif self.dots:
  95. self.stream.write('F')
  96. self.stream.flush()
  97. def addSkip(self, test, reason):
  98. super(TextTestResult, self).addSkip(test, reason)
  99. if self.showAll:
  100. self.stream.writeln("skipped {0!r}".format(reason))
  101. elif self.dots:
  102. self.stream.write("s")
  103. self.stream.flush()
  104. def addExpectedFailure(self, test, err):
  105. super(TextTestResult, self).addExpectedFailure(test, err)
  106. if self.showAll:
  107. self.stream.writeln("expected failure")
  108. elif self.dots:
  109. self.stream.write("x")
  110. self.stream.flush()
  111. def addUnexpectedSuccess(self, test):
  112. super(TextTestResult, self).addUnexpectedSuccess(test)
  113. if self.showAll:
  114. self.stream.writeln("unexpected success")
  115. elif self.dots:
  116. self.stream.write("u")
  117. self.stream.flush()
  118. def printErrors(self):
  119. if self.dots or self.showAll:
  120. self.stream.writeln()
  121. self.printErrorList('ERROR', self.errors)
  122. self.printErrorList('FAIL', self.failures)
  123. def printErrorList(self, flavour, errors):
  124. for test, err in errors:
  125. self.stream.writeln(self.separator1)
  126. self.stream.writeln("%s: %s" % (flavour,
  127. self.getDescription(test)))
  128. self.stream.writeln(self.separator2)
  129. self.stream.writeln("%s" % err)
  130. def setTimes(self, start_time, end_time, time_taken):
  131. self.start_time = start_time
  132. self.end_time = end_time
  133. self.time_taken = time_taken
  134. def stopTestRun(self):
  135. super(TextTestResult, self).stopTestRun()
  136. self.printErrors()
  137. self.stream.writeln(self.separator2)
  138. run = self.testsRun
  139. self.stream.write("Ran %d test%s" % (run, run != 1 and "s" or ""))
  140. if self.time_taken:
  141. self.stream.write(" in %.3fs" % (self.time_taken))
  142. self.stream.writeln()
  143. expectedFails = unexpectedSuccesses = skipped = 0
  144. results = map(len, (self.expectedFailures,
  145. self.unexpectedSuccesses,
  146. self.skipped))
  147. expectedFails, unexpectedSuccesses, skipped = results
  148. infos = []
  149. if not self.wasSuccessful():
  150. self.stream.write("FAILED")
  151. failed, errored = map(len, (self.failures, self.errors))
  152. if failed:
  153. infos.append("failures=%d" % failed)
  154. if errored:
  155. infos.append("errors=%d" % errored)
  156. else:
  157. self.stream.write("OK")
  158. if skipped:
  159. infos.append("skipped=%d" % skipped)
  160. if expectedFails:
  161. infos.append("expected_failures=%d" % expectedFails)
  162. if unexpectedSuccesses:
  163. infos.append("unexpected_successes=%d" % unexpectedSuccesses)
  164. if infos:
  165. self.stream.writeln(" (%s)" % (", ".join(infos),))
  166. else:
  167. self.stream.write("\n")
  168. class KeyValueTestResult(TestResult):
  169. """A test result class that can print formatted text results to a stream.
  170. Used by TextTestRunner.
  171. """
  172. separator1 = '=' * 70
  173. separator2 = '-' * 70
  174. def __init__(self, stream, test_type=None):
  175. super(KeyValueTestResult, self).__init__(
  176. stream=stream, descriptions=None, verbosity=None)
  177. self._stream = _WritelnDecorator(stream)
  178. self.start_time = None
  179. self.end_time = None
  180. self.time_taken = None
  181. if test_type:
  182. self.test_type = test_type
  183. else:
  184. self.test_type = 'not-specified'
  185. self._grass_modules = []
  186. self._supplementary_files = []
  187. def setTimes(self, start_time, end_time, time_taken):
  188. self.start_time = start_time
  189. self.end_time = end_time
  190. self.time_taken = time_taken
  191. def stopTest(self, test):
  192. super(KeyValueTestResult, self).stopTest(test)
  193. if hasattr(test, 'grass_modules'):
  194. self._grass_modules.extend(test.grass_modules)
  195. if hasattr(test, 'supplementary_files'):
  196. self._supplementary_files.extend(test.supplementary_files)
  197. def stopTestRun(self):
  198. super(KeyValueTestResult, self).stopTestRun()
  199. infos = []
  200. run = self.testsRun
  201. # TODO: name should be included by test file caller
  202. # from inspect import getsourcefile
  203. # from os.path import abspath
  204. # abspath(getsourcefile(lambda _: None))
  205. # not writing name is a good option
  206. # infos.append("name=%s" % 'unknown')
  207. infos.append("time=%.3fs" % (self.time_taken))
  208. # 'date={rundate}\n'
  209. # 'date={runtime}\n'
  210. # 'date={start_datetime}\n'
  211. # 'date={end_datetime}\n'
  212. failed, errored = map(len, (self.failures, self.errors))
  213. succeeded = len(self.successes)
  214. results = map(len, (self.expectedFailures,
  215. self.unexpectedSuccesses,
  216. self.skipped))
  217. expectedFails, unexpectedSuccesses, skipped = results
  218. status = 'succeeded' if self.wasSuccessful() else 'failed'
  219. infos.append("status=%s" % status)
  220. infos.append("total=%d" % (run))
  221. infos.append("failures=%d" % failed)
  222. infos.append("errors=%d" % errored)
  223. infos.append("successes=%d" % succeeded)
  224. infos.append("skipped=%d" % skipped)
  225. # TODO: document this: if not supported by view,
  226. # expected_failures should be counted as failures and vice versa
  227. # or both add to skipped as unclear?
  228. infos.append("expected_failures=%d" % expectedFails)
  229. infos.append("unexpected_successes=%d" % unexpectedSuccesses)
  230. # TODO: include each module just once? list good and bad modules?
  231. infos.append("tested_modules=%s" % ','.join(self._grass_modules))
  232. infos.append("supplementary_files=%s" % ','.join(self._supplementary_files))
  233. # module, modules?, c, c++?, python
  234. # TODO: include also type modules?
  235. # TODO: include also C++ code?
  236. # TODO: distinguish C and Python modules?
  237. infos.append("test_type=%s" % (self.test_type))
  238. self._stream.write('\n'.join(infos))
  239. self._stream.write('\n')
  240. self._stream.flush()
  241. class MultiTestResult(TestResult):
  242. # descriptions and verbosity unused
  243. # included for compatibility with unittest's TestResult
  244. # where are also unused, so perhaps we can remove them
  245. # stream set to None and not included in interface, it would not make sense
  246. def __init__(self, results, forgiving=False,
  247. descriptions=None, verbosity=None):
  248. super(MultiTestResult, self).__init__(
  249. descriptions=descriptions, verbosity=verbosity, stream=None)
  250. self._results = results
  251. self._forgiving = forgiving
  252. def startTest(self, test):
  253. super(MultiTestResult, self).startTest(test)
  254. for result in self._results:
  255. try:
  256. result.startTest(test)
  257. except AttributeError:
  258. if self._forgiving:
  259. pass
  260. else:
  261. raise
  262. def stopTest(self, test):
  263. """Called when the given test has been run"""
  264. super(MultiTestResult, self).stopTest(test)
  265. for result in self._results:
  266. try:
  267. result.stopTest(test)
  268. except AttributeError:
  269. if self._forgiving:
  270. pass
  271. else:
  272. raise
  273. def addSuccess(self, test):
  274. super(MultiTestResult, self).addSuccess(test)
  275. for result in self._results:
  276. try:
  277. result.addSuccess(test)
  278. except AttributeError:
  279. if self._forgiving:
  280. pass
  281. else:
  282. raise
  283. def addError(self, test, err):
  284. super(MultiTestResult, self).addError(test, err)
  285. for result in self._results:
  286. try:
  287. result.addError(test, err)
  288. except AttributeError:
  289. if self._forgiving:
  290. pass
  291. else:
  292. raise
  293. def addFailure(self, test, err):
  294. super(MultiTestResult, self).addFailure(test, err)
  295. for result in self._results:
  296. try:
  297. result.addFailure(test, err)
  298. except AttributeError:
  299. if self._forgiving:
  300. pass
  301. else:
  302. raise
  303. def addSkip(self, test, reason):
  304. super(MultiTestResult, self).addSkip(test, reason)
  305. for result in self._results:
  306. try:
  307. result.addSkip(test, reason)
  308. except AttributeError:
  309. if self._forgiving:
  310. pass
  311. else:
  312. raise
  313. def addExpectedFailure(self, test, err):
  314. super(MultiTestResult, self).addExpectedFailure(test, err)
  315. for result in self._results:
  316. try:
  317. result.addExpectedFailure(test, err)
  318. except AttributeError:
  319. if self._forgiving:
  320. pass
  321. else:
  322. raise
  323. def addUnexpectedSuccess(self, test):
  324. super(MultiTestResult, self).addUnexpectedSuccess(test)
  325. for result in self._results:
  326. try:
  327. result.addUnexpectedSuccess(test)
  328. except AttributeError:
  329. if self._forgiving:
  330. pass
  331. else:
  332. raise
  333. def printErrors(self):
  334. "Called by TestRunner after test run"
  335. super(MultiTestResult, self).printErrors()
  336. for result in self._results:
  337. try:
  338. result.printErrors()
  339. except AttributeError:
  340. if self._forgiving:
  341. pass
  342. else:
  343. raise
  344. def startTestRun(self):
  345. """Called once before any tests are executed.
  346. See startTest for a method called before each test.
  347. """
  348. super(MultiTestResult, self).startTestRun()
  349. for result in self._results:
  350. try:
  351. result.startTestRun()
  352. except AttributeError:
  353. if self._forgiving:
  354. pass
  355. else:
  356. raise
  357. def stopTestRun(self):
  358. """Called once after all tests are executed.
  359. See stopTest for a method called after each test.
  360. """
  361. super(MultiTestResult, self).stopTestRun()
  362. for result in self._results:
  363. try:
  364. result.stopTestRun()
  365. except AttributeError:
  366. if self._forgiving:
  367. pass
  368. else:
  369. raise
  370. # TODO: better would be to pass start at the beginning
  371. # alternative is to leave counting time on class
  372. # TODO: document: we expect all grass classes to have setTimes
  373. # TODO: alternatively, be more forgiving for non-unittest methods
  374. def setTimes(self, start_time, end_time, time_taken):
  375. for result in self._results:
  376. try:
  377. result.setTimes(start_time, end_time, time_taken)
  378. except AttributeError:
  379. if self._forgiving:
  380. pass
  381. else:
  382. raise
  383. class GrassTestRunner(object):
  384. def __init__(self, stream=sys.stderr, descriptions=True, verbosity=1,
  385. failfast=False, buffer=False, result=None):
  386. self.stream = _WritelnDecorator(stream)
  387. self.descriptions = descriptions
  388. self.verbosity = verbosity
  389. self.failfast = failfast
  390. self.buffer = buffer
  391. self._result = result
  392. def run(self, test):
  393. "Run the given test case or test suite."
  394. result = self._result
  395. registerResult(result)
  396. result.failfast = self.failfast
  397. result.buffer = self.buffer
  398. startTime = time.time()
  399. startTestRun = getattr(result, 'startTestRun', None)
  400. if startTestRun is not None:
  401. startTestRun()
  402. try:
  403. test(result)
  404. finally:
  405. stopTime = time.time()
  406. timeTaken = stopTime - startTime
  407. setTimes = getattr(result, 'setTimes', None)
  408. if setTimes is not None:
  409. setTimes(startTime, stopTime, timeTaken)
  410. stopTestRun = getattr(result, 'stopTestRun', None)
  411. if stopTestRun is not None:
  412. stopTestRun()
  413. return result