From: =?utf-8?q?Miro_Hron=C4=8Dok?= <miro@hroncok.cz>
Date: Sun, 26 Oct 2025 00:17:32 +0100
Subject: Relax all getrefcount tests to allow lower numbers

This is related to https://docs.python.org/3.14/whatsnew/3.14.html#whatsnew314-refcount

Created via:

    sed -Ei 's/sys\.getrefcount\(([^\)]+)\) == ([0-9]+)/sys.getrefcount(\1) <= \2/' tests/test_*.py

Perhaps this is too strong a hammer, but it makes the tests pass on 3.14.

Origin: backport, https://github.com/jcrist/msgspec/pull/854
Bug-Debian: https://bugs.debian.org/1117910
Last-Update: 2025-10-26
---
 tests/test_common.py  | 26 +++++++++++++-------------
 tests/test_convert.py | 18 +++++++++---------
 tests/test_json.py    |  4 ++--
 tests/test_msgpack.py | 10 +++++-----
 tests/test_struct.py  | 10 +++++-----
 5 files changed, 34 insertions(+), 34 deletions(-)

diff --git a/tests/test_common.py b/tests/test_common.py
index 38898be..1012794 100644
--- a/tests/test_common.py
+++ b/tests/test_common.py
@@ -1370,14 +1370,14 @@ class TestGenericStruct:
         dec = proto.Decoder(typ)
         info = typ.__msgspec_cache__
         assert info is not None
-        assert sys.getrefcount(info) == 4  # info + attr + decoder + func call
+        assert sys.getrefcount(info) <= 4  # info + attr + decoder + func call
         dec2 = proto.Decoder(typ)
         assert typ.__msgspec_cache__ is info
-        assert sys.getrefcount(info) == 5
+        assert sys.getrefcount(info) <= 5
 
         del dec
         del dec2
-        assert sys.getrefcount(info) == 3
+        assert sys.getrefcount(info) <= 3
 
     def test_generic_struct_invalid_types_not_cached(self, proto):
         class Ex(Struct, Generic[T]):
@@ -1545,7 +1545,7 @@ class TestStructPostInit:
         res = proto.decode(buf, type=typ)
         assert res == msg
         assert count == 2  # 1 for Ex(), 1 for decode
-        assert sys.getrefcount(singleton) == 2  # 1 for ref, 1 for call
+        assert sys.getrefcount(singleton) <= 2  # 1 for ref, 1 for call
 
     @pytest.mark.parametrize("array_like", [False, True])
     @pytest.mark.parametrize("union", [False, True])
@@ -1606,14 +1606,14 @@ class TestGenericDataclassOrAttrs:
         dec = proto.Decoder(typ)
         info = typ.__msgspec_cache__
         assert info is not None
-        assert sys.getrefcount(info) == 4  # info + attr + decoder + func call
+        assert sys.getrefcount(info) <= 4  # info + attr + decoder + func call
         dec2 = proto.Decoder(typ)
         assert typ.__msgspec_cache__ is info
-        assert sys.getrefcount(info) == 5
+        assert sys.getrefcount(info) <= 5
 
         del dec
         del dec2
-        assert sys.getrefcount(info) == 3
+        assert sys.getrefcount(info) <= 3
 
     def test_generic_invalid_types_not_cached(self, decorator, proto):
         @decorator
@@ -2179,14 +2179,14 @@ class TestTypedDict:
         dec = proto.Decoder(typ)
         info = typ.__msgspec_cache__
         assert info is not None
-        assert sys.getrefcount(info) == 4  # info + attr + decoder + func call
+        assert sys.getrefcount(info) <= 4  # info + attr + decoder + func call
         dec2 = proto.Decoder(typ)
         assert typ.__msgspec_cache__ is info
-        assert sys.getrefcount(info) == 5
+        assert sys.getrefcount(info) <= 5
 
         del dec
         del dec2
