runner.py 16 KB

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