Hi,
I've made a patch swig-py3@1850520, to accept both of bytes and str
objects for input args correspondings char * args of Subversion API
on Python 3 wrapper functions. It is just a interim report, because
I have some points I want to make clear, and/or need to fix.
(Moreover, I've not touched code to convert return value of Python
callback functions yet)
The patch attached modifies 4 kind of input argment translations.
(1) typemap(in) char * (with/without const modifiers); not allow NULL,
typemap(in) const char * MAY_BE_NULL; allows NULL
These had done by using 'parse=' typemap modifier, however there is no
PyArg_Parse() format unit to convert both of str and bytes in py3.
So I make a function svn_swig_py_string_to_cstring() in swigutil_py.c,
and use it in for new typemap definition.
consideration:
* For py2, my patch code uses svn_swig_py_string_to_cstring()
- It isn't allow Unicode for input, however 's' and 'z' format units
PyArg_Parse() Unicode in py2. If it is need to accept Unicode in py2,
it is need to fix. (use svn_swig_py_string_to_cstring() py3 only, or
add code to conversion for py2)
- Difference of TypeError exception message. Pyrg_Parse() reports
argment by argnum in Python wrapper function. However it can't
determin in typemap definition code, so my patch code uses argment
symbol instead.
* For py3, it seems to need more kindly Exception message for
UnicodeEncodeError, which can be caused if input str contains surrogate
data (U+DC00 - U+DCFF).
* test case for UnicodeEncodeError is needed
(2) in core.i, typemap for svn_stream_write
consideration:
* As this typemap doesn't seems to accept Unicode on py2, there seems to
be no regression like described in (1)
* As this typemap only used for svn_stream_write wrapper which have only
one char * arg, default UnicodeEncodeError message seems to be sufficient.
(3) typemap(in) apr_hash_t * (for various types)
These are using make_string_from_ob() for hash key string conversion,
and typemap(in) apr_hash_t *HASH_CSTRING uses it for hash value
conversion, too. Similarly typemap(in) apr_hash_t *PROPHASH uses
make_svn_strinf_from_ob() for hash value conversion
consideration:
* It seems some of API (e.g. svn_prop_diffs()) allows NULL for hash value,
but current implementation of conversion function doesn't allows. Isn't
it needed? (I added test for this case, but disabled until it make clear)
* test case for UnicodeEncodeError is needed (for both of hash key and
hash value)
(4) typemap(in) apr_array_header_t *STRINGLIST
This typemap is using svn_swig_py_unwrap_string() through the function
svn_swig_py_seq_to_array().
consideration:
* test case for UnicodeEncodeError is needed (for both of hash key and
hash value)
Thanks,
--
Yasuhito FUTATSUKI
[In subversion/bindings/swig]
* core.i (%typemap(in) (const char *data, apr_size_t *len): On Python 3,
allow Str as well for data argment of svn_stream_write()
* include/svn_global.swg
(remove)(%typemap(in) char *, char const *, char * const,
char const * const): Move this typemap into include/svn_strings as
typemap (in) IN_STRING
* include/svn_string.swg (new)(%typemap(in) IN_STRING): replacement of
%typemap(in) char *, char const *, char * const, char const * const.
actual processing code is moved new svn_swig_py_string_to_cstring()
function in python/libsvn_swig_py/swigutil_py.c
* include/svn_types.swg (%typemap(in) const char *MAY_BE_NULL):
Move processing code into new svn_swig_py_string_to_cstring() function
in python/libsvn_swig_py/swigutil_py.c
* python/libsvn_swig_py/swigutil_py.c
(svn_swig_py_string_to_cstring): New function to convert Python
bytes or str into const char *, with better TypeError exception message
(svn_swig_py_string_type_exception): New function to construct
TypeError exception for new make_string_from_ob_maybe_null() function
(make_string_from_ob, make_svn_string_from_ob):
- Allow Str as well as bytes for ob on Python 3
- Don't raise TypeError exception because all callers don't check it
(make_string_from_ob_maybe_null): New function same as
make_string_from_ob() but allows None input represents NULL value and
raise TypeError if input value don't have appropriate type
(svn_swig_py_proparray_from_dict, svn_swig_py_proparray_from_dict):
Include acceptable type in TypeError exception message on Python 3
(svn_swig_py_unwrap_string):
- Allow Str as well as bytes for source argument on Python 3
(svn_swig_py_make_file):
- Allow Str as well as bytes for py_file argument as file path
on Python 3
(svn_swig_py_auth_gnome_keyring_unlock_prompt_func):
- Use new function make_string_from_ob_maybe_null() instead of
make_string_from_ob() to check TypeError
- Report Python exception caused by Python callback function as
callback exception error
* python/libsvn_swig_py/swigutil_py.h
Expose new public function make_string_from_ob_maybe_null(), which is
used by typemap(in) char IN_STRING, typemap(in) const char *MAY_BY_NULL
* python/tests/client.py
(SubversionClientTestCase.log_entry_receiver_whole): New helper
callback function for new SubversionClientTestCase.test_log5_revprops
(SubversionClientTestCase.test_log5_revprops): new test for
typemap(in) apr_array_t *STRINGLIST and its helper function
svn_swig_py_unwrap_string()
* python/tests/core.py
(SubversionCoreTestCase.test_stream_write_exception):
- As unicode input is now valid, use int value as invalid input
- (Only for Python 3) Denote that input including surrogate causes
UnicodeEncodeError
(SubversionCoreTestCase.test_stream_write_str):(Only for Python 3)
New test case for svn_stream_write() to pass Str object as data argument
(SubversionCoreTestCase.test_stream_write_bytes):
Renamed from SubversionCoreTestCase.test_stream_write
* python/tests/run_all.py: Register new test module typemap
* python/tests/typemap.py: New unittest module for typemaps
(SubversionTypemapTestCase.test_char_ptr_in): New test case
(SubversionTypemapTestCase.test_char_ptr_may_be_null): New test case
(SubversionTypemapTestCase.test_make_string_from_ob): New test case
(SubversionTypemapTestCase.test_prophash_from_dict_null_value):
New test case, not activate until make specification clear
diff --git a/subversion/bindings/swig/core.i b/subversion/bindings/swig/core.i
index c309d48070..6993ef4c9c 100644
--- a/subversion/bindings/swig/core.i
+++ b/subversion/bindings/swig/core.i
@@ -442,18 +442,31 @@
*/
#ifdef SWIGPYTHON
%typemap(in) (const char *data, apr_size_t *len) ($*2_type temp) {
- char *tmpdata;
Py_ssize_t length;
- if (!PyBytes_Check($input)) {
- PyErr_SetString(PyExc_TypeError,
- "expecting a bytes object for the buffer");
- SWIG_fail;
+ if (PyBytes_Check($input)) {
+ if (PyBytes_AsStringAndSize($input, (char **)&$1, &length) == -1) {
+ SWIG_fail;
+ }
+ }
+%#if IS_PY3
+ else if (PyStr_Check($input)) {
+ $1 = PyStr_AsUTF8AndSize($input, &length);
+ if (PyErr_Occurred()) {
+ SWIG_fail;
+ }
}
- if (PyBytes_AsStringAndSize($input, &tmpdata, &length) == -1) {
+%#endif
+ else {
+ PyErr_SetString(PyExc_TypeError,
+%#if IS_PY3
+ "expecting a bytes or str object for the buffer"
+%#else
+ "expecting a string for the buffer"
+%#endif
+ );
SWIG_fail;
}
temp = ($*2_type)length;
- $1 = tmpdata;
$2 = ($2_ltype)&temp;
}
#endif
diff --git a/subversion/bindings/swig/include/svn_global.swg
b/subversion/bindings/swig/include/svn_global.swg
index f7364be28f..57b1e236db 100644
--- a/subversion/bindings/swig/include/svn_global.swg
+++ b/subversion/bindings/swig/include/svn_global.swg
@@ -142,13 +142,6 @@ static PyObject * _global_py_pool = NULL;
/* Python format specifiers. Use Python instead of SWIG to parse
these basic types, because Python reports better error messages
(with correct argument numbers). */
-#if defined(PY3)
-%typemap (in, parse="y")
- char *, char const *, char * const, char const * const "";
-#else
-%typemap (in, parse="s")
- char *, char const *, char * const, char const * const "";
-#endif
%typemap (in, parse="c") char "";
%typemap (in, fragment=SWIG_As_frag(long)) long
diff --git a/subversion/bindings/swig/include/svn_string.swg
b/subversion/bindings/swig/include/svn_string.swg
index 0fc64ebdcc..8be4c3d746 100644
--- a/subversion/bindings/swig/include/svn_string.swg
+++ b/subversion/bindings/swig/include/svn_string.swg
@@ -251,6 +251,26 @@ typedef struct svn_string_t svn_string_t;
}
#endif
+ /* -----------------------------------------------------------------------
+ Type: char * (input)
+*/
+#ifdef SWIGPYTHON
+%typemap (in) IN_STRING
+{
+ $1 = svn_swig_py_string_to_cstring($input, FALSE, "$symname", "$1_name");
+ if (PyErr_Occurred()) SWIG_fail;
+}
+
+%typemap (freearg) IN_STRING "";
+
+%apply IN_STRING {
+ const char *,
+ char *,
+ char const *,
+ char * const,
+ char const * const
+};
+#endif
/* -----------------------------------------------------------------------
define a way to return a 'const char *'
*/
diff --git a/subversion/bindings/swig/include/svn_types.swg
b/subversion/bindings/swig/include/svn_types.swg
index 319f7daa6b..7c933b1ac7 100644
--- a/subversion/bindings/swig/include/svn_types.swg
+++ b/subversion/bindings/swig/include/svn_types.swg
@@ -348,12 +348,8 @@ svn_ ## TYPE ## _swig_rb_closed(VALUE self)
#ifdef SWIGPYTHON
%typemap(in) const char *MAY_BE_NULL
{
- if ($input == Py_None) {
- $1 = NULL;
- } else {
- $1 = PyBytes_AsString($input);
- if ($1 == NULL) SWIG_fail;
- }
+ $1 = svn_swig_py_string_to_cstring($input, TRUE, "$symname", "$1_name");
+ if (PyErr_Occurred()) SWIG_fail;
}
#endif
diff --git a/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
b/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
index 8a4ec631be..58cfec30a3 100644
--- a/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
+++ b/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
@@ -506,6 +506,32 @@ void svn_swig_py_svn_exception(svn_error_t *error_chain)
/*** Helper/Conversion Routines ***/
+/* Function to get char * representation of bytes/str object */
+char *svn_swig_py_string_to_cstring(PyObject *input, int maybe_null,
+ const char * funcsym, const char * argsym)
+{
+ char *retval = NULL;
+ if (PyBytes_Check(input)) {
+ retval = PyBytes_AsString(input);
+ }
+#if IS_PY3
+ else if (PyStr_Check(input)) {
+ retval = (char *)PyStr_AsUTF8(input);
+ }
+#endif
+ else if (input != Py_None || ! maybe_null) {
+ PyErr_Format(PyExc_TypeError,
+#if IS_PY3
+ "%s() argument %s must be bytes or str%s, not %s",
+#else
+ "%s() argument %s must be string%s, not %s",
+#endif
+ funcsym, argsym, maybe_null?" or None":"",
+ Py_TYPE(input)->tp_name);
+ }
+ return retval;
+}
+
/* Functions for making Python wrappers around Subversion structs */
static PyObject *make_ob_pool(void *pool)
{
@@ -540,29 +566,62 @@ static PyObject *make_ob_error(svn_error_t *err)
/***/
+static void svn_swig_py_string_type_exception(int maybe_null) {
+ PyErr_Format(PyExc_TypeError,
+#if IS_PY3
+ "not a bytes or a str%s",
+#else
+ "not a string%s",
+#endif
+ maybe_null?" or None":"");
+}
+
/* Conversion from Python single objects (not hashes/lists/etc.) to
Subversion types. */
static char *make_string_from_ob(PyObject *ob, apr_pool_t *pool)
{
- if (ob == Py_None)
- return NULL;
- if (! PyBytes_Check(ob))
+ /* caller should not expect to raise TypeError: check return value
+ whether it is NULL or not, if needed */
+ if (PyBytes_Check(ob))
{
- PyErr_SetString(PyExc_TypeError, "not a bytes object");
- return NULL;
+ return apr_pstrdup(pool, PyBytes_AsString(ob));
+ }
+#if IS_PY3
+ if (PyStr_Check(ob))
+ {
+ return apr_pstrdup(pool, PyStr_AsUTF8(ob));
}
- return apr_pstrdup(pool, PyBytes_AsString(ob));
+#endif
+ return NULL;
}
-static svn_string_t *make_svn_string_from_ob(PyObject *ob, apr_pool_t *pool)
+
+static char *make_string_from_ob_maybe_null(PyObject *ob, apr_pool_t *pool)
{
+ char * retval;
if (ob == Py_None)
return NULL;
- if (! PyBytes_Check(ob))
+ retval = make_string_from_ob(ob, pool);
+ if (!retval) {
+ svn_swig_py_string_type_exception(TRUE);
+ }
+ return retval;
+}
+
+static svn_string_t *make_svn_string_from_ob(PyObject *ob, apr_pool_t *pool)
+{
+ /* caller should not expect to raise TypeError: check return value
+ whether it is NULL or not, if needed */
+ if (PyBytes_Check(ob))
{
- PyErr_SetString(PyExc_TypeError, "not a bytes object");
- return NULL;
+ return svn_string_create(PyBytes_AsString(ob), pool);
}
- return svn_string_create(PyBytes_AsString(ob), pool);
+#if IS_PY3
+ if (PyStr_Check(ob))
+ {
+ return svn_string_create(PyStr_AsUTF8(ob), pool);
+ }
+#endif
+ return NULL;
}
@@ -1136,6 +1195,14 @@ apr_hash_t *svn_swig_py_mergeinfo_from_dict(PyObject
*dict,
return hash;
}
+#if IS_PY3
+#define TYPE_ERROR_DICT_STRING \
+ "dictionary keys/values aren't bytes or str objects"
+#else
+#define TYPE_ERROR_DICT_STRING \
+ "dictionary keys/values aren't strings"
+#endif
+
apr_array_header_t *svn_swig_py_proparray_from_dict(PyObject *dict,
apr_pool_t *pool)
{
@@ -1164,8 +1231,7 @@ apr_array_header_t
*svn_swig_py_proparray_from_dict(PyObject *dict,
prop->value = make_svn_string_from_ob(value, pool);
if (! (prop->name && prop->value))
{
- PyErr_SetString(PyExc_TypeError,
- "dictionary keys/values aren't strings");
+ PyErr_SetString(PyExc_TypeError, TYPE_ERROR_DICT_STRING);
Py_DECREF(keys);
return NULL;
}
@@ -1202,8 +1268,7 @@ apr_hash_t *svn_swig_py_prophash_from_dict(PyObject *dict,
svn_string_t *propval = make_svn_string_from_ob(value, pool);
if (! (propname && propval))
{
- PyErr_SetString(PyExc_TypeError,
- "dictionary keys/values aren't strings");
+ PyErr_SetString(PyExc_TypeError, TYPE_ERROR_DICT_STRING);
Py_DECREF(keys);
return NULL;
}
@@ -1321,8 +1386,27 @@ svn_swig_py_unwrap_string(PyObject *source,
void *baton)
{
const char **ptr_dest = destination;
- *ptr_dest = PyBytes_AsString(source);
-
+ if (PyBytes_Check(source))
+ {
+ *ptr_dest = PyBytes_AsString(source);
+ }
+#if IS_PY3
+ else if (PyStr_Check(source))
+ {
+ *ptr_dest = PyStr_AsUTF8(source);
+ }
+#endif
+ else
+ {
+ PyErr_Format(PyExc_TypeError,
+#if IS_PY3
+ "Expected bytes or str object, %s found",
+#else
+ "Expected string or Unicode object, %s found",
+#endif
+ Py_TYPE(source)->tp_name);
+ *ptr_dest = NULL;
+ }
if (*ptr_dest != NULL)
return 0;
else
@@ -2546,14 +2630,25 @@ apr_file_t *svn_swig_py_make_file(PyObject *py_file,
{
apr_file_t *apr_file = NULL;
apr_status_t apr_err;
+ const char* fname = NULL;
if (py_file == NULL || py_file == Py_None)
return NULL;
+ /* check if input is a path */
if (PyBytes_Check(py_file))
+ {
+ fname = PyBytes_AsString(py_file);
+ }
+#if IS_PY3
+ else if (PyStr_Check(py_file))
+ {
+ fname = PyStr_AsUTF8(py_file);
+ }
+#endif
+ if (fname)
{
/* input is a path -- just open an apr_file_t */
- const char* fname = PyBytes_AsString(py_file);
apr_err = apr_file_open(&apr_file, fname,
APR_CREATE | APR_READ | APR_WRITE,
APR_OS_DEFAULT, pool);
@@ -3659,7 +3754,11 @@ svn_swig_py_auth_gnome_keyring_unlock_prompt_func(char
**keyring_passwd,
}
else
{
- *keyring_passwd = make_string_from_ob(result, pool);
+ *keyring_passwd = make_string_from_ob_maybe_null(result, pool);
+ if (PyErr_Occurred())
+ {
+ err = callback_exception_error();
+ }
Py_DECREF(result);
}
diff --git a/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
b/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
index 89e7c53c28..37f0975846 100644
--- a/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
+++ b/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
@@ -107,6 +107,10 @@ void svn_swig_py_svn_exception(svn_error_t *err);
+/* helper function to get char * representation of bytes/str object */
+char *svn_swig_py_string_to_cstring(PyObject *input, int maybe_null,
+ const char * funcsym, const char * argsym);
+
/* helper function to convert an apr_hash_t* (char* -> svnstring_t*) to
a Python dict */
PyObject *svn_swig_py_prophash_to_dict(apr_hash_t *hash);
diff --git a/subversion/bindings/swig/python/tests/client.py
b/subversion/bindings/swig/python/tests/client.py
index 88f7fcf5e4..54c50f569f 100644
--- a/subversion/bindings/swig/python/tests/client.py
+++ b/subversion/bindings/swig/python/tests/client.py
@@ -52,6 +52,10 @@ class SubversionClientTestCase(unittest.TestCase):
"""An implementation of svn_log_entry_receiver_t."""
self.received_revisions.append(log_entry.revision)
+ def log_entry_receiver_whole(self, log_entry, pool):
+ """An implementation of svn_log_entry_receiver_t, holds whole log
entries."""
+ self.received_log_entries.append(log_entry)
+
def setUp(self):
"""Set up authentication and client context"""
self.client_ctx = client.svn_client_create_context()
@@ -243,6 +247,29 @@ class SubversionClientTestCase(unittest.TestCase):
self.assertEqual(self.received_revisions, list(range(0, 5)))
+ def test_log5_revprops(self):
+ """Test svn_client_log5 revprops (for typemap(in) apr_array_t
*STRINGLIST)"""
+ directory = urljoin(self.repos_uri+b"/", b"trunk/dir1")
+ start = core.svn_opt_revision_t()
+ end = core.svn_opt_revision_t()
+ core.svn_opt_parse_revision(start, end, b"4:0")
+ rev_range = core.svn_opt_revision_range_t()
+ rev_range.start = start
+ rev_range.end = end
+
+ self.received_log_entries = []
+
+ # (Python 3: pass tuple of bytes and str mixture as revprops argment)
+ client.log5((directory,), start, (rev_range,), 1, True, False, False,
+ ('svn:author', b'svn:log'),
+ self.log_entry_receiver_whole, self.client_ctx)
+ self.assertEqual(len(self.received_log_entries), 1)
+ revprops = self.received_log_entries[0].revprops
+ self.assertEqual(revprops[b'svn:log'], b"More directories.")
+ self.assertEqual(revprops[b'svn:author'], b"john")
+ with self.assertRaises(KeyError):
+ commit_date = revprops['svn:date']
+
def test_uuid_from_url(self):
"""Test svn_client_uuid_from_url on a file:// URL"""
self.assertTrue(isinstance(
diff --git a/subversion/bindings/swig/python/tests/core.py
b/subversion/bindings/swig/python/tests/core.py
index 6370fa5e1e..9bd82d6535 100644
--- a/subversion/bindings/swig/python/tests/core.py
+++ b/subversion/bindings/swig/python/tests/core.py
@@ -21,6 +21,9 @@
import unittest
import os
import tempfile
+import sys
+
+IS_PY3 = sys.version_info[0] >= 3
import svn.core, svn.client
import utils
@@ -220,13 +223,52 @@ class SubversionCoreTestCase(unittest.TestCase):
svn.core.svn_stream_close(stream)
def test_stream_write_exception(self):
- ostr_unicode = b'Python'.decode()
stream = svn.core.svn_stream_empty()
with self.assertRaises(TypeError):
- svn.core.svn_stream_write(stream, ostr_unicode)
+ svn.core.svn_stream_write(stream, 16)
+ # Check UnicodeEncodeError which can be caused only in Python 3
+ if IS_PY3:
+ o1_str = b'Python\x00\xa4\xd1\xa4\xa4\xa4\xbd\xa4\xf3\r\n'
+ ostr_unicode = o1_str.decode('ascii', 'surrogateescape')
+ with self.assertRaises(UnicodeEncodeError):
+ svn.core.svn_stream_write(stream, ostr_unicode)
svn.core.svn_stream_close(stream)
- def test_stream_write(self):
+ @unittest.skipUnless(IS_PY3, "test for Python 3 only")
+ def test_stream_write_str(self):
+ o1_str = 'Python\x00\xa4\xd1\xa4\xa4\xa4\xbd\xa4\xf3\r\n'
+ o2_str = ('subVersioN\x00'
+ '\xa4\xb5\xa4\xd6\xa4\xd0\xa1\xbc\xa4\xb8\xa4\xe7\xa4\xf3\n')
+ o3_str = 'swig\x00\xa4\xb9\xa4\xa6\xa4\xa3\xa4\xb0\rend'
+ out_str = o1_str + o2_str + o3_str
+ rewrite_str = 'Subversion'
+ fd, fname = tempfile.mkstemp()
+ os.close(fd)
+ try:
+ stream = svn.core.svn_stream_from_aprfile2(fname, False)
+ self.assertEqual(svn.core.svn_stream_write(stream, out_str),
+ len(out_str.encode('UTF-8')))
+ svn.core.svn_stream_seek(stream, None)
+ self.assertEqual(svn.core.svn_stream_read_full(stream, 4096),
+ out_str.encode('UTF-8'))
+ svn.core.svn_stream_seek(stream, None)
+ svn.core.svn_stream_skip(stream, len(o1_str.encode('UTF-8')))
+ self.assertEqual(svn.core.svn_stream_write(stream, rewrite_str),
+ len(rewrite_str.encode('UTF-8')))
+ svn.core.svn_stream_seek(stream, None)
+ self.assertEqual(
+ svn.core.svn_stream_read_full(stream, 4096),
+ (o1_str + rewrite_str
+ + o2_str[len(rewrite_str.encode('UTF-8')):]
+ + o3_str ).encode('UTF-8'))
+ svn.core.svn_stream_close(stream)
+ finally:
+ try:
+ os.remove(fname)
+ except OSError:
+ pass
+
+ def test_stream_write_bytes(self):
o1_str = b'Python\x00\xa4\xd1\xa4\xa4\xa4\xbd\xa4\xf3\r\n'
o2_str = (b'subVersioN\x00'
b'\xa4\xb5\xa4\xd6\xa4\xd0\xa1\xbc\xa4\xb8\xa4\xe7\xa4\xf3\n')
diff --git a/subversion/bindings/swig/python/tests/run_all.py
b/subversion/bindings/swig/python/tests/run_all.py
index 5cfd9d7536..3a042e012c 100644
--- a/subversion/bindings/swig/python/tests/run_all.py
+++ b/subversion/bindings/swig/python/tests/run_all.py
@@ -21,7 +21,7 @@
import sys
import unittest, setup_path
import mergeinfo, core, client, delta, checksum, pool, fs, ra, wc, repository,
\
- auth, trac.versioncontrol.tests
+ auth, trac.versioncontrol.tests, typemap
from svn.core import svn_cache_config_get, svn_cache_config_set
# Run all tests
@@ -47,6 +47,7 @@ def suite():
s.addTest(repository.suite())
s.addTest(auth.suite())
s.addTest(trac.versioncontrol.tests.suite())
+ s.addTest(typemap.suite())
return s
if __name__ == '__main__':
diff --git a/subversion/bindings/swig/python/tests/typemap.py
b/subversion/bindings/swig/python/tests/typemap.py
new file mode 100644
index 0000000000..0e69602b6a
--- /dev/null
+++ b/subversion/bindings/swig/python/tests/typemap.py
@@ -0,0 +1,108 @@
+#
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+#
+import unittest
+import os
+import tempfile
+import sys
+
+IS_PY3 = sys.version_info[0] >= 3
+
+import svn.core
+
+class SubversionTypemapTestCase(unittest.TestCase):
+ """Test cases for the SWIG typemaps argments and return values transration"""
+
+ def test_char_ptr_in(self):
+ """Check %typemap(in) IN_STRING works correctly"""
+ self.assertEqual(svn.core.svn_path_canonicalize(b'foo'), b'foo')
+ self.assertEqual(svn.core.svn_dirent_join(b'foo', 'bar'), b'foo/bar')
+ with self.assertRaises(TypeError) as cm:
+ svn.core.svn_dirent_join(None, b'bar')
+ self.assertEqual(str(cm.exception),
+ "svn_dirent_join() argument base must be %s,"
+ " not %s" % ("bytes or str" if IS_PY3 else "string",
+ None.__class__.__name__))
+ with self.assertRaises(TypeError) as cm:
+ svn.core.svn_dirent_join(b'foo', self)
+ self.assertEqual(str(cm.exception),
+ "svn_dirent_join() argument component must be %s,"
+ " not %s" % ("bytes or str" if IS_PY3 else "string",
+ self.__class__.__name__))
+ with self.assertRaises(TypeError) as cm:
+ svn.core.svn_dirent_join('foo', 10)
+ self.assertEqual(str(cm.exception),
+ "svn_dirent_join() argument component must be %s,"
+ " not int" % ("bytes or str" if IS_PY3 else "string"))
+
+ def test_char_ptr_may_be_null(self):
+ """Check %typemap(in) IN_STRING works correctly"""
+ cfg = svn.core.svn_config_create2(False, False)
+ self.assertEqual(svn.core.svn_config_get(cfg, b'foo', b'bar', b'baz'),
+ b'baz')
+ self.assertEqual(svn.core.svn_config_get(cfg, b'foo', b'bar', 'baz'),
+ b'baz')
+ self.assertIsNone(svn.core.svn_config_get(cfg, b'foo', b'bar', None))
+ with self.assertRaises(TypeError) as cm:
+ svn.core.svn_config_get(cfg, b'foo', b'bar', self)
+ self.assertEqual(str(cm.exception),
+ "svn_config_get() argument default_value"
+ " must be %s or None, not %s"
+ % ("bytes or str" if IS_PY3 else "string",
+ self.__class__.__name__))
+
+ def test_make_string_from_ob(self):
+ """Check test_make_string_from_ob and test_make_svn_string_from_ob
+ work correctly"""
+ target_props = { b'a' : b'foo',
+ b'b' : 'foo',
+ 'c' : b'' }
+ source_props = { b'a' : '',
+ 'b' : 'bar',
+ b'c' : b'baz' }
+ expected = { b'a' : b'',
+ b'b' : b'bar',
+ b'c' : b'baz' }
+ self.assertEqual(svn.core.svn_prop_diffs(source_props, target_props),
+ expected)
+
+ @unittest.skip('incomplete implementation of svn.core.svn_prop_diffs()')
+ def test_prophash_from_dict_null_value(self):
+ # FIXME: Current implementation of svn_swig_py_prophash_from_dict()
+ # does not allow None as prop value, however svn_prop_diffs() allows
+ target_props = { 'a' : 'foo',
+ 'b' : 'foo',
+ 'c' : None }
+ source_props = { 'a' : None,
+ 'b' : 'bar',
+ 'c' : 'baz' }
+ expected = { b'a' : b'foo',
+ b'b' : b'foo',
+ b'c' : None }
+ self.assertEqual(svn.core.svn_prop_diffs(source_props, target_props),
+ expected)
+
+def suite():
+ return unittest.defaultTestLoader.loadTestsFromTestCase(
+ SubversionTypemapTestCase)
+
+if __name__ == '__main__':
+ runner = unittest.TextTestRunner()
+ runner.run(suite())