should be it

This commit is contained in:
2025-10-24 19:21:19 -05:00
parent a4b23fc57c
commit f09560c7b1
14047 changed files with 3161551 additions and 1 deletions

View File

@@ -0,0 +1,180 @@
import pytest
import os
import subprocess
import sys
from typing import List, NamedTuple, Union
def pytest_addoption(parser):
parser.addoption(
"--shell-binary", action="store", default=None, help="Provide the shell binary to use for the tests"
)
parser.addoption("--start-offset", action="store", type=int, help="Skip the first 'n' tests")
def pytest_collection_modifyitems(config, items):
start_offset = config.getoption("--start-offset")
if not start_offset:
# --skiplist not given in cli, therefore move on
return
skipped = pytest.mark.skip(reason="included in --skiplist")
skipped_items = items[:start_offset]
for item in skipped_items:
item.add_marker(skipped)
class TestResult:
def __init__(self, stdout, stderr, status_code):
self.stdout: Union[str, bytes] = stdout
self.stderr: Union[str, bytes] = stderr
self.status_code: int = status_code
def check_stdout(self, expected: Union[str, List[str], bytes]):
if isinstance(expected, list):
expected = '\n'.join(expected)
assert self.status_code == 0
assert expected in self.stdout
def check_not_exist(self, not_exist: Union[str, List[str], bytes]):
if isinstance(not_exist, list):
not_exist = '\n'.join(not_exist)
assert self.status_code == 0
assert not_exist not in self.stdout
def check_stderr(self, expected: str):
assert expected in self.stderr
class ShellTest:
def __init__(self, shell, arguments=[]):
if not shell:
raise ValueError("Please provide a shell binary")
self.shell = shell
self.arguments = [shell, '--batch', '--init', '/dev/null'] + arguments
self.statements: List[str] = []
self.input = None
self.output = None
self.environment = {}
def add_argument(self, *args):
self.arguments.extend(args)
return self
def statement(self, stmt):
self.statements.append(stmt)
return self
def query(self, *stmts):
self.statements.extend(stmts)
return self
def input_file(self, file_path):
self.input = file_path
return self
def output_file(self, file_path):
self.output = file_path
return self
# Test Running methods
def get_command(self, cmd: str) -> List[str]:
command = self.arguments
if self.input:
command += [cmd]
return command
def get_input_data(self, cmd: str):
if self.input:
input_data = open(self.input, 'rb').read()
else:
input_data = bytearray(cmd, 'utf8')
return input_data
def get_output_pipe(self):
output_pipe = subprocess.PIPE
if self.output:
output_pipe = open(self.output, 'w+')
return output_pipe
def get_statements(self):
result = ""
statements = []
for statement in self.statements:
if statement.startswith('.'):
statements.append(statement)
else:
statements.append(statement + ';')
return '\n'.join(statements)
def get_output_data(self, res):
if self.output:
stdout = open(self.output, 'r').read()
else:
stdout = res.stdout.decode('utf8').strip()
stderr = res.stderr.decode('utf8').strip()
return stdout, stderr
def run(self):
statements = self.get_statements()
command = self.get_command(statements)
input_data = self.get_input_data(statements)
output_pipe = self.get_output_pipe()
my_env = os.environ.copy()
for key, val in self.environment.items():
my_env[key] = val
res = subprocess.run(command, input=input_data, stdout=output_pipe, stderr=subprocess.PIPE, env=my_env)
stdout, stderr = self.get_output_data(res)
return TestResult(stdout, stderr, res.returncode)
@pytest.fixture()
def shell(request):
custom_arg = request.config.getoption("--shell-binary")
if not custom_arg:
raise ValueError("Please provide a shell binary path to the tester, using '--shell-binary <path_to_cli>'")
return custom_arg
@pytest.fixture()
def random_filepath(request, tmp_path):
tmp_file = tmp_path / "random_import_file"
return tmp_file
@pytest.fixture()
def generated_file(request, random_filepath):
param = request.param
tmp_file = random_filepath
with open(tmp_file, 'w+') as f:
f.write(param)
return tmp_file
def check_load_status(shell, extension: str):
binary = ShellTest(shell)
binary.statement(f"select loaded from duckdb_extensions() where extension_name = '{extension}';")
result = binary.run()
return result.stdout
def assert_loaded(shell, extension: str):
# TODO: add a command line argument to fail instead of skip if the extension is not loaded
out = check_load_status(shell, extension)
if 'true' not in out:
pytest.skip(reason=f"'{extension}' extension is not loaded!")
return
@pytest.fixture()
def autocomplete_extension(shell):
assert_loaded(shell, 'autocomplete')
@pytest.fixture()
def json_extension(shell):
assert_loaded(shell, 'json')

View File

