Repository navigation
Conversation
This was referenced Oct 22, 2026
…sis stubs Hypothesis lets a test pin a known-failing input with `@example(...).xfail(reason=..., raises=...)`, which is the natural way to record a bug found by a property test without taking the input out of the strategy or writing a separate expectedFailure test. The stub `example` is a plain decorator function, so any test using `.xfail()` or `.via()` failed to import when Hypothesis is not installed. This turns the stub into a class with those two methods. An expected failure is strict, as in Hypothesis: when the examples are replayed the test must raise one of the `raises` exceptions for that input, and an unexpected pass fails the test. `.via()` is a no-op, as it is upstream.
The existing codec tests check each codec against fixed strings, and test_basics runs the incremental and stream interfaces on "abc123" only. This adds test_codecs_property.py, which runs every codec in all_unicode_encodings through the incremental codecs (any chunking, getstate/setstate round trips), StreamReader/StreamWriter, the error handlers (backslashreplace decoded back through unicode_escape, xmlcharrefreplace through html.unescape, surrogateescape round trips), charmap_decode/charmap_encode against a small oracle, name normalization in codecs.lookup, and the transform codecs, all on generated input and compared against the stateless codec functions. Six divergences turned up, each recorded as an expectedFailure with the minimal input as an explicit example and carved out of the broad tests only where the comments say so: - euc_jp, euc_jis_2004, euc_jisx0213 and gb18030 incremental decoders drop input after an incomplete trailing sequence under surrogateescape. - The ISO-2022 encoders emit U+001B as a bare ESC (iso2022_kr also SO and SI) which their decoders reject or misread. - StreamReader.read() never decodes with final=True, so an incomplete trailing character is silently dropped. - The punycode incremental codecs transform each chunk on its own. - euc_kr encodes U+3164 to the Hangul filler bytes it cannot decode alone. - iso2022_jp_3 decodes plane 2 0x7D3B with the 2004 table because the 2000 emulation is followed by "if" rather than "else if". The by-design differences (BOM-less utf-16/32 incrementally, utf-7 and lone surrogates, the many-to-one compatibility mappings in cp932, cp950, euc_jp and shift_jis, surrogateescape on non-ASCII-compatible codecs) are documented in the module rather than hidden in the strategies. With Hypothesis the file runs in about five seconds; 3000 examples per test pass as well. Without it the explicit examples replay through the stubs.
…xpected failures The StreamReader truncation is pythongh-56717, open since 2011 with the same diagnosis, and the euc_kr filler asymmetry was closed as not a bug in pythongh-101863; the punycode chunking is mentioned in pythongh-64331.
…ailures Previously the expectedFailure tests drew from the same broad strategies as the properties they were carved out of, so most of their generated inputs passed and only the explicit examples made them fail. Each one now generates just the known-failing shape (a trailing incomplete three- or four-byte sequence, a trailing ESC/SO/SI, a filler or the 0x7D3B character between printable ASCII, a chopped multi-byte character, two non-empty ASCII punycode chunks), so the whole test flips to passing once the bug is fixed. A search for a passing input in each strategy finds none.
…xpected failure
…mples Previously each known bug had its own expectedFailure test generating only the failing inputs. Hypothesis already has a way to say that: an `@example(...).xfail(reason=..., raises=...)` on the test the input belongs to, which is strict in both Hypothesis and (after pythongh-160356) the stubs. Each bug is now a few such examples on test_decode_in_chunks, test_encode_in_chunks, test_encode_decode or test_reader_matches_decode, and the carve-outs in the strategies point at them. Also from review: - MULTIBYTE_ENCODINGS is an issubclass check against _multibytecodec.MultibyteIncrementalDecoder rather than a look at the repr of the MRO. - Only raw_unicode_escape is listed out of the surrogateescape codecs by hand, and for the right reason (a truncated \u escape raises); unicode_escape and utf_7 already fail _lossless_bytes. - xmlcharrefreplace text leaves out the control characters and noncharacters whose references html.unescape drops, not only "&" and the C1 range. - The charmap encoding tables leave out U+FFFE, which charmap_build treats as undefined, and the two encode tests share the strategy. - test_unicode_escape_with_surrogates compared an expression with itself; it now checks the unicode_escape round trip and the raw_unicode_escape round trip on text without backslashes. - StreamReader.read(size) takes a byte count, so the reads are no longer expected to return at most that many characters (big5hkscs decodes one sequence to two). test_reader_read_chars covers the chars argument of the pure Python reader, which the multibyte readers do not have, and test_reader_matches_decode compares the reader with the stateless decoder under every error handler.
pganssle
force-pushed
the
codecs-hypothesis
branch
from
October 22, 2026 21:54
ccb8596 to
ab96b89
Compare
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.
Part of #107862, on top of #160356 (
example.xfail()in the stubs). This addsLib/test/test_codecs_property.py, Hypothesis property tests for the codec machinery, in the same shape as #159960 for the compression modules.The existing codec tests check each codec against fixed strings, and
test_basicsruns the incremental and stream interfaces on"abc123"only. The new file runs every codec inall_unicode_encodingson generated input and compares against the stateless codec functions:getstate()/setstate()round trips (the conditions documented forIncrementalDecoder.getstate());StreamWriterthenStreamReaderwith arbitrary write and read sizes, the reader against the stateless decoder under every error handler, thecharsargument of the pure Python reader, andreadline()/read()interleaving againststr.splitlines();backslashreplaceoutput decoded viaunicode_escape,xmlcharrefreplaceviahtml.unescape,surrogateescaperound trips on the ASCII-compatible single-byte codecs;charmap_decode/charmap_encodewith generated tables against a small oracle, andcharmap_buildmaps against equivalent dicts;escape_encode/escape_decode, the escape codecs with surrogates, the bytes transform codecs androt_13, and name normalization incodecs.lookup.With Hypothesis the file takes about five seconds; 3000 examples per test also pass. Without it the explicit examples replay through the stubs (23 tests in both modes).
The by-design differences are written down in the module rather than hidden in the strategies: BOM-less utf-16/32 is rejected incrementally (tested as such), utf-7 accepts lone surrogates and closes its shift sequence at every chunk, cp932/cp950/euc_jp/shift_jis fold a few compatibility characters, idna applies nameprep, raw_unicode_escape leaves backslashes alone, and
surrogateescapeonly round-trips on codecs where every byte decodes injectively.Six real divergences turned up. Each is pinned as
@example(...).xfail(reason="gh-...")on the test it belongs to, so the test fails if the input starts passing, and the input is carved out of the generated data only where the comments say so:euc_jp,euc_jis_2004,euc_jisx0213andgb18030drop input after an incomplete trailing sequence undersurrogateescape:b'\x806'.decode('gb18030', 'surrogateescape')is'\udc806', the incremental decoder gives'\udc80'(also throughTextIOWrapper). Filed aseuc_jpandgb18030incremental decoders drop input after an incomplete trailing sequence undersurrogateescape#160307.'\x1b\xa0'.encode('iso2022_jp_2004').decode('iso2022_jp_2004')is'\x1b\x1b$(Q)"');iso2022_krdoes the same with SO and SI. Filed as ISO-2022 encoders accept U+001B (andiso2022_krU+000E/U+000F) although the encoding cannot represent them #160302.codecs.StreamReader.read()never decodes withfinal=True, so an incomplete trailing character is silently dropped:codecs.getreader('utf-8')(io.BytesIO(b'ab\xe2')).read()is'ab', with no error in strict mode and no replacement character in replace mode. Filed ascodecs.StreamReader.read()silently drops an incomplete character at the end of the stream #160309 (same mechanism as codecs.StreamReader doesn't pass final=1 to the UTF-8 codec #56717, open since 2011).'a'then'b'encodes tob'a-b-', notb'ab-').test_codecsalready lists punycode as "broken in stateful mode". Many incremental codecs don’t handle fragmented data #64331 mentions it in passing. Filed as The punycode incremental encoder and decoder transform each chunk separately #160308.'\u3164'.encode('euc_kr')gives the Hangul fillerb'\xa4\xd4', whicheuc_krcannot decode on its own (cp949can). This was euc_kr char '0x3164' decode('ksx1001') cause UnicodeDecodeError #101863, closed as not a bug; the test records the asymmetry either way.iso2022_jp_3encodes U+9B1D but decodes the result as U+9B1C: injisx0213_2000_2_decoderthe 2000-table emulation macro is followed byifinstead ofelse if, so the table lookup overwrites it. Filed asiso2022_jp_3encodes U+9B1D but decodes the result as U+9B1C #160306.