diff --git a/tests/wildcards/test_wildcard_encoding_strict.py b/tests/wildcards/test_wildcard_encoding_strict.py index 4c0d8f56..8f75379b 100644 --- a/tests/wildcards/test_wildcard_encoding_strict.py +++ b/tests/wildcards/test_wildcard_encoding_strict.py @@ -1,6 +1,7 @@ """Strict UTF-8 wildcard decoding checks against the fixture ground truth.""" import logging import os +from pathlib import Path import sys import tempfile @@ -203,15 +204,28 @@ def test_decoding_across_loaders(tmp_path, monkeypatch, caplog, encoding, file_t assert not caplog.records +@pytest.fixture +def wildcard_loading(monkeypatch): + for name in ("wildcard_dict", "loaded_wildcards", "available_wildcards"): + monkeypatch.setattr(wc, name, {}) + monkeypatch.setattr(wc, "_on_demand_mode", False) + walk = wc.os.walk + + def ordered_walk(*args, **kwargs): + for root, directories, files in walk(*args, **kwargs): + # The broken file must precede a good file, regardless of filesystem order. + yield root, directories, sorted(files) + + monkeypatch.setattr(wc.os, "walk", ordered_walk) + + @pytest.mark.parametrize("on_demand", [False, True]) -def test_bad_file_is_skipped_and_named(tmp_path, monkeypatch, caplog, on_demand): +def test_bad_file_is_skipped_and_named(tmp_path, wildcard_loading, caplog, on_demand): (tmp_path / "a_good.txt").write_text("red\nblue\n", encoding="utf-8") bad = tmp_path / "b_broken.yaml" bad.write_text("colors: [unclosed\n", encoding="utf-8") (tmp_path / "c_empty.yaml").write_text("", encoding="utf-8") (tmp_path / "d_good.yaml").write_text("shapes:\n - circle\n", encoding="utf-8") - monkeypatch.setattr(wc, "wildcard_dict", {}) - monkeypatch.setattr(wc, "loaded_wildcards", {}) with caplog.at_level(logging.WARNING): wc.read_wildcard_dict(tmp_path, on_demand=on_demand) @@ -220,6 +234,56 @@ def test_bad_file_is_skipped_and_named(tmp_path, monkeypatch, caplog, on_demand) assert list(wc.wildcard_dict["shapes"]) == ["circle"] assert len(caplog.records) == 1 assert str(bad) in caplog.text + assert "ParserError" in caplog.text + + +@pytest.mark.parametrize("on_demand", [False, True], ids=["eager", "on_demand"]) +@pytest.mark.parametrize("bad_directory", ["builtin", "custom"]) +@pytest.mark.parametrize("contents", [b"colors: [unclosed\n", b"\x00\x05\x16\x07MacOS\x00", None], + ids=["syntax", "binary", "read_error"]) +def test_wildcard_load_continues_after_bad_file(tmp_path, wildcard_loading, monkeypatch, caplog, + on_demand, bad_directory, contents): + builtin, custom = tmp_path / "builtin", tmp_path / "custom" + for label, directory in (("builtin", builtin), ("custom", custom)): + directory.mkdir() + (directory / f"a_{label}.txt").write_text(f"{label}-before\n", encoding="utf-8") + (directory / "c_empty.yaml").write_text("", encoding="utf-8") + (directory / "d_after.yml").write_text(f"{label}_after: [{label}-after]\n", encoding="utf-8") + bad = tmp_path / bad_directory / "b_bad.yaml" + bad.write_bytes(contents if contents is not None else b"unreadable: [value]\n") + if contents is None: + read_text = wc._read_wildcard_text + + def unreadable(path): + if Path(path) == bad: + raise OSError("wildcard fixture is unreadable") + return read_text(path) + + monkeypatch.setattr(wc, "_read_wildcard_text", unreadable) + monkeypatch.setattr(wc, "wildcards_path", str(builtin)) + monkeypatch.setattr(wc.config, "get_config", lambda: { + "custom_wildcards": str(custom), "wildcard_cache_limit_mb": 0 if on_demand else 50}) + + with caplog.at_level(logging.WARNING): + wc.wildcard_load() + + assert wc.is_on_demand_mode() is on_demand + assert wc.process("__a_builtin__ __builtin_after__ __a_custom__ __custom_after__", seed=7) == ( + "builtin-before builtin-after custom-before custom-after") + assert len(caplog.records) == 1 + assert str(bad) in caplog.text + + +@pytest.mark.parametrize("lazy", [False, True], ids=["direct", "lazy"]) +@pytest.mark.parametrize("contents", [b"colors: [unclosed\n", b"\x00\x05\x16\x07MacOS\x00"], ids=["syntax", "binary"]) +def test_single_file_loader_still_raises(tmp_path, lazy, contents): + bad = tmp_path / "bad.yaml" + bad.write_bytes(contents) + with pytest.raises(yaml.YAMLError): + if lazy: + wc.LazyWildcardLoader(bad, "yaml").get_data() + else: + wc.load_yaml_wildcard(bad) def test_invalid_yaml_is_not_redecoded_as_latin1(tmp_path, caplog):