runner.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. """
  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. )
  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. )
  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, 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(
  144. len, (self.expectedFailures, self.unexpectedSuccesses, self.skipped)
  145. )
  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. )
  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(
  215. len, (self.expectedFailures, self.unexpectedSuccesses, self.skipped)
  216. )
  217. expectedFails, unexpectedSuccesses, skipped = results
  218. status = "succeeded" if self.wasSuccessful() else "failed"
  219. infos.append("status=%s" % status)
  220. # if only errors occur, tests are not counted properly
  221. # in unittest, so reconstruct their count here
  222. # (using general equation, although now errors would be enough)
  223. # alternative is to behave as failed file, i.e. do not
  224. # write test details and just write status=failed
  225. if not run:
  226. run = errored + failed + succeeded
  227. infos.append("total=%d" % (run))
  228. infos.append("failures=%d" % failed)
  229. infos.append("errors=%d" % errored)
  230. infos.append("successes=%d" % succeeded)
  231. infos.append("skipped=%d" % skipped)
  232. # TODO: document this: if not supported by view,
  233. # expected_failures should be counted as failures and vice versa
  234. # or both add to skipped as unclear?
  235. infos.append("expected_failures=%d" % expectedFails)
  236. infos.append("unexpected_successes=%d" % unexpectedSuccesses)
  237. # TODO: include each module just once? list good and bad modules?
  238. infos.append("tested_modules=%s" % ",".join(self._grass_modules))
  239. infos.append("supplementary_files=%s" % ",".join(self._supplementary_files))
  240. # module, modules?, c, c++?, python
  241. # TODO: include also type modules?
  242. # TODO: include also C++ code?
  243. # TODO: distinguish C and Python modules?
  244. infos.append("test_type=%s" % (self.test_type))
  245. self._stream.write("\n".join(infos))
  246. self._stream.write("\n")
  247. self._stream.flush()
  248. class MultiTestResult(TestResult):
  249. # descriptions and verbosity unused
  250. # included for compatibility with unittest's TestResult
  251. # where are also unused, so perhaps we can remove them
  252. # stream set to None and not included in interface, it would not make sense
  253. def __init__(self, results, forgiving=False, descriptions=None, verbosity=None):
  254. super(MultiTestResult, self).__init__(
  255. descriptions=descriptions, verbosity=verbosity, stream=None
  256. )
  257. self._results = results
  258. self._forgiving = forgiving
  259. def startTest(self, test):
  260. super(MultiTestResult, self).startTest(test)
  261. for result in self._results:
  262. try:
  263. result.startTest(test)
  264. except AttributeError:
  265. if self._forgiving:
  266. pass
  267. else:
  268. raise
  269. def stopTest(self, test):
  270. """Called when the given test has been run"""
  271. super(MultiTestResult, self).stopTest(test)
  272. for result in self._results:
  273. try:
  274. result.stopTest(test)
  275. except AttributeError:
  276. if self._forgiving:
  277. pass
  278. else:
  279. raise
  280. def addSuccess(self, test):
  281. super(MultiTestResult, self).addSuccess(test)
  282. for result in self._results:
  283. try:
  284. result.addSuccess(test)
  285. except AttributeError:
  286. if self._forgiving:
  287. pass
  288. else:
  289. raise
  290. def addError(self, test, err):
  291. super(MultiTestResult, self).addError(test, err)
  292. for result in self._results:
  293. try:
  294. result.addError(test, err)
  295. except AttributeError:
  296. if self._forgiving:
  297. pass
  298. else:
  299. raise
  300. def addFailure(self, test, err):
  301. super(MultiTestResult, self).addFailure(test, err)
  302. for result in self._results:
  303. try:
  304. result.addFailure(test, err)
  305. except AttributeError:
  306. if self._forgiving:
  307. pass
  308. else:
  309. raise
  310. def addSkip(self, test, reason):
  311. super(MultiTestResult, self).addSkip(test, reason)
  312. for result in self._results:
  313. try:
  314. result.addSkip(test, reason)
  315. except AttributeError:
  316. if self._forgiving:
  317. pass
  318. else:
  319. raise
  320. def addExpectedFailure(self, test, err):
  321. super(MultiTestResult, self).addExpectedFailure(test, err)
  322. for result in self._results:
  323. try:
  324. result.addExpectedFailure(test, err)
  325. except AttributeError:
  326. if self._forgiving:
  327. pass
  328. else:
  329. raise
  330. def addUnexpectedSuccess(self, test):
  331. super(MultiTestResult, self).addUnexpectedSuccess(test)
  332. for result in self._results:
  333. try:
  334. result.addUnexpectedSuccess(test)
  335. except AttributeError:
  336. if self._forgiving:
  337. pass
  338. else:
  339. raise
  340. def printErrors(self):
  341. "Called by TestRunner after test run"
  342. super(MultiTestResult, self).printErrors()
  343. for result in self._results:
  344. try:
  345. result.printErrors()
  346. except AttributeError:
  347. if self._forgiving:
  348. pass
  349. else:
  350. raise
  351. def startTestRun(self):
  352. """Called once before any tests are executed.
  353. See startTest for a method called before each test.
  354. """
  355. super(MultiTestResult, self).startTestRun()
  356. for result in self._results:
  357. try:
  358. result.startTestRun()
  359. except AttributeError:
  360. if self._forgiving:
  361. pass
  362. else:
  363. raise
  364. def stopTestRun(self):
  365. """Called once after all tests are executed.
  366. See stopTest for a method called after each test.
  367. """
  368. super(MultiTestResult, self).stopTestRun()
  369. for result in self._results:
  370. try:
  371. result.stopTestRun()
  372. except AttributeError:
  373. if self._forgiving:
  374. pass
  375. else:
  376. raise
  377. # TODO: better would be to pass start at the beginning
  378. # alternative is to leave counting time on class
  379. # TODO: document: we expect all grass classes to have setTimes
  380. # TODO: alternatively, be more forgiving for non-unittest methods
  381. def setTimes(self, start_time, end_time, time_taken):
  382. for result in self._results:
  383. try:
  384. result.setTimes(start_time, end_time, time_taken)
  385. except AttributeError:
  386. if self._forgiving:
  387. pass
  388. else:
  389. raise
  390. class GrassTestRunner(object):
  391. def __init__(
  392. self,
  393. stream=sys.stderr,
  394. descriptions=True,
  395. verbosity=1,
  396. failfast=False,
  397. buffer=False,
  398. result=None,
  399. ):
  400. self.stream = _WritelnDecorator(stream)
  401. self.descriptions = descriptions
  402. self.verbosity = verbosity
  403. self.failfast = failfast
  404. self.buffer = buffer
  405. self._result = result
  406. def run(self, test):
  407. "Run the given test case or test suite."
  408. result = self._result
  409. unittest.registerResult(result)
  410. result.failfast = self.failfast
  411. result.buffer = self.buffer
  412. startTime = time.time()
  413. startTestRun = getattr(result, "startTestRun", None)
  414. if startTestRun is not None:
  415. startTestRun()
  416. try:
  417. test(result)
  418. finally:
  419. stopTime = time.time()
  420. timeTaken = stopTime - startTime
  421. setTimes = getattr(result, "setTimes", None)
  422. if setTimes is not None:
  423. setTimes(startTime, stopTime, timeTaken)
  424. stopTestRun = getattr(result, "stopTestRun", None)
  425. if stopTestRun is not None:
  426. stopTestRun()
  427. return result