@@ -0,0 +1,340 @@
# fmt: off
import pytest
import subprocess
import sys
from typing import List
from conftest import ShellTest
from conftest import autocomplete_extension
import os
# 'autocomplete_extension' is a fixture which will skip the test if 'autocomplete' is not loaded
def test_autocomplete_select(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("CALL sql_auto_complete('SEL')")
)
result = test.run()
result.check_stdout('SELECT')
def test_autocomplete_first_from(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("CALL sql_auto_complete('FRO')")
)
result = test.run()
result.check_stdout('FROM')
def test_autocomplete_column(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("CREATE TABLE my_table(my_column INTEGER);")
.statement("SELECT * FROM sql_auto_complete('SELECT my_') LIMIT 1;")
)
result = test.run()
result.check_stdout('my_column')
def test_autocomplete_where(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("CREATE TABLE my_table(my_column INTEGER);")
.statement("SELECT * FROM sql_auto_complete('SELECT my_column FROM my_table WH') LIMIT 1;")
)
result = test.run()
result.check_stdout('WHERE')
def test_autocomplete_insert(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("CREATE TABLE my_table(my_column INTEGER);")
.statement("SELECT * FROM sql_auto_complete('INS') LIMIT 1;")
)
result = test.run()
result.check_stdout('INSERT')
def test_autocomplete_into(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("CREATE TABLE my_table(my_column INTEGER);")
.statement("SELECT * FROM sql_auto_complete('INSERT IN') LIMIT 1;")
)
result = test.run()
result.check_stdout('INTO')
def test_autocomplete_into_table(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("CREATE TABLE my_table(my_column INTEGER);")
.statement("SELECT * FROM sql_auto_complete('INSERT INTO my_t') LIMIT 1;")
)
result = test.run()
result.check_stdout('my_table')
def test_autocomplete_values(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("CREATE TABLE my_table(my_column INTEGER);")
.statement("SELECT * FROM sql_auto_complete('INSERT INTO my_table VAL') LIMIT 1;")
)
result = test.run()
result.check_stdout('VALUES')
def test_autocomplete_delete(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("CREATE TABLE my_table(my_column INTEGER);")
.statement("SELECT * FROM sql_auto_complete('DEL') LIMIT 1;")
)
result = test.run()
result.check_stdout('DELETE')
def test_autocomplete_delete_from(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("CREATE TABLE my_table(my_column INTEGER);")
.statement("SELECT * FROM sql_auto_complete('DELETE F') LIMIT 1;")
)
result = test.run()
result.check_stdout('FROM')
def test_autocomplete_from_table(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("CREATE TABLE my_table(my_column INTEGER);")
.statement("SELECT * FROM sql_auto_complete('DELETE FROM m') LIMIT 1;")
)
result = test.run()
result.check_stdout('my_table')
def test_autocomplete_update(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("CREATE TABLE my_table(my_column INTEGER);")
.statement("SELECT * FROM sql_auto_complete('UP') LIMIT 1;")
)
result = test.run()
result.check_stdout('UPDATE')
def test_autocomplete_update_table(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("CREATE TABLE my_table(my_column INTEGER);")
.statement("SELECT * FROM sql_auto_complete('UPDATE m') LIMIT 1;")
)
result = test.run()
result.check_stdout('my_table')
test = (
ShellTest(shell)
.statement("CREATE TABLE my_table(my_column INTEGER);")
.statement("""SELECT * FROM sql_auto_complete('UPDATE "m') LIMIT 1;""")
)
result = test.run()
result.check_stdout('my_table')
def test_autocomplete_update_column(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("CREATE TABLE my_table(my_column INTEGER);")
.statement("SELECT * FROM sql_auto_complete('UPDATE my_table SET m') LIMIT 1;")
)
result = test.run()
result.check_stdout('my_column')
def test_autocomplete_funky_table(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("""CREATE TABLE "Funky Table With Spaces"(my_column INTEGER);""")
.statement("SELECT * FROM sql_auto_complete('SELECT * FROM F') LIMIT 1;")
)
result = test.run()
result.check_stdout('"Funky Table With Spaces"')
test = (
ShellTest(shell)
.statement("""CREATE TABLE "Funky Table With Spaces"("Funky Column" int);""")
.statement("""SELECT * FROM sql_auto_complete('select "Funky Column" FROM f') LIMIT 1;""")
)
result = test.run()
result.check_stdout('"Funky Table With Spaces"')
def test_autocomplete_funky_column(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("""CREATE TABLE "Funky Table With Spaces"("Funky Column" int);""")
.statement("SELECT * FROM sql_auto_complete('select f') LIMIT 1;")
)
result = test.run()
result.check_stdout('"Funky Column"')
def test_autocomplete_semicolon(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("SELECT * FROM sql_auto_complete('SELECT 42; SEL') LIMIT 1;")
)
result = test.run()
result.check_stdout('SELECT')
def test_autocomplete_comments(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("""
SELECT * FROM sql_auto_complete('--SELECT * FROM
SEL') LIMIT 1;""")
)
result = test.run()
result.check_stdout('SELECT')
def test_autocomplete_scalar_functions(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("SELECT * FROM sql_auto_complete('SELECT regexp_m') LIMIT 1;")
)
result = test.run()
result.check_stdout('regexp_matches')
def test_autocomplete_aggregates(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("SELECT * FROM sql_auto_complete('SELECT approx_c') LIMIT 1;")
)
result = test.run()
result.check_stdout('approx_count_distinct')
def test_autocomplete_builtin_views(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("SELECT * FROM sql_auto_complete('SELECT * FROM sqlite_ma') LIMIT 1;")
)
result = test.run()
result.check_stdout('sqlite_master')
def test_autocomplete_table_function(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("SELECT * FROM sql_auto_complete('SELECT * FROM read_csv_a') LIMIT 1;")
)
result = test.run()
result.check_stdout('read_csv_auto')
def test_autocomplete_tpch(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("CREATE TABLE partsupp(ps_suppkey int);")
.statement("CREATE TABLE supplier(s_suppkey int);")
.statement("CREATE TABLE nation(n_nationkey int);")
.statement("SELECT * FROM sql_auto_complete('DROP TABLE na') LIMIT 1;")
)
result = test.run()
result.check_stdout('nation')
test = (
ShellTest(shell)
.statement("CREATE TABLE partsupp(ps_suppkey int);")
.statement("CREATE TABLE supplier(s_suppkey int);")
.statement("CREATE TABLE nation(n_nationkey int);")
.statement("SELECT * FROM sql_auto_complete('SELECT s_supp') LIMIT 1;")
)
result = test.run()
result.check_stdout('s_suppkey')
test = (
ShellTest(shell)
.statement("CREATE TABLE partsupp(ps_suppkey int);")
.statement("CREATE TABLE supplier(s_suppkey int);")
.statement("CREATE TABLE nation(n_nationkey int);")
.statement("SELECT * FROM sql_auto_complete('SELECT * FROM partsupp JOIN supp') LIMIT 1;")
)
result = test.run()
result.check_stdout('supplier')
test = (
ShellTest(shell)
.statement("CREATE TABLE partsupp(ps_suppkey int);")
.statement("CREATE TABLE supplier(s_suppkey int);")
.statement("CREATE TABLE nation(n_nationkey int);")
.statement(".mode csv")
.statement("SELECT l,l FROM sql_auto_complete('SELECT * FROM partsupp JOIN supplier ON (s_supp') t(l) LIMIT 1;")
)
result = test.run()
result.check_stdout('s_suppkey,s_suppkey')
test = (
ShellTest(shell)
.statement("CREATE TABLE partsupp(ps_suppkey int);")
.statement("CREATE TABLE supplier(s_suppkey int);")
.statement("CREATE TABLE nation(n_nationkey int);")
.statement("SELECT * FROM sql_auto_complete('SELECT * FROM partsupp JOIN supplier USING (ps_su') LIMIT 1;")
)
result = test.run()
result.check_stdout('ps_suppkey')
def test_autocomplete_from(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("SELECT * FROM sql_auto_complete('SELECT * FR') LIMIT 1;")
)
result = test.run()
result.check_stdout('FROM')
def test_autocomplete_disambiguation_column(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("CREATE TABLE MyTable(MyColumn Varchar);")
.statement("SELECT * FROM sql_auto_complete('SELECT My') LIMIT 1;")
)
result = test.run()
result.check_stdout('MyColumn')
def test_autocomplete_disambiguation_table(shell, autocomplete_extension):
test = (
ShellTest(shell)
.statement("CREATE TABLE MyTable(MyColumn Varchar);")
.statement("SELECT * FROM sql_auto_complete('SELECT MyColumn FROM My') LIMIT 1;")
)
result = test.run()
result.check_stdout('MyTable')
def test_autocomplete_directory(shell, autocomplete_extension, tmp_path):
shell_test_dir = tmp_path / 'shell_test_dir'
extra_path = tmp_path / 'shell_test_dir' / 'extra_path'
shell_test_dir.mkdir()
extra_path.mkdir()
# Create the files
base_files = ['extra.parquet', 'extra.file']
for fname in base_files:
with open(shell_test_dir / fname, 'w+') as f:
f.write('')
# Complete the directory
partial_directory = tmp_path / 'shell_test'
test = (
ShellTest(shell)
.statement("CREATE TABLE MyTable(MyColumn Varchar);")
.statement(f"SELECT * FROM sql_auto_complete('SELECT * FROM ''{partial_directory.as_posix()}') LIMIT 1;")
)
result = test.run()
result.check_stdout("shell_test_dir")
# Complete the sub directory as well
partial_subdirectory = tmp_path / 'shell_test_dir' / 'extra'
test = (
ShellTest(shell)
.statement("CREATE TABLE MyTable(MyColumn Varchar);")
.statement(f"SELECT * FROM sql_auto_complete('SELECT * FROM ''{partial_subdirectory.as_posix()}') LIMIT 1;")
)
result = test.run()
result.check_stdout("extra_path")
# Complete the parquet file in the sub directory
partial_parquet = tmp_path / 'shell_test_dir' / 'extra.par'
test = (
ShellTest(shell)
.statement("CREATE TABLE MyTable(MyColumn Varchar);")
.statement(f"SELECT * FROM sql_auto_complete('SELECT * FROM ''{partial_parquet.as_posix()}') LIMIT 1;")
)
result = test.run()
result.check_stdout("extra.parquet")
# fmt: on

