Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html)

---

## [1.6.0] — 2026-06-17

### Added
- **Fine-grained migration step control** — `MRTFixture` gains `upgrade_to(revision)`, `upgrade_one()`, `downgrade_one()`, `downgrade_to(revision)`, and `current_revision()` for testing data migration logic at any intermediate point in the chain. Achieves full API parity with pytest-alembic. (#86, #92)

### Fixed
- **Django mode step methods raised `AttributeError` instead of `RuntimeError`** — `upgrade()`, `upgrade_to()`, `upgrade_one()`, `downgrade()`, `downgrade_one()`, `downgrade_to()`, and `current_revision()` all called `self._runner.*` without checking `_django_mode`. In Django mode `_runner` is `None`, so these methods crashed with an uninformative `AttributeError`. They now raise `RuntimeError` with a clear message, consistent with `check_revision()` and `assert_reversible()`.
- **`check_static()` raised `AttributeError` in Django mode** — `check_static()` called `self._runner.get_versions_dir()` without a Django mode guard. Now raises `RuntimeError` with a message directing users to `check_migration()` / `check_all()`.
- **Timeout path did not attempt DB state recovery** — when a migration exceeded `MRTConfig.migration_timeout`, the `FuturesTimeout` was caught but no recovery was attempted. The DB could be left in an upgraded state, corrupting all subsequent revisions in `check_all()`. Recovery now mirrors the `except Exception` path added in v0.7.

---

## [1.5.0] — 2026-06-11

### Removed
Expand Down
30 changes: 21 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<a href="https://github.com/croc100/pytest-mrt/actions"><img src="https://img.shields.io/github/actions/workflow/status/croc100/pytest-mrt/ci.yml?branch=main&label=tests" alt="CI"></a>
<a href="https://codecov.io/gh/croc100/pytest-mrt"><img src="https://codecov.io/gh/croc100/pytest-mrt/graph/badge.svg?token=CODECOV_TOKEN" alt="Coverage"></a>
<img src="https://img.shields.io/badge/status-stable-brightgreen" alt="Production/Stable">
<img src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue" alt="Python 3.10-3.13">
<img src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue" alt="Python 3.10-3.14">
<img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License">
<a href="https://gcv-five.vercel.app/croc100/pytest-mrt"><img src="https://img.shields.io/badge/contributors-GCV-6e40c9?logo=github" alt="Contributors"></a>
</p>
Expand Down Expand Up @@ -167,14 +167,14 @@ Add to `.pre-commit-config.yaml` to run `mrt check` automatically before every p
```yaml
# Alembic
- repo: https://github.com/croc100/pytest-mrt
rev: v1.4.0
rev: v1.5.0
hooks:
- id: mrt-check
args: [alembic/versions/]

# Django
- repo: https://github.com/croc100/pytest-mrt
rev: v1.4.0
rev: v1.5.0
hooks:
- id: mrt-check
args: [myapp/migrations/]
Expand Down Expand Up @@ -263,13 +263,25 @@ Legacy syntax `# mrt: ignore` is still supported for backward compatibility.

The key difference from pytest-alembic: pytest-mrt seeds actual rows before each rollback and verifies they survive. A migration that reverses the schema cleanly but silently destroys data will pass pytest-alembic and fail pytest-mrt.

## What's new in v1.4.0
## What's new in v1.6.0

- **`mrt check --format json/html`** — structured JSON output for CI tooling; self-contained HTML safety report
- **`mrt check --watch`** — re-runs automatically whenever a migration file changes
- **`mrt check --min-revision`** — skip revisions older than a configured floor (mirrors `MRTConfig.minimum_downgrade_revision`)
- **Django squashmigrations detection** — MRT601/MRT602 catch unsafe `RunPython` in squashed migrations
- **`minimum_downgrade_revision` in dynamic tests** — floor now respected by the `mrt` fixture `check_all()`, not just static analysis
- **Fine-grained migration step control** — `upgrade_to()`, `upgrade_one()`, `downgrade_one()`, `downgrade_to()`, `current_revision()` let you test data migration logic at any point in the chain:

```python
def test_data_migration(mrt):
mrt.upgrade_to("abc123") # upgrade to a specific revision
mrt.seed("users", [...]) # seed data at that checkpoint
mrt.upgrade_one() # apply exactly one more step
assert mrt.current_revision() == "def456"
mrt.downgrade_one() # roll back one step
mrt.downgrade_to("base") # roll all the way back
```

## Migrating from v1.4.x

**v1.5.0 removed `mrt fix` and `mrt clean-backups`** — migration code generation is a *transform* operation, not a *verify* operation, and was out of scope. Projects relying on these commands should pin `pytest-mrt<1.5.0`.

The `fixable` field in `mrt check --format json` output was also removed in v1.5.0.

## Changelog

Expand Down
46 changes: 29 additions & 17 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Roadmap

## Current status: Production/Stable (v1.3.0)
## Current status: Production/Stable (v1.5.0 on PyPI — v1.6.0 on main)

pytest-mrt is production-ready. The core API (`MRTConfig`, `mrt` fixture, `mrt check`) is stable and
breaking changes will be versioned. See [`docs/api.md`](docs/api.md) for the stability guarantee.
Expand Down Expand Up @@ -64,45 +64,57 @@ breaking changes will be versioned. See [`docs/api.md`](docs/api.md) for the sta
- Test coverage: `default_tests.py` and `drift.py` at 100%
- Documentation fully updated (pattern counts, version refs, suppression docs)

## v1.3.0 — Incremental CI + pre-commit + Django fix (shipped)
## v1.3.0 — Incremental CI + pre-commit (shipped)

- **`mrt check --since <revision>`** — check only migrations added since a given revision; eliminates re-scanning full history on every PR
- **pre-commit hook** — `.pre-commit-hooks.yaml` ships with the package; two-line setup
- **Django-aware `mrt fix`** — auto-generates reverse operations for `RunSQL`, `RunPython`, `RemoveField`, `DeleteModel` with transactional DB backup/restore scaffolding
- **`mrt clean-backups`** — CLI command to remove `_mrt_backups` data after deployment

---

## v1.4.0 — Under consideration
## v1.4.0 — CI integration + rolling deploy (shipped)

- **`mrt check --watch`** — re-run on file change during development
- **Django: `squashmigrations` detection** — squashed migrations with unresolved refs
- **Per-pattern confidence scores** in JSON output
- **HTML report: source line links** — click a finding to jump to the migration file
- **Sentry integration** — report migration failures as Sentry events
- **GitHub App** — automated PR comments with migration risk summary
- **VS Code extension** — inline warnings in migration files
- **`mrt check --format json/html`** — structured JSON output for CI tooling; self-contained HTML safety report (`--output` to write file)
- **`mrt check --watch`** — re-run automatically when migration files change; Ctrl-C to stop
- **`mrt check --min-revision`** — skip revisions at or before a floor; mirrors `MRTConfig.minimum_downgrade_revision`
- **`mrt check --check-compat`** — rolling-deploy compatibility checks (MRT701–MRT705): DROP COLUMN, RENAME COLUMN, DROP TABLE, ADD NOT NULL without default, incompatible type changes
- **Django squashmigrations detection** — MRT601/MRT602: catches `RunPython` without `reverse_code` in squashed migrations and suspicious squash filenames
- **`MRTConfig.minimum_downgrade_revision`** — permanent rollback floor respected by `check_all()` in the `mrt` fixture
- **`croc100/pytest-mrt-action` v1.0.0** — GitHub Actions action that posts findings as a job summary

---

## v1.5.0 — Scope reduction (breaking, shipped)

- **Removed `mrt fix` and `mrt clean-backups`** — migration code generation is a *transform* operation, not a *verify* operation. Out of scope. Projects that rely on these must pin `pytest-mrt<1.5.0`.
- **Removed `fixable` field from `mrt check --format json`** — advertised the removed auto-fix capability. Breaking for downstream tooling that read this field.
- **Fixed** `RollbackVerifier` false positive on failed custom seeds — rows whose `INSERT` fails are no longer tracked, eliminating "lost after rollback" false positives.

---

## v1.6.0 — Fine-grained step control (shipped on main, pending release)

- **`upgrade_to(revision)`**, **`upgrade_one()`**, **`downgrade_one()`**, **`downgrade_to(revision)`**, **`current_revision()`** — call any migration step from a test, enabling mid-chain data seeding and assertion

---

## Next / Under consideration
## Under consideration

These are not committed to a specific version yet:
These are not committed to a version yet:

- **`mrt check --watch`** — re-run on file change during development
- **Django: `squashmigrations` detection** — squashed migrations with unresolved refs
- **Django squashmigrations: full rollback plan** — detect and test rollback paths through squashed migration graphs dynamically (static detection is already in v1.4.0)
- **Per-pattern confidence scores** in JSON output
- **HTML report: source line links** — click a finding to jump to the migration file
- **Sentry integration** — report migration failures as Sentry events
- **GitHub App** — automated PR comments with migration risk summary
- **VS Code extension** — inline warnings in migration files
- **`mrt check --check-compat` Django support** — currently Alembic only

---

## What won't be in scope

- Executing migrations in production (this is a *testing* tool only)
- **Migration code generation / auto-fix** — pytest-mrt verifies migrations; it does not rewrite them. The former `mrt fix` / `mrt clean-backups` commands were removed in v1.5.0 (out of scope: "transform", not "verify")
- **Migration code generation / auto-fix** — removed in v1.5.0 (out of scope: "transform", not "verify")
- Schema diff tools (use `alembic check` or `django-migration-linter`)
- ORM-agnostic support (focused on Alembic and Django)

Expand Down
Loading