Juho Naalisvaara
04/22/2026, 9:28 AMpaulk_asert
04/22/2026, 9:31 AMJuho Naalisvaara
04/22/2026, 9:42 AMJuho Naalisvaara
04/22/2026, 9:51 AMJuho Naalisvaara
04/22/2026, 9:55 AMJuho Naalisvaara
04/22/2026, 9:58 AMJuho Naalisvaara
04/22/2026, 10:39 AMJuho Naalisvaara
04/22/2026, 10:57 AMJuho Naalisvaara
04/22/2026, 12:13 PMpaulk_asert
04/22/2026, 12:55 PMcode_length in the classfile spec is a u4 but practically capped at 65535). It's not really a Groovy bug — ASM is faithfully reporting that the generated method can't fit.
Why this reproducer blows up so quickly (at ~589 assertions):
1. Everything inside verifyAll { … } becomes the body of a single closure's doCall method.
2. Spock's @Specification AST transform rewrites every assert inside that closure into a value-recording form (each subexpression is captured into a ValueRecorder so Spock can render the pretty "Condition not satisfied" table on failure). That rewrite typically produces ~80–150 bytes of bytecode per assert, far more than a plain assert.
3. Groovy itself also adds line-number, callsite, and power-assert scaffolding per statement.
So even though the source looks tiny, each assert obj.fieldN == 'valueN' expands to a substantial chunk, and 589 of them push doCall past 65535 bytes.
Fixes / workarounds (in order of preference):
- Split the verifyAll block — verifyAll is allowed multiple times in one expect: / then: block, and each one becomes its own closure/method:
groovy
expect:
verifyAll { /* first ~300 asserts */ }
verifyAll { /* next ~300 asserts */ }
- Use a data-driven feature — put the field/value pairs in a where: table and assert once; the loop replaces the unrolled asserts entirely.
- Split into multiple feature methods — each def "…"() is its own method, so the 64 KB budget resets.
- Drop into a plain `for`/`each` loop inside verifyAll — one assert in a loop instead of N textual asserts.
Can Groovy do anything about it? Not really, without changing semantics:
- Automatic method-splitting for closures is very risky because closures capture locals, this, delegate, owner, and return/continue semantics — splitting would need to preserve all of that. The compiler already splits some large `<clinit>`/`<init>` patterns, but closure bodies are harder.
- A nicer error message is fair game, though — catching MethodTooLargeException during class writing and re-reporting it with the offending source method/closure name and a hint ("consider splitting the block") would be a legitimate small UX improvement in `CompilationUnit`'s phase-7 writer. If you want, I can look at wiring that in.Juho Naalisvaara
04/22/2026, 12:56 PMJuho Naalisvaara
04/22/2026, 1:00 PMpaulk_asert
04/22/2026, 1:07 PMJuho Naalisvaara
04/22/2026, 1:12 PMpaulk_asert
04/22/2026, 1:16 PMpaulk_asert
04/22/2026, 1:17 PMpaulk_asert
04/22/2026, 1:17 PMpaulk_asert
04/22/2026, 1:18 PMJuho Naalisvaara
04/22/2026, 1:22 PMpaulk_asert
04/22/2026, 1:25 PMpaulk_asert
04/22/2026, 1:26 PMpaulk_asert
04/22/2026, 1:26 PMpaulk_asert
04/22/2026, 1:27 PMpaulk_asert
04/22/2026, 1:28 PMpaulk_asert
04/22/2026, 1:29 PMJuho Naalisvaara
04/22/2026, 1:31 PMpaulk_asert
04/22/2026, 1:38 PMpaulk_asert
04/23/2026, 10:53 AMJuho Naalisvaara
04/23/2026, 11:56 AM