View File

@@ -0,0 +1,58 @@
# fmt: off
import pytest
import subprocess
import sys
from typing import List
from conftest import ShellTest
import os
def test_version_dev(shell):
test = (
ShellTest(shell)
.statement(".open test/storage/bc/db_dev.db")
)
result = test.run()
result.check_stderr("older development version")
def test_version_0_3_1(shell):
test = (
ShellTest(shell)
.statement(".open test/storage/bc/db_031.db")
)
result = test.run()
result.check_stderr("v0.3.1")
def test_version_0_3_2(shell):
test = (
ShellTest(shell)
.statement(".open test/storage/bc/db_032.db")
)
result = test.run()
result.check_stderr("v0.3.2")
def test_version_0_4(shell):
test = (
ShellTest(shell)
.statement(".open test/storage/bc/db_04.db")
)
result = test.run()
result.check_stderr("v0.4.0")
def test_version_0_5_1(shell):
test = (
ShellTest(shell)
.statement(".open test/storage/bc/db_051.db")
)
result = test.run()
result.check_stderr("v0.5.1")
def test_version_0_6_0(shell):
test = (
ShellTest(shell)
.statement(".open test/storage/bc/db_060.db")
)
result = test.run()
result.check_stderr("v0.6.0")
# fmt: on

View File

