diff --git a/.github/workflows/driver-smoke.yaml b/.github/workflows/driver-smoke.yaml new file mode 100644 index 0000000..240cc2c --- /dev/null +++ b/.github/workflows/driver-smoke.yaml @@ -0,0 +1,94 @@ +# Copyright (c) 2025-2026 ADBC Drivers Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Run the driver-facing statement tests against real, released ADBC drivers +# to make sure changes to the validation suite keep working against actual +# driver implementations, not just the framework's own unit tests. + +name: Driver Smoke Test + +on: + pull_request: {} + push: + branches: + - main + +defaults: + run: + shell: bash + +permissions: + contents: read + +jobs: + sqlite: + name: "SQLite" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Install uv + uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 + + - name: Install + run: | + uv venv + uv pip install -p .venv/bin/python pytest pyarrow adbc-driver-manager adbc-driver-sqlite . + + - name: Test + env: + SMOKE_DRIVER: sqlite + ADBC_SQLITE_TEST_URI: "file:${{ runner.temp }}/smoke.db" + run: | + .venv/bin/python -m pytest -c smoke/pytest.ini smoke/tests/test_statement.py -v + + postgresql: + name: "PostgreSQL" + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:18 + env: + POSTGRES_PASSWORD: password + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Install uv + uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 + + - name: Install + run: | + uv venv + uv pip install -p .venv/bin/python pytest pyarrow adbc-driver-manager adbc-driver-postgresql . + + - name: Test + env: + SMOKE_DRIVER: postgresql + ADBC_POSTGRESQL_TEST_URI: "postgresql://postgres:password@localhost:5432/postgres" + run: | + .venv/bin/python -m pytest -c smoke/pytest.ini smoke/tests/test_statement.py -v diff --git a/.rat-excludes b/.rat-excludes index 877217f..3da1566 100644 --- a/.rat-excludes +++ b/.rat-excludes @@ -16,3 +16,4 @@ adbc_drivers_validation/queries/*/*.json adbc_drivers_validation/queries/*/*.sql pixi.lock +smoke/queries/.gitkeep diff --git a/pyproject.toml b/pyproject.toml index 2fd7ef4..288e374 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,10 @@ dependencies = [ requires = ["setuptools >= 82.0.1"] build-backend = "setuptools.build_meta" +[tool.setuptools.packages.find] +# Only ship the package itself; smoke/ holds the CI-only driver smoke tests. +include = ["adbc_drivers_validation*"] + [dependency-groups] dev = [ "ruff>=0.15.20", diff --git a/pytest.ini b/pytest.ini index 1b9ef9b..20ee193 100644 --- a/pytest.ini +++ b/pytest.ini @@ -13,6 +13,10 @@ # limitations under the License. [pytest] +# The driver smoke tests in smoke/ have their own configuration +# (smoke/pytest.ini) and are not collected by default. +testpaths = tests + junit_suite_name = validation junit_duration_report = call xfail_strict = true diff --git a/smoke/pytest.ini b/smoke/pytest.ini new file mode 100644 index 0000000..514f24e --- /dev/null +++ b/smoke/pytest.ini @@ -0,0 +1,21 @@ +# Copyright (c) 2025-2026 ADBC Drivers Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[pytest] +markers = + feature: test for a driver-specific feature + requires_features: driver features required for the test to run + +filterwarnings = + ignore:Cannot disable autocommit; conn will not be DB-API 2.0 compliant:adbc_driver_manager._lib.Warning diff --git a/smoke/queries/.gitkeep b/smoke/queries/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/smoke/tests/__init__.py b/smoke/tests/__init__.py new file mode 100644 index 0000000..26001d6 --- /dev/null +++ b/smoke/tests/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2025-2026 ADBC Drivers Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/smoke/tests/conftest.py b/smoke/tests/conftest.py new file mode 100644 index 0000000..d8a8755 --- /dev/null +++ b/smoke/tests/conftest.py @@ -0,0 +1,44 @@ +# Copyright (c) 2025-2026 ADBC Drivers Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Pytest configuration for the driver smoke tests.""" + +import importlib + +import pytest + +from adbc_drivers_validation import model +from adbc_drivers_validation.tests.conftest import ( # noqa: F401 + conn, + conn_factory, + db_kwargs, + manual_test, + noci, + pytest_addoption, + pytest_collection_modifyitems, +) + +from . import quirks + + +@pytest.fixture(scope="session") +def driver(request: pytest.FixtureRequest) -> model.DriverQuirks: + selected = quirks.selected_quirks() + assert request.param == f"{selected.name}:{selected.short_version}" + return selected + + +@pytest.fixture(scope="session") +def driver_path(driver: model.DriverQuirks) -> str: + return importlib.import_module(driver.driver)._driver_path() diff --git a/smoke/tests/quirks.py b/smoke/tests/quirks.py new file mode 100644 index 0000000..9a2cddf --- /dev/null +++ b/smoke/tests/quirks.py @@ -0,0 +1,106 @@ +# Copyright (c) 2025-2026 ADBC Drivers Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Minimal driver quirks for the CI smoke tests. + +These quirks are intentionally small: they define only what is needed to +run the statement-level validation tests against real, released drivers. +They are not shipped as part of the package. +""" + +import os +import re +from pathlib import Path + +from adbc_drivers_validation import model + +_QUERIES_PATH = Path(__file__).parent.parent / "queries" + + +class SQLiteQuirks(model.DriverQuirks): + name = "sqlite" + driver = "adbc_driver_sqlite" + driver_name = "ADBC SQLite Driver" + vendor_name = "SQLite" + vendor_version = "3" + short_version = "3" + features = model.DriverFeatures( + connection_transactions=True, + statement_bind=True, + # The released driver raises NOT_IMPLEMENTED for execute_schema. + statement_execute_schema=False, + statement_get_parameter_schema=True, + statement_prepare=True, + statement_rows_affected=True, + statement_rows_affected_ddl=True, + ) + setup = model.DriverSetup( + database={"uri": model.FromEnv("ADBC_SQLITE_TEST_URI")}, + connection={}, + statement={}, + ) + + @property + def queries_paths(self) -> tuple[Path]: + return (_QUERIES_PATH,) + + def is_table_not_found(self, table_name: str | None, error: Exception) -> bool: + return "no such table" in str(error).lower() + + +class PostgreSQLQuirks(model.DriverQuirks): + name = "postgresql" + driver = "adbc_driver_postgresql" + driver_name = "ADBC PostgreSQL Driver" + vendor_name = "PostgreSQL" + vendor_version = re.compile(r"18[0-9]{4}") + short_version = "18" + features = model.DriverFeatures( + connection_transactions=True, + statement_bind=True, + statement_execute_schema=True, + statement_get_parameter_schema=True, + statement_prepare=True, + statement_rows_affected=True, + statement_rows_affected_ddl=False, + ) + setup = model.DriverSetup( + database={"uri": model.FromEnv("ADBC_POSTGRESQL_TEST_URI")}, + connection={}, + statement={}, + ) + + @property + def queries_paths(self) -> tuple[Path]: + return (_QUERIES_PATH,) + + def bind_parameter(self, index: int) -> str: + """PostgreSQL uses $1, $2, $3, etc. for parameter placeholders.""" + return f"${index}" + + def is_table_not_found(self, table_name: str | None, error: Exception) -> bool: + message = str(error).lower() + if table_name is not None and table_name.lower() not in message: + return False + return "does not exist" in message or "undefined_table" in message + + +def selected_quirks() -> model.DriverQuirks: + """Return the quirks selected by the SMOKE_DRIVER environment variable.""" + name = os.environ.get("SMOKE_DRIVER", "sqlite") + if name == "sqlite": + return SQLiteQuirks() + elif name == "postgresql": + return PostgreSQLQuirks() + raise ValueError(f"Unknown SMOKE_DRIVER: {name!r}") diff --git a/smoke/tests/test_statement.py b/smoke/tests/test_statement.py new file mode 100644 index 0000000..cd6235d --- /dev/null +++ b/smoke/tests/test_statement.py @@ -0,0 +1,30 @@ +# Copyright (c) 2025-2026 ADBC Drivers Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Run the statement-level validation tests against a real driver.""" + +import pytest + +import adbc_drivers_validation.tests.statement +from adbc_drivers_validation.tests.statement import ( + TestStatement, # noqa: F401 +) + +from . import quirks + + +def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: + adbc_drivers_validation.tests.statement.generate_tests( + [quirks.selected_quirks()], metafunc + )