You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by rd...@apache.org on 2010/08/02 00:19:59 UTC
svn commit: r981330 - in /subversion/trunk/subversion/bindings/swig/python:
libsvn_swig_py/swigutil_py.c tests/core.py
Author: rdonch
Date: Sun Aug 1 22:19:59 2010
New Revision: 981330
URL: http://svn.apache.org/viewvc?rev=981330&view=rev
Log:
SWIG/Python: enhance the exception translation mechanism so that a
SubversionException thrown from a callback would be fully translated into an
svn_error_t, including any nested exceptions.
* subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c:
(callback_exception_error): Split into two, the other half being
exception_to_error.
(exception_to_error): New function for handling the conversion itself.
* subversion/bindings/swig/python/tests/core.py:
(SubversionCoreTestCase.test_exception_interoperability): New test for
the exception translation mechanism, including this revision's
improvements.
Modified:
subversion/trunk/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
subversion/trunk/subversion/bindings/swig/python/tests/core.py
Modified: subversion/trunk/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c?rev=981330&r1=981329&r2=981330&view=diff
==============================================================================
--- subversion/trunk/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c (original)
+++ subversion/trunk/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c Sun Aug 1 22:19:59 2010
@@ -1320,6 +1320,61 @@ commit_item_array_to_list(const apr_arra
/*** Errors ***/
+/* Convert a given SubversionException to an svn_error_t. On failure returns
+ NULL and sets a Python exception. */
+static svn_error_t *exception_to_error(PyObject * exc)
+{
+ const char *message, *file = NULL;
+ apr_status_t apr_err;
+ long line = 0;
+ PyObject *apr_err_ob = NULL, *child_ob = NULL, *message_ob = NULL;
+ PyObject *file_ob = NULL, *line_ob = NULL;
+ svn_error_t *rv = NULL, *child = NULL;
+
+ if ((apr_err_ob = PyObject_GetAttrString(exc, "apr_err")) == NULL)
+ goto finished;
+ apr_err = (apr_status_t) PyInt_AsLong(apr_err_ob);
+ if (PyErr_Occurred()) goto finished;
+
+ if ((message_ob = PyObject_GetAttrString(exc, "message")) == NULL)
+ goto finished;
+ message = PyString_AsString(message_ob);
+ if (PyErr_Occurred()) goto finished;
+
+ if ((file_ob = PyObject_GetAttrString(exc, "file")) == NULL)
+ goto finished;
+ if (file_ob != Py_None)
+ file = PyString_AsString(file_ob);
+ if (PyErr_Occurred()) goto finished;
+
+ if ((line_ob = PyObject_GetAttrString(exc, "line")) == NULL)
+ goto finished;
+ if (line_ob != Py_None)
+ line = PyInt_AsLong(line_ob);
+ if (PyErr_Occurred()) goto finished;
+
+ if ((child_ob = PyObject_GetAttrString(exc, "child")) == NULL)
+ goto finished;
+ /* We could check if the child is a Subversion exception too,
+ but let's just apply duck typing. */
+ if (child_ob != Py_None)
+ child = exception_to_error(child_ob);
+ if (PyErr_Occurred()) goto finished;
+
+ rv = svn_error_create(apr_err, child, message);
+ /* Somewhat hacky, but we need to preserve original file/line info. */
+ rv->file = file ? apr_pstrdup(rv->pool, file) : NULL;
+ rv->line = line;
+
+finished:
+ Py_XDECREF(child_ob);
+ Py_XDECREF(line_ob);
+ Py_XDECREF(file_ob);
+ Py_XDECREF(message_ob);
+ Py_XDECREF(apr_err_ob);
+ return rv;
+}
+
/* If the currently set Python exception is a valid SubversionException,
clear exception state and transform it into a Subversion error.
Otherwise, return a Subversion error about an exception in a callback. */
@@ -1327,48 +1382,36 @@ static svn_error_t *callback_exception_e
{
PyObject *svn_module = NULL, *svn_exc = NULL;
PyObject *exc, *exc_type, *exc_traceback;
- PyObject *message_ob = NULL, *apr_err_ob = NULL;
- const char *message;
- int apr_err;
svn_error_t *rv = NULL;
PyErr_Fetch(&exc_type, &exc, &exc_traceback);
if ((svn_module = PyImport_ImportModule("svn.core")) == NULL)
- goto finished;
- if ((svn_exc = PyObject_GetAttrString(svn_module, "SubversionException"))
- == NULL)
- goto finished;
-
- if (!PyErr_GivenExceptionMatches(exc_type, svn_exc))
- {
- PyErr_Restore(exc_type, exc, exc_traceback);
- exc = exc_type = exc_traceback = NULL;
goto finished;
- }
- if ((apr_err_ob = PyObject_GetAttrString(exc, "apr_err")) == NULL)
- goto finished;
- apr_err = PyInt_AsLong(apr_err_ob);
- if (PyErr_Occurred()) goto finished;
+ svn_exc = PyObject_GetAttrString(svn_module, "SubversionException");
+ Py_DECREF(svn_module);
- if ((message_ob = PyObject_GetAttrString(exc, "message")) == NULL)
- goto finished;
- message = PyString_AsString(message_ob);
- if (PyErr_Occurred()) goto finished;
+ if (svn_exc == NULL)
+ goto finished;
- /* A possible improvement here would be to convert the whole
- SubversionException chain. */
- rv = svn_error_create(apr_err, NULL, message);
+ if (PyErr_GivenExceptionMatches(exc_type, svn_exc))
+ {
+ rv = exception_to_error(exc);
+ }
+ else
+ {
+ PyErr_Restore(exc_type, exc, exc_traceback);
+ exc_type = exc = exc_traceback = NULL;
+ }
finished:
- Py_XDECREF(exc);
+ Py_XDECREF(svn_exc);
Py_XDECREF(exc_type);
+ Py_XDECREF(exc);
Py_XDECREF(exc_traceback);
- Py_XDECREF(svn_module);
- Py_XDECREF(svn_exc);
- Py_XDECREF(apr_err_ob);
- Py_XDECREF(message_ob);
+ /* By now, either rv is set and the exception is cleared, or rv is NULL
+ and an exception is pending (possibly a new one). */
return rv ? rv : svn_error_create(SVN_ERR_SWIG_PY_EXCEPTION_SET, NULL,
"Python callback raised an exception");
}
Modified: subversion/trunk/subversion/bindings/swig/python/tests/core.py
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/swig/python/tests/core.py?rev=981330&r1=981329&r2=981330&view=diff
==============================================================================
--- subversion/trunk/subversion/bindings/swig/python/tests/core.py (original)
+++ subversion/trunk/subversion/bindings/swig/python/tests/core.py Sun Aug 1 22:19:59 2010
@@ -20,7 +20,8 @@
#
import unittest
-import svn.core
+import svn.core, svn.client
+import utils
class SubversionCoreTestCase(unittest.TestCase):
"""Test cases for the basic SWIG Subversion core"""
@@ -46,6 +47,64 @@ class SubversionCoreTestCase(unittest.Te
svn.core.svn_mime_type_validate, "this\nis\ninvalid\n")
svn.core.svn_mime_type_validate("unknown/but-valid; charset=utf8")
+ def test_exception_interoperability(self):
+ """Test if SubversionException is correctly converted into svn_error_t
+ and vice versa."""
+ t = utils.Temper()
+ (_, _, repos_uri) = t.alloc_empty_repo(suffix='-core')
+ rev = svn.core.svn_opt_revision_t()
+ rev.kind = svn.core.svn_opt_revision_head
+ ctx = svn.client.create_context()
+
+ class Receiver:
+ def __call__(self, path, info, pool):
+ raise self.e
+
+ rec = Receiver()
+ args = (repos_uri, rev, rev, rec, svn.core.svn_depth_empty, None, ctx)
+
+ try:
+ # ordinary Python exceptions must be passed through
+ rec.e = TypeError()
+ self.assertRaises(TypeError, svn.client.info2, *args)
+
+ # SubversionException will be translated into an svn_error_t, propagated
+ # through the call chain and translated back to SubversionException.
+ rec.e = svn.core.SubversionException("Bla bla bla.",
+ svn.core.SVN_ERR_INCORRECT_PARAMS,
+ file=__file__, line=866)
+ rec.e.child = svn.core.SubversionException("Yada yada.",
+ svn.core.SVN_ERR_INCOMPLETE_DATA)
+ self.assertRaises(svn.core.SubversionException, svn.client.info2, *args)
+
+ # It must remain unchanged through the process.
+ try:
+ svn.client.info2(*args)
+ except svn.core.SubversionException as exc:
+ # find the original exception
+ while exc.file != rec.e.file: exc = exc.child
+
+ self.assertEqual(exc.message, rec.e.message)
+ self.assertEqual(exc.apr_err, rec.e.apr_err)
+ self.assertEqual(exc.line, rec.e.line)
+ self.assertEqual(exc.child.message, rec.e.child.message)
+ self.assertEqual(exc.child.apr_err, rec.e.child.apr_err)
+ self.assertEqual(exc.child.child, None)
+ self.assertEqual(exc.child.file, None)
+ self.assertEqual(exc.child.line, 0)
+
+ # Incomplete SubversionExceptions must trigger Python exceptions, which
+ # will be passed through.
+ rec.e = svn.core.SubversionException("No fields except message.")
+ # e.apr_err is None but should be an int
+ self.assertRaises(TypeError, svn.client.info2, args)
+ finally:
+ # This would happen without the finally block as well, but we expliticly
+ # order the operations so that the cleanup is not hindered by any open
+ # handles.
+ del ctx
+ t.cleanup()
+
def suite():
return unittest.makeSuite(SubversionCoreTestCase, 'test')