runner.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  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. def setTimes(self, start_time, end_time, time_taken):
  187. self.start_time = start_time
  188. self.end_time = end_time
  189. self.time_taken = time_taken
  190. def stopTest(self, test):
  191. super(KeyValueTestResult, self).stopTest(test)
  192. if hasattr(test, 'grass_modules'):
  193. self._grass_modules.extend(test.grass_modules)
  194. def stopTestRun(self):
  195. super(KeyValueTestResult, self).stopTestRun()
  196. infos = []
  197. run = self.testsRun
  198. # TODO: name should be included by test file caller
  199. # from inspect import getsourcefile
  200. # from os.path import abspath
  201. # abspath(getsourcefile(lambda _: None))
  202. # not writing name is a good option
  203. # infos.append("name=%s" % 'unknown')
  204. infos.append("time=%.3fs" % (self.time_taken))
  205. # 'date={rundate}\n'
  206. # 'date={runtime}\n'
  207. # 'date={start_datetime}\n'
  208. # 'date={end_datetime}\n'
  209. failed, errored = map(len, (self.failures, self.errors))
  210. succeeded = len(self.successes)
  211. results = map(len, (self.expectedFailures,
  212. self.unexpectedSuccesses,
  213. self.skipped))
  214. expectedFails, unexpectedSuccesses, skipped = results
  215. status = 'succeeded' if self.wasSuccessful() else 'failed'
  216. infos.append("status=%s" % status)
  217. infos.append("total=%d" % (run))
  218. infos.append("failures=%d" % failed)
  219. infos.append("errors=%d" % errored)
  220. infos.append("successes=%d" % succeeded)
  221. infos.append("skipped=%d" % skipped)
  222. # TODO: document this: if not supported by view,
  223. # expected_failures should be counted as failures and vice versa
  224. # or both add to skipped as unclear?
  225. infos.append("expected_failures=%d" % expectedFails)
  226. infos.append("unexpected_successes=%d" % unexpectedSuccesses)
  227. # TODO: include each module just once? list good and bad modules?
  228. infos.append("modules=%s" % ','.join(self._grass_modules))
  229. # module, modules?, c, c++?, python
  230. # TODO: include also type modules?
  231. # TODO: include also C++ code?
  232. # TODO: distinguish C and Python modules?
  233. infos.append("test_type=%s" % (self.test_type))
  234. self._stream.write('\n'.join(infos))
  235. self._stream.write('\n')
  236. self._stream.flush()
  237. class MultiTestResult(TestResult):
  238. # descriptions and verbosity unused
  239. # included for compatibility with unittest's TestResult
  240. # where are also unused, so perhaps we can remove them
  241. # stream set to None and not included in interface, it would not make sense
  242. def __init__(self, results, forgiving=False,
  243. descriptions=None, verbosity=None):
  244. super(MultiTestResult, self).__init__(
  245. descriptions=descriptions, verbosity=verbosity, stream=None)
  246. self._results = results
  247. self._forgiving = forgiving
  248. def startTest(self, test):
  249. super(MultiTestResult, self).startTest(test)
  250. for result in self._results:
  251. try:
  252. result.startTest(test)
  253. except AttributeError:
  254. if self._forgiving:
  255. pass
  256. else:
  257. raise
  258. def stopTest(self, test):
  259. """Called when the given test has been run"""
  260. super(MultiTestResult, self).stopTest(test)
  261. for result in self._results:
  262. try:
  263. result.stopTest(test)
  264. except AttributeError:
  265. if self._forgiving:
  266. pass
  267. else:
  268. raise
  269. def addSuccess(self, test):
  270. super(MultiTestResult, self).addSuccess(test)
  271. for result in self._results:
  272. try:
  273. result.addSuccess(test)
  274. except AttributeError:
  275. if self._forgiving:
  276. pass
  277. else:
  278. raise
  279. def addError(self, test, err):
  280. super(MultiTestResult, self).addError(test, err)
  281. for result in self._results:
  282. try:
  283. result.addError(test, err)
  284. except AttributeError:
  285. if self._forgiving:
  286. pass
  287. else:
  288. raise
  289. def addFailure(self, test, err):
  290. super(MultiTestResult, self).addFailure(test, err)
  291. for result in self._results:
  292. try:
  293. result.addFailure(test, err)
  294. except AttributeError:
  295. if self._forgiving:
  296. pass
  297. else:
  298. raise
  299. def addSkip(self, test, reason):
  300. super(MultiTestResult, self).addSkip(test, reason)
  301. for result in self._results:
  302. try:
  303. result.addSuccess(test, reason)
  304. except AttributeError:
  305. if self._forgiving:
  306. pass
  307. else:
  308. raise
  309. def addExpectedFailure(self, test, err):
  310. super(MultiTestResult, self).addExpectedFailure(test, err)
  311. for result in self._results:
  312. try:
  313. result.addExpectedFailure(test, err)
  314. except AttributeError:
  315. if self._forgiving:
  316. pass
  317. else:
  318. raise
  319. def addUnexpectedSuccess(self, test):
  320. super(MultiTestResult, self).addUnexpectedSuccess(test)
  321. for result in self._results:
  322. try:
  323. result.addUnexpectedSuccess(test)
  324. except AttributeError:
  325. if self._forgiving:
  326. pass
  327. else:
  328. raise
  329. def printErrors(self):
  330. "Called by TestRunner after test run"
  331. super(MultiTestResult, self).printErrors()
  332. for result in self._results:
  333. try:
  334. result.printErrors()
  335. except AttributeError:
  336. if self._forgiving:
  337. pass
  338. else:
  339. raise
  340. def startTestRun(self):
  341. """Called once before any tests are executed.
  342. See startTest for a method called before each test.
  343. """
  344. super(MultiTestResult, self).startTestRun()
  345. for result in self._results:
  346. try:
  347. result.startTestRun()
  348. except AttributeError:
  349. if self._forgiving:
  350. pass
  351. else:
  352. raise
  353. def stopTestRun(self):
  354. """Called once after all tests are executed.
  355. See stopTest for a method called after each test.
  356. """
  357. super(MultiTestResult, self).stopTestRun()
  358. for result in self._results:
  359. try:
  360. result.stopTestRun()
  361. except AttributeError:
  362. if self._forgiving:
  363. pass
  364. else:
  365. raise
  366. # TODO: better would be to pass start at the beginning
  367. # alternative is to leave counting time on class
  368. # TODO: document: we expect all grass classes to have setTimes
  369. # TODO: alternatively, be more forgiving for non-unittest methods
  370. def setTimes(self, start_time, end_time, time_taken):
  371. for result in self._results:
  372. try:
  373. result.setTimes(start_time, end_time, time_taken)
  374. except AttributeError:
  375. if self._forgiving:
  376. pass
  377. else:
  378. raise
  379. class GrassTestRunner(object):
  380. def __init__(self, stream=sys.stderr, descriptions=True, verbosity=1,
  381. failfast=False, buffer=False, result=None):
  382. self.stream = _WritelnDecorator(stream)
  383. self.descriptions = descriptions
  384. self.verbosity = verbosity
  385. self.failfast = failfast
  386. self.buffer = buffer
  387. self._result = result
  388. def run(self, test):
  389. "Run the given test case or test suite."
  390. result = self._result
  391. registerResult(result)
  392. result.failfast = self.failfast
  393. result.buffer = self.buffer
  394. startTime = time.time()
  395. startTestRun = getattr(result, 'startTestRun', None)
  396. if startTestRun is not None:
  397. startTestRun()
  398. try:
  399. test(result)
  400. finally:
  401. stopTime = time.time()
  402. timeTaken = stopTime - startTime
  403. setTimes = getattr(result, 'setTimes', None)
  404. if setTimes is not None:
  405. setTimes(startTime, stopTime, timeTaken)
  406. stopTestRun = getattr(result, 'stopTestRun', None)
  407. if stopTestRun is not None:
  408. stopTestRun()
  409. return result