Repository navigation
Add spec-compliance test suite, close WHATWG/IANA alias gaps - #366
Merged
Merged
Conversation
Pins detection output correctness against Python codec round-trip (no U+FEFF leaks), WHATWG <meta charset> markup end-to-end, IANA registry, and Unicode/RFC BOM/UTF-8/UTF-16 conformance. Motivated by 2a54c68, which we caught by inspection rather than by a test. Surfaces and closes ~90 registry alias gaps along the way (WHATWG web labels, IANA csXxx identifiers, IBM 5-digit forms, archaic Unicode 1.1 UTF-8 labels). Deliberately does not add GB18030 BOM detection: WHATWG does not sniff it, CPython has no gb18030-sig variant, so the new stdlib-limitations file pins Python's current leaking behavior instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #366 +/- ##
=======================================
Coverage 99.82% 99.82%
=======================================
Files 24 24
Lines 1679 1679
=======================================
Hits 1676 1676
Misses 3 3 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Both labels are WHATWG-registered but my local Python 3.14 resolves them via its stdlib codec alias table, which Python 3.10-3.13 do not. CI caught two new test failures as a result: - WHATWG Shift_JIS label windows-31j -> should route to cp932 - WHATWG ISO-8859-8 label iso-8859-8-e -> should route to iso8859-8 Adding both to chardet's own alias table so the tests are independent of the Python version's stdlib codec table. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
codecov reported a -0.06% drop on #366 because one previously-hit line in src/chardet/registry.py stopped getting exercised after the alias additions. Line 1000 is the final ``return None`` branch when ``codecs.lookup`` succeeds but the resolved name equals the input and chardet doesn't have the name in its registry (the "Python knows this codec but chardet intentionally doesn't claim it" path). Adding a dedicated unit test for that path. mbcs/idna/punycode/rot_13 are all Python stdlib codecs outside chardet's detection scope, so ``lookup_encoding`` should return None for each. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
2a54c68 fixed a bug where chardet returned
utf-16-le/utf-16-befor BOM-prefixed input. Python's-le/-becodecs don't strip the BOM on decode, so users got a leading U+FEFF in their decoded strings. We caught that by inspection, not by a test, which felt like a near-miss worth addressing.This PR adds a 5-file spec-compliance test suite, and closes the interop gaps it surfaced in the registry along the way.
What the suite tests
Four dimensions of output correctness, all one-way (spec -> chardet):
detect()can return must decode cleanly viabytes.decode()without leaking U+FEFF or raisingLookupError. 516 parametrized cases across all 86 registry encodings, covering the top-pick name, thecompat_names=Truedrop-in contract, all aliases vialookup_encoding, and everydetect_all()candidate.<meta charset=\"X\">label WHATWG specifies must route through chardet's markup stage to an expected canonical. Tests are end-to-end via the realchardet.detect()pipeline, not justlookup_encodingin isolation, so we're exercising what real users hit when they parse HTML.pipeline.bom._BOMSmatches WHATWG's decode-algorithm sniff set exactly;pipeline.utf8.detect_utf8matches RFC 3629 boundary cases (overlong rejections, surrogate rejections, U+10FFFF); UTF-16 surrogate pairs round-trip per RFC 2781.Plus a dedicated
tests/test_python_stdlib_limitations.pythat pins CPython codec behaviors chardet depends on (utf-16 BOM stripping, utf-8-sig, and the gb18030 leak, see below). These aren't tests of chardet's own code: they're forward-looking regression guards for upstream invariants.Spec data is hardcoded as Python constants with
# Source: URL, Snapshot: YYYY-MM-DDcomments. WHATWG'sencodings.jsonhasn't changed since 2020-05-06 and IANA's registry was last updated 2024-06-06, so a fetcher script with freshness automation felt like overkill for data that changes once per decade.Registry alias additions
The WHATWG and IANA tests surfaced real interop gaps: 57 of 58 failing WHATWG labels weren't recognized by Python's stdlib codec table either, which meant any
<meta charset=\"x-cp1252\">style declaration was being silently dropped by chardet's markup stage. Rather than allow-list the gaps, I added the missing aliases directly:x-cp1250..x-cp1258,x-sjis,x-gbk,x-x-big5, compact no-hypheniso88591..iso885916,dos-874,visual/logicalHebrew bidi variants, etc.)csXxxidentifiers (csUTF8,csISO885913..csISO885916,cswindows1250..cswindows1258,csGB18030,csBig5HKSCS, etc.)cp01140,ibm01140,csibm01140on the existingcp1140entry)unicode-1-1-utf-8,unicode11utf8,unicode20utf8,x-unicode20utf8). Rarely used in practice but WHATWG-registered.Every alias was driven by a failing test, not speculative. Two labels I initially added and then dropped during review:
\"chinese\"and\"korean\". Both are semantically lossy (chinesewould route Traditional Chinese content atgb18030, which silently mis-decodes Big5 bytes to garbled output), and Python's codec fallback resolves them both anyway viagb2312andeuc_kr, so chardet's own table doesn't need to bless the ambiguity.One new test in
tests/test_registry.py(test_aliases_are_unique_across_entries) catches the kind of cross-entry collision bug that a careless refactor could introduce, plus two referential-integrity tests that make sure_WHATWG_TO_CHARDETandIANA_ENTRIESnever end up pointing at a canonical that's been renamed out from under them.What this PR deliberately does NOT do
No GB18030 BOM detection. The byte sequence
0x84 0x31 0x95 0x33is GB18030's encoding of U+FEFF, and Python'sgb18030codec doesn't strip it on decode. That's the same bug class as the UTF-16 issue, but after some research:gb18030-sigvariant, so there's no alternative codec name we could return to get BOM strippingInstead,
tests/test_python_stdlib_limitations.py::test_python_gb18030_bom_leak_still_presentpins Python's current leaking behavior. If CPython ever starts stripping the BOM, that test fails, and we reconsider whether chardet should add detection. Users who encounter real-world BOM-prefixed GB18030 content need tolstrip('\\ufeff')themselves after decoding.Test plan
uv run python -m pytest -n auto-> 9271 passed, 28 xfailed, 0 failed (the 28 xfailed are pre-existing known-failure corpus entries intest_accuracy.py)uv run python -m pytest tests/test_accuracy.py -n auto-> 7523 passed, 28 xfailed, 0 failed. Zero regressions from the ~90 alias additions.uv run ruff check .+uv run ruff format --check .-> cleanscripts/benchmark_time.py --encoding-era all) on the 2517-file corpus: baseline 4980ms -> post-change 5003ms (+0.46%, well within run-to-run noise).lookup_encodingis@functools.cache-decorated, so the expanded alias count only matters for first-call cost on previously-unseen names, and that's at most 24us in the worst case.Fixes the class of bug that slipped through in 2a54c68, not any specific new user report.
🤖 Generated with Claude Code