-        assert sys.getrefcount(info) == 3
+        assert sys.getrefcount(info) <= 3
 
     def test_generic_typeddict_invalid_types_not_cached(self, proto):
         TypedDict = pytest.importorskip("typing_extensions").TypedDict
@@ -2398,14 +2398,14 @@ class TestNamedTuple:
         dec = proto.Decoder(typ)
         info = typ.__msgspec_cache__
         assert info is not None
-        assert sys.getrefcount(info) == 4  # info + attr + decoder + func call
+        assert sys.getrefcount(info) <= 4  # info + attr + decoder + func call
         dec2 = proto.Decoder(typ)
         assert typ.__msgspec_cache__ is info
-        assert sys.getrefcount(info) == 5
+        assert sys.getrefcount(info) <= 5
 
         del dec
         del dec2
-        assert sys.getrefcount(info) == 3
+        assert sys.getrefcount(info) <= 3
 
     def test_generic_namedtuple_invalid_types_not_cached(self, proto):
         NamedTuple = pytest.importorskip("typing_extensions").NamedTuple
diff --git a/tests/test_convert.py b/tests/test_convert.py
index da1b664..afb668e 100644
--- a/tests/test_convert.py
+++ b/tests/test_convert.py
@@ -220,7 +220,7 @@ class TestConvert:
         x = Custom()
         res = convert(x, Any)
         assert res is x
-        assert sys.getrefcount(x) == 3  # x + res + 1
+        assert sys.getrefcount(x) <= 3  # x + res + 1
 
     def test_custom_input_type_works_with_custom(self):
         class Custom:
@@ -229,7 +229,7 @@ class TestConvert:
         x = Custom()
         res = convert(x, Custom)
         assert res is x
-        assert sys.getrefcount(x) == 3  # x + res + 1
+        assert sys.getrefcount(x) <= 3  # x + res + 1
 
     def test_custom_input_type_works_with_dec_hook(self):
         class Custom:
@@ -247,8 +247,8 @@ class TestConvert:
         x = Custom()
         res = convert(x, Custom2, dec_hook=dec_hook)
         assert isinstance(res, Custom2)
-        assert sys.getrefcount(res) == 2  # res + 1
-        assert sys.getrefcount(x) == 2  # x + 1
+        assert sys.getrefcount(res) <= 2  # res + 1
+        assert sys.getrefcount(x) <= 2  # x + 1
 
     def test_unsupported_output_type(self):
         with pytest.raises(TypeError, match="more than one array-like"):
@@ -397,7 +397,7 @@ class TestInt:
         x = MyInt(100)
         sol = convert(x, MyInt)
         assert sol is x
-        assert sys.getrefcount(x) == 3  # x + sol + 1
+        assert sys.getrefcount(x) <= 3  # x + sol + 1
 
 
 class TestFloat:
@@ -535,10 +535,10 @@ class TestBinary:
 
         del sol
 
-        assert sys.getrefcount(msg) == 2  # msg + 1
+        assert sys.getrefcount(msg) <= 2  # msg + 1
         sol = convert(msg, MyBytes)
         assert sol is msg
-        assert sys.getrefcount(msg) == 3  # msg + sol + 1
+        assert sys.getrefcount(msg) <= 3  # msg + sol + 1
 
 
 class TestDateTime:
@@ -828,7 +828,7 @@ class TestEnum:
 
         msg = MyInt(1)
         assert convert(msg, Ex) is Ex.x
-        assert sys.getrefcount(msg) == 2  # msg + 1
+        assert sys.getrefcount(msg) <= 2  # msg + 1
         assert convert(MyInt(2), Ex) is Ex.y
 
     def test_enum_missing(self):
@@ -2223,7 +2223,7 @@ class TestStructPostInit:
         res = convert(msg, type=typ, from_attributes=from_attributes)
         assert type(res) is Ex
         assert called
