"""overclock_manager profile storage: load/merge/validate only. Nothing here applies a profile or shells out. CONFIG_PATH is redirected to tmp_path so the repo's real overclock_profiles.json is never written, and conftest's autouse fixture blocks _sh() and every actuation entry point. """ import json import pytest import overclock_manager as ocm @pytest.fixture def config(tmp_path, monkeypatch): """Redirect the profile store to a temp file. Returns a writer for its contents.""" path = tmp_path / "overclock_profiles.json" monkeypatch.setattr(ocm, "CONFIG_PATH", str(path)) def write(data): path.write_text(json.dumps(data)) return type("Cfg", (), {"path": path, "write": staticmethod(write)}) def test_defaults_are_returned_when_no_config_file_exists(config): profiles = ocm.load_profiles() assert set(profiles) == set(ocm.DEFAULT_PROFILES) assert profiles["ollama"]["power_limit_w"] == ocm.DEFAULT_PROFILES["ollama"]["power_limit_w"] def test_stored_values_override_defaults_key_by_key(config): """Only the keys present on disk change; the rest of the default profile survives. A whole-profile replacement would silently drop keys added by a later version.""" config.write({"ollama": {"power_limit_w": 340}}) profiles = ocm.load_profiles() assert profiles["ollama"]["power_limit_w"] == 340 assert profiles["ollama"]["label"] == ocm.DEFAULT_PROFILES["ollama"]["label"] assert profiles["ollama"]["mem_offset_mhz"] == ocm.DEFAULT_PROFILES["ollama"]["mem_offset_mhz"] def test_unknown_stored_profiles_are_preserved(config): """A user-created profile is not in DEFAULT_PROFILES and must not be dropped on load.""" config.write({"my-custom": {"label": "mine", "power_limit_w": 300}}) profiles = ocm.load_profiles() assert profiles["my-custom"]["power_limit_w"] == 300 assert set(ocm.DEFAULT_PROFILES) <= set(profiles) def test_loading_does_not_mutate_the_defaults(config): """load_profiles deep-copies DEFAULT_PROFILES; if it did not, one load with a stored override would poison the defaults for the rest of the process.""" config.write({"ollama": {"power_limit_w": 111}}) ocm.load_profiles() config.write({}) assert ocm.load_profiles()["ollama"]["power_limit_w"] == \ ocm.DEFAULT_PROFILES["ollama"]["power_limit_w"] assert ocm.DEFAULT_PROFILES["ollama"]["power_limit_w"] != 111 def test_corrupt_config_falls_back_to_defaults(config): """A half-written JSON file must not take the whole service down at startup.""" config.path.write_text("{ not valid json") profiles = ocm.load_profiles() assert set(profiles) == set(ocm.DEFAULT_PROFILES) def test_set_profile_rejects_an_unknown_name(config): """set_profile edits an existing profile; it is not a create-anything endpoint.""" res = ocm.set_profile("no-such-profile", {"power_limit_w": 400}) assert res["success"] is False assert "unknown profile" in res["error"] assert not config.path.exists() def test_set_profile_persists_a_partial_update(config): res = ocm.set_profile("comfy", {"power_limit_w": 350}) assert res["success"] is True stored = json.loads(config.path.read_text()) assert stored["comfy"]["power_limit_w"] == 350 # The other profiles are written back untouched. assert stored["ollama"]["power_limit_w"] == ocm.DEFAULT_PROFILES["ollama"]["power_limit_w"] assert ocm.load_profiles()["comfy"]["power_limit_w"] == 350 def test_set_profile_can_edit_a_previously_stored_custom_profile(config): config.write({"my-custom": {"label": "mine", "power_limit_w": 300}}) assert ocm.set_profile("my-custom", {"power_limit_w": 310})["success"] is True assert ocm.load_profiles()["my-custom"]["power_limit_w"] == 310 def test_get_profiles_matches_load_profiles(config): config.write({"ollama": {"core_offset_mhz": 42}}) assert ocm.get_profiles() == ocm.load_profiles() def test_save_profiles_reports_failure_instead_of_raising(tmp_path, monkeypatch): """The dashboard calls this; an unwritable config must surface as success=False.""" monkeypatch.setattr(ocm, "CONFIG_PATH", str(tmp_path / "no-such-dir" / "p.json")) assert ocm.save_profiles({"ollama": {}}) is False @pytest.mark.parametrize("name", ["ollama", "comfy", "balanced"]) def test_default_profiles_declare_the_keys_the_appliers_read(name): """apply_profile and the thermal governor both index these keys directly; a profile missing one would KeyError inside the actuation thread.""" cfg = ocm.DEFAULT_PROFILES[name] for key in ("label", "power_limit_w", "core_offset_mhz", "mem_offset_mhz", "lock_core_min", "lock_core_max"): assert key in cfg, f"{name} is missing {key}" def test_default_power_limits_stay_within_the_cards_range(): """RTX 4080 SUPER: 320 W stock, 370 W maximum. A profile above that is silently clamped by the driver and the sweep results become meaningless.""" for name, cfg in ocm.DEFAULT_PROFILES.items(): assert 100 <= cfg["power_limit_w"] <= 370, name