@@ -0,0 +1,95 @@
# fmt: off
import pytest
import subprocess
import sys
from typing import List
from conftest import ShellTest
import os
lineitem_ddl = 'CREATE TABLE lineitem(l_orderkey BIGINT NOT NULL, l_partkey BIGINT NOT NULL, l_suppkey BIGINT NOT NULL, l_linenumber BIGINT NOT NULL, l_quantity DECIMAL(15,2) NOT NULL, l_extendedprice DECIMAL(15,2) NOT NULL, l_discount DECIMAL(15,2) NOT NULL, l_tax DECIMAL(15,2) NOT NULL, l_returnflag VARCHAR NOT NULL, l_linestatus VARCHAR NOT NULL, l_shipdate DATE NOT NULL, l_commitdate DATE NOT NULL, l_receiptdate DATE NOT NULL, l_shipinstruct VARCHAR NOT NULL, l_shipmode VARCHAR NOT NULL, l_comment VARCHAR NOT NULL);'
@pytest.mark.skipif(os.name == 'nt', reason="Windows highlighting does not use shell escapes")
def test_incorrect_column(shell):
test = (
ShellTest(shell)
.statement(".highlight_errors on")
.statement(lineitem_ddl)
.statement('select * from lineitem where l_extendedpric=5;')
)
result = test.run()
result.check_stderr('"\x1b[33ml_extendedprice')
result.check_stderr('"\x1b[33ml_extendedpric\x1b[0m')
@pytest.mark.skipif(os.name == 'nt', reason="Windows highlighting does not use shell escapes")
def test_missing_table(shell):
test = (
ShellTest(shell)
.statement(".highlight_errors on")
.statement(lineitem_ddl)
.statement('select * from lineite where l_extendedprice=5;')
)
result = test.run()
result.check_stderr('"\x1b[33mlineitem\x1b[0m')
@pytest.mark.skipif(os.name == 'nt', reason="Windows highlighting does not use shell escapes")
def test_long_error(shell):
test = (
ShellTest(shell)
.statement(".highlight_errors on")
.statement(lineitem_ddl)
.statement('''SELECT
l_returnflag,
l_linestatus,
sum(l_quantity) AS sum_qty,
sum(l_extendedprice) AS sum_base_price,
sum(l_extendedprice * (1 - l_discount)) AS sum_disc_price,
sum(l_extendedprice * (1 - l_discount) * (1 + l_tax)) AS sum_charge,
avg(l_quantity) AS avg_qty,
avg(l_extendedprice) AS avg_price,
avg(l_discount) AS avg_disc,
count(*) AS count_order
FROM
lineitem
WHERE
l_shipdate <= CAST('1998-09-02' AS date) + timestamp '2020-01-01'
GROUP BY
l_returnflag,
l_linestatus
ORDER BY
l_returnflag,
l_linestatus;''')
)
result = test.run()
result.check_stderr('\x1b[33m+(DATE, TIMESTAMP)\x1b[0m')
result.check_stderr('\x1b[32mCAST\x1b[0m')
@pytest.mark.skipif(os.name == 'nt', reason="Windows highlighting does not use shell escapes")
def test_single_quotes_in_error(shell):
test = (
ShellTest(shell)
.statement(".highlight_errors on")
.statement("select \"I'm an error\"")
)
result = test.run()
result.check_stderr('"\x1b[33mI\'m an error\x1b[0m')
@pytest.mark.skipif(os.name == 'nt', reason="Windows highlighting does not use shell escapes")
def test_double_quotes_in_error(shell):
test = (
ShellTest(shell)
.statement(".highlight_errors on")
.statement("select error('''I\"m an error''')")
)
result = test.run()
result.check_stderr('\x1b[33mI"m an error\x1b[0m')
@pytest.mark.skipif(os.name == 'nt', reason="Windows highlighting does not use shell escapes")
def test_unterminated_quote(shell):
test = (
ShellTest(shell)
.statement(".highlight_errors on")
.statement("select error('I''m an error')")
)
result = test.run()
result.check_stderr('I\'m an error')

View File

@@ -0,0 +1,15 @@
# fmt: off
import pytest
import subprocess
import sys
from typing import List
from conftest import ShellTest
import os
def test_invalid_explain(shell):
test = (
ShellTest(shell)
.statement("EXPLAIN SELECT 'any_string' IN ?;")
)
result = test.run()

View File

@@ -0,0 +1,30 @@
# fmt: off
from conftest import ShellTest
def test_get_env(shell):
test = (
ShellTest(shell)
.statement('.null NULL')
.statement("SET default_null_order=getenv('DEFAULT_NULL_ORDER');")
.statement("SELECT * FROM (VALUES (42), (NULL)) ORDER BY 1 LIMIT 1;")
)
test.environment['DEFAULT_NULL_ORDER'] = 'NULLS_FIRST'
result = test.run()
result.check_stdout('NULL')
test.environment['DEFAULT_NULL_ORDER'] = 'NULLS_LAST'
result = test.run()
result.check_stdout('42')
def test_get_env_permissions(shell):
test = (
ShellTest(shell)
.statement('SET enable_external_access=false')
.statement("SELECT getenv('DEFAULT_NULL_ORDER');")
)
test.environment['DEFAULT_NULL_ORDER'] = 'NULLS_FIRST'
result = test.run()
result.check_stderr('disabled through configuration')
# fmt: on

View File

@@ -0,0 +1,49 @@
# fmt: off
import pytest
import subprocess
import sys
from typing import List
from conftest import ShellTest
import os
lineitem_ddl = 'CREATE TABLE lineitem(l_orderkey BIGINT NOT NULL, l_partkey BIGINT NOT NULL, l_suppkey BIGINT NOT NULL, l_linenumber BIGINT NOT NULL, l_quantity DECIMAL(15,2) NOT NULL, l_extendedprice DECIMAL(15,2) NOT NULL, l_discount DECIMAL(15,2) NOT NULL, l_tax DECIMAL(15,2) NOT NULL, l_returnflag VARCHAR NOT NULL, l_linestatus VARCHAR NOT NULL, l_shipdate DATE NOT NULL, l_commitdate DATE NOT NULL, l_receiptdate DATE NOT NULL, l_shipinstruct VARCHAR NOT NULL, l_shipmode VARCHAR NOT NULL, l_comment VARCHAR NOT NULL);'
@pytest.mark.skipif(os.name == 'nt', reason="Windows highlighting does not use shell escapes")
def test_highlight_column_header(shell):
test = (
ShellTest(shell)
.statement(".highlight_results on")
.statement('select NULL AS r;')
)
result = test.run()
result.check_stdout('\x1b[90mNULL\x1b[0m')
@pytest.mark.skipif(os.name == 'nt', reason="Windows highlighting does not use shell escapes")
def test_custom_highlight(shell):
test = (
ShellTest(shell)
.statement(".highlight_results on")
.statement(".highlight_colors column_name red bold")
.statement(".highlight_colors column_type yellow")
.statement(lineitem_ddl)
.statement('select * from lineitem;')
)
result = test.run()
result.check_stdout('\x1b[1m\x1b[31ml_comment\x1b[0m')
result.check_stdout('\x1b[33mvarchar\x1b[0m')
def test_custom_highlight_error(shell):
test = (
ShellTest(shell)
.statement(".highlight_colors column_nameXX red")
.statement(".highlight_colors column_name redXX")
.statement(".highlight_colors column_name red boldXX")
.statement(".highlight_colors column_name red bold zz")
)
result = test.run()
result.check_stderr("Unknown element 'column_nameXX'")
result.check_stderr("Unknown color 'redXX'")
result.check_stderr("Unknown intensity 'boldXX'")
result.check_stderr("Usage: .highlight_colors")
# fmt: on

View File

@@ -0,0 +1,27 @@
# fmt: off
import pytest
import subprocess
import sys
from typing import List
from conftest import ShellTest
import os
@pytest.mark.skip(reason="Skip after File Logging rework")
def test_http_logging_file(shell, tmp_path):
temp_dir = tmp_path / 'http_logging_dir'
temp_dir.mkdir()
temp_file = temp_dir / 'myfile'
test = (
ShellTest(shell)
.statement("SET enable_http_logging=true;")
.statement(f"SET http_logging_output='{temp_file.as_posix()}'")
.statement("install 'http://extensions.duckdb.org/v0.10.1/osx_arm64/httpfs.duckdb_extension.gzzz';")
)
result = test.run()
with open(temp_file, 'r') as f:
file_content = f.read()
assert "HTTP Request" in file_content
assert "HTTP Response" in file_content

View File

@@ -0,0 +1,29 @@
# fmt: off
import pytest
import subprocess
import sys
from typing import List
from conftest import ShellTest
def test_profiling_json(shell, tmp_path):
target_dir = tmp_path / 'export_test'
test = (
ShellTest(shell)
.statement(".mode csv")
.statement(".changes off")
.statement("CREATE TABLE integers(i INTEGER);")
.statement("CREATE TABLE integers2(i INTEGER);")
.statement("INSERT INTO integers SELECT * FROM range(100);")
.statement("INSERT INTO integers2 VALUES (1), (3), (99);")
.statement(f"EXPORT DATABASE '{target_dir.as_posix()}';")
.statement("DROP TABLE integers;")
.statement("DROP TABLE integers2;")
.statement(f"IMPORT DATABASE '{target_dir.as_posix()}';")
.statement("SELECT SUM(i)*MAX(i) FROM integers JOIN integers2 USING (i);")
)
result = test.run()
result.check_stdout('10197')
# fmt: on

View File

@@ -0,0 +1,43 @@
# fmt: off
import pytest
import subprocess
import sys
import os
from typing import List
from conftest import ShellTest
def test_logging(shell):
test = (
ShellTest(shell)
.statement("CALL enable_logging('QueryLog', storage='stdout')")
.statement('SELECT 1 as a')
)
result = test.run()
newline = "\r\n" if os.name == "nt" else "\n"
result.check_stdout(f"QueryLog\tINFO\tSELECT 1 as a;{newline}┌───────┐")
def test_logging_custom_delim(shell):
test = (
ShellTest(shell)
.statement("CALL enable_logging('QueryLog', storage='stdout', storage_config={'delim':','})")
.statement('SELECT 1 as a')
)
result = test.run()
newline = "\r\n" if os.name == "nt" else "\n"
result.check_stdout(f"QueryLog,INFO,SELECT 1 as a;{newline}┌───────┐")
# By default stdoutlogging has buffer size of 1, but we can increase it if we want. We use `only_flush_on_full_buffer` to ensure we can test this
def test_logging_buffering(shell):
test = (
ShellTest(shell)
.statement("CALL enable_logging('QueryLog', storage='stdout', storage_buffer_size=1000, storage_config={'only_flush_on_full_buffer': true})")
.statement('SELECT 1 as a')
.statement('SELECT 2 as b')
)
result = test.run()
result.check_not_exist("QueryLog")
# fmt: on

View File

@@ -0,0 +1,32 @@
# fmt: off
from conftest import ShellTest
def test_temp_directory(shell):
test = (
ShellTest(shell)
.statement(".mode csv")
.statement("CREATE SEQUENCE id_seq;")
.statement("""
CREATE TABLE my_table (
id INTEGER DEFAULT nextval('id_seq'),
a INTEGER
);""")
.statement("ATTACH ':memory:' AS s1;")
.statement("CREATE TABLE s1.tbl AS FROM range(2000000);")
.statement("INSERT INTO my_table (a) SELECT * FROM s1.tbl;")
.statement("INSERT INTO my_table (a) SELECT * FROM s1.tbl;")
.statement("INSERT INTO my_table (a) SELECT * FROM s1.tbl;")
.statement("INSERT INTO my_table (a) SELECT * FROM s1.tbl;")
.statement("INSERT INTO my_table (a) SELECT * FROM s1.tbl;")
.statement("INSERT INTO my_table (a) SELECT * FROM s1.tbl;")
.statement("INSERT INTO my_table (a) SELECT * FROM s1.tbl;")
.statement("INSERT INTO my_table (a) SELECT * FROM s1.tbl;")
.statement("INSERT INTO my_table (a) SELECT * FROM s1.tbl;")
.statement("INSERT INTO my_table (a) SELECT * FROM s1.tbl;")
)
test = test.statement("select count(*) from my_table")
result = test.run()
result.check_stdout("20000000")
# fmt: on

