Skip to content

Commit

Permalink
Use DSLR instead of postgres client
Browse files Browse the repository at this point in the history
  • Loading branch information
AdamHawtin committed Nov 22, 2024
1 parent 043298f commit 8a307ea
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 72 deletions.
7 changes: 0 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,6 @@ jobs:
env:
DJANGO_SETTINGS_MODULE: cms.settings.functional_test
SECRET_KEY: fake_secret_key_to_run_tests # pragma: allowlist secret
PG_DUMP: /home/linuxbrew/.linuxbrew/opt/postgresql@16/bin/pg_dump
PG_RESTORE: /home/linuxbrew/.linuxbrew/opt/postgresql@16/bin/pg_restore
BROWSER: ${{ matrix.browser }}
PLAYWRIGHT_TRACES_DIR: playwright_traces
PLAYWRIGHT_TRACE: 'true'
Expand All @@ -173,11 +171,6 @@ jobs:
- name: Playwright Install
run: poetry run python -m playwright install --with-deps ${{ matrix.browser }}

- name: Install/Update Postgres
# We need pg_dump and pg_restore for the functional tests
# The Ubuntu runner does come with postgresql, but the wrong version, so we must acquire v16
run: /home/linuxbrew/.linuxbrew/bin/brew install postgresql@16

- uses: actions/download-artifact@v4
with:
name: static
Expand Down
78 changes: 14 additions & 64 deletions functional_tests/behave_fixtures.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
import os
import subprocess
from pathlib import Path

import django
from behave import fixture
from behave.runner import Context
from django.conf import settings
from django.db import close_old_connections
from django.test.runner import DiscoverRunner
from django.test.testcases import LiveServerTestCase
from dslr.config import settings as dslr_settings
from dslr.operations import create_snapshot, delete_snapshot, find_snapshot, restore_snapshot

# Adapted from https://behave.readthedocs.io/en/stable/usecase_django.html#manual-integration

# Allow the path to the postgres client tools we depend on to be configured from the environment,
# as they may not always be accessible by name
PG_DUMP = os.getenv("PG_DUMP", "/usr/local/bin/pg_dump")
PG_RESTORE = os.getenv("PG_RESTORE", "/usr/local/bin/pg_restore")


@fixture
def django_test_runner(context: Context) -> None:
Expand All @@ -33,15 +26,17 @@ def django_test_runner(context: Context) -> None:
context.old_db_config = context.test_runner.setup_databases()

# At this point, the Django test runner has initialised the test database and run migrations
# We dump the database out to a file, to allow us to later restore the database to this clean, initial state
context.initial_pg_dump_file = dump_database_to_file()
# Initialize DSLR and take a snapshot of the clean, post migration test database
initialize_dslr()
context.clean_snapshot_name = "clean_snapshot"
create_snapshot(context.clean_snapshot_name)

# Yield to resume the tests execution
yield # Everything after this point is run after the entire test run

context.test_runner.teardown_databases(context.old_db_config)
context.test_runner.teardown_test_environment()
context.initial_pg_dump_file.unlink(missing_ok=True) # Delete the dump file
delete_snapshot(find_snapshot(context.clean_snapshot_name))


@fixture
Expand Down Expand Up @@ -73,62 +68,17 @@ def django_test_case(context: Context) -> None:
close_old_connections()

context.test_case.tearDown()
restore_database_dump(context.initial_pg_dump_file)
restore_snapshot(find_snapshot(context.clean_snapshot_name))
context.test_case_class.tearDownClass()
del context.test_case_class
del context.test_case


def dump_database_to_file() -> Path:
"""Dump the Django default database out to a file.
Returns the Path for the dumped file.
"""
dump_file = Path("tmp_test_pg_dump.sql")

def initialize_dslr() -> None:
"""Initialize DSLR to point to the Django default database."""
db_config = settings.DATABASES["default"]

# Use the pg_dump utility to dump the entire database out into the dump file
with dump_file.open("w", encoding="utf-8") as write_file:
subprocess.run( # noqa: S603
[
PG_DUMP,
"--format=custom", # Custom format is required to use pg_restore,
"-d",
db_config["NAME"],
"-h",
db_config["HOST"],
"-p",
str(db_config["PORT"]),
"-U",
db_config["USER"],
],
stdout=write_file,
env={"PGPASSWORD": db_config["PASSWORD"]}, # The password cannot be passed on the command line
check=True,
)
return dump_file


def restore_database_dump(dump_file: Path) -> None:
"""Do a clean restore on the default Django database from a dump file."""
db_config = settings.DATABASES["default"]

# Use the pg_restore utility to load the database dump file
subprocess.run( # noqa: S603
[
PG_RESTORE,
"--format=custom",
"--clean", # Custom format is required to use pg_restore,
"-d",
db_config["NAME"],
"-h",
db_config["HOST"],
"-p",
str(db_config["PORT"]),
"-U",
db_config["USER"],
dump_file.absolute(),
],
env={"PGPASSWORD": db_config["PASSWORD"]}, # The password cannot be passed on the command line
check=True,
db_conn_str = (
f"postgresql://{db_config['USER']}:{db_config['PASSWORD']}"
f"@{db_config['HOST']}:{db_config['PORT']}/{db_config['NAME']}"
)
dslr_settings.initialize(url=db_conn_str, debug=False)
78 changes: 77 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ wagtail-factories = "^4.1.0"
coverage = "^7.6.4"

dslr = "^0.4.0"
psycopg2-binary = "^2.9.10"
django-debug-toolbar = "^4.4.6"
pudb = "^2024.1"
tblib = "^3.0.0"
Expand Down

0 comments on commit 8a307ea

Please sign in to comment.