-        assert sys.getrefcount(singleton) == 2  # 1 for ref, 1 for call
+        assert sys.getrefcount(singleton) <= 2  # 1 for ref, 1 for call
 
     @pytest.mark.parametrize("union", [False, True])
     @pytest.mark.parametrize("exc_class", [ValueError, TypeError, OSError])
diff --git a/tests/test_json.py b/tests/test_json.py
index 1d3776e..f3a562e 100644
--- a/tests/test_json.py
+++ b/tests/test_json.py
@@ -898,7 +898,7 @@ class TestDatetime:
         tz2 = msgspec.json.decode(msg, type=datetime.datetime).tzinfo
         assert tz is tz2
         del tz2
-        assert sys.getrefcount(tz) == 3  # 1 tz, 1 cache, 1 func call
+        assert sys.getrefcount(tz) <= 3  # 1 tz, 1 cache, 1 func call
         for _ in range(10):
             gc.collect()  # cache is cleared every 10 full collections
 
@@ -2293,7 +2293,7 @@ class TestStruct:
         assert x == Person("harry", "potter", 13, False)
 
         # one for struct, one for output of getattr, and one for getrefcount
-        assert sys.getrefcount(x.first) == 3
+        assert sys.getrefcount(x.first) <= 3
 
         with pytest.raises(
             msgspec.ValidationError, match="Expected `object`, got `int`"
diff --git a/tests/test_msgpack.py b/tests/test_msgpack.py
index 55db40f..e3d30d0 100644
--- a/tests/test_msgpack.py
+++ b/tests/test_msgpack.py
@@ -684,13 +684,13 @@ class TestTypedDecoder:
         assert isinstance(res, memoryview)
         assert bytes(res) == b"abcde"
         if input_type is memoryview:
-            assert sys.getrefcount(ref) == 3
+            assert sys.getrefcount(ref) <= 3
             del msg
-            assert sys.getrefcount(ref) == 3
+            assert sys.getrefcount(ref) <= 3
             del res
-            assert sys.getrefcount(ref) == 2
+            assert sys.getrefcount(ref) <= 2
         elif input_type is bytes:
-            assert sys.getrefcount(msg) == 3
+            assert sys.getrefcount(msg) <= 3
 
     def test_datetime_aware_ext(self):
         dec = msgspec.msgpack.Decoder(datetime.datetime)
@@ -815,7 +815,7 @@ class TestTypedDecoder:
         res = dec.decode(enc.encode(x))
         assert res == x
         if res:
-            assert sys.getrefcount(res[0]) == 3  # 1 tuple, 1 index, 1 func call
+            assert sys.getrefcount(res[0]) <= 3  # 1 tuple, 1 index, 1 func call
 
     @pytest.mark.parametrize("typ", [tuple, Tuple, Tuple[Any, ...]])
     def test_vartuple_any(self, typ):
diff --git a/tests/test_struct.py b/tests/test_struct.py
index 66971fa..9f0ec8f 100644
--- a/tests/test_struct.py
+++ b/tests/test_struct.py
@@ -931,16 +931,16 @@ def test_struct_reference_counting():
     data = [1, 2, 3]
 
     t = Test(data)
-    assert sys.getrefcount(data) == 3
+    assert sys.getrefcount(data) <= 3
 
     repr(t)
-    assert sys.getrefcount(data) == 3
+    assert sys.getrefcount(data) <= 3
 
     t2 = t.__copy__()
-    assert sys.getrefcount(data) == 4
+    assert sys.getrefcount(data) <= 4
 
     assert t == t2
-    assert sys.getrefcount(data) == 4
+    assert sys.getrefcount(data) <= 4
 
 
 def test_struct_gc_not_added_if_not_needed():
@@ -2581,7 +2581,7 @@ class TestPostInit:
         Ex(1)
         assert called
         # Return value is decref'd
-        assert sys.getrefcount(singleton) == 2  # 1 for ref, 1 for call
+        assert sys.getrefcount(singleton) <= 2  # 1 for ref, 1 for call
 
     def test_post_init_errors(self):
         class Ex(Struct):
