Make the deeply nested body test independent of stack size
Fixes #3146
test_modern_post_with_deeply_nested_body_is_parse_error_not_a_crash posted a well-formed 100,000-deep JSON array and asserted PARSE_ERROR. That only holds where json.loads hits its recursion guard. On CPython 3.14 with a large enough C stack the body parses, the server correctly answers INVALID_REQUEST for a body that isn't a request object, and the assertion fails. The server is right in both cases, so this only changes the test.
What changed
- The body is now left unterminated (
b"[" * 100_000), so it can't parse on any interpreter:RecursionErrorwhere the guard trips firstJSONDecodeErrorwhere the stack is deep enough for the scanner to reach the end of input
- The handler already maps both to 400 +
PARSE_ERROR, so the assertion holds either way. - The docstring says that. The test name and assertions are unchanged, and
tests/server/mcpserver/test_func_metadata.pyalready uses an unterminated"[" * 200_000for the same reason.
How it was checked
-
Reproduced the failure on Linux with CPython 3.14.6 by raising the stack limit (
ulimit -s 65536): the old test fails withassert -32600 == -32700, the same as the report. Withulimit -s 8192it passes. -
Watched what the handler's
json.loadscall does with each body on 3.14:stack limit old body (closed) new body (unterminated) 8 MiB RecursionError, -32700RecursionError, -3270016 MiB RecursionError, -32700RecursionError, -3270064 MiB parses, -32600 JSONDecodeError, -32700 -
The new test passes on 3.14 under both 8 MiB and 64 MiB limits.
-
./scripts/test(full suite, 100% coverage,strict-no-cover),ruff check,ruff format --checkandpyrighton the file are clean. -
Not run:
- macOS, where the issue was reported. The 64 MiB Linux run fails with the same assertion.
- The test itself on 3.10 to 3.13, which is left to CI. On 3.12 and 3.13
json.loadsraisesRecursionErrorfor both bodies at either stack limit.
Trade-off
Where the stack is large enough that the guard doesn't trip, the test goes through the JSONDecodeError arm of the except, not the RecursionError one. The RecursionError arm is still the one exercised in CI: the old well-formed body only passed there because the guard trips on every current cell.