summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEzio Melotti <ezio.melotti@gmail.com>2009-12-24 22:25:17 (GMT)
committerEzio Melotti <ezio.melotti@gmail.com>2009-12-24 22:25:17 (GMT)
commitf84caf4edae9d1357db6afec75b042caf54cf3f5 (patch)
tree967c31d1ac2e16935d07036e560f744de51d5f7f
parentdb69f01ea909ccd5209f0e2cd532263760f67ce7 (diff)
downloadcpython-f84caf4edae9d1357db6afec75b042caf54cf3f5.zip
cpython-f84caf4edae9d1357db6afec75b042caf54cf3f5.tar.gz
cpython-f84caf4edae9d1357db6afec75b042caf54cf3f5.tar.bz2
#6108: unicode(exception) and str(exception) should return the same message
-rw-r--r--Lib/test/test_exceptions.py110
-rw-r--r--Misc/NEWS3
-rw-r--r--Objects/exceptions.c15
3 files changed, 127 insertions, 1 deletions
diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py
index f547eb2..d5ce80b 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -430,8 +430,116 @@ class ExceptionTests(unittest.TestCase):
self.assertTrue("maximum recursion depth exceeded" in str(v), v)
+
+# Helper class used by TestSameStrAndUnicodeMsg
+class ExcWithOverriddenStr(Exception):
+ """Subclass of Exception that accepts a keyword 'msg' arg that is
+ returned by __str__. 'msg' won't be included in self.args"""
+ def __init__(self, *args, **kwargs):
+ self.msg = kwargs.pop('msg') # msg should always be present
+ super(ExcWithOverriddenStr, self).__init__(*args, **kwargs)
+ def __str__(self):
+ return self.msg
+
+
+class TestSameStrAndUnicodeMsg(unittest.TestCase):
+ """unicode(err) should return the same message of str(err). See #6108"""
+
+ def check_same_msg(self, exc, msg):
+ """Helper function that checks if str(exc) == unicode(exc) == msg"""
+ self.assertEqual(str(exc), msg)
+ self.assertEqual(str(exc), unicode(exc))
+
+ def test_builtin_exceptions(self):
+ """Check same msg for built-in exceptions"""
+ # These exceptions implement a __str__ method that uses the args
+ # to create a better error message. unicode(e) should return the same
+ # message.
+ exceptions = [
+ SyntaxError('invalid syntax', ('<string>', 1, 3, '2+*3')),
+ IOError(2, 'No such file or directory'),
+ KeyError('both should have the same quotes'),
+ UnicodeDecodeError('ascii', '\xc3\xa0', 0, 1,
+ 'ordinal not in range(128)'),
+ UnicodeEncodeError('ascii', u'\u1234', 0, 1,
+ 'ordinal not in range(128)')
+ ]
+ for exception in exceptions:
+ self.assertEqual(str(exception), unicode(exception))
+
+ def test_0_args(self):
+ """Check same msg for Exception with 0 args"""
+ # str() and unicode() on an Exception with no args should return an
+ # empty string
+ self.check_same_msg(Exception(), '')
+
+ def test_0_args_with_overridden___str__(self):
+ """Check same msg for exceptions with 0 args and overridden __str__"""
+ # str() and unicode() on an exception with overridden __str__ that
+ # returns an ascii-only string should return the same string
+ for msg in ('foo', u'foo'):
+ self.check_same_msg(ExcWithOverriddenStr(msg=msg), msg)
+
+ # if __str__ returns a non-ascii unicode string str() should fail
+ # but unicode() should return the unicode string
+ e = ExcWithOverriddenStr(msg=u'f\xf6\xf6') # no args
+ self.assertRaises(UnicodeEncodeError, str, e)
+ self.assertEqual(unicode(e), u'f\xf6\xf6')
+
+ def test_1_arg(self):
+ """Check same msg for Exceptions with 1 arg"""
+ for arg in ('foo', u'foo'):
+ self.check_same_msg(Exception(arg), arg)
+
+ # if __str__ is not overridden and self.args[0] is a non-ascii unicode
+ # string, str() should try to return str(self.args[0]) and fail.
+ # unicode() should return unicode(self.args[0]) and succeed.
+ e = Exception(u'f\xf6\xf6')
+ self.assertRaises(UnicodeEncodeError, str, e)
+ self.assertEqual(unicode(e), u'f\xf6\xf6')
+
+ def test_1_arg_with_overridden___str__(self):
+ """Check same msg for exceptions with overridden __str__ and 1 arg"""
+ # when __str__ is overridden and __unicode__ is not implemented
+ # unicode(e) returns the same as unicode(e.__str__()).
+ for msg in ('foo', u'foo'):
+ self.check_same_msg(ExcWithOverriddenStr('arg', msg=msg), msg)
+
+ # if __str__ returns a non-ascii unicode string, str() should fail
+ # but unicode() should succeed.
+ e = ExcWithOverriddenStr('arg', msg=u'f\xf6\xf6') # 1 arg
+ self.assertRaises(UnicodeEncodeError, str, e)
+ self.assertEqual(unicode(e), u'f\xf6\xf6')
+
+ def test_many_args(self):
+ """Check same msg for Exceptions with many args"""
+ argslist = [
+ (3, 'foo'),
+ (1, u'foo', 'bar'),
+ (4, u'f\xf6\xf6', u'bar', 'baz')
+ ]
+ # both str() and unicode() should return a repr() of the args
+ for args in argslist:
+ self.check_same_msg(Exception(*args), repr(args))
+
+ def test_many_args_with_overridden___str__(self):
+ """Check same msg for exceptions with overridden __str__ and many args"""
+ # if __str__ returns an ascii string / ascii unicode string
+ # both str() and unicode() should succeed
+ for msg in ('foo', u'foo'):
+ e = ExcWithOverriddenStr('arg1', u'arg2', u'f\xf6\xf6', msg=msg)
+ self.check_same_msg(e, msg)
+
+ # if __str__ returns a non-ascii unicode string, str() should fail
+ # but unicode() should succeed
+ e = ExcWithOverriddenStr('arg1', u'f\xf6\xf6', u'arg3', # 3 args
+ msg=u'f\xf6\xf6')
+ self.assertRaises(UnicodeEncodeError, str, e)
+ self.assertEqual(unicode(e), u'f\xf6\xf6')
+
+
def test_main():
- run_unittest(ExceptionTests)
+ run_unittest(ExceptionTests, TestSameStrAndUnicodeMsg)
if __name__ == '__main__':
test_main()
diff --git a/Misc/NEWS b/Misc/NEWS
index 1ed3421..bcb2060 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,9 @@ What's New in Python 2.7 alpha 2?
Core and Builtins
-----------------
+- Issue #6108: unicode(exception) and str(exception) should return the same
+ message when only __str__ (and not __unicode__) is overridden in the subclass.
+
- Issue #6834: replace the implementation for the 'python' and 'pythonw'
executables on OSX.
diff --git a/Objects/exceptions.c b/Objects/exceptions.c
index 0a39a6b..c246d67 100644
--- a/Objects/exceptions.c
+++ b/Objects/exceptions.c
@@ -122,6 +122,21 @@ BaseException_unicode(PyBaseExceptionObject *self)
{
PyObject *out;
+ /* issue6108: if __str__ has been overridden in the subclass, unicode()
+ should return the message returned by __str__ as used to happen
+ before this method was implemented. */
+ if (Py_TYPE(self)->tp_str != (reprfunc)BaseException_str) {
+ PyObject *str;
+ /* Unlike PyObject_Str, tp_str can return unicode (i.e. return the
+ equivalent of unicode(e.__str__()) instead of unicode(str(e))). */
+ str = Py_TYPE(self)->tp_str((PyObject*)self);
+ if (str == NULL)
+ return NULL;
+ out = PyObject_Unicode(str);
+ Py_DECREF(str);
+ return out;
+ }
+
switch (PyTuple_GET_SIZE(self->args)) {
case 0:
out = PyUnicode_FromString("");