]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
test: add gc fixtures to skip tests on PyPy more easily 686/head
authorNick Pope <nick.pope@infogrid.io>
Tue, 28 Nov 2023 15:50:30 +0000 (15:50 +0000)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sat, 2 Dec 2023 00:09:16 +0000 (01:09 +0100)
26 files changed:
tests/conftest.py
tests/crdb/test_copy.py
tests/crdb/test_copy_async.py
tests/pool/test_pool.py
tests/pool/test_pool_async.py
tests/pool/test_pool_async_noasyncio.py
tests/pool/test_pool_common.py
tests/pool/test_pool_common_async.py
tests/pq/test_pgconn.py
tests/test_connection.py
tests/test_connection_async.py
tests/test_copy.py
tests/test_copy_async.py
tests/test_cursor.py
tests/test_cursor_async.py
tests/test_cursor_client.py
tests/test_cursor_client_async.py
tests/test_cursor_common.py
tests/test_cursor_common_async.py
tests/test_cursor_raw.py
tests/test_cursor_raw_async.py
tests/test_cursor_server.py
tests/test_cursor_server_async.py
tests/test_errors.py
tests/types/test_array.py
tests/utils.py

index 8438fc6c6b3149ae8cc66d60228391f6919a68a7..5b02855d5edfd91c2ad2d44cab2de7237b8d1437 100644 (file)
@@ -1,7 +1,8 @@
+import gc
 import sys
 import asyncio
 import selectors
-from typing import Any, Dict, List
+from typing import Any, Dict, List, Tuple
 
 import pytest
 
@@ -100,3 +101,70 @@ def pytest_terminal_summary(terminalreporter, exitstatus, config):
         terminalreporter.section("failed tests ignored")
         for msg in allow_fail_messages:
             terminalreporter.line(msg)
+
+
+NO_COUNT_TYPES: Tuple[type, ...] = ()
+
+if sys.version_info[:2] == (3, 10):
+    # On my laptop there are occasional creations of a single one of these objects
+    # with empty content, which might be some Decimal caching.
+    # Keeping the guard as strict as possible, to be extended if other types
+    # or versions are necessary.
+    try:
+        from _contextvars import Context  # type: ignore
+    except ImportError:
+        pass
+    else:
+        NO_COUNT_TYPES += (Context,)
+
+
+class GCFixture:
+    __slots__ = ()
+
+    @staticmethod
+    def collect() -> None:
+        """
+        gc.collect(), but more insisting.
+        """
+        for i in range(3):
+            gc.collect()
+
+    @staticmethod
+    def count() -> int:
+        """
+        len(gc.get_objects()), with subtleties.
+        """
+
+        if not NO_COUNT_TYPES:
+            return len(gc.get_objects())
+
+        # Note: not using a list comprehension because it pollutes the objects list.
+        rv = 0
+        for obj in gc.get_objects():
+            if isinstance(obj, NO_COUNT_TYPES):
+                continue
+            rv += 1
+
+        return rv
+
+
+@pytest.fixture(name="gc")
+def fixture_gc():
+    """
+    Provides a consistent way to run garbage collection and count references.
+
+    **Note:** This will skip tests on PyPy.
+    """
+    if sys.implementation.name == "pypy":
+        pytest.skip(reason="depends on refcount semantics")
+    return GCFixture()
+
+
+@pytest.fixture
+def gc_collect():
+    """
+    Provides a consistent way to run garbage collection.
+
+    **Note:** This will *not* skip tests on PyPy.
+    """
+    return GCFixture.collect
index 2bf714f1c3389ebb4525b52a56300ab8b87e8ac1..4100c33f605a6478d93111c9c822c23e43187849 100644 (file)
@@ -7,7 +7,7 @@ from psycopg.pq import Format
 from psycopg.adapt import PyFormat
 from psycopg.types.numeric import Int4
 
-from ..utils import eur, gc_collect, gc_count
+from ..utils import eur
 from .._test_copy import sample_text, sample_binary  # noqa
 from .._test_copy import ensure_table, sample_records
 from .._test_copy import sample_tabledef as sample_tabledef_pg
@@ -191,7 +191,7 @@ from copy_in group by 1, 2, 3
     [(Format.TEXT, True), (Format.TEXT, False), (Format.BINARY, True)],
 )
 @pytest.mark.crdb_skip("copy array")
-def test_copy_from_leaks(conn_cls, dsn, faker, fmt, set_types):
+def test_copy_from_leaks(conn_cls, dsn, faker, fmt, set_types, gc):
     faker.format = PyFormat.from_pq(fmt)
     faker.choose_schema(ncols=20)
     faker.make_records(20)
@@ -219,12 +219,12 @@ def test_copy_from_leaks(conn_cls, dsn, faker, fmt, set_types):
                 for got, want in zip(recs, faker.records):
                     faker.assert_record(got, want)
 
