python_api.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  1. import math
  2. import pprint
  3. import sys
  4. from numbers import Number
  5. from decimal import Decimal
  6. import six
  7. from six.moves import zip, filterfalse
  8. from more_itertools.more import always_iterable
  9. from _pytest.compat import isclass
  10. from _pytest.compat import Mapping, Sequence
  11. from _pytest.compat import STRING_TYPES
  12. from _pytest.outcomes import fail
  13. import _pytest._code
  14. BASE_TYPE = (type, STRING_TYPES)
  15. def _cmp_raises_type_error(self, other):
  16. """__cmp__ implementation which raises TypeError. Used
  17. by Approx base classes to implement only == and != and raise a
  18. TypeError for other comparisons.
  19. Needed in Python 2 only, Python 3 all it takes is not implementing the
  20. other operators at all.
  21. """
  22. __tracebackhide__ = True
  23. raise TypeError(
  24. "Comparison operators other than == and != not supported by approx objects"
  25. )
  26. def _non_numeric_type_error(value, at):
  27. at_str = " at {}".format(at) if at else ""
  28. return TypeError(
  29. "cannot make approximate comparisons to non-numeric values: {!r} {}".format(
  30. value, at_str
  31. )
  32. )
  33. # builtin pytest.approx helper
  34. class ApproxBase(object):
  35. """
  36. Provide shared utilities for making approximate comparisons between numbers
  37. or sequences of numbers.
  38. """
  39. # Tell numpy to use our `__eq__` operator instead of its.
  40. __array_ufunc__ = None
  41. __array_priority__ = 100
  42. def __init__(self, expected, rel=None, abs=None, nan_ok=False):
  43. __tracebackhide__ = True
  44. self.expected = expected
  45. self.abs = abs
  46. self.rel = rel
  47. self.nan_ok = nan_ok
  48. self._check_type()
  49. def __repr__(self):
  50. raise NotImplementedError
  51. def __eq__(self, actual):
  52. return all(
  53. a == self._approx_scalar(x) for a, x in self._yield_comparisons(actual)
  54. )
  55. __hash__ = None
  56. def __ne__(self, actual):
  57. return not (actual == self)
  58. if sys.version_info[0] == 2:
  59. __cmp__ = _cmp_raises_type_error
  60. def _approx_scalar(self, x):
  61. return ApproxScalar(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok)
  62. def _yield_comparisons(self, actual):
  63. """
  64. Yield all the pairs of numbers to be compared. This is used to
  65. implement the `__eq__` method.
  66. """
  67. raise NotImplementedError
  68. def _check_type(self):
  69. """
  70. Raise a TypeError if the expected value is not a valid type.
  71. """
  72. # This is only a concern if the expected value is a sequence. In every
  73. # other case, the approx() function ensures that the expected value has
  74. # a numeric type. For this reason, the default is to do nothing. The
  75. # classes that deal with sequences should reimplement this method to
  76. # raise if there are any non-numeric elements in the sequence.
  77. pass
  78. def _recursive_list_map(f, x):
  79. if isinstance(x, list):
  80. return list(_recursive_list_map(f, xi) for xi in x)
  81. else:
  82. return f(x)
  83. class ApproxNumpy(ApproxBase):
  84. """
  85. Perform approximate comparisons where the expected value is numpy array.
  86. """
  87. def __repr__(self):
  88. list_scalars = _recursive_list_map(self._approx_scalar, self.expected.tolist())
  89. return "approx({!r})".format(list_scalars)
  90. if sys.version_info[0] == 2:
  91. __cmp__ = _cmp_raises_type_error
  92. def __eq__(self, actual):
  93. import numpy as np
  94. # self.expected is supposed to always be an array here
  95. if not np.isscalar(actual):
  96. try:
  97. actual = np.asarray(actual)
  98. except: # noqa
  99. raise TypeError("cannot compare '{}' to numpy.ndarray".format(actual))
  100. if not np.isscalar(actual) and actual.shape != self.expected.shape:
  101. return False
  102. return ApproxBase.__eq__(self, actual)
  103. def _yield_comparisons(self, actual):
  104. import numpy as np
  105. # `actual` can either be a numpy array or a scalar, it is treated in
  106. # `__eq__` before being passed to `ApproxBase.__eq__`, which is the
  107. # only method that calls this one.
  108. if np.isscalar(actual):
  109. for i in np.ndindex(self.expected.shape):
  110. yield actual, np.asscalar(self.expected[i])
  111. else:
  112. for i in np.ndindex(self.expected.shape):
  113. yield np.asscalar(actual[i]), np.asscalar(self.expected[i])
  114. class ApproxMapping(ApproxBase):
  115. """
  116. Perform approximate comparisons where the expected value is a mapping with
  117. numeric values (the keys can be anything).
  118. """
  119. def __repr__(self):
  120. return "approx({!r})".format(
  121. {k: self._approx_scalar(v) for k, v in self.expected.items()}
  122. )
  123. def __eq__(self, actual):
  124. if set(actual.keys()) != set(self.expected.keys()):
  125. return False
  126. return ApproxBase.__eq__(self, actual)
  127. def _yield_comparisons(self, actual):
  128. for k in self.expected.keys():
  129. yield actual[k], self.expected[k]
  130. def _check_type(self):
  131. __tracebackhide__ = True
  132. for key, value in self.expected.items():
  133. if isinstance(value, type(self.expected)):
  134. msg = "pytest.approx() does not support nested dictionaries: key={!r} value={!r}\n full mapping={}"
  135. raise TypeError(msg.format(key, value, pprint.pformat(self.expected)))
  136. elif not isinstance(value, Number):
  137. raise _non_numeric_type_error(self.expected, at="key={!r}".format(key))
  138. class ApproxSequence(ApproxBase):
  139. """
  140. Perform approximate comparisons where the expected value is a sequence of
  141. numbers.
  142. """
  143. def __repr__(self):
  144. seq_type = type(self.expected)
  145. if seq_type not in (tuple, list, set):
  146. seq_type = list
  147. return "approx({!r})".format(
  148. seq_type(self._approx_scalar(x) for x in self.expected)
  149. )
  150. def __eq__(self, actual):
  151. if len(actual) != len(self.expected):
  152. return False
  153. return ApproxBase.__eq__(self, actual)
  154. def _yield_comparisons(self, actual):
  155. return zip(actual, self.expected)
  156. def _check_type(self):
  157. __tracebackhide__ = True
  158. for index, x in enumerate(self.expected):
  159. if isinstance(x, type(self.expected)):
  160. msg = "pytest.approx() does not support nested data structures: {!r} at index {}\n full sequence: {}"
  161. raise TypeError(msg.format(x, index, pprint.pformat(self.expected)))
  162. elif not isinstance(x, Number):
  163. raise _non_numeric_type_error(
  164. self.expected, at="index {}".format(index)
  165. )
  166. class ApproxScalar(ApproxBase):
  167. """
  168. Perform approximate comparisons where the expected value is a single number.
  169. """
  170. DEFAULT_ABSOLUTE_TOLERANCE = 1e-12
  171. DEFAULT_RELATIVE_TOLERANCE = 1e-6
  172. def __repr__(self):
  173. """
  174. Return a string communicating both the expected value and the tolerance
  175. for the comparison being made, e.g. '1.0 +- 1e-6'. Use the unicode
  176. plus/minus symbol if this is python3 (it's too hard to get right for
  177. python2).
  178. """
  179. if isinstance(self.expected, complex):
  180. return str(self.expected)
  181. # Infinities aren't compared using tolerances, so don't show a
  182. # tolerance.
  183. if math.isinf(self.expected):
  184. return str(self.expected)
  185. # If a sensible tolerance can't be calculated, self.tolerance will
  186. # raise a ValueError. In this case, display '???'.
  187. try:
  188. vetted_tolerance = "{:.1e}".format(self.tolerance)
  189. except ValueError:
  190. vetted_tolerance = "???"
  191. if sys.version_info[0] == 2:
  192. return "{} +- {}".format(self.expected, vetted_tolerance)
  193. else:
  194. return u"{} \u00b1 {}".format(self.expected, vetted_tolerance)
  195. def __eq__(self, actual):
  196. """
  197. Return true if the given value is equal to the expected value within
  198. the pre-specified tolerance.
  199. """
  200. if _is_numpy_array(actual):
  201. # Call ``__eq__()`` manually to prevent infinite-recursion with
  202. # numpy<1.13. See #3748.
  203. return all(self.__eq__(a) for a in actual.flat)
  204. # Short-circuit exact equality.
  205. if actual == self.expected:
  206. return True
  207. # Allow the user to control whether NaNs are considered equal to each
  208. # other or not. The abs() calls are for compatibility with complex
  209. # numbers.
  210. if math.isnan(abs(self.expected)):
  211. return self.nan_ok and math.isnan(abs(actual))
  212. # Infinity shouldn't be approximately equal to anything but itself, but
  213. # if there's a relative tolerance, it will be infinite and infinity
  214. # will seem approximately equal to everything. The equal-to-itself
  215. # case would have been short circuited above, so here we can just
  216. # return false if the expected value is infinite. The abs() call is
  217. # for compatibility with complex numbers.
  218. if math.isinf(abs(self.expected)):
  219. return False
  220. # Return true if the two numbers are within the tolerance.
  221. return abs(self.expected - actual) <= self.tolerance
  222. __hash__ = None
  223. @property
  224. def tolerance(self):
  225. """
  226. Return the tolerance for the comparison. This could be either an
  227. absolute tolerance or a relative tolerance, depending on what the user
  228. specified or which would be larger.
  229. """
  230. def set_default(x, default):
  231. return x if x is not None else default
  232. # Figure out what the absolute tolerance should be. ``self.abs`` is
  233. # either None or a value specified by the user.
  234. absolute_tolerance = set_default(self.abs, self.DEFAULT_ABSOLUTE_TOLERANCE)
  235. if absolute_tolerance < 0:
  236. raise ValueError(
  237. "absolute tolerance can't be negative: {}".format(absolute_tolerance)
  238. )
  239. if math.isnan(absolute_tolerance):
  240. raise ValueError("absolute tolerance can't be NaN.")
  241. # If the user specified an absolute tolerance but not a relative one,
  242. # just return the absolute tolerance.
  243. if self.rel is None:
  244. if self.abs is not None:
  245. return absolute_tolerance
  246. # Figure out what the relative tolerance should be. ``self.rel`` is
  247. # either None or a value specified by the user. This is done after
  248. # we've made sure the user didn't ask for an absolute tolerance only,
  249. # because we don't want to raise errors about the relative tolerance if
  250. # we aren't even going to use it.
  251. relative_tolerance = set_default(
  252. self.rel, self.DEFAULT_RELATIVE_TOLERANCE
  253. ) * abs(self.expected)
  254. if relative_tolerance < 0:
  255. raise ValueError(
  256. "relative tolerance can't be negative: {}".format(absolute_tolerance)
  257. )
  258. if math.isnan(relative_tolerance):
  259. raise ValueError("relative tolerance can't be NaN.")
  260. # Return the larger of the relative and absolute tolerances.
  261. return max(relative_tolerance, absolute_tolerance)
  262. class ApproxDecimal(ApproxScalar):
  263. """
  264. Perform approximate comparisons where the expected value is a decimal.
  265. """
  266. DEFAULT_ABSOLUTE_TOLERANCE = Decimal("1e-12")
  267. DEFAULT_RELATIVE_TOLERANCE = Decimal("1e-6")
  268. def approx(expected, rel=None, abs=None, nan_ok=False):
  269. """
  270. Assert that two numbers (or two sets of numbers) are equal to each other
  271. within some tolerance.
  272. Due to the `intricacies of floating-point arithmetic`__, numbers that we
  273. would intuitively expect to be equal are not always so::
  274. >>> 0.1 + 0.2 == 0.3
  275. False
  276. __ https://docs.python.org/3/tutorial/floatingpoint.html
  277. This problem is commonly encountered when writing tests, e.g. when making
  278. sure that floating-point values are what you expect them to be. One way to
  279. deal with this problem is to assert that two floating-point numbers are
  280. equal to within some appropriate tolerance::
  281. >>> abs((0.1 + 0.2) - 0.3) < 1e-6
  282. True
  283. However, comparisons like this are tedious to write and difficult to
  284. understand. Furthermore, absolute comparisons like the one above are
  285. usually discouraged because there's no tolerance that works well for all
  286. situations. ``1e-6`` is good for numbers around ``1``, but too small for
  287. very big numbers and too big for very small ones. It's better to express
  288. the tolerance as a fraction of the expected value, but relative comparisons
  289. like that are even more difficult to write correctly and concisely.
  290. The ``approx`` class performs floating-point comparisons using a syntax
  291. that's as intuitive as possible::
  292. >>> from pytest import approx
  293. >>> 0.1 + 0.2 == approx(0.3)
  294. True
  295. The same syntax also works for sequences of numbers::
  296. >>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6))
  297. True
  298. Dictionary *values*::
  299. >>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
  300. True
  301. ``numpy`` arrays::
  302. >>> import numpy as np # doctest: +SKIP
  303. >>> np.array([0.1, 0.2]) + np.array([0.2, 0.4]) == approx(np.array([0.3, 0.6])) # doctest: +SKIP
  304. True
  305. And for a ``numpy`` array against a scalar::
  306. >>> import numpy as np # doctest: +SKIP
  307. >>> np.array([0.1, 0.2]) + np.array([0.2, 0.1]) == approx(0.3) # doctest: +SKIP
  308. True
  309. By default, ``approx`` considers numbers within a relative tolerance of
  310. ``1e-6`` (i.e. one part in a million) of its expected value to be equal.
  311. This treatment would lead to surprising results if the expected value was
  312. ``0.0``, because nothing but ``0.0`` itself is relatively close to ``0.0``.
  313. To handle this case less surprisingly, ``approx`` also considers numbers
  314. within an absolute tolerance of ``1e-12`` of its expected value to be
  315. equal. Infinity and NaN are special cases. Infinity is only considered
  316. equal to itself, regardless of the relative tolerance. NaN is not
  317. considered equal to anything by default, but you can make it be equal to
  318. itself by setting the ``nan_ok`` argument to True. (This is meant to
  319. facilitate comparing arrays that use NaN to mean "no data".)
  320. Both the relative and absolute tolerances can be changed by passing
  321. arguments to the ``approx`` constructor::
  322. >>> 1.0001 == approx(1)
  323. False
  324. >>> 1.0001 == approx(1, rel=1e-3)
  325. True
  326. >>> 1.0001 == approx(1, abs=1e-3)
  327. True
  328. If you specify ``abs`` but not ``rel``, the comparison will not consider
  329. the relative tolerance at all. In other words, two numbers that are within
  330. the default relative tolerance of ``1e-6`` will still be considered unequal
  331. if they exceed the specified absolute tolerance. If you specify both
  332. ``abs`` and ``rel``, the numbers will be considered equal if either
  333. tolerance is met::
  334. >>> 1 + 1e-8 == approx(1)
  335. True
  336. >>> 1 + 1e-8 == approx(1, abs=1e-12)
  337. False
  338. >>> 1 + 1e-8 == approx(1, rel=1e-6, abs=1e-12)
  339. True
  340. If you're thinking about using ``approx``, then you might want to know how
  341. it compares to other good ways of comparing floating-point numbers. All of
  342. these algorithms are based on relative and absolute tolerances and should
  343. agree for the most part, but they do have meaningful differences:
  344. - ``math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)``: True if the relative
  345. tolerance is met w.r.t. either ``a`` or ``b`` or if the absolute
  346. tolerance is met. Because the relative tolerance is calculated w.r.t.
  347. both ``a`` and ``b``, this test is symmetric (i.e. neither ``a`` nor
  348. ``b`` is a "reference value"). You have to specify an absolute tolerance
  349. if you want to compare to ``0.0`` because there is no tolerance by
  350. default. Only available in python>=3.5. `More information...`__
  351. __ https://docs.python.org/3/library/math.html#math.isclose
  352. - ``numpy.isclose(a, b, rtol=1e-5, atol=1e-8)``: True if the difference
  353. between ``a`` and ``b`` is less that the sum of the relative tolerance
  354. w.r.t. ``b`` and the absolute tolerance. Because the relative tolerance
  355. is only calculated w.r.t. ``b``, this test is asymmetric and you can
  356. think of ``b`` as the reference value. Support for comparing sequences
  357. is provided by ``numpy.allclose``. `More information...`__
  358. __ http://docs.scipy.org/doc/numpy-1.10.0/reference/generated/numpy.isclose.html
  359. - ``unittest.TestCase.assertAlmostEqual(a, b)``: True if ``a`` and ``b``
  360. are within an absolute tolerance of ``1e-7``. No relative tolerance is
  361. considered and the absolute tolerance cannot be changed, so this function
  362. is not appropriate for very large or very small numbers. Also, it's only
  363. available in subclasses of ``unittest.TestCase`` and it's ugly because it
  364. doesn't follow PEP8. `More information...`__
  365. __ https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertAlmostEqual
  366. - ``a == pytest.approx(b, rel=1e-6, abs=1e-12)``: True if the relative
  367. tolerance is met w.r.t. ``b`` or if the absolute tolerance is met.
  368. Because the relative tolerance is only calculated w.r.t. ``b``, this test
  369. is asymmetric and you can think of ``b`` as the reference value. In the
  370. special case that you explicitly specify an absolute tolerance but not a
  371. relative tolerance, only the absolute tolerance is considered.
  372. .. warning::
  373. .. versionchanged:: 3.2
  374. In order to avoid inconsistent behavior, ``TypeError`` is
  375. raised for ``>``, ``>=``, ``<`` and ``<=`` comparisons.
  376. The example below illustrates the problem::
  377. assert approx(0.1) > 0.1 + 1e-10 # calls approx(0.1).__gt__(0.1 + 1e-10)
  378. assert 0.1 + 1e-10 > approx(0.1) # calls approx(0.1).__lt__(0.1 + 1e-10)
  379. In the second example one expects ``approx(0.1).__le__(0.1 + 1e-10)``
  380. to be called. But instead, ``approx(0.1).__lt__(0.1 + 1e-10)`` is used to
  381. comparison. This is because the call hierarchy of rich comparisons
  382. follows a fixed behavior. `More information...`__
  383. __ https://docs.python.org/3/reference/datamodel.html#object.__ge__
  384. """
  385. # Delegate the comparison to a class that knows how to deal with the type
  386. # of the expected value (e.g. int, float, list, dict, numpy.array, etc).
  387. #
  388. # The primary responsibility of these classes is to implement ``__eq__()``
  389. # and ``__repr__()``. The former is used to actually check if some
  390. # "actual" value is equivalent to the given expected value within the
  391. # allowed tolerance. The latter is used to show the user the expected
  392. # value and tolerance, in the case that a test failed.
  393. #
  394. # The actual logic for making approximate comparisons can be found in
  395. # ApproxScalar, which is used to compare individual numbers. All of the
  396. # other Approx classes eventually delegate to this class. The ApproxBase
  397. # class provides some convenient methods and overloads, but isn't really
  398. # essential.
  399. __tracebackhide__ = True
  400. if isinstance(expected, Decimal):
  401. cls = ApproxDecimal
  402. elif isinstance(expected, Number):
  403. cls = ApproxScalar
  404. elif isinstance(expected, Mapping):
  405. cls = ApproxMapping
  406. elif isinstance(expected, Sequence) and not isinstance(expected, STRING_TYPES):
  407. cls = ApproxSequence
  408. elif _is_numpy_array(expected):
  409. cls = ApproxNumpy
  410. else:
  411. raise _non_numeric_type_error(expected, at=None)
  412. return cls(expected, rel, abs, nan_ok)
  413. def _is_numpy_array(obj):
  414. """
  415. Return true if the given object is a numpy array. Make a special effort to
  416. avoid importing numpy unless it's really necessary.
  417. """
  418. import sys
  419. np = sys.modules.get("numpy")
  420. if np is not None:
  421. return isinstance(obj, np.ndarray)
  422. return False
  423. # builtin pytest.raises helper
  424. def raises(expected_exception, *args, **kwargs):
  425. r"""
  426. Assert that a code block/function call raises ``expected_exception``
  427. and raise a failure exception otherwise.
  428. :arg message: if specified, provides a custom failure message if the
  429. exception is not raised
  430. :arg match: if specified, asserts that the exception matches a text or regex
  431. This helper produces a ``ExceptionInfo()`` object (see below).
  432. You may use this function as a context manager::
  433. >>> with raises(ZeroDivisionError):
  434. ... 1/0
  435. .. versionchanged:: 2.10
  436. In the context manager form you may use the keyword argument
  437. ``message`` to specify a custom failure message::
  438. >>> with raises(ZeroDivisionError, message="Expecting ZeroDivisionError"):
  439. ... pass
  440. Traceback (most recent call last):
  441. ...
  442. Failed: Expecting ZeroDivisionError
  443. .. note::
  444. When using ``pytest.raises`` as a context manager, it's worthwhile to
  445. note that normal context manager rules apply and that the exception
  446. raised *must* be the final line in the scope of the context manager.
  447. Lines of code after that, within the scope of the context manager will
  448. not be executed. For example::
  449. >>> value = 15
  450. >>> with raises(ValueError) as exc_info:
  451. ... if value > 10:
  452. ... raise ValueError("value must be <= 10")
  453. ... assert exc_info.type == ValueError # this will not execute
  454. Instead, the following approach must be taken (note the difference in
  455. scope)::
  456. >>> with raises(ValueError) as exc_info:
  457. ... if value > 10:
  458. ... raise ValueError("value must be <= 10")
  459. ...
  460. >>> assert exc_info.type == ValueError
  461. Since version ``3.1`` you can use the keyword argument ``match`` to assert that the
  462. exception matches a text or regex::
  463. >>> with raises(ValueError, match='must be 0 or None'):
  464. ... raise ValueError("value must be 0 or None")
  465. >>> with raises(ValueError, match=r'must be \d+$'):
  466. ... raise ValueError("value must be 42")
  467. **Legacy forms**
  468. The forms below are fully supported but are discouraged for new code because the
  469. context manager form is regarded as more readable and less error-prone.
  470. It is possible to specify a callable by passing a to-be-called lambda::
  471. >>> raises(ZeroDivisionError, lambda: 1/0)
  472. <ExceptionInfo ...>
  473. or you can specify an arbitrary callable with arguments::
  474. >>> def f(x): return 1/x
  475. ...
  476. >>> raises(ZeroDivisionError, f, 0)
  477. <ExceptionInfo ...>
  478. >>> raises(ZeroDivisionError, f, x=0)
  479. <ExceptionInfo ...>
  480. It is also possible to pass a string to be evaluated at runtime::
  481. >>> raises(ZeroDivisionError, "f(0)")
  482. <ExceptionInfo ...>
  483. The string will be evaluated using the same ``locals()`` and ``globals()``
  484. at the moment of the ``raises`` call.
  485. .. currentmodule:: _pytest._code
  486. Consult the API of ``excinfo`` objects: :class:`ExceptionInfo`.
  487. .. note::
  488. Similar to caught exception objects in Python, explicitly clearing
  489. local references to returned ``ExceptionInfo`` objects can
  490. help the Python interpreter speed up its garbage collection.
  491. Clearing those references breaks a reference cycle
  492. (``ExceptionInfo`` --> caught exception --> frame stack raising
  493. the exception --> current frame stack --> local variables -->
  494. ``ExceptionInfo``) which makes Python keep all objects referenced
  495. from that cycle (including all local variables in the current
  496. frame) alive until the next cyclic garbage collection run. See the
  497. official Python ``try`` statement documentation for more detailed
  498. information.
  499. """
  500. __tracebackhide__ = True
  501. for exc in filterfalse(isclass, always_iterable(expected_exception, BASE_TYPE)):
  502. msg = (
  503. "exceptions must be old-style classes or"
  504. " derived from BaseException, not %s"
  505. )
  506. raise TypeError(msg % type(exc))
  507. message = "DID NOT RAISE {}".format(expected_exception)
  508. match_expr = None
  509. if not args:
  510. if "message" in kwargs:
  511. message = kwargs.pop("message")
  512. if "match" in kwargs:
  513. match_expr = kwargs.pop("match")
  514. if kwargs:
  515. msg = "Unexpected keyword arguments passed to pytest.raises: "
  516. msg += ", ".join(kwargs.keys())
  517. raise TypeError(msg)
  518. return RaisesContext(expected_exception, message, match_expr)
  519. elif isinstance(args[0], str):
  520. code, = args
  521. assert isinstance(code, str)
  522. frame = sys._getframe(1)
  523. loc = frame.f_locals.copy()
  524. loc.update(kwargs)
  525. # print "raises frame scope: %r" % frame.f_locals
  526. try:
  527. code = _pytest._code.Source(code).compile()
  528. six.exec_(code, frame.f_globals, loc)
  529. # XXX didn't mean f_globals == f_locals something special?
  530. # this is destroyed here ...
  531. except expected_exception:
  532. return _pytest._code.ExceptionInfo()
  533. else:
  534. func = args[0]
  535. try:
  536. func(*args[1:], **kwargs)
  537. except expected_exception:
  538. return _pytest._code.ExceptionInfo()
  539. fail(message)
  540. raises.Exception = fail.Exception
  541. class RaisesContext(object):
  542. def __init__(self, expected_exception, message, match_expr):
  543. self.expected_exception = expected_exception
  544. self.message = message
  545. self.match_expr = match_expr
  546. self.excinfo = None
  547. def __enter__(self):
  548. self.excinfo = object.__new__(_pytest._code.ExceptionInfo)
  549. return self.excinfo
  550. def __exit__(self, *tp):
  551. __tracebackhide__ = True
  552. if tp[0] is None:
  553. fail(self.message)
  554. self.excinfo.__init__(tp)
  555. suppress_exception = issubclass(self.excinfo.type, self.expected_exception)
  556. if sys.version_info[0] == 2 and suppress_exception:
  557. sys.exc_clear()
  558. if self.match_expr and suppress_exception:
  559. self.excinfo.match(self.match_expr)
  560. return suppress_exception