Skip to content

perf(core): reduce repeated tree and text work - #795

Merged
gaborbernat merged 165 commits into
tox-dev:mainfrom
gaborbernat:main
Sep 12, 2026
Merged

gaborbernat merged 165 commits into
tox-dev:mainfrom
gaborbernat:main

Conversation

@gaborbernat

@gaborbernat gaborbernat commented Sep 8, 2026

Copy link
Copy Markdown
Member

This PR reduces repeated scans, copying and temporary allocation across parsing, DOM operations, queries, serialization, extraction and validation. It extends the existing benchmark suite and the performance and migration tables with the workloads that exposed those costs. The public API stays compatible; the correctness fixes below address behavior found while exercising the changed paths.

The comparisons below describe individual matched experiments against their recorded baselines; the percentages are not a cumulative speedup over upstream. Retained target changes require at least 5% lower elapsed time. Small-input, construction, mutation and ordinary-workload controls accompany the affected paths, and the published feeds retain sample-spread warnings and differences in setup cost.

HTML parsing reuses validated formatting-stack positions, caches unchanged scope queries and indexes formatting attribute signatures while retaining full equality checks. Representative deep-formatting, ignored-end-tag and distinct-formatting-attribute inputs improve by 36.96%, 68.94% and 52.46%. Clean text can retain source spans when another token contains a NUL, and normalized CRLF input can retain an owned normalized source buffer. Adjacent parsed text reuses capacity instead of copying its accumulated prefix for each token. These paths preserve source positions, malformed-input handling and foreign-content behavior; retained normalized input trades a persistent source buffer for fewer text copies.

XML parsing avoids a second attribute-name lookup after duplicate validation and retains namespace-resolution metadata for expanded-name checks. The 1,000-name case improves from 210.203 to 57.124 µs (72.82%); the 128-prefix case improves from 633.785 to 47.403 µs (92.52%). Clean one-byte text uses block scanning, reducing the 64 KiB case from 98.903 to 4.886 µs (95.06%). Clean attribute prefixes reserve scratch storage once, reducing the 64 KiB value case from 113.798 to 40.866 µs (64.09%). Namespace metadata uses 8 KiB instead of 4 KiB for the prefix target. Small and reference-rich controls retain their recorded costs, including a 2.81% increase for five characters of XML text.

DOM attribute arrays reserve capacity by a constant factor without enlarging the existing node layout. Ten retained trees with 1,000 attributes add 0.94 MiB RSS during mutation instead of 118.4 MiB in the matched observation. Text normalization sizes a run before copying it and removes empty tails during that walk; 1,000 nonempty nodes improve by 97.2%, and the corrected empty-tail case improves by 46.66%. Large attribute comparisons use an index after enough repeated search work to justify it, while preserving first-match behavior for duplicate normalized names. The 1,000-attribute aligned and reversed equality cases improve by about 98%; small controls remain within 3.5%.

Range operations stop boundary scans at the requested offset, resolve contained child intervals once and copy partial ancestors without excluded descendants. Representative improvements are 67.55% for a start boundary, 99.29% for cloning 1,000 contained children and 73.41% for a partial range with 1,000 excluded siblings. Observer dispatch rejects unmatched event kinds before ancestry scans, improving the 1,000-registration target by 97.46%. Shadow flattening reuses temporary storage and indexes assignments within a call; 1,000 unique slots improve from 4,997.827 to 84.605 µs (98.31%). These operations keep their ordering and mutation semantics.

Form-data collection skips disabled fieldset subtrees while retaining eligible first-legend controls. With the same detached-ancestor fix in both builds, the disabled target improves from 97.981 to 0.093 µs (99.90%); the enabled control improves by 0.74%. Radio updates reuse an existing document input index for document-wide groups. That target improves from 441.671 to 31.658 µs (92.83%), including index setup, while small-form and many-form controls cost 2.58% more. Form-local and detached radio groups keep their scan path.