-    gc_collect()
+    gc.collect()
     n = []
     for i in range(3):
         work()
-        gc_collect()
-        n.append(gc_count())
+        gc.collect()
+        n.append(gc.count())
 
     assert n[0] == n[1] == n[2], f"objects leaked: {n[1] - n[0]}, {n[2] - n[1]}"
 
index a994d9071fad16cdf7bc9325d97ad27783416256..17a37a95fa1517fdb3875df81a997818c0826568 100644 (file)
@@ -7,7 +7,7 @@ from psycopg import sql, errors as e
 from psycopg.adapt import PyFormat
 from psycopg.types.numeric import Int4
 
-from ..utils import eur, gc_collect, gc_count
+from ..utils import eur
 from .._test_copy import sample_text, sample_binary  # noqa
 from .._test_copy import ensure_table_async, sample_records
 from .test_copy import sample_tabledef, copyopt
@@ -196,7 +196,7 @@ from copy_in group by 1, 2, 3
     [(Format.TEXT, True), (Format.TEXT, False), (Format.BINARY, True)],
 )
 @pytest.mark.crdb_skip("copy array")
-async def test_copy_from_leaks(aconn_cls, dsn, faker, fmt, set_types):
+async def test_copy_from_leaks(aconn_cls, dsn, faker, fmt, set_types, gc):
     faker.format = PyFormat.from_pq(fmt)
     faker.choose_schema(ncols=20)
     faker.make_records(20)
@@ -224,11 +224,11 @@ async def test_copy_from_leaks(aconn_cls, dsn, faker, fmt, set_types):
                 for got, want in zip(recs, faker.records):
                     faker.assert_record(got, want)
 
-    gc_collect()
+    gc.collect()
     n = []
     for i in range(3):
         await work()
-        gc_collect()
-        n.append(gc_count())
+        gc.collect()
+        n.append(gc.count())
 
     assert n[0] == n[1] == n[2], f"objects leaked: {n[1] - n[0]}, {n[2] - n[1]}"
index 11a4cf30d87adb04cc90438041459df79d79c112..7886c288ea15af36d0d2edfc7a50a8bb04afbced 100644 (file)
@@ -12,7 +12,7 @@ import psycopg
 from psycopg.pq import TransactionStatus
 from psycopg.rows import class_row, Row, TupleRow
 
-from ..utils import assert_type, Counter, gc_collect, set_autocommit
+from ..utils import assert_type, Counter, set_autocommit
 from ..acompat import Event, spawn, gather, sleep, skip_sync
 from .test_pool_common import delay_connection
 
@@ -358,7 +358,7 @@ def test_fail_rollback_close(dsn, caplog, monkeypatch):
     assert "BAD" in caplog.records[2].message
 
 
-def test_del_no_warning(dsn, recwarn):
+def test_del_no_warning(dsn, recwarn, gc_collect):
     p = pool.ConnectionPool(dsn, min_size=2, open=False)
     p.open()
     with p.connection() as conn:
index 0d6dbdfd08fdf464210833a3462a5873583d8aa3..71694c0cf3205c79c2af475bd7cc713e3437a82d 100644 (file)
@@ -9,7 +9,7 @@ import psycopg
 from psycopg.pq import TransactionStatus
 from psycopg.rows import class_row, Row, TupleRow
 
-from ..utils import assert_type, Counter, gc_collect, set_autocommit
+from ..utils import assert_type, Counter, set_autocommit
 from ..acompat import AEvent, spawn, gather, asleep, skip_sync
 from .test_pool_common_async import delay_connection
 
@@ -362,7 +362,7 @@ async def test_fail_rollback_close(dsn, caplog, monkeypatch):
     assert "BAD" in caplog.records[2].message
 
 
-async def test_del_no_warning(dsn, recwarn):
+async def test_del_no_warning(dsn, recwarn, gc_collect):
     p = pool.AsyncConnectionPool(dsn, min_size=2, open=False)
     await p.open()
     async with p.connection() as conn:
index 3b96d9a34614c7ed1bc05021d5394111fb9dd8a6..b2ad2ffa2c81b9c9b4572af762a7151a2d124916 100644 (file)
@@ -6,8 +6,6 @@ import sys
 
 import pytest
 
-from ..utils import gc_collect
-
 try:
     import psycopg_pool as pool
 except ImportError:
@@ -63,7 +61,7 @@ def test_cant_create_open_outside_loop(dsn):
 
 
 @pytest.fixture
