Hello, On 2025/06/19 20:30, Branko Čibej wrote: > On 19. 6. 25 12:42, Joe Orton wrote: >> On Thu, Jun 19, 2025 at 10:26:43AM +0200, Branko Čibej wrote: >>> On 19. 6. 25 10:22,jor...@apache.org wrote: >>>> Author: jorton >>>> Date: Thu Jun 19 08:22:56 2025 >>>> New Revision: 1926575 >>>> >>>> URL:http://svn.apache.org/viewvc?rev=1926575&view=rev >>>> Log: >>>> Fix SWIG test cases for Python 3.14. >>>> >>>> The interpreter internally avoids some reference count modifications when >>>> loading objects onto the operands stack by borrowing references when >>>> possible. This can lead to smaller reference count values compared to >>>> previous Python versions. >>>> >>>> https://docs.python.org/3.14/whatsnew/3.14.html#whatsnew314-refcount >>> How come all the other getrefcount()s in repository.py aren't affected? >> I have no idea, sorry, but all the tests pass under 3.14 with this >> applied. > > Yes, I verified that (3.14.0b3 via pyenv). Well, as long as it works ...
Then, I've tweak repository.py without using magic number of refcount and adding comments. Although I didn't test it on Python 3.14, but it is expected to work. -- Yasuhito FUTATSUKI <futat...@yf.bsdclub.org>
Index: subversion/bindings/swig/python/tests/repository.py =================================================================== --- subversion/bindings/swig/python/tests/repository.py (revision 1926579) +++ subversion/bindings/swig/python/tests/repository.py (working copy) @@ -95,7 +95,9 @@ 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))) + # refcount for the object refered by bt is counted down after + # the variable bt is deleted (i.e. after leaving this function) + self.batons.append((b'dir baton', b'', bt, sys.getrefcount(bt) - 1)) return bt def add_directory(self, path, parent_baton, @@ -104,7 +106,9 @@ copyfrom_path, copyfrom_revision, dir_pool) - self.batons.append((b'dir baton', path, bt, sys.getrefcount(bt))) + # refcount for the object refered by bt is counted down after + # the variable bt is deleted (i.e. after leaving this function) + self.batons.append((b'dir baton', path, bt, sys.getrefcount(bt) - 1)) return bt def open_directory(self, path, parent_baton, base_revision, @@ -111,7 +115,9 @@ 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))) + # refcount for the object refered by bt is counted down after + # the variable bt is deleted (i.e. after leaving this function) + self.batons.append((b'dir baton', path, bt, sys.getrefcount(bt) - 1)) return bt def add_file(self, path, parent_baton, @@ -119,13 +125,17 @@ 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))) + # refcount for the object refered by bt is counted down after + # the variable bt is deleted (i.e. after leaving this function) + self.batons.append((b'file baton', path, bt, sys.getrefcount(bt) - 1)) 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))) + # refcount for the object refered by bt is counted down after + # the variable bt is deleted (i.e. after leaving this function) + self.batons.append((b'file baton', path, bt, sys.getrefcount(bt) - 1)) return bt def close_edit(self, pool=None): @@ -429,19 +439,22 @@ root = fs.revision_root(self.fs, self.rev) editor = BatonCollector(self.fs, root) e_ptr, e_baton = delta.make_editor(editor) - expected = 1 if sys.version_info >= (3, 14) else 2 + 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), expected, + 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 @@ -448,11 +461,13 @@ # if an error has occurred during processing, references of Python objects # in descendant 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: + # 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 with an error" - % repr(baton)) - self.assertEqual(sys.getrefcount(e_ptr), expected, + % 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):