This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch v3-2-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-2-test by this push:
     new 2aac928e6ba [v3-2-test] Add stairway test for DB migrations (#64905) 
(#66262)
2aac928e6ba is described below

commit 2aac928e6ba8ba4901b32bcdfb0094992e866562
Author: Jason(Zhe-You) Liu <[email protected]>
AuthorDate: Sun May 3 03:01:57 2026 +0800

    [v3-2-test] Add stairway test for DB migrations (#64905) (#66262)
    
    * add stairway tests for Airflow DB migrations to verify reversibility
    
    * refactor migration stairway test to use _REVISION_HEADS_MAP for baseline 
and improve DB state management
    
    * refactor stairway test to start from 3.0.0 revision and improve migration 
handling
    
    * refactor stairway test failure handling to raise AssertionError for 
better clarity
    
    * fix: add space in return statement for consistency in revision order
    
    ---------
    (cherry picked from commit 8a6a79f126f789e11889d1878f3fb93e45b68b53)
    
    Co-authored-by: iting0321 <[email protected]>
    Co-authored-by: iting0321 <[email protected]>
---
 .../tests/unit/migrations/test_migration_utils.py  | 118 +++++++++++++++++++++
 1 file changed, 118 insertions(+)

diff --git a/airflow-core/tests/unit/migrations/test_migration_utils.py 
b/airflow-core/tests/unit/migrations/test_migration_utils.py
new file mode 100644
index 00000000000..d31d4d930d2
--- /dev/null
+++ b/airflow-core/tests/unit/migrations/test_migration_utils.py
@@ -0,0 +1,118 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""
+Stairway test for Airflow DB migrations.
+
+Walks every migration step since the 3.0.0 release forward (upgrade) then
+backward (downgrade) to verify that all migrations are fully reversible.
+This catches problems like:
+- a migration that creates a constraint the downgrade forgets to drop
+- a downgrade that references a column that was never added by the upgrade
+
+The test runs as a single (non-parametrized) function so that execution order
+and DB state are guaranteed without relying on xdist grouping or cross-test
+state.  Failures include the specific revision ID so the broken migration is
+immediately identifiable in CI logs.
+"""
+
+from __future__ import annotations
+
+import pytest
+from alembic.script import ScriptDirectory
+
+from airflow.utils.db import (
+    _REVISION_HEADS_MAP,
+    _get_alembic_config,
+    downgrade,
+    upgradedb,
+)
+
+pytestmark = pytest.mark.db_test
+
+# Stairway starts from the 3.0.0 head revision.  Starting here (rather than
+# the 2.6.2 squashed baseline) avoids triggering FAB-provider downgrade
+# handling that airflow.utils.db.downgrade() applies when the target is below
+# 2.10.3, and keeps the number of steps manageable on slower backends.
+_STAIRWAY_START_REVISION = _REVISION_HEADS_MAP["3.0.0"]
+
+
+def _get_revisions_in_order() -> list[str]:
+    """Return revision IDs in upgrade order, starting after the stairway 
base."""
+    config = _get_alembic_config()
+    script = ScriptDirectory.from_config(config)
+
+    # walk_revisions() yields from head → base; reverse for upgrade order.
+    all_revisions = list(script.walk_revisions())
+    all_revisions.reverse()
+
+    revision_ids = [rev.revision for rev in all_revisions]
+
+    if _STAIRWAY_START_REVISION not in revision_ids:
+        raise ValueError(
+            f"Stairway base revision {_STAIRWAY_START_REVISION!r} not found in 
migration scripts. "
+            "Update _STAIRWAY_START_REVISION to a valid revision from 
_REVISION_HEADS_MAP."
+        )
+
+    base_index = revision_ids.index(_STAIRWAY_START_REVISION)
+    return revision_ids[base_index + 1 :]
+
+
[email protected](scope="module")
+def stairway_db():
+    """
+    Bring the DB to the stairway base revision before the test runs, and
+    restore it to the current heads afterwards so other tests are unaffected.
+
+    Uses Airflow's downgrade()/upgradedb() wrappers so that backend-specific
+    handling (MySQL metadata-lock, global migration lock, single-connection
+    pool) is applied consistently.
+    """
+    downgrade(to_revision=_STAIRWAY_START_REVISION)
+
+    yield
+
+    # Restore to latest heads even if the test failed mid-way.
+    upgradedb()
+
+
+def test_migration_stairway(stairway_db) -> None:
+    """
+    Walk every incremental migration step since 3.0.0: upgrade → downgrade → 
re-upgrade.
+
+    Uses Airflow's upgradedb()/downgrade() wrappers for every step so that
+    the global migration lock, MySQL metadata-lock handling, and
+    single-connection pool are active — matching real-world upgrade/downgrade
+    behaviour and preventing backend-specific hangs.
+
+    The relative ``-1`` specifier is used for downgrade so that merge revisions
+    are handled correctly: it always rolls back exactly one step from the
+    current head regardless of how many parents a merge revision has.
+    """
+    revisions = _get_revisions_in_order()
+
+    for revision_id in revisions:
+        try:
+            # Step 1: upgrade to this revision.
+            upgradedb(to_revision=revision_id)
+
+            # Step 2: downgrade exactly one step back.
+            downgrade(to_revision="-1")
+
+            # Step 3: re-apply so the next iteration starts from the right 
state.
+            upgradedb(to_revision=revision_id)
+        except Exception as e:
+            raise AssertionError(f"Stairway test failed at revision 
{revision_id!r}") from e

Reply via email to