In the ctypes-python bindings, when I try to use the Stream class (either via the RemoteRepository.cat() method, or just on its own), I find that the program crashes at the moment the C code in svn_stream_write() calls the callback function which is provided by subversion/bindings/ctypes-python/csvn/types.py:240:
> class Stream(object): > > def __init__(self, buffer, disown=False): > [...] > svn_stream_set_read(self.stream, svn_read_fn_t(self._read)) > > def _write(self, baton, data, l): > [...] So, using the attached patch (with or without the experimental edits it contains in _read() and _write()), I find the tests suite bombs out without reporting any results. My usage of GDB and the Python debugger pdb indicates that _write() is never reached. If I write a test using a ctypes callback type with the C standard library's qsort(), as shown in the Python ctypes module documentation <http://docs.python.org/library/ctypes.html#callback-functions>, that works fine for me. I wonder if something is wrong with my building and linking of ctypesgen or the ctypes-python bindings, but on the other hand the obvious bug in Stream._read() where 'string' is referenced instead of 's' (see my patch) makes me wonder if this class has ever worked. Another data point: the log message receiver callback seems to work fine for me, as tested in subversion/bindings/ctypes-python/test/remoterepos.py:test_log(). - Julian
In the ctypes-python bindings, fix the Stream class and add tests and doc strings. ### This is just an attempt, and not yet correct. * subversion/bindings/ctypes-python/csvn/types.py (Stream._read): This was totally broken: at the very least it should say 's' instead of 'string'. Try using plain Python code instead of 'memmove'; this might be totally wrong. Another guess: maybe it should be 'memmove(buffer.raw, create_string_buffer(s, len(s)), len(s))'. (Stream._write): Try using plain Python code instead of special ctypes code, for symmetry and simplicity; this might be totally wrong. (Stream): Add doc strings to the methods, as their argument types are a little unclear, especially 'buffer' which has two different meanings. * subversion/bindings/ctypes-python/test/svntypes.py (StreamTestCase): New test case class containing one simple test. (suite): Include it. * subversion/bindings/ctypes-python/test/remoterepos.py (RemoteRepositoryTestCase): Add a test for 'cat'. (suite): ### Run it instead of the normal tests, just for debugging. --This line, and those below, will be ignored-- Index: subversion/bindings/ctypes-python/csvn/types.py =================================================================== --- subversion/bindings/ctypes-python/csvn/types.py (revision 1157788) +++ subversion/bindings/ctypes-python/csvn/types.py (working copy) @@ -246,13 +262,13 @@ class Stream(object): def _read(self, baton, buffer, l): s = self.buffer.read(l[0]) - memmove(buffer, string, len(s)) + buffer.data = s l[0] = len(s) return SVN_NO_ERROR def _write(self, baton, data, l): - s = string_at(data.raw, l[0]) - self.buffer.write(s) + s = str(data) + self.buffer.write(s[:l[0]]) return SVN_NO_ERROR def _close(self, baton): Index: subversion/bindings/ctypes-python/csvn/types.py =================================================================== --- subversion/bindings/ctypes-python/csvn/types.py (revision 1157734) +++ subversion/bindings/ctypes-python/csvn/types.py (working copy) @@ -232,7 +232,9 @@ class APRFile(object): class Stream(object): def __init__(self, buffer, disown=False): - """Create a stream which wraps a Python file or file-like object""" + """Create a stream which wraps the Python file or file-like object + BUFFER. If DISOWN is true, closing this stream won't close + BUFFER.""" self.pool = Pool() self.buffer = buffer @@ -245,17 +247,23 @@ class Stream(object): _as_parameter_ = property(fget=lambda self: self.stream) def _read(self, baton, buffer, l): + """Read (*L) bytes from self.buffer into BUFFER. Implements svn_read_fn_t. + BATON is unused; BUFFER is a String; L is a POINTER(apr_size_t).""" s = self.buffer.read(l[0]) memmove(buffer, string, len(s)) l[0] = len(s) return SVN_NO_ERROR def _write(self, baton, data, l): + """Write (*L) bytes into self.buffer from DATA. Implements svn_write_fn_t. + BATON is unused; DATA is a String; L is a POINTER(apr_size_t).""" s = string_at(data.raw, l[0]) self.buffer.write(s) return SVN_NO_ERROR def _close(self, baton): + """Close this stream and the underlying file-like object + 'self.buffer'. Implements svn_close_fn_t.""" self.buffer.close() return SVN_NO_ERROR Index: subversion/bindings/ctypes-python/test/svntypes.py =================================================================== --- subversion/bindings/ctypes-python/test/svntypes.py (revision 1157904) +++ subversion/bindings/ctypes-python/test/svntypes.py (working copy) @@ -23,6 +23,15 @@ from csvn.core import * import csvn.types as _types from csvn.types import SvnDate, Hash, Array, APRFile, Stream, SvnStringPtr +from sys import version_info # For Python version check +if version_info[0] >= 3: + # Python >=3.0 + from io import StringIO +else: + # Python <3.0 + from StringIO import StringIO + + class SvnDateTestCase(unittest.TestCase): def test_as_apr_time_t(self): @@ -86,11 +95,23 @@ class ArrayTestCase(unittest.TestCase): self.assertEqual(self.svnarray[2], "vici") self.assertEqual(self.svnarray[1], "vidi") +class StreamTestCase(unittest.TestCase): + + def test_stream(self): + buf = StringIO() + s = Stream(buf, disown=True) + l = apr_size_t(5) + svn_stream_write(s, 'Hello', l) + self.assertEqual(l, 5) + buf.seek(0) + self.assertEqual(buf.read(), 'Hello') + def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(SvnDateTestCase, 'test')) suite.addTest(unittest.makeSuite(HashTestCase, 'test')) suite.addTest(unittest.makeSuite(ArrayTestCase, 'test')) + suite.addTest(unittest.makeSuite(StreamTestCase, 'test')) return suite if __name__ == '__main__': Index: subversion/bindings/ctypes-python/test/remoterepos.py =================================================================== --- subversion/bindings/ctypes-python/test/remoterepos.py (revision 1157904) +++ subversion/bindings/ctypes-python/test/remoterepos.py (working copy) @@ -106,6 +106,15 @@ class RemoteRepositoryTestCase(unittest. for path in found: self.svn_dirent_t_assert_equal(found[path], expected[path]) + ### This test does not merely fail, it bombs right out of the test suite + ### at the point where the C svn_stream_write() calls the Python + ### callback. + def zzz_test_cat(self): + buf = StringIO() + self.repos.cat(buf, "trunk/README.txt") + buf.seek(0) + self.assertEqual(buf.read(), "") + def test_info(self): e = svn_dirent_t(kind=svn_node_file, size=159, has_props=True, created_rev=9, last_author=String('bruce')) @@ -203,7 +212,7 @@ class RemoteRepositoryTestCase(unittest. self.assertEqual(commit_info.revision, 10) def suite(): - return unittest.makeSuite(RemoteRepositoryTestCase, 'test') + return unittest.makeSuite(RemoteRepositoryTestCase, 'zzz_test') if __name__ == '__main__': runner = unittest.TextTestRunner()