Query traversal reuses sibling positions and tree order, deduplicates native identities before creating wrappers, and avoids revisiting shared prune ancestry. Large unordered root groups use comparison sorting after an ordered-input check; the 512-root reversed case improves from 47.867 to 1.787 ms (96.27%). Detached-root and document-handle indexes reduce the corresponding grouping cases by 97.43% and 60.87%. Parent, sibling and closest joins preserve first-encounter order. The optimized joins retain their existing free-threaded fallback. Selector-cache eviction now retains the original tree across cleanup callbacks, keeps cache entries coherent before releasing keys and retries after ownership changes. Same-tree traversal locks cover the touched free-threaded paths; concurrent cross-tree adoption still needs a shared wrapper-ownership protocol.

XPath work includes indexed set membership and distinct values, scalar comparisons without temporary text copies, numeric-extrema comparisons, deferred translation-map indexing and reused repeated-character mappings. Matched long scalar-string equality improves by 30.88%, repeated 32 KiB translation by 33.2% to 34.3%, and the long duplicate-map case by 82.7%. Concatenation and ID-argument buffers grow by capacity, improving their many-short-value targets by 19.15% and 18.17%. The ID long-value control costs 4.72% more; lxml and parsel remain faster on the concatenation inputs. Those differences remain visible in the consumed tables.

XSLT applications share static numbering matches and prefix counts, cache winning template rules by node and mode, and use compiled indexes for large named-declaration collections. Dynamic patterns retain per-call evaluation. The static-predicate numbering target improves from 38.214 to 0.253 ms (99.34%); repeated rule dispatch improves by 35.94%, and large named-declaration application by 27.13%. The declaration index adds about 75.8 KiB of arrays for that target and increases compilation time by 2.29%. Reusing identical numbering patterns also addresses the reported transform-reuse regression: the matched comparison improves by 39.7% against upstream. The lxml tables state when its timed result omits Python string conversion.

Serialization avoids repeated word scans when Markdown wrapping does not need them and caches line positions for wrapping. The escaped-asterisk case improves from 8,652.19 to 43.81 µs (99.49%); ordinary-text spread limits the precision of its control result. Canonical output sorts large attribute sets and carries active xlink scope through traversal; the sparse depth-150 xlink case improves by 51.41%, with deep xlink-free and shallow controls costing 3.57% and 3.89% more. Reused exact-type HTML, plain-text and canonical options cache field metadata, reducing tiny prepared-node rendering by about 40% while retaining current option values and subclass behavior. Token attributes allocate their known list length, and typed SAX records avoid temporary field lists; the latter improves full iteration over 4,096 elements by 16.54%.

CSS computed style caches alternative specificity, checks required IDs or classes before expensive pseudo-classes and shares eligible descendant :has() results through existing invalidation rules. Nested-selector specificity improves by 58.29%, irrelevant-rule filtering by 38.70%, and the two descendant-lookup targets by 8.81% and 8.62%. Specificity retains maximum matching-alternative behavior and source order, at 12 retained bytes per alternative. Replacing a memo entry no longer increases its count or retained capacity. CSS minification copies adjacent equal media blocks and identical declaration runs once: 1,000-block targets improve by 70.35% and 93.69%. Reusing declaration spans for intervening-rule conflict checks improves the long-value target by 64.14%. Small and nonmerging controls accompany both changes; seven competitor adapters preserve the fixture-specific cascade semantics. Lightningcss takes 952.8 µs on the long-value conflict fixture versus turbohtml’s 1,534.8 µs, with both emitting 8,818 bytes.

JavaScript minification retains sequence tails and folds guard returns in reverse order, improving the large sequence and guard cases by 51.73% and 54.97%. It also stops recreating canonical booleans: Underscore improves by 41.17% and held-out jQuery by 37.34%, with identical output bytes. Literal propagation indexes plans by symbol and retains declarator identities, improving separate targeted cases by 24.90% and 29.05%. Integer printing skips exponent formatting when fewer than three trailing zeros could be replaced: 4,096 integers improve from 345.365 to 245.119 µs (29.03%), with at most 0.50% cost across the trailing-zero and ordinary-script controls. These gains come from different stages and must not be added together.

