Opened 9 years ago

Closed 9 years ago

#25188 closed Bug (fixed)

inconsistent escaping in assertRaisesMessage test output

Reported by: Chris Jerdonek Owned by: nobody
Component: Testing framework Version: 1.8
Severity: Normal Keywords: assertRaisesMessage
Cc: Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

If assertRaisesMessage fails with differing exception text, then the test output escapes the two strings differently in the display.

For example--

def test(self):
    with self.assertRaisesMessage(Exception, "two words"):
        raise Exception("no, three words")

yields--

----------------------------------------------------------------------
Exception: no, three words

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "***.py", line 178, in test
    raise Exception("no, three words")
AssertionError: "two\ words" does not match "no, three words"

Observe the spurious slash ("\") before the space. The test output is especially annoying if the string is long and has many spaces. It can look something like--

\{\
\ \ \ \ \"

Attachments (1)

25188.diff (1.3 KB ) - added by Tim Graham 9 years ago.

Download all attachments as: .zip

Change History (11)

comment:1 by Tim Graham, 9 years ago

Easy pickings: unset

I don't see an easy way to address this issue. The expected message is passed through re.escape(expected_message) before it's passed to six.assertRaisesRegex. It seems we would have to reimplement large parts of six.assertRaisesRegex in order to unescape the pattern in the error message.

comment:2 by Chris Jerdonek, 9 years ago

Since only a plain string comparison is needed, is there a reason the method needs to be using assertRaisesRegex? For example, couldn't the implementation bypass assertRaisesRegex and go something like this? (This is just an idea so shouldn't be taken as is.)

@contextmanager
def assertRaisesMessage(self, expected_exception, expected_message):
    with self.assertRaises(expected_exception) as cm:
        yield
    err = cm.exception
    self.assertEqual(str(err), expected_message)

An implementation like this would have the further advantage that in the case of a string mismatch the test output would show where the strings differ by using unittest's assertEqual, which is better equipped for this and has this added functionality.

comment:3 by Tim Graham, 9 years ago

Has patch: set
Patch needs improvement: set
Triage Stage: UnreviewedAccepted

The attached patch passes the test suite, however, it doesn't fail correctly when assertRaisesMessage() is used in a non-context manager context. If this issue can be solved, I don't see a reason not to use that approach.

by Tim Graham, 9 years ago

Attachment: 25188.diff added

comment:4 by Chris Jerdonek, 9 years ago

What about something like this?

@contextmanager
def _assert_raises_message_cm(self, expected_exception, expected_message):
    with self.assertRaises(expected_exception) as cm:
        yield cm
    err = cm.exception
    self.assertEqual(str(err), expected_message)

def assertRaisesMessage(self, expected_exception, expected_message, _callable=None, *args, **kwargs):
    cm = self._assert_raises_message_cm(expected_exception, expected_message)
    if _callable is None:
        # Then return the context manager.
        return cm
    # Otherwise, call the callable inside the context manager.
    with cm:
        _callable(*args, **kwargs)

comment:5 by Chris Jerdonek, 9 years ago

(Actually, if callable is supposed to be positional-only, then it could be pulled as the first element of *args.)

comment:6 by Tim Graham, 9 years ago

Patch needs improvement: unset

Yes, that seems to work. Thanks for the help. PR for review.

comment:7 by Chris Jerdonek, 9 years ago

Thanks. I'm not sure if you prefer using GitHub's inline commenting feature. But my only comment is: are you really supposed to be using assertIn() here? The method name and documentation suggest that an exact match is required (and that was my understanding before). If it really should be assertIn(), perhaps the documentation can be updated to clarify?

comment:8 by Chris Jerdonek, 9 years ago

From the docstring: "Asserts that the message in a raised exception matches the passed value."

comment:9 by Tim Graham, 9 years ago

Yes, that's the current behavior since it uses a regular expression match. Some tests in Django's test suite rely on this, so I don't think changing it would be a good idea. I'll update the docs.

comment:10 by Tim Graham <timograham@…>, 9 years ago

Resolution: fixed
Status: newclosed

In 1c7c782:

Fixed #25188 -- Improved message raised by SimpleTestCase.assertRaisesMessage().

Thanks Chris Jerdonek for the suggestion and help with the patch.

Note: See TracTickets for help on using tickets.
Back to Top