Author: futatuki
Date: Thu Feb 14 16:48:40 2019
New Revision: 1853592

URL: http://svn.apache.org/viewvc?rev=1853592&view=rev
Log:
On branch swig-py3: accept both of bytes/str for input char * arguments

* Replace typemap(in) for char * using 'parse' modifier with one using
 function allows both of bytes/str in py2/py3 in libsvn_swig_py library
* Fix functions to convert Python objects into char pointer to accept
 both of bytes/str object in py2/py3
* Fix to accept None as representation of NULL value on conversion
  prop dict into apr_hash_t * and apr_array_header_t * of svn_props_t * 

[In subversion/bindings/swig]

* core.i (%typemap(in) (const char *data, apr_size_t *len): Allow str
 as well as bytes 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
  - Don't raise TypeError exception because all callers don't expect it
 (make_string_from_ob_maybe_null, make_svn_string_from_ob_maybe_null):
  New function same as make_string_from_ob() and make_svn_string_fromob()
  but allows None input represents NULL value and raise TypeError
  if input value don't have appropriate type
 (svn_swig_py_stringhash_from_dict, svn_swig_py_mergeinfo_from_dict,
  svn_swig_py_proparray_from_dict, svn_swig_py_prophash_from_dict,
  svn_swig_py_path_rev_hash_from_dict,
  svn_swig_py_struct_ptr_hash_from_dict): separate check of key conversion
  result and value conversion result
 (svn_swig_py_proparray_from_dict, svn_swig_py_prophash_from_dict):
  allow NULL for prop values
 (svn_swig_py_unwrap_string):
  - Allow str as well as bytes for source argument
 (svn_swig_py_make_file):
  - Allow str as well as bytes for py_file argument as file path
 (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
  - Add case to be expected to cause 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): New unittest subclass for unit test about
  typemaps 
 (SubversionTypemapTestCase.test_char_ptr_in): New test case
 (SubversionTypemapTestCase.test_char_ptr_in_unicode_exception):
  New test case
 (SubversionTypemapTestCase.test_char_ptr_may_be_null): New test case
 (SubversionTypemapTestCase.test_char_ptr_may_be_null_unicode_exception):
  New test case
 (SubversionTypemapTestCase.test_make_string_from_ob): New test case
 (SubversionTypemapTestCase.test_prophash_from_dict_null_value):
  New test case
 (SubversionTypemapTestCase.test_make-string_ob_unicode_exception):
  New test case
 (SubversionTypemapTestCase.test_make_svn_string_ob_unicode_exception):
  New test case
 (suite): New function to drive SubversionTypemapTestCase

Added:
    
subversion/branches/swig-py3/subversion/bindings/swig/python/tests/typemap.py   
(with props)
Modified:
    subversion/branches/swig-py3/subversion/bindings/swig/core.i
    subversion/branches/swig-py3/subversion/bindings/swig/include/svn_global.swg
    subversion/branches/swig-py3/subversion/bindings/swig/include/svn_string.swg
    subversion/branches/swig-py3/subversion/bindings/swig/include/svn_types.swg
    
subversion/branches/swig-py3/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
    
subversion/branches/swig-py3/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
    subversion/branches/swig-py3/subversion/bindings/swig/python/tests/client.py
    subversion/branches/swig-py3/subversion/bindings/swig/python/tests/core.py
    
subversion/branches/swig-py3/subversion/bindings/swig/python/tests/run_all.py

Modified: subversion/branches/swig-py3/subversion/bindings/swig/core.i
URL: 
http://svn.apache.org/viewvc/subversion/branches/swig-py3/subversion/bindings/swig/core.i?rev=1853592&r1=1853591&r2=1853592&view=diff
==============================================================================
--- subversion/branches/swig-py3/subversion/bindings/swig/core.i (original)
+++ subversion/branches/swig-py3/subversion/bindings/swig/core.i Thu Feb 14 
16:48:40 2019
@@ -442,18 +442,24 @@
 */
 #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 (PyBytes_AsStringAndSize($input, &tmpdata, &length) == -1) {
+    else if (PyUnicode_Check($input)) {
+        $1 = (char *)PyStr_AsUTF8AndSize($input, &length);
+        if (PyErr_Occurred()) {
+            SWIG_fail;
+        }
+    }
+    else {
+        PyErr_SetString(PyExc_TypeError,
+                        "expecting a bytes or str object for the buffer");
         SWIG_fail;
     }
     temp = ($*2_type)length;
-    $1 = tmpdata;
     $2 = ($2_ltype)&temp;
 }
 #endif

