test(muya): stop the deepClone spec from overflowing the stack itself by Jocs · Pull Request #5505 · marktext/marktext · GitHub
Skip to content

test(muya): stop the deepClone spec from overflowing the stack itself - #5505

Merged
Jocs merged 1 commit into
developfrom
fix/5504-deepclone-spec-flake
Sep 22, 2026
Merged

Jocs merged 1 commit into
developfrom
fix/5504-deepclone-spec-flake

Conversation

@Jocs

@Jocs Jocs commented Sep 22, 2026

Copy link
Copy Markdown
Member

Closes #5504

Summary

copies a 600-level nested list without exhausting the stack (#4747) fails intermittently on CI with RangeError: Maximum call stack size exceeded — it has now blocked two unrelated PRs (#5496, #5502) and passed on re-run with no code change.

The recursion is in the test, not in deepClone.

Measured ceilings for the fixture shape this spec builds

Operation Ceiling
deepClone(nested(d)) alone 100 000 levels, never threw
the test body, verbatim ok ≤ 900, RangeError @ 1000
expect(a).not.toBe(b) on nested(d) ok ≤ 900, RangeError @ 1000
structuredClone — the #4747 control ok ≤ 600, throws @ 700
JSON.stringify — control ok ≤ 1000, throws @ 2000

Root cause

expect(copy).not.toBe(state)
expect(copy[0]).not.toBe(state[0])

These hand the whole 600-level structure (~2400 nested containers) to Vitest, which recurses once per level while inspecting the argument. The test's margin was 600 → ~900, under 1.5×, and the ceiling moves with the Node build, the worker thread's stack size and how deep the caller's stack already is — so CI lands on the wrong side of it about half the time.

The deepClone rewrite from #5259 is fine: iterative, 100 000 levels with no trouble. The spec's own comment already warned that "a recursive equality check" would overflow this input; expect(deepObject) is one.

Fix

Compare identity outside the matcher so only booleans reach expect.

Verification — controlled, both directions

With the fixture temporarily raised to 10 000 levels:

Variant Result
with this fix 4 passed
only the two expect(deep) lines restored, same depth RangeError: Maximum call stack size exceeded — the CI failure, verbatim

That isolates the cause to those two lines. Committed state keeps the fixture at 600; muya unit suite 2003 passed (256 files), ESLint clean.

🤖 Generated with Claude Code

…#5504)

`copies a 600-level nested list without exhausting the stack` failed
intermittently on CI with RangeError — twice on unrelated PRs (#5496, #5502),
passing on re-run with no code change.

The recursion was in the test, not in `deepClone`. Measured ceilings for the
fixture shape this spec builds:

  deepClone(nested(d)) alone     100000 levels, never threw
  the test body, verbatim        ok <= 900, RangeError @ 1000
  expect(a).not.toBe(b)          ok <= 900, RangeError @ 1000
  structuredClone (the #4747     ok <= 600, throws @ 700
    control)

`expect(copy).not.toBe(state)` hands the whole 600-level structure (~2400
nested containers) to Vitest, which recurses once per level inspecting the
argument. That left a margin of 600 -> ~900, and the ceiling moves with the
Node build and the worker thread's stack size, so CI landed on the wrong side
of it about half the time.

Compare identity outside the matcher so only booleans reach `expect`. With
the fixture raised to 10000 levels the spec now passes; restoring just those
two lines at the same depth reproduces the CI RangeError exactly.

The spec's own comment already warned that "a recursive equality check" would
overflow this input — `expect(deepObject)` is one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Jocs
Jocs merged commit c754f2c into develop Sep 22, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

deepClone spec flakes on CI: expect() recurses over the 600-level fixture

1 participant