Further JavaScript changes unlink eligible declarators in one traversal, retain declaration tails while merging, record whether reads precede initialization and maintain predecessor information during removal. Matched mixed-declaration, declaration-tail and initialization-order targets improve by 19.58%, 29.60% and 47.86%. With the initialization snapshot present in both variants, predecessor tracking improves that target by another 20.23%; interleaved unused bindings improve by 55.22%. Tiny controls cost at most 2.17% more in these experiments. Closure calls, captured var, duplicate declarations and switch-case jumps retain their initialization errors and side effects. Rjsmin still leads several timing cells; terser produces smaller output on some inputs. CLI adapter measurements include process startup, and unsupported syntax or semantic mismatches have no timing claim.

Extraction reuses article candidate totals, avoids unnecessary local property sorting, orders large shuffled Microdata collections with one tree walk, hashes repeated visible-date tallies and collects feed entry fields once. The 1,000-reference Microdata case improves by 71.82%; extension-heavy RSS improves by 6.80%, with 5.25% candidate variation. Shared table spans reuse trimmed snapshots and immutable strings while keeping output containers independent: the wide long-text cases improve by 97.47% for rows and 97.12% for records. External-link filtering bounds suffix probes by the maximum depth generated from the pinned public-suffix data, improving the long-subdomain workload by 51.39%. Existing precedence, duplicate handling and document order remain part of the comparison contract.

Sanitizer compaction applies to private trees with at least 32 attributes when reporting and relevant callbacks are off. It improves the 1,024-rejected-attribute case by 67.57%; allowed-attribute and small controls cost 0.06% and 2.82% more. Value, URL and style checks still run. Linkification borrows its owned wide-text snapshot through mutation, improving the one-link wide-text target by 5.32%, with four controls within 0.86% of baseline.

Compiled XSD schemas retain regex graphs and effective inherited facets, improving repeated-pattern and inherited-facet validation by 8.23% and 15.42%. Facet construction costs an additional 1.028 µs (26.19%) for the recorded schema. Named-type validation now avoids gathering a discarded second facet copy, improving element and attribute workloads by 19.66% and 15.59%; the latter retains a 12.78% spread warning. Per-call instance-attribute indexes complement declared-name indexes and improve the 512-attribute case from 399.558 to 126.712 µs (68.29%), with the three shape controls within 0.86%. Numeric validation converts to double only when a min/max bound needs it; unbounded decimals improve by 9.85%, while lexical validation and other facets still run.

RELAX NG stores nullability for patterns independent of recursive references and retains the recursion guard for the others. Optional-group validation improves from 3.314 ms to 210.3 µs (93.65%); interleaves improve by 8.73%, and the small group by 5.22%. Recursive validation and both compilation controls differ by 0.51% to 1.17%. The field fits existing pattern padding. Both matched variants include the same namespace and schema-text ownership corrections; per-call validation storage no longer retains temporary realized text.

Language detection counts trigrams before ranking, and Unicode normalization bounds long combining-mark ordering. Encoding detection constructs only the winning EncodingMatch for detect() and streaming close(); detect_all() still constructs every ranked result. The legacy one-shot and streaming cases improve by 17.16% and 16.70%; ASCII and BOM one-shot controls improve by 11.93% and 13.03%. The shared competitor checks preserve decoded text rather than comparing confidence scales. Chardet and charset-normalizer misdecode the legacy fixture, so the three affected comparison cells explain the mismatch and omit timing claims. Resiliparse and faust-cchardet remain faster on these short inputs; UnicodeDammit uses the installed faust-cchardet backend in this comparison.

Wheel imports use a generated version constant; editable installs retain installed-metadata behavior. Fresh imports improve by 24.99% to 26.47%, and CLI startup controls by 15.23% to 16.19%, across small and larger installed-distribution environments. One import batch retains its 11.22% variation warning. Unicode-table generation shares pinned-data scans and emits identical headers in 6.10% less elapsed time. Startup and full generator measurements stay outside CodSpeed simulation. Benchmark workers prepare only their assigned input; the light worker's observed peak RSS decreases by 3.14 MiB, without an operation-speed claim.

