diff --git a/tests/test_adopt_template_values.py b/tests/test_adopt_template_values.py index fd60cf3b..991687f5 100644 --- a/tests/test_adopt_template_values.py +++ b/tests/test_adopt_template_values.py @@ -246,16 +246,25 @@ def test_plan_names_the_elector_it_took_from_git_config(tmp_path): b"again") -def refused_then_moved(base: Path, parent: Path, reason: bytes) -> None: +def refused_then_moved(base: Path, parent: Path, reason: bytes, + skip_where_git_fails: bool = True) -> None: """A source in the directory `parent`: `plan` and `check` pass it, as #247 made them, and `execute` refuses its path as the assembly root's clone URL, giving `reason`, before any leg exists. Then the remediation the refusal gives, followed as it is worded -- `parent` renamed, then `plan --source `, `check` and `execute` run again -- ends - `adoption verified`.""" + `adoption verified`. + + A source repository that git cannot make under `parent` skips the test + where `skip_where_git_fails` is true, for a name that is not UTF-8, which + git may not work under. A name that is valid text asks for it false, and + the failure is raised: the cause is the disk or git, never the name + (#834).""" try: source = make_source_repo(parent / "Thing") except AssertionError as exc: + if not skip_where_git_fails: + raise pytest.skip(f"git will not work under {parent.name!r} here: {exc}") runs = plan_check_execute(base, source) assert_no_traceback_and_no_leg(base, runs) @@ -306,13 +315,15 @@ def test_execute_refuses_a_local_source_directory_holding_a_bidi_control( """A source under a directory whose name begins with U+202E RIGHT-TO-LEFT OVERRIDE: on main `execute` adopted it, and the shape's README showed the rest of its `git clone` line reversed. Now it is refused as the - byte is, and moving the source adopts.""" + byte is, and moving the source adopts. Nothing here is guarded: U+202E + is valid text, which a POSIX disk, NTFS and APFS hold and git works + under (reasoned, not run, on the last two), so an error of the disk when + the directory is made, and a failure of git when the source is, are the + test's own and are raised, not skipped (#834).""" parent = tmp_path / "\u202ecaf" - try: - parent.mkdir() - except OSError as exc: - pytest.skip(f"no directory here is named '\\u202ecaf': {exc!r}") - refused_then_moved(tmp_path, parent, b"a bidirectional control") + parent.mkdir() + refused_then_moved(tmp_path, parent, b"a bidirectional control", + skip_where_git_fails=False) # --- `check` and `execute`: a plan value edited by hand ---------------------- diff --git a/tests/test_bootstrap_leg_moves.py b/tests/test_bootstrap_leg_moves.py index b0a950a9..41f1d43b 100644 --- a/tests/test_bootstrap_leg_moves.py +++ b/tests/test_bootstrap_leg_moves.py @@ -55,7 +55,8 @@ import pytest -from conftest import IDENTITY_CONFIG, git, run_script +from conftest import (IDENTITY_CONFIG, git, run_script, + skip_where_a_name_cannot_be_not_utf_8) from test_bootstrap_after_bump import ( # noqa: F401 ADDED, EDITED, MAIN, NOTHING_ELSE, bootstrapped, bumped, failing_post_checkout, head_ref, left_branch, moved_and_refused, rev, @@ -546,11 +547,11 @@ def test_a_branch_whose_name_is_not_utf8_is_read_back_on_every_run( the check of where `checkout -b` left HEAD (S3) reads HEAD back; the second places the leg on it. Each read `symbolic-ref` decoded as UTF-8, and on base the second run ended in a `UnicodeDecodeError` traceback. - Made on a disk that takes the byte, skipped on one that does not.""" - try: - (tmp_path / "probe-\udcff").write_bytes(b"") - except (OSError, ValueError): - pytest.skip("this disk takes no file name with the byte 0xff") + Made on a system and a disk that take the byte, skipped on one that + does not: `skip_where_a_name_cannot_be_not_utf_8` asks. Any other + error of the disk, a full one or a read-only `tmp_path`, fails the + test (#834).""" + skip_where_a_name_cannot_be_not_utf_8(tmp_path) leg = root / "spec" pin = rev(leg) git("checkout", "-q", "--detach", pin, cwd=leg) diff --git a/tests/test_cli_reference.py b/tests/test_cli_reference.py index a770e1bf..6863056b 100644 --- a/tests/test_cli_reference.py +++ b/tests/test_cli_reference.py @@ -49,7 +49,8 @@ import pytest -from conftest import REPO, WINDOWS_SKIP, git +from conftest import (REPO, WINDOWS_SKIP, git, + skip_where_a_name_cannot_be_not_utf_8) REFERENCE = REPO / "docs" / "cli.md" GENERATOR = REPO / "scripts" / "render-cli-reference.py" @@ -642,12 +643,9 @@ def test_a_checkout_whose_path_ends_in_a_space_is_a_checkout(tmp_path): @WINDOWS_SKIP def test_a_checkout_whose_path_is_not_utf8_is_a_checkout(tmp_path): + skip_where_a_name_cannot_be_not_utf_8(tmp_path) parent = tmp_path / os.fsdecode(b"bad\xffname") - try: - parent.mkdir() - except OSError as error: - pytest.skip(f"this file system cannot hold a name that is not UTF-8: " - f"{error}") + parent.mkdir() tree = a_checkout_at(parent / "openRepoShape") with pytest.raises(UnicodeDecodeError): os.fsencode(tree).decode("utf-8") diff --git a/tests/test_doctor_validator_verdicts.py b/tests/test_doctor_validator_verdicts.py index 7c41223c..0f4f5ebc 100644 --- a/tests/test_doctor_validator_verdicts.py +++ b/tests/test_doctor_validator_verdicts.py @@ -48,7 +48,8 @@ import pytest -from conftest import WINDOWS_SKIP, clone_just_pushed, run_script +from conftest import (WINDOWS_SKIP, clone_just_pushed, run_script, + skip_where_a_name_cannot_be_not_utf_8) #: The layer stand-in a chain descendant pins, from the module that scaffolds #: one: a second way of building it would test a different one. from test_scaffold_pin_and_reuse import layer_repo @@ -542,16 +543,15 @@ def test_a_name_that_is_not_utf8_is_never_an_exception_quoted_as_a_finding( today the row is the crash row; a script that prints such a name will make it the `naming-unclassified` FINDING, and either is a true row -- what this test holds is that the exception is never the finding. - `WINDOWS_SKIP`: Windows has no name that is not UTF-8, and a disk that - refuses one (APFS) skips. + `WINDOWS_SKIP`: Windows has no name that is not UTF-8. A system or a + disk that cannot carry one (APFS's EILSEQ) skips, asked of + `skip_where_a_name_cannot_be_not_utf_8`; any other error of the disk, + a full one or a read-only `tmp_path`, fails the test (#834). """ + skip_where_a_name_cannot_be_not_utf_8(tmp_path) name = os.fsdecode(b"Atl\xffas") here = tmp_path / "elsewhere" / name - try: - here.mkdir(parents=True) - except OSError as exc: - pytest.skip(f"this filesystem refuses a name that is not UTF-8: " - f"{exc}") + here.mkdir(parents=True) result = doctor(standard, here, "--json") payload = payload_of(result) row = rows_of(result)["naming"] diff --git a/tests/test_non_utf8_git_reads.py b/tests/test_non_utf8_git_reads.py index be2fa550..a53d6547 100644 --- a/tests/test_non_utf8_git_reads.py +++ b/tests/test_non_utf8_git_reads.py @@ -43,6 +43,7 @@ from __future__ import annotations +import errno import os import shutil import subprocess @@ -94,13 +95,25 @@ def make_file(path: Path, data: bytes = b"on: [push]\n") -> None: system that carries the byte and a filesystem that stores it (Linux does; APFS refuses it, and on Windows `os.fsencode` makes no byte of it, which is asked FIRST: NTFS most likely takes the surrogate, and the file would - then not be the one the test means). A name that is UTF-8 is written.""" + then not be the one the test means). A name that is UTF-8 is written. + + Two answers are the system's, each skipped on a reason of its own: a + file name encoding that cannot spell the name (`UnicodeError`: ASCII + under `LC_ALL=C`) and a disk that refuses the name (EILSEQ: macOS). + Every other `OSError`, a full disk (ENOSPC), a read-only one (EROFS) + or a folder nobody can write in (EACCES), is the test's own and is + raised (#834).""" skip_where_fsencode_gives_a_surrogate_no_byte(path.name) try: path.write_bytes(data) - except (OSError, UnicodeError) as exc: - pytest.skip("this filesystem cannot hold a file name that is not " - f"UTF-8: {exc}") + except UnicodeError as exc: + pytest.skip("this system's file name encoding cannot spell " + f"{path.name!r}: {exc}") + except OSError as exc: + if exc.errno != errno.EILSEQ: + raise + pytest.skip("this filesystem refuses a file name that is not " + f"UTF-8 (EILSEQ): {exc}") def test_make_file_skips_where_fsencode_makes_no_byte_and_writes_nothing( diff --git a/tests/test_non_utf8_name_callers.py b/tests/test_non_utf8_name_callers.py index 3b45ffe0..76db45df 100644 --- a/tests/test_non_utf8_name_callers.py +++ b/tests/test_non_utf8_name_callers.py @@ -113,12 +113,14 @@ def mkdir(path, *args, **kwargs): return mkdir -def fail_where_the_caller_ends(call, *args) -> None: +def fail_where_the_caller_ends(call, *args, said: str = "") -> None: """Fail where `call(*args)` ends without an error, SKIPPED or not: the caller was to raise. A `pytest.raises` block may hold one call and no - more (python:S5778), so the failure is made here and not beside it.""" + more (python:S5778), so the failure is made here and not beside it. + `said` goes in front of the message, for a caller that asks the same + question many times and says which one failed (#834).""" reason = skip_message(call, *args) - pytest.fail("the caller ended " + pytest.fail(said + "the caller ended " + (f"SKIPPED: {reason}" if reason else "without an error")) @@ -279,17 +281,19 @@ def test_make_files_utf8_name_test_skips_where_the_encoding_is_ascii( def test_a_write_the_disk_refuses_still_fails_the_make_file_test( tmp_path, monkeypatch, code): """The name is plain UTF-8, so a refused write is the disk's and not the - encoding's: the test FAILS on its assert, as it does without this change. - A skip on `make_file`'s own "filesystem" reason would end it SKIPPED, and - no run fails on that.""" + encoding's: `make_file` raises it (#834), and the test FAILS on it. A + skip on `make_file`'s own "filesystem" reason would end it SKIPPED, and + no run fails on that; before #834 it did skip, and the test failed on + its assert that the call is not a skip.""" non_utf8_git_reads.skip_where_the_environment_cannot_carry( non_utf8_git_reads.UTF8) def write_bytes(self, data): raise OSError(code, os.strerror(code), str(self)) monkeypatch.setattr(Path, "write_bytes", write_bytes) - with pytest.raises(AssertionError): + with pytest.raises(OSError) as raised: fail_where_the_caller_ends(MAKE_FILE_TEST, tmp_path, monkeypatch) + assert raised.value.errno == code @pytest.mark.parametrize("value", ["Jos\u00e9", "Mehr\u200cdad", "\u202eevil"], diff --git a/tests/test_non_utf8_oserror_sites.py b/tests/test_non_utf8_oserror_sites.py new file mode 100644 index 00000000..181ed792 --- /dev/null +++ b/tests/test_non_utf8_oserror_sites.py @@ -0,0 +1,349 @@ +# SPDX-License-Identifier: Apache-2.0 +"""A test that makes a directory or a file named with a byte that is not UTF-8 +(or with U+202E) skips on the system's answer and on no other error of the +disk (#834, #461). + +Five more places beside the ones #757 moved onto +`skip_where_a_name_cannot_be_not_utf_8` (#663) turned ANY `OSError` into +`pytest.skip`, so a disk that filled up, a read-only `tmp_path` or a folder +this user cannot write in read as SKIPPED, and no run fails on a skip: + +- `test_a_checkout_whose_path_is_not_utf8_is_a_checkout` + (`tests/test_cli_reference.py`) and + `test_a_name_that_is_not_utf8_is_never_an_exception_quoted_as_a_finding` + (`tests/test_doctor_validator_verdicts.py`) made their own directory; +- `test_a_branch_whose_name_is_not_utf8_is_read_back_on_every_run` + (`tests/test_bootstrap_leg_moves.py`) probed the disk with `write_bytes`; +- `make_file` (`tests/test_non_utf8_git_reads.py`) skipped on any `OSError` of + its `write_bytes`, so each of its callers inherited the skip; +- the U+202E directory of `tests/test_adopt_template_values.py` is valid text, + which no system known here refuses, so it skipped for nothing but a fault, + and so did the source repository `refused_then_moved` makes in it, when git + failed. + +The first three now ask the helper, which skips on the two answers that mean +"this system cannot carry the name" (Windows' `os.fsencode` and macOS's EILSEQ) +and raises the rest. `make_file` skips on EILSEQ and on a `UnicodeError` (an +ASCII file name encoding, #757), each on a reason of its own, and raises the +rest. The U+202E test makes its directory, and its source repository, with no +guard. + +Each is called here as a plain function, with the disk answering as it would on +a system this suite does not run on (CI is Linux only, #597), and asked what +it did. No Windows or macOS run was made: + +- a disk that refuses the PROBE with EILSEQ (macOS): the test skips, on the + helper's reason; +- a disk that refuses the probe, or the test's own name, with ANY other errno, + every one `errno.errorcode` holds: the test raises it, where it skipped. The + errnos are drawn from there and not listed, so a guard that re-raises the + three somebody thought of (ENOSPC, EROFS, EACCES) and skips the rest, EIO or + EDQUOT, fails here; +- Windows, by an `os.fsencode` that gives a surrogate three bytes: the three + tests that ask the helper skip before the disk is touched; +- git failing under the U+202E directory fails the test, and under the + directory named with a byte that is not UTF-8 still skips it (#757 kept that + second skip, and it is a known limit: a full disk skips there). +""" + +from __future__ import annotations + +import errno +import os +from pathlib import Path + +import pytest + +import test_adopt_template_values as template_values +import test_bootstrap_leg_moves as leg_moves +import test_cli_reference as cli_reference +import test_doctor_validator_verdicts as doctor_verdicts +import test_non_utf8_git_reads as non_utf8_git_reads +from conftest import skip_message, windows_fsencode +# The fixtures and the two functions of #757's callers file, which ask the same +# questions of the same helper. +from test_non_utf8_name_callers import ( # noqa: F401 - fixtures + PROBE, a_disk_that_takes_the_probe, a_system_that_carries_the_byte, + fail_where_the_caller_ends) + +#: The three tests that ask `skip_where_a_name_cannot_be_not_utf_8` for the +#: name of a directory under their `tmp_path`, each called with that and, for +#: the two that take a fixture of the project, `None`: they raise or skip +#: before they use it. +ASK_THE_HELPER = [ + pytest.param( + lambda tmp_path: cli_reference + .test_a_checkout_whose_path_is_not_utf8_is_a_checkout(tmp_path), + id="cli-reference"), + pytest.param( + lambda tmp_path: doctor_verdicts + .test_a_name_that_is_not_utf8_is_never_an_exception_quoted_as_a_finding( + None, tmp_path), + id="doctor-validator-verdicts"), + pytest.param( + lambda tmp_path: leg_moves + .test_a_branch_whose_name_is_not_utf8_is_read_back_on_every_run( + None, tmp_path), + id="bootstrap-leg-moves"), +] +#: Those of them that go on to make a directory of their own after the probe. +THEN_MAKE_THEIR_OWN = [row for row in ASK_THE_HELPER + if row.id != "bootstrap-leg-moves"] + +#: Every answer a disk can give but the one that means "this system cannot +#: carry the name": drawn from `errno.errorcode` and not listed, so an errno +#: nobody thought of is asked too. +EVERY_OTHER_ERRNO = sorted(code for code in errno.errorcode + if code != errno.EILSEQ) + +#: A `git` failure as the disk gives it: what `make_source_repo` raises when +#: `git commit` cannot write an object. +GIT_FAILED = ("git commit failed: error: unable to write file " + ".git/objects/xx: No space left on device") + + +def a_source_git_cannot_make(*args, **kwargs): + raise AssertionError(GIT_FAILED) + + +def the_probe(name: str) -> bool: + return name.startswith(PROBE) + + +def anything_but_the_probe(name: str) -> bool: + return not the_probe(name) + + +class Disk: + """What `a_disk_that_refuses` puts in: it answers `code`, which a test may + change, to every name `names` accepts, and to all of them where that is + None.""" + + def __init__(self, code: int, names=None): + self.code = code + self.names = names + + def refuses(self, name: str) -> bool: + return self.names is None or self.names(name) + + def error(self, path) -> OSError: + return OSError(self.code, os.strerror(self.code), os.fsdecode(path)) + + +def a_disk_that_refuses(monkeypatch, code: int, names=None) -> Disk: + """From here on a disk that answers `code` to every directory (`os.mkdir`, + which `Path.mkdir` calls) and every file (`Path.write_bytes`) whose last + part `names` accepts, to all of them where it is None, and makes the + rest.""" + disk = Disk(code, names) + real_mkdir = os.mkdir + real_write_bytes = Path.write_bytes + + def mkdir(path, *args, **kwargs): + if disk.refuses(os.path.basename(os.fsdecode(path))): + raise disk.error(path) + return real_mkdir(path, *args, **kwargs) + + def write_bytes(self, data): + if disk.refuses(self.name): + raise disk.error(self) + return real_write_bytes(self, data) + + monkeypatch.setattr(os, "mkdir", mkdir) + monkeypatch.setattr(Path, "write_bytes", write_bytes) + return disk + + +def the_error_of(kind, call, *args, said: str = "") -> Exception: + """The `kind` of error `call(*args)` raises. A call that skips or ends + without one FAILS the test, as `fail_where_the_caller_ends` has it, with + `said` in front of the message.""" + with pytest.raises(kind) as raised: + fail_where_the_caller_ends(call, *args, said=said) + return raised.value + + +def raised_for_every_errno(disk: Disk, call, *args) -> None: + """Fail unless `call(*args)` raises what `disk` answers, whichever of + `EVERY_OTHER_ERRNO` it answers, as Python makes it (`PermissionError` for + EACCES). It stops at the first errno that does not, naming it in the + failure, so a caller that goes on past a refused name runs its whole + test once and not 129 times.""" + for code in EVERY_OTHER_ERRNO: + disk.code = code + error = the_error_of(OSError, call, *args, + said=f"errno {code} ({errno.errorcode[code]}): ") + assert error.errno == code, (code, error) + assert type(error) is type(OSError(code, "")), (code, error) + + +# --- the three tests that ask the helper ------------------------------------ + +@pytest.mark.usefixtures("a_system_that_carries_the_byte") +@pytest.mark.parametrize("call", ASK_THE_HELPER) +def test_a_disk_that_refuses_the_probe_with_eilseq_skips_the_test( + tmp_path, monkeypatch, call): + """macOS (emulated): the probe is `Illegal byte sequence`, so the test is + SKIPPED, on the helper's reason, and nothing else is asked of the disk.""" + a_disk_that_refuses(monkeypatch, errno.EILSEQ, the_probe) + reason = skip_message(call, tmp_path) + assert reason is not None, "the test went on past the probe's EILSEQ" + assert "EILSEQ" in reason, reason + + +@pytest.mark.usefixtures("a_system_that_carries_the_byte") +@pytest.mark.parametrize("call", ASK_THE_HELPER) +def test_a_disk_that_refuses_the_probe_with_any_other_errno_fails_the_test( + tmp_path, monkeypatch, call): + """A full disk, a read-only one, a folder nobody can write in, a failing + drive, a quota: the helper raises each, so the test FAILS, and no run reads + it as a skip.""" + disk = a_disk_that_refuses(monkeypatch, errno.EACCES, the_probe) + raised_for_every_errno(disk, call, tmp_path) + + +@pytest.mark.usefixtures("a_disk_that_takes_the_probe") +@pytest.mark.parametrize("call", THEN_MAKE_THEIR_OWN) +def test_the_cli_reference_and_doctor_tests_fail_where_the_disk_refuses_their_own_directory( + tmp_path, monkeypatch, call): + """The probe is made and removed, and then the directory the test means + to use is refused with any errno: that is the test's own, raised, where it + was a skip. The rows ask the real disk for the probe first, and skip where + it answers EILSEQ: they would FAIL there on a system that holds no + defect.""" + disk = a_disk_that_refuses(monkeypatch, errno.EACCES, + anything_but_the_probe) + raised_for_every_errno(disk, call, tmp_path) + + +@pytest.mark.parametrize("call", ASK_THE_HELPER) +def test_where_fsencode_makes_no_byte_of_the_surrogate_the_test_skips_first( + tmp_path, monkeypatch, call): + """Windows (emulated): the helper is asked BEFORE the disk, which would + take the name, so the test skips on the system's reason and nothing is + made under `tmp_path`: a test that made its directory first, or probed + the disk and went on, ends without a skip here.""" + monkeypatch.setattr(os, "fsencode", windows_fsencode) + reason = skip_message(call, tmp_path) + assert reason is not None, "the test went on where fsencode gives no byte" + assert "os.fsencode" in reason, reason + assert list(tmp_path.iterdir()) == [] + + +# --- `make_file` ------------------------------------------------------------ + +@pytest.mark.usefixtures("a_system_that_carries_the_byte") +def test_make_file_skips_where_the_disk_answers_eilseq(tmp_path, monkeypatch): + """macOS (emulated): APFS refuses a file name that is not UTF-8 with + `Illegal byte sequence`, which is the disk's answer, so the file is not + written and the test that asked is skipped, on a reason that says it is + the filesystem.""" + a_disk_that_refuses(monkeypatch, errno.EILSEQ) + target = tmp_path / non_utf8_git_reads.NOT_UTF8 + reason = skip_message(non_utf8_git_reads.make_file, target) + assert reason is not None, "make_file went on past the disk's EILSEQ" + assert "filesystem" in reason, reason + assert "EILSEQ" in reason, reason + + +def test_make_file_skips_where_the_file_name_encoding_cannot_spell_the_name( + tmp_path, monkeypatch): + """ASCII under `LC_ALL=C` (#757): the write raises `UnicodeEncodeError` + before the disk is asked, which is the system's answer, and the reason + says it is the encoding: the name is UTF-8, and the disk is not + involved.""" + def write_bytes(self, data): + raise UnicodeEncodeError("ascii", self.name, 0, 1, + "ordinal not in range(128)") + monkeypatch.setattr(Path, "write_bytes", write_bytes) + target = tmp_path / non_utf8_git_reads.UTF8 + reason = skip_message(non_utf8_git_reads.make_file, target) + assert reason is not None, "make_file raised where the encoding says no" + assert "file name encoding" in reason, reason + assert "filesystem" not in reason, reason + + +@pytest.mark.usefixtures("a_system_that_carries_the_byte") +@pytest.mark.parametrize("name", [non_utf8_git_reads.NOT_UTF8, + non_utf8_git_reads.UTF8], + ids=["not-utf-8", "utf-8"]) +def test_make_file_raises_any_other_error_of_the_disk( + tmp_path, monkeypatch, name): + """A full disk, a read-only one, a folder nobody can write in or a failing + drive is the test's own, for a name that is not UTF-8 and for one that is: + raised as it was, and never a skip that hides it, whichever errno it is.""" + disk = a_disk_that_refuses(monkeypatch, errno.EACCES) + raised_for_every_errno(disk, non_utf8_git_reads.make_file, tmp_path / name) + + +# --- the U+202E directory --------------------------------------------------- + +#: U+202E RIGHT-TO-LEFT OVERRIDE, built from its code point: typed raw it +#: would reorder the line it is on. +BIDI = chr(0x202E) +BIDI_TEST = (template_values + .test_execute_refuses_a_local_source_directory_holding_a_bidi_control) +BYTE_TEST = (template_values + .test_execute_refuses_a_local_source_directory_that_is_not_utf8) + + +def test_a_disk_that_refuses_the_bidi_directory_fails_the_test( + tmp_path, monkeypatch): + """U+202E is valid text: no probe is asked, and the directory is made with + no guard, so what the disk answers to it is raised, whichever errno it is. + It was a skip that said "no directory here is named ..." for a full disk + or a read-only one.""" + disk = a_disk_that_refuses(monkeypatch, errno.EACCES) + raised_for_every_errno(disk, BIDI_TEST, tmp_path) + + +def test_a_failure_of_git_under_the_bidi_directory_fails_the_test( + tmp_path, monkeypatch): + """The source repository `refused_then_moved` makes in the directory: + where git cannot (a disk that filled after the `mkdir`), the failure is + raised, as the name is valid text and not the cause. It was a skip that + said "git will not work under ...". Skipped where this system's file name + encoding cannot spell U+202E (ASCII under `LC_ALL=C`): the directory is + really made here, and `mkdir` raises `UnicodeEncodeError` first.""" + non_utf8_git_reads.skip_where_the_environment_cannot_carry(BIDI) + monkeypatch.setattr(template_values, "make_source_repo", + a_source_git_cannot_make) + error = the_error_of(AssertionError, BIDI_TEST, tmp_path) + assert str(error) == GIT_FAILED + + +@pytest.mark.usefixtures("a_disk_that_takes_the_probe") +def test_a_failure_of_git_under_the_byte_directory_still_skips_the_test( + tmp_path, monkeypatch): + """The control, and the known limit: #757 kept the second skip for a name + that is not UTF-8, which git may not work under, so a disk that filled + after the `mkdir` still reads as SKIPPED there.""" + monkeypatch.setattr(template_values, "make_source_repo", + a_source_git_cannot_make) + reason = skip_message(BYTE_TEST, tmp_path) + assert reason is not None, "the test went on past git's failure" + assert "git will not work under" in reason, reason + + +# --- the sweep itself ------------------------------------------------------- + +def test_the_sweep_names_the_first_errno_that_fails_and_stops_there(): + """A caller that skips where it should raise fails the test at the first + errno, with that errno named in front of the message, and is not asked + the other 128: the label is passed in and not made by catching a failure + (python:S8714), so a failure that drops it, or a sweep that goes on, shows + here.""" + asked = [] + + def hides_the_error(tmp_path): + asked.append(tmp_path) + pytest.skip("hidden") + + first = EVERY_OTHER_ERRNO[0] + disk = Disk(first) + with pytest.raises(pytest.fail.Exception) as failed: + raised_for_every_errno(disk, hides_the_error, "a path") + assert failed.value.msg == ( + f"errno {first} ({errno.errorcode[first]}): " + "the caller ended SKIPPED: hidden") + assert asked == ["a path"]