3030import unittest
3131import urllib .parse
3232import warnings
33+ from collections import Counter
3334
35+ from test import support
3436from test .support import (
3537 SHORT_TIMEOUT , check_disallow_instantiation , requires_subprocess , subTests
3638)
@@ -1747,18 +1749,53 @@ def ParamsCxCloseInNext(cx):
17471749class ExecutionConcurrentlyCloseConnectionBaseTests :
17481750 """Tests when execute() and executemany() concurrently close the connection."""
17491751
1750- def inittest (self ):
1752+ @classmethod
1753+ def setUpClass (cls ):
1754+ super ().setUpClass ()
1755+ cls .sqlite = import_helper .import_fresh_module ("sqlite3" , fresh = ["_sqlite3" ])
1756+
1757+ def inittest (self , ** adapters ):
17511758 """Return a pair (connection, connection or cursor) to use in tests."""
1752- cx = sqlite .connect (":memory:" )
1753- cx .execute ("create table tmp(a number)" )
1759+ # Counter for the number of calls to the tracked functions.
1760+ self .ncalls = Counter ()
1761+ self .colname = "a"
1762+
1763+ cx = self .sqlite .connect (":memory:" )
1764+ # table to use to query the database to ensure that it's not closed
1765+ cx .execute (f"create table canary({ self .colname } nunmber)" )
1766+ cx .execute (f"insert into canary({ self .colname } ) values (?)" , (1 ,))
1767+ cx .execute (f"create table tmp({ self .colname } number)" )
17541768 self .addCleanup (cx .close )
1755- return cx , self .interface (cx )
1769+ return cx , self .executor (cx )
17561770
1757- def interface (self , connection ):
1771+ def executor (self , connection ):
17581772 """Return a cursor-like interface from a given SQLite3 connection."""
17591773 raise NotImplementedError
17601774
1761- def test_execute_use___getitem__ (self ):
1775+ def check_alive (self , executor ):
1776+ # check that the connection is alive by making a dummy query
1777+ res = executor .execute ("SELECT * from canary" ).fetchall ()
1778+ self .assertEqual (res , [(1 ,)])
1779+
1780+ def check_execute (self , executor , payload , * , named = False ):
1781+ self .assertEqual (self .ncalls .total (), 0 )
1782+ self .check_alive (executor )
1783+ msg = r"Cannot operate on a closed database\."
1784+ binding = "(:a)" if named else "(?)"
1785+ with self .assertRaisesRegex (self .sqlite .ProgrammingError , msg ):
1786+ executor .execute (f"insert into tmp(a) values { binding } " , payload )
1787+
1788+ def check_executemany (self , executor , payload , * , named = False ):
1789+ self .assertEqual (self .ncalls .total (), 0 )
1790+ self .check_alive (executor )
1791+ msg = r"Cannot operate on a closed database\."
1792+ binding = "(:a)" if named else "(?)"
1793+ with self .assertRaisesRegex (self .sqlite .ProgrammingError , msg ):
1794+ executor .executemany (f"insert into tmp(a) values { binding } " , payload )
1795+
1796+ # Simple tests
1797+
1798+ def test_execute (self ):
17621799 # Prevent SIGSEGV when closing the connection while binding parameters.
17631800 #
17641801 # Internally, the connection's state is checked after bind_parameters().
@@ -1769,33 +1806,31 @@ def test_execute_use___getitem__(self):
17691806 # Regression test for https://github.com/python/cpython/issues/143198.
17701807
17711808 class PT :
1772- def __getitem__ (self , i ):
1809+ def __getitem__ (_ , i ):
1810+ self .ncalls [None ] += 1
17731811 cx .close ()
17741812 return 1
17751813 def __len__ (self ):
17761814 return 1
17771815
1778- cx , cu = self .inittest ()
1779- msg = r"Cannot operate on a closed database\."
1780- with self .assertRaisesRegex (sqlite .ProgrammingError , msg ):
1781- cu .execute ("insert into tmp(a) values (?)" , PT ())
1816+ cx , ex = self .inittest ()
1817+ self .check_execute (ex , PT ())
1818+ self .assertEqual (self .ncalls [None ], 1 )
17821819
17831820 @subTests ("params_factory" , (ParamsCxCloseInIterMany , ParamsCxCloseInNext ))
1784- def test_executemany_use___iter__ (self , params_factory ):
1821+ def test_executemany_iterator (self , params_factory ):
17851822 # Prevent SIGSEGV with iterable of parameters closing the connection.
17861823 # Regression test for https://github.com/python/cpython/issues/143198.
1787- cx , cu = self .inittest ()
1788- msg = r"Cannot operate on a closed database\."
1789- with self .assertRaisesRegex (sqlite .ProgrammingError , msg ):
1790- cu .executemany ("insert into tmp(a) values (?)" , params_factory (cx ))
1824+ cx , ex = self .inittest ()
1825+ self .check_executemany (ex , params_factory (cx ))
17911826
17921827 # The test constructs an iterable of parameters of length 'n'
17931828 # and the connection is closed when we access the j-th one.
17941829 # The iterable is of type 'map' but the test wraps that map
17951830 # with 'iterable_wrapper' to exercise internals.
17961831 @subTests (("j" , "n" ), ([0 , 1 ], [0 , 3 ], [1 , 3 ], [2 , 3 ]))
17971832 @subTests ("iterable_wrapper" , (list , lambda x : x , lambda x : iter (x )))
1798- def test_executemany_use___getitem__ (self , j , n , iterable_wrapper ):
1833+ def test_executemany_iterable (self , j , n , iterable_wrapper ):
17991834 # Prevent SIGSEGV when closing the connection while binding parameters.
18001835 #
18011836 # Internally, the connection's state is checked after bind_parameters().
@@ -1805,30 +1840,157 @@ def test_executemany_use___getitem__(self, j, n, iterable_wrapper):
18051840 #
18061841 # Regression test for https://github.com/python/cpython/issues/143198.
18071842
1808- cx , cu = self .inittest ()
1809-
18101843 class PT :
1844+ case = self
18111845 def __init__ (self , value ):
18121846 self .value = value
18131847 def __getitem__ (self , i ):
18141848 if self .value == j :
1849+ self .case .ncalls [None ] = j
18151850 cx .close ()
18161851 return self .value
18171852 def __len__ (self ):
18181853 return 1
18191854
1820- msg = r"Cannot operate on a closed database\."
1855+ cx , ex = self . inittest ()
18211856 items = iterable_wrapper (map (PT , range (n )))
1822- with self .assertRaisesRegex (sqlite .ProgrammingError , msg ):
1823- cu .executemany ("insert into tmp(a) values (?)" , items )
1857+ self .check_executemany (ex , items )
1858+ self .assertEqual (self .ncalls [None ], j )
1859+
1860+ # Tests when the SQL parameters are given as a sequence.
1861+
1862+ def test_invalid_params_sequence_size (self ):
1863+ class I :
1864+ def __index__ (_ ):
1865+ self .ncalls ["I.__index__" ] += 1
1866+ cx .close ()
1867+ return 1
1868+
1869+ class S : # emulate a non-native sequence object
1870+ def __getitem__ (self ):
1871+ raise RuntimeError ("must not be called" )
1872+ def __len__ (_ ):
1873+ self .ncalls ["S.__len__" ] += 1
1874+ return I ()
1875+
1876+ cx , ex = self .inittest ()
1877+ self .check_execute (ex , S ())
1878+ self .assertEqual (self .ncalls ["S.__len__" ], 1 )
1879+ self .assertEqual (self .ncalls ["I.__index__" ], 1 )
1880+
1881+ cx , ex = self .inittest ()
1882+ self .check_executemany (ex , [S ()])
1883+ self .assertEqual (self .ncalls ["S.__len__" ], 1 )
1884+ self .assertEqual (self .ncalls ["I.__index__" ], 1 )
1885+
1886+ def test_invalid_params_sequence_item (self ):
1887+ class S : # emulate a non-native sequence object
1888+ def __getitem__ (_ , i ):
1889+ self .ncalls [None ] += 1
1890+ cx .close ()
1891+ return 1
1892+ def __len__ (self ):
1893+ return 1
1894+
1895+ cx , ex = self .inittest ()
1896+ self .check_execute (ex , S ())
1897+ self .assertEqual (self .ncalls [None ], 1 )
1898+
1899+ cx , ex = self .inittest ()
1900+ self .check_executemany (ex , [S ()])
1901+ self .assertEqual (self .ncalls [None ], 1 )
1902+
1903+ def test_invalid_params_sequence_item_close_in_adapter (self ):
1904+ class S : pass
1905+ def adapter (s ):
1906+ self .ncalls [None ] += 1
1907+ cx .close ()
1908+ return 1
1909+
1910+ self .sqlite .register_adapter (S , adapter )
1911+
1912+ cx , ex = self .inittest ()
1913+ self .check_execute (ex , [S ()])
1914+ self .assertEqual (self .ncalls [None ], 1 )
1915+
1916+ cx , ex = self .inittest ()
1917+ self .check_executemany (ex , [[S ()]])
1918+ self .assertEqual (self .ncalls [None ], 1 )
1919+
1920+ def test_invalid_params_sequence_item_close_after_adapted (self ):
1921+ class B (bytearray ):
1922+ def __buffer__ (_ , flags ):
1923+ self .ncalls [None ] += 1
1924+ cx .close ()
1925+ return super ().__buffer__ (flags )
1926+
1927+ cx , ex = self .inittest ()
1928+ self .check_execute (ex , [B ()])
1929+ self .assertEqual (self .ncalls [None ], 1 )
1930+
1931+ cx , ex = self .inittest ()
1932+ self .check_executemany (ex , [[B ()]])
1933+ self .assertEqual (self .ncalls [None ], 1 )
1934+
1935+ # Tests when the SQL parameters are given as a mapping.
1936+
1937+ def test_invalid_params_mapping_item (self ):
1938+ class S (dict ):
1939+ def __getitem__ (_ , key ):
1940+ self .assertEqual (key , self .colname )
1941+ self .ncalls [self .colname ] += 1
1942+ cx .close ()
1943+ return 1
1944+ def __len__ (self ):
1945+ return 1
1946+
1947+ cx , ex = self .inittest ()
1948+ self .check_execute (ex , S (), named = True )
1949+ self .assertEqual (self .ncalls [self .colname ], 1 )
1950+
1951+ cx , ex = self .inittest ()
1952+ self .check_executemany (ex , [S ()], named = True )
1953+ self .assertEqual (self .ncalls [self .colname ], 1 )
1954+
1955+ def test_invalid_params_mapping_item_close_in_adapter (self ):
1956+ class S : pass
1957+ def adapter (s ):
1958+ self .ncalls [None ] += 1
1959+ cx .close ()
1960+ return 1
1961+
1962+ self .sqlite .register_adapter (S , adapter )
1963+
1964+ cx , ex = self .inittest ()
1965+ self .check_execute (ex , {self .colname : S ()}, named = True )
1966+ self .assertEqual (self .ncalls [None ], 1 )
1967+
1968+ cx , ex = self .inittest ()
1969+ self .check_executemany (ex , [{self .colname : S ()}], named = True )
1970+ self .assertEqual (self .ncalls [None ], 1 )
1971+
1972+ def test_invalid_params_mapping_item_close_after_adapted (self ):
1973+ class B (bytearray ):
1974+ def __buffer__ (_ , flags ):
1975+ self .ncalls [None ] += 1
1976+ cx .close ()
1977+ return super ().__buffer__ (flags )
1978+
1979+ cx , ex = self .inittest ()
1980+ self .check_execute (ex , {self .colname : B ()}, named = True )
1981+ self .assertEqual (self .ncalls [None ], 1 )
1982+
1983+ cx , ex = self .inittest ()
1984+ self .check_executemany (ex , [{self .colname : B ()}], named = True )
1985+ self .assertEqual (self .ncalls [None ], 1 )
18241986
18251987
18261988class ConnectionExecutionConcurrentlyCloseConnectionTests (
18271989 ExecutionConcurrentlyCloseConnectionBaseTests ,
18281990 unittest .TestCase ,
18291991):
18301992 """Regression tests for conn.execute() and conn.executemany()."""
1831- def interface (self , connection ):
1993+ def executor (self , connection ):
18321994 return connection
18331995
18341996
@@ -1837,7 +1999,7 @@ class CursorExecutionConcurrentlyCloseConnectionTests(
18371999 unittest .TestCase ,
18382000):
18392001 """Regression tests for cursor.execute() and cursor.executemany()."""
1840- def interface (self , connection ):
2002+ def executor (self , connection ):
18412003 return connection .cursor ()
18422004
18432005
0 commit comments