The correctness fixes cover temporary annotation-label ownership, extraction and schema text lifetimes, detached form/minifier ancestry, exact escaping-byte counts, XPath numeric casts and repeated regex flags, multi-level XSLT separators, JavaScript initialization and CSS nth-filter specificity. Markup conversion propagates errors from HTML-method lookup. These fixes carry no speedup requirement. The repository-wide scenario and oracle consolidation follows source ownership while keeping optional dependency and runtime gates.

The existing performance and migration tables report current library timings, output sizes and measurement spread. This PR extends those tables and the shared benchmark registry, including CodSpeed where simulation is appropriate. The committed JSON feeds have documentation consumers. Competitors remain faster in several cells.

Add size sweeps for sibling selectors, XPath ordering, deep styles,
Microdata traversal, structured metadata, and article scoring. Register
nine middle-size CodSpeed cases so CI can track these costs.

All nine CodSpeed cases pass. Changed executable lines have full coverage.
Without itemref, preorder yields each property once in tree order. Avoid
the growing visited-list scan and tree-walking sort comparator for these
items while keeping referenced-item traversal and ordering semantics.

On an M4 release build, 10,000 properties including parsing fell from
934 ms to 2.50 ms, returned to 990 ms on source restoration, and measured
3.22 ms after reapplying. CPU idle averaged 29-46% across these runs.
The noisy itemref control showed an unresolved 11% mean slowdown.

Verify property order, nested scopes, empty attributes, and cyclic graphs
through the public API. The normal suite passed 65,067 tests; all 12 new
ordering cases pass after adding four further edge cases. Python, C, and
changed-line coverage are 100%. Pre-commit checks pass.
@gaborbernat gaborbernat added the enhancement New feature or request label Sep 8, 2026
@codspeed

codspeed Bot commented Sep 8, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 49.82%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 25 improved benchmarks
✅ 129 untouched benchmarks
🆕 244 new benchmarks
⏩ 32 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
test_feature[detect-language] 29.7 ms 5.1 ms ×5.8
test_feature[path-xpath] 45.9 ms 13.9 ms ×3.3
test_feature[path] 46 ms 14 ms ×3.3
test_feature[microdata-itemref] 35.4 ms 11.3 ms ×3.1
test_feature[range-clone] 11.3 ms 4.5 ms ×2.5
test_feature[computed-style-dense] 60.1 ms 30.8 ms +95.06%
test_feature[minify-js] 28.8 ms 15 ms +92.03%
test_feature[transform-reuse] 817.4 µs 486.5 µs +68.01%
test_feature[transform-dense] 25.5 ms 16.6 ms +53.47%
test_feature[computed-style] 11.2 ms 9 ms +24.36%
test_feature[find-cold] 35.3 µs 29 µs +21.73%
test_feature[xpath] 1.2 ms 1.1 ms +17.99%
test_feature[boilerplate-spec] 23.7 ms 20.6 ms +15.18%
test_feature[text-main-parse-spec] 21.9 ms 19.5 ms +12.29%
test_feature[parse-xml-names] 15 ms 13.5 ms +11.22%
test_feature[article-parse-spec] 24.2 ms 22 ms +10.2%
test_feature[text-content] 3 ms 2.7 ms +8.63%
test_feature[canonicalize-deep] 474.5 µs 437.1 µs +8.54%
test_feature[serialize-inner-minify] 7.8 ms 7.2 ms +7.92%
test_feature[transform-sort] 574.8 µs 540 µs +6.45%
... ... ... ... ...

ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing gaborbernat:main (e15a996) with main (ef09bf3)

Open in CodSpeed

Footnotes

  1. 32 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Wide-tree queries recount siblings and sort ordered results. Reuse
