From ff0a298c6d765b73fb2cee4d90c1055742f9f7f4 Mon Sep 17 00:00:00 2001 From: Joseph Magly <1159087+jmagly@users.noreply.github.com> Date: Sat, 15 Aug 2026 03:01:06 -0400 Subject: [PATCH] Test Windows console and path portability contracts Cover legacy console fallbacks, platform temp paths, local UI disk probing, and lazy analysis exports. Bind the new local UI test to the executable source-to-test risk map. --- ci/test-risk-map.json | 7 +++- tests/test_cli.py | 43 +++++++++++++++++++++++ tests/test_cli_boundaries.py | 10 ++++++ tests/test_local_ui_portability.py | 35 ++++++++++++++++++ tests/test_package_export_contracts.py | 35 ++++++++++++++++++ tests/test_telemetry_failure_contracts.py | 4 ++- 6 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 tests/test_local_ui_portability.py diff --git a/ci/test-risk-map.json b/ci/test-risk-map.json index ea2f35c..3c9e91d 100644 --- a/ci/test-risk-map.json +++ b/ci/test-risk-map.json @@ -110,6 +110,7 @@ "required_tests": [ "tests/test_cli.py", "tests/test_cli_boundaries.py", + "tests/test_local_ui_portability.py", "tests/test_remote_boundaries.py", "tests/test_remote_contracts.py", "tests/conditional/test_network_services.py", @@ -496,7 +497,11 @@ "path": "obliteratus/local_ui.py", "risk_class": "mixed-runtime", "risk": "Gradio construction, launch configuration, authentication, and signals", - "required_tests": ["tests/test_cli.py", "tests/conditional/test_operator_ui.py"], + "required_tests": [ + "tests/test_cli.py", + "tests/test_local_ui_portability.py", + "tests/conditional/test_operator_ui.py" + ], "conditional_gates": ["operator-ui"] }, { diff --git a/tests/test_cli.py b/tests/test_cli.py index 43fbeed..8926c9b 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -154,3 +154,46 @@ class TestCLIDispatch: args_passed = mock_cmd.call_args[0][0] assert args_passed.contribute is True assert args_passed.contribute_notes == "Testing contribution system" + + +class _EncodingOnlyStdout: + """Minimal stream stand-in for encoding-selection tests.""" + + def __init__(self, encoding: str | None) -> None: + self.encoding = encoding + + +class TestConsoleEncoding: + """The CLI must remain renderable on legacy Windows code pages.""" + + @pytest.mark.parametrize("encoding", ["cp1252", "ascii", "not-a-codec"]) + def test_console_text_falls_back_when_text_is_not_encodable(self, encoding): + from obliteratus.cli import _console_text + + with patch("sys.stdout", _EncodingOnlyStdout(encoding)): + assert _console_text("█→", "FALLBACK") == "FALLBACK" + + @pytest.mark.parametrize("encoding", ["utf-8", None]) + def test_console_text_keeps_unicode_on_utf8_compatible_streams(self, encoding): + from obliteratus.cli import _console_text + + with patch("sys.stdout", _EncodingOnlyStdout(encoding)): + assert _console_text("█→", "FALLBACK") == "█→" + + def test_banner_degrades_to_ascii_on_cp1252(self): + from obliteratus.cli import _banner_for_console + + with patch("sys.stdout", _EncodingOnlyStdout("cp1252")): + banner = _banner_for_console() + + assert "OBLITERATUS" in banner + assert banner.isascii() + + def test_main_renders_selected_banner(self): + with ( + patch("sys.stdout", _EncodingOnlyStdout("cp1252")), + patch("obliteratus.cli.console") as mock_console, + ): + main(["models"]) + + assert mock_console.print.call_args_list[0].args[0].isascii() diff --git a/tests/test_cli_boundaries.py b/tests/test_cli_boundaries.py index 4292658..310f513 100644 --- a/tests/test_cli_boundaries.py +++ b/tests/test_cli_boundaries.py @@ -58,6 +58,16 @@ def test_main_routes_remote_commands(monkeypatch, argv, target): command.assert_called_once() +def test_tourney_default_output_uses_platform_temp_directory(monkeypatch, tmp_path): + command = Mock() + monkeypatch.setattr(cli, "_cmd_tourney", command) + monkeypatch.setattr(cli.tempfile, "gettempdir", lambda: str(tmp_path)) + + cli.main(["tourney", "local/model"]) + + assert command.call_args.args[0].output_dir == str(tmp_path / "obliteratus_tourney") + + def test_refusal_max_tokens_cli_default_and_positive_override(monkeypatch): command = Mock() monkeypatch.setattr(cli, "_cmd_abliterate", command) diff --git a/tests/test_local_ui_portability.py b/tests/test_local_ui_portability.py new file mode 100644 index 0000000..c42ecad --- /dev/null +++ b/tests/test_local_ui_portability.py @@ -0,0 +1,35 @@ +"""Portable local-launcher rendering and filesystem contracts.""" + +from __future__ import annotations + +from unittest.mock import Mock + +from obliteratus import local_ui + + +class _EncodingOnlyStdout: + def __init__(self, encoding: str | None) -> None: + self.encoding = encoding + + +def test_local_ui_banner_degrades_to_ascii_on_cp1252(monkeypatch): + monkeypatch.setattr(local_ui.sys, "stdout", _EncodingOnlyStdout("cp1252")) + banner = local_ui._banner_for_console() + assert "OBLITERATUS" in banner + assert banner.isascii() + + +def test_local_ui_banner_keeps_block_art_on_utf8(monkeypatch): + monkeypatch.setattr(local_ui.sys, "stdout", _EncodingOnlyStdout("utf-8")) + assert "░" in local_ui._banner_for_console() + + +def test_system_info_queries_the_platform_temp_directory(monkeypatch, tmp_path): + disk_free = Mock(return_value=12.5) + monkeypatch.setattr(local_ui.tempfile, "gettempdir", lambda: str(tmp_path)) + monkeypatch.setattr(local_ui, "_get_disk_free_gb", disk_free) + monkeypatch.setattr(local_ui, "console", Mock()) + + local_ui._print_system_info([]) + + disk_free.assert_called_once_with(str(tmp_path)) diff --git a/tests/test_package_export_contracts.py b/tests/test_package_export_contracts.py index 94b0150..6727c7e 100644 --- a/tests/test_package_export_contracts.py +++ b/tests/test_package_export_contracts.py @@ -2,9 +2,13 @@ from __future__ import annotations +import subprocess +import sys + import pytest import obliteratus +import obliteratus.analysis as analysis @pytest.mark.parametrize( @@ -33,3 +37,34 @@ def test_documented_lazy_export_resolves(name): def test_unknown_lazy_export_raises_attribute_error(): with pytest.raises(AttributeError, match="has no attribute 'not_an_export'"): getattr(obliteratus, "not_an_export") + + +def test_analysis_export_map_covers_the_documented_surface(): + assert set(analysis._LAZY_IMPORTS) == set(analysis.__all__) + assert analysis.CrossLayerAlignmentAnalyzer.__name__ == "CrossLayerAlignmentAnalyzer" + assert "CrossLayerAlignmentAnalyzer" in dir(analysis) + + +def test_unknown_analysis_export_raises_attribute_error(): + with pytest.raises(AttributeError, match="has no attribute 'not_an_export'"): + getattr(analysis, "not_an_export") + + +def test_importing_analysis_package_does_not_eagerly_import_torch(): + probe = subprocess.run( + [ + sys.executable, + "-c", + ( + "import sys; import obliteratus.analysis as analysis; " + "assert 'torch' not in sys.modules; " + "assert not any(name.startswith('obliteratus.analysis.') " + "for name in sys.modules); " + "assert analysis.__all__" + ), + ], + check=False, + capture_output=True, + text=True, + ) + assert probe.returncode == 0, probe.stderr diff --git a/tests/test_telemetry_failure_contracts.py b/tests/test_telemetry_failure_contracts.py index 4dbd31b..fdfd1d9 100644 --- a/tests/test_telemetry_failure_contracts.py +++ b/tests/test_telemetry_failure_contracts.py @@ -59,7 +59,9 @@ def test_telemetry_directory_has_ephemeral_fallback(monkeypatch, tmp_path): monkeypatch.setattr(telemetry, "_ON_HF_SPACES", False) monkeypatch.setattr(telemetry, "_test_writable", lambda _path: False) monkeypatch.setattr(Path, "home", staticmethod(lambda: tmp_path / "home")) - assert telemetry._telemetry_dir() == Path("/tmp/obliteratus_telemetry") + platform_temp = tmp_path / "platform-temp" + monkeypatch.setattr(telemetry.tempfile, "gettempdir", lambda: str(platform_temp)) + assert telemetry._telemetry_dir() == platform_temp / "obliteratus_telemetry" class _HubApi: