mirror of
https://github.com/elder-plinius/OBLITERATUS.git
synced 2026-08-18 00:47:23 +02:00
216 lines
7.1 KiB
Python
216 lines
7.1 KiB
Python
"""Boundary contracts for deterministic and transactional checkpoint state."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
import torch
|
|
|
|
import obliteratus.persistence_contracts as persistence
|
|
|
|
|
|
pytestmark = pytest.mark.cpu
|
|
|
|
@pytest.mark.parametrize(
|
|
("state_dict", "expected"),
|
|
[
|
|
({}, 0),
|
|
({"float": torch.ones(3, dtype=torch.float32)}, 12),
|
|
(
|
|
{
|
|
"half": torch.ones(5, dtype=torch.float16),
|
|
"index": torch.ones(2, dtype=torch.int64),
|
|
},
|
|
26,
|
|
),
|
|
],
|
|
)
|
|
def test_state_dict_size_bytes_is_exact(state_dict, expected):
|
|
assert persistence.state_dict_size_bytes(state_dict) == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("payload", "required"),
|
|
[(0, 0), (1, 2), (9, 10), (10, 11), (101, 112)],
|
|
)
|
|
def test_required_checkpoint_bytes_rounds_headroom_up(payload, required):
|
|
assert persistence.required_checkpoint_bytes(payload) == required
|
|
|
|
|
|
def test_required_checkpoint_bytes_rejects_negative_payload():
|
|
with pytest.raises(ValueError) as exc_info:
|
|
persistence.required_checkpoint_bytes(-1)
|
|
|
|
assert str(exc_info.value) == "checkpoint payload size cannot be negative"
|
|
|
|
|
|
def test_checkpoint_capacity_accepts_exact_boundary_and_rejects_one_byte_less():
|
|
persistence.ensure_checkpoint_capacity(free_bytes=11, payload_bytes=10)
|
|
|
|
with pytest.raises(
|
|
OSError,
|
|
match=r"Insufficient disk space: 0\.0 GB free, need ~0\.0 GB",
|
|
):
|
|
persistence.ensure_checkpoint_capacity(free_bytes=10, payload_bytes=10)
|
|
|
|
|
|
def test_checkpoint_capacity_error_reports_decimal_gigabytes_exactly():
|
|
payload_bytes = 1_000_000_000_000_000_000
|
|
|
|
with pytest.raises(OSError) as exc_info:
|
|
persistence.ensure_checkpoint_capacity(
|
|
free_bytes=payload_bytes,
|
|
payload_bytes=payload_bytes,
|
|
)
|
|
|
|
assert str(exc_info.value) == (
|
|
"Insufficient disk space: 1000000000.0 GB free, "
|
|
"need ~1000000000.0 GB. "
|
|
"Try a different --output-dir on a larger filesystem."
|
|
)
|
|
|
|
|
|
def test_metadata_serialization_is_stable_strict_json():
|
|
first = persistence.serialize_checkpoint_metadata({"z": 1, "a": [True, None]})
|
|
second = persistence.serialize_checkpoint_metadata({"a": [True, None], "z": 1})
|
|
|
|
assert first == second
|
|
assert json.loads(first) == {"a": [True, None], "z": 1}
|
|
assert first == (
|
|
'{\n "a": [\n true,\n null\n ],\n "z": 1\n}'
|
|
)
|
|
|
|
|
|
def test_metadata_serializer_passes_strict_format_options(monkeypatch):
|
|
real_dumps = json.dumps
|
|
observed = {}
|
|
|
|
def record_dumps(metadata, **kwargs):
|
|
observed.update(kwargs)
|
|
return real_dumps(metadata, **kwargs)
|
|
|
|
monkeypatch.setattr(persistence.json, "dumps", record_dumps)
|
|
|
|
assert json.loads(persistence.serialize_checkpoint_metadata({"schema": 1})) == {
|
|
"schema": 1,
|
|
}
|
|
assert observed == {"indent": 2, "sort_keys": True, "allow_nan": False}
|
|
|
|
|
|
@pytest.mark.parametrize("value", [object(), float("nan"), float("inf")])
|
|
def test_metadata_serialization_rejects_nonportable_values(value):
|
|
with pytest.raises((TypeError, ValueError)):
|
|
persistence.serialize_checkpoint_metadata({"invalid": value})
|
|
|
|
|
|
def test_remove_checkpoint_path_handles_file_directory_symlink_and_missing(tmp_path):
|
|
file_path = tmp_path / "file"
|
|
file_path.write_text("data", encoding="utf-8")
|
|
directory = tmp_path / "directory"
|
|
directory.mkdir()
|
|
(directory / "nested").write_text("data", encoding="utf-8")
|
|
target = tmp_path / "target"
|
|
target.mkdir()
|
|
(target / "sentinel").write_text("preserve", encoding="utf-8")
|
|
symlink = tmp_path / "link"
|
|
symlink.symlink_to(target, target_is_directory=True)
|
|
|
|
for path in (file_path, directory, symlink, tmp_path / "missing"):
|
|
persistence._remove_checkpoint_path(path)
|
|
|
|
assert not file_path.exists()
|
|
assert not directory.exists()
|
|
assert not symlink.exists()
|
|
assert (target / "sentinel").read_text(encoding="utf-8") == "preserve"
|
|
|
|
|
|
def test_remove_checkpoint_path_requests_race_safe_file_unlink():
|
|
path = MagicMock()
|
|
path.is_symlink.return_value = True
|
|
|
|
persistence._remove_checkpoint_path(path)
|
|
|
|
path.unlink.assert_called_once_with(missing_ok=True)
|
|
path.is_file.assert_not_called()
|
|
|
|
|
|
def test_remove_checkpoint_path_requests_best_effort_directory_cleanup(monkeypatch):
|
|
path = MagicMock()
|
|
path.is_symlink.return_value = False
|
|
path.is_file.return_value = False
|
|
path.exists.return_value = True
|
|
remove_tree = MagicMock()
|
|
monkeypatch.setattr(persistence.shutil, "rmtree", remove_tree)
|
|
|
|
persistence._remove_checkpoint_path(path)
|
|
|
|
remove_tree.assert_called_once_with(path, ignore_errors=True)
|
|
|
|
|
|
def test_atomic_checkpoint_creates_parent_and_promotes_new_destination(tmp_path):
|
|
destination = tmp_path / "nested" / "checkpoint"
|
|
|
|
with persistence.atomic_checkpoint_directory(destination) as staging:
|
|
assert staging.parent == destination.parent
|
|
assert staging.name.startswith(".checkpoint.staging-")
|
|
(staging / "model.bin").write_bytes(b"complete")
|
|
|
|
assert (destination / "model.bin").read_bytes() == b"complete"
|
|
assert list(destination.parent.glob(".checkpoint.*-*")) == []
|
|
|
|
|
|
def test_atomic_checkpoint_cleans_staging_when_new_destination_promotion_fails(
|
|
tmp_path,
|
|
monkeypatch,
|
|
):
|
|
destination = tmp_path / "checkpoint"
|
|
|
|
def fail_promotion(source, target):
|
|
raise OSError(f"cannot promote {Path(source).name} to {Path(target).name}")
|
|
|
|
monkeypatch.setattr(persistence.os, "replace", fail_promotion)
|
|
with pytest.raises(OSError, match="cannot promote"):
|
|
with persistence.atomic_checkpoint_directory(destination) as staging:
|
|
(staging / "model.bin").write_bytes(b"partial")
|
|
|
|
assert not destination.exists()
|
|
assert list(tmp_path.glob(".checkpoint.staging-*")) == []
|
|
|
|
|
|
def test_atomic_checkpoint_replaces_symlink_without_touching_target(tmp_path):
|
|
target = tmp_path / "target"
|
|
target.mkdir()
|
|
sentinel = target / "sentinel"
|
|
sentinel.write_text("preserve", encoding="utf-8")
|
|
destination = tmp_path / "checkpoint"
|
|
destination.symlink_to(target, target_is_directory=True)
|
|
|
|
with persistence.atomic_checkpoint_directory(destination) as staging:
|
|
(staging / "model.bin").write_bytes(b"replacement")
|
|
|
|
assert not destination.is_symlink()
|
|
assert (destination / "model.bin").read_bytes() == b"replacement"
|
|
assert sentinel.read_text(encoding="utf-8") == "preserve"
|
|
|
|
|
|
def test_atomic_checkpoint_uses_os_replace_not_copy(tmp_path, monkeypatch):
|
|
destination = tmp_path / "checkpoint"
|
|
calls = []
|
|
real_replace = os.replace
|
|
|
|
def record_replace(source, target):
|
|
calls.append((Path(source), Path(target)))
|
|
return real_replace(source, target)
|
|
|
|
monkeypatch.setattr(persistence.os, "replace", record_replace)
|
|
with persistence.atomic_checkpoint_directory(destination) as staging:
|
|
(staging / "model.bin").write_bytes(b"saved")
|
|
|
|
assert len(calls) == 1
|
|
assert calls[0][1] == destination
|
|
assert ".staging-" in calls[0][0].name
|