positions within queries and merge ordered unions. Cache computed styles
and requested path positions, invalidating them after relevant mutations.
Each computed result owns its values.
Index article candidates and visited Microdata reference nodes. Preserve
tree order while avoiding redundant sorts, and skip absent metadata
formats without removing the callback-safe snapshot.
Long disordered combining runs trigger quadratic insertion sorting. Use
stable counting for long runs and retain insertion sorting for short ones.
Preserve input order for marks with equal combining classes.
Elements with thousands of attributes make insertion sorting quadratic.
Use qsort above 32 attributes and preserve canonical key ordering.
Count distinct trigrams before ranking them, avoiding sorting a record
for every occurrence in long prose. Preserve frequency ties and model
selection.
Cover long text and paths on fresh trees through the shared pyperf and
CodSpeed registry. Include class edits to detect path ID index rebuilds.

Defer parser imports until the two node input loaders run so competitor
workers can import the registry without installing turbohtml.
Include the source inventory, matched worker samples, and CPU headroom
beside the measured improvements and rejected approaches. Record the
unfinished release-table refresh so reviewers can distinguish the audit
evidence from historical comparison tables.
@gaborbernat gaborbernat added the bug Something isn't working label Sep 8, 2026
@gaborbernat gaborbernat changed the title perf(extract): avoid redundant local Microdata scans perf(core): reduce repeated tree and text work Sep 8, 2026
@gaborbernat gaborbernat added the documentation Improvements or additions to documentation label Sep 8, 2026
Publish the PGO measurements after CPU-load checks pass for all 34 affected
operations. Preserve unmeasured migration rows and record accepted and
rejected CPU windows alongside the audit evidence.
Keep the allocation-failure condition on one line so Clang versions agree
on the coverage exclusion. The multiline expression left three uncovered
branches in the macOS Python 3.13 job.
Avoid rebuilding and comparing earlier node strings for each candidate.
Retain the first node for each value and bypass hashing for empty and
singleton results to avoid their allocation overhead.
Grow character-class range storage by capacity to bound arena allocation
and copying costs. Preserve matching behavior for XSD and RELAX NG.
Search the shadow tree once per assignment call after finding a matching
light child. Avoid searches for empty, comment-only, and nonmatching hosts
while preserving first-slot precedence and mutation behavior.
Add cardinality and empty-input controls alongside growing character
classes and late shadow slots. Register nine CodSpeed cases and lxml and
parsel comparisons through the existing benchmark and documentation feeds.
Record matched scaling comparisons, small-input controls, superseded
candidates, source hashes, and CPU headroom. Refresh seven release-build
operations and preserve unmeasured migration rows.

Document the ordinary shadow control slowdown and timing variation
alongside the retained assignment gains.
@gaborbernat
gaborbernat marked this pull request as draft September 9, 2026 00:53
CPU idle checks did not establish memory headroom. Mark wall-clock
comparisons provisional and retain the swap observations from saved logs.
Keep measured values unchanged pending runs with memory checks.
Reusing a compiled schema retained validation allocations until schema
destruction. Give each call its own arena and RELAX NG definition state
to free temporary buffers and avoid sharing mutable definition state.
Disjoint node sets required a scan of the second set for each first-set
member. Hash node and attribute identity above the small-set threshold,
and preserve the first-node overlap shortcut. The guarded 10,000-node
cases improve by 75 to 156 times.
Nested article candidates rescanned the same descendant text for link
density. Accumulate totals in one traversal with a depth-sized stack.
Keep individual scans for small candidate sets. The guarded depth-500
case improves by 7.03 times, with ordinary article controls close to base.
Add shared workloads and five CodSpeed cases for XPath membership and
nested article extraction. Refresh eight operation feeds and measured
rows in eight migration tables using a bounded PGO build.

Record before/after samples and CPU, memory-pressure, and swap gates.
Keep the earlier audit timings provisional because those runs did not
enforce memory limits.
Keep the benchmark feeds rendered by the documentation. Remove the three
raw measurement downloads and their links from the audit report.
Hash larger equality sets and compare numeric extrema to avoid repeated
pairwise conversions. Existential inequality needs at most one scan of
each set. Scalar strings can compare their owned buffers without copies.

Matched release runs reduce the 32 KiB scalar case by about 35% and the
1,000-node numeric less-than case by 522 times. Retain first-pair exits
and small-set scans; preserve NaN and XPath coercion behavior.
Validation retained allocated text copies without freeing them. Borrow
realized tree text and realize shared schema spans during compilation so
lazy definitions cannot mutate those spans during concurrent validation.
Keep the XML namespace constant immutable as well.

