Skip to content

Commit 93d6314

Browse files
authored
test: make MV schedule and seed_onto_view functional tests rerun-safe (#1581)
## Description Two functional tests flake in the nightly Integration suites when a transient warehouse/auth blip kills an attempt mid-test. `pytest-rerunfailures` retries in the same process **without** tearing down the class-scoped `project`, so the leftover relation and the in-place-rewritten model file leak into the retry, which then fails on a spurious `AssertionError`: - `TestMaterializedViewScheduleLifecycle::test_full_lifecycle` — `assert 'cron' == 'manual'` (retry re-enters with the MV already switched to cron). - `TestSeedOntoView::test_seed_onto_view_is_rejected` — `dbt exit state did not match expected` (leftover `seed_over_view` relation). These are **not** code regressions — the same tests pass on a clean run — but they turn transient infra flakiness into red CI that reads like a regression during nightly triage. ## Fix Adopt the existing `RerunSafeMixin` (`tests/functional/adapter/fixtures.py`) — the codebase-wide convention already used by ~30 classes. Its autouse, function-scoped `reset_project_state` fires on every attempt (the class-scoped `project` survives reruns), restoring the `models` files to their initial content and dropping the named `relations_to_reset` before the retry, so each attempt starts from a clean slate. On the first attempt it is a no-op (files already at initial content, relations don't exist yet), so **happy-path behavior is unchanged**. - `test_schedule_modes.py`: all four MV schedule classes inherit `RerunSafeMixin` with a `relations_to_reset` naming their single MV (`mv_on_update_bare`, `mv_drop_readd`, `mv_lifecycle`, `mv_every_inputs`). The observed failure was `mv_lifecycle`; the siblings share the identical latent leak (in-place `write_file` + fixed names), so they are hardened too. - `test_seeds.py`: `TestSeedOntoView` inherits `RerunSafeMixin` with `relations_to_reset = ("seed_over_view",)`; `TestSeedOntoViewV2` inherits it transitively. ## Verification On the SQL-warehouse profile (`databricks_uc_sql_endpoint`): - **No happy-path regression**: `test_schedule_modes.py` = 4 passed; `TestSeedOntoView` + `TestSeedOntoViewV2` = 2 passed. - **Rerun-safety proof** (throwaway test, not committed): injected a one-shot failure right after the cron transition and ran `--reruns 1`. A bare no-mixin copy **failed on rerun** with the exact nightly signature `assert 'cron' == 'manual'`; the `RerunSafeMixin` copy **passed on rerun**. Final: `1 failed, 1 passed, 2 rerun` — confirming both that the leak is real and that the mixin fixes it. - `pre-commit` (ruff, ruff-format, mypy) passes. Test-only; no adapter/runtime code changes. ## Checklist - [x] I have run this code in development, and it appears to resolve the stated issue. - [x] This PR includes tests, or tests are not required/relevant for this PR. - [x] I have updated the `CHANGELOG.md` and added information about my change.
1 parent 4f5c38e commit 93d6314

3 files changed

Lines changed: 28 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
- Raise the `dbt-adapters` upper bound to `<1.25.0` ([#1507](https://github.com/databricks/dbt-databricks/pull/1507))
3131
- Raise the `databricks-sdk` upper bound to `<0.118.0` to pick up 0.117.0, which fixes `WorkspaceClient` construction failing with `CONTEXT_UNAVAILABLE_FOR_REMOTE_CLIENT` on Spark Connect clusters ([#1517](https://github.com/databricks/dbt-databricks/pull/1517) closes [#1252](https://github.com/databricks/dbt-databricks/issues/1252))
3232
- Raise the `dbt-core` upper bound to `<1.11.13` to include dbt-core 1.11.12 ([#1578](https://github.com/databricks/dbt-databricks/pull/1578))
33-
- Make the remaining incremental functional tests (tags, column tags, tblproperties, liquid clustering, column masks, persist_docs, replace table) rerun-safe so a `pytest --reruns` retry no longer inherits mutated state (rewritten `schema.yml`/model files, half-built relations) from the failed attempt (test-only, no runtime impact).
33+
- Make stateful functional tests rerun-safe so a `pytest --reruns` retry no longer inherits mutated state (rewritten `schema.yml`/model files, half-built relations) from the failed attempt: the remaining incremental tests (tags, column tags, tblproperties, liquid clustering, column masks, persist_docs, replace table), the materialized-view schedule tests, and the `seed_onto_view` test (test-only, no runtime impact). ([#1512](https://github.com/databricks/dbt-databricks/pull/1512) [#1581](https://github.com/databricks/dbt-databricks/pull/1581))
3434
- Add unit tests for the previously-uncovered live event/logging classes (`QueryError`, `ConnectionCreate`, `ConnectionCreateError`, and `DbtCoreHandler` log-level routing), bringing the live code in `events/` and `logging.py` to full coverage (test-only, no runtime impact). ([#1548](https://github.com/databricks/dbt-databricks/pull/1548))
3535

3636
## dbt-databricks 1.12.1 (June 10, 2026)

tests/functional/adapter/materialized_view_tests/test_schedule_modes.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
MaterializedViewConfig,
1010
)
1111
from dbt.adapters.databricks.relation_configs.refresh import RefreshConfig
12+
from tests.functional.adapter.fixtures import RerunSafeMixin
1213
from tests.functional.adapter.materialized_view_tests import fixtures
1314

1415

@@ -27,7 +28,7 @@ def _get_refresh_config(project, identifier):
2728

2829
@pytest.mark.dlt
2930
@pytest.mark.skip_profile("databricks_cluster", "databricks_uc_cluster")
30-
class TestMaterializedViewScheduleModes:
31+
class TestMaterializedViewScheduleModes(RerunSafeMixin):
3132
@pytest.fixture(scope="class", autouse=True)
3233
def seeds(self):
3334
yield {"my_seed.csv": MY_SEED}
@@ -36,6 +37,10 @@ def seeds(self):
3637
def models(self):
3738
yield {"mv_on_update_bare.sql": fixtures.materialized_view_on_update_bare}
3839

40+
@pytest.fixture(scope="class")
41+
def relations_to_reset(self):
42+
return ("mv_on_update_bare",)
43+
3944
def test_on_update_bare_mode_roundtrip(self, project):
4045
util.run_dbt(["seed"])
4146
util.run_dbt(["run", "--models", "mv_on_update_bare"])
@@ -46,7 +51,7 @@ def test_on_update_bare_mode_roundtrip(self, project):
4651

4752
@pytest.mark.dlt
4853
@pytest.mark.skip_profile("databricks_cluster", "databricks_uc_cluster")
49-
class TestMaterializedViewDropAndReadd:
54+
class TestMaterializedViewDropAndReadd(RerunSafeMixin):
5055
"""Drop schedule (config removed) and re-add."""
5156

5257
@pytest.fixture(scope="class", autouse=True)
@@ -57,6 +62,10 @@ def seeds(self):
5762
def models(self):
5863
yield {"mv_drop_readd.sql": fixtures.materialized_view_cron_no_tz}
5964

65+
@pytest.fixture(scope="class")
66+
def relations_to_reset(self):
67+
return ("mv_drop_readd",)
68+
6069
@pytest.fixture(scope="class")
6170
def project_config_update(self):
6271
return {"models": {"on_configuration_change": "apply"}}
@@ -82,7 +91,7 @@ def test_drop_then_readd(self, project):
8291

8392
@pytest.mark.dlt
8493
@pytest.mark.skip_profile("databricks_cluster", "databricks_uc_cluster")
85-
class TestMaterializedViewScheduleLifecycle:
94+
class TestMaterializedViewScheduleLifecycle(RerunSafeMixin):
8695
"""Walks one MV through the realistic schedule lifecycle:
8796
MANUAL → CRON → ON_UPDATE rate-limited → EVERY → (non-refresh change) → MANUAL.
8897
Each transition asserts the post-state schedule via DESCRIBE EXTENDED."""
@@ -95,6 +104,10 @@ def seeds(self):
95104
def models(self):
96105
yield {"mv_lifecycle.sql": fixtures.materialized_view_no_schedule}
97106

107+
@pytest.fixture(scope="class")
108+
def relations_to_reset(self):
109+
return ("mv_lifecycle",)
110+
98111
@pytest.fixture(scope="class")
99112
def project_config_update(self):
100113
return {"models": {"on_configuration_change": "apply"}}
@@ -144,7 +157,7 @@ def test_full_lifecycle(self, project):
144157

145158
@pytest.mark.dlt
146159
@pytest.mark.skip_profile("databricks_cluster", "databricks_uc_cluster")
147-
class TestMaterializedViewEveryAcceptedInputs:
160+
class TestMaterializedViewEveryAcceptedInputs(RerunSafeMixin):
148161
"""Walk one MV through each EVERY unit the regex accepts (HOUR / DAY / WEEK) and
149162
confirm dbt round-trips each against a real warehouse. Uses
150163
`on_configuration_change: apply` so each iteration is an ALTER, not a fresh CREATE."""
@@ -157,6 +170,10 @@ def seeds(self):
157170
def models(self):
158171
yield {"mv_every_inputs.sql": fixtures.materialized_view_with_every("2 HOURS")}
159172

173+
@pytest.fixture(scope="class")
174+
def relations_to_reset(self):
175+
return ("mv_every_inputs",)
176+
160177
@pytest.fixture(scope="class")
161178
def project_config_update(self):
162179
return {"models": {"on_configuration_change": "apply"}}

tests/functional/adapter/simple_seed/test_seeds.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
SeedTestBase,
1818
)
1919

20-
from tests.functional.adapter.fixtures import MaterializationV2Mixin
20+
from tests.functional.adapter.fixtures import MaterializationV2Mixin, RerunSafeMixin
2121
from tests.functional.adapter.simple_seed import fixtures
2222

2323

@@ -185,7 +185,11 @@ class TestSeedColumnTypesV2(TestSeedColumnTypes, MaterializationV2Mixin):
185185
pass
186186

187187

188-
class TestSeedOntoView:
188+
class TestSeedOntoView(RerunSafeMixin):
189+
@pytest.fixture(scope="class")
190+
def relations_to_reset(self):
191+
return ("seed_over_view",)
192+
189193
@pytest.fixture(scope="class")
190194
def seeds(self):
191195
return {"seed_over_view.csv": fixtures.seeds__over_view_csv}

0 commit comments

Comments
 (0)