reporter.py 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289
  1. # -*- test-case-name: twisted.trial.test.test_reporter -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. #
  5. # Maintainer: Jonathan Lange
  6. """
  7. Defines classes that handle the results of tests.
  8. """
  9. from __future__ import annotations
  10. import os
  11. import sys
  12. import time
  13. import unittest as pyunit
  14. import warnings
  15. from collections import OrderedDict
  16. from types import TracebackType
  17. from typing import TYPE_CHECKING, List, Optional, Tuple, Type, Union
  18. from zope.interface import implementer
  19. from typing_extensions import TypeAlias
  20. from twisted.python import log, reflect
  21. from twisted.python.components import proxyForInterface
  22. from twisted.python.failure import Failure
  23. from twisted.python.util import untilConcludes
  24. from twisted.trial import itrial, util
  25. if TYPE_CHECKING:
  26. from ._synctest import Todo
  27. try:
  28. from subunit import TestProtocolClient
  29. except ImportError:
  30. TestProtocolClient = None
  31. ExcInfo: TypeAlias = Tuple[Type[BaseException], BaseException, TracebackType]
  32. XUnitFailure = Union[ExcInfo, Tuple[None, None, None]]
  33. TrialFailure = Union[XUnitFailure, Failure]
  34. def _makeTodo(value: str) -> "Todo":
  35. """
  36. Return a L{Todo} object built from C{value}.
  37. This is a synonym for L{twisted.trial.unittest.makeTodo}, but imported
  38. locally to avoid circular imports.
  39. @param value: A string or a tuple of C{(errors, reason)}, where C{errors}
  40. is either a single exception class or an iterable of exception classes.
  41. @return: A L{Todo} object.
  42. """
  43. from twisted.trial.unittest import makeTodo
  44. return makeTodo(value)
  45. class BrokenTestCaseWarning(Warning):
  46. """
  47. Emitted as a warning when an exception occurs in one of setUp or tearDown.
  48. """
  49. class SafeStream:
  50. """
  51. Wraps a stream object so that all C{write} calls are wrapped in
  52. L{untilConcludes<twisted.python.util.untilConcludes>}.
  53. """
  54. def __init__(self, original):
  55. self.original = original
  56. def __getattr__(self, name):
  57. return getattr(self.original, name)
  58. def write(self, *a, **kw):
  59. return untilConcludes(self.original.write, *a, **kw)
  60. @implementer(itrial.IReporter)
  61. class TestResult(pyunit.TestResult):
  62. """
  63. Accumulates the results of several L{twisted.trial.unittest.TestCase}s.
  64. @ivar successes: count the number of successes achieved by the test run.
  65. @type successes: C{int}
  66. @ivar _startTime: The time when the current test was started. It defaults to
  67. L{None}, which means that the test was skipped.
  68. @ivar _lastTime: The duration of the current test run. It defaults to
  69. L{None}, which means that the test was skipped.
  70. """
  71. # Used when no todo provided to addExpectedFailure or addUnexpectedSuccess.
  72. _DEFAULT_TODO = "Test expected to fail"
  73. skips: List[Tuple[itrial.ITestCase, str]]
  74. expectedFailures: List[Tuple[itrial.ITestCase, str, "Todo"]] # type: ignore[assignment]
  75. unexpectedSuccesses: List[Tuple[itrial.ITestCase, str]] # type: ignore[assignment]
  76. successes: int
  77. _testStarted: Optional[int]
  78. # The duration of the test. It is None until the test completes.
  79. _lastTime: Optional[int]
  80. def __init__(self):
  81. super().__init__()
  82. self.skips = []
  83. self.expectedFailures = []
  84. self.unexpectedSuccesses = []
  85. self.successes = 0
  86. self._timings = []
  87. self._testStarted = None
  88. self._lastTime = None
  89. def __repr__(self) -> str:
  90. return "<%s run=%d errors=%d failures=%d todos=%d dones=%d skips=%d>" % (
  91. reflect.qual(self.__class__),
  92. self.testsRun,
  93. len(self.errors),
  94. len(self.failures),
  95. len(self.expectedFailures),
  96. len(self.skips),
  97. len(self.unexpectedSuccesses),
  98. )
  99. def _getTime(self):
  100. return time.time()
  101. def _getFailure(self, error):
  102. """
  103. Convert a C{sys.exc_info()}-style tuple to a L{Failure}, if necessary.
  104. """
  105. is_exc_info_tuple = isinstance(error, tuple) and len(error) == 3
  106. if is_exc_info_tuple:
  107. return Failure(error[1], error[0], error[2])
  108. elif isinstance(error, Failure):
  109. return error
  110. raise TypeError(f"Cannot convert {error} to a Failure")
  111. def startTest(self, test):
  112. """
  113. This must be called before the given test is commenced.
  114. @type test: L{pyunit.TestCase}
  115. """
  116. super().startTest(test)
  117. self._testStarted = self._getTime()
  118. def stopTest(self, test):
  119. """
  120. This must be called after the given test is completed.
  121. @type test: L{pyunit.TestCase}
  122. """
  123. super().stopTest(test)
  124. if self._testStarted is not None:
  125. self._lastTime = self._getTime() - self._testStarted
  126. def addFailure(self, test, fail):
  127. """
  128. Report a failed assertion for the given test.
  129. @type test: L{pyunit.TestCase}
  130. @type fail: L{Failure} or L{tuple}
  131. """
  132. self.failures.append((test, self._getFailure(fail)))
  133. def addError(self, test, error):
  134. """
  135. Report an error that occurred while running the given test.
  136. @type test: L{pyunit.TestCase}
  137. @type error: L{Failure} or L{tuple}
  138. """
  139. self.errors.append((test, self._getFailure(error)))
  140. def addSkip(self, test, reason):
  141. """
  142. Report that the given test was skipped.
  143. In Trial, tests can be 'skipped'. Tests are skipped mostly because
  144. there is some platform or configuration issue that prevents them from
  145. being run correctly.
  146. @type test: L{pyunit.TestCase}
  147. @type reason: L{str}
  148. """
  149. self.skips.append((test, reason))
  150. def addUnexpectedSuccess(self, test, todo=None):
  151. """
  152. Report that the given test succeeded against expectations.
  153. In Trial, tests can be marked 'todo'. That is, they are expected to
  154. fail. When a test that is expected to fail instead succeeds, it should
  155. call this method to report the unexpected success.
  156. @type test: L{pyunit.TestCase}
  157. @type todo: L{unittest.Todo}, or L{None}, in which case a default todo
  158. message is provided.
  159. """
  160. if todo is None:
  161. todo = _makeTodo(self._DEFAULT_TODO)
  162. self.unexpectedSuccesses.append((test, todo))
  163. def addExpectedFailure(self, test, error, todo=None):
  164. """
  165. Report that the given test failed, and was expected to do so.
  166. In Trial, tests can be marked 'todo'. That is, they are expected to
  167. fail.
  168. @type test: L{pyunit.TestCase}
  169. @type error: L{Failure}
  170. @type todo: L{unittest.Todo}, or L{None}, in which case a default todo
  171. message is provided.
  172. """
  173. if todo is None:
  174. todo = _makeTodo(self._DEFAULT_TODO)
  175. self.expectedFailures.append((test, error, todo))
  176. def addSuccess(self, test):
  177. """
  178. Report that the given test succeeded.
  179. @type test: L{pyunit.TestCase}
  180. """
  181. self.successes += 1
  182. def wasSuccessful(self):
  183. """
  184. Report whether or not this test suite was successful or not.
  185. The behaviour of this method changed in L{pyunit} in Python 3.4 to
  186. fail if there are any errors, failures, or unexpected successes.
  187. Previous to 3.4, it was only if there were errors or failures. This
  188. method implements the old behaviour for backwards compatibility reasons,
  189. checking just for errors and failures.
  190. @rtype: L{bool}
  191. """
  192. return len(self.failures) == len(self.errors) == 0
  193. def done(self):
  194. """
  195. The test suite has finished running.
  196. """
  197. @implementer(itrial.IReporter)
  198. class TestResultDecorator(
  199. proxyForInterface(itrial.IReporter, "_originalReporter") # type: ignore[misc]
  200. ):
  201. """
  202. Base class for TestResult decorators.
  203. @ivar _originalReporter: The wrapped instance of reporter.
  204. @type _originalReporter: A provider of L{itrial.IReporter}
  205. """
  206. @implementer(itrial.IReporter)
  207. class UncleanWarningsReporterWrapper(TestResultDecorator):
  208. """
  209. A wrapper for a reporter that converts L{util.DirtyReactorAggregateError}s
  210. to warnings.
  211. """
  212. def addError(self, test, error):
  213. """
  214. If the error is a L{util.DirtyReactorAggregateError}, instead of
  215. reporting it as a normal error, throw a warning.
  216. """
  217. if isinstance(error, Failure) and error.check(util.DirtyReactorAggregateError):
  218. warnings.warn(error.getErrorMessage())
  219. else:
  220. self._originalReporter.addError(test, error)
  221. @implementer(itrial.IReporter)
  222. class _ExitWrapper(TestResultDecorator):
  223. """
  224. A wrapper for a reporter that causes the reporter to stop after
  225. unsuccessful tests.
  226. """
  227. def addError(self, *args, **kwargs):
  228. self.shouldStop = True
  229. return self._originalReporter.addError(*args, **kwargs)
  230. def addFailure(self, *args, **kwargs):
  231. self.shouldStop = True
  232. return self._originalReporter.addFailure(*args, **kwargs)
  233. class _AdaptedReporter(TestResultDecorator):
  234. """
  235. TestResult decorator that makes sure that addError only gets tests that
  236. have been adapted with a particular test adapter.
  237. """
  238. def __init__(self, original, testAdapter):
  239. """
  240. Construct an L{_AdaptedReporter}.
  241. @param original: An {itrial.IReporter}.
  242. @param testAdapter: A callable that returns an L{itrial.ITestCase}.
  243. """
  244. TestResultDecorator.__init__(self, original)
  245. self.testAdapter = testAdapter
  246. def addError(self, test, error):
  247. """
  248. See L{itrial.IReporter}.
  249. """
  250. test = self.testAdapter(test)
  251. return self._originalReporter.addError(test, error)
  252. def addExpectedFailure(self, test, failure, todo=None):
  253. """
  254. See L{itrial.IReporter}.
  255. @type test: A L{pyunit.TestCase}.
  256. @type failure: A L{failure.Failure} or L{AssertionError}
  257. @type todo: A L{unittest.Todo} or None
  258. When C{todo} is L{None} a generic C{unittest.Todo} is built.
  259. L{pyunit.TestCase}'s C{run()} calls this with 3 positional arguments
  260. (without C{todo}).
  261. """
  262. return self._originalReporter.addExpectedFailure(
  263. self.testAdapter(test), failure, todo
  264. )
  265. def addFailure(self, test, failure):
  266. """
  267. See L{itrial.IReporter}.
  268. """
  269. test = self.testAdapter(test)
  270. return self._originalReporter.addFailure(test, failure)
  271. def addSkip(self, test, skip):
  272. """
  273. See L{itrial.IReporter}.
  274. """
  275. test = self.testAdapter(test)
  276. return self._originalReporter.addSkip(test, skip)
  277. def addUnexpectedSuccess(self, test, todo=None):
  278. """
  279. See L{itrial.IReporter}.
  280. @type test: A L{pyunit.TestCase}.
  281. @type todo: A L{unittest.Todo} or None
  282. When C{todo} is L{None} a generic C{unittest.Todo} is built.
  283. L{pyunit.TestCase}'s C{run()} calls this with 2 positional arguments
  284. (without C{todo}).
  285. """
  286. test = self.testAdapter(test)
  287. return self._originalReporter.addUnexpectedSuccess(test, todo)
  288. def startTest(self, test):
  289. """
  290. See L{itrial.IReporter}.
  291. """
  292. return self._originalReporter.startTest(self.testAdapter(test))
  293. def stopTest(self, test):
  294. """
  295. See L{itrial.IReporter}.
  296. """
  297. return self._originalReporter.stopTest(self.testAdapter(test))
  298. @implementer(itrial.IReporter)
  299. class Reporter(TestResult):
  300. """
  301. A basic L{TestResult} with support for writing to a stream.
  302. @ivar _startTime: The time when the first test was started. It defaults to
  303. L{None}, which means that no test was actually launched.
  304. @type _startTime: C{float} or L{None}
  305. @ivar _warningCache: A C{set} of tuples of warning message (file, line,
  306. text, category) which have already been written to the output stream
  307. during the currently executing test. This is used to avoid writing
  308. duplicates of the same warning to the output stream.
  309. @type _warningCache: C{set}
  310. @ivar _publisher: The log publisher which will be observed for warning
  311. events.
  312. @type _publisher: L{twisted.python.log.LogPublisher}
  313. """
  314. _separator = "-" * 79
  315. _doubleSeparator = "=" * 79
  316. def __init__(
  317. self, stream=sys.stdout, tbformat="default", realtime=False, publisher=None
  318. ):
  319. super().__init__()
  320. self._stream = SafeStream(stream)
  321. self.tbformat = tbformat
  322. self.realtime = realtime
  323. self._startTime = None
  324. self._warningCache = set()
  325. # Start observing log events so as to be able to report warnings.
  326. self._publisher = publisher
  327. if publisher is not None:
  328. publisher.addObserver(self._observeWarnings)
  329. def _observeWarnings(self, event):
  330. """
  331. Observe warning events and write them to C{self._stream}.
  332. This method is a log observer which will be registered with
  333. C{self._publisher.addObserver}.
  334. @param event: A C{dict} from the logging system. If it has a
  335. C{'warning'} key, a logged warning will be extracted from it and
  336. possibly written to C{self.stream}.
  337. """
  338. if "warning" in event:
  339. key = (
  340. event["filename"],
  341. event["lineno"],
  342. event["category"].split(".")[-1],
  343. str(event["warning"]),
  344. )
  345. if key not in self._warningCache:
  346. self._warningCache.add(key)
  347. self._stream.write("%s:%s: %s: %s\n" % key)
  348. def startTest(self, test):
  349. """
  350. Called when a test begins to run. Records the time when it was first
  351. called and resets the warning cache.
  352. @param test: L{ITestCase}
  353. """
  354. super().startTest(test)
  355. if self._startTime is None:
  356. self._startTime = self._getTime()
  357. self._warningCache = set()
  358. def addFailure(self, test, fail):
  359. """
  360. Called when a test fails. If C{realtime} is set, then it prints the
  361. error to the stream.
  362. @param test: L{ITestCase} that failed.
  363. @param fail: L{failure.Failure} containing the error.
  364. """
  365. super().addFailure(test, fail)
  366. if self.realtime:
  367. fail = self.failures[-1][1] # guarantee it's a Failure
  368. self._write(self._formatFailureTraceback(fail))
  369. def addError(self, test, error):
  370. """
  371. Called when a test raises an error. If C{realtime} is set, then it
  372. prints the error to the stream.
  373. @param test: L{ITestCase} that raised the error.
  374. @param error: L{failure.Failure} containing the error.
  375. """
  376. error = self._getFailure(error)
  377. super().addError(test, error)
  378. if self.realtime:
  379. error = self.errors[-1][1] # guarantee it's a Failure
  380. self._write(self._formatFailureTraceback(error))
  381. def _write(self, format, *args):
  382. """
  383. Safely write to the reporter's stream.
  384. @param format: A format string to write.
  385. @param args: The arguments for the format string.
  386. """
  387. s = str(format)
  388. assert isinstance(s, str)
  389. if args:
  390. self._stream.write(s % args)
  391. else:
  392. self._stream.write(s)
  393. untilConcludes(self._stream.flush)
  394. def _writeln(self, format, *args):
  395. """
  396. Safely write a line to the reporter's stream. Newline is appended to
  397. the format string.
  398. @param format: A format string to write.
  399. @param args: The arguments for the format string.
  400. """
  401. self._write(format, *args)
  402. self._write("\n")
  403. def upDownError(self, method, error, warn=True, printStatus=True):
  404. super().upDownError(method, error, warn, printStatus)
  405. if warn:
  406. tbStr = self._formatFailureTraceback(error)
  407. log.msg(tbStr)
  408. msg = "caught exception in {}, your TestCase is broken\n\n{}".format(
  409. method,
  410. tbStr,
  411. )
  412. warnings.warn(msg, BrokenTestCaseWarning, stacklevel=2)
  413. def cleanupErrors(self, errs):
  414. super().cleanupErrors(errs)
  415. warnings.warn(
  416. "%s\n%s"
  417. % (
  418. "REACTOR UNCLEAN! traceback(s) follow: ",
  419. self._formatFailureTraceback(errs),
  420. ),
  421. BrokenTestCaseWarning,
  422. )
  423. def _trimFrames(self, frames):
  424. """
  425. Trim frames to remove internal paths.
  426. When a C{SynchronousTestCase} method fails synchronously, the stack
  427. looks like this:
  428. - [0]: C{SynchronousTestCase._run}
  429. - [1]: C{util.runWithWarningsSuppressed}
  430. - [2:-2]: code in the test method which failed
  431. - [-1]: C{_synctest.fail}
  432. When a C{TestCase} method fails synchronously, the stack looks like
  433. this:
  434. - [0]: C{defer.maybeDeferred}
  435. - [1]: C{utils.runWithWarningsSuppressed}
  436. - [2]: C{utils.runWithWarningsSuppressed}
  437. - [3:-2]: code in the test method which failed
  438. - [-1]: C{_synctest.fail}
  439. When a method fails inside a C{Deferred} (i.e., when the test method
  440. returns a C{Deferred}, and that C{Deferred}'s errback fires), the stack
  441. captured inside the resulting C{Failure} looks like this:
  442. - [0]: C{defer.Deferred._runCallbacks}
  443. - [1:-2]: code in the testmethod which failed
  444. - [-1]: C{_synctest.fail}
  445. As a result, we want to trim either [maybeDeferred, runWWS, runWWS] or
  446. [Deferred._runCallbacks] or [SynchronousTestCase._run, runWWS] from the
  447. front, and trim the [unittest.fail] from the end.
  448. There is also another case, when the test method is badly defined and
  449. contains extra arguments.
  450. If it doesn't recognize one of these cases, it just returns the
  451. original frames.
  452. @param frames: The C{list} of frames from the test failure.
  453. @return: The C{list} of frames to display.
  454. """
  455. newFrames = list(frames)
  456. if len(frames) < 2:
  457. return newFrames
  458. firstMethod = newFrames[0][0]
  459. firstFile = os.path.splitext(os.path.basename(newFrames[0][1]))[0]
  460. secondMethod = newFrames[1][0]
  461. secondFile = os.path.splitext(os.path.basename(newFrames[1][1]))[0]
  462. syncCase = (("_run", "_synctest"), ("runWithWarningsSuppressed", "util"))
  463. asyncCase = (("maybeDeferred", "defer"), ("runWithWarningsSuppressed", "utils"))
  464. twoFrames = ((firstMethod, firstFile), (secondMethod, secondFile))
  465. # On PY3, we have an extra frame which is reraising the exception
  466. for frame in newFrames:
  467. frameFile = os.path.splitext(os.path.basename(frame[1]))[0]
  468. if frameFile == "compat" and frame[0] == "reraise":
  469. # If it's in the compat module and is reraise, BLAM IT
  470. newFrames.pop(newFrames.index(frame))
  471. if twoFrames == syncCase:
  472. newFrames = newFrames[2:]
  473. elif twoFrames == asyncCase:
  474. newFrames = newFrames[3:]
  475. elif (firstMethod, firstFile) == ("_runCallbacks", "defer"):
  476. newFrames = newFrames[1:]
  477. if not newFrames:
  478. # The method fails before getting called, probably an argument
  479. # problem
  480. return newFrames
  481. last = newFrames[-1]
  482. if (
  483. last[0].startswith("fail")
  484. and os.path.splitext(os.path.basename(last[1]))[0] == "_synctest"
  485. ):
  486. newFrames = newFrames[:-1]
  487. return newFrames
  488. def _formatFailureTraceback(self, fail):
  489. if isinstance(fail, str):
  490. return fail.rstrip() + "\n"
  491. fail.frames, frames = self._trimFrames(fail.frames), fail.frames
  492. result = fail.getTraceback(detail=self.tbformat, elideFrameworkCode=True)
  493. fail.frames = frames
  494. return result
  495. def _groupResults(self, results, formatter):
  496. """
  497. Group tests together based on their results.
  498. @param results: An iterable of tuples of two or more elements. The
  499. first element of each tuple is a test case. The remaining
  500. elements describe the outcome of that test case.
  501. @param formatter: A callable which turns a test case result into a
  502. string. The elements after the first of the tuples in
  503. C{results} will be passed as positional arguments to
  504. C{formatter}.
  505. @return: A C{list} of two-tuples. The first element of each tuple
  506. is a unique string describing one result from at least one of
  507. the test cases in C{results}. The second element is a list of
  508. the test cases which had that result.
  509. """
  510. groups = OrderedDict()
  511. for content in results:
  512. case = content[0]
  513. outcome = content[1:]
  514. key = formatter(*outcome)
  515. groups.setdefault(key, []).append(case)
  516. return list(groups.items())
  517. def _printResults(self, flavor, errors, formatter):
  518. """
  519. Print a group of errors to the stream.
  520. @param flavor: A string indicating the kind of error (e.g. 'TODO').
  521. @param errors: A list of errors, often L{failure.Failure}s, but
  522. sometimes 'todo' errors.
  523. @param formatter: A callable that knows how to format the errors.
  524. """
  525. for reason, cases in self._groupResults(errors, formatter):
  526. self._writeln(self._doubleSeparator)
  527. self._writeln(flavor)
  528. self._write(reason)
  529. self._writeln("")
  530. for case in cases:
  531. self._writeln(case.id())
  532. def _printExpectedFailure(self, error, todo):
  533. return "Reason: {!r}\n{}".format(
  534. todo.reason, self._formatFailureTraceback(error)
  535. )
  536. def _printUnexpectedSuccess(self, todo):
  537. ret = f"Reason: {todo.reason!r}\n"
  538. if todo.errors:
  539. ret += "Expected errors: {}\n".format(", ".join(todo.errors))
  540. return ret
  541. def _printErrors(self):
  542. """
  543. Print all of the non-success results to the stream in full.
  544. """
  545. self._write("\n")
  546. self._printResults("[SKIPPED]", self.skips, lambda x: "%s\n" % x)
  547. self._printResults("[TODO]", self.expectedFailures, self._printExpectedFailure)
  548. self._printResults("[FAIL]", self.failures, self._formatFailureTraceback)
  549. self._printResults("[ERROR]", self.errors, self._formatFailureTraceback)
  550. self._printResults(
  551. "[SUCCESS!?!]", self.unexpectedSuccesses, self._printUnexpectedSuccess
  552. )
  553. def _getSummary(self):
  554. """
  555. Return a formatted count of tests status results.
  556. """
  557. summaries = []
  558. for stat in (
  559. "skips",
  560. "expectedFailures",
  561. "failures",
  562. "errors",
  563. "unexpectedSuccesses",
  564. ):
  565. num = len(getattr(self, stat))
  566. if num:
  567. summaries.append("%s=%d" % (stat, num))
  568. if self.successes:
  569. summaries.append("successes=%d" % (self.successes,))
  570. summary = (summaries and " (" + ", ".join(summaries) + ")") or ""
  571. return summary
  572. def _printSummary(self):
  573. """
  574. Print a line summarising the test results to the stream.
  575. """
  576. summary = self._getSummary()
  577. if self.wasSuccessful():
  578. status = "PASSED"
  579. else:
  580. status = "FAILED"
  581. self._write("%s%s\n", status, summary)
  582. def done(self):
  583. """
  584. Summarize the result of the test run.
  585. The summary includes a report of all of the errors, todos, skips and
  586. so forth that occurred during the run. It also includes the number of
  587. tests that were run and how long it took to run them (not including
  588. load time).
  589. Expects that C{_printErrors}, C{_writeln}, C{_write}, C{_printSummary}
  590. and C{_separator} are all implemented.
  591. """
  592. if self._publisher is not None:
  593. self._publisher.removeObserver(self._observeWarnings)
  594. self._printErrors()
  595. self._writeln(self._separator)
  596. if self._startTime is not None:
  597. self._writeln(
  598. "Ran %d tests in %.3fs", self.testsRun, time.time() - self._startTime
  599. )
  600. self._write("\n")
  601. self._printSummary()
  602. class MinimalReporter(Reporter):
  603. """
  604. A minimalist reporter that prints only a summary of the test result, in
  605. the form of (timeTaken, #tests, #tests, #errors, #failures, #skips).
  606. """
  607. def _printErrors(self):
  608. """
  609. Don't print a detailed summary of errors. We only care about the
  610. counts.
  611. """
  612. def _printSummary(self):
  613. """
  614. Print out a one-line summary of the form:
  615. '%(runtime) %(number_of_tests) %(number_of_tests) %(num_errors)
  616. %(num_failures) %(num_skips)'
  617. """
  618. numTests = self.testsRun
  619. if self._startTime is not None:
  620. timing = self._getTime() - self._startTime
  621. else:
  622. timing = 0
  623. t = (
  624. timing,
  625. numTests,
  626. numTests,
  627. len(self.errors),
  628. len(self.failures),
  629. len(self.skips),
  630. )
  631. self._writeln(" ".join(map(str, t)))
  632. class TextReporter(Reporter):
  633. """
  634. Simple reporter that prints a single character for each test as it runs,
  635. along with the standard Trial summary text.
  636. """
  637. def addSuccess(self, test):
  638. super().addSuccess(test)
  639. self._write(".")
  640. def addError(self, *args):
  641. super().addError(*args)
  642. self._write("E")
  643. def addFailure(self, *args):
  644. super().addFailure(*args)
  645. self._write("F")
  646. def addSkip(self, *args):
  647. super().addSkip(*args)
  648. self._write("S")
  649. def addExpectedFailure(self, *args):
  650. super().addExpectedFailure(*args)
  651. self._write("T")
  652. def addUnexpectedSuccess(self, *args):
  653. super().addUnexpectedSuccess(*args)
  654. self._write("!")
  655. class VerboseTextReporter(Reporter):
  656. """
  657. A verbose reporter that prints the name of each test as it is running.
  658. Each line is printed with the name of the test, followed by the result of
  659. that test.
  660. """
  661. # This is actually the bwverbose option
  662. def startTest(self, tm):
  663. self._write("%s ... ", tm.id())
  664. super().startTest(tm)
  665. def addSuccess(self, test):
  666. super().addSuccess(test)
  667. self._write("[OK]")
  668. def addError(self, *args):
  669. super().addError(*args)
  670. self._write("[ERROR]")
  671. def addFailure(self, *args):
  672. super().addFailure(*args)
  673. self._write("[FAILURE]")
  674. def addSkip(self, *args):
  675. super().addSkip(*args)
  676. self._write("[SKIPPED]")
  677. def addExpectedFailure(self, *args):
  678. super().addExpectedFailure(*args)
  679. self._write("[TODO]")
  680. def addUnexpectedSuccess(self, *args):
  681. super().addUnexpectedSuccess(*args)
  682. self._write("[SUCCESS!?!]")
  683. def stopTest(self, test):
  684. super().stopTest(test)
  685. self._write("\n")
  686. class TimingTextReporter(VerboseTextReporter):
  687. """
  688. Prints out each test as it is running, followed by the time taken for each
  689. test to run.
  690. """
  691. def stopTest(self, method):
  692. """
  693. Mark the test as stopped, and write the time it took to run the test
  694. to the stream.
  695. """
  696. super().stopTest(method)
  697. self._write("(%.03f secs)\n" % self._lastTime)
  698. class _AnsiColorizer:
  699. """
  700. A colorizer is an object that loosely wraps around a stream, allowing
  701. callers to write text to the stream in a particular color.
  702. Colorizer classes must implement C{supported()} and C{write(text, color)}.
  703. """
  704. _colors = dict(
  705. black=30, red=31, green=32, yellow=33, blue=34, magenta=35, cyan=36, white=37
  706. )
  707. def __init__(self, stream):
  708. self.stream = stream
  709. @classmethod
  710. def supported(cls, stream=sys.stdout):
  711. """
  712. A class method that returns True if the current platform supports
  713. coloring terminal output using this method. Returns False otherwise.
  714. """
  715. if not stream.isatty():
  716. return False # auto color only on TTYs
  717. try:
  718. import curses
  719. except ImportError:
  720. return False
  721. else:
  722. try:
  723. try:
  724. return curses.tigetnum("colors") > 2
  725. except curses.error:
  726. curses.setupterm()
  727. return curses.tigetnum("colors") > 2
  728. except BaseException:
  729. # guess false in case of error
  730. return False
  731. def write(self, text, color):
  732. """
  733. Write the given text to the stream in the given color.
  734. @param text: Text to be written to the stream.
  735. @param color: A string label for a color. e.g. 'red', 'white'.
  736. """
  737. color = self._colors[color]
  738. self.stream.write(f"\x1b[{color};1m{text}\x1b[0m")
  739. class _Win32Colorizer:
  740. """
  741. See _AnsiColorizer docstring.
  742. """
  743. def __init__(self, stream):
  744. from win32console import (
  745. FOREGROUND_BLUE,
  746. FOREGROUND_GREEN,
  747. FOREGROUND_INTENSITY,
  748. FOREGROUND_RED,
  749. STD_OUTPUT_HANDLE,
  750. GetStdHandle,
  751. )
  752. red, green, blue, bold = (
  753. FOREGROUND_RED,
  754. FOREGROUND_GREEN,
  755. FOREGROUND_BLUE,
  756. FOREGROUND_INTENSITY,
  757. )
  758. self.stream = stream
  759. self.screenBuffer = GetStdHandle(STD_OUTPUT_HANDLE)
  760. self._colors = {
  761. "normal": red | green | blue,
  762. "red": red | bold,
  763. "green": green | bold,
  764. "blue": blue | bold,
  765. "yellow": red | green | bold,
  766. "magenta": red | blue | bold,
  767. "cyan": green | blue | bold,
  768. "white": red | green | blue | bold,
  769. }
  770. @classmethod
  771. def supported(cls, stream=sys.stdout):
  772. try:
  773. import win32console
  774. screenBuffer = win32console.GetStdHandle(win32console.STD_OUTPUT_HANDLE)
  775. except ImportError:
  776. return False
  777. import pywintypes
  778. try:
  779. screenBuffer.SetConsoleTextAttribute(
  780. win32console.FOREGROUND_RED
  781. | win32console.FOREGROUND_GREEN
  782. | win32console.FOREGROUND_BLUE
  783. )
  784. except pywintypes.error:
  785. return False
  786. else:
  787. return True
  788. def write(self, text, color):
  789. color = self._colors[color]
  790. self.screenBuffer.SetConsoleTextAttribute(color)
  791. self.stream.write(text)
  792. self.screenBuffer.SetConsoleTextAttribute(self._colors["normal"])
  793. class _NullColorizer:
  794. """
  795. See _AnsiColorizer docstring.
  796. """
  797. def __init__(self, stream):
  798. self.stream = stream
  799. @classmethod
  800. def supported(cls, stream=sys.stdout):
  801. return True
  802. def write(self, text, color):
  803. self.stream.write(text)
  804. @implementer(itrial.IReporter)
  805. class SubunitReporter:
  806. """
  807. Reports test output via Subunit.
  808. @ivar _subunit: The subunit protocol client that we are wrapping.
  809. @ivar _successful: An internal variable, used to track whether we have
  810. received only successful results.
  811. @since: 10.0
  812. """
  813. testsRun = None
  814. def __init__(
  815. self, stream=sys.stdout, tbformat="default", realtime=False, publisher=None
  816. ):
  817. """
  818. Construct a L{SubunitReporter}.
  819. @param stream: A file-like object representing the stream to print
  820. output to. Defaults to stdout.
  821. @param tbformat: The format for tracebacks. Ignored, since subunit
  822. always uses Python's standard format.
  823. @param realtime: Whether or not to print exceptions in the middle
  824. of the test results. Ignored, since subunit always does this.
  825. @param publisher: The log publisher which will be preserved for
  826. reporting events. Ignored, as it's not relevant to subunit.
  827. """
  828. if TestProtocolClient is None:
  829. raise Exception("Subunit not available")
  830. self._subunit = TestProtocolClient(stream)
  831. self._successful = True
  832. def done(self):
  833. """
  834. Record that the entire test suite run is finished.
  835. We do nothing, since a summary clause is irrelevant to the subunit
  836. protocol.
  837. """
  838. pass
  839. @property
  840. def shouldStop(self):
  841. """
  842. Whether or not the test runner should stop running tests.
  843. """
  844. return self._subunit.shouldStop
  845. def stop(self):
  846. """
  847. Signal that the test runner should stop running tests.
  848. """
  849. return self._subunit.stop()
  850. def wasSuccessful(self):
  851. """
  852. Has the test run been successful so far?
  853. @return: C{True} if we have received no reports of errors or failures,
  854. C{False} otherwise.
  855. """
  856. # Subunit has a bug in its implementation of wasSuccessful, see
  857. # https://bugs.edge.launchpad.net/subunit/+bug/491090, so we can't
  858. # simply forward it on.
  859. return self._successful
  860. def startTest(self, test):
  861. """
  862. Record that C{test} has started.
  863. """
  864. return self._subunit.startTest(test)
  865. def stopTest(self, test):
  866. """
  867. Record that C{test} has completed.
  868. """
  869. return self._subunit.stopTest(test)
  870. def addSuccess(self, test):
  871. """
  872. Record that C{test} was successful.
  873. """
  874. return self._subunit.addSuccess(test)
  875. def addSkip(self, test, reason):
  876. """
  877. Record that C{test} was skipped for C{reason}.
  878. Some versions of subunit don't have support for addSkip. In those
  879. cases, the skip is reported as a success.
  880. @param test: A unittest-compatible C{TestCase}.
  881. @param reason: The reason for it being skipped. The C{str()} of this
  882. object will be included in the subunit output stream.
  883. """
  884. addSkip = getattr(self._subunit, "addSkip", None)
  885. if addSkip is None:
  886. self.addSuccess(test)
  887. else:
  888. self._subunit.addSkip(test, reason)
  889. def addError(self, test, err):
  890. """
  891. Record that C{test} failed with an unexpected error C{err}.
  892. Also marks the run as being unsuccessful, causing
  893. L{SubunitReporter.wasSuccessful} to return C{False}.
  894. """
  895. self._successful = False
  896. return self._subunit.addError(test, util.excInfoOrFailureToExcInfo(err))
  897. def addFailure(self, test, err):
  898. """
  899. Record that C{test} failed an assertion with the error C{err}.
  900. Also marks the run as being unsuccessful, causing
  901. L{SubunitReporter.wasSuccessful} to return C{False}.
  902. """
  903. self._successful = False
  904. return self._subunit.addFailure(test, util.excInfoOrFailureToExcInfo(err))
  905. def addExpectedFailure(self, test, failure, todo=None):
  906. """
  907. Record an expected failure from a test.
  908. Some versions of subunit do not implement this. For those versions, we
  909. record a success.
  910. """
  911. failure = util.excInfoOrFailureToExcInfo(failure)
  912. addExpectedFailure = getattr(self._subunit, "addExpectedFailure", None)
  913. if addExpectedFailure is None:
  914. self.addSuccess(test)
  915. else:
  916. addExpectedFailure(test, failure)
  917. def addUnexpectedSuccess(self, test, todo=None):
  918. """
  919. Record an unexpected success.
  920. Since subunit has no way of expressing this concept, we record a
  921. success on the subunit stream.
  922. """
  923. # Not represented in pyunit/subunit.
  924. self.addSuccess(test)
  925. class TreeReporter(Reporter):
  926. """
  927. Print out the tests in the form a tree.
  928. Tests are indented according to which class and module they belong.
  929. Results are printed in ANSI color.
  930. """
  931. currentLine = ""
  932. indent = " "
  933. columns = 79
  934. FAILURE = "red"
  935. ERROR = "red"
  936. TODO = "blue"
  937. SKIP = "blue"
  938. TODONE = "red"
  939. SUCCESS = "green"
  940. def __init__(self, stream=sys.stdout, *args, **kwargs):
  941. super().__init__(stream, *args, **kwargs)
  942. self._lastTest = []
  943. for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]:
  944. if colorizer.supported(stream):
  945. self._colorizer = colorizer(stream)
  946. break
  947. def getDescription(self, test):
  948. """
  949. Return the name of the method which 'test' represents. This is
  950. what gets displayed in the leaves of the tree.
  951. e.g. getDescription(TestCase('test_foo')) ==> test_foo
  952. """
  953. return test.id().split(".")[-1]
  954. def addSuccess(self, test):
  955. super().addSuccess(test)
  956. self.endLine("[OK]", self.SUCCESS)
  957. def addError(self, *args):
  958. super().addError(*args)
  959. self.endLine("[ERROR]", self.ERROR)
  960. def addFailure(self, *args):
  961. super().addFailure(*args)
  962. self.endLine("[FAIL]", self.FAILURE)
  963. def addSkip(self, *args):
  964. super().addSkip(*args)
  965. self.endLine("[SKIPPED]", self.SKIP)
  966. def addExpectedFailure(self, *args):
  967. super().addExpectedFailure(*args)
  968. self.endLine("[TODO]", self.TODO)
  969. def addUnexpectedSuccess(self, *args):
  970. super().addUnexpectedSuccess(*args)
  971. self.endLine("[SUCCESS!?!]", self.TODONE)
  972. def _write(self, format, *args):
  973. if args:
  974. format = format % args
  975. self.currentLine = format
  976. super()._write(self.currentLine)
  977. def _getPreludeSegments(self, testID):
  978. """
  979. Return a list of all non-leaf segments to display in the tree.
  980. Normally this is the module and class name.
  981. """
  982. segments = testID.split(".")[:-1]
  983. if len(segments) == 0:
  984. return segments
  985. segments = [
  986. seg for seg in (".".join(segments[:-1]), segments[-1]) if len(seg) > 0
  987. ]
  988. return segments
  989. def _testPrelude(self, testID):
  990. """
  991. Write the name of the test to the stream, indenting it appropriately.
  992. If the test is the first test in a new 'branch' of the tree, also
  993. write all of the parents in that branch.
  994. """
  995. segments = self._getPreludeSegments(testID)
  996. indentLevel = 0
  997. for seg in segments:
  998. if indentLevel < len(self._lastTest):
  999. if seg != self._lastTest[indentLevel]:
  1000. self._write(f"{self.indent * indentLevel}{seg}\n")
  1001. else:
  1002. self._write(f"{self.indent * indentLevel}{seg}\n")
  1003. indentLevel += 1
  1004. self._lastTest = segments
  1005. def cleanupErrors(self, errs):
  1006. self._colorizer.write(" cleanup errors", self.ERROR)
  1007. self.endLine("[ERROR]", self.ERROR)
  1008. super().cleanupErrors(errs)
  1009. def upDownError(self, method, error, warn, printStatus):
  1010. self._colorizer.write(" %s" % method, self.ERROR)
  1011. if printStatus:
  1012. self.endLine("[ERROR]", self.ERROR)
  1013. super().upDownError(method, error, warn, printStatus)
  1014. def startTest(self, test):
  1015. """
  1016. Called when C{test} starts. Writes the tests name to the stream using
  1017. a tree format.
  1018. """
  1019. self._testPrelude(test.id())
  1020. self._write(
  1021. "%s%s ... "
  1022. % (self.indent * (len(self._lastTest)), self.getDescription(test))
  1023. )
  1024. super().startTest(test)
  1025. def endLine(self, message, color):
  1026. """
  1027. Print 'message' in the given color.
  1028. @param message: A string message, usually '[OK]' or something similar.
  1029. @param color: A string color, 'red', 'green' and so forth.
  1030. """
  1031. spaces = " " * (self.columns - len(self.currentLine) - len(message))
  1032. super()._write(spaces)
  1033. self._colorizer.write(message, color)
  1034. super()._write("\n")
  1035. def _printSummary(self):
  1036. """
  1037. Print a line summarising the test results to the stream, and color the
  1038. status result.
  1039. """
  1040. summary = self._getSummary()
  1041. if self.wasSuccessful():
  1042. status = "PASSED"
  1043. color = self.SUCCESS
  1044. else:
  1045. status = "FAILED"
  1046. color = self.FAILURE
  1047. self._colorizer.write(status, color)
  1048. self._write("%s\n", summary)