Normal and ASan checks cover text, CDATA and shared-schema behavior, with
complete changed native coverage. Across 64 repeated calls, three probes
previously retained 1 MiB, 1 MiB and 2 MiB; each now retains zero extra
bytes. Performance comparisons include these fixes on both sides.
CI imports the editable generated version module, so its wheel branch
remained uncovered even though copied-file layout tests passed.

Import the installed source through a real symlinked wheel-layout
package under coverage. Distinct metadata proves the frozen version wins.
The focused check covers all nine generated statements and both branches
without exclusions. Types, Ruff and scoped hooks pass.
Move 29 optional comparisons into the existing XML parser, table and
canonicalization test modules. Reuse the XML tree helpers and public
traversal instead of keeping duplicate helpers in a separate file.

All 29 cases pass; raw coverage records every moved test statement.
Ruff, types and scoped hooks pass. Remove three obsolete files and retain
the existing optional-oracle coverage policy.
Batch literal-declaration removal, remember declarator predecessors and
initialization order during binding, and retain the declaration merge tail.
The predecessor and initialization fields use existing 64-bit padding.

Matched stage benchmarks improved batched unlinking by 13.78-19.58%,
initialization checks by 47.86-68.04%, predecessor lookup by 20.23-55.22%,
and adjacent declaration merging by 29.60%. Controls stayed within 2.18%.
Normal and ASan tests verify declaration order and side effects and exercise
changed native lines and branches. Extend the shared benchmark inputs and
CodSpeed cases for the affected paths.
Keep minifier oracle cases in the existing test module. Scope Node and
competitor requirements to those cases so core tests remain collectable.

Validated 28 focused cases with two expected calmjs skips. Reused the
previous results for unchanged competitor cases; Ruff, types and hooks pass.
Keep XSD and RELAX NG oracle cases with their respective source tests.
Scope optional lxml coverage exclusions to those functions and remove
the duplicate untyped validation helpers.

All 28 relocated cases passed, along with eight pending benchmark oracle
cases. Ruff and types pass.
Batch adjacent media blocks and identical declaration bodies to avoid
copying each growing prefix. Reuse parsed property summaries when checking
whether a rule can cross intervening declarations. Preserve selector-list
barriers, shorthand conflicts and allocation-failure fallbacks.

Matched targets improve 70.35% for media blocks, 93.69% for selector lists,
and 64.14%/48.55% for long/short disjoint declarations. Controls show no
material regression. Existing native and ASan checks pass, as do nine
CodSpeed cases and 72 independent competitor output checks. Changed native
branch coverage passes with allocation-failure exclusions.
Move differential and corpus tests beside the source modules they cover.
Load optional libraries through fixtures so missing oracles do not skip
core tests. Preserve corpus cases and add processing-instruction checks
for the lxml tree builder.
Cache reference-independent RELAX NG nullability, index wide instance
attributes, and reuse resolved simple-type facets. Avoid converting
numeric values to doubles when no numeric bound uses the result.

Matched target gains are 93.65% for optional groups, 68.29% for wide
attributes, 19.66% for named element types, and 9.85% for unbounded
decimals. Preserve recursive references and diagnostic order. Extend
the shared suite and CodSpeed with target and control cases.
Keep ranked rows native until the caller needs EncodingMatch objects.
Single-result detection and stream close materialize one result;
detect_all retains the full list.

Matched legacy-byte gains are 17.16% for detect and 16.70% for stream
close. Add ASCII and BOM controls to the shared suite and CodSpeed,
with decoded-text checks for five competing detectors.
Load optional libraries inside marked oracle checks so missing packages
do not leave fixture setup in portable coverage. Mark the lxml builder
and its conversion functions with the same existing oracle scope.
Publish 146 guarded competitor measurements across minification, schema
validation and encoding detection. Refresh 12 development tables and
11 migration tables while preserving unrelated values and spread.