Modified: 
subversion/branches/swig-py3/subversion/bindings/swig/include/svn_global.swg
URL: 
http://svn.apache.org/viewvc/subversion/branches/swig-py3/subversion/bindings/swig/include/svn_global.swg?rev=1853592&r1=1853591&r2=1853592&view=diff
==============================================================================
--- 
subversion/branches/swig-py3/subversion/bindings/swig/include/svn_global.swg 
(original)
+++ 
subversion/branches/swig-py3/subversion/bindings/swig/include/svn_global.swg 
Thu Feb 14 16:48:40 2019
@@ -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

Modified: 
subversion/branches/swig-py3/subversion/bindings/swig/include/svn_string.swg
URL: 
http://svn.apache.org/viewvc/subversion/branches/swig-py3/subversion/bindings/swig/include/svn_string.swg?rev=1853592&r1=1853591&r2=1853592&view=diff
==============================================================================
--- 
subversion/branches/swig-py3/subversion/bindings/swig/include/svn_string.swg 
(original)
+++ 
subversion/branches/swig-py3/subversion/bindings/swig/include/svn_string.swg 
Thu Feb 14 16:48:40 2019
@@ -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 *'
 */

Modified: 
subversion/branches/swig-py3/subversion/bindings/swig/include/svn_types.swg
URL: 
http://svn.apache.org/viewvc/subversion/branches/swig-py3/subversion/bindings/swig/include/svn_types.swg?rev=1853592&r1=1853591&r2=1853592&view=diff
==============================================================================
--- subversion/branches/swig-py3/subversion/bindings/swig/include/svn_types.swg 
(original)
+++ subversion/branches/swig-py3/subversion/bindings/swig/include/svn_types.swg 
Thu Feb 14 16:48:40 2019
@@ -348,12 +348,8 @@ svn_ ## TYPE ## _swig_rb_closed(VALUE se
 #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
 

Modified: 
subversion/branches/swig-py3/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/swig-py3/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c?rev=1853592&r1=1853591&r2=1853592&view=diff
==============================================================================
--- 
subversion/branches/swig-py3/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
 (original)
+++ 
subversion/branches/swig-py3/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
 Thu Feb 14 16:48:40 2019
