Repository navigation
validate-family.py: the holder's gate escapes what its stdout cannot encode, so it passes under Latin-1 where it ended in a traceback, and 45 rows in 11 test files that cannot judge under a one-byte locale skip there on their own premise (#1214) - #1258
brettheap wants to merge 2 commits into
Conversation
…encode, so it passes under Latin-1 where it ended in a traceback, and 47 rows in 12 test files that cannot judge under a one-byte locale skip there on their own premise (#1214) Of the 50 tests #1214 counts as failing under a Latin-1 locale (built with `localedef -c`; `LOCPATH`, `LC_ALL=en_US.ISO-8859-1`, `PYTHONUTF8=0`), 3 meet a tool defect and 47 cannot judge there. - templates/family-root/scripts/validate-family.py: `main` calls `repo_shape.escape_unencodable_stdout()` first, as `siblings.py` and both roots' `bootstrap.py` do (#391, #454). Each member's note ends its digest in U+2026, which a Latin-1 or ASCII stdout cannot hold, so the gate of a holder that passed ended in a `UnicodeEncodeError` traceback, exit 1. That was the [add], [bump] and [remove] rows of `test_each_act_refuses_the_tracking_branch_then_lands_on_a_branch`. - tests/test_family.py: `test_the_gate_passes_on_a_stdout_that_cannot_hold_ its_notes` runs the gate with `PYTHONIOENCODING=latin-1`. - tests/conftest.py: `skip_where_the_locale_does_not_spell_it_as_utf_8(text, premise)` and four premise sentences (`A_BYTE_READ_AS_A_SURROGATE`, `A_NAME_HELD_AS_UTF_8`, `PRINTED_AS_ITSELF`, `A_NAME_READ_AS_UTF_8`). It asks whether `os.fsencode` can spell the text, gives UTF-8's bytes, and whether `os.fsdecode` reads them back as the text. It is asked only of the text a row's premise is about, never inside `skip_where_a_name_cannot_be_not_utf_8`, so a branch whose bytes are not UTF-8 still judges in every locale (#1224's N1). - 18 rows the test process itself cannot run under Latin-1 (a name such as U+65E5 U+672C or U+200D it cannot make or pass) skip by `skip_where_the_environment_cannot_carry`: tests/test_stdout_encoding.py (10), tests/test_adopt_pins.py (5), tests/test_pin_source_unreadable.py (3). - 29 rows where the tool answers rightly for the bytes Latin-1 gives, and the row expects UTF-8's, skip by the new helper: tests/test_adopt_pins.py (9), tests/test_adopt_template_values.py (4), tests/test_clone_url_bytes.py (4), tests/test_pin_source_unreadable.py (3), tests/test_bump_leg_branch_and_push_line.py (2), tests/test_bootstrap_leg_moves.py (2), tests/test_family_tracking_branch.py, tests/test_update_shape_apply_lines_and_headers.py, tests/test_adopt_spelled_names.py, tests/test_family_member_branch_not_utf8.py (its [trailing-no-break-space] row only) and tests/test_family_head_by_hand_bytes.py (1 each). - tests/test_non_utf8_name_callers.py: `windows_names_it` emulates Windows' `os.fsdecode` too (UTF-8 with `surrogatepass`), so its bidi-override row gets Windows' answer to the locale's question that `skip_where_this_system_cannot_name` now asks, and not this run's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideImplementationEscape unencodable stdout in the family holder’s validator before argument parsing, matching the other entry points so a passing holder does not end in a UnicodeEncodeError traceback.
TestsAdd coverage for the validator’s Latin-1 stdout behavior and locale-aware premise guards, then apply those guards to rows that cannot judge UTF-8-specific behavior under a one-byte locale; emulate Windows filesystem decoding and test the helper’s UTF-8, Latin-1, and ASCII answers.
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
RV1258: independent review of #1258 at Lane 4's independent adversarial review (task Bench
1. The fix is real
2. Over-skip: each of the 47, by premiseRead from each row's code and from its failure on main under Latin-1 with the
3. Counts by test idThe gate, 35 files (the body's 27,
Main moved while the gate ran, to 4.
|
| Mutant | Latin-1 | UTF-8 (CI's locale) |
|---|---|---|
| A1 the first question takes an unspellable text as UTF-8's bytes | survives (as baseline) | - |
| A2 the second question dropped | survives | - |
| A3 the third question dropped | killed: +11 failed, the 11 A_BYTE_READ_AS_A_SURROGATE rows |
- |
| A4 only the third question left (first two deleted) | survives; under ASCII survives too | - |
A5 the second question's UTF-8 bytes made with surrogatepass |
survives | survives: 83 passed, 11 skipped, 0 failed (the 11 A_BYTE rows skip silently) |
| A6 the third question inverted | killed: +16 failed | killed: 5 callers rows fail |
| B1 the bidi guard dropped | killed: +6 failed (4 + 2 bidi rows) | - |
| B2 the locale question also asked of a name that is not UTF-8 | killed: 4 callers rows fail (and the 4 [not-utf8] rows skip) |
- |
| C1 the member guard asked of every branch | survives: [not-utf8] passed to skipped, nothing fails |
- |
| C2 the member guard inverted | killed: [trailing-no-break-space] fails |
- |
D1 the escape_unencodable_stdout() call dropped |
killed: +4 failed | killed: the new row |
| D2 the call moved to after the notes print | killed: +4 failed | killed: the new row |
D3 the call moved to after parse_args |
survives (equivalent today: the parser's text is ASCII) | survives |
E1 windows_names_it without the os.fsdecode emulation (callers file alone) |
killed: 1 failed | survives: 36 passed |
A1, A2 and A4 show that in the three locales measured the third question alone decides; the first two act only under an emulated Windows (N2). A5 is S2.
6. Merge-trees
git merge-tree --write-tree of the head, each clean: against origin/main 5926ccca; #1209 13a2c58c; #1185 d193617c; #1154 8a782833; #1251 825923c7; #1198 d38e4e36; and the scratch merge against each of those five, clean too. Main moved 9 commits since the body's bd3e9c14, one of them in validate-family.py; in the merged file main's first statement is still the call. Main + this head + #1209 (local 80e44af7), the five files both touch or share (test_non_utf8_name_callers.py, test_non_utf8_name_helpers.py, test_stdout_encoding.py, test_clone_url_bytes.py, test_adopt_pins.py): UTF-8 287 passed and 1 skipped; Latin-1 221 passed, 67 skipped, 0 failed.
Findings
S1 (SHOULD): the guard on test_a_utf8_source_directory_still_adopts_with_local_remotes hides #1247 under a false reason, and should not stay.
- On main under Latin-1 with CI's pip launcher the row PASSES:
executeadopts the source undercaf<0xE9>. The skip now given says "held as other bytes it is a name that is not UTF-8, and the tool names it as one". The tool names nothing; it adopts. It is the only row this PR takes from passed to skipped. - With the
-Elauncher it is the one guarded row whose failure on main is a tool traceback: filter-repo'sUnicodeEncodeErroron U+FFFD, adopt-project.py: under a one-byte locale such as Latin-1,executestops extracting the spec leg of a source whose path holds U+00E9, becausegit filter-repoprints the clone's origin url as U+FFFD to a stdout that cannot hold it #1247. tests: 50 tests in 12 files fail under a Latin-1 locale before and after #1209 (ten of them rows oftest_stdout_encoding.py), and whether each is a test that cannot judge under a one-byte locale or a tool defect is not measured (W1196's leftovers; follows #1196) #1214 sends such a row to "a defect, and gets its own issue", which adopt-project.py: under a one-byte locale such as Latin-1,executestops extracting the spec leg of a source whose path holds U+00E9, becausegit filter-repoprints the clone's origin url as U+FFFD to a stdout that cannot hold it #1247 is; it does not ask for a skip. - The body plans to drop the guard when adopt-project.py:
executeunsets the leg clone'sremote.origin.urlbeforegit filter-repo, so under a Latin-1 locale a source under a directory named with the byte 0xE9 is extracted, where filter-repo died printing that url as U+FFFD, and every branch of the source is still rewritten as before (#1247) #1251 lands ("whichever of the two PRs lands second drops this PR's premise guard"). A guard planned to go when a tool fix lands is not a premise guard; and the plan lives only in two PR bodies, while adopt-project.py:executeunsets the leg clone'sremote.origin.urlbeforegit filter-repo, so under a Latin-1 locale a source under a directory named with the byte 0xE9 is extracted, where filter-repo died printing that url as U+FFFD, and every branch of the source is still rewritten as before (#1247) #1251 does not touch this file. - Change: drop the guard and the
A_NAME_HELD_AS_UTF_8import it alone uses intests/test_adopt_template_values.py. Under the pip launcher the row passes under Latin-1, so the 50 still give 0 failed. Under-Eit fails on adopt-project.py: under a one-byte locale such as Latin-1,executestops extracting the spec leg of a source whose path holds U+00E9, becausegit filter-repoprints the clone's origin url as U+FFFD to a stdout that cannot hold it #1247 alone until adopt-project.py:executeunsets the leg clone'sremote.origin.urlbeforegit filter-repo, so under a Latin-1 locale a source under a directory named with the byte 0xE9 is extracted, where filter-repo died printing that url as U+FFFD, and every branch of the source is still rewritten as before (#1247) #1251 lands, and the two PRs no longer need to know about each other.
S2 (SHOULD): no row CI runs holds the new helper's answers, so a plausible slip skips 11 rows in CI and nothing goes red.
- Mutant A5 (
text.encode("utf-8", "surrogatepass")for the second question's bytes) makes everyA_BYTE_READ_AS_A_SURROGATErow skip under UTF-8: 83 passed, 11 skipped, 0 failed, against 94 passed.conftest.py's ownskip_messagedocstring names this hazard: "a helper that skipped where it should not would pass". - Only the names
skip_where_this_system_cannot_nameasks it of (ASCII controls and U+202E) have CI-run witnesses, the callers rows that kill A6. No row witnessesA_BYTE_READ_AS_A_SURROGATE,A_NAME_HELD_AS_UTF_8orPRINTED_AS_ITSELFtext. - Change: add rows for
skip_where_the_locale_does_not_spell_it_as_utf_8beside the other helpers' (tests/test_non_utf8_name_helpers.py), each throughskip_messagewithos.fsencodeandos.fsdecodeemulated:- UTF-8 returns None for
'gl\udcffass','feat\xa0','\u2026'and'\u202eevil'; - Latin-1 skips with the premise in the reason, once by each question that can answer there;
- ASCII returns None for
'\udcff'and skips'\u2026'.
A5 must then fail in CI's locale.
- UTF-8 returns None for
N1 (NIT): nothing holds that the call is main's FIRST statement, as the comment says (D3 survives). test_bootstrap_spelling.py::test_main_escapes_stdout_before_it_prints_or_parses_anything holds it for both roots' bootstrap.py. Change: add templates/family-root/scripts/validate-family.py to its parametrization.
N2 (NIT, reasoned, not run on Windows): under Windows' codecs the helper's second question skips a surrogate byte, with a reason ("here it is a character, and the tool takes it as one") that is false there. A Windows command line is UTF-16 and hands U+DCE9 to the child as itself. Measured by emulation (skip_message, with os.fsencode/os.fsdecode patched to Windows' codec; P stands for the premise):
--- windows (utf-8 surrogatepass)
A_BYTE 'Jos\udce9' -> SKIP: this locale's encoding (utf-8) writes 'Jos\udce9' as b'Jos\xed\xb3\xa9', not as UTF-8's b'Jos\xe9': P
The rows with no POSIX-only mark are test_plan_refuses_a_flag_no_file_can_hold's two U+DCE9 rows and test_plan_and_check_say_the_same_sentence. The third question skips there too (Windows' os.fsdecode raises on the byte 0xE9, read as None), so no reordering of the three cures it. Change: ask the locale question only off Windows, where a command line is bytes, or say in the docstring and the reason what Windows answers.
N3 (NIT): test_surrogateescape_reads_the_issues_branch_as_caf_and_u_dce9 now ends SKIPPED under Latin-1, though its main claim, Python's own surrogateescape read, ran and passed first. Change: narrow its existing if to the case its docstring names, by adding and os.fsdecode(b"\xe9") == chr(0xDCE9) after the os.fsencode half (which keeps Windows, where that os.fsdecode raises, from asking), and drop the guard; the row then passes under Latin-1.
Lane: openreposhape-4 (openRepoShape-4)
…does_not_spell_it_as_utf_8` are held under emulated UTF-8, Latin-1 and ASCII codecs, `validate-family.py` is held to call `escape_unencodable_stdout()` first, and the two rows that can judge under Latin-1 run there (#1214) RV1258's review of #1258 (S1, S2, N1, N3): - tests/test_adopt_template_values.py (S1): the premise guard on `test_a_utf8_source_directory_still_adopts_with_local_remotes` is gone, with the `A_NAME_HELD_AS_UTF_8` import it left unused there. The row passes under Latin-1 with the pip-installed git-filter-repo; with a launcher started as `python -E` it fails on #1247's traceback until #1251 lands, which is the row's own failure and not a premise it cannot judge. #1258 no longer carries a guard for #1251 to drop. - tests/test_non_utf8_name_helpers.py (S2): 20 rows hold `skip_where_the_locale_does_not_spell_it_as_utf_8` under `os.fsencode`, `os.fsdecode` and `sys.getfilesystemencoding` emulated as UTF-8, Latin-1 and ASCII have them: 15 for the question that skips each of five texts in each locale (none, "cannot spell", "writes", "reads") and for the reason's encoding, ending and ASCII, and 5 that hold the emulation to this run's real locale. A helper that built UTF-8's bytes with `surrogatepass`, or dropped, reordered or inverted a question, now fails them under CI's UTF-8 locale. - tests/test_bootstrap_spelling.py (N1): the first-statement row also reads the holder's `validate-family.py`, so a call moved after `parse_args` fails it. - tests/test_family_head_by_hand_bytes.py (N3): `test_surrogateescape_reads_the_issues_branch_as_caf_and_u_dce9` asks `os.fsdecode(b"\xe9")` in its `if`, in place of the helper, so under Latin-1 it passes where it skipped; under UTF-8 and ASCII it asserts as before. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
F1258 fix round on this PR for RV1258's NOT READY review (#1258 (comment)). New head: Findings and fixes
Counts (scratch merge of this head with
Main moved while this was gated ( Mutants (each restored, sha256 checked)
Merge-trees ( Also found, outside this issue (left, named in the body): 5 rows of The body and title are updated: the counts (45 rows in 11 files skip, 2 run), the table, the second commit, the runs, and "Left". Lane: openreposhape-4 (openRepoShape-4) |
|
|
RD1258: independent delta review of #1258 at Scope: Everything below was run with CI's git-filter-repo pin (2.47.0, the pip launcher with no What I measuredS1 (the guard on the source-directory row is gone). The guard and the
S2 (the 20 new rows).
N1. N3. MutantsEach was made in a scratch worktree at the head and restored afterwards, and every file's sha256 was checked equal to the head's. Run: UTF-8,
The same helper mutants over the 50 under Latin-1 (pip): A1, A2, A4 and A5 give 5 passed, 45 skipped (they survive there, as RV1258 found, and only the new rows tell them apart); A3 and A6 give 10 failed, 5 passed, 35 skipped. No new over-skip (29-file gate, by test id)The 29 files are the body's 27 plus
The 50, by test id (the ids F1258 used, the same 50 as #1214):
The body and the title
Merge-trees
Findings
Not graded: 4 to 5 Lane: openreposhape-4 (openRepoShape-4) |



Closes #1214
Goal
#1214 counts 50 tests in 12 files that fail under a Latin-1 locale before and after #1209, and asks of each whether it is a test that cannot judge under a one-byte locale, or a tool that misbehaves there. All 50 are settled here:
scripts/validate-family.pyended in aUnicodeEncodeErrortraceback, exit 1, on any stdout that cannot hold U+2026, when the holder passed.test_a_utf8_source_directory_still_adopts_with_local_remotes(it passes with the pip-installed git-filter-repo, and fails with apython -Eone on adopt-project.py: under a one-byte locale such as Latin-1,executestops extracting the spec leg of a source whose path holds U+00E9, becausegit filter-repoprints the clone's origin url as U+FFFD to a stdout that cannot hold it #1247's tool defect; see "Left") andtest_surrogateescape_reads_the_issues_branch_as_caf_and_u_dce9(itsifasks the one thing its assertion needs). Both came out of RV1258's review of the first commit.Under Latin-1 with the pip-installed git-filter-repo, none of the 50 fails on this head. Under UTF-8 every row keeps its outcome, and the rows added pass.
The classification is W1214's: lane 4's first writer of #1214, who read each test's code and stopped at a checkpoint with half the guards written and nothing run. This branch finishes the guards, runs every gate below, and measures each row. RV1258 reviewed the first commit; the second answers it (next section).
The second commit
RV1258 reviewed the first commit,
c8641c691(NOT READY: two SHOULDs, three NITs, no MUST; #1258 (comment)). The second commit,cd9f3cfd1, answers it on top of the first, with no rebase. It changes four test files and no tool:test_a_utf8_source_directory_still_adopts_with_local_remotesis gone, and so is theA_NAME_HELD_AS_UTF_8import it alone used intests/test_adopt_template_values.py.executeadopts the source.python -Eit fails on adopt-project.py: under a one-byte locale such as Latin-1,executestops extracting the spec leg of a source whose path holds U+00E9, becausegit filter-repoprints the clone's origin url as U+FFFD to a stdout that cannot hold it #1247's tool defect (git-filter-repo'sUnicodeEncodeErroron U+FFFD) until adopt-project.py:executeunsets the leg clone'sremote.origin.urlbeforegit filter-repo, so under a Latin-1 locale a source under a directory named with the byte 0xE9 is extracted, where filter-repo died printing that url as U+FFFD, and every branch of the source is still rewritten as before (#1247) #1251 lands. That is a failure of the tool, and tests: 50 tests in 12 files fail under a Latin-1 locale before and after #1209 (ten of them rows oftest_stdout_encoding.py), and whether each is a test that cannot judge under a one-byte locale or a tool defect is not measured (W1196's leftovers; follows #1196) #1214's rule sends such a row to an issue of its own, not to a skip.executeunsets the leg clone'sremote.origin.urlbeforegit filter-repo, so under a Latin-1 locale a source under a directory named with the byte 0xE9 is extracted, where filter-repo died printing that url as U+FFFD, and every branch of the source is still rewritten as before (#1247) #1251 are now independent of each other. This PR carries no guard for adopt-project.py:executeunsets the leg clone'sremote.origin.urlbeforegit filter-repo, so under a Latin-1 locale a source under a directory named with the byte 0xE9 is extracted, where filter-repo died printing that url as U+FFFD, and every branch of the source is still rewritten as before (#1247) #1251 to drop, so the earlier plan, that whichever of the two lands second drops this PR's guard, is void. Neither waits for the other.tests/test_non_utf8_name_helpers.pygets 20 rows, written as that file writes its Windows rows:os.fsencode,os.fsdecodeandsys.getfilesystemencodingare replaced for one test by UTF-8, Latin-1 and ASCII as POSIX has them.osas with the emulation of its file system encoding.surrogatepassinstead ofsurrogateescape) now fails 3 rows under CI's UTF-8 locale; before, it made 11 rows skip silently. The mutants that survived, A1, A2 and A4, now fail 5, 1 and 6 rows (see the mutants below).tests/test_bootstrap_spelling.pyalso readstemplates/family-root/scripts/validate-family.py, so RV1258's mutant D3 (theescape_unencodable_stdout()call moved afterparse_args) fails it.test_surrogateescape_reads_the_issues_branch_as_caf_and_u_dce9no longer asks the helper. Itsifalso asksos.fsdecode(b"\xe9") == chr(0xDCE9), so under Latin-1 the row runs its first assertion and passes where it skipped, and under UTF-8 and ASCII it asserts as before.The 50, by file
Kinds:
UnicodeEncodeErrorbefore the tool runs. Guard: the existingskip_where_the_environment_cannot_carry.skip_where_the_locale_does_not_spell_it_as_utf_8(text, premise), asked only of the text the row's premise is about.test_stdout_encoding.pycomes first, as #1214 asks. 18 a1 + 27 a2 + 3 b + 2 c = 50.tests/test_stdout_encoding.pytest_plan_reports_every_name_whatever_its_stdout_can_encode(3),test_plan_on_the_posix_default_stdout,test_every_tool_exits_alike_whatever_its_stdout_can_encode(6)skip_where_the_environment_cannot_carry, inplan_argumentsand in each of the six builders. (bump-leg-dry-runwrites the name as bytes and passes, so it has none.)tests/test_adopt_pins.py[zwj]rowscopytree.skip_where_the_environment_cannot_carry(name)tests/test_adopt_pins.py[not-utf-8...]rowsplangoes on where UTF-8 gives U+DCFF and a refusal.A_BYTE_READ_AS_A_SURROGATE, where the name holds U+DCFFtests/test_adopt_pins.pypinorwriteline\u2026, as #214 designed.PRINTED_AS_ITSELF, just before the line is comparedtests/test_pin_source_unreadable.py[not-ascii-*]-missingcannot go on the command line.skip_where_the_environment_cannot_carrytests/test_pin_source_unreadable.py[not-utf-8-*]miss<0xFF>ingreaches the tool as text, and is printed as itself, not as\udcff.A_BYTE_READ_AS_A_SURROGATEtests/test_family_tracking_branch.pytest_each_act_refuses_the_tracking_branch_then_lands_on_a_branch[add],[bump],[remove]validate(holder). The holder'svalidate-family.pyprintsmembers/X: tree digest recomputes (<12 hex>...), ending in U+2026, and never escaped its stdout, so it ended in a traceback, exit 1.tests/test_family_tracking_branch.pytest_a_branch_ending_in_a_no_break_space_is_pushed_as_itselfgit switch -c feat<U+00A0>writes the single byte 0xA0, so the branch is not UTF-8, and the tool rightly refuses it (#765).A_NAME_HELD_AS_UTF_8tests/test_adopt_template_values.py--elected-byand--referencerows oftest_plan_refuses_a_flag_no_file_can_holdwhose value holds U+DCE9, andtest_plan_and_check_say_the_same_sentenceplantakes.A_BYTE_READ_AS_A_SURROGATE(in the flag test, only where the value holds U+DCE9)tests/test_adopt_template_values.pytest_a_utf8_source_directory_still_adopts_with_local_remotescaf<U+00E9>directory is the single byte 0xE9, andexecuteadopts the source all the same. It fails only where git-filter-repo is started aspython -E, on #1247's tool defect (see "Left"), which is an issue of its own and not a premise to skip on.tests/test_clone_url_bytes.py[bidi-override-*]rowsA_NAME_READ_AS_UTF_8, inskip_where_this_system_cannot_nametests/test_bump_leg_branch_and_push_line.pyfeat<U+00A0>andmain<U+00A0>hold the single byte 0xA0.A_NAME_HELD_AS_UTF_8tests/test_bootstrap_leg_moves.pybootstrap.pyprints U+2014 escaped on a Latin-1 stdout.PRINTED_AS_ITSELF, just before the asserttests/test_update_shape_apply_lines_and_headers.pyfeat<U+00A0>.A_NAME_HELD_AS_UTF_8tests/test_adopt_spelled_names.pycaf<U+00E9>.mdas 0xE9, so git holds a name that is not UTF-8, and the C-locale child printscaf\udce9.md, notcaf\xe9.md.A_NAME_HELD_AS_UTF_8, after its C-locale probetests/test_family_member_branch_not_utf8.py[trailing-no-break-space]_member_headreads the byte 0xA0 as U+DCA0, which is the branch git wrote.A_NAME_HELD_AS_UTF_8, only where the branch is notNOT_UTF_8_BRANCH:[not-utf8]passes under Latin-1 and still runs theretests/test_family_head_by_hand_bytes.pyif,os.fsencode(chr(0xDCE9)) == b"\xe9", holds under Latin-1 too, and its assertion,os.fsdecode(LATIN_1) == branch, holds only where the locale reads 0xE9 as U+DCE9 (UTF-8, ASCII). Its first assertion holds in every locale.ifalso asksos.fsdecode(b"\xe9") == chr(0xDCE9), so under Latin-1 the row runs its first assertion and passesWhat changed, file by file
18 files: one tool,
tests/conftest.py, and 16 test files.templates/family-root/scripts/validate-family.py(the tool):main's first statement is nowescape_unencodable_stdout(), imported fromrepo_shape, as insiblings.pyand both roots'bootstrap.py(templates/assembly-root/scripts/bootstrap.py and templates/family-root/scripts/bootstrap.py:mainprints the manifest'snamewith a plainprintbefore any step runs, soname: "a\ud83db"(a lone surrogate) endsmake bootstrapin aUnicodeEncodeErrortraceback, exit 1 (make: 2), and the validator that names the problem is never reached; a valid name the stdout cannot encode ends the same way #391, family-root siblings.py:make siblings,parkandresumeprint the family'snamewith a plainprint, soname: "a\ud83db"ends each in aUnicodeEncodeError, and the literal dash ends every name underPYTHONIOENCODING=ascii#454).spelled(ec4a7a0), which the file already imports, and it is not intest_pinned_copy_text.py'sNEWER_THAN_A_KEPT_LIBRARY.init, so there is no static digest to move.tests/test_family.py: one new row,test_the_gate_passes_on_a_stdout_that_cannot_hold_its_notes. It runs the holder's gate withPYTHONIOENCODING=latin-1and expects exit 0, no traceback, and the two notes ending in\u2026).tests/conftest.py:skip_where_the_locale_does_not_spell_it_as_utf_8(text, premise), asks three questions, each throughosat call time: whetheros.fsencodecan spell the text, whether it gives UTF-8's bytes, and whetheros.fsdecodereads those bytes back as the text.A_BYTE_READ_AS_A_SURROGATE,A_NAME_HELD_AS_UTF_8,PRINTED_AS_ITSELFandA_NAME_READ_AS_UTF_8. Every skip reason is ASCII.skip_where_a_name_cannot_be_not_utf_8. A branch whose bytes are not UTF-8 is one in every locale, so those rows still judge under Latin-1, and this takes none of tests/conftest.py: follow-up of PR #1209 (#1196) from its review: the helper's locale skip also catches 80 rows that can judge the tool under a one-byte locale, and four smaller gaps (a Windows failure, an unheld order, a triple predicate, a docstring) #1224's N1 over-skip.tests/test_family_head_by_hand_bytes.pyhas none: itsifis narrowed (N3).tests/test_non_utf8_name_helpers.py(the second commit): 20 rows holdskip_where_the_locale_does_not_spell_it_as_utf_8under emulated UTF-8, Latin-1 and ASCII (S2, above).tests/test_bootstrap_spelling.py(the second commit): the first-statement row takesvalidate-family.pyas a third file (N1).tests/test_non_utf8_name_callers.py:windows_names_itnow emulates Windows'os.fsdecodetoo (UTF-8 withsurrogatepass,windows_fsdecode).skip_where_this_system_cannot_nameasked this run's real Latin-1os.fsdecodeunder an emulated Windows.test_where_windows_holds_a_bidi_override_the_name_check_does_not_skipthen failed under Latin-1 (1 failed, 35 passed), where it passes on main. With the emulation, the file gives 36 passed in both locales.bidi-overriderow ... runs on Windows as it does here") assumes.Runs
Environment. One runner for every run:
/usr/bin/python3 -m pytest -p no:cacheprovider(Python 3.12.3).ghfirst on PATH, a bogusGH_TOKENand an emptyGH_CONFIG_DIR.GIT_CONFIG_GLOBALan empty file,GIT_CONFIG_NOSYSTEM=1, the test identity, andEMAILset.GIT_ALLOW_PROTOCOL,GIT_SSH_COMMANDand theLANES_*variables unset.git filter-repo --version:a40bce548d2c, CI's pin, pip-installed in a venv). An earlier pair of head runs without it skipped 58 adoption rows, and is not counted.python -E, so a test's ownPYTHONIOENCODINGreaches it. The-Ecomparison is the last paragraph of this section.Locales.
LC_ALL=C.UTF-8.localedef -c, as adopt-project.py: under a one-byte locale such as Latin-1, a clonecheckmakes again for a plan's clone record that neither spelling names is compared with both, so it is kept and the next run reads it rather than cloning again (#1112) #1153'stest_adopt_one_byte_locale.pybuilds one; thenLOCPATH,LC_ALL=en_US.ISO-8859-1,PYTHONUTF8=0. Python reportsiso8859-1and reads 0xFF as U+00FF.LC_ALL=C PYTHONUTF8=0 PYTHONCOERCECLOCALE=0.Round one's gate (the first commit,
c8641c691, on mainbd3e9c148, 27 files, F1214's runs): UTF-8 1520 passed and 1 skipped on main, 1521 and 1 on the head; Latin-1 123 failed, 1373 passed, 25 skipped on main, and 44 failed, 1404 passed, 74 skipped on the head. RV1258 re-ran it on 35 files (main5926ccca, merge103ada82) and got the same transitions. The gate below replaces it.The gate: 29 files, on a scratch merge. The merge is this head (
cd9f3cfd1) intoorigin/main46fe6ac19(local, never pushed, clean); the baseline is46fe6ac19itself. Both with the pip launcher.skip_where_a_name_cannot_be_not_utf_8oron_a_branch_that_is_not_utf_8, withtest_repo_hygiene.py(W1196's 24, less the two files tests/conftest.py and tests/test_adopt_plan_paths_spelled.py: under a one-byte locale such as Latin-1 the 0xff--work-dirrow skips and the two U+00E9 rows read the child's UTF-8, so none of the three fails on a tool that is right, and the bytes helper starts its child asrun_scriptdoes (#1196) #1209 adds, plustest_family_leg_path_by_hand_bytes.py), and four more that run the changed code:test_family.py,test_pinned_copy_text.py,test_update_shape_kept_helper.pyandtest_local_remote_dir_urls.py. They includetest_non_utf8_name_helpers.py.test_path_guard_hygiene.py(the second hygiene file) andtest_bootstrap_spelling.py.46fe6ac19-Elauncherbd3e9c148, round one)The 5 that pass under Latin-1 are the 3 tracking-branch rows (the tool fix),
test_a_utf8_source_directory_still_adopts_with_local_remotesandtest_surrogateescape_reads_the_issues_branch_as_caf_and_u_dce9.Main moved twice while this was gated, none of its commits touching a file of the gate or of this PR. The merge was run again on each: on
9729315f1the same 29 files, with identical outcomes by test id in both locales (1895 passed, 1 skipped; 49 failed, 1775 passed, 72 skipped); on801a7d6d0the 50 and the four files the second commit changes (test_non_utf8_name_helpers.py,test_bootstrap_spelling.py,test_family_head_by_hand_bytes.py,test_adopt_template_values.py). The 50 give the same as above in all three modes (UTF-8 50 passed; Latin-1 pip 0 failed, 5 passed, 45 skipped; Latin-1-E1 failed, 4 passed, 45 skipped), the four files give 385 passed under UTF-8, and 6 failed, 371 passed, 8 skipped under Latin-1: the 5test_bootstrap_spelling.pyrows andtest_execute_refuses_a_local_source_directory_that_is_not_utf8, all of which fail on main too.By test id, from main to the scratch merge:
test_non_utf8_name_helpers.py, the[validate-family]row of the first-statement test, and the first commit'stest_the_gate_passes_on_a_stdout_that_cannot_hold_its_notes. Nothing else changes: no new skip, no row from passed to failed. The one skip istest_stdout_encoding.py's Windows default-stdout row, on both.test_surrogateescape_reads_the_issues_branch_as_caf_and_u_dce9of the 50, and 28 other rows oftest_family.pywhose holder's gate ended in the same traceback.test_local_remote_dir_urls.py, which shareBAD_DIRECTORIES.--work-dirrow skips and the two U+00E9 rows read the child's UTF-8, so none of the three fails on a tool that is right, and the bytes helper starts its child asrun_scriptdoes (#1196) #1209 settles, the rows under "Left", and 5 rows oftest_bootstrap_spelling.py(see "Left").On a scratch merge with #1209 (main
46fe6ac19, #1209 at13a2c58cb, then this head; local, never pushed, both merges clean). #1209 shares four files with this PR:tests/conftest.py,tests/test_non_utf8_name_callers.py,tests/test_non_utf8_name_helpers.pyandtests/test_stdout_encoding.py. Run on 8 files: the three test files among those four, #1209'stest_adopt_plan_paths_spelled.pyandtest_run_script_encoding.py, andtest_adopt_template_values.py,test_family_head_by_hand_bytes.pyandtest_bootstrap_spelling.py. Against main plus #1209 alone:[validate-family]row), nothing else.test_bootstrap_spelling.pyrows, which fail on both. Of the 50, the 15 in these files give 13 skipped and 2 passed, none failed.The other 59 test files that name
validate-family(round one, on the first commit, UTF-8): 3374 passed, 28 skipped, 0 failed. The second commit changes no tool.The same launcher started as
python -E(W1247's copy of it with-Eadded, used read-only), Latin-1, the 50: on mainbd3e9c148, 50 failed, the 50 exactly as #1214 lists them. On the first commit, 0 failed. On this head, 1 failed, 4 passed, 45 skipped: the S1 row,test_a_utf8_source_directory_still_adopts_with_local_remotes, on #1247's traceback (UnicodeEncodeErroron U+FFFD ingit_filter_repo.py's_migrate_origin_to_heads, thenREFUSED extracting the spec leg). It stays red with that launcher until #1251 lands, and the other 49 rows are as with the pip launcher.The new rows, and mutants
Each mutant was made in a scratch worktree at the head and restored afterwards, with each file's sha256 checked. "UTF-8" and "Latin-1" name the locale of the run.
The second commit's rows. The helper's own rows run in CI's UTF-8 locale, with the three locales emulated.
test_non_utf8_name_helpers.pytests/conftest.py, which has no helpercannot import name 'skip_where_the_locale_does_not_spell_it_as_utf_8'surrogatepassbyte-e9row in each of the three locales. Before, the same mutant made 11 rows skip silently under UTF-8 and failed none (RV1258).iso8859-1andasciiforbidi-overrideandellipsis, andascii-e-acuteiso8859-1-e-acute(it reads the bytes back and says "reads", not "writes")iso8859-1-byte-e9utf-8alwaysstrictbyte-e9rows, and the row that holds the emulation to the real localeos.fsencodeof a str raises onlyUnicodeEncodeErrorvalidate-family.py'sescape_unencodable_stdout()moved afterparse_args[validate-family]rowvalidate-family.pyunder the head's tests[validate-family]row andtest_the_gate_passes_on_a_stdout_that_cannot_hold_its_notesiftaken out (main'sif, with no helper call)test_surrogateescape_reads_the_issues_branch_as_caf_and_u_dce9(it passes under UTF-8)On the 50 under Latin-1 the helper's mutants are as in round one, re-measured on this head: A3 gives 10 failed (it was 11; the N3 row no longer asks the helper), the first question unguarded gives 11 failed, and A1, A2 and A4 survive (5 passed, 45 skipped), because on Linux the third question alone decides there, as RV1258 found. Only the new rows tell them apart.
The first commit's rows (mutants made on
c8641c691, which the second commit does not change):test_family.pyvalidate-family.pyunder the head's tests (the new row on main)validate-family.py'smainwithout theescape_unencodable_stdout()call (import kept)[add],[bump],[remove]rows oftest_each_act_refuses_the_tracking_branch_then_lands_on_a_branchskip_where_this_system_cannot_nametest_clone_url_bytes.pyand the 2 oftest_local_remote_dir_urls.py, the sameBAD_DIRECTORIESwindows_names_itwithout theos.fsdecodeemulationtest_where_windows_holds_a_bidi_override_the_name_check_does_not_skiptest_the_member_head_is_the_branch_git_wroteasked of every branch (itsbranch != NOT_UTF_8_BRANCHdropped)[not-utf8]goes from passed (the head) to skipped; no row fails. A skip fails no run, so only a comparison of outcomes sees this one.Merge-trees
git merge-tree --write-treeof the head (cd9f3cfd1):origin/main46fe6ac19: clean;--work-dirrow skips and the two U+00E9 rows read the child's UTF-8, so none of the three fails on a tool that is right, and the bytes helper starts its child asrun_scriptdoes (#1196) #120913a2c58cb, tests/test_adopt_plan_paths_spelled.py: seven mutants of theplan written toandcloning ... intolines that no row killed now fail one, so a printable non-ASCII path prints as typed, ESC, CR and a non-UTF-8--work-dirare spelled, a relative--outis not made absolute, an--outthat ends in a space or is wrapped in backticks is spelled, andexecutespells the clone line asplandoes (#1167) #1185d193617c7, adopt-project.py:plan written toandcloning <repository> (read only) intoname the caller's--outand--work-dirwith_shown, as the other lines of the report name a path, so a bidi control, a line feed or a tab in either no longer reaches the terminal as it was typed (#1115) #11548a7828332, adopt-project.py:executeunsets the leg clone'sremote.origin.urlbeforegit filter-repo, so under a Latin-1 locale a source under a directory named with the byte 0xE9 is extracted, where filter-repo died printing that url as U+FFFD, and every branch of the source is still rewritten as before (#1247) #1251abeac66deand adopt-project.py:_pin_row_problemsays a pin field whose value is not text as_read_assays what the plan's reader read, "recordscommit:as the number 7", where it spelled it as Python does,commit: 7andTrue, socheckandexecutename it in the words the plan's other refusals use under the sameplan-pin-malformed(#1114) #1198d38e4e36d: each clean;HEADthis user cannot open, under.git/modules/<path>or in a.gitdirectory, is refusedmember-unreadableby name, where it endedmember-commit-unresolvablewith a fetch (#1256) #12639ef8f0ab5, validate-family.py: a member whose.gitfile this user cannot open, or whose git directory (its own.git, or the one its gitfile names) this user cannot search, is refusedmember-unreadablenaming that part, and no longermember-commit-unresolvable, which said the pinned commit was missing and prescribed a fetch (#1221) #12460deac3a65, update-shape.py: a line with U+00A0 or U+3000 in front ofshape:orfiles:is no block header, as it is none to the reader (#1220) #1231aca00dfd9, validate-family.py: the check for values git dies on reads the index's.gitmodulesbeside the work tree's and its stage remedies endgit add -- .gitmodules, an index file git cannot parse is refused, and an unmerged one is read as none (#1150) #12272d56c1906, adopt-project.py:plan's table pads each name by the columns a terminal gives it, not the code points it holds, so a wide or combining name'slegcolumn lines up underlegwith every other row's (#1034) #1178c4beba193, bootstrap.py, validate-family.py and siblings.py: a root or holder whose.gitthis user cannot read is refusedgit-repo-unreadable, exit 2, not a traceback ornot-a-git-repo, and the doctor'smembersrow names it once, never drift (#1148) #11722eabec8e0and adopt-project.py:checkandexecuteask a plan'sorg:andid:right after its three repository names, so a bad one beside an unanswered entry, a moved source, a refused name or another repository is that one refusal in both, and apins:entry that is no pin is said as_read_assays it (#1020) #11259549006feare clean. family.py: theadd,bumpandremovelines its remedies name begin with the interpreter and the script's path, quote the row's name, fill in the family's org, say the branch they run on, andmember-already-presentprints itsremove(#1127) #1201eac177af9conflicts atdocs/handbook.html, which this branch does not touch, and conflicts there againstorigin/mainalone too.#1209 changes
skip_where_a_name_cannot_be_not_utf_8and the callers fixturea_system_that_carries_the_byte. The new helper sits more than 100 lines from the first, andwindows_names_it65 lines from the second. #1209 also editstests/test_non_utf8_name_helpers.py, where the second commit adds its rows at the end of the file; the two merge without a conflict, and the merged file's rows pass (see "Runs").Left, and outside this issue
executestops extracting the spec leg of a source whose path holds U+00E9, becausegit filter-repoprints the clone's origin url as U+FFFD to a stdout that cannot hold it #1247, and adopt-project.py:executeunsets the leg clone'sremote.origin.urlbeforegit filter-repo, so under a Latin-1 locale a source under a directory named with the byte 0xE9 is extracted, where filter-repo died printing that url as U+FFFD, and every branch of the source is still rewritten as before (#1247) #1251 (a draft for it) is independent of this PR.adopt-project.py executefails for a Latin-1 user whose source path holds U+00E9. The leg clone'sremote.origin.urlholds the byte 0xE9, git-filter-repo decodes it witherrors='replace'and prints U+FFFD to a Latin-1 stdout, andexecutestops atREFUSED extracting the spec leg.python -Eignores thePYTHONIOENCODINGthe tool gives its children; a pip-installed one, as CI installs it, is not started that way. Measured here (see "Runs"):test_a_utf8_source_directory_still_adopts_with_local_remotespasses on main under Latin-1 with the pip launcher, and fails there with a-Ecopy of the same launcher. W1196's list of 50 has this row failing, which this bench gives only with the-Elauncher.executeunsets the leg clone'sremote.origin.urlbeforegit filter-repo, so under a Latin-1 locale a source under a directory named with the byte 0xE9 is extracted, where filter-repo died printing that url as U+FFFD, and every branch of the source is still rewritten as before (#1247) #1251. This PR no longer guards that row (RV1258's S1), so it carries nothing for adopt-project.py:executeunsets the leg clone'sremote.origin.urlbeforegit filter-repo, so under a Latin-1 locale a source under a directory named with the byte 0xE9 is extracted, where filter-repo died printing that url as U+FFFD, and every branch of the source is still rewritten as before (#1247) #1251 to drop, and the earlier plan, that whichever of the two PRs lands second drops this PR's guard, is void. adopt-project.py:executeunsets the leg clone'sremote.origin.urlbeforegit filter-repo, so under a Latin-1 locale a source under a directory named with the byte 0xE9 is extracted, where filter-repo died printing that url as U+FFFD, and every branch of the source is still rewritten as before (#1247) #1251 changes_extract_leginadopt-project.pyand addstests/test_adopt_filter_repo_origin_url.py, and touches none of these 18 files (git merge-treeagainst its headabeac66deis clean). The row passes under Latin-1 with the pip launcher whichever lands first. With a-Elauncher it fails on adopt-project.py: under a one-byte locale such as Latin-1,executestops extracting the spec leg of a source whose path holds U+00E9, becausegit filter-repoprints the clone's origin url as U+FFFD to a stdout that cannot hold it #1247's traceback until adopt-project.py:executeunsets the leg clone'sremote.origin.urlbeforegit filter-repo, so under a Latin-1 locale a source under a directory named with the byte 0xE9 is extracted, where filter-repo died printing that url as U+FFFD, and every branch of the source is still rewritten as before (#1247) #1251 lands, which is the tool's failure showing, not a premise this PR can judge.tests/test_bootstrap_spelling.pyfail under Latin-1 on main, on this head and on the merge with tests/conftest.py and tests/test_adopt_plan_paths_spelled.py: under a one-byte locale such as Latin-1 the 0xff--work-dirrow skips and the two U+00E9 rows read the child's UTF-8, so none of the three fails on a tool that is right, and the bytes helper starts its child asrun_scriptdoes (#1196) #1209 alike. It is a file tests: 50 tests in 12 files fail under a Latin-1 locale before and after #1209 (ten of them rows oftest_stdout_encoding.py), and whether each is a test that cannot judge under a one-byte locale or a tool defect is not measured (W1196's leftovers; follows #1196) #1214 does not list, read from the failure messages only: four rows end in aUnicodeEncodeErroron U+2028, which Latin-1 cannot encode, andtest_a_branch_name_is_spelled_in_every_line_that_names_itfails its assertion. They are left for an issue of their own.test_stdout_encoding.py), and whether each is a test that cannot judge under a one-byte locale or a tool defect is not measured (W1196's leftovers; follows #1196) #1214 does not list fail under Latin-1 on main, on this head and on the merge with tests/conftest.py and tests/test_adopt_plan_paths_spelled.py: under a one-byte locale such as Latin-1 the 0xff--work-dirrow skips and the two U+00E9 rows read the child's UTF-8, so none of the three fails on a tool that is right, and the bytes helper starts its child asrun_scriptdoes (#1196) #1209 alike:test_pinned_copy_text.py: the tool prints U+2014 or U+2026 escaped on a Latin-1 stdout, and the row compares the character.test_family.py'stest_init_writes_the_holders_agent_files: it reads the UTF-8 file it checks in the locale's encoding.test_family.py'stest_commit_one_refuses_a_trailer_that_is_no_text_before_it_stages:DID NOT RAISE Refusal, a byte Latin-1 reads as text.tests/test_adopt_template_values.pythat fails under Latin-1 on main istest_execute_refuses_a_local_source_directory_that_is_not_utf8. It is not one of the 50, and this PR leaves it alone.skip_where_a_name_cannot_be_not_utf_8, and tests/conftest.py and tests/test_adopt_plan_paths_spelled.py: under a one-byte locale such as Latin-1 the 0xff--work-dirrow skips and the two U+00E9 rows read the child's UTF-8, so none of the three fails on a tool that is right, and the bytes helper starts its child asrun_scriptdoes (#1196) #1209 gives that helper the locale question. So it skips at tests/conftest.py and tests/test_adopt_plan_paths_spelled.py: under a one-byte locale such as Latin-1 the 0xff--work-dirrow skips and the two U+00E9 rows read the child's UTF-8, so none of the three fails on a tool that is right, and the bytes helper starts its child asrun_scriptdoes (#1196) #1209's head, where the 50 were counted, and it skips on the scratch merge with tests/conftest.py and tests/test_adopt_plan_paths_spelled.py: under a one-byte locale such as Latin-1 the 0xff--work-dirrow skips and the two U+00E9 rows read the child's UTF-8, so none of the three fails on a tool that is right, and the bytes helper starts its child asrun_scriptdoes (#1196) #1209.--work-dirand the two U+00E9 paths), and its new helper builds the child's environment withoutblank_unnamed_pin_sourcesor the git identity defaults #1196's (tests/conftest.py and tests/test_adopt_plan_paths_spelled.py: under a one-byte locale such as Latin-1 the 0xff--work-dirrow skips and the two U+00E9 rows read the child's UTF-8, so none of the three fails on a tool that is right, and the bytes helper starts its child asrun_scriptdoes (#1196) #1209's) row, not tests: 50 tests in 12 files fail under a Latin-1 locale before and after #1209 (ten of them rows oftest_stdout_encoding.py), and whether each is a test that cannot judge under a one-byte locale or a tool defect is not measured (W1196's leftovers; follows #1196) #1214's.test_plan_and_check_say_the_same_sentence. Each skips here.🤖 Generated with Claude Code
Lane: openreposhape-4 (openRepoShape-4)
Summary by Sourcery
Make family validation robust across non-UTF-8 stdout encodings and ensure locale-dependent tests skip only when their UTF-8-specific premises cannot be evaluated.
Bug Fixes:
Enhancements:
Tests: