From 40e662dbe0fac7c88c0c6abf91c60f33ec807a11 Mon Sep 17 00:00:00 2001 From: Denis Laxalde Date: Tue, 10 May 2022 09:01:07 +0200 Subject: [PATCH] test: parametrize transaction tests with pipeline on/off The 'conn' fixture is overloaded in transaction tests, pulling the 'pipeline' fixture to have them run with or without a pipeline. When checking for connection's transaction state, just after entering the transaction() block (e.g. in test_basic), we need to sync the pipeline in order to get the state right. This is just for tests as the exact transaction state, in pipeline mode, is not deterministic since query results are not fetched directly. The following tests fail: tests/test_transaction.py::test_basic[pipeline=on] tests/test_transaction.py::test_begins_on_enter[pipeline=on] tests/test_transaction.py::test_commit_on_successful_exit[pipeline=on] tests/test_transaction.py::test_rollback_on_exception_exit[pipeline=on] tests/test_transaction.py::test_autocommit_off_but_no_tx_started_successful_exit[pipeline=on] tests/test_transaction.py::test_autocommit_off_but_no_tx_started_exception_exit[pipeline=on] tests/test_transaction.py::test_nested_all_changes_persisted_on_successful_exit[pipeline=on] tests/test_transaction.py::test_nested_all_changes_discarded_on_outer_exception[pipeline=on] tests/test_transaction.py::test_nested_all_changes_discarded_on_inner_exception[pipeline=on] tests/test_transaction.py::test_nested_inner_scope_exception_handled_in_outer_scope[pipeline=on] tests/test_transaction.py::test_nested_three_levels_successful_exit[pipeline=on] tests/test_transaction.py::test_named_savepoints_successful_exit[pipeline=on] tests/test_transaction.py::test_explicit_rollback_of_enclosing_tx_outer_tx_unaffected[pipeline=on] tests/test_transaction.py::test_str[pipeline=on] This is because the connection state is not correct at the end of transaction, we'll fix this in next commit. Some others were adjusted as follows: * when entering the transaction() block, e.g. in test_begins_on_enter, we eventually call pipeline.sync() in order to get the state right (this is just for tests as the exact transaction state, in pipeline mode, is not deterministic since query results are not fetched directly); * test_context_inerror_rollback_no_clobber[pipeline=on] is skipped, as explained in comment inline; * test_prohibits_use_of_commit_rollback_autocommit[pipeline=on] is xfail, because Connection._check_intrans() does not check for pipeline mode (see TODO inline about a possible solution); * test_str now checks for [, pipeline=ON] in str(Transaction). --- tests/test_transaction.py | 53 ++++++++++++++++++++++++------ tests/test_transaction_async.py | 57 +++++++++++++++++++++++++++------ 2 files changed, 90 insertions(+), 20 deletions(-) diff --git a/tests/test_transaction.py b/tests/test_transaction.py index 802ea0577..094746e27 100644 --- a/tests/test_transaction.py +++ b/tests/test_transaction.py @@ -7,6 +7,11 @@ import pytest from psycopg import Connection, ProgrammingError, Rollback +@pytest.fixture +def conn(conn, pipeline): + return conn + + @pytest.fixture(autouse=True) def create_test_table(svcconn): """Creates a table called 'test_table' for use in tests.""" @@ -70,10 +75,12 @@ class ExpectedException(Exception): pass -def test_basic(conn): +def test_basic(conn, pipeline): """Basic use of transaction() to BEGIN and COMMIT a transaction.""" assert not in_transaction(conn) with conn.transaction(): + if pipeline: + pipeline.sync() assert in_transaction(conn) assert not in_transaction(conn) @@ -103,11 +110,13 @@ def test_cant_reenter(conn): pass -def test_begins_on_enter(conn): +def test_begins_on_enter(conn, pipeline): """Transaction does not begin until __enter__() is called.""" tx = conn.transaction() assert not in_transaction(conn) with tx: + if pipeline: + pipeline.sync() assert in_transaction(conn) assert not in_transaction(conn) @@ -132,7 +141,11 @@ def test_rollback_on_exception_exit(conn): assert not inserted(conn) -def test_context_inerror_rollback_no_clobber(conn, dsn, caplog): +def test_context_inerror_rollback_no_clobber(conn, pipeline, dsn, caplog): + if pipeline: + # Only 'conn' is possibly in pipeline mode, but the transaction and + # checks are on 'conn2'. + pytest.skip("not applicable") caplog.set_level(logging.WARNING, logger="psycopg") with pytest.raises(ZeroDivisionError): @@ -186,12 +199,17 @@ def test_interaction_dbapi_transaction(conn): assert inserted(conn) == {"foo", "baz"} -def test_prohibits_use_of_commit_rollback_autocommit(conn): +def test_prohibits_use_of_commit_rollback_autocommit(conn, pipeline): """ Within a Transaction block, it is forbidden to touch commit, rollback, or the autocommit setting on the connection, as this would interfere with the transaction scope being managed by the Transaction block. """ + if pipeline: + # TODO: Fixing Connection._check_intrans() would require calling + # conn._pipeline.sync(), which implies turning _check_intrans() into a + # generator method. + pytest.xfail("Connection._check_intrans() does not account for pipeline mode") conn.autocommit = False conn.commit() conn.rollback() @@ -264,7 +282,7 @@ def test_autocommit_off_but_no_tx_started_exception_exit(conn, svcconn): assert not inserted(svcconn) -def test_autocommit_off_and_tx_in_progress_successful_exit(conn, svcconn): +def test_autocommit_off_and_tx_in_progress_successful_exit(conn, pipeline, svcconn): """ Scenario: * Connection has autocommit off but and a transaction is already in @@ -278,6 +296,8 @@ def test_autocommit_off_and_tx_in_progress_successful_exit(conn, svcconn): """ conn.autocommit = False insert_row(conn, "prior") + if pipeline: + pipeline.sync() assert in_transaction(conn) with conn.transaction(): insert_row(conn, "new") @@ -287,7 +307,7 @@ def test_autocommit_off_and_tx_in_progress_successful_exit(conn, svcconn): assert not inserted(svcconn) -def test_autocommit_off_and_tx_in_progress_exception_exit(conn, svcconn): +def test_autocommit_off_and_tx_in_progress_exception_exit(conn, pipeline, svcconn): """ Scenario: * Connection has autocommit off but and a transaction is already in @@ -302,6 +322,8 @@ def test_autocommit_off_and_tx_in_progress_exception_exit(conn, svcconn): """ conn.autocommit = False insert_row(conn, "prior") + if pipeline: + pipeline.sync() assert in_transaction(conn) with pytest.raises(ExpectedException): with conn.transaction(): @@ -643,16 +665,27 @@ def test_explicit_rollback_of_enclosing_tx_outer_tx_unaffected(conn, svcconn): assert inserted(svcconn) == {"outer-before", "outer-after"} -def test_str(conn): +def test_str(conn, pipeline): with conn.transaction() as tx: - assert "[INTRANS]" in str(tx) + if pipeline: + assert "INTRANS" not in str(tx) + pipeline.sync() + assert "[INTRANS, pipeline=ON]" in str(tx) + else: + assert "[INTRANS]" in str(tx) assert "(active)" in str(tx) assert "'" not in str(tx) with conn.transaction("wat") as tx2: - assert "[INTRANS]" in str(tx2) + if pipeline: + assert "[INTRANS, pipeline=ON]" in str(tx2) + else: + assert "[INTRANS]" in str(tx2) assert "'wat'" in str(tx2) - assert "[IDLE]" in str(tx) + if pipeline: + assert "[IDLE, pipeline=ON]" in str(tx) + else: + assert "[IDLE]" in str(tx) assert "(terminated)" in str(tx) with pytest.raises(ZeroDivisionError): diff --git a/tests/test_transaction_async.py b/tests/test_transaction_async.py index 3c01c4631..1af82484e 100644 --- a/tests/test_transaction_async.py +++ b/tests/test_transaction_async.py @@ -13,10 +13,17 @@ from .test_transaction import create_test_table # noqa # autouse fixture pytestmark = pytest.mark.asyncio -async def test_basic(aconn): +@pytest.fixture +async def aconn(aconn, apipeline): + return aconn + + +async def test_basic(aconn, apipeline): """Basic use of transaction() to BEGIN and COMMIT a transaction.""" assert not in_transaction(aconn) async with aconn.transaction(): + if apipeline: + await apipeline.sync() assert in_transaction(aconn) assert not in_transaction(aconn) @@ -46,11 +53,13 @@ async def test_cant_reenter(aconn): pass -async def test_begins_on_enter(aconn): +async def test_begins_on_enter(aconn, apipeline): """Transaction does not begin until __enter__() is called.""" tx = aconn.transaction() assert not in_transaction(aconn) async with tx: + if apipeline: + await apipeline.sync() assert in_transaction(aconn) assert not in_transaction(aconn) @@ -75,7 +84,11 @@ async def test_rollback_on_exception_exit(aconn): assert not await inserted(aconn) -async def test_context_inerror_rollback_no_clobber(aconn, dsn, caplog): +async def test_context_inerror_rollback_no_clobber(aconn, apipeline, dsn, caplog): + if apipeline: + # Only 'aconn' is possibly in pipeline mode, but the transaction and + # checks are on 'conn2'. + pytest.skip("not applicable") caplog.set_level(logging.WARNING, logger="psycopg") with pytest.raises(ZeroDivisionError): @@ -129,12 +142,17 @@ async def test_interaction_dbapi_transaction(aconn): assert await inserted(aconn) == {"foo", "baz"} -async def test_prohibits_use_of_commit_rollback_autocommit(aconn): +async def test_prohibits_use_of_commit_rollback_autocommit(aconn, apipeline): """ Within a Transaction block, it is forbidden to touch commit, rollback, or the autocommit setting on the connection, as this would interfere with the transaction scope being managed by the Transaction block. """ + if apipeline: + # TODO: Fixing Connection._check_intrans() would require calling + # conn._pipeline.sync(), which implies turning _check_intrans() into a + # generator method. + pytest.xfail("Connection._check_intrans() does not account for pipeline mode") await aconn.set_autocommit(False) await aconn.commit() await aconn.rollback() @@ -207,7 +225,9 @@ async def test_autocommit_off_but_no_tx_started_exception_exit(aconn, svcconn): assert not inserted(svcconn) -async def test_autocommit_off_and_tx_in_progress_successful_exit(aconn, svcconn): +async def test_autocommit_off_and_tx_in_progress_successful_exit( + aconn, apipeline, svcconn +): """ Scenario: * Connection has autocommit off but and a transaction is already in @@ -221,6 +241,8 @@ async def test_autocommit_off_and_tx_in_progress_successful_exit(aconn, svcconn) """ await aconn.set_autocommit(False) await insert_row(aconn, "prior") + if apipeline: + await apipeline.sync() assert in_transaction(aconn) async with aconn.transaction(): await insert_row(aconn, "new") @@ -230,7 +252,9 @@ async def test_autocommit_off_and_tx_in_progress_successful_exit(aconn, svcconn) assert not inserted(svcconn) -async def test_autocommit_off_and_tx_in_progress_exception_exit(aconn, svcconn): +async def test_autocommit_off_and_tx_in_progress_exception_exit( + aconn, apipeline, svcconn +): """ Scenario: * Connection has autocommit off but and a transaction is already in @@ -245,6 +269,8 @@ async def test_autocommit_off_and_tx_in_progress_exception_exit(aconn, svcconn): """ await aconn.set_autocommit(False) await insert_row(aconn, "prior") + if apipeline: + await apipeline.sync() assert in_transaction(aconn) with pytest.raises(ExpectedException): async with aconn.transaction(): @@ -590,16 +616,27 @@ async def test_explicit_rollback_of_enclosing_tx_outer_tx_unaffected(aconn, svcc assert inserted(svcconn) == {"outer-before", "outer-after"} -async def test_str(aconn): +async def test_str(aconn, apipeline): async with aconn.transaction() as tx: - assert "[INTRANS]" in str(tx) + if apipeline: + assert "[INTRANS]" not in str(tx) + await apipeline.sync() + assert "[INTRANS, pipeline=ON]" in str(tx) + else: + assert "[INTRANS]" in str(tx) assert "(active)" in str(tx) assert "'" not in str(tx) async with aconn.transaction("wat") as tx2: - assert "[INTRANS]" in str(tx2) + if apipeline: + assert "[INTRANS, pipeline=ON]" in str(tx2) + else: + assert "[INTRANS]" in str(tx2) assert "'wat'" in str(tx2) - assert "[IDLE]" in str(tx) + if apipeline: + assert "[IDLE, pipeline=ON]" in str(tx) + else: + assert "[IDLE]" in str(tx) assert "(terminated)" in str(tx) with pytest.raises(ZeroDivisionError): -- 2.47.2