Skip to content

Commit

Permalink
Download example databases for use in CI tests (#608)
Browse files Browse the repository at this point in the history
* 💽 Create SQL test data and dockerfile spec

* Remove large binary file from history w/git-filter

* Rely on externally provided example data.

* Remove slow-test code, no longer needed.

* Skip test if example database does not exist.

* Docker container no longer hosted here

* Download, get and mount 💾 .sql data (empty)

* Correct mountpoint 🐳

* Namespaced test database deployment 📇

* Pin Postgres image to ensure compat with .sql dump.

* Use curl instead of wget; make script idempotent.

* Download sqlite data.

* Test against SQLite with example data

---------

Co-authored-by: Dan Allan <[email protected]>
  • Loading branch information
Kezzsim and danielballan authored Jan 2, 2024
1 parent 381f94b commit 58969e7
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 39 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ jobs:
shell: bash -l {0}
run: |
set -vxeuo pipefail
coverage run -m pytest -v -m "not slow"
coverage run -m pytest -v
coverage report
env:
# Provide test suite with a PostgreSQL database to use.
Expand Down
4 changes: 4 additions & 0 deletions continuous_integration/scripts/download_sqlite_data.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
set -e

curl -fsSLO https://github.com/bluesky/tiled-example-database/releases/latest/download/tiled_test_db_sqlite.db
5 changes: 4 additions & 1 deletion continuous_integration/scripts/start_postgres.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#!/bin/bash
set -e

docker run -d --rm --name tiled-test-postgres -p 5432:5432 -e POSTGRES_PASSWORD=secret -d docker.io/postgres
# Try and get the latest postgres test data from the tiled-example-database repository
curl -fsSLO https://github.com/bluesky/tiled-example-database/releases/latest/download/tiled_test_db_pg.sql

docker run -d --rm --name tiled-test-postgres -p 5432:5432 -e POSTGRES_PASSWORD=secret -e POSTGRES_DB=tiled-example-data -v ./tiled_test_db_pg.sql:/docker-entrypoint-initdb.d/tiled_test_db_pg.sql -d docker.io/postgres:16
docker ps
3 changes: 0 additions & 3 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,3 @@ log_cli = 1
log_cli_level = WARNING
log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
log_cli_date_format=%Y-%m-%d %H:%M:%S
addopts = --strict-markers -m 'not slow'
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
118 changes: 98 additions & 20 deletions tiled/_tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import tempfile
from pathlib import Path

import asyncpg
import pytest
import pytest_asyncio
from sqlalchemy.ext.asyncio import create_async_engine

from .. import profiles
from ..catalog import from_uri, in_memory
Expand Down Expand Up @@ -97,37 +99,113 @@ def poll_enumerate():

# To test with postgres, start a container like:
#
# docker run --name tiled-test-postgres -p 5432:5432 -e POSTGRES_PASSWORD=secret -d docker.io/postgres
# docker run --name tiled-test-postgres -p 5432:5432 -e POSTGRES_PASSWORD=secret -d docker.io/postgres:16
# and set this env var like:
#
# TILED_TEST_POSTGRESQL_URI=postgresql+asyncpg://postgres:secret@localhost:5432

TILED_TEST_POSTGRESQL_URI = os.getenv("TILED_TEST_POSTGRESQL_URI")


@pytest_asyncio.fixture(params=["sqlite", "postgresql"])
async def adapter(request, tmpdir):
@pytest_asyncio.fixture
async def postgresql_adapter(request, tmpdir):
"""
Adapter instance
Note that startup() and shutdown() are not called, and must be run
either manually (as in the fixture 'a') or via the app (as in the fixture 'client').
"""
if request.param == "sqlite":
adapter = in_memory(writable_storage=str(tmpdir))
if not TILED_TEST_POSTGRESQL_URI:
raise pytest.skip("No TILED_TEST_POSTGRESQL_URI configured")
# Create temporary database.
async with temp_postgres(TILED_TEST_POSTGRESQL_URI) as uri_with_database_name:
# Build an adapter on it, and initialize the database.
adapter = from_uri(
uri_with_database_name,
writable_storage=str(tmpdir),
init_if_not_exists=True,
)
yield adapter
elif request.param == "postgresql":
if not TILED_TEST_POSTGRESQL_URI:
raise pytest.skip("No TILED_TEST_POSTGRESQL_URI configured")
# Create temporary database.
async with temp_postgres(TILED_TEST_POSTGRESQL_URI) as uri_with_database_name:
# Build an adapter on it, and initialize the database.
adapter = from_uri(
uri_with_database_name,
writable_storage=str(tmpdir),
init_if_not_exists=True,
)
yield adapter
await adapter.shutdown()
else:
assert False


@pytest_asyncio.fixture
async def postgresql_with_example_data_adapter(request, tmpdir):
"""
Adapter instance
Note that startup() and shutdown() are not called, and must be run
either manually (as in the fixture 'a') or via the app (as in the fixture 'client').
"""
if not TILED_TEST_POSTGRESQL_URI:
raise pytest.skip("No TILED_TEST_POSTGRESQL_URI configured")
DATABASE_NAME = "tiled-example-data"
uri = TILED_TEST_POSTGRESQL_URI
if uri.endswith("/"):
uri = uri[:-1]
uri_with_database_name = f"{uri}/{DATABASE_NAME}"
engine = create_async_engine(uri_with_database_name)
try:
async with engine.connect():
pass
except asyncpg.exceptions.InvalidCatalogNameError:
raise pytest.skip(
f"PostgreSQL instance contains no database named {DATABASE_NAME!r}"
)
adapter = from_uri(
uri_with_database_name,
writable_storage=str(tmpdir),
)
yield adapter


@pytest_asyncio.fixture
async def sqlite_adapter(request, tmpdir):
"""
Adapter instance
Note that startup() and shutdown() are not called, and must be run
either manually (as in the fixture 'a') or via the app (as in the fixture 'client').
"""
yield in_memory(writable_storage=str(tmpdir))


@pytest_asyncio.fixture
async def sqlite_with_example_data_adapter(request, tmpdir):
"""
Adapter instance
Note that startup() and shutdown() are not called, and must be run
either manually (as in the fixture 'a') or via the app (as in the fixture 'client').
"""
SQLITE_DATABASE_PATH = Path("./tiled_test_db_sqlite.db")
if not SQLITE_DATABASE_PATH.is_file():
raise pytest.skip(f"Could not find {SQLITE_DATABASE_PATH}")
adapter = from_uri(
f"sqlite+aiosqlite:///{SQLITE_DATABASE_PATH}",
writable_storage=str(tmpdir),
)
yield adapter


@pytest.fixture(params=["sqlite_adapter", "postgresql_adapter"])
def adapter(request):
"""
Adapter instance
Note that startup() and shutdown() are not called, and must be run
either manually (as in the fixture 'a') or via the app (as in the fixture 'client').
"""
yield request.getfixturevalue(request.param)


@pytest.fixture(
params=["sqlite_with_example_data_adapter", "postgresql_with_example_data_adapter"]
)
def example_data_adapter(request):
"""
Adapter instance
Note that startup() and shutdown() are not called, and must be run
either manually (as in the fixture 'a') or via the app (as in the fixture 'client').
"""
yield request.getfixturevalue(request.param)
19 changes: 5 additions & 14 deletions tiled/_tests/test_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,24 +156,14 @@ async def test_search(a):
assert await d.search(Eq("number", 12)).keys_range(0, 5) == ["c"]


@pytest.mark.slow
@pytest.mark.asyncio
async def test_metadata_index_is_used(a):
for i in range(10000):
await a.create_node(
metadata={
"number": i,
"number_as_string": str(i),
"nested": {"number": i, "number_as_string": str(i), "bool": bool(i)},
"bool": bool(i),
},
specs=[],
structure_family="array",
)
# Check that an index (specifically the 'top_level_metdata' index) is used
async def test_metadata_index_is_used(example_data_adapter):
a = example_data_adapter # for succinctness below
# Check that an index (specifically the 'top_level_metadata' index) is used
# by inspecting the content of an 'EXPLAIN ...' query. The exact content
# is intended for humans and is not an API, but we can coarsely check
# that the index of interest is mentioned.
await a.startup()
with record_explanations() as e:
results = await a.search(Key("number_as_string") == "3").keys_range(0, 5)
assert len(results) == 1
Expand All @@ -200,6 +190,7 @@ async def test_metadata_index_is_used(a):
)
assert len(results) == 1
assert "top_level_metadata" in str(e)
await a.shutdown()


@pytest.mark.asyncio
Expand Down

0 comments on commit 58969e7

Please sign in to comment.