Record output bytes, CLI startup and the detector backend. Omit three
legacy-encoding timings whose detectors misdecode the input. Extend the
JavaScript adapters and shared comparison notes for these workloads.
Avoid formatting an exponent when an integer has fewer than three
trailing zeros. The matched integer-array benchmark improves 29.03%;
trailing-zero and ordinary-script controls cost at most 0.50%.

Add shared benchmark inputs, CodSpeed cases and public value checks.
Restore the existing guide prose and methodology. Keep the new benchmark
tables and their data feeds; remove optimization histories and internal
work-tracking details from the published documentation.
Give each retained optimization and user-visible fix its own changelog
item. Use Towncrier numbered fragments so every item links to PR 795,
and accept that format in the filename check.
@gaborbernat
gaborbernat marked this pull request as ready for review September 11, 2026 13:38
@gaborbernat
gaborbernat enabled auto-merge (squash) September 11, 2026 13:39
Remove integer-printing and RELAX NG branches made unreachable by their
earlier guards. Cover conditional regex reads, long numeric bounds and
uncached reference nullability through existing public API tests.

Scope the CSS allocation-failure exclusion across its multiline condition
so GCC and Clang use the same boundary. Keep coverage thresholds intact.

Signed-off-by: Bernát Gábor <gaborjbernat@gmail.com>
gaborbernat and others added 3 commits September 11, 2026 22:53
CI's gcovr gate requires 100% line and branch coverage, and three
independent gaps were keeping it at 99.9%: phone.c's native candidate
path, a CSS shorthand/longhand conflict branch, and a JS regex-inlining
edge case were each missing exercise from the public API.

phone.c and css_grammar.h needed straightforward regression tests
exercising the untested branches through their existing public
surfaces (phone parsing, linkify, and CSS minification).

mangle.c's gap was different: chain_contains(node->d, target) can only
be reached from a for-loop body or a try's finally block, and both
introduce a new lexical scope relative to the declaration being
considered for inlining. The caller already guards on the reference
scope matching the declaration scope before reaching this point, so a
true result from that call is unreachable by construction, not merely
hard to trigger from JS. Marking it with the project's existing
GCOVR_EXCL_BR_LINE convention (already used for allocation-failure
paths elsewhere) is more honest than writing a test that pretends this
branch is reachable.

Verified against a from-scratch Linux gcov build (matching the CI
runner's toolchain rather than relying on the noisier macOS
xcrun llvm-cov gcov results, which had reported an unrelated set of
false gaps.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
EOF
)
macOS CI's clang-based llvm-cov gcov reported css_grammar.h at 99.9%
branch coverage even though gcc's model already showed 100% on Linux,
because clang tracks the two conjuncts of the summaries-based conflict
check's final OR clause as separate branch points while gcc collapses
them.

The existing large-rule-conflict-barrier cases exercised the summaries
path's longhand-list check only when the left operand's clause failed
outright or the right operand had no longhands at all. Neither hit the
combination where the right (incoming) rule has a shorthand property
but the property blocking it belongs to an unrelated property family,
so css_prop_in_list on the right side had never returned false with a
non-null longhand list to compare against.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
macOS CI still reported css_grammar.h at 99.9% branch coverage after
the previous commit's test addition, while Linux's gcc-based gcov
already saw the same file at 100%. Both outcomes of the two longhand
checks in css_summaries_conflict were genuinely exercised by existing
tests (confirmed with temporary instrumentation), so this was a
coverage-tool discrepancy rather than a missing test: clang's
llvm-cov gcov attributes branches for a multi-line short-circuited
||-chain differently than gcc does, and it kept marking one arm of
the chain as never taken no matter which combination of inputs ran
through it.

Splitting the two longhand-list checks into named boolean locals
ahead of the final if removes the ambiguous multi-line chain that
llvm-cov struggled to attribute, without changing the conflict
semantics. Verified locally with a from-scratch clang 18 build
running llvm-cov gcov the same way macOS CI does; both compilers now
report full 100% branch coverage for this file end to end.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@gaborbernat
gaborbernat merged commit c877768 into tox-dev:main Sep 12, 2026
51 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant