Skip to content

test: 29 mutants killed in −2 net test lines — jsoniter oracle rewrite, circe bounds property, core exists/Index, and the equivalent-mutant catalogue - #111

Merged
kryptt merged 8 commits into
mainfrom
test/kill-survivors-jsoniter-circe-core
Sep 18, 2026
Merged

kryptt merged 8 commits into
mainfrom
test/kill-survivors-jsoniter-circe-core

Conversation

@kryptt

@kryptt kryptt commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Rebased onto main — #105 has landed (squashed as 75c7eaa), so this branch no longer carries its six commits and the diff is this PR's own work only: 9 files, jsoniter/circe/tests/ specs plus site/docs/quality-assurance.md and site/tools/gen-qa-report.py. No stack ordering left to respect.

Supersedes #108 (circe) and #110 (jsoniter): their commits are cherry-picked here unchanged, plus the core artifacts, a consolidation fold and the QA-page documentation. Close those two unmerged rather than merging them alongside this.

One improve-test-leverage run against the 2026-09-18 whole-repo sweep (201 survivors, avro handled separately). 29 mutants killed for −2 net test lines. The headline result is not a coverage gap closed — it is an oracle bug: the jsoniter prefix property already drove all 69 JsonPathScanner survivors, on every prefix, five paths, both surfaces, but asserted only Try(…).isSuccess, i.e. "did not throw" — and every one of those mutants turns a Miss into a wrong-but-non-throwing Span. Replacing the assertion, not adding tests, is what moved the number.

Per module

Module Survivors before after Killed Detected before → after Net test lines
jsoniter 77 58 19 313 → 332 +5
circe 12 6 6 38 → 44 −17
core (artifacts in tests/) 22 19 4 194 → 197 +10
total 111 83 29 545 → 573 −2

Repo-wide survivors 201 → 173. core's 22 − 4 = 18 but reads 19 because data/ObjArrBuilder.scala:17:8 flipped Timeout → Survived between runs — the documented timeout-oscillation class, in a builder path neither artifact touches. Recorded as incidental, not claimed, not treated as a regression.

Every kill is verified by diffing report.json against the baseline sweep on the full (file, line, column, mutator, replacement) key. 0 regressions, 0 mutant keys added or removed, in any module.

Artifacts, kills per line

Artifact Kills Body lines Lines/kill
jsoniter A1 — model generator + three-part oracle (absolute / bounds / differential) replacing the isSuccess prefix oracle, rewritten in place 7 ≈0 (replacement) —
jsoniter A2–A5 — malformed constants no generator can produce (non-container step, unquoted key, missing colon, skipObjectLoop twins) 11 18 1.6
jsoniter A6 — digit-less e/E added to the existing exponent alternatives 1 0 0
jsoniter — deleted the object-count and array-index sweeps (61 lines, 2 properties) 0 −61 —
circe — differential index-bounds oracle over Gen.chooseNum(-2, 5) × {get, modify, modifyUnsafe} 6 −10 net-negative
core — fold-surface agreement property (exists(p) === foci.exists(p), both surfaces) 2 +8 4.0
core — Index miss-write at the setter surface 2 +2 1.0
consolidation — JsonFailureSpec's IndexOutOfRange fire scenario folded into the circe property 0 −7 —

Objective terms

Metric Before After
detected mutants (the 3 modules) 545 573
suite_test_count (the 3 suites) 352 350
suite_test_loc (the 3 suites) 5731 5729
suite_kill_density 0.0951 0.1000

suite_test_count delta −2 (jsoniter 56 → 54; circe and tests/ unchanged — one example block out and one property in, and the Index kills are assertions appended to two existing examples). iteration_leverage: 29 kills against a negative denominator. The suite is smaller and kills more.

Three findings worth keeping

  1. The absolute oracle half is not optional. A purely differential prefix-vs-full oracle — the shape the plan proposed — compares two runs of the same mutated code, so any uniformly-wrong mutant passes. 245:8 and 245:14 (skipObject returning −1 for {} on prefix and full alike) would both have been missed.
  2. "Must be a Miss otherwise" is false. The scanner is deliberately permissive about truncated numbers, so find("{\"k0\":12", $.k0) returns the Hit 1. The strongest true form is full span fits inside the prefix ⇒ prefix result equals full result, with the weakened converse (sp.end <= sf.end) as a separate clause.
  3. Members beat extensions. Traversal declares its own exists and ForgetFold inherits one from CanFold, which is exactly why the pre-existing each.exists / listFold.exists assertions never touched Optic.scala:443. The property has to ascribe its receiver to the bare Optic[…] to reach the extension, and use a multi-focus carrier (Tuple2's foldMap is f(a); it never evaluates combine).

Two plan premises were refuted by the source and the artifacts built around the corrections: ContainerOpticsSpec did already write through a missed Index (the mutants survive because Optional#modify short-circuits on getOrModify's Left and never calls the stored reverseGet — so the assertion has to land on the raw Optional#reverseGet field), and CapsMatrixSpec's line claiming to cover CanFold.scala:27 covered nothing (its carrier is Affine and the subject hits, so the monoid identity is never consulted) — deleted.

Consolidation

Measured, never assumed: after every deletion the affected module's stryker run was repeated and diffed by mutant key.

  • jsoniter: the member-count × target-position sweep and the length × index sweep (61 lines, 2 properties) deleted — the absolute oracle clause subsumes both exactly. Re-run: 328 Killed / 58 Survived, byte-identical to the run before the deletion.
  • circe: the replaced example plus unsafeOOR / defaultOOR / negIndex and their covers: lines; defaultOOR was already redundant with JsonFailureSpec. Then JsonFailureSpec's IndexOutOfRange fire scenario — at(5) on a one-element array is one point of what the new property asserts over the whole index range. Re-run: 44 Killed / 6 Survived, byte-identical, 0 keys moved.
  • core: the ForgetfulFold extensions example block (14 hard-coded assertions, no oracle) replaced by the property; the false CapsMatrixSpec coverage line deleted.

Net suite_test_count −2 and net LOC −2. core's own row is +10 lines: the property needs two generic agreement helpers plus five carrier-ascribed vals where the old block used 14 fixed assertions — assertion strength rose much further than LOC (14 fixed assertions → 24 oracle-checked assertions per generated row over 15 input shapes), and the two Index assertions are a deliberate add, since the examples they sit beside pin Optional#modify's miss semantics and must not be removed.

QA page: the equivalent mutants are now documented

site/docs/quality-assurance.md gains a Known equivalent mutants section — 40 survivors keyed (file, line:column, mutator → replacement), each with the reason read off the source, not inherited from the sweep: core 19 (its entire survivor set), circe 6 (likewise), schemes 8 (likewise), jsoniter 7 of 58. They are perf fast paths whose general path computes the same value (PSVec.unsafeWrap normalising a 0-length array back to Empty; freeze trimming either way; transformMachine agreeing with the on-stack walk), and loop-boundary operators on cursors that advance by exactly one. Every future run of this skill re-triaged them; now it doesn't have to.

Deliberately not certified: the 51 remaining JsonPathScanner survivors and JsoniterPrism:130:8. One class was measured wrong this run — 254:13 >= → > was called equivalent and is in fact killable (with > the left disjunct is dead-false and the right one then indexes at kpos == bytes.length). The page says so, and states the rule: a guard whose right-hand side indexes the array needs the reachability of pos == length checked individually before it may be called equivalent.

Two stale caveats retired, both disproven by measurement today

  • avro — "Not scored: stryker's forked test-runner fails to initialise in the sandbox". False. It scored in ~126 s (391 K / 78 S / 17 NC).
  • jsoniter — "instrumenting PathParser.parseField overflows the JVM 64 KB method limit". False. 420 mutants, 0 CompileError; PathParser.scala itself produced 24 mutants, 20 killed.

Root cause of the staleness: site/tools/gen-qa-report.py hard-codes the Notes column per module, so each caveat printed over real numbers for months (numbers have been landing since ffe5116). Fixed at the source, with a comment saying a claim of unscoreability must be retired once numbers land.

Recorded as structural and permanent instead, so nobody retries it: generics 0 % (86 NoCoverage), avro's 17 AvroPrismMacro and kyo's 28 RecordIsoMacro NoCoverage mutants — all quoted-macro bodies that expand inside the compiler and have no runtime footprint. Plus the two modules that never had a row: zio (0 mutants exist — no pool, score n/a, not 0 %) and kyo (83.3 % covered, but unscoreable until the single-method extension block of #109 is braced).

Law candidate (gated, not promoted)

exists(p) === foci.exists(p), with length / headOption twins, as a FoldLaws / CanFoldTests law — it would fire at every checkAll site repo-wide and in downstream clients. Law promotion is user-gated; flagged, not done.

Gates

sbt "scalafixAll; scalafmtAll" then sbt "scalafmtCheckAll; scalafixAll --check" clean; sbt "++ 3" test on the root aggregate green under Temurin 25 (kyoIntegration/name → cats-eo-kyo, so kyo was in); docs/mdoc + docs/laikaSite build (only the pre-existing slug warnings). avro was not touched.

🤖 Generated with Claude Code

https://claude.ai/code/session_0194EHFR4NamCpTHiqy7B74V

kryptt and others added 8 commits September 18, 2026 15:33
…x property — kills the end-of-input guard cluster

The prefix property drove every skip*/find* guard already; its oracle was
`Try(...).isSuccess`, i.e. "did not throw". Every mutant in the cluster turns
a Miss into a wrong-but-non-throwing Span, so the oracle was blind to all of
them.

Replaced in place with a model-generator + three-part oracle:
  (a) ABSOLUTE — the generator returns the rendered children alongside the
      document, so each top-level step`s expected span text is known without
      a second parse. A purely differential oracle compares two runs of the
      SAME mutated code and passes any uniformly-wrong scanner.
  (b) BOUNDS — 0 <= start <= end <= len on both `find` and `findAll`.
  (c) DIFFERENTIAL — R1/R2/R3 between prefix and full.

Separators became ", " and child counts became frequency-weighted so empty
containers occur in value position and a post-comma-space cut is reachable.
Exception = failure now, so the no-throw guarantee is kept for free.

Subsumed and deleted: section 8`s rootHit/negHit/untermOk clauses, and
literalCases rows 1-6 (valid + truncated literals). Widened the existing
numberGen exponent alternatives with digit-less "e"/"E".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0194EHFR4NamCpTHiqy7B74V
…can reach — kills 11 mutants in 9 data rows

Each row is a byte layout whose broken syntax is followed by bytes that
accidentally spell the probed member/element, so a dropped guard mis-parses
onto the target instead of being rejected by a later guard:

  1"target":2         a step applied to a non-container value (78/85/108/113/118)
  {"a":}              a value position holding no value at all (240:41)
  {Xk0":1}            key-quote guard; skipString finds the CLOSING quote (168)
  {"k0"1 2}           colon guard (173:8)
  ...nested twins     the same two inside a SKIPPED value (254:8, 258:8)

The existing malformed block`s oracle widened from `!find(...).isHit` to also
require `findAll(...) == Nil`, which is what makes the walkAll guards
observable; the widening covers the six pre-existing rows at no extra lines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0194EHFR4NamCpTHiqy7B74V
…umed, 0 kills lost

The new absolute oracle asserts each top-level child`s span text by index on
every generated document, which is exactly what these two enumerated sweeps
were: a target-position sweep plus absent-key / index-past-end / negative-index
Miss rows plus a skip-over-a-sibling assertion.

Measured, not assumed: stryker before the deletion 328K/58S, after 328K/58S —
zero regressions by mutant key (file, line, column, mutator, replacement).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0194EHFR4NamCpTHiqy7B74V
…0 lines

`JsonWalk` carries two copies of `idx < 0 || idx >= arr.length`: one in
`readPath` (:56), one in `modifyPath` (:81). `JsonIndexBoundsSpec` was written
for :56 but drove only `.modify(...)`, so all six operator mutants on the read
copy survived while the write copy was 11/11 killed.

Replace the hard-coded example with one `forAll(Gen.chooseNum(-2, 5))` property
over a fixed size-3 array whose expectations are DERIVED from the backing
`Vector`, driving three surfaces per index: `get` (read → :56), `modify` (Ior
write → :81) and `modifyUnsafe` (silent pass-through). The range straddles all
five discriminating classes — i<0, i=0, 0<i<3, i=3, i>3 — which is what the
two `>=` variants need (i=3 alone kills `>=`→`>`, i>=4 alone kills `>=`→`==`).

Deletions: the subsumed `unsafeOOR` / `defaultOOR` / `negIndex` assertions in
JsonPrismSpec. `defaultOOR` was already redundant with JsonFailureSpec:66-71;
the other two are subsumed by the property's `silent` conjunct, which asserts
pass-through across every out-of-range class rather than two constants.

Measured: circe 38K/12S → 44K/6S (76.00% → 88.00%), net -10 test lines, suite
test count unchanged. The remaining 6 survivors are provably equivalent mutants
(`i >= n` → `i == n` on two `+ 1`-incremented loop heads, and four empty-path
shortcuts whose general branch computes the same value), so circe is now at its
killable ceiling.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0194EHFR4NamCpTHiqy7B74V
…in 2 lines

`Index`'s scaladoc promises a missed write is a silent pass-through: the Seq
setter must not throw and the Map setter must not insert. Both guards survived
mutation despite ContainerOpticsSpec already writing through a missed Index —
`Optional#modify` returns `getOrModify`'s `Left(t)` directly and never calls
the stored `reverseGet`, where the guard lives. No optic-surface operation
reaches it (`from` only calls it on `Affine.Hit`; `fuseToOptional` only on
`Right`; `OptionalLaws` holds the erased `Optic[S, S, A, A, Affine]`), so the
assertion is made against the public `reverseGet` field.

kills: optics/Index.scala:37:27, optics/Index.scala:55:27

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0194EHFR4NamCpTHiqy7B74V
…ple out / 1 property in

Replaces OpticsBehaviorSpec's 14 hand-written "ForgetfulFold extensions"
assertions with one property over an independent oracle: for each carrier row,
foldMap / headOption / length / exists must agree with an expected focus list.

Two receiver shapes are load-bearing. `extAgrees` ascribes the optic to the
bare `Optic[…]`, the only way to reach the Optic.scala EXTENSION — `Traversal`
and `ForgetFold` (via `CanFold`) both declare member twins that win by
precedence, which is why the old `each.exists(…)` / `listFold.exists(…)` lines
never touched it. `cfAgrees` ascribes to `CanFold`, reaching the trait body.
The generator straddles both monoid classes on purpose: k in {-1, 4} with
n >= 1 gives "some focus satisfies" (needed for `||` -> `&&`), k = 10 and n = 0
give "none satisfies / empty fold" (needed for the `false` seed).

Also drops CapsMatrixSpec's `(zipOptional: CanFold[…]).exists(_ > 999999)`
line and its comment: the claim was false. `zipOptional`'s carrier is `Affine`
and `addr` HITS, so `foldMap` returns `f(a)` and the monoid identity is never
consulted — measured, that assertion killed nothing.

kills: CanFold.scala:27:41, optics/Optic.scala:443:69

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0194EHFR4NamCpTHiqy7B74V
…roperty

`JsonFailureSpec` asserted that `at(5)` on a one-element array yields
`Ior.Both(IndexOutOfRange(Index(5), 1), …)`. That is one point of what the
rewritten `JsonIndexBoundsSpec` property now asserts across the whole index
range (-2..5 against a size-3 array), against an expectation derived from the
backing `Vector` rather than hard-coded — so the example adds no
discrimination the property does not already have. The message projection
(`size=3`), which the property does not look at, stays.

Measured: `project circeIntegration; stryker` re-run after the deletion gives
44 Killed / 6 Survived / 36 Ignored, byte-identical to the run before it on the
full (file, line, column, mutator, replacement) key — 0 mutants flipped, 0 keys
added or removed. -7 body lines, suite_test_count unchanged (the block is a
composite that keeps its four other cases).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0194EHFR4NamCpTHiqy7B74V
The mutation sweep keeps getting re-triaged from scratch because the page
records only scores, never which survivors are unkillable. Forty of them are:
perf fast paths whose general path computes the same value, and loop-boundary
operators on cursors that advance by one. They are now tabulated per module,
keyed (file, line:column, mutator -> replacement), each with the reason read
off the source — core 19 (its entire survivor set), circe 6 (likewise),
schemes 8 (likewise), jsoniter 7 of 58.

Deliberately NOT certified: the 51 remaining JsonPathScanner survivors and
JsoniterPrism:130:8. One class of them was measured wrong today — 254:13
`>=` -> `>` was called equivalent and is killable, because the mutant makes the
left disjunct dead-false and the right one then indexes at
`kpos == bytes.length`. Any guard whose right-hand side indexes the array needs
`pos == length` reachability checked individually first; the page says so
instead of pretending.

Two caveats on this page were false and are retired, both re-tested today:
avro ("stryker's forked test-runner fails to initialise") scores in ~2 min, and
jsoniter ("instrumenting PathParser.parseField overflows the 64 KB method
limit") mutates clean with 0 compile errors. Root cause of the staleness:
gen-qa-report.py hard-codes the Notes column per module, so the caveat printed
over real numbers for months. The notes are corrected at the source and the
comment above them now says a claim of unscoreability must be retired when
numbers land.

Recorded as structural and permanent instead: generics' 86 NoCoverage mutants,
avro's 17 AvroPrismMacro ones and kyo's 28 RecordIsoMacro ones are all
quoted-macro bodies that expand in the compiler and have no runtime footprint.
Also adds the never-before-reported zio (0 mutants exist — no pool, score n/a)
and kyo (83.3% covered, needs the braced extension of #109 to run at all)
readings, and a note that the generated table lags between release tags, with
the current sweep's numbers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0194EHFR4NamCpTHiqy7B74V
@kryptt
kryptt force-pushed the test/kill-survivors-jsoniter-circe-core branch from a3501ac to f8bc796 Compare September 18, 2026 13:40
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Cloudflare Pages preview for test/kill-survivors-jsoniter-circe-core is live:

https://46b33363.cats-eo-docs.pages.dev

Branch alias: https://test-kill-survivors-jsoniter.cats-eo-docs.pages.dev

Built from commit f8bc7964a8a2fef5fe332a96317e54eb9eec184d · updated on every push.

@github-actions

Copy link
Copy Markdown
Contributor

Benchmark A/B

Allocation (B/op) — authoritative

Benchmark params base head Δ
OrderJsoniterBench.eoReadStreet size=64 72.0 128.0 +77.8%
PlatedBench.visitorUniverseJson n=4096 327,806.2 533,757.4 +62.8%
JsoniterBench.jReadId size=64 48.0 72.0 +50.0%
OrderJsoniterBench.eoReadStreet size=512 128.2 72.2 -43.7%
PlatedBench.visitorUniverseJson n=64 4,088.0 5,096.0 +24.7%
PlatedBench.mUniverseJson n=4096 7,669,562.0 6,489,851.7 -15.4%
PlatedBench.visitorUniverseJson n=512 45,034.2 40,954.3 -9.1%
OrderAvroBench.eoModifyNames size=8 1,992.0 2,160.0 +8.4%
JsoniterBench.jMiss size=64 0.0 0.0 +5.3%
JsoniterBench.jMiss size=512 0.1 0.1 -5.1%
JsoniterBench.jMiss size=8 0.0 0.0 +4.6%
OpticBuildBench.reuseUse - 2,648.0 2,720.0 +2.7%
JsoniterBench.jModifyId size=8 992.0 968.0 -2.4%
OrderCirceBench.eoNamesIor size=64 89,547.4 87,530.8 -2.3%
OrderCirceBench.eoNames size=512 601,730.7 614,034.7 +2.0%
OrderCirceBench.hcursorNames size=64 77,810.9 76,255.2 -2.0%
AvroJsonBridgeBench.eoClickToJson - 4,000.0 3,952.0 -1.2%
OrderAvroBench.monocleModifyNames size=8 5,400.0 5,448.0 +0.9%
OrderCirceBench.directStreet size=8 2,728.0 2,752.0 +0.9%
OrderCirceBench.directStreet size=64 2,728.1 2,752.1 +0.9%
159 more benchmarks
Benchmark params base head Δ
OrderCirceBench.directStreet size=512 2,752.8 2,728.8 -0.9%
JsoniterBench.cReplaceId size=8 20,644.4 20,724.3 +0.4%
AvroJsonBridgeBench.eoClickToAvro - 9,400.0 9,368.0 -0.3%
OrderAvroBench.eoModifyNames size=64 12,624.2 12,584.2 -0.3%
OrderJsoniterBench.nativeReadStreet size=512 456.9 458.2 +0.3%
JsoniterBench.cModifyId size=8 20,716.4 20,692.4 -0.1%
OrderCirceBench.naiveNames size=8 17,808.1 17,824.1 +0.1%
OrderAvroBench.naiveModifyNames size=64 27,936.3 27,912.3 -0.1%
OrderCirceBench.monocleNames size=8 19,456.1 19,472.1 +0.1%
JsoniterBench.cSumPrices size=8 22,703.5 22,720.1 +0.1%
OrderJsoniterBench.nativeSumPrices size=8 1,513.2 1,514.3 +0.1%
OrderAvroBench.monocleModifyNames size=64 39,868.1 39,842.9 -0.1%
OrderCirceBench.eoStreetIor size=512 2,736.8 2,738.1 +0.0%
OrderCirceBench.eoStreet size=512 2,720.8 2,722.1 +0.0%
OrderJsoniterBench.monocleSumPrices size=64 47,369.7 47,390.6 +0.0%
OrderCirceBench.eoStreet size=8 2,720.0 2,720.8 +0.0%
OrderCirceBench.naiveNames size=64 120,886.1 120,869.1 -0.0%
OrderCirceBench.monocleNames size=64 132,762.5 132,778.7 +0.0%
OrderCirceBench.naiveStreet size=64 113,807.7 113,795.5 -0.0%
OrderJsoniterBench.monocleModifyStreet size=512 333,598.9 333,575.2 -0.0%
OrderJsoniterBench.nativeSumPrices size=64 10,939.4 10,938.7 -0.0%
OrderAvroBench.eoModifyNames size=512 97,403.8 97,408.8 +0.0%
OrderCirceBench.directNames size=512 614,012.0 613,993.9 -0.0%
JsoniterBench.jReadId size=512 48.0 48.0 -0.0%
OrderJsoniterBench.naiveModifyStreet size=64 30,057.4 30,058.1 +0.0%
OrderCirceBench.eoStreet size=64 2,720.9 2,721.0 +0.0%
OrderCirceBench.eoStreetIor size=8 2,736.9 2,736.9 -0.0%
OrderCirceBench.eoStreetIor size=64 2,736.9 2,736.9 +0.0%
JsoniterBench.cReplaceId size=512 1,066,763.7 1,066,743.0 -0.0%
OrderCirceBench.directNames size=64 77,730.5 77,729.0 -0.0%
OrderCirceBench.hcursorNames size=512 613,988.2 613,999.4 +0.0%
OrderAvroBench.monocleModifyStreet size=512 169,064.0 169,061.3 -0.0%
JsoniterBench.cModifyId size=64 136,266.0 136,264.2 -0.0%
OrderJsoniterBench.nativeSumPrices size=512 86,320.5 86,321.6 +0.0%
PlatedBench.eoUniverseDeep n=4096 786,586.7 786,593.1 +0.0%
PlatedBench.mUniverseExpr n=4096 4,687,928.2 4,687,891.0 -0.0%
OrderJsoniterBench.monocleModifyStreet size=64 30,082.1 30,082.0 -0.0%
JsoniterBench.cReadStreet size=64 101,310.9 101,310.4 -0.0%
OrderJsoniterBench.naiveReadStreet size=512 193,235.1 193,236.0 +0.0%
JsoniterBench.jReadStreet size=512 128.1 128.1 -0.0%
JsoniterBench.cSumPrices size=64 156,251.1 156,250.4 -0.0%
PlatedBench.visitorUniverseDeep n=4096 196,709.1 196,709.9 +0.0%
OrderCirceBench.naiveStreet size=512 908,035.7 908,032.1 -0.0%
OrderCirceBench.naiveNames size=512 965,281.6 965,277.8 -0.0%
JsoniterBench.cModifyId size=512 1,066,809.8 1,066,813.9 +0.0%
PlatedBench.eoUniverseJson n=4096 786,491.5 786,494.4 +0.0%
JsoniterBench.cReadStreet size=512 797,947.7 797,944.8 -0.0%
OrderJsoniterBench.nativeReadStreet size=64 424.5 424.5 -0.0%
OrderJsoniterBench.naiveReadStreet size=64 24,705.3 24,705.3 -0.0%
OrderJsoniterBench.eoSumPrices size=64 8,121.3 8,121.3 -0.0%
OrderJsoniterBench.eoModifyStreet size=512 42,027.2 42,027.1 -0.0%
OrderCirceBench.monocleStreet size=512 908,029.5 908,027.3 -0.0%
OrderJsoniterBench.monocleReadStreet size=512 193,234.7 193,235.2 +0.0%
JsoniterBench.jSumPrices size=512 63,633.4 63,633.3 -0.0%
PlatedBench.visitorTransformDeep n=4096 163,890.6 163,890.2 -0.0%
OrderJsoniterBench.naiveSumPrices size=512 230,128.2 230,127.8 -0.0%
OrderAvroBench.eoModifyStreet size=512 328.0 328.0 -0.0%
OrderAvroBench.naiveReadStreet size=512 69,803.6 69,803.5 -0.0%
JsoniterBench.cReplaceId size=64 136,216.3 136,216.5 +0.0%
OrderAvroBench.naiveModifyStreet size=512 169,056.6 169,056.4 -0.0%
PlatedBench.eoUniverseExpr n=4096 786,390.5 786,389.4 -0.0%
OrderCirceBench.hcursorStreet size=64 3,032.1 3,032.1 -0.0%
OrderJsoniterBench.eoSumPrices size=512 63,691.4 63,691.3 -0.0%
PlatedBench.mUniverseExpr n=64 45,424.3 45,424.3 +0.0%
OrderJsoniterBench.monocleSumPrices size=512 1,190,795.6 1,190,794.5 -0.0%
OrderCirceBench.monocleStreet size=64 113,798.8 113,798.7 -0.0%
OrderAvroBench.monocleModifyStreet size=64 20,904.3 20,904.3 +0.0%
PlatedBench.eoTransformExpr n=4096 655,361.6 655,361.1 -0.0%
OrderAvroBench.monocleReadStreet size=512 69,803.6 69,803.5 -0.0%
OrderCirceBench.eoNamesIor size=8 11,592.1 11,592.1 -0.0%
OrderCirceBench.eoNames size=64 79,299.7 79,299.7 +0.0%
OrderJsoniterBench.naiveModifyStreet size=512 333,575.3 333,575.1 -0.0%
PlatedBench.mTransformExpr n=4096 753,745.6 753,746.0 +0.0%
PlatedBench.visitorUniverseExpr n=4096 196,657.2 196,657.3 +0.0%
JsoniterBench.jSumPrices size=64 8,120.3 8,120.3 -0.0%
OrderAvroBench.monocleModifyNames size=512 382,760.5 382,760.7 +0.0%
OrderJsoniterBench.eoReadStreet size=8 128.0 128.0 +0.0%
OrderJsoniterBench.monocleModifyStreet size=8 4,664.1 4,664.1 +0.0%
OrderCirceBench.hcursorStreet size=8 3,032.0 3,032.0 -0.0%
OrderJsoniterBench.monocleReadStreet size=64 24,705.3 24,705.3 +0.0%
PlatedBench.eoTransformDeep n=4096 624,389.8 624,389.6 -0.0%
OrderJsoniterBench.naiveSumPrices size=64 29,337.3 29,337.3 -0.0%
JsoniterBench.cReadId size=64 101,310.4 101,310.5 +0.0%
OrderJsoniterBench.nativeReadStreet size=8 424.0 424.0 -0.0%
OrderAvroBench.monocleModifyStreet size=8 2,992.0 2,992.0 +0.0%
JsoniterBench.cSumPrices size=512 1,240,819.0 1,240,819.4 +0.0%
PlatedBench.eoTransformDeep n=64 7,184.0 7,184.0 +0.0%
OrderJsoniterBench.eoModifyStreet size=64 5,432.1 5,432.1 +0.0%
AvroJsonBridgeBench.eoWideToJson - 1,472.0 1,472.0 +0.0%
AvroJsonBridgeBench.naiveClickToJson - 4,696.0 4,696.0 +0.0%
JsoniterBench.jReadStreet size=64 128.0 128.0 +0.0%
JsoniterBench.jModifyId size=512 41,920.9 41,920.8 -0.0%
JsoniterBench.cReadId size=512 797,940.9 797,941.1 +0.0%
JsoniterBench.jReadStreet size=8 128.0 128.0 -0.0%
OrderCirceBench.eoNamesIor size=512 683,541.2 683,541.0 -0.0%
OrderJsoniterBench.naiveModifyStreet size=8 4,640.1 4,640.1 +0.0%
OrderAvroBench.eoReadStreet size=512 88.0 88.0 +0.0%
OrderJsoniterBench.naiveReadStreet size=8 3,648.0 3,648.0 -0.0%
PlatedBench.eoUniverseJson n=512 98,187.0 98,186.9 -0.0%
PlatedBench.visitorUniverseDeep n=64 3,128.0 3,128.0 -0.0%
OrderCirceBench.directNames size=8 10,688.1 10,688.1 -0.0%
OrderAvroBench.naiveModifyNames size=512 226,282.1 226,282.2 +0.0%
JsoniterBench.jSumPrices size=8 1,176.0 1,176.0 +0.0%
OrderAvroBench.naiveModifyStreet size=8 2,968.0 2,968.0 +0.0%
PlatedBench.mUniverseJson n=64 73,208.4 73,208.4 -0.0%
OrderAvroBench.naiveModifyStreet size=64 20,880.3 20,880.3 +0.0%
OpticBuildBench.buildAndUse - 2,816.0 2,816.0 -0.0%
PlatedBench.mUniverseExpr n=512 475,011.5 475,011.6 +0.0%
OrderCirceBench.hcursorNames size=8 10,768.1 10,768.1 +0.0%
OrderCirceBench.eoNames size=8 10,944.1 10,944.1 +0.0%
PlatedBench.eoTransformDeep n=512 57,361.4 57,361.4 +0.0%
PlatedBench.visitorUniverseDeep n=512 24,632.8 24,632.8 -0.0%
PlatedBench.mUniverseJson n=512 699,916.7 699,916.8 +0.0%
OrderJsoniterBench.eoSumPrices size=8 1,176.0 1,176.0 +0.0%
AvroJsonBridgeBench.eoWideToAvro - 6,744.0 6,744.0 -0.0%
JsoniterBench.jReplaceId size=512 41,888.9 41,888.8 -0.0%
OrderAvroBench.eoModifyStreet size=64 328.0 328.0 -0.0%
PlatedBench.eoUniverseExpr n=64 12,168.0 12,168.0 -0.0%
JsoniterBench.cReadId size=8 15,568.0 15,568.0 +0.0%
PlatedBench.visitorTransformExpr n=4096 360,475.0 360,474.9 -0.0%
AvroJsonBridgeBench.naiveWideToJson - 4,376.0 4,376.0 +0.0%
PlatedBench.visitorUniverseExpr n=512 24,584.7 24,584.7 -0.0%
AvroJsonBridgeBench.naiveClickToAvro - 3,928.0 3,928.0 +0.0%
OrderAvroBench.eoReadStreet size=8 88.0 88.0 -0.0%
PlatedBench.mTransformExpr n=512 69,585.7 69,585.7 -0.0%
PlatedBench.eoUniverseExpr n=512 98,185.6 98,185.6 -0.0%
AvroJsonBridgeBench.naiveWideToAvro - 3,504.0 3,504.0 +0.0%
OrderCirceBench.monocleNames size=512 1,121,813.9 1,121,813.9 +0.0%
OrderJsoniterBench.naiveSumPrices size=8 4,248.0 4,248.0 -0.0%
PlatedBench.visitorTransformDeep n=512 20,496.5 20,496.5 +0.0%
OrderAvroBench.monocleReadStreet size=8 1,208.0 1,208.0 -0.0%
OrderCirceBench.monocleStreet size=8 17,048.1 17,048.1 +0.0%
PlatedBench.eoUniverseDeep n=64 12,360.0 12,360.0 -0.0%
PlatedBench.mTransformExpr n=64 11,728.1 11,728.1 +0.0%
OrderAvroBench.naiveReadStreet size=64 8,848.2 8,848.2 +0.0%
PlatedBench.eoUniverseJson n=64 12,168.1 12,168.1 +0.0%
PlatedBench.eoTransformExpr n=512 81,825.7 81,825.7 -0.0%
PlatedBench.eoUniverseDeep n=512 98,377.7 98,377.7 +0.0%
OpticBuildBench.build - 184.0 184.0 +0.0%
OrderJsoniterBench.monocleReadStreet size=8 3,648.0 3,648.0 +0.0%
OrderAvroBench.monocleReadStreet size=64 8,848.2 8,848.2 -0.0%
OrderAvroBench.eoReadStreet size=64 88.0 88.0 +0.0%
JsoniterBench.jModifyId size=64 5,328.0 5,328.0 +0.0%
JsoniterBench.cReadStreet size=8 15,568.0 15,568.0 -0.0%
PlatedBench.visitorUniverseExpr n=64 3,080.0 3,080.0 +0.0%
OrderCirceBench.naiveStreet size=8 17,040.1 17,040.1 +0.0%
OrderAvroBench.naiveReadStreet size=8 1,208.0 1,208.0 -0.0%
OrderJsoniterBench.eoModifyStreet size=8 1,072.0 1,072.0 +0.0%
OrderAvroBench.naiveModifyNames size=8 3,752.0 3,752.0 +0.0%
PlatedBench.visitorTransformExpr n=64 5,608.0 5,608.0 +0.0%
JsoniterBench.jReplaceId size=8 960.0 960.0 -0.0%
OrderCirceBench.hcursorStreet size=512 3,032.8 3,032.8 -0.0%
JsoniterBench.jReadId size=8 48.0 48.0 +0.0%
PlatedBench.eoTransformExpr n=64 10,144.0 10,144.0 -0.0%
PlatedBench.visitorTransformDeep n=64 2,576.0 2,576.0 -0.0%
OrderAvroBench.eoModifyStreet size=8 328.0 328.0 -0.0%
OrderJsoniterBench.monocleSumPrices size=8 6,640.1 6,640.1 -0.0%
PlatedBench.visitorTransformExpr n=512 45,032.9 45,032.9 +0.0%
JsoniterBench.jReplaceId size=64 5,296.0 5,296.0 +0.0%
Timing (ns/op) — directional only, same-VM but shared runner
Benchmark params base head Δ
PlatedBench.visitorUniverseJson n=4096 140,425.8 183,329.7 +30.6%
PlatedBench.mUniverseExpr n=64 14,751.8 16,902.4 +14.6%
JsoniterBench.jReadId size=64 36.7 41.0 +11.9%
AvroJsonBridgeBench.eoWideToAvro - 999.2 883.4 -11.6%
OrderAvroBench.naiveModifyNames size=64 9,371.2 8,309.3 -11.3%
JsoniterBench.jReadId size=512 41.3 36.6 -11.2%
OrderAvroBench.monocleModifyNames size=64 11,052.7 9,993.1 -9.6%
OrderAvroBench.monocleModifyStreet size=8 965.1 1,055.4 +9.4%
AvroJsonBridgeBench.eoWideToJson - 586.0 640.7 +9.3%
OrderCirceBench.directNames size=512 264,628.2 241,999.9 -8.6%
PlatedBench.mUniverseJson n=4096 3,468,725.9 3,173,715.7 -8.5%
PlatedBench.eoUniverseDeep n=4096 113,673.9 122,453.4 +7.7%
AvroJsonBridgeBench.naiveClickToJson - 2,662.9 2,836.7 +6.5%
OrderCirceBench.directNames size=64 30,788.8 28,783.7 -6.5%
PlatedBench.visitorUniverseJson n=64 2,161.6 2,022.0 -6.5%
OrderAvroBench.monocleModifyStreet size=64 7,579.8 8,059.8 +6.3%
OrderCirceBench.hcursorNames size=512 234,817.4 248,706.5 +5.9%
OrderJsoniterBench.naiveReadStreet size=64 13,055.7 12,298.2 -5.8%
OrderAvroBench.monocleModifyNames size=8 1,460.8 1,543.2 +5.6%
OpticBuildBench.buildAndUse - 1,086.5 1,026.3 -5.5%
OrderAvroBench.naiveModifyStreet size=8 963.9 1,015.5 +5.3%
JsoniterBench.jMiss size=512 193.1 183.2 -5.1%
JsoniterBench.jMiss size=64 183.1 192.5 +5.1%
JsoniterBench.jMiss size=8 183.6 192.5 +4.8%
PlatedBench.visitorUniverseJson n=512 20,878.9 21,884.2 +4.8%
JsoniterBench.cReplaceId size=512 466,782.0 444,883.0 -4.7%
OrderCirceBench.naiveStreet size=64 22,775.3 21,713.1 -4.7%
OrderCirceBench.eoNamesIor size=8 4,271.9 4,089.1 -4.3%
PlatedBench.visitorUniverseDeep n=64 863.2 828.5 -4.0%
OrderAvroBench.monocleModifyStreet size=512 58,907.6 61,230.4 +3.9%
OrderCirceBench.eoNames size=512 252,830.6 243,059.0 -3.9%
OrderCirceBench.eoStreet size=64 1,031.3 992.2 -3.8%
OrderCirceBench.hcursorStreet size=64 1,093.1 1,053.5 -3.6%
AvroJsonBridgeBench.eoClickToJson - 2,877.1 2,773.8 -3.6%
AvroJsonBridgeBench.eoClickToAvro - 3,314.9 3,199.8 -3.5%
JsoniterBench.cSumPrices size=8 6,682.8 6,472.9 -3.1%
JsoniterBench.cModifyId size=8 9,352.0 9,642.8 +3.1%
AvroJsonBridgeBench.naiveWideToAvro - 950.8 980.2 +3.1%
OrderJsoniterBench.eoReadStreet size=64 211.0 217.5 +3.1%
PlatedBench.eoUniverseExpr n=64 1,974.9 1,916.9 -2.9%
JsoniterBench.cReadId size=8 4,236.6 4,359.0 +2.9%
PlatedBench.mUniverseJson n=64 19,216.7 18,685.0 -2.8%
OrderCirceBench.monocleStreet size=512 181,478.2 176,529.9 -2.7%
AvroJsonBridgeBench.naiveWideToJson - 1,854.3 1,904.6 +2.7%
AvroJsonBridgeBench.naiveClickToAvro - 1,399.5 1,437.3 +2.7%
PlatedBench.eoUniverseDeep n=64 2,023.8 1,970.9 -2.6%
OrderCirceBench.directNames size=8 4,052.0 3,948.6 -2.6%
OrderJsoniterBench.eoModifyStreet size=64 490.2 502.5 +2.5%
OrderCirceBench.naiveNames size=64 23,772.1 23,176.8 -2.5%
OrderJsoniterBench.monocleModifyStreet size=8 3,409.4 3,489.6 +2.4%
JsoniterBench.cReplaceId size=8 9,482.5 9,265.3 -2.3%
JsoniterBench.cReadStreet size=64 28,657.8 28,033.3 -2.2%
OrderJsoniterBench.naiveReadStreet size=8 1,911.8 1,871.8 -2.1%
OrderCirceBench.hcursorNames size=64 28,734.9 28,147.7 -2.0%
OrderAvroBench.eoModifyStreet size=512 133.9 131.3 -2.0%
OrderCirceBench.eoNames size=8 3,973.5 4,050.6 +1.9%
PlatedBench.visitorUniverseDeep n=4096 61,912.9 63,095.5 +1.9%
OrderCirceBench.hcursorNames size=8 3,924.5 3,999.4 +1.9%
OrderCirceBench.monocleNames size=512 228,327.9 232,584.8 +1.9%
OrderAvroBench.eoModifyNames size=8 603.3 614.0 +1.8%
JsoniterBench.jSumPrices size=8 1,450.0 1,473.8 +1.6%
OrderCirceBench.hcursorStreet size=8 1,036.9 1,053.8 +1.6%
OrderCirceBench.monocleStreet size=8 3,234.3 3,285.9 +1.6%
PlatedBench.eoUniverseJson n=4096 246,723.5 250,562.2 +1.6%
JsoniterBench.cReadStreet size=512 224,080.6 220,628.7 -1.5%
OrderJsoniterBench.naiveModifyStreet size=8 3,419.7 3,471.5 +1.5%
OrderJsoniterBench.eoSumPrices size=64 11,028.0 10,868.4 -1.4%
JsoniterBench.jModifyId size=64 304.1 308.5 +1.4%
PlatedBench.eoUniverseExpr n=4096 107,913.4 106,364.1 -1.4%
OrderCirceBench.naiveStreet size=512 183,216.5 180,673.7 -1.4%
OrderAvroBench.naiveModifyStreet size=64 7,575.2 7,677.7 +1.4%
OrderCirceBench.eoNamesIor size=64 32,196.5 31,778.4 -1.3%
OrderCirceBench.directStreet size=8 989.4 1,002.2 +1.3%
PlatedBench.mUniverseExpr n=4096 3,091,383.8 3,052,004.8 -1.3%
OrderCirceBench.eoStreetIor size=8 993.1 1,005.7 +1.3%
OrderCirceBench.monocleNames size=64 25,188.8 25,501.8 +1.2%
OrderAvroBench.eoReadStreet size=8 40.6 40.1 -1.2%
OrderCirceBench.monocleNames size=8 3,733.7 3,779.9 +1.2%
JsoniterBench.jReadStreet size=8 210.4 207.8 -1.2%
JsoniterBench.jModifyId size=512 3,013.8 2,978.1 -1.2%
OrderJsoniterBench.naiveReadStreet size=512 97,081.5 98,220.1 +1.2%
OrderAvroBench.monocleReadStreet size=8 547.8 541.5 -1.1%
JsoniterBench.jSumPrices size=64 11,001.2 10,877.5 -1.1%
JsoniterBench.jReadStreet size=512 210.9 208.7 -1.0%
PlatedBench.mTransformExpr n=64 2,733.6 2,761.5 +1.0%
PlatedBench.visitorTransformDeep n=4096 47,481.8 46,997.6 -1.0%
OrderJsoniterBench.eoReadStreet size=512 213.8 211.7 -1.0%
OrderJsoniterBench.eoReadStreet size=8 211.4 213.5 +1.0%
OrderJsoniterBench.naiveModifyStreet size=64 21,150.1 20,948.9 -1.0%
OrderCirceBench.eoStreet size=8 998.9 1,007.0 +0.8%
OrderJsoniterBench.naiveSumPrices size=64 12,993.2 12,897.1 -0.7%
OrderJsoniterBench.nativeReadStreet size=8 881.7 875.3 -0.7%
PlatedBench.eoUniverseJson n=512 28,020.8 27,819.8 -0.7%
JsoniterBench.cReadStreet size=8 4,289.4 4,258.6 -0.7%
OrderJsoniterBench.monocleReadStreet size=64 12,300.7 12,387.7 +0.7%
JsoniterBench.cReadId size=512 219,806.0 221,294.0 +0.7%
OrderAvroBench.monocleModifyNames size=512 106,075.8 106,748.9 +0.6%
OrderAvroBench.naiveModifyStreet size=512 58,537.6 58,904.5 +0.6%
OrderJsoniterBench.naiveSumPrices size=512 103,122.7 102,508.9 -0.6%
OrderJsoniterBench.monocleModifyStreet size=64 20,960.5 21,083.8 +0.6%
JsoniterBench.jReadStreet size=64 208.3 209.5 +0.6%
OrderAvroBench.naiveReadStreet size=512 36,739.9 36,528.0 -0.6%
OrderCirceBench.eoStreetIor size=64 1,018.3 1,012.7 -0.6%
OrderAvroBench.eoModifyStreet size=64 131.6 130.9 -0.6%
JsoniterBench.jSumPrices size=512 89,333.1 88,848.9 -0.5%
JsoniterBench.jReplaceId size=8 99.5 99.0 -0.5%
PlatedBench.eoTransformDeep n=512 13,107.1 13,176.4 +0.5%
OrderJsoniterBench.naiveSumPrices size=8 1,967.2 1,956.9 -0.5%
JsoniterBench.jReplaceId size=512 2,994.5 2,979.0 -0.5%
OrderCirceBench.naiveNames size=8 3,452.0 3,469.5 +0.5%
OrderJsoniterBench.monocleReadStreet size=512 96,688.2 97,178.0 +0.5%
OrderCirceBench.monocleStreet size=64 22,037.3 22,143.8 +0.5%
PlatedBench.eoUniverseJson n=64 3,313.1 3,297.3 -0.5%
PlatedBench.eoTransformExpr n=4096 134,054.3 133,431.7 -0.5%
JsoniterBench.cReplaceId size=64 56,661.2 56,402.9 -0.5%
OrderJsoniterBench.eoSumPrices size=8 1,457.6 1,464.2 +0.5%
OrderAvroBench.eoModifyNames size=64 4,502.8 4,482.4 -0.5%
PlatedBench.mUniverseJson n=512 196,353.9 197,238.4 +0.5%
JsoniterBench.cSumPrices size=512 364,284.3 365,893.8 +0.4%
OrderJsoniterBench.nativeSumPrices size=8 1,268.1 1,262.6 -0.4%
PlatedBench.visitorUniverseDeep n=512 7,576.2 7,543.9 -0.4%
OrderJsoniterBench.nativeSumPrices size=512 68,447.5 68,159.1 -0.4%
OrderCirceBench.directStreet size=64 1,025.0 1,020.8 -0.4%
OrderCirceBench.naiveStreet size=8 3,243.6 3,256.3 +0.4%
PlatedBench.mUniverseExpr n=512 185,215.0 185,933.3 +0.4%
PlatedBench.visitorUniverseExpr n=64 831.7 834.8 +0.4%
OrderCirceBench.naiveNames size=512 199,762.2 200,503.9 +0.4%
OrderCirceBench.eoStreet size=512 1,022.2 1,018.6 -0.4%
OrderJsoniterBench.nativeReadStreet size=512 38,064.8 37,933.1 -0.3%
PlatedBench.mTransformExpr n=4096 178,100.5 178,715.4 +0.3%
OrderCirceBench.directStreet size=512 995.1 991.9 -0.3%
OrderJsoniterBench.monocleSumPrices size=512 461,220.7 459,800.1 -0.3%
OrderJsoniterBench.nativeReadStreet size=64 4,776.9 4,763.0 -0.3%
OrderAvroBench.eoModifyNames size=512 36,820.1 36,926.0 +0.3%
OrderAvroBench.naiveModifyNames size=8 1,124.2 1,127.3 +0.3%
PlatedBench.visitorTransformDeep n=512 4,465.0 4,476.5 +0.3%
OrderCirceBench.eoNames size=64 29,314.0 29,389.4 +0.3%
PlatedBench.eoTransformDeep n=4096 128,875.5 128,548.3 -0.3%
PlatedBench.eoUniverseExpr n=512 15,351.9 15,313.6 -0.2%
OrderAvroBench.naiveReadStreet size=8 542.3 541.0 -0.2%
PlatedBench.visitorUniverseExpr n=4096 56,602.4 56,742.8 +0.2%
OrderAvroBench.naiveModifyNames size=512 73,569.4 73,748.6 +0.2%
PlatedBench.visitorUniverseExpr n=512 7,037.9 7,021.2 -0.2%
OrderJsoniterBench.nativeSumPrices size=64 8,527.2 8,507.0 -0.2%
PlatedBench.mTransformExpr n=512 16,064.2 16,026.9 -0.2%
OrderJsoniterBench.monocleModifyStreet size=512 169,374.8 169,759.1 +0.2%
JsoniterBench.cSumPrices size=64 44,567.0 44,664.4 +0.2%
PlatedBench.visitorTransformExpr n=64 1,058.6 1,060.9 +0.2%
OrderAvroBench.eoReadStreet size=512 40.1 40.2 +0.2%
OrderJsoniterBench.eoModifyStreet size=512 3,284.2 3,291.2 +0.2%
JsoniterBench.cModifyId size=64 56,633.3 56,746.9 +0.2%
OrderJsoniterBench.monocleSumPrices size=64 16,968.7 16,934.7 -0.2%
PlatedBench.eoUniverseDeep n=512 15,940.8 15,967.9 +0.2%
PlatedBench.eoTransformDeep n=64 1,459.1 1,456.6 -0.2%
OrderJsoniterBench.eoModifyStreet size=8 304.7 305.2 +0.2%
OrderJsoniterBench.monocleReadStreet size=8 1,873.6 1,876.6 +0.2%
OrderAvroBench.naiveReadStreet size=64 4,699.5 4,706.6 +0.2%
JsoniterBench.jModifyId size=8 105.2 105.0 -0.1%
OrderJsoniterBench.naiveModifyStreet size=512 169,862.8 169,653.7 -0.1%
JsoniterBench.cReadId size=64 28,006.0 28,039.1 +0.1%
OrderCirceBench.eoStreetIor size=512 999.7 1,000.7 +0.1%
OrderAvroBench.eoReadStreet size=64 40.1 40.1 +0.1%
PlatedBench.eoTransformExpr n=64 2,016.3 2,018.3 +0.1%
OpticBuildBench.reuseUse - 987.4 988.3 +0.1%
OrderAvroBench.monocleReadStreet size=512 36,663.9 36,627.6 -0.1%
PlatedBench.visitorTransformExpr n=4096 70,049.2 69,985.3 -0.1%
PlatedBench.visitorTransformDeep n=64 472.7 472.3 -0.1%
JsoniterBench.cModifyId size=512 444,939.9 444,547.4 -0.1%
OrderJsoniterBench.monocleSumPrices size=8 2,568.1 2,570.3 +0.1%
OrderCirceBench.eoNamesIor size=512 245,984.4 245,785.7 -0.1%
OpticBuildBench.build - 48.0 47.9 -0.1%
OrderJsoniterBench.eoSumPrices size=512 89,898.2 89,838.3 -0.1%
PlatedBench.eoTransformExpr n=512 16,519.6 16,508.7 -0.1%
OrderAvroBench.monocleReadStreet size=64 4,702.4 4,699.4 -0.1%
JsoniterBench.jReadId size=8 42.4 42.4 +0.0%
OrderAvroBench.eoModifyStreet size=8 130.8 130.8 -0.0%
PlatedBench.visitorTransformExpr n=512 8,447.5 8,448.6 +0.0%
JsoniterBench.jReplaceId size=64 308.2 308.2 +0.0%
OrderCirceBench.hcursorStreet size=512 1,035.0 1,035.0 -0.0%

base_sha: 75c7eaa712a23c7503b51858dcdfc61347e600c8 · head_sha: f8bc7964a8a2fef5fe332a96317e54eb9eec184d · jdk: temurin-21 · runner: ubuntu-22.04 · jmh_params: -i 3 -wi 2 -f 1 -t 1 -foe true -prof gc -rf json · profile: pr:-i3-wi2-f1-t1-gc

@kryptt
kryptt merged commit 3c5d258 into main Sep 18, 2026
20 of 23 checks passed
kryptt added a commit that referenced this pull request Sep 18, 2026
The rebase onto main invalidated this branch's own premise. When it was
written, `zio` was `ZEnvironment` / `ZLayer` / `Ref` wiring that delegated
straight into ZIO's API, so stryker generated ZERO mutants for it and the
honest row was `n/a`. #92 then landed the ecosystem optics — the zio-schema
`DynamicValue` kit, the zio-json AST and `JsonCursor` bridge, the STM focus
ops, `Chunks` — and with them a great deal of genuinely mutatable code.

`project zioIntegration; stryker` on the rebased tree does not print `n/a`.
It aborts, with the same UnableToFixCompilerErrorsException that #109 fixed
for kyo, on `json/JsonOptics.scala`:

    'Extension without extension methods'
    'Not found: type To' / 'Not found: self' / 'Not found: type A'

Same mechanism, same fix. stryker4s 0.20.3 re-prints every mutated file
through scalameta; a SINGLE-METHOD significant-indentation `extension` clause
is not a `Term.Block`, so it comes back as the one-line form with the method's
leading Scaladoc replayed between the two, and the forced newline lands `def`
in column 0. Four such clauses exist in the module — two in `json/JsonOptics`
(`JsonCursor.optional`, `JsonCodec.stringPrism`) and two in
`schema/SchemaOptics` (`Schema.dynamicPrism`, `BinaryCodec.prism`). Bracing
their bodies makes the printer emit the braced form and the newline harmless.
`ZioOptics`'s single-method clause already hoists its Scaladoc above
`extension`, which dodges the same trap, so it is left alone.

Verified, not asserted: `javap -p -c` over `JsonOptics$package$` and
`SchemaOptics$package$` is byte-for-byte identical before and after, and
`zioIntegration/test` passes unchanged (268 examples).

With that, both effect-system rows are real measurements rather than
placeholders, regenerated by `site/tools/gen-qa-report.py` from the actual
report.json:

  | `zio` | 37 | 0 | 6 | 0 | 0 | 86.0% | 86.0% |
  | `kyo` | 30 | 0 | 5 | 28 | 0 | 47.6% | 85.7% |

zio is a clean high-signal row: 37 killed, 6 survived, and no no-coverage
block at all. kyo moved off #109's 20/4/28 (38.5% / 83.3%) because #111's
survivor-killing pass landed in between; its 28 no-coverage mutants are still
all `RecordIsoMacro`, so the note stands.

The generator's `n/a` branch stays. Nothing hits it today, but rendering a
zero-mutant module as the same em-dash it uses for "no report at all"
conflates "nothing to score" with "nothing ran", and printing 0.0% would read
as a test-quality failure where there is no bar to fail. The page now says so
explicitly rather than pointing at zio as the example.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0194EHFR4NamCpTHiqy7B74V
kryptt added a commit that referenced this pull request Sep 18, 2026
…run (#113)

* fix(qa): score zio and kyo on the QA page

`site/tools/gen-qa-report.py`'s `MUTATION_MODULES` stopped at `jsoniter`, so
the two effect-system integrations never had a row on the quality-assurance
page even though `mutationAll` has been running stryker over both. The
generator looked for reports it was never told to look for, which is the
quietest possible failure: no error, no empty row, just absence.

`.github/workflows/quality.yml`'s per-module loop had drifted the same way,
and further: it was missing `schemesLaws` too. A module absent from that loop
produces no report.json on the release-tag run, so its row falls back to
dashes — or, worse, keeps whatever numbers a developer's local run happened to
leave on disk. The loop now matches the `mutationAll` alias, and both lists
carry a comment saying they must stay in step. The loop takes sbt PROJECT IDS
(`zioIntegration`), `MUTATION_MODULES` takes DIRECTORIES (`zio`); getting that
backwards is what silently finds nothing, so it is written down.

Two honest edge cases the rows had to render correctly:

  - `zio` generates ZERO mutants on main — it is `ZEnvironment` / `ZLayer` /
    `Ref` wiring that delegates straight into ZIO's API, with no operator,
    literal or branch for a mutator to change. That is `n/a`, NOT 0%: a module
    with nothing to mutate has not failed a test-quality bar, it has no bar to
    fail. The old code divided only when the denominator was non-zero, but it
    rendered the empty case as the same em-dash it uses for "no report at all",
    conflating "nothing to score" with "nothing ran". `n/a` is also what
    stryker's own console prints here.
  - `kyo` scores 38.5% total / 83.3% covered, and the entire no-coverage block
    is `RecordIsoMacro` — quoted-macro code that expands at compile time, the
    same structural reason `generics` scores 0%. The Notes column now says so,
    so the total score does not read as a coverage hole.

The all-NoCoverage shape (`generics`: 0.0% total, covered undefined) is
deliberately left alone — those mutants exist, they are simply never executed.

Rows verified end to end: stryker was run per-module in a clone
(`project zioIntegration; stryker`, same for kyo — never `<module>/stryker`)
and the generator read the resulting report.json for both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0194EHFR4NamCpTHiqy7B74V

* fix(zio): brace the extension clauses, and score zio for real

The rebase onto main invalidated this branch's own premise. When it was
written, `zio` was `ZEnvironment` / `ZLayer` / `Ref` wiring that delegated
straight into ZIO's API, so stryker generated ZERO mutants for it and the
honest row was `n/a`. #92 then landed the ecosystem optics — the zio-schema
`DynamicValue` kit, the zio-json AST and `JsonCursor` bridge, the STM focus
ops, `Chunks` — and with them a great deal of genuinely mutatable code.

`project zioIntegration; stryker` on the rebased tree does not print `n/a`.
It aborts, with the same UnableToFixCompilerErrorsException that #109 fixed
for kyo, on `json/JsonOptics.scala`:

    'Extension without extension methods'
    'Not found: type To' / 'Not found: self' / 'Not found: type A'

Same mechanism, same fix. stryker4s 0.20.3 re-prints every mutated file
through scalameta; a SINGLE-METHOD significant-indentation `extension` clause
is not a `Term.Block`, so it comes back as the one-line form with the method's
leading Scaladoc replayed between the two, and the forced newline lands `def`
in column 0. Four such clauses exist in the module — two in `json/JsonOptics`
(`JsonCursor.optional`, `JsonCodec.stringPrism`) and two in
`schema/SchemaOptics` (`Schema.dynamicPrism`, `BinaryCodec.prism`). Bracing
their bodies makes the printer emit the braced form and the newline harmless.
`ZioOptics`'s single-method clause already hoists its Scaladoc above
`extension`, which dodges the same trap, so it is left alone.

Verified, not asserted: `javap -p -c` over `JsonOptics$package$` and
`SchemaOptics$package$` is byte-for-byte identical before and after, and
`zioIntegration/test` passes unchanged (268 examples).

With that, both effect-system rows are real measurements rather than
placeholders, regenerated by `site/tools/gen-qa-report.py` from the actual
report.json:

  | `zio` | 37 | 0 | 6 | 0 | 0 | 86.0% | 86.0% |
  | `kyo` | 30 | 0 | 5 | 28 | 0 | 47.6% | 85.7% |

zio is a clean high-signal row: 37 killed, 6 survived, and no no-coverage
block at all. kyo moved off #109's 20/4/28 (38.5% / 83.3%) because #111's
survivor-killing pass landed in between; its 28 no-coverage mutants are still
all `RecordIsoMacro`, so the note stands.

The generator's `n/a` branch stays. Nothing hits it today, but rendering a
zero-mutant module as the same em-dash it uses for "no report at all"
conflates "nothing to score" with "nothing ran", and printing 0.0% would read
as a test-quality failure where there is no bar to fail. The page now says so
explicitly rather than pointing at zio as the example.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0194EHFR4NamCpTHiqy7B74V

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant