| 1 |
"""Path resolution + layout creation.""" |
| 2 |
|
| 3 |
from __future__ import annotations |
| 4 |
|
| 5 |
from pathlib import Path |
| 6 |
|
| 7 |
import pytest |
| 8 |
|
| 9 |
from dlm.store import errors |
| 10 |
from dlm.store.layout import ( |
| 11 |
ADAPTER_DIR, |
| 12 |
LOCK_FILENAME, |
| 13 |
LOGS_DIR, |
| 14 |
MANIFEST_FILENAME, |
| 15 |
) |
| 16 |
from dlm.store.paths import StorePath, _current_os_name, dlm_home, ensure_home, for_dlm |
| 17 |
|
| 18 |
VALID_ID = "01HZ4X7TGZM3J1A2B3C4D5E6F7" |
| 19 |
|
| 20 |
|
| 21 |
class TestDlmHome: |
| 22 |
def test_current_os_name_passthrough(self) -> None: |
| 23 |
import os |
| 24 |
|
| 25 |
assert _current_os_name() == os.name |
| 26 |
|
| 27 |
def test_override_takes_precedence(self, tmp_path: Path) -> None: |
| 28 |
assert dlm_home(override=tmp_path / "custom") == (tmp_path / "custom").resolve() |
| 29 |
|
| 30 |
def test_env_var_respected(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: |
| 31 |
monkeypatch.setenv("DLM_HOME", str(tmp_path / "env-home")) |
| 32 |
assert dlm_home() == (tmp_path / "env-home").resolve() |
| 33 |
|
| 34 |
def test_explicit_override_beats_env( |
| 35 |
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path |
| 36 |
) -> None: |
| 37 |
monkeypatch.setenv("DLM_HOME", str(tmp_path / "env")) |
| 38 |
override = tmp_path / "cli" |
| 39 |
assert dlm_home(override=override) == override.resolve() |
| 40 |
|
| 41 |
def test_default_on_posix(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: |
| 42 |
monkeypatch.delenv("DLM_HOME", raising=False) |
| 43 |
monkeypatch.setattr("dlm.store.paths._current_os_name", lambda: "posix") |
| 44 |
monkeypatch.setattr(Path, "home", lambda: tmp_path / "u") |
| 45 |
assert dlm_home() == tmp_path / "u" / ".dlm" |
| 46 |
|
| 47 |
def test_default_on_nt_prefers_appdata( |
| 48 |
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path |
| 49 |
) -> None: |
| 50 |
monkeypatch.delenv("DLM_HOME", raising=False) |
| 51 |
monkeypatch.setenv("APPDATA", str(tmp_path / "AppData" / "Roaming")) |
| 52 |
monkeypatch.setattr("dlm.store.paths._current_os_name", lambda: "nt") |
| 53 |
assert dlm_home() == (tmp_path / "AppData" / "Roaming").resolve() / "dlm" |
| 54 |
|
| 55 |
|
| 56 |
class TestEnsureHome: |
| 57 |
def test_creates_store_subdir(self, tmp_path: Path) -> None: |
| 58 |
home = ensure_home(override=tmp_path / "h") |
| 59 |
assert home.exists() |
| 60 |
assert (home / "store").exists() |
| 61 |
|
| 62 |
|
| 63 |
class TestForDlm: |
| 64 |
def test_returns_store_path_under_home(self, tmp_path: Path) -> None: |
| 65 |
sp = for_dlm(VALID_ID, home=tmp_path) |
| 66 |
assert sp.root == tmp_path.resolve() / "store" / VALID_ID |
| 67 |
|
| 68 |
def test_empty_id_rejected(self, tmp_path: Path) -> None: |
| 69 |
with pytest.raises(ValueError, match="non-empty"): |
| 70 |
for_dlm("", home=tmp_path) |
| 71 |
|
| 72 |
|
| 73 |
class TestStorePathAccessors: |
| 74 |
@pytest.fixture |
| 75 |
def store(self, tmp_path: Path) -> StorePath: |
| 76 |
return for_dlm(VALID_ID, home=tmp_path) |
| 77 |
|
| 78 |
def test_manifest_path(self, store: StorePath) -> None: |
| 79 |
assert store.manifest.name == MANIFEST_FILENAME |
| 80 |
assert store.manifest.parent == store.root |
| 81 |
|
| 82 |
def test_lock_path(self, store: StorePath) -> None: |
| 83 |
assert store.lock.name == LOCK_FILENAME |
| 84 |
|
| 85 |
def test_training_state_paths(self, store: StorePath) -> None: |
| 86 |
assert store.training_state.name == "training_state.pt" |
| 87 |
assert store.training_state_sha.name == "training_state.pt.sha256" |
| 88 |
|
| 89 |
def test_adapter_subpaths(self, store: StorePath) -> None: |
| 90 |
assert store.adapter.name == ADAPTER_DIR |
| 91 |
assert store.adapter_versions.parent == store.adapter |
| 92 |
assert store.adapter_version(1).name == "v0001" |
| 93 |
assert store.adapter_version(1234).name == "v1234" |
| 94 |
|
| 95 |
def test_logs_dir(self, store: StorePath) -> None: |
| 96 |
assert store.logs.name == LOGS_DIR |
| 97 |
|
| 98 |
def test_replay_paths(self, store: StorePath) -> None: |
| 99 |
assert store.replay_corpus.name == "corpus.zst" |
| 100 |
assert store.replay_index.name == "index.json" |
| 101 |
|
| 102 |
def test_adapter_version_zero_rejected(self, store: StorePath) -> None: |
| 103 |
with pytest.raises(ValueError, match="1-indexed"): |
| 104 |
store.adapter_version(0) |
| 105 |
|
| 106 |
def test_cache_dir_for_slug(self, store: StorePath) -> None: |
| 107 |
assert store.cache_dir_for("qwen2.5-1.5b").name == "qwen2.5-1.5b" |
| 108 |
|
| 109 |
def test_cache_dir_for_empty_rejected(self, store: StorePath) -> None: |
| 110 |
with pytest.raises(ValueError): |
| 111 |
store.cache_dir_for("") |
| 112 |
|
| 113 |
def test_export_quant_dir(self, store: StorePath) -> None: |
| 114 |
assert store.export_quant_dir("Q4_K_M").name == "Q4_K_M" |
| 115 |
|
| 116 |
def test_export_quant_empty_rejected(self, store: StorePath) -> None: |
| 117 |
with pytest.raises(ValueError): |
| 118 |
store.export_quant_dir("") |
| 119 |
|
| 120 |
def test_blob_dir(self, store: StorePath) -> None: |
| 121 |
assert store.blob_dir.name == "blobs" |
| 122 |
assert store.blob_dir.parent == store.root |
| 123 |
|
| 124 |
def test_vl_cache_dir(self, store: StorePath) -> None: |
| 125 |
assert store.vl_cache_dir.name == "vl-cache" |
| 126 |
assert store.vl_cache_dir.parent == store.root |
| 127 |
|
| 128 |
def test_other_lazy_dirs(self, store: StorePath) -> None: |
| 129 |
assert store.tokenized_cache_dir.name == "tokenized-cache" |
| 130 |
assert store.audio_cache_dir.name == "audio-cache" |
| 131 |
assert store.audio_waveform_cache_dir.name == "audio-waveform-cache" |
| 132 |
assert store.controls_dir.name == "controls" |
| 133 |
assert store.control_file("demo").name == "demo.safetensors" |
| 134 |
assert store.control_meta("demo").name == "demo.meta.json" |
| 135 |
|
| 136 |
def test_blob_and_vl_cache_lazy(self, tmp_path: Path) -> None: |
| 137 |
sp = for_dlm(VALID_ID, home=tmp_path) |
| 138 |
sp.ensure_layout() |
| 139 |
assert not sp.blob_dir.exists() |
| 140 |
assert not sp.vl_cache_dir.exists() |
| 141 |
|
| 142 |
def test_exists_reflects_store_root(self, tmp_path: Path) -> None: |
| 143 |
sp = for_dlm(VALID_ID, home=tmp_path) |
| 144 |
assert sp.exists() is False |
| 145 |
sp.ensure_layout() |
| 146 |
assert sp.exists() is True |
| 147 |
|
| 148 |
|
| 149 |
class TestEnsureLayout: |
| 150 |
@pytest.fixture |
| 151 |
def store(self, tmp_path: Path) -> StorePath: |
| 152 |
sp = for_dlm(VALID_ID, home=tmp_path) |
| 153 |
sp.ensure_layout() |
| 154 |
return sp |
| 155 |
|
| 156 |
def test_root_created(self, store: StorePath) -> None: |
| 157 |
assert store.root.is_dir() |
| 158 |
|
| 159 |
def test_always_on_dirs_created(self, store: StorePath) -> None: |
| 160 |
assert store.adapter.is_dir() |
| 161 |
assert store.adapter_versions.is_dir() |
| 162 |
assert store.logs.is_dir() |
| 163 |
|
| 164 |
def test_lazy_dirs_not_created(self, store: StorePath) -> None: |
| 165 |
# Sprint 08/11/06 own these; Sprint 04 must not materialize them. |
| 166 |
assert not store.replay.exists() |
| 167 |
assert not store.exports.exists() |
| 168 |
assert not store.cache.exists() |
| 169 |
|
| 170 |
def test_is_idempotent(self, store: StorePath) -> None: |
| 171 |
store.ensure_layout() |
| 172 |
store.ensure_layout() |
| 173 |
assert store.adapter.is_dir() |
| 174 |
|
| 175 |
|
| 176 |
class TestAdapterCurrentPointer: |
| 177 |
@pytest.fixture |
| 178 |
def store(self, tmp_path: Path) -> StorePath: |
| 179 |
sp = for_dlm(VALID_ID, home=tmp_path) |
| 180 |
sp.ensure_layout() |
| 181 |
return sp |
| 182 |
|
| 183 |
def test_resolve_returns_none_when_absent(self, store: StorePath) -> None: |
| 184 |
assert store.resolve_current_adapter() is None |
| 185 |
|
| 186 |
def test_set_and_resolve_roundtrip(self, store: StorePath) -> None: |
| 187 |
version_dir = store.adapter_version(1) |
| 188 |
version_dir.mkdir(parents=True, exist_ok=True) |
| 189 |
store.set_current_adapter(version_dir) |
| 190 |
resolved = store.resolve_current_adapter() |
| 191 |
assert resolved == version_dir.resolve() |
| 192 |
|
| 193 |
def test_set_rejects_outside_root(self, store: StorePath, tmp_path: Path) -> None: |
| 194 |
rogue = tmp_path / "rogue" |
| 195 |
rogue.mkdir() |
| 196 |
with pytest.raises(ValueError, match="outside store root"): |
| 197 |
store.set_current_adapter(rogue) |
| 198 |
|
| 199 |
def test_empty_pointer_returns_none(self, store: StorePath) -> None: |
| 200 |
store.adapter_current_pointer.write_text("", encoding="utf-8") |
| 201 |
assert store.resolve_current_adapter() is None |
| 202 |
|
| 203 |
def test_resolve_rejects_escape_via_parent_refs(self, store: StorePath) -> None: |
| 204 |
# An adversarial pointer pointing outside the root (`..`) must fail. |
| 205 |
store.adapter_current_pointer.write_text("../../escape", encoding="utf-8") |
| 206 |
with pytest.raises(ValueError, match="escapes store root"): |
| 207 |
store.resolve_current_adapter() |
| 208 |
|
| 209 |
|
| 210 |
class TestErrorsSurface: |
| 211 |
"""Sanity-check the errors module exports.""" |
| 212 |
|
| 213 |
def test_unknown_store_error_message(self, tmp_path: Path) -> None: |
| 214 |
err = errors.UnknownStoreError("deadbeef", tmp_path) |
| 215 |
assert "deadbeef" in str(err) |