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)

Reply via email to