Skip to content

Commit 295370a

Browse files
committed
test! significantly increase test coverage
1 parent 36607ac commit 295370a

1 file changed

Lines changed: 186 additions & 24 deletions

File tree

Lib/test/test_sqlite3/test_dbapi.py

Lines changed: 186 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@
3030
import unittest
3131
import urllib.parse
3232
import warnings
33+
from collections import Counter
3334

35+
from test import support
3436
from test.support import (
3537
SHORT_TIMEOUT, check_disallow_instantiation, requires_subprocess, subTests
3638
)
@@ -1747,18 +1749,53 @@ def ParamsCxCloseInNext(cx):
17471749
class 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

18261988
class 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

Comments
 (0)