Skip to content

Commit f5c5f32

Browse files
miss-islingtonerlend-aaslandserhiy-storchaka
authored
[3.12] gh-108550: Speed up sqlite3 tests (GH-108551) (#108566)
gh-108550: Speed up sqlite3 tests (GH-108551) Refactor the CLI so we can easily invoke it and mock command-line arguments. Adapt the CLI tests so we no longer have to launch a separate process. Disable the busy handler for all concurrency tests; we have full control over the order of the SQLite C API calls, so we can safely do this. The sqlite3 test suite now completes ~8 times faster than before. (cherry picked from commit 0e8b3fc) Co-authored-by: Erlend E. Aasland <[email protected]> Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent b451e90 commit f5c5f32

File tree

4 files changed

+74
-101
lines changed

4 files changed

+74
-101
lines changed

Lib/sqlite3/__main__.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def runsource(self, source, filename="<input>", symbol="single"):
6262
return False
6363

6464

65-
def main():
65+
def main(*args):
6666
parser = ArgumentParser(
6767
description="Python sqlite3 CLI",
6868
prog="python -m sqlite3",
@@ -86,7 +86,7 @@ def main():
8686
version=f"SQLite version {sqlite3.sqlite_version}",
8787
help="Print underlying SQLite library version",
8888
)
89-
args = parser.parse_args()
89+
args = parser.parse_args(*args)
9090

9191
if args.filename == ":memory:":
9292
db_name = "a transient in-memory database"
@@ -120,5 +120,8 @@ def main():
120120
finally:
121121
con.close()
122122

123+
sys.exit(0)
123124

124-
main()
125+
126+
if __name__ == "__main__":
127+
main(sys.argv)

Lib/test/test_sqlite3/test_cli.py

+61-87
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,35 @@
11
"""sqlite3 CLI tests."""
2-
3-
import sqlite3 as sqlite
4-
import subprocess
5-
import sys
2+
import sqlite3
63
import unittest
74

8-
from test.support import SHORT_TIMEOUT, requires_subprocess
5+
from sqlite3.__main__ import main as cli
96
from test.support.os_helper import TESTFN, unlink
7+
from test.support import captured_stdout, captured_stderr, captured_stdin
108

119

12-
@requires_subprocess()
1310
class CommandLineInterface(unittest.TestCase):
1411

1512
def _do_test(self, *args, expect_success=True):
16-
with subprocess.Popen(
17-
[sys.executable, "-Xutf8", "-m", "sqlite3", *args],
18-
encoding="utf-8",
19-
bufsize=0,
20-
stdout=subprocess.PIPE,
21-
stderr=subprocess.PIPE,
22-
) as proc:
23-
proc.wait()
24-
if expect_success == bool(proc.returncode):
25-
self.fail("".join(proc.stderr))
26-
stdout = proc.stdout.read()
27-
stderr = proc.stderr.read()
28-
if expect_success:
29-
self.assertEqual(stderr, "")
30-
else:
31-
self.assertEqual(stdout, "")
32-
return stdout, stderr
13+
with (
14+
captured_stdout() as out,
15+
captured_stderr() as err,
16+
self.assertRaises(SystemExit) as cm
17+
):
18+
cli(args)
19+
return out.getvalue(), err.getvalue(), cm.exception.code
3320

3421
def expect_success(self, *args):
35-
out, _ = self._do_test(*args)
22+
out, err, code = self._do_test(*args)
23+
self.assertEqual(code, 0,
24+
"\n".join([f"Unexpected failure: {args=}", out, err]))
25+
self.assertEqual(err, "")
3626
return out
3727

3828
def expect_failure(self, *args):
39-
_, err = self._do_test(*args, expect_success=False)
29+
out, err, code = self._do_test(*args, expect_success=False)
30+
self.assertNotEqual(code, 0,
31+
"\n".join([f"Unexpected failure: {args=}", out, err]))
32+
self.assertEqual(out, "")
4033
return err
4134

4235
def test_cli_help(self):
@@ -45,7 +38,7 @@ def test_cli_help(self):
4538

4639
def test_cli_version(self):
4740
out = self.expect_success("-v")
48-
self.assertIn(sqlite.sqlite_version, out)
41+
self.assertIn(sqlite3.sqlite_version, out)
4942

5043
def test_cli_execute_sql(self):
5144
out = self.expect_success(":memory:", "select 1")
@@ -68,87 +61,68 @@ def test_cli_on_disk_db(self):
6861
self.assertIn("(0,)", out)
6962

7063

71-
@requires_subprocess()
7264
class InteractiveSession(unittest.TestCase):
73-
TIMEOUT = SHORT_TIMEOUT / 10.
7465
MEMORY_DB_MSG = "Connected to a transient in-memory database"
7566
PS1 = "sqlite> "
7667
PS2 = "... "
7768

78-
def start_cli(self, *args):
79-
return subprocess.Popen(
80-
[sys.executable, "-Xutf8", "-m", "sqlite3", *args],
81-
encoding="utf-8",
82-
bufsize=0,
83-
stdin=subprocess.PIPE,
84-
# Note: the banner is printed to stderr, the prompt to stdout.
85-
stdout=subprocess.PIPE,
86-
stderr=subprocess.PIPE,
87-
)
88-
89-
def expect_success(self, proc):
90-
proc.wait()
91-
if proc.returncode:
92-
self.fail("".join(proc.stderr))
69+
def run_cli(self, *args, commands=()):
70+
with (
71+
captured_stdin() as stdin,
72+
captured_stdout() as stdout,
73+
captured_stderr() as stderr,
74+
self.assertRaises(SystemExit) as cm
75+
):
76+
for cmd in commands:
77+
stdin.write(cmd + "\n")
78+
stdin.seek(0)
79+
cli(args)
80+
81+
out = stdout.getvalue()
82+
err = stderr.getvalue()
83+
self.assertEqual(cm.exception.code, 0,
84+
f"Unexpected failure: {args=}\n{out}\n{err}")
85+
return out, err
9386

9487
def test_interact(self):
95-
with self.start_cli() as proc:
96-
out, err = proc.communicate(timeout=self.TIMEOUT)
97-
self.assertIn(self.MEMORY_DB_MSG, err)
98-
self.assertIn(self.PS1, out)
99-
self.expect_success(proc)
88+
out, err = self.run_cli()
89+
self.assertIn(self.MEMORY_DB_MSG, err)
90+
self.assertIn(self.PS1, out)
10091

10192
def test_interact_quit(self):
102-
with self.start_cli() as proc:
103-
out, err = proc.communicate(input=".quit", timeout=self.TIMEOUT)
104-
self.assertIn(self.MEMORY_DB_MSG, err)
105-
self.assertIn(self.PS1, out)
106-
self.expect_success(proc)
93+
out, err = self.run_cli(commands=(".quit",))
94+
self.assertIn(self.PS1, out)
10795

10896
def test_interact_version(self):
109-
with self.start_cli() as proc:
110-
out, err = proc.communicate(input=".version", timeout=self.TIMEOUT)
111-
self.assertIn(self.MEMORY_DB_MSG, err)
112-
self.assertIn(sqlite.sqlite_version, out)
113-
self.expect_success(proc)
97+
out, err = self.run_cli(commands=(".version",))
98+
self.assertIn(self.MEMORY_DB_MSG, err)
99+
self.assertIn(sqlite3.sqlite_version, out)
114100

115101
def test_interact_valid_sql(self):
116-
with self.start_cli() as proc:
117-
out, err = proc.communicate(input="select 1;",
118-
timeout=self.TIMEOUT)
119-
self.assertIn(self.MEMORY_DB_MSG, err)
120-
self.assertIn("(1,)", out)
121-
self.expect_success(proc)
102+
out, err = self.run_cli(commands=("SELECT 1;",))
103+
self.assertIn(self.MEMORY_DB_MSG, err)
104+
self.assertIn("(1,)", out)
122105

123106
def test_interact_valid_multiline_sql(self):
124-
with self.start_cli() as proc:
125-
out, err = proc.communicate(input="select 1\n;",
126-
timeout=self.TIMEOUT)
127-
self.assertIn(self.MEMORY_DB_MSG, err)
128-
self.assertIn(self.PS2, out)
129-
self.assertIn("(1,)", out)
130-
self.expect_success(proc)
107+
out, err = self.run_cli(commands=("SELECT 1\n;",))
108+
self.assertIn(self.MEMORY_DB_MSG, err)
109+
self.assertIn(self.PS2, out)
110+
self.assertIn("(1,)", out)
131111

132112
def test_interact_invalid_sql(self):
133-
with self.start_cli() as proc:
134-
out, err = proc.communicate(input="sel;", timeout=self.TIMEOUT)
135-
self.assertIn(self.MEMORY_DB_MSG, err)
136-
self.assertIn("OperationalError (SQLITE_ERROR)", err)
137-
self.expect_success(proc)
113+
out, err = self.run_cli(commands=("sel;",))
114+
self.assertIn(self.MEMORY_DB_MSG, err)
115+
self.assertIn("OperationalError (SQLITE_ERROR)", err)
138116

139117
def test_interact_on_disk_file(self):
140118
self.addCleanup(unlink, TESTFN)
141-
with self.start_cli(TESTFN) as proc:
142-
out, err = proc.communicate(input="create table t(t);",
143-
timeout=self.TIMEOUT)
144-
self.assertIn(TESTFN, err)
145-
self.assertIn(self.PS1, out)
146-
self.expect_success(proc)
147-
with self.start_cli(TESTFN, "select count(t) from t") as proc:
148-
out = proc.stdout.read()
149-
err = proc.stderr.read()
150-
self.assertIn("(0,)", out)
151-
self.expect_success(proc)
119+
120+
out, err = self.run_cli(TESTFN, commands=("CREATE TABLE t(t);",))
121+
self.assertIn(TESTFN, err)
122+
self.assertIn(self.PS1, out)
123+
124+
out, _ = self.run_cli(TESTFN, commands=("SELECT count(t) FROM t;",))
125+
self.assertIn("(0,)", out)
152126

153127

154128
if __name__ == "__main__":

Lib/test/test_sqlite3/test_dbapi.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1871,7 +1871,7 @@ def test_on_conflict_replace(self):
18711871

18721872
@requires_subprocess()
18731873
class MultiprocessTests(unittest.TestCase):
1874-
CONNECTION_TIMEOUT = SHORT_TIMEOUT / 1000. # Defaults to 30 ms
1874+
CONNECTION_TIMEOUT = 0 # Disable the busy timeout.
18751875

18761876
def tearDown(self):
18771877
unlink(TESTFN)

Lib/test/test_sqlite3/test_transactions.py

+6-10
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,20 @@
2424
import sqlite3 as sqlite
2525
from contextlib import contextmanager
2626

27-
from test.support import LOOPBACK_TIMEOUT
2827
from test.support.os_helper import TESTFN, unlink
2928
from test.support.script_helper import assert_python_ok
3029

3130
from test.test_sqlite3.test_dbapi import memory_database
3231

3332

34-
TIMEOUT = LOOPBACK_TIMEOUT / 10
35-
36-
3733
class TransactionTests(unittest.TestCase):
3834
def setUp(self):
39-
self.con1 = sqlite.connect(TESTFN, timeout=TIMEOUT)
35+
# We can disable the busy handlers, since we control
36+
# the order of SQLite C API operations.
37+
self.con1 = sqlite.connect(TESTFN, timeout=0)
4038
self.cur1 = self.con1.cursor()
4139

42-
self.con2 = sqlite.connect(TESTFN, timeout=TIMEOUT)
40+
self.con2 = sqlite.connect(TESTFN, timeout=0)
4341
self.cur2 = self.con2.cursor()
4442

4543
def tearDown(self):
@@ -119,10 +117,8 @@ def test_raise_timeout(self):
119117
self.cur2.execute("insert into test(i) values (5)")
120118

121119
def test_locking(self):
122-
"""
123-
This tests the improved concurrency with pysqlite 2.3.4. You needed
124-
to roll back con2 before you could commit con1.
125-
"""
120+
# This tests the improved concurrency with pysqlite 2.3.4. You needed
121+
# to roll back con2 before you could commit con1.
126122
self.cur1.execute("create table test(i)")
127123
self.cur1.execute("insert into test(i) values (5)")
128124
with self.assertRaises(sqlite.OperationalError):

0 commit comments

Comments
 (0)