gh-133390: Support SQL keyword completion for sqlite3 CLI (GH-133393) (GH-135292)
Some checks are pending
Tests / Windows MSI (push) Blocked by required conditions
Tests / Change detection (push) Waiting to run
Tests / Docs (push) Blocked by required conditions
Tests / Check if Autoconf files are up to date (push) Blocked by required conditions
Tests / Check if generated files are up to date (push) Blocked by required conditions
Tests / (push) Blocked by required conditions
Tests / Ubuntu SSL tests with OpenSSL (push) Blocked by required conditions
Tests / WASI (push) Blocked by required conditions
Tests / Hypothesis tests on Ubuntu (push) Blocked by required conditions
Tests / Address sanitizer (push) Blocked by required conditions
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run

Co-authored-by: Tan Long <tanloong@foxmail.com>
This commit is contained in:
Petr Viktorin 2025-06-12 16:28:30 +02:00 committed by GitHub
parent e6c3039cb3
commit e7a3c20b92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 206 additions and 6 deletions

View file

@ -1,14 +1,22 @@
"""sqlite3 CLI tests."""
import sqlite3
import sys
import textwrap
import unittest
import unittest.mock
import os
from sqlite3.__main__ import main as cli
from test.support.import_helper import import_module
from test.support.os_helper import TESTFN, unlink
from test.support.pty_helper import run_pty
from test.support import (
captured_stdout,
captured_stderr,
captured_stdin,
force_not_colorized_test_class,
requires_subprocess,
verbose,
)
@ -200,5 +208,108 @@ class InteractiveSession(unittest.TestCase):
self.assertIn('\x1b[1;35mOperationalError (SQLITE_ERROR)\x1b[0m: '
'\x1b[35mnear "sel": syntax error\x1b[0m', err)
@requires_subprocess()
@force_not_colorized_test_class
class Completion(unittest.TestCase):
PS1 = "sqlite> "
@classmethod
def setUpClass(cls):
_sqlite3 = import_module("_sqlite3")
if not hasattr(_sqlite3, "SQLITE_KEYWORDS"):
raise unittest.SkipTest("unable to determine SQLite keywords")
readline = import_module("readline")
if readline.backend == "editline":
raise unittest.SkipTest("libedit readline is not supported")
def write_input(self, input_, env=None):
script = textwrap.dedent("""
import readline
from sqlite3.__main__ import main
readline.parse_and_bind("set colored-completion-prefix off")
main()
""")
return run_pty(script, input_, env)
def test_complete_sql_keywords(self):
# List candidates starting with 'S', there should be multiple matches.
input_ = b"S\t\tEL\t 1;\n.quit\n"
output = self.write_input(input_)
self.assertIn(b"SELECT", output)
self.assertIn(b"SET", output)
self.assertIn(b"SAVEPOINT", output)
self.assertIn(b"(1,)", output)
# Keywords are completed in upper case for even lower case user input.
input_ = b"sel\t\t 1;\n.quit\n"
output = self.write_input(input_)
self.assertIn(b"SELECT", output)
self.assertIn(b"(1,)", output)
@unittest.skipIf(sys.platform.startswith("freebsd"),
"Two actual tabs are inserted when there are no matching"
" completions in the pseudo-terminal opened by run_pty()"
" on FreeBSD")
def test_complete_no_match(self):
input_ = b"xyzzy\t\t\b\b\b\b\b\b\b.quit\n"
# Set NO_COLOR to disable coloring for self.PS1.
output = self.write_input(input_, env={**os.environ, "NO_COLOR": "1"})
lines = output.decode().splitlines()
indices = (
i for i, line in enumerate(lines, 1)
if line.startswith(f"{self.PS1}xyzzy")
)
line_num = next(indices, -1)
self.assertNotEqual(line_num, -1)
# Completions occupy lines, assert no extra lines when there is nothing
# to complete.
self.assertEqual(line_num, len(lines))
def test_complete_no_input(self):
from _sqlite3 import SQLITE_KEYWORDS
script = textwrap.dedent("""
import readline
from sqlite3.__main__ import main
# Configure readline to ...:
# - hide control sequences surrounding each candidate
# - hide "Display all xxx possibilities? (y or n)"
# - hide "--More--"
# - show candidates one per line
readline.parse_and_bind("set colored-completion-prefix off")
readline.parse_and_bind("set colored-stats off")
readline.parse_and_bind("set completion-query-items 0")
readline.parse_and_bind("set page-completions off")
readline.parse_and_bind("set completion-display-width 0")
readline.parse_and_bind("set show-all-if-ambiguous off")
readline.parse_and_bind("set show-all-if-unmodified off")
main()
""")
input_ = b"\t\t.quit\n"
output = run_pty(script, input_, env={**os.environ, "NO_COLOR": "1"})
try:
lines = output.decode().splitlines()
indices = [
i for i, line in enumerate(lines)
if line.startswith(self.PS1)
]
self.assertEqual(len(indices), 2)
start, end = indices
candidates = [l.strip() for l in lines[start+1:end]]
self.assertEqual(candidates, sorted(SQLITE_KEYWORDS))
except:
if verbose:
print(' PTY output: '.center(30, '-'))
print(output.decode(errors='replace'))
print(' end PTY output '.center(30, '-'))
raise
if __name__ == "__main__":
unittest.main()