-def asyncio_run(recwarn):
+def asyncio_run(recwarn, gc_collect):
     """Fixture reuturning asyncio.run, but managing resources at exit.
 
     In certain runs, fd objects are leaked and the error will only be caught
index 9165eb785369fd1c2508b2a5ffc3527e4a92ba41..3b83cbad1b897d41778c97da25f9de118ce8f9fa 100644 (file)
@@ -9,7 +9,7 @@ import pytest
 
 import psycopg
 
-from ..utils import gc_collect, set_autocommit
+from ..utils import set_autocommit
 from ..acompat import Event, spawn, gather, sleep, is_alive, skip_async, skip_sync
 
 try:
@@ -347,12 +347,12 @@ def test_putconn_wrong_pool(pool_cls, dsn):
 
 @skip_async
 @pytest.mark.slow
-def test_del_stops_threads(pool_cls, dsn):
+def test_del_stops_threads(pool_cls, dsn, gc):
     p = pool_cls(dsn)
     assert p._sched_runner is not None
     ts = [p._sched_runner] + p._workers
     del p
-    gc_collect()
+    gc.collect()
     sleep(0.1)
     for t in ts:
         assert not is_alive(t), t
index e1b7a2584e0de9135648be2fba347c5211092ff8..6605fc531603e06eb5d7804209bb04d250abf346 100644 (file)
@@ -6,7 +6,7 @@ import pytest
 
 import psycopg
 
-from ..utils import gc_collect, set_autocommit
+from ..utils import set_autocommit
 from ..acompat import AEvent, spawn, gather, asleep, is_alive, skip_async, skip_sync
 
 try:
@@ -364,12 +364,12 @@ async def test_putconn_wrong_pool(pool_cls, dsn):
 
 @skip_async
 @pytest.mark.slow
-async def test_del_stops_threads(pool_cls, dsn):
+async def test_del_stops_threads(pool_cls, dsn, gc):
     p = pool_cls(dsn)
     assert p._sched_runner is not None
     ts = [p._sched_runner] + p._workers
     del p
-    gc_collect()
+    gc.collect()
     await asleep(0.1)
     for t in ts:
         assert not is_alive(t), t
index 05661511ad0b14f16561f6d1bea07549fff0d83f..ff18379a44ed7cabe19ff44506241b796102baea 100644 (file)
@@ -11,8 +11,6 @@ import psycopg
 from psycopg import pq
 import psycopg.generators
 
-from ..utils import gc_collect
-
 
 def test_connectdb(dsn):
     conn = pq.PGconn.connect(dsn.encode())
@@ -82,7 +80,7 @@ def test_finish(pgconn):
 
 
 @pytest.mark.slow
-def test_weakref(dsn):
+def test_weakref(dsn, gc_collect):
     conn = pq.PGconn.connect(dsn.encode())
     w = weakref.ref(conn)
     conn.finish()
index ef066653fac5c37f36c1ed8edef4f817bdce2e11..698ff8d643ba8a9e550dd8b24972d43489bfa5a9 100644 (file)
@@ -13,7 +13,6 @@ from psycopg import Notify, pq, errors as e
 from psycopg.rows import tuple_row
 from psycopg.conninfo import conninfo_to_dict, make_conninfo
 
-from .utils import gc_collect
 from .acompat import is_async, skip_sync, skip_async
 from ._test_cursor import my_row_factory
 from ._test_connection import tx_params, tx_params_isolation, tx_values_map
@@ -137,7 +136,7 @@ def test_cursor_closed(conn):
     and (not is_async(__name__)),
     reason="Something with Exceptions, C, Python 3.12",
 )
-def test_connection_warn_close(conn_cls, dsn, recwarn):
+def test_connection_warn_close(conn_cls, dsn, recwarn, gc_collect):
     conn = conn_cls.connect(dsn)
     conn.close()
     del conn
@@ -244,7 +243,7 @@ def test_context_active_rollback_no_clobber(conn_cls, dsn, caplog):
 
 
 @pytest.mark.slow
-def test_weakref(conn_cls, dsn):
+def test_weakref(conn_cls, dsn, gc_collect):
     conn = conn_cls.connect(dsn)
     w = weakref.ref(conn)
     conn.close()
index d0110a01aabfd434b8cdbdb943d27f48a007de9d..32cc417b7b71d9cb733e71ec5df4a473c7a8a26c 100644 (file)
@@ -10,7 +10,6 @@ from psycopg import Notify, pq, errors as e
 from psycopg.rows import tuple_row
 from psycopg.conninfo import conninfo_to_dict, make_conninfo
 
-from .utils import gc_collect
 from .acompat import is_async, skip_sync, skip_async
 from ._test_cursor import my_row_factory
 from ._test_connection import tx_params, tx_params_isolation, tx_values_map
@@ -134,7 +133,7 @@ async def test_cursor_closed(aconn):
     and not is_async(__name__),
     reason="Something with Exceptions, C, Python 3.12",
 )
-async def test_connection_warn_close(aconn_cls, dsn, recwarn):
+async def test_connection_warn_close(aconn_cls, dsn, recwarn, gc_collect):
     conn = await aconn_cls.connect(dsn)
     await conn.close()
     del conn
@@ -242,7 +241,7 @@ async def test_context_active_rollback_no_clobber(aconn_cls, dsn, caplog):
 
 
 @pytest.mark.slow
-async def test_weakref(aconn_cls, dsn):
+async def test_weakref(aconn_cls, dsn, gc_collect):
     conn = await aconn_cls.connect(dsn)
     w = weakref.ref(conn)
     await conn.close()
index 25d15f2f8bc843941f342d270bc208ca1756ff30..fda854e60bea1adeee1eea27bb3e7b6f0888f5ef 100644 (file)
@@ -3,7 +3,6 @@
 # DO NOT CHANGE! Change the original file instead.
 import string
 import hashlib
-import sys
 from io import BytesIO, StringIO
 from random import choice, randrange
 from itertools import cycle
@@ -21,7 +20,7 @@ from psycopg.types import TypeInfo
 from psycopg.types.hstore import register_hstore
 from psycopg.types.numeric import Int4
 
-from .utils import eur, gc_collect, gc_count
+from .utils import eur
 from ._test_copy import sample_text, sample_binary, sample_binary_rows  # noqa
 from ._test_copy import sample_values, sample_records, sample_tabledef
 from ._test_copy import ensure_table, py_to_raw, special_chars, FileWriter
@@ -674,14 +673,11 @@ def test_connection_writer(conn, format, buffer):
 
 
 @pytest.mark.slow
-@pytest.mark.skipif(
-    sys.implementation.name == "pypy", reason="depends on refcount semantics"
-)
 @pytest.mark.parametrize(
     "fmt, set_types", [(Format.TEXT, True), (Format.TEXT, False), (Format.BINARY, True)]
 )
 @pytest.mark.parametrize("method", ["read", "iter", "row", "rows"])
-def test_copy_to_leaks(conn_cls, dsn, faker, fmt, set_types, method):
+def test_copy_to_leaks(conn_cls, dsn, faker, fmt, set_types, method, gc):
     faker.format = PyFormat.from_pq(fmt)
     faker.choose_schema(ncols=20)
     faker.make_records(20)
@@ -721,24 +717,21 @@ def test_copy_to_leaks(conn_cls, dsn, faker, fmt, set_types, method):
                     elif method == "rows":
                         list(copy.rows())
 
-    gc_collect()
+    gc.collect()
     n = []
     for i in range(3):
         work()
-        gc_collect()
-        n.append(gc_count())
+        gc.collect()
+        n.append(gc.count())
 
     assert n[0] == n[1] == n[2], f"objects leaked: {n[1] - n[0]}, {n[2] - n[1]}"
 
 
 @pytest.mark.slow
-@pytest.mark.skipif(
-    sys.implementation.name == "pypy", reason="depends on refcount semantics"
-)
 @pytest.mark.parametrize(
     "fmt, set_types", [(Format.TEXT, True), (Format.TEXT, False), (Format.BINARY, True)]
 )
-def test_copy_from_leaks(conn_cls, dsn, faker, fmt, set_types):
+def test_copy_from_leaks(conn_cls, dsn, faker, fmt, set_types, gc):
     faker.format = PyFormat.from_pq(fmt)
     faker.choose_schema(ncols=20)
     faker.make_records(20)
@@ -766,12 +759,12 @@ def test_copy_from_leaks(conn_cls, dsn, faker, fmt, set_types):
                 for got, want in zip(recs, faker.records):
                     faker.assert_record(got, want)
 
-    gc_collect()
+    gc.collect()
     n = []
     for i in range(3):
         work()
-        gc_collect()
-        n.append(gc_count())
+        gc.collect()
+        n.append(gc.count())
 
     assert n[0] == n[1] == n[2], f"objects leaked: {n[1] - n[0]}, {n[2] - n[1]}"
 
index 807d94bd577da817a84dfc14de1fdbdad50ad725..f52cc614431dbe5cc93b3f39dfd2ca8dbe568fce 100644 (file)
@@ -1,6 +1,5 @@
 import string
 import hashlib
-import sys
 from io import BytesIO, StringIO
 from random import choice, randrange
 from itertools import cycle
@@ -18,7 +17,7 @@ from psycopg.types import TypeInfo
 from psycopg.types.hstore import register_hstore
 from psycopg.types.numeric import Int4
 
-from .utils import eur, gc_collect, gc_count
+from .utils import eur
 from .acompat import alist
 from ._test_copy import sample_text, sample_binary, sample_binary_rows  # noqa
 from ._test_copy import sample_values, sample_records, sample_tabledef
@@ -691,15 +690,12 @@ async def test_connection_writer(aconn, format, buffer):
 
 
 @pytest.mark.slow
-@pytest.mark.skipif(
-    sys.implementation.name == "pypy", reason="depends on refcount semantics"
-)
 @pytest.mark.parametrize(
     "fmt, set_types",
     [(Format.TEXT, True), (Format.TEXT, False), (Format.BINARY, True)],
 )
 @pytest.mark.parametrize("method", ["read", "iter", "row", "rows"])
-async def test_copy_to_leaks(aconn_cls, dsn, faker, fmt, set_types, method):
+async def test_copy_to_leaks(aconn_cls, dsn, faker, fmt, set_types, method, gc):
     faker.format = PyFormat.from_pq(fmt)
     faker.choose_schema(ncols=20)
     faker.make_records(20)
@@ -739,25 +735,22 @@ async def test_copy_to_leaks(aconn_cls, dsn, faker, fmt, set_types, method):
                     elif method == "rows":
                         await alist(copy.rows())
 
-    gc_collect()
+    gc.collect()
     n = []
     for i in range(3):
         await work()
-        gc_collect()
-        n.append(gc_count())
+        gc.collect()
+        n.append(gc.count())
 
     assert n[0] == n[1] == n[2], f"objects leaked: {n[1] - n[0]}, {n[2] - n[1]}"
 
 
 @pytest.mark.slow
-@pytest.mark.skipif(
-    sys.implementation.name == "pypy", reason="depends on refcount semantics"
-)
 @pytest.mark.parametrize(
     "fmt, set_types",
     [(Format.TEXT, True), (Format.TEXT, False), (Format.BINARY, True)],
 )
-async def test_copy_from_leaks(aconn_cls, dsn, faker, fmt, set_types):
+async def test_copy_from_leaks(aconn_cls, dsn, faker, fmt, set_types, gc):
     faker.format = PyFormat.from_pq(fmt)
     faker.choose_schema(ncols=20)
     faker.make_records(20)
@@ -785,12 +778,12 @@ async def test_copy_from_leaks(aconn_cls, dsn, faker, fmt, set_types):
                 for got, want in zip(recs, faker.records):
                     faker.assert_record(got, want)
 
-    gc_collect()
+    gc.collect()
     n = []
     for i in range(3):
         await work()
-        gc_collect()
-        n.append(gc_count())
+        gc.collect()
+        n.append(gc.count())
 
     assert n[0] == n[1] == n[2], f"objects leaked: {n[1] - n[0]}, {n[2] - n[1]}"
 
index 845b0941ce06fb8de468c85fef1cb6abfa8a2918..28b96a7b4d270a7c328cac07acd78744fb170ac1 100644 (file)
@@ -7,12 +7,9 @@ Tests for psycopg.Cursor that are not supposed to pass for subclasses.
 
 import pytest
 import psycopg
-import sys
 from psycopg import pq, rows, errors as e
 from psycopg.adapt import PyFormat
 
-from .utils import gc_collect, gc_count
-
 
 def test_default_cursor(conn):
     cur = conn.cursor()
@@ -66,14 +63,11 @@ def test_query_params_executemany(conn):
 
 
 @pytest.mark.slow
-@pytest.mark.skipif(
-    sys.implementation.name == "pypy", reason="depends on refcount semantics"
-)
 @pytest.mark.parametrize("fmt", PyFormat)
 @pytest.mark.parametrize("fmt_out", pq.Format)
 @pytest.mark.parametrize("fetch", ["one", "many", "all", "iter"])
 @pytest.mark.parametrize("row_factory", ["tuple_row", "dict_row", "namedtuple_row"])
-def test_leak(conn_cls, dsn, faker, fmt, fmt_out, fetch, row_factory):
+def test_leak(conn_cls, dsn, faker, fmt, fmt_out, fetch, row_factory, gc):
     faker.format = fmt
     faker.choose_schema(ncols=5)
     faker.make_records(10)
@@ -105,10 +99,10 @@ def test_leak(conn_cls, dsn, faker, fmt, fmt_out, fetch, row_factory):
                         pass
 
     n = []
-    gc_collect()
+    gc.collect()
     for i in range(3):
         work()
-        gc_collect()
-        n.append(gc_count())
+        gc.collect()
+        n.append(gc.count())
 
     assert n[0] == n[1] == n[2], f"objects leaked: {n[1] - n[0]}, {n[2] - n[1]}"
index 00e10883c4363329255541f10e1285fe5c84f885..9c5c3ebdd96e364db3adb29f163a188358a64b0c 100644 (file)
@@ -4,12 +4,9 @@ Tests for psycopg.Cursor that are not supposed to pass for subclasses.
 
 import pytest
 import psycopg
-import sys
 from psycopg import pq, rows, errors as e
 from psycopg.adapt import PyFormat
 
-from .utils import gc_collect, gc_count
-
 
 async def test_default_cursor(aconn):
     cur = aconn.cursor()
@@ -65,14 +62,11 @@ async def test_query_params_executemany(aconn):
 
 
 @pytest.mark.slow
-@pytest.mark.skipif(
-    sys.implementation.name == "pypy", reason="depends on refcount semantics"
-)
 @pytest.mark.parametrize("fmt", PyFormat)
 @pytest.mark.parametrize("fmt_out", pq.Format)
 @pytest.mark.parametrize("fetch", ["one", "many", "all", "iter"])
 @pytest.mark.parametrize("row_factory", ["tuple_row", "dict_row", "namedtuple_row"])
-async def test_leak(aconn_cls, dsn, faker, fmt, fmt_out, fetch, row_factory):
+async def test_leak(aconn_cls, dsn, faker, fmt, fmt_out, fetch, row_factory, gc):
     faker.format = fmt
     faker.choose_schema(ncols=5)
     faker.make_records(10)
@@ -106,10 +100,10 @@ async def test_leak(aconn_cls, dsn, faker, fmt, fmt_out, fetch, row_factory):
                         pass
 
     n = []
-    gc_collect()
+    gc.collect()
     for i in range(3):
         await work()
-        gc_collect()
-        n.append(gc_count())
+        gc.collect()
+        n.append(gc.count())
 
     assert n[0] == n[1] == n[2], f"objects leaked: {n[1] - n[0]}, {n[2] - n[1]}"
index a0284d17a97817f068ca5694ad9b154cecb5c2c6..f9d9d97447860138805322c81d80ff24f7482714 100644 (file)
@@ -5,10 +5,8 @@ import datetime as dt
 
 import pytest
 import psycopg
-import sys
 from psycopg import rows
 
-from .utils import gc_collect, gc_count
 from .fix_crdb import crdb_encoding
 
 
@@ -79,12 +77,9 @@ def test_query_params_executemany(conn):
 
 
 @pytest.mark.slow
-@pytest.mark.skipif(
-    sys.implementation.name == "pypy", reason="depends on refcount semantics"
-)
 @pytest.mark.parametrize("fetch", ["one", "many", "all", "iter"])
 @pytest.mark.parametrize("row_factory", ["tuple_row", "dict_row", "namedtuple_row"])
-def test_leak(conn_cls, dsn, faker, fetch, row_factory):
+def test_leak(conn_cls, dsn, faker, fetch, row_factory, gc):
     faker.choose_schema(ncols=5)
     faker.make_records(10)
     row_factory = getattr(rows, row_factory)
@@ -115,11 +110,11 @@ def test_leak(conn_cls, dsn, faker, fetch, row_factory):
                         pass
 
     n = []
-    gc_collect()
+    gc.collect()
     for i in range(3):
         work()
-        gc_collect()
-        n.append(gc_count())
+        gc.collect()
+        n.append(gc.count())
 
     assert n[0] == n[1] == n[2], f"objects leaked: {n[1] - n[0]}, {n[2] - n[1]}"
 
index a3ba12ae6ab3111c9647fbeaaced88b7c7ba0d26..63fb5d5af934c2d0221d89bd48dcadf6dcb3298e 100644 (file)
@@ -2,10 +2,8 @@ import datetime as dt
 
 import pytest
 import psycopg
-import sys
 from psycopg import rows
 
-from .utils import gc_collect, gc_count
 from .fix_crdb import crdb_encoding
 
 
@@ -78,12 +76,9 @@ async def test_query_params_executemany(aconn):
 
 
 @pytest.mark.slow
-@pytest.mark.skipif(
-    sys.implementation.name == "pypy", reason="depends on refcount semantics"
-)
 @pytest.mark.parametrize("fetch", ["one", "many", "all", "iter"])
 @pytest.mark.parametrize("row_factory", ["tuple_row", "dict_row", "namedtuple_row"])
-async def test_leak(aconn_cls, dsn, faker, fetch, row_factory):
+async def test_leak(aconn_cls, dsn, faker, fetch, row_factory, gc):
     faker.choose_schema(ncols=5)
     faker.make_records(10)
     row_factory = getattr(rows, row_factory)
@@ -116,11 +111,11 @@ async def test_leak(aconn_cls, dsn, faker, fetch, row_factory):
                         pass
 
     n = []
-    gc_collect()
+    gc.collect()
     for i in range(3):
         await work()
-        gc_collect()
-        n.append(gc_count())
+        gc.collect()
+        n.append(gc.count())
 
     assert n[0] == n[1] == n[2], f"objects leaked: {n[1] - n[0]}, {n[2] - n[1]}"
 
index 535aa01535c2ff242ea43e12f666a12b214e54a0..159e67cb1e92de995418ffe24bb6622c4af0c42f 100644 (file)
@@ -17,7 +17,7 @@ from psycopg import sql, rows
 from psycopg.adapt import PyFormat
 from psycopg.types import TypeInfo
 
-from .utils import gc_collect, raiseif
+from .utils import raiseif
 from .acompat import closing
 from .fix_crdb import crdb_encoding
 from ._test_cursor import my_row_factory, ph
@@ -123,7 +123,7 @@ def test_context(conn):
 
 
 @pytest.mark.slow
-def test_weakref(conn):
+def test_weakref(conn, gc_collect):
     cur = conn.cursor()
     w = weakref.ref(cur)
     cur.close()
index 4268fdd70606442a479e815e719180e84adb9a74..840de65ca47c84c7e846fe60c73743993bf0b6ef 100644 (file)
@@ -14,7 +14,7 @@ from psycopg import sql, rows
 from psycopg.adapt import PyFormat
 from psycopg.types import TypeInfo
 
-from .utils import gc_collect, raiseif
+from .utils import raiseif
 from .acompat import aclosing, alist, anext
 from .fix_crdb import crdb_encoding
 from ._test_cursor import my_row_factory, ph
@@ -121,7 +121,7 @@ async def test_context(aconn):
 
 
 @pytest.mark.slow
-async def test_weakref(aconn):
+async def test_weakref(aconn, gc_collect):
     cur = aconn.cursor()
     w = weakref.ref(cur)
     await cur.close()
index 76726aa8d8a312677642bc77919e3fac4e27f8c6..059d22d1c02d4acdf1ca3fb887eed29c5912ec77 100644 (file)
@@ -3,12 +3,10 @@
 # DO NOT CHANGE! Change the original file instead.
 import pytest
 import psycopg
-import sys
 from psycopg import pq, rows, errors as e
 from psycopg.adapt import PyFormat
 
 from ._test_cursor import ph
-from .utils import gc_collect, gc_count
 
 
 @pytest.fixture
@@ -72,14 +70,11 @@ def test_query_params_executemany(conn):
 
 
 @pytest.mark.slow
-@pytest.mark.skipif(
-    sys.implementation.name == "pypy", reason="depends on refcount semantics"
-)
 @pytest.mark.parametrize("fmt", PyFormat)
 @pytest.mark.parametrize("fmt_out", pq.Format)
 @pytest.mark.parametrize("fetch", ["one", "many", "all", "iter"])
 @pytest.mark.parametrize("row_factory", ["tuple_row", "dict_row", "namedtuple_row"])
-def test_leak(conn_cls, dsn, faker, fmt, fmt_out, fetch, row_factory):
+def test_leak(conn_cls, dsn, faker, fmt, fmt_out, fetch, row_factory, gc):
     faker.format = fmt
     faker.choose_schema(ncols=5)
     faker.make_records(10)
@@ -112,9 +107,9 @@ def test_leak(conn_cls, dsn, faker, fmt, fmt_out, fetch, row_factory):
                             pass
 
     n = []
-    gc_collect()
+    gc.collect()
     for i in range(3):
         work()
-        gc_collect()
-        n.append(gc_count())
+        gc.collect()
+        n.append(gc.count())
     assert n[0] == n[1] == n[2], f"objects leaked: {n[1] - n[0]}, {n[2] - n[1]}"
index 24d69cebb2591e18664546c4e67a18aded0f11d7..e6e85124a87ac897fc560cfd6b7abdb378b2a8a6 100644 (file)
@@ -1,11 +1,9 @@
 import pytest
 import psycopg
-import sys
 from psycopg import pq, rows, errors as e
 from psycopg.adapt import PyFormat
 
 from ._test_cursor import ph
-from .utils import gc_collect, gc_count
 
 
 @pytest.fixture
@@ -69,14 +67,11 @@ async def test_query_params_executemany(aconn):
 
 
 @pytest.mark.slow
-@pytest.mark.skipif(
-    sys.implementation.name == "pypy", reason="depends on refcount semantics"
-)
 @pytest.mark.parametrize("fmt", PyFormat)
 @pytest.mark.parametrize("fmt_out", pq.Format)
 @pytest.mark.parametrize("fetch", ["one", "many", "all", "iter"])
 @pytest.mark.parametrize("row_factory", ["tuple_row", "dict_row", "namedtuple_row"])
-async def test_leak(aconn_cls, dsn, faker, fmt, fmt_out, fetch, row_factory):
+async def test_leak(aconn_cls, dsn, faker, fmt, fmt_out, fetch, row_factory, gc):
     faker.format = fmt
     faker.choose_schema(ncols=5)
     faker.make_records(10)
@@ -109,9 +104,9 @@ async def test_leak(aconn_cls, dsn, faker, fmt, fmt_out, fetch, row_factory):
                             pass
 
     n = []
-    gc_collect()
+    gc.collect()
     for i in range(3):
         await work()
-        gc_collect()
-        n.append(gc_count())
+        gc.collect()
+        n.append(gc.count())
     assert n[0] == n[1] == n[2], f"objects leaked: {n[1] - n[0]}, {n[2] - n[1]}"
index 9fdfdbda71d282a1a167052fb75a1f374d87f458..b4259abb2f3bc127f9283e088817092514194189 100644 (file)
@@ -7,7 +7,6 @@ import psycopg
 from psycopg import rows, errors as e
 from psycopg.pq import Format
 
-from .utils import gc_collect
 
 pytestmark = pytest.mark.crdb_skip("server-side cursor")
 
@@ -256,7 +255,7 @@ def test_close_no_clobber(conn):
             cur.fetchall()
 
 
-def test_warn_close(conn, recwarn):
+def test_warn_close(conn, recwarn, gc_collect):
     recwarn.clear()
     cur = conn.cursor("foo")
     cur.execute("select generate_series(1, 10) as bar")
index 0417ab3c3bc6cd4556eefbb3cce2f777f28f17df..0f0efa2a96c69797e83bd6c97d69f3e5e192c763 100644 (file)
@@ -5,7 +5,7 @@ from psycopg import rows, errors as e
 from psycopg.pq import Format
 
 from .acompat import alist
-from .utils import gc_collect
+
 
 pytestmark = pytest.mark.crdb_skip("server-side cursor")
 
@@ -262,7 +262,7 @@ async def test_close_no_clobber(aconn):
             await cur.fetchall()
 
 
-async def test_warn_close(aconn, recwarn):
+async def test_warn_close(aconn, recwarn, gc_collect):
     recwarn.clear()
     cur = aconn.cursor("foo")
     await cur.execute("select generate_series(1, 10) as bar")
index ddf57513ce78218f2731708ffcfe6f82957e1d78..a5016ae32dc95da28d5bd208f671d1e5378b4c71 100644 (file)
@@ -9,7 +9,7 @@ import psycopg
 from psycopg import pq
 from psycopg import errors as e
 
-from .utils import eur, gc_collect
+from .utils import eur
 from .fix_crdb import is_crdb
 
 
@@ -187,7 +187,7 @@ def test_diag_pickle(conn):
     (pq.__impl__ in ("c", "binary") and sys.version_info[:2] == (3, 12)),
     reason="Something with Exceptions, C, Python 3.12",
 )
-def test_diag_survives_cursor(conn):
+def test_diag_survives_cursor(conn, gc_collect):
     cur = conn.cursor()
     with pytest.raises(e.Error) as exc:
         cur.execute("select * from nosuchtable")
index 5f84c5a192828e8fc5850ad9830981bee860b2fa..4da6a21490f0edf31ccffde0a4ea7f1ac33d7adf 100644 (file)
@@ -13,8 +13,6 @@ from psycopg.types import TypeInfo
 from psycopg.postgres import types as builtins
 from psycopg.types.array import register_array
 
-from ..utils import gc_collect
-
 
 tests_str = [
     ([[[[[["a"]]]]]], "{{{{{{a}}}}}}"),
@@ -342,7 +340,7 @@ def test_all_chars_with_bounds(conn, fmt_out):
 
 
 @pytest.mark.slow
-def test_register_array_leak(conn):
+def test_register_array_leak(conn, gc_collect):
     info = TypeInfo.fetch(conn, "date")
     ntypes = []
     for i in range(2):
index 88a6bb9a03479963f2dfbce792ced4c1663ab9c7..8eef880bd33cd2a2f13280d22308782af22de7c8 100644 (file)
@@ -1,4 +1,3 @@
-import gc
 import re
 import sys
 import operator
@@ -156,46 +155,6 @@ class VersionCheck:
         return (ver_maj, ver_min, ver_fix)
 
 
-def gc_collect():
-    """
-    gc.collect(), but more insisting.
-    """
-    for i in range(3):
-        gc.collect()
-
-
-NO_COUNT_TYPES: Tuple[type, ...] = ()
-
-if sys.version_info[:2] == (3, 10):
-    # On my laptop there are occasional creations of a single one of these objects
-    # with empty content, which might be some Decimal caching.
-    # Keeping the guard as strict as possible, to be extended if other types
-    # or versions are necessary.
-    try:
-        from _contextvars import Context  # type: ignore
-    except ImportError:
-        pass
-    else:
-        NO_COUNT_TYPES += (Context,)
-
-
-def gc_count() -> int:
-    """
-    len(gc.get_objects()), with subtleties.
-    """
-    if not NO_COUNT_TYPES:
-        return len(gc.get_objects())
-
-    # Note: not using a list comprehension because it pollutes the objects list.
-    rv = 0
-    for obj in gc.get_objects():
-        if isinstance(obj, NO_COUNT_TYPES):
-            continue
-        rv += 1
-
-    return rv
-
-
 @contextmanager
 def raiseif(cond, *args, **kwargs):
     """