Author: svn-role
Date: Wed Oct 15 04:00:06 2025
New Revision: 1929147
Log:
Merge the r1926575 group from trunk:
* r1926575, r1927715
Fix SWIG test cases for Python 3.14.
Justification:
Preparing Python 3.14 release
Votes:
+1: futatuki, jamessan
Modified:
subversion/branches/1.14.x/ (props changed)
subversion/branches/1.14.x/STATUS
subversion/branches/1.14.x/subversion/bindings/swig/python/tests/mergeinfo.py
subversion/branches/1.14.x/subversion/bindings/swig/python/tests/repository.py
subversion/branches/1.14.x/subversion/bindings/swig/python/tests/utils.py
Modified: subversion/branches/1.14.x/STATUS
==============================================================================
--- subversion/branches/1.14.x/STATUS Wed Oct 15 02:51:03 2025 (r1929146)
+++ subversion/branches/1.14.x/STATUS Wed Oct 15 04:00:06 2025 (r1929147)
@@ -65,11 +65,3 @@ Veto-blocked changes:
Approved changes:
=================
-
- * r1926575, r1927715
- Fix SWIG test cases for Python 3.14.
- Justification:
- Preparing Python 3.14 release
- Votes:
- +1: futatuki, jamessan
-
Modified: subversion/branches/1.14.x/subversion/bindings/swig/python/tests/mergeinfo.py
==============================================================================
--- subversion/branches/1.14.x/subversion/bindings/swig/python/tests/mergeinfo.py Wed Oct 15 02:51:03 2025 (r1929146)
+++ subversion/branches/1.14.x/subversion/bindings/swig/python/tests/mergeinfo.py Wed Oct 15 04:00:06 2025 (r1929147)
@@ -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 @@ class SubversionMergeinfoTestCase(unitte
}
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 @@ class SubversionMergeinfoTestCase(unitte
# ....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 @@ class SubversionMergeinfoTestCase(unitte
"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 @@ class SubversionMergeinfoTestCase(unitte
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()
Modified: subversion/branches/1.14.x/subversion/bindings/swig/python/tests/repository.py
==============================================================================
--- subversion/branches/1.14.x/subversion/bindings/swig/python/tests/repository.py Wed Oct 15 02:51:03 2025 (r1929146)
+++ subversion/branches/1.14.x/subversion/bindings/swig/python/tests/repository.py Wed Oct 15 04:00:06 2025 (r1929147)
@@ -87,15 +87,32 @@ class DumpStreamParser(repos.ParseFns3):
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,14 +121,14 @@ class BatonCollector(repos.ChangeCollect
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,
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 @@ class BatonCollector(repos.ChangeCollect
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,29 +446,33 @@ class SubversionRepositoryTestCase(unitt
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
# 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):
Modified: subversion/branches/1.14.x/subversion/bindings/swig/python/tests/utils.py
==============================================================================
--- subversion/branches/1.14.x/subversion/bindings/swig/python/tests/utils.py Wed Oct 15 02:51:03 2025 (r1929146)
+++ subversion/branches/1.14.x/subversion/bindings/swig/python/tests/utils.py Wed Oct 15 04:00:06 2025 (r1929147)
@@ -95,3 +95,13 @@ def codecs_eq(a, b):
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not
affiliated with the servers or forums shown here and is not responsible for
the content of articles, which is written by their respective authors.