Skip to content

feat: add rowx.testing pytest plugin - #164

Merged
mplemay merged 2 commits into
mainfrom
feat/149-testing-plugin
Nov 1, 2026
Merged

mplemay merged 2 commits into
mainfrom
feat/149-testing-plugin

Conversation

@mplemay

@mplemay mplemay commented Nov 1, 2026 •

Copy link
Copy Markdown
Owner

Closes #149

What

  • Adds src/rowx/testing.py, an opt-in pytest plugin (pytest_plugins = ["rowx.testing"] or -p rowx.testing). There
    is no pytest11 entry point, and import rowx / import rowx.postgres never import pytest.
  • Config: the rowx_database_url ini option, else the database_url_env variable from [tool.rowx]. The migrations
    come from [tool.rowx.migrate] through the check/config.py loader and load_migrator. It also registers
    --rowx-keep-templates.
  • Template: rowx_tpl_<hash> (sha256, first 16 hex chars) over everything that shapes the template: the history
    table, the create_schemas (sorted, deduplicated) and the sorted forward (version, checksum) list. A config change
    therefore builds a new template instead of reusing one built under the old config.
  • Locking: the build lock (an advisory lock) is always taken in the postgres maintenance database, the one the admin
    helpers use for these names, so sessions whose URLs select different databases still serialise on it (advisory locks
    are per database). The check, build and stale cleanup, and every clone, run under it.
  • A template counts as complete once it carries a COMMENT ON DATABASE mark; an existing unmarked one (a build that
    died) is dropped and rebuilt. The build connection is closed before the mark is written and before any clone.
  • In-use templates: each session holds a shared advisory lock keyed on its template (on its own connection in the
    maintenance database) for the whole session, taken before the build lock is released. Stale cleanup only drops a
    rowx_tpl_* database when it can take that key exclusively, so a template another live session is using (idle
    between clones) is never dropped. Cleanup runs after a new build unless --rowx-keep-templates is set.
  • Fixtures (function-scoped, synchronous, driven by asyncio.run): rowx_database_url clones the template as
    rowx_test_<hex> (URL built with with_database) and drops it WITH (FORCE) at teardown, also after a failing test;
    rowx_connection and rowx_pool sit on top of it.
  • With no URL the fixtures skip, and with ROWX_REQUIRE_DB=1 they fail. Failures while building, cloning or dropping
    are reported with the URL redacted.

New tests

All in src/rowx/__tests__/integration/test_testing.py. Inner sessions run as pytest subprocesses.

  • test_importing_rowx_does_not_import_pytest (subprocess; not DB)
  • test_template_name_follows_the_migrations_and_their_config (not DB; covers table and create_schemas)
  • test_plugin_is_inactive_unless_opted_in
  • test_no_url_skips_or_fails[False|True]
  • test_url_comes_from_the_database_url_env (db)
  • test_each_test_gets_an_isolated_migrated_database (db)
  • test_a_failing_test_still_drops_its_clone (db)
  • test_pool_and_connection_work_in_a_coroutine (db)
  • test_template_is_reused_then_rebuilt_when_a_migration_changes (db; also asserts stale cleanup)
  • test_a_config_change_rebuilds_the_template (db)
  • test_keep_templates_keeps_the_stale_template (db)
  • test_an_incomplete_template_is_rebuilt (db)
  • test_a_failing_migration_reports_and_leaves_nothing (db)
  • test_cleanup_spares_a_template_another_session_holds (db): one session sits idle between clones (gated on an
    advisory lock, synchronised with LISTEN/NOTIFY) while a second session, with other migrations and a URL selecting
    another database, builds its template and runs cleanup; the first session's next clone still succeeds.
  • test_sessions_on_different_databases_share_the_build_lock (db): two sessions with the same migrations and URLs
    selecting different databases both queue on the one build lock (polled in pg_locks, never sleep), then both pass
    on the one template. This stands in for the xdist case.
  • test_the_template_is_built_once_under_xdist (db; skips because xdist is not in the dev group)

The project fixture asserts that no new rowx_test_* database outlives each test and drops the templates it created.

pytest-asyncio and anyio are not dev dependencies, so the issue's "from a pytest-asyncio test" case is covered by a plain
sync test that drives the fixtures through asyncio.run. No dependencies were added to the dev group.

Wiring and out-of-scope edits

  • pyproject.toml: new optional extra pytest = ["pytest>=9.1.1"] (and the matching uv.lock change). ty's
    missing-direct-dependency rule rejects import pytest in src/rowx/testing.py unless pytest is a declared
    dependency. The extra declares what the plugin really needs (rowx[pytest]) without making pytest a runtime
    dependency, and avoids both a suppression and a linter-config change. Note that architecture.md says pytest "stays in
    the dev group"; it still does, and the extra is an addition the design may want to mention.
  • Out of scope: crates/rowx-postgres/tests/migrate.rs's TestDb leaked its rowx_mig_* database whenever a test
    failed (a panicking assert or an early ? skipped finish). It now drops the database in Drop (on a fresh runtime
    in its own thread) unless finish already did.
  • The unit-style tests live in the integration file because a unit/test_testing.py would clash with it on the module
    basename (neither directory is a package).

Spec ambiguities

  • The "complete" marker: the design says reuse when the template "exists and is complete" without saying how. I use a
    COMMENT ON DATABASE mark written after the migrations succeed.
  • Hash input: the design says (version, checksum). This PR also hashes the history table and create_schemas,
    since both change what the template holds; .down.sql files are left out. Proposed design change: "over the
    migration config and the (version, checksum) list".
  • The build lock lives in the postgres maintenance database, and sessions mark their template in use with a shared
    advisory lock; neither is spelled out by the design.
  • The template is created from template0 (as --scratch does), not template1.
  • Clones are serialised with the same advisory lock as the build, because Postgres refuses to clone a template another
    session is connected to.
  • Stale cleanup is best effort (a template of another role it can't drop is skipped) and only runs when a new template
    is built.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-01T03:04:56.626632Z 4e87aa5 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4e87aa5231

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/rowx/testing.py Resolved
Comment thread src/rowx/testing.py Resolved
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mplemay
mplemay merged commit 5023417 into main Nov 1, 2026
5 of 6 checks passed
@mplemay
mplemay deleted the feat/149-testing-plugin branch November 1, 2026 03:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add rowx.testing pytest plugin

1 participant