diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c4d5120d8..8704ab47c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,6 +18,10 @@ * New Add ability to add global properties in the final xunit output file. Thanks `@tareqalayan`_ for the complete PR `#1454`_). +* New ``ExceptionInfo.match()`` method to match a regular expression on the + string representation of an exception. Closes proposal `#372`_. + Thanks `@omarkohl`_ for the complete PR (`#1502`_) and `@nicoddemus`_ for the + implementation tips. * @@ -38,6 +42,7 @@ .. _@kalekundert: https://github.com/kalekundert .. _@tareqalayan: https://github.com/tareqalayan .. _@palaviv: https://github.com/palaviv +.. _@omarkohl: https://github.com/omarkohl .. _#1428: https://github.com/pytest-dev/pytest/pull/1428 .. _#1444: https://github.com/pytest-dev/pytest/pull/1444 @@ -45,6 +50,8 @@ .. _#1454: https://github.com/pytest-dev/pytest/pull/1454 .. _#1468: https://github.com/pytest-dev/pytest/pull/1468 .. _#1474: https://github.com/pytest-dev/pytest/pull/1474 +.. _#1502: https://github.com/pytest-dev/pytest/pull/1502 +.. _#372: https://github.com/pytest-dev/pytest/issues/372 2.9.2.dev1 diff --git a/_pytest/_code/code.py b/_pytest/_code/code.py index bc68aac55..48c3da1a8 100644 --- a/_pytest/_code/code.py +++ b/_pytest/_code/code.py @@ -1,5 +1,6 @@ import sys from inspect import CO_VARARGS, CO_VARKEYWORDS +import re import py @@ -427,6 +428,19 @@ class ExceptionInfo(object): loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly()) return unicode(loc) + def match(self, regexp): + """ + Match the regular expression 'regexp' on the string representation of + the exception. If it matches then True is returned (so that it is + possible to write 'assert excinfo.match()'). If it doesn't match an + AssertionError is raised. + """ + __tracebackhide__ = True + if not re.search(regexp, str(self.value)): + assert 0, "Pattern '{0!s}' not found in '{1!s}'".format( + regexp, self.value) + return True + class FormattedExcinfo(object): """ presenting information about failing Functions and Generators. """ diff --git a/doc/en/assert.rst b/doc/en/assert.rst index e7f14e8bd..f095d6878 100644 --- a/doc/en/assert.rst +++ b/doc/en/assert.rst @@ -110,6 +110,24 @@ exceptions your own code is deliberately raising, whereas using like documenting unfixed bugs (where the test describes what "should" happen) or bugs in dependencies. +If you want to test that a regular expression matches on the string +representation of an exception (like the ``TestCase.assertRaisesRegexp`` method +from ``unittest``) you can use the ``ExceptionInfo.match`` method:: + + import pytest + + def myfunc(): + raise ValueError("Exception 123 raised") + + def test_match(): + with pytest.raises(ValueError) as excinfo: + myfunc() + excinfo.match(r'.* 123 .*') + +The regexp parameter of the ``match`` method is matched with the ``re.search`` +function. So in the above example ``excinfo.match('123')`` would have worked as +well. + .. _`assertwarns`: diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 2defa3103..c925b1d28 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -323,6 +323,25 @@ def test_codepath_Queue_example(): assert path.basename.lower() == "queue.py" assert path.check() +def test_match_succeeds(): + with pytest.raises(ZeroDivisionError) as excinfo: + 0 / 0 + excinfo.match(r'.*zero.*') + +def test_match_raises_error(testdir): + testdir.makepyfile(""" + import pytest + def test_division_zero(): + with pytest.raises(ZeroDivisionError) as excinfo: + 0 / 0 + excinfo.match(r'[123]+') + """) + result = testdir.runpytest() + assert result.ret != 0 + result.stdout.fnmatch_lines([ + "*AssertionError*Pattern*[123]*not found*", + ]) + class TestFormattedExcinfo: def pytest_funcarg__importasmod(self, request): def importasmod(source):