This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new e4b2e49ac13 Make the dag version diff recursion test independent of
Python version (#73664)
e4b2e49ac13 is described below
commit e4b2e49ac1336eed38385f04404368146ac1783e
Author: Jarek Potiuk <[email protected]>
AuthorDate: Thu Sep 24 15:14:58 2026 +0200
Make the dag version diff recursion test independent of Python version
(#73664)
The undecodable-payload test relied on JSON nested 20,000 levels deep
making json.loads raise RecursionError. Python 3.14 bounds C recursion
by stack size rather than by the recursion limit, and its json decoder
parses that payload, so the diff builder was called, the assertion
failed, and pytest-icdiff then spent over 60s pretty-printing the nested
list - the test timed out on every 3.14 backend. The RecursionError is
now raised by json.loads patched for one sentinel payload, for both the
compressed and uncompressed columns.
Generated-by: Claude Opus 5
---
airflow-core/tests/unit/models/test_dag_version.py | 48 +++++++++++++++++-----
1 file changed, 37 insertions(+), 11 deletions(-)
diff --git a/airflow-core/tests/unit/models/test_dag_version.py
b/airflow-core/tests/unit/models/test_dag_version.py
index b6e39472fc7..176378691d3 100644
--- a/airflow-core/tests/unit/models/test_dag_version.py
+++ b/airflow-core/tests/unit/models/test_dag_version.py
@@ -49,9 +49,6 @@ from tests_common.test_utils.db import clear_db_dag_bundles,
clear_db_dags, clea
pytestmark = pytest.mark.db_test
-# Valid JSON nested past the interpreter's recursion limit, so json.loads
raises RecursionError.
-_DEEPLY_NESTED_JSON = "[" * 20_000 + "]" * 20_000
-
async def _handle_deadline(context, **kwargs):
pass
@@ -479,14 +476,6 @@ class TestDagVersionGetDiff:
{"_data": None, "_data_compressed": b"not a zlib stream"},
id="compressed-undecompressable",
),
- pytest.param(
- {"_data": None, "_data_compressed":
zlib.compress(_DEEPLY_NESTED_JSON.encode())},
- id="compressed-deeply-nested",
- ),
- pytest.param(
- {"_data": _DEEPLY_NESTED_JSON, "_data_compressed": None},
- id="uncompressed-deeply-nested",
- ),
],
)
@mock.patch("airflow.serialization.dag_version_diff.build_serialized_dag_diff",
autospec=True)
@@ -508,6 +497,43 @@ class TestDagVersionGetDiff:
assert result["changes"] == []
assert result["values"] == {"status": "unavailable"}
+ @pytest.mark.parametrize("compressed", [True, False], ids=["compressed",
"uncompressed"])
+
@mock.patch("airflow.serialization.dag_version_diff.build_serialized_dag_diff",
autospec=True)
+ def test_marks_diff_unavailable_when_decoding_exceeds_recursion_limit(
+ self, mock_build_diff, compressed, dag_id, session
+ ):
+ # A payload nested deeply enough to exhaust the recursion limit is not
portable: Python 3.14
+ # bounds C recursion by stack size, and its json decoder handles
depths that raise on 3.13.
+ # Raise the RecursionError for this one payload instead; every other
json.loads call is real.
+ payload = '{"dag": "nested too deeply to decode"}'
+ real_loads = json.loads
+
+ def loads(s, *args, **kwargs):
+ if s in (payload, payload.encode()):
+ raise RecursionError("maximum recursion depth exceeded while
decoding a JSON array")
+ return real_loads(s, *args, **kwargs)
+
+ stored_columns = (
+ {"_data": None, "_data_compressed":
zlib.compress(payload.encode())}
+ if compressed
+ else {"_data": payload, "_data_compressed": None}
+ )
+ base = DagVersion.get_version(dag_id, 1,
session=session).serialized_dag
+ session.execute(
+ update(SerializedDagModel).where(SerializedDagModel.id ==
base.id).values(**stored_columns)
+ )
+ session.commit()
+ session.expunge_all()
+
+ with mock.patch("json.loads", side_effect=loads):
+ result = DagVersion.get_diff(dag_id, 1, 2,
values_status="available", session=session)
+
+ mock_build_diff.assert_not_called()
+ assert result["mode"] == "unavailable"
+ assert result["unavailable_reason"] == "serialized_dag_decode_failed"
+ assert result["changes"] == []
+ assert result["values"] == {"status": "unavailable"}
+
@pytest.mark.parametrize("missing_version", [1, 2])
def test_marks_diff_unavailable_when_serialized_dag_missing(self, dag_id,
session, missing_version):
version = DagVersion.get_version(dag_id, missing_version,
session=session)