diff options
author | Erlend Egeberg Aasland <erlend.aasland@innova.no> | 2021-08-28 18:26:00 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-28 18:26:00 (GMT) |
commit | 2a80893e5c023a73ccd32cc319f4f0404f548c00 (patch) | |
tree | 4af5a963d284756e2654bbfa4b8856d8a7204d09 /Lib/sqlite3/test/dbapi.py | |
parent | 81fa08c5ea2cf15254b951034b9d6c7358f96d79 (diff) | |
download | cpython-2a80893e5c023a73ccd32cc319f4f0404f548c00.zip cpython-2a80893e5c023a73ccd32cc319f4f0404f548c00.tar.gz cpython-2a80893e5c023a73ccd32cc319f4f0404f548c00.tar.bz2 |
[3.10] bpo-27334: roll back transaction if sqlite3 context manager fails to commit (GH-26202) (GH-27943)
Diffstat (limited to 'Lib/sqlite3/test/dbapi.py')
-rw-r--r-- | Lib/sqlite3/test/dbapi.py | 75 |
1 files changed, 74 insertions, 1 deletions
diff --git a/Lib/sqlite3/test/dbapi.py b/Lib/sqlite3/test/dbapi.py index 2d02045..5d521d0 100644 --- a/Lib/sqlite3/test/dbapi.py +++ b/Lib/sqlite3/test/dbapi.py @@ -20,12 +20,13 @@ # misrepresented as being the original software. # 3. This notice may not be removed or altered from any source distribution. +import subprocess import threading import unittest import sqlite3 as sqlite import sys -from test.support import check_disallow_instantiation +from test.support import check_disallow_instantiation, SHORT_TIMEOUT from test.support.os_helper import TESTFN, unlink @@ -958,6 +959,77 @@ class SqliteOnConflictTests(unittest.TestCase): self.assertEqual(self.cu.fetchall(), [('Very different data!', 'foo')]) +class MultiprocessTests(unittest.TestCase): + CONNECTION_TIMEOUT = SHORT_TIMEOUT / 1000. # Defaults to 30 ms + + def tearDown(self): + unlink(TESTFN) + + def test_ctx_mgr_rollback_if_commit_failed(self): + # bpo-27334: ctx manager does not rollback if commit fails + SCRIPT = f"""if 1: + import sqlite3 + def wait(): + print("started") + assert "database is locked" in input() + + cx = sqlite3.connect("{TESTFN}", timeout={self.CONNECTION_TIMEOUT}) + cx.create_function("wait", 0, wait) + with cx: + cx.execute("create table t(t)") + try: + # execute two transactions; both will try to lock the db + cx.executescript(''' + -- start a transaction and wait for parent + begin transaction; + select * from t; + select wait(); + rollback; + + -- start a new transaction; would fail if parent holds lock + begin transaction; + select * from t; + rollback; + ''') + finally: + cx.close() + """ + + # spawn child process + proc = subprocess.Popen( + [sys.executable, "-c", SCRIPT], + encoding="utf-8", + bufsize=0, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + ) + self.addCleanup(proc.communicate) + + # wait for child process to start + self.assertEqual("started", proc.stdout.readline().strip()) + + cx = sqlite.connect(TESTFN, timeout=self.CONNECTION_TIMEOUT) + try: # context manager should correctly release the db lock + with cx: + cx.execute("insert into t values('test')") + except sqlite.OperationalError as exc: + proc.stdin.write(str(exc)) + else: + proc.stdin.write("no error") + finally: + cx.close() + + # terminate child process + self.assertIsNone(proc.returncode) + try: + proc.communicate(input="end", timeout=SHORT_TIMEOUT) + except subprocess.TimeoutExpired: + proc.kill() + proc.communicate() + raise + self.assertEqual(proc.returncode, 0) + + def suite(): tests = [ ClosedConTests, @@ -967,6 +1039,7 @@ def suite(): CursorTests, ExtensionTests, ModuleTests, + MultiprocessTests, SqliteOnConflictTests, ThreadTests, UninitialisedConnectionTests, |