21
21
# misrepresented as being the original software.
22
22
# 3. This notice may not be removed or altered from any source distribution.
23
23
24
+ import subprocess
24
25
import threading
25
26
import unittest
26
27
import sqlite3 as sqlite
28
+ import sys
27
29
28
- from test .support import TESTFN , unlink
30
+ from test .support import SHORT_TIMEOUT , TESTFN , unlink
29
31
30
32
31
33
class ModuleTests (unittest .TestCase ):
@@ -954,6 +956,77 @@ def CheckOnConflictReplace(self):
954
956
self .assertEqual (self .cu .fetchall (), [('Very different data!' , 'foo' )])
955
957
956
958
959
+ class MultiprocessTests (unittest .TestCase ):
960
+ CONNECTION_TIMEOUT = SHORT_TIMEOUT / 1000. # Defaults to 30 ms
961
+
962
+ def tearDown (self ):
963
+ unlink (TESTFN )
964
+
965
+ def test_ctx_mgr_rollback_if_commit_failed (self ):
966
+ # bpo-27334: ctx manager does not rollback if commit fails
967
+ SCRIPT = f"""if 1:
968
+ import sqlite3
969
+ def wait():
970
+ print("started")
971
+ assert "database is locked" in input()
972
+
973
+ cx = sqlite3.connect("{ TESTFN } ", timeout={ self .CONNECTION_TIMEOUT } )
974
+ cx.create_function("wait", 0, wait)
975
+ with cx:
976
+ cx.execute("create table t(t)")
977
+ try:
978
+ # execute two transactions; both will try to lock the db
979
+ cx.executescript('''
980
+ -- start a transaction and wait for parent
981
+ begin transaction;
982
+ select * from t;
983
+ select wait();
984
+ rollback;
985
+
986
+ -- start a new transaction; would fail if parent holds lock
987
+ begin transaction;
988
+ select * from t;
989
+ rollback;
990
+ ''')
991
+ finally:
992
+ cx.close()
993
+ """
994
+
995
+ # spawn child process
996
+ proc = subprocess .Popen (
997
+ [sys .executable , "-c" , SCRIPT ],
998
+ encoding = "utf-8" ,
999
+ bufsize = 0 ,
1000
+ stdin = subprocess .PIPE ,
1001
+ stdout = subprocess .PIPE ,
1002
+ )
1003
+ self .addCleanup (proc .communicate )
1004
+
1005
+ # wait for child process to start
1006
+ self .assertEqual ("started" , proc .stdout .readline ().strip ())
1007
+
1008
+ cx = sqlite .connect (TESTFN , timeout = self .CONNECTION_TIMEOUT )
1009
+ try : # context manager should correctly release the db lock
1010
+ with cx :
1011
+ cx .execute ("insert into t values('test')" )
1012
+ except sqlite .OperationalError as exc :
1013
+ proc .stdin .write (str (exc ))
1014
+ else :
1015
+ proc .stdin .write ("no error" )
1016
+ finally :
1017
+ cx .close ()
1018
+
1019
+ # terminate child process
1020
+ self .assertIsNone (proc .returncode )
1021
+ try :
1022
+ proc .communicate (input = "end" , timeout = SHORT_TIMEOUT )
1023
+ except subprocess .TimeoutExpired :
1024
+ proc .kill ()
1025
+ proc .communicate ()
1026
+ raise
1027
+ self .assertEqual (proc .returncode , 0 )
1028
+
1029
+
957
1030
def suite ():
958
1031
module_suite = unittest .makeSuite (ModuleTests , "Check" )
959
1032
connection_suite = unittest .makeSuite (ConnectionTests , "Check" )
@@ -965,10 +1038,11 @@ def suite():
965
1038
closed_cur_suite = unittest .makeSuite (ClosedCurTests , "Check" )
966
1039
on_conflict_suite = unittest .makeSuite (SqliteOnConflictTests , "Check" )
967
1040
uninit_con_suite = unittest .makeSuite (UninitialisedConnectionTests )
1041
+ multiproc_con_suite = unittest .makeSuite (MultiprocessTests )
968
1042
return unittest .TestSuite ((
969
1043
module_suite , connection_suite , cursor_suite , thread_suite ,
970
1044
constructor_suite , ext_suite , closed_con_suite , closed_cur_suite ,
971
- on_conflict_suite , uninit_con_suite ,
1045
+ on_conflict_suite , uninit_con_suite , multiproc_con_suite ,
972
1046
))
973
1047
974
1048
def test ():
0 commit comments