View File

@@ -0,0 +1,23 @@
# fmt: off
import pytest
import subprocess
import sys
from typing import List
from conftest import ShellTest
def test_profiling_json(shell):
test = (
ShellTest(shell)
.statement("PRAGMA enable_profiling=json;")
.statement('CREATE TABLE "foo"("hello world" INT);')
.statement("""SELECT "hello world", '\r\t\n\b\f\\' FROM "foo";""")
)
result = test.run()
result.check_stderr(r'"hello world"')
# This is incorrectly split but that's impossible to do correctly currently.
result.check_stderr(r''''\r\t"''')
result.check_stderr(r""""\b\f\\'""")
# fmt: on

View File

@@ -0,0 +1,278 @@
# fmt: off
import pytest
import subprocess
import sys
from typing import List
from conftest import ShellTest
from conftest import json_extension
import os
@pytest.mark.skipif(os.name == 'nt', reason="Skipped on windows")
class TestReadFromStdin(object):
def test_read_stdin_csv(self, shell):
test = (
ShellTest(shell)
.input_file('data/csv/test/test.csv')
.statement("""
create table mytable as select * from
read_csv('/dev/stdin',
columns=STRUCT_PACK(foo := 'INTEGER', bar := 'INTEGER', baz := 'VARCHAR'),
AUTO_DETECT='false'
)
""")
.statement("select * from mytable limit 1;")
.add_argument(
'-csv',
':memory:'
)
)
result = test.run()
result.check_stdout("foo,bar,baz")
result.check_stdout('0,0, test')
def test_read_stdin_csv_where_filename(self, shell):
test = (
ShellTest(shell)
.input_file('data/csv/test/test.csv')
.statement("""
SELECT * FROM read_csv_auto(
'data/csv/bug_9005/teste*.csv',
header=TRUE,
filename=true,
union_by_name=True
) where filename='data/csv/bug_9005/teste1.csv'
""")
.add_argument(
'-csv',
':memory:'
)
)
result = test.run()
result.check_stdout([
'id,name,factor,filename',
'1,Ricardo,1.5,data/csv/bug_9005/teste1.csv',
'2,Jose,2.0,data/csv/bug_9005/teste1.csv'
])
def test_read_stdin_csv_auto(self, shell):
test = (
ShellTest(shell)
.input_file('data/csv/test/test.csv')
.statement("""
create table mytable as select * from
read_csv_auto('/dev/stdin')
""")
.statement("select * from mytable limit 1;")
.add_argument(
'-csv',
':memory:'
)
)
result = test.run()
result.check_stdout("column0,column1,column2")
result.check_stdout('0,0, test')
def test_split_part_csv(self, shell):
test = (
ShellTest(shell)
.input_file('data/csv/split_part.csv')
.statement("""
FROM read_csv('/dev/stdin') select split_part(C1, ',', 2) as res;
""")
.add_argument(
'-csv',
':memory:'
)
)
result = test.run()
result.check_stdout("res")
result.check_stdout('12')
result.check_stdout('12')
def test_read_stdin_csv_auto_projection(self, shell):
test = (
ShellTest(shell)
.input_file('data/csv/tpcds_14.csv')
.statement("""
create table mytable as select * from
read_csv_auto('/dev/stdin')
""")
.statement("select channel,i_brand_id,sum_sales,number_sales from mytable;")
.add_argument(
'-csv',
':memory:'
)
)
result = test.run()
result.check_stdout("web,8006004,844.21,21")
def test_read_stdin_ndjson(self, shell, json_extension):
test = (
ShellTest(shell)
.input_file('data/json/example_rn.ndjson')
.statement("""
create table mytable as select * from
read_ndjson_objects('/dev/stdin')
""")
.statement("select * from mytable;")
.add_argument(
'-list',
':memory:'
)
)
result = test.run()
result.check_stdout([
"json",
'{"id":1,"name":"O Brother, Where Art Thou?"}',
'{"id":2,"name":"Home for the Holidays"}',
'{"id":3,"name":"The Firm"}',
'{"id":4,"name":"Broadcast News"}',
'{"id":5,"name":"Raising Arizona"}'
])
def test_read_stdin_json_auto(self, shell, json_extension):
test = (
ShellTest(shell)
.input_file('data/json/example_rn.ndjson')
.statement("""
create table mytable as select * from
read_json_auto('/dev/stdin')
""")
.statement("select * from mytable;")
.add_argument(
'-list',
':memory:'
)
)
result = test.run()
result.check_stdout([
'id|name',
'1|O Brother, Where Art Thou?',
'2|Home for the Holidays',
'3|The Firm',
'4|Broadcast News',
'5|Raising Arizona'
])
def test_read_stdin_json_array(self, shell, json_extension):
test = (
ShellTest(shell)
.input_file('data/json/11407.json')
.statement("""
create table mytable as select * from
read_json_auto('/dev/stdin')
""")
.statement("select * from mytable;")
.add_argument(
'-list',
':memory:'
)
)
result = test.run()
result.check_stdout([
'k',
'v',
'v2'
])
def test_read_stdin_json_auto_recursive_cte(self, shell, json_extension):
# FIXME: disabled for now
return
test = (
ShellTest(shell)
.input_file('data/json/filter_keystage.ndjson')
.statement("""
CREATE TABLE mytable AS
WITH RECURSIVE nums AS (
SELECT 0 AS n
UNION ALL
SELECT n + 1
FROM nums
WHERE n < (
SELECT MAX(JSON_ARRAY_LENGTH(filter_keystage))::int - 1
FROM read_json_auto('/dev/stdin'))
) SELECT * FROM nums;
""")
.statement("select * from mytable;")
.add_argument(
'-list',
':memory:'
)
)
result = test.run()
result.check_stdout([
'n',
'0',
'1',
'2',
])
@pytest.mark.parametrize("alias", [
"'/dev/stdout'",
'stdout'
])
def test_copy_to_stdout(self, shell, alias):
test = (
ShellTest(shell)
.statement(f"COPY (SELECT 42) TO {alias};")
)
result = test.run()
result.check_stdout('42')
@pytest.mark.parametrize("alias", [
"'/dev/stdout'",
'stdout'
])
def test_copy_csv_to_stdout(self, shell, alias):
test = (
ShellTest(shell)
.statement(f"COPY (SELECT 42) TO {alias} WITH (FORMAT 'csv');")
.add_argument(
'-csv',
':memory:'
)
)
result = test.run()
result.check_stdout('42')
@pytest.mark.parametrize("alias", [
"'/dev/stderr'"
])
def test_copy_csv_to_stderr(self, shell, alias):
test = (
ShellTest(shell)
.statement(f"COPY (SELECT 42) TO {alias} WITH (FORMAT 'csv');")
.add_argument(
'-csv',
':memory:'
)
)
result = test.run()
result.check_stderr('42')
def test_copy_non_inlined_string(self, shell):
test = (
ShellTest(shell)
.statement("select list(concat('thisisalongstring', range::VARCHAR)) i from range(10000)")
)
result = test.run()
result.check_stdout('thisisalongstring')
def test_write_to_stdout_piped_to_file(self, shell, random_filepath):
test = (
ShellTest(shell)
.statement("copy (select * from range(10000) tbl(i)) to '/dev/stdout' (format csv)")
.output_file(random_filepath.as_posix())
)
result = test.run()
result.check_stdout('9999')
# fmt: on

