runner.py 16 KB

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