git: b8e6b2a99628 - main - devel/py-subversion: split test target, with fix tests in python >= 3.14
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Sun, 06 Sep 2026 14:17:49 UTC
The branch main has been updated by michaelo:
URL: https://cgit.FreeBSD.org/ports/commit/?id=b8e6b2a99628582839f56de7301796fba0aa0326
commit b8e6b2a99628582839f56de7301796fba0aa0326
Author: FUTATSUKI Yasuhito <futatuki@yf.bsdclub.org>
AuthorDate: 2026-07-04 17:26:23 +0000
Commit: Michael Osipov <michaelo@FreeBSD.org>
CommitDate: 2026-09-06 14:07:05 +0000
devel/py-subversion: split test target, with fix tests in python >= 3.14
To fix both of latest and LTS, add a patch to fix the tests
both in devel/subversion and devel/subversion-lts.
PR: 294183
MFH: 2026Q3
---
devel/py-subversion/Makefile | 8 +-
.../files/extra-patch-check-swig-py314 | 231 +++++++++++++++++++++
.../subversion/files/extra-patch-check-swig-py314 | 231 +++++++++++++++++++++
3 files changed, 467 insertions(+), 3 deletions(-)
diff --git a/devel/py-subversion/Makefile b/devel/py-subversion/Makefile
index 3841e4229632..7c7983ec4820 100644
--- a/devel/py-subversion/Makefile
+++ b/devel/py-subversion/Makefile
@@ -12,14 +12,15 @@ USE_PYTHON= flavors py3kplist
# There is bug in python bindings Makefile
MAKE_JOBS_UNSAFE=yes
-PORTREVISION_LATEST= 0
-PORTREVISION_LTS= 0
+PORTREVISION_LATEST= 1
+PORTREVISION_LTS= 1
SVN_BUILD_BINDINGS= yes
.include "${.CURDIR}/../../devel/subversion/Makefile.addons"
CATEGORIES+= python
+EXTRA_PATCHES+= ${PATCHDIR}/extra-patch-check-swig-py314
CONFIGURE_ARGS+= --with-swig-python=${PYTHON_CMD} \
--without-swig-perl \
@@ -28,7 +29,8 @@ CONFIGURE_ARGS+= --with-swig-python=${PYTHON_CMD} \
--without-kwallet \
--with-apxs=no
-ALL_TARGET= swig-py check-swig-py
+ALL_TARGET= swig-py
+TEST_TARGET= check-swig-py
INSTALL_TARGET= install-swig-py
.include <bsd.port.post.mk>
diff --git a/devel/subversion-lts/files/extra-patch-check-swig-py314 b/devel/subversion-lts/files/extra-patch-check-swig-py314
new file mode 100644
index 000000000000..5156311eb6d4
--- /dev/null
+++ b/devel/subversion-lts/files/extra-patch-check-swig-py314
@@ -0,0 +1,231 @@
+This patch was extracted by the command
+svn diff -c r1929147 --patch-compatible \
+ https://svn.apache.org/repos/asf/subversion/branches/1.14.x | tail -n +17
+And it can be applied only for Subversion < 1.15.0
+Index: subversion/bindings/swig/python/tests/mergeinfo.py
+===================================================================
+--- subversion/bindings/swig/python/tests/mergeinfo.py (revision 1929146)
++++ subversion/bindings/swig/python/tests/mergeinfo.py (revision 1929147)
+@@ -18,7 +18,7 @@
+ # under the License.
+ #
+ #
+-import unittest, os, sys, gc
++import unittest, os, sys, weakref, gc
+ from svn import core, repos, fs
+ import utils
+
+@@ -125,6 +125,9 @@
+ }
+ self.compare_mergeinfo_catalogs(mergeinfo, expected_mergeinfo)
+
++ @unittest.skipIf(utils.HAS_DEFERRED_REFCOUNT,
++ "Reference counting tests skipped because of deferred "
++ "reference counting")
+ def test_mergeinfo_leakage__incorrect_range_t_refcounts(self):
+ """Ensure that the ref counts on svn_merge_range_t objects returned by
+ svn_mergeinfo_parse() are correct."""
+@@ -138,7 +141,8 @@
+ # ....and now 3 (incref during iteration of each range object)
+
+ refcount = sys.getrefcount(r)
+- # ....and finally, 4 (getrefcount() also increfs)
++ # ....and finally, 4 (getrefcount() also increfs, unless deferred
++ # reference counting)
+ expected = 4
+
+ # Note: if path and index are not '/trunk' and 0 respectively, then
+@@ -150,8 +154,49 @@
+ "cause: incorrect Py_INCREF/Py_DECREF usage in libsvn_swig_py/"
+ "swigutil_py.c." % (expected, refcount, path, i)))
+
++ def test_mergeinfo_leakage__incorrect_range_t_weakrefs(self):
++ """Ensure that the ref counts on svn_merge_range_t objects returned by
++ svn_mergeinfo_parse() are correct."""
++ # When reference counting is working properly, each svn_merge_range_t in
++ # the returned mergeinfo will have a ref count of 1...
++ mergeinfo = core.svn_mergeinfo_parse(self.TEXT_MERGEINFO1)
++ merge_range_refdict = weakref.WeakValueDictionary()
++ merge_range_indexes = []
++ n_merge_range = 0
++ for (path, rangelist) in core._as_list(mergeinfo.items()):
++ # ....and now 2 (incref during iteration of rangelist)
++
++ for (i, r) in enumerate(rangelist):
++ # ....and now 3 (incref during iteration of each range object)
++
++ idx = (path, i)
++ merge_range_refdict[idx] = r
++ merge_range_indexes.append(idx)
++ n_merge_range += 1
++
++ # Note: if path and index are not '/trunk' and 0 respectively, then
++ # only some of the range objects are leaking, which is, as far as
++ # leaks go, even more impressive.
++
++ del rangelist, r
++ gc.collect()
++ # Now (strong) reference count of all svn_merge_range_t should be 1
++ # again and those objects should not be removed yet.
++ for idx in merge_range_indexes:
++ self.assertIn(idx, merge_range_refdict, (
++ "Refarence count error on svn_merge_info_t object for "
++ "(path: %s, index: %d). It should still exists because "
++ "mergeinfo holds its reference, but after GC, it already "
++ "removed." % idx))
+ del mergeinfo
+ gc.collect()
++ if merge_range_refdict:
++ # certainly memory leak, but we want to listing up leaked objects
++ # before raise an assertion error.
++ self.assertFalse(merge_range_refdict,
++ "Memory leak! All svn_merge_range_t object holded "
++ "by mergeinfo object should be removed, but at least "
++ "one object still alive.")
+
+ def test_mergeinfo_leakage__lingering_range_t_objects_after_del(self):
+ """Ensure that there are no svn_merge_range_t objects being tracked by
+@@ -162,6 +207,9 @@
+ objects will be garbage collected and thus, not appear in the list of
+ objects returned by gc.get_objects()."""
+ mergeinfo = core.svn_mergeinfo_parse(self.TEXT_MERGEINFO1)
++ lingering = get_svn_merge_range_t_objects()
++ self.assertNotEqual(lingering, list())
++ del lingering
+ del mergeinfo
+ gc.collect()
+ lingering = get_svn_merge_range_t_objects()
+Index: subversion/bindings/swig/python/tests/repository.py
+===================================================================
+--- subversion/bindings/swig/python/tests/repository.py (revision 1929146)
++++ subversion/bindings/swig/python/tests/repository.py (revision 1929147)
+@@ -87,15 +87,32 @@
+
+ class BatonCollector(repos.ChangeCollector):
+ """A ChangeCollector with collecting batons, too"""
++
+ def __init__(self, fs_ptr, root, pool=None, notify_cb=None):
++
++ def get_expected_baton_refcount():
++ """determine expected refcount of batons within a batoun_tuple,
++ by using dumy object"""
++ self.open_root(-1, None)
++ for baton_tuple in self.batons:
++ rc = sys.getrefcount(baton_tuple[2])
++ break
++ return rc
++
+ repos.ChangeCollector.__init__(self, fs_ptr, root, pool, notify_cb)
+- self.batons = []
+ self.close_called = False
+ self.abort_called = False
++ # temporary values for get_expected_baton_refcount
++ self.batons = []
++ self.expected_baton_refcount = 0
++ # determin expected_baton_refcount
++ self.expected_baton_refcount = get_expected_baton_refcount()
++ # re-initialize the values after calling get_expected_baton_refcount()
++ self.batons = []
+
+ def open_root(self, base_revision, dir_pool=None):
+ bt = repos.ChangeCollector.open_root(self, base_revision, dir_pool)
+- self.batons.append((b'dir baton', b'', bt, sys.getrefcount(bt)))
++ self.batons.append((b'dir baton', b'', bt, self.expected_baton_refcount))
+ return bt
+
+ def add_directory(self, path, parent_baton,
+@@ -104,7 +121,7 @@
+ copyfrom_path,
+ copyfrom_revision,
+ dir_pool)
+- self.batons.append((b'dir baton', path, bt, sys.getrefcount(bt)))
++ self.batons.append((b'dir baton', path, bt, self.expected_baton_refcount))
+ return bt
+
+ def open_directory(self, path, parent_baton, base_revision,
+@@ -111,7 +128,7 @@
+ dir_pool=None):
+ bt = repos.ChangeCollector.open_directory(self, path, parent_baton,
+ base_revision, dir_pool)
+- self.batons.append((b'dir baton', path, bt, sys.getrefcount(bt)))
++ self.batons.append((b'dir baton', path, bt, self.expected_baton_refcount))
+ return bt
+
+ def add_file(self, path, parent_baton,
+@@ -119,13 +136,13 @@
+ bt = repos.ChangeCollector.add_file(self, path, parent_baton,
+ copyfrom_path, copyfrom_revision,
+ file_pool)
+- self.batons.append((b'file baton', path, bt, sys.getrefcount(bt)))
++ self.batons.append((b'file baton', path, bt, self.expected_baton_refcount))
+ return bt
+
+ def open_file(self, path, parent_baton, base_revision, file_pool=None):
+ bt = repos.ChangeCollector.open_file(self, path, parent_baton,
+ base_revision, file_pool)
+- self.batons.append((b'file baton', path, bt, sys.getrefcount(bt)))
++ self.batons.append((b'file baton', path, bt, self.expected_baton_refcount))
+ return bt
+
+ def close_edit(self, pool=None):
+@@ -429,18 +446,22 @@
+ root = fs.revision_root(self.fs, self.rev)
+ editor = BatonCollector(self.fs, root)
+ e_ptr, e_baton = delta.make_editor(editor)
++ refcount_at_first = sys.getrefcount(e_ptr)
+ repos.replay(root, e_ptr, e_baton)
+- for baton in editor.batons:
+- self.assertEqual(sys.getrefcount(baton[2]), 2,
++ for baton_tuple in editor.batons:
++ # baton_tuple: 4-tuple(baton_type: bytes, node: bytes, bt: baton,
++ # expected_refcount_of_bt: int)
++ self.assertEqual(sys.getrefcount(baton_tuple[2]), baton_tuple[3],
+ "leak on baton %s after replay without errors"
+- % repr(baton))
++ % repr(baton_tuple))
+ del e_baton
+- self.assertEqual(sys.getrefcount(e_ptr), 2,
++ self.assertEqual(sys.getrefcount(e_ptr), refcount_at_first,
+ "leak on editor baton after replay without errors")
+
+ editor = BatonCollectorErrorOnClose(self.fs, root,
+ error_path=b'branches/v1x')
+ e_ptr, e_baton = delta.make_editor(editor)
++ refcount_at_first = sys.getrefcount(e_ptr)
+ self.assertRaises(SubversionException, repos.replay, root, e_ptr, e_baton)
+ batons = editor.batons
+ # As svn_repos_replay calls neither close_edit callback nor abort_edit
+@@ -447,11 +468,11 @@
+ # if an error has occured during processing, references of Python objects
+ # in decendant batons may live until e_baton is deleted.
+ del e_baton
+- for baton in batons:
+- self.assertEqual(sys.getrefcount(baton[2]), 2,
++ for baton_tuple in batons:
++ self.assertEqual(sys.getrefcount(baton_tuple[2]), baton_tuple[3],
+ "leak on baton %s after replay with an error"
+- % repr(baton))
+- self.assertEqual(sys.getrefcount(e_ptr), 2,
++ % repr(baton_tuple))
++ self.assertEqual(sys.getrefcount(e_ptr), refcount_at_first,
+ "leak on editor baton after replay with an error")
+
+ def test_delta_editor_apply_textdelta_handler_refcount(self):
+Index: subversion/bindings/swig/python/tests/utils.py
+===================================================================
+--- subversion/bindings/swig/python/tests/utils.py (revision 1929146)
++++ subversion/bindings/swig/python/tests/utils.py (revision 1929147)
+@@ -95,3 +95,13 @@
+
+ def is_defaultencoding_utf8():
+ return codecs_eq(sys.getdefaultencoding(), 'utf-8')
++
++def get_holded_refcount_by_getrefcount():
++ "get refcount holded by sys.getrefcount() if its arg is a local variable"
++ a = []
++ rv = sys.getrefcount(a) - 1
++ return rv
++
++HAS_DEFERRED_REFCOUNT = not get_holded_refcount_by_getrefcount()
++
++del get_holded_refcount_by_getrefcount
diff --git a/devel/subversion/files/extra-patch-check-swig-py314 b/devel/subversion/files/extra-patch-check-swig-py314
new file mode 100644
index 000000000000..5156311eb6d4
--- /dev/null
+++ b/devel/subversion/files/extra-patch-check-swig-py314
@@ -0,0 +1,231 @@
+This patch was extracted by the command
+svn diff -c r1929147 --patch-compatible \
+ https://svn.apache.org/repos/asf/subversion/branches/1.14.x | tail -n +17
+And it can be applied only for Subversion < 1.15.0
+Index: subversion/bindings/swig/python/tests/mergeinfo.py
+===================================================================
+--- subversion/bindings/swig/python/tests/mergeinfo.py (revision 1929146)
++++ subversion/bindings/swig/python/tests/mergeinfo.py (revision 1929147)
+@@ -18,7 +18,7 @@
+ # under the License.
+ #
+ #
+-import unittest, os, sys, gc
++import unittest, os, sys, weakref, gc
+ from svn import core, repos, fs
+ import utils
+
+@@ -125,6 +125,9 @@
+ }
+ self.compare_mergeinfo_catalogs(mergeinfo, expected_mergeinfo)
+
++ @unittest.skipIf(utils.HAS_DEFERRED_REFCOUNT,
++ "Reference counting tests skipped because of deferred "
++ "reference counting")
+ def test_mergeinfo_leakage__incorrect_range_t_refcounts(self):
+ """Ensure that the ref counts on svn_merge_range_t objects returned by
+ svn_mergeinfo_parse() are correct."""
+@@ -138,7 +141,8 @@
+ # ....and now 3 (incref during iteration of each range object)
+
+ refcount = sys.getrefcount(r)
+- # ....and finally, 4 (getrefcount() also increfs)
++ # ....and finally, 4 (getrefcount() also increfs, unless deferred
++ # reference counting)
+ expected = 4
+
+ # Note: if path and index are not '/trunk' and 0 respectively, then
+@@ -150,8 +154,49 @@
+ "cause: incorrect Py_INCREF/Py_DECREF usage in libsvn_swig_py/"
+ "swigutil_py.c." % (expected, refcount, path, i)))
+
++ def test_mergeinfo_leakage__incorrect_range_t_weakrefs(self):
++ """Ensure that the ref counts on svn_merge_range_t objects returned by
++ svn_mergeinfo_parse() are correct."""
++ # When reference counting is working properly, each svn_merge_range_t in
++ # the returned mergeinfo will have a ref count of 1...
++ mergeinfo = core.svn_mergeinfo_parse(self.TEXT_MERGEINFO1)
++ merge_range_refdict = weakref.WeakValueDictionary()
++ merge_range_indexes = []
++ n_merge_range = 0
++ for (path, rangelist) in core._as_list(mergeinfo.items()):
++ # ....and now 2 (incref during iteration of rangelist)
++
++ for (i, r) in enumerate(rangelist):
++ # ....and now 3 (incref during iteration of each range object)
++
++ idx = (path, i)
++ merge_range_refdict[idx] = r
++ merge_range_indexes.append(idx)
++ n_merge_range += 1
++
++ # Note: if path and index are not '/trunk' and 0 respectively, then
++ # only some of the range objects are leaking, which is, as far as
++ # leaks go, even more impressive.
++
++ del rangelist, r
++ gc.collect()
++ # Now (strong) reference count of all svn_merge_range_t should be 1
++ # again and those objects should not be removed yet.
++ for idx in merge_range_indexes:
++ self.assertIn(idx, merge_range_refdict, (
++ "Refarence count error on svn_merge_info_t object for "
++ "(path: %s, index: %d). It should still exists because "
++ "mergeinfo holds its reference, but after GC, it already "
++ "removed." % idx))
+ del mergeinfo
+ gc.collect()
++ if merge_range_refdict:
++ # certainly memory leak, but we want to listing up leaked objects
++ # before raise an assertion error.
++ self.assertFalse(merge_range_refdict,
++ "Memory leak! All svn_merge_range_t object holded "
++ "by mergeinfo object should be removed, but at least "
++ "one object still alive.")
+
+ def test_mergeinfo_leakage__lingering_range_t_objects_after_del(self):
+ """Ensure that there are no svn_merge_range_t objects being tracked by
+@@ -162,6 +207,9 @@
+ objects will be garbage collected and thus, not appear in the list of
+ objects returned by gc.get_objects()."""
+ mergeinfo = core.svn_mergeinfo_parse(self.TEXT_MERGEINFO1)
++ lingering = get_svn_merge_range_t_objects()
++ self.assertNotEqual(lingering, list())
++ del lingering
+ del mergeinfo
+ gc.collect()
+ lingering = get_svn_merge_range_t_objects()
+Index: subversion/bindings/swig/python/tests/repository.py
+===================================================================
+--- subversion/bindings/swig/python/tests/repository.py (revision 1929146)
++++ subversion/bindings/swig/python/tests/repository.py (revision 1929147)
+@@ -87,15 +87,32 @@
+
+ class BatonCollector(repos.ChangeCollector):
+ """A ChangeCollector with collecting batons, too"""
++
+ def __init__(self, fs_ptr, root, pool=None, notify_cb=None):
++
++ def get_expected_baton_refcount():
++ """determine expected refcount of batons within a batoun_tuple,
++ by using dumy object"""
++ self.open_root(-1, None)
++ for baton_tuple in self.batons:
++ rc = sys.getrefcount(baton_tuple[2])
++ break
++ return rc
++
+ repos.ChangeCollector.__init__(self, fs_ptr, root, pool, notify_cb)
+- self.batons = []
+ self.close_called = False
+ self.abort_called = False
++ # temporary values for get_expected_baton_refcount
++ self.batons = []
++ self.expected_baton_refcount = 0
++ # determin expected_baton_refcount
++ self.expected_baton_refcount = get_expected_baton_refcount()
++ # re-initialize the values after calling get_expected_baton_refcount()
++ self.batons = []
+
+ def open_root(self, base_revision, dir_pool=None):
+ bt = repos.ChangeCollector.open_root(self, base_revision, dir_pool)
+- self.batons.append((b'dir baton', b'', bt, sys.getrefcount(bt)))
++ self.batons.append((b'dir baton', b'', bt, self.expected_baton_refcount))
+ return bt
+
+ def add_directory(self, path, parent_baton,
+@@ -104,7 +121,7 @@
+ copyfrom_path,
+ copyfrom_revision,
+ dir_pool)
+- self.batons.append((b'dir baton', path, bt, sys.getrefcount(bt)))
++ self.batons.append((b'dir baton', path, bt, self.expected_baton_refcount))
+ return bt
+
+ def open_directory(self, path, parent_baton, base_revision,
+@@ -111,7 +128,7 @@
+ dir_pool=None):
+ bt = repos.ChangeCollector.open_directory(self, path, parent_baton,
+ base_revision, dir_pool)
+- self.batons.append((b'dir baton', path, bt, sys.getrefcount(bt)))
++ self.batons.append((b'dir baton', path, bt, self.expected_baton_refcount))
+ return bt
+
+ def add_file(self, path, parent_baton,
+@@ -119,13 +136,13 @@
+ bt = repos.ChangeCollector.add_file(self, path, parent_baton,
+ copyfrom_path, copyfrom_revision,
+ file_pool)
+- self.batons.append((b'file baton', path, bt, sys.getrefcount(bt)))
++ self.batons.append((b'file baton', path, bt, self.expected_baton_refcount))
+ return bt
+
+ def open_file(self, path, parent_baton, base_revision, file_pool=None):
+ bt = repos.ChangeCollector.open_file(self, path, parent_baton,
+ base_revision, file_pool)
+- self.batons.append((b'file baton', path, bt, sys.getrefcount(bt)))
++ self.batons.append((b'file baton', path, bt, self.expected_baton_refcount))
+ return bt
+
+ def close_edit(self, pool=None):
+@@ -429,18 +446,22 @@
+ root = fs.revision_root(self.fs, self.rev)
+ editor = BatonCollector(self.fs, root)
+ e_ptr, e_baton = delta.make_editor(editor)
++ refcount_at_first = sys.getrefcount(e_ptr)
+ repos.replay(root, e_ptr, e_baton)
+- for baton in editor.batons:
+- self.assertEqual(sys.getrefcount(baton[2]), 2,
++ for baton_tuple in editor.batons:
++ # baton_tuple: 4-tuple(baton_type: bytes, node: bytes, bt: baton,
++ # expected_refcount_of_bt: int)
++ self.assertEqual(sys.getrefcount(baton_tuple[2]), baton_tuple[3],
+ "leak on baton %s after replay without errors"
+- % repr(baton))
++ % repr(baton_tuple))
+ del e_baton
+- self.assertEqual(sys.getrefcount(e_ptr), 2,
++ self.assertEqual(sys.getrefcount(e_ptr), refcount_at_first,
+ "leak on editor baton after replay without errors")
+
+ editor = BatonCollectorErrorOnClose(self.fs, root,
+ error_path=b'branches/v1x')
+ e_ptr, e_baton = delta.make_editor(editor)
++ refcount_at_first = sys.getrefcount(e_ptr)
+ self.assertRaises(SubversionException, repos.replay, root, e_ptr, e_baton)
+ batons = editor.batons
+ # As svn_repos_replay calls neither close_edit callback nor abort_edit
+@@ -447,11 +468,11 @@
+ # if an error has occured during processing, references of Python objects
+ # in decendant batons may live until e_baton is deleted.
+ del e_baton
+- for baton in batons:
+- self.assertEqual(sys.getrefcount(baton[2]), 2,
++ for baton_tuple in batons:
++ self.assertEqual(sys.getrefcount(baton_tuple[2]), baton_tuple[3],
+ "leak on baton %s after replay with an error"
+- % repr(baton))
+- self.assertEqual(sys.getrefcount(e_ptr), 2,
++ % repr(baton_tuple))
++ self.assertEqual(sys.getrefcount(e_ptr), refcount_at_first,
+ "leak on editor baton after replay with an error")
+
+ def test_delta_editor_apply_textdelta_handler_refcount(self):
+Index: subversion/bindings/swig/python/tests/utils.py
+===================================================================
+--- subversion/bindings/swig/python/tests/utils.py (revision 1929146)
++++ subversion/bindings/swig/python/tests/utils.py (revision 1929147)
+@@ -95,3 +95,13 @@
+
+ def is_defaultencoding_utf8():
+ return codecs_eq(sys.getdefaultencoding(), 'utf-8')
++
++def get_holded_refcount_by_getrefcount():
++ "get refcount holded by sys.getrefcount() if its arg is a local variable"
++ a = []
++ rv = sys.getrefcount(a) - 1
++ return rv
++
++HAS_DEFERRED_REFCOUNT = not get_holded_refcount_by_getrefcount()
++
++del get_holded_refcount_by_getrefcount