@@ -506,6 +506,36 @@ void svn_swig_py_svn_exception(svn_error
 
 /*** Helper/Conversion Routines ***/
 
+/* Function to get char * representation of bytes/str object. This is
+   the replacement of typemap(in, parse="s") and typemap(in, parse="z")
+   to accept both of bytes object and str object, and it assumes to be
+   used from those typemaps only.
+   Note: type of return value should be char const *, however, as SWIG
+   produces variables for C function without 'const' modifier, to avoid
+   ton of cast in SWIG produced C code we drop it from return value
+   types as well  */
+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);
+      }
+    else if (PyUnicode_Check(input))
+      {
+        retval = (char *)PyStr_AsUTF8(input);
+      }
+    else if (input != Py_None || ! maybe_null)
+      {
+        PyErr_Format(PyExc_TypeError,
+                     "%s() argument %s must be bytes or str%s, not %s",
+                     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,32 +570,89 @@ static PyObject *make_ob_error(svn_error
 
 /***/
 
+static void svn_swig_py_string_type_exception(int maybe_null)
+{
+  PyErr_Format(PyExc_TypeError, "not a bytes or a str%s",
+               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)
 {
+  /* caller should not expect to raise TypeError: check return value
+     whether it is NULL or not, if needed */
+  if (PyBytes_Check(ob))
+    {
+      return apr_pstrdup(pool, PyBytes_AsString(ob));
+    }
+  if (PyUnicode_Check(ob))
+    {
+      /* PyStr_AsUTF8() may cause UnicodeEncodeError,
+         but apr_pstrdup() allows NULL for s */
+      return apr_pstrdup(pool, PyStr_AsUTF8(ob));
+    }
+  return NULL;
+}
+
+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))
     {
-      PyErr_SetString(PyExc_TypeError, "not a bytes object");
       return NULL;
     }
-  return apr_pstrdup(pool, PyBytes_AsString(ob));
+  retval = make_string_from_ob(ob, pool);
+  if (!retval)
+    {
+      if (!PyErr_Occurred())
+        {
+          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))
+    {
+      return svn_string_create(PyBytes_AsString(ob), pool);
+    }
+  if (PyUnicode_Check(ob))
+    {
+      /* PyStr_AsUTF8() may cause UnicodeEncodeError,
+         and svn_string_create() does not allows NULL for cstring */
+      const char *obstr = PyStr_AsUTF8(ob);
+      if (obstr)
+        {
+          return svn_string_create(obstr, pool);
+        }
+    }
+  return NULL;
+}
+
+static svn_string_t *make_svn_string_from_ob_maybe_null(PyObject *ob,
+                                                        apr_pool_t *pool)
+{
+  svn_string_t * retval;
   if (ob == Py_None)
-    return NULL;
-  if (! PyBytes_Check(ob))
     {
-      PyErr_SetString(PyExc_TypeError, "not a bytes object");
       return NULL;
     }
-  return svn_string_create(PyBytes_AsString(ob), pool);
+  retval = make_svn_string_from_ob(ob, pool);
+  if (!retval)
+    {
+      if (!PyErr_Occurred())
+        {
+          svn_swig_py_string_type_exception(TRUE);
+        }
+    }
+  return retval;
 }
 
-
 /***/
 
 static PyObject *convert_hash(apr_hash_t *hash,
@@ -1055,6 +1142,9 @@ PyObject *svn_swig_py_changed_path2_hash
   return dict;
 }
 
+#define TYPE_ERROR_DICT_STRING_KEY \
+        "dictionary keys aren't bytes or str objects"
+
 apr_hash_t *svn_swig_py_stringhash_from_dict(PyObject *dict,
                                              apr_pool_t *pool)
 {
@@ -1079,11 +1169,18 @@ apr_hash_t *svn_swig_py_stringhash_from_
       PyObject *key = PyList_GetItem(keys, i);
       PyObject *value = PyDict_GetItem(dict, key);
       const char *propname = make_string_from_ob(key, pool);
-      const char *propval = make_string_from_ob(value, pool);
-      if (! (propname && propval))
+      if (!propname)
+        {
+          if (!PyErr_Occurred())
+            {
+              PyErr_SetString(PyExc_TypeError, TYPE_ERROR_DICT_STRING_KEY);
+            }
+          Py_DECREF(keys);
+          return NULL;
+        }
+      const char *propval = make_string_from_ob_maybe_null(value, pool);
+      if (PyErr_Occurred())
         {
-          PyErr_SetString(PyExc_TypeError,
-                          "dictionary keys/values aren't strings");
           Py_DECREF(keys);
           return NULL;
         }
@@ -1116,6 +1213,15 @@ apr_hash_t *svn_swig_py_mergeinfo_from_d
       PyObject *key = PyList_GetItem(keys, i);
       PyObject *value = PyDict_GetItem(dict, key);
       const char *pathname = make_string_from_ob(key, pool);
+      if (!pathname)
+        {
+          if (!PyErr_Occurred())
+            {
+              PyErr_SetString(PyExc_TypeError, TYPE_ERROR_DICT_STRING_KEY);
+            }
+          Py_DECREF(keys);
+          return NULL;
+        }
       const svn_rangelist_t *ranges = svn_swig_py_seq_to_array(value,
         sizeof(const svn_merge_range_t *),
         svn_swig_py_unwrap_struct_ptr,
@@ -1123,10 +1229,10 @@ apr_hash_t *svn_swig_py_mergeinfo_from_d
         pool
       );
 
-      if (! (pathname && ranges))
+      if (!ranges)
         {
           PyErr_SetString(PyExc_TypeError,
-                          "dictionary keys aren't strings or values aren't 
svn_merge_range_t *'s");
+                          "dictionary values aren't svn_merge_range_t *'s");
           Py_DECREF(keys);
           return NULL;
         }
@@ -1161,11 +1267,18 @@ apr_array_header_t *svn_swig_py_proparra
       PyObject *value = PyDict_GetItem(dict, key);
       svn_prop_t *prop = apr_palloc(pool, sizeof(*prop));
       prop->name = make_string_from_ob(key, pool);
-      prop->value = make_svn_string_from_ob(value, pool);
-      if (! (prop->name && prop->value))
+      if (! prop->name)
+        {
+          if (!PyErr_Occurred())
+            {
+              PyErr_SetString(PyExc_TypeError, TYPE_ERROR_DICT_STRING_KEY);
+            }
+          Py_DECREF(keys);
+          return NULL;
+        }
+      prop->value = make_svn_string_from_ob_maybe_null(value, pool);
+      if (PyErr_Occurred())
         {
-          PyErr_SetString(PyExc_TypeError,
-                          "dictionary keys/values aren't strings");
           Py_DECREF(keys);
           return NULL;
         }
@@ -1199,11 +1312,18 @@ apr_hash_t *svn_swig_py_prophash_from_di
       PyObject *key = PyList_GetItem(keys, i);
       PyObject *value = PyDict_GetItem(dict, key);
       const char *propname = make_string_from_ob(key, pool);
-      svn_string_t *propval = make_svn_string_from_ob(value, pool);
-      if (! (propname && propval))
+      if (!propname)
+        {
+          if (!PyErr_Occurred())
+            {
+              PyErr_SetString(PyExc_TypeError, TYPE_ERROR_DICT_STRING_KEY);
+            }
+          Py_DECREF(keys);
+          return NULL;
+        }
+      svn_string_t *propval = make_svn_string_from_ob_maybe_null(value, pool);
+      if (PyErr_Occurred())
         {
-          PyErr_SetString(PyExc_TypeError,
-                          "dictionary keys/values aren't strings");
           Py_DECREF(keys);
           return NULL;
         }
@@ -1241,8 +1361,10 @@ apr_hash_t *svn_swig_py_path_revs_hash_f
 
       if (!(path))
         {
-          PyErr_SetString(PyExc_TypeError,
-                          "dictionary keys aren't strings");
+          if (!PyErr_Occurred())
+            {
+              PyErr_SetString(PyExc_TypeError, TYPE_ERROR_DICT_STRING_KEY);
+            }
           Py_DECREF(keys);
           return NULL;
         }
@@ -1296,8 +1418,10 @@ apr_hash_t *svn_swig_py_struct_ptr_hash_
 
       if (!c_key)
         {
-          PyErr_SetString(PyExc_TypeError,
-                          "dictionary keys aren't strings");
+          if (!PyErr_Occurred())
+            {
+              PyErr_SetString(PyExc_TypeError, TYPE_ERROR_DICT_STRING_KEY);
+            }
           Py_DECREF(keys);
           return NULL;
         }
@@ -1321,8 +1445,21 @@ svn_swig_py_unwrap_string(PyObject *sour
                           void *baton)
 {
     const char **ptr_dest = destination;
-    *ptr_dest = PyBytes_AsString(source);
-
+    if (PyBytes_Check(source))
+      {
+        *ptr_dest = PyBytes_AsString(source);
+      }
+    else if (PyUnicode_Check(source))
+      {
+        *ptr_dest = PyStr_AsUTF8(source);
+      }
+    else
+      {
+        PyErr_Format(PyExc_TypeError,
+                        "Expected bytes or str object, %s found",
+                        Py_TYPE(source)->tp_name);
+        *ptr_dest = NULL;
+      }
     if (*ptr_dest != NULL)
         return 0;
     else
@@ -1494,7 +1631,7 @@ commit_item_array_to_list(const apr_arra
 }
 
 
- 
+
 /*** Errors ***/
 
 /* Convert a given SubversionException to an svn_error_t. On failure returns
@@ -2546,14 +2683,23 @@ apr_file_t *svn_swig_py_make_file(PyObje
 {
   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);
+    }
+  else if (PyUnicode_Check(py_file))
+    {
+      fname = PyStr_AsUTF8(py_file);
+    }
+  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);
@@ -3707,7 +3853,11 @@ svn_swig_py_auth_gnome_keyring_unlock_pr
     }
   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);
     }
 

Modified: 
subversion/branches/swig-py3/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
URL: 
http://svn.apache.org/viewvc/subversion/branches/swig-py3/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h?rev=1853592&r1=1853591&r2=1853592&view=diff
==============================================================================
--- 
subversion/branches/swig-py3/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
 (original)
+++ 
subversion/branches/swig-py3/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
 Thu Feb 14 16:48:40 2019
@@ -107,6 +107,17 @@ void svn_swig_py_svn_exception(svn_error
 
 
 
+/* Function to get char * representation of bytes/str object. This is
+   the replacement of typemap(in, parse="s") and typemap(in, parse="z")
+   to accept both of bytes object and str object, and it assumes to be
+   used from those typemaps only.
+   Note: type of return value should be char const *, however, as SWIG
+   produces variables for C function without 'const' modifier, to avoid
+   ton of cast in SWIG produced C code we drop it from return value
+   types as well  */
+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);

Modified: 
subversion/branches/swig-py3/subversion/bindings/swig/python/tests/client.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/swig-py3/subversion/bindings/swig/python/tests/client.py?rev=1853592&r1=1853591&r2=1853592&view=diff
==============================================================================
--- 
subversion/branches/swig-py3/subversion/bindings/swig/python/tests/client.py 
(original)
+++ 
subversion/branches/swig-py3/subversion/bindings/swig/python/tests/client.py 
Thu Feb 14 16:48:40 2019
@@ -52,6 +52,10 @@ class SubversionClientTestCase(unittest.
     """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,33 @@ class SubversionClientTestCase(unittest.
 
     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']
+    with self.assertRaises(UnicodeEncodeError):
+      client.log5((directory,), start, (rev_range,), 1, True, False, False,
+                  (u'svn:\udc61uthor', b'svn:log'),
+                  self.log_entry_receiver_whole, self.client_ctx)
+
   def test_uuid_from_url(self):
     """Test svn_client_uuid_from_url on a file:// URL"""
     self.assertTrue(isinstance(

Modified: 
subversion/branches/swig-py3/subversion/bindings/swig/python/tests/core.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/swig-py3/subversion/bindings/swig/python/tests/core.py?rev=1853592&r1=1853591&r2=1853592&view=diff
==============================================================================
--- subversion/branches/swig-py3/subversion/bindings/swig/python/tests/core.py 
(original)
+++ subversion/branches/swig-py3/subversion/bindings/swig/python/tests/core.py 
Thu Feb 14 16:48:40 2019
@@ -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,55 @@ class SubversionCoreTestCase(unittest.Te
     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
+    # o1_str = b'Python\x00\xa4\xd1\xa4\xa4\xa4\xbd\xa4\xf3\r\n'
+    # ostr_unicode = o1_str.decode('ascii', 'surrogateescape')
+    ostr_unicode = (u'Python\x00'
+                    u'\udca4\udcd1\udca4\udca4\udca4\udcbd\udca4\udcf3\r\n')
+    with self.assertRaises(UnicodeEncodeError):
+      svn.core.svn_stream_write(stream, ostr_unicode)
     svn.core.svn_stream_close(stream)
 
-  def test_stream_write(self):
+  # As default codec of Python 2 is 'ascii', conversion from unicode to bytes 
+  # will be success only if all characters of target strings are in the range
+  # of \u0000 ~ \u007f.
+  @unittest.skipUnless(IS_PY3, "test for Python 3 only")
+  def test_stream_write_str(self):
+    o1_str = u'Python\x00\u3071\u3044\u305d\83093\r\n'
+    o2_str = u'subVersioN\x00\u3055\u3076\u3070\u30fc\u3058\u3087\u3093'
+    o3_str = u'swig\x00\u3059\u3046\u3043\u3050\rend'
+    out_str = o1_str + o2_str + o3_str
+    rewrite_str = u'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')

Modified: 
subversion/branches/swig-py3/subversion/bindings/swig/python/tests/run_all.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/swig-py3/subversion/bindings/swig/python/tests/run_all.py?rev=1853592&r1=1853591&r2=1853592&view=diff
==============================================================================
--- 
subversion/branches/swig-py3/subversion/bindings/swig/python/tests/run_all.py 
(original)
+++ 
subversion/branches/swig-py3/subversion/bindings/swig/python/tests/run_all.py 
Thu Feb 14 16:48:40 2019
@@ -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__':

Added: 
subversion/branches/swig-py3/subversion/bindings/swig/python/tests/typemap.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/swig-py3/subversion/bindings/swig/python/tests/typemap.py?rev=1853592&view=auto
==============================================================================
--- 
subversion/branches/swig-py3/subversion/bindings/swig/python/tests/typemap.py 
(added)
+++ 
subversion/branches/swig-py3/subversion/bindings/swig/python/tests/typemap.py 
Thu Feb 14 16:48:40 2019
@@ -0,0 +1,133 @@
+#
+#
+# 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 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"
+                     " bytes or str, not %s" % 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"
+                     " bytes or str, not %s" % 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"
+                     " bytes or str, not int")
+
+  def test_char_ptr_in_unicode_exception(self):
+    """Check %typemap(in) IN_STRING handles unicode encode error correctly"""
+    with self.assertRaises(UnicodeEncodeError):
+      svn.core.svn_dirent_join(b'foo', u'\udc62\udc61\udc72')
+
+  def test_char_ptr_may_be_null(self):
+    """Check %typemap(in) char *MAY_BE_NULL 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 bytes or str or None, not %s"
+                     % self.__class__.__name__)
+
+  def test_char_ptr_may_be_null_unicode_exception(self):
+    """Check %typemap(in) char *MAY_BE_NULL handles unicode encode error 
correctly"""
+    cfg = svn.core.svn_config_create2(False, False)
+    with self.assertRaises(UnicodeEncodeError):
+      svn.core.svn_config_get(cfg, u'f\udc6fo', b'bar', None)
+
+  def test_make_string_from_ob(self):
+    """Check make_string_from_ob and make_svn_string_from_ob work correctly"""
+    source_props = { b'a' : b'foo',
+                     b'b' :  'foo',
+                      'c' : b''     }
+    target_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(target_props, source_props),
+                     expected)
+
+  def test_prophash_from_dict_null_value(self):
+    """Check make_svn_string_from_ob_maybe_null work correctly"""
+    source_props = {  'a' :  'foo',
+                      'b' :  'foo',
+                      'c' : None    }
+    target_props = {  'a' : None,
+                      'b' :  'bar',
+                      'c' :  'baz'  }
+    expected     = { b'a' : None,
+                     b'b' : b'bar',
+                     b'c' : b'baz'  }
+    self.assertEqual(svn.core.svn_prop_diffs(target_props, source_props),
+                     expected)
+
+  def test_make_string_from_ob_unicode_exception(self):
+    """Check make_string_from_ob  handles unicode encode error correctly"""
+    source_props = { b'a'      : b'foo',
+                     b'b'      : u'foo',
+                     u'\udc63' : b''     }
+    target_props = { b'a'      : u'',
+                     u'b'      : u'bar',
+                     b'c'      : b'baz'  }
+    with self.assertRaises(UnicodeEncodeError):
+      svn.core.svn_prop_diffs(target_props, source_props)
+
+  def test_make_svn_string_from_ob_unicode_exception(self):
+    """Check make_string_from_ob handles unicode encode error correctly"""
+    source_props = { b'a' : b'foo',
+                     b'b' :  'foo',
+                     u'c' : b''     }
+    target_props = { b'a' : u'',
+                     u'b' : u'b\udc61r',
+                     b'c' : b'baz'  }
+    with self.assertRaises(UnicodeEncodeError):
+      svn.core.svn_prop_diffs(target_props, source_props)
+
+
+def suite():
+    return unittest.defaultTestLoader.loadTestsFromTestCase(
+      SubversionTypemapTestCase)
+
+if __name__ == '__main__':
+    runner = unittest.TextTestRunner()
+    runner.run(suite())

Propchange: 
subversion/branches/swig-py3/subversion/bindings/swig/python/tests/typemap.py
------------------------------------------------------------------------------
    svn:eol-style = native


Reply via email to