View File

@@ -0,0 +1,151 @@
# fmt: off
import pytest
import subprocess
import sys
from typing import List
from conftest import ShellTest
import os
def test_readable_numbers(shell):
test = (
ShellTest(shell)
.statement(".large_number_rendering footer")
.statement("select 59986052 as count, 123456789 as count2, 9999999999 count3, -9999999999 count4;")
)
result = test.run()
result.check_stdout("(59.99 million)")
result.check_stdout("(123.46 million)")
result.check_stdout("(10.00 billion)")
result.check_stdout("(-10.00 billion)")
@pytest.mark.parametrize('test_rounding', [False, True])
def test_readable_numbers_exhaustive(shell, test_rounding):
query = "select "
for i in range(1, 20):
if i > 1:
query += ", "
if test_rounding:
query += '9' * i
else:
query += '1' + ('0' * i)
test = (
ShellTest(shell)
.statement(".large_number_rendering all")
.statement(".maxwidth 99999")
.statement(query)
)
result = test.run()
for unit in ['million', 'billion', 'trillion', 'quadrillion', 'quintillion']:
for number in ['1.00', '10.00', '100.00']:
if unit == 'quintillion' and number in ['10.00', '100.00']:
continue
result.check_stdout(number + " " + unit)
def test_readable_numbers_rounding(shell):
test = (
ShellTest(shell)
.statement(".large_number_rendering footer")
.statement(".maxwidth 99999")
.statement("select 1005000, 1004999, -1005000, -1004999;")
)
result = test.run()
result.check_stdout("(1.01 million)")
result.check_stdout("(1.00 million)")
result.check_stdout("(-1.01 million)")
result.check_stdout("(-1.00 million)")
def test_readable_rounding_edge_case(shell):
test = (
ShellTest(shell)
.statement(".large_number_rendering all")
.statement(".maxwidth 99999")
.statement("select 994999, 995000")
)
result = test.run()
result.check_stdout("1.00 million")
def test_readable_numbers_limit(shell):
test = (
ShellTest(shell)
.statement(".maxwidth 99999")
.statement(".large_number_rendering all")
.statement("select 18446744073709551616, -18446744073709551616, 9999999999999999999, -9999999999999999999;")
)
result = test.run()
result.check_stdout("10.00 quintillion")
result.check_stdout("-10.00 quintillion")
def test_decimal_separator(shell):
test = (
ShellTest(shell)
.statement(".decimal_sep ,")
.statement(".large_number_rendering all")
.statement("select 59986052, 59986052.5, 999999999.123456789, 1e20, 'nan'::double;")
)
result = test.run()
result.check_stdout("59,99 million")
result.check_stdout("1,00 billion")
def test_odd_floating_points(shell):
test = (
ShellTest(shell)
.statement("select 1e20, 'nan'::double;")
)
result = test.run()
result.check_stdout("nan")
def test_disable_readable_numbers(shell):
test = (
ShellTest(shell)
.statement(".large_number_rendering off")
.statement("select 123456789;")
)
result = test.run()
result.check_not_exist('(123.46 million)')
def test_large_number_rendering_all(shell):
test = (
ShellTest(shell)
.statement(".large_number_rendering all")
.statement("select 123456789 from range(10);")
)
result = test.run()
result.check_stdout('123.46 million')
result.check_not_exist('(123.46 million)')
def test_readable_numbers_columns(shell):
test = (
ShellTest(shell)
.statement(".columns")
.statement("select 123456789;")
)
result = test.run()
result.check_not_exist('(123.46 million)')
def test_readable_numbers_row_count(shell):
test = (
ShellTest(shell)
.statement(".large_number_rendering footer")
.statement("select r from range(1230000) t(r);")
)
result = test.run()
result.check_stdout('1.23 million rows')
def test_readable_numbers_row_count_wide(shell):
test = (
ShellTest(shell)
.statement(".large_number_rendering footer")
.statement("select r, r, r, r, r, r, r from range(1230000) t(r);")
)
result = test.run()
result.check_stdout('1.23 million rows')
def test_readable_numbers_row_count_wide_single_col(shell):
test = (
ShellTest(shell)
.statement(".large_number_rendering footer")
.statement("select concat(r, r, r, r, r, r, r) c from range(1230000) t(r);")
)
result = test.run()
result.check_stdout('1.23 million rows')

View File

@@ -0,0 +1,65 @@
# fmt: off
import pytest
import subprocess
import sys
from typing import List
from conftest import ShellTest
from tools.shell.tests.conftest import random_filepath
@pytest.mark.parametrize("command", [".sh ls", ".cd ..", ".log file", ".import file.csv tbl", ".open new_file", ".output out", ".once out", ".excel out", ".read myfile.sql"])
def test_safe_mode_command(shell, command):
test = (
ShellTest(shell, ['-safe'])
.statement(command)
)
result = test.run()
result.check_stderr('cannot be used in -safe mode')
@pytest.mark.parametrize("param", [(".sh ls", 'cannot be used in -safe mode'), ("INSTALL extension", "Permission Error")])
def test_safe_mode_dot_command(shell, param):
command = param[0]
expected_error = param[1]
test = (
ShellTest(shell)
.statement('.safe_mode')
.statement(command)
)
result = test.run()
result.check_stderr(expected_error)
def test_safe_mode_database_basic(shell, random_filepath):
test = (
ShellTest(shell, [random_filepath, '-safe'])
.statement('CREATE TABLE integers(i INT)')
.statement('INSERT INTO integers VALUES (1), (2), (3)')
.statement('SELECT SUM(i) FROM integers')
)
result = test.run()
result.check_stdout("6")
@pytest.mark.parametrize("command", [".sh ls", ".cd ..", ".log file", ".import file.csv tbl", ".open new_file", ".output out", ".once out", ".excel out", ".read myfile.sql"])
@pytest.mark.parametrize("persistent", [False, True])
def test_safe_mode_database_commands(shell, random_filepath, command, persistent):
arguments = ['-safe'] if not persistent else [random_filepath, '-safe']
test = (
ShellTest(shell, arguments)
.statement(command)
)
result = test.run()
result.check_stderr('cannot be used in -safe mode')
@pytest.mark.parametrize("sql", ["COPY (SELECT 42) TO 'test.csv'", "LOAD spatial", "INSTALL spatial", "ATTACH 'file.db' AS file"])
@pytest.mark.parametrize("persistent", [False, True])
def test_safe_mode_query(shell, random_filepath, sql, persistent):
arguments = ['-safe'] if not persistent else [random_filepath, '-safe']
test = (
ShellTest(shell, arguments)
.statement(sql)
)
result = test.run()
result.check_stderr('disabled')
# fmt: on

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,51 @@
# fmt: off
import pytest
import subprocess
import sys
from typing import List
from conftest import ShellTest
import os
from pathlib import Path
def test_left_align(shell):
test = (
ShellTest(shell)
.statement(".mode box")
.statement(".width 5")
.statement(f'select 100 AS r')
)
result = test.run()
result.check_stdout("│ 100 │")
def test_right_align(shell):
test = (
ShellTest(shell)
.statement(".mode box")
.statement(".width -5")
.statement(f'select 100 AS r')
)
result = test.run()
result.check_stdout("│ 100 │")
def test_markdown(shell):
test = (
ShellTest(shell)
.statement(".mode markdown")
.statement("select 42 a, 'hello' str")
)
result = test.run()
result.check_stdout("| a | str |")
def test_mode_insert_table(shell):
test = (
ShellTest(shell)
.statement(".mode box mytable")
)
result = test.run()
result.check_stderr("TABLE argument can only be used with .mode insert")

View File

@@ -0,0 +1,47 @@
# fmt: off
import pytest
import subprocess
import sys
from typing import List
from conftest import ShellTest
import os
def test_temp_directory(shell, tmp_path):
temp_dir = tmp_path / 'random_dir'
temp_dir.mkdir()
temp_file = temp_dir / 'myfile'
with open(temp_file, 'w+') as f:
f.write('hello world')
test = (
ShellTest(shell)
.statement(f"SET temp_directory='{temp_dir.as_posix()}';")
.statement("PRAGMA memory_limit='2MB';")
.statement("CREATE TABLE t1 AS SELECT * FROM range(1000000);")
)
result = test.run()
# make sure the temp directory or existing files are not deleted
assert os.path.isdir(temp_dir)
with open(temp_file, 'r') as f:
assert f.read() == "hello world"
# all other files are gone
assert os.listdir(temp_dir) == ['myfile']
os.remove(temp_file)
os.rmdir(temp_dir)
test = (
ShellTest(shell)
.statement(f"SET temp_directory='{temp_dir.as_posix()}';")
.statement("PRAGMA memory_limit='2MB';")
.statement("CREATE TABLE t1 AS SELECT * FROM range(1000000);")
)
result = test.run()
# make sure the temp directory is deleted
assert not os.path.exists(temp_dir)
assert not os.path.isdir(temp_dir)
# fmt: on