fix: stop masking runtime failures

This commit is contained in:
chengyongru
2026-07-21 11:44:52 +08:00
committed by chengyongru
parent afc65c086e
commit dfc3919b52
22 changed files with 446 additions and 300 deletions
+8 -6
View File
@@ -7,7 +7,7 @@ from unittest.mock import patch
import pytest
from nanobot.utils.gitstore import GitStore
from nanobot.utils.gitstore import GitStore, GitStoreError
@pytest.fixture
@@ -63,11 +63,13 @@ class TestLineAges:
assert len(ages) == 2
assert all(a.age_days == 30 for a in ages)
def test_annotate_failure_returns_empty(self, tmp_path):
"""If annotate fails, line_ages should return [] gracefully."""
git = GitStore(tmp_path, tracked_files=["MEMORY.md"])
# Don't init — annotate will fail
assert git.line_ages("MEMORY.md") == []
def test_annotate_failure_is_explicit(self, git, tmp_path):
(tmp_path / "MEMORY.md").write_text("important\n", encoding="utf-8")
git.auto_commit("initial")
with patch("dulwich.porcelain.annotate", side_effect=OSError("broken repo")):
with pytest.raises(GitStoreError, match="annotation failed"):
git.line_ages("MEMORY.md")
def test_partial_edit_only_updates_changed_lines(self, git, tmp_path):
"""Only modified lines should reflect the new commit's timestamp."""
+13 -1
View File
@@ -1,9 +1,16 @@
from pathlib import Path
from zoneinfo import ZoneInfoNotFoundError
import pytest
import tiktoken
from nanobot.utils import helpers
from nanobot.utils.helpers import _write_text_atomic, split_message, truncate_text_to_tokens
from nanobot.utils.helpers import (
_write_text_atomic,
current_time_str,
split_message,
truncate_text_to_tokens,
)
def test_split_message_no_code_blocks_unchanged():
@@ -43,6 +50,11 @@ def test_truncate_text_to_tokens_non_positive_budget_returns_text():
assert truncate_text_to_tokens(text, 0) == text
def test_current_time_str_rejects_unknown_timezone():
with pytest.raises(ZoneInfoNotFoundError):
current_time_str("Not/AZone")
def test_write_text_atomic_fsyncs_file_and_parent_directory(
tmp_path: Path, monkeypatch
) -> None:
+57 -1
View File
@@ -1,7 +1,12 @@
import json
from nanobot.utils import helpers
from nanobot.utils.helpers import estimate_prompt_tokens, estimate_prompt_tokens_chain
from nanobot.utils.helpers import (
estimate_message_tokens,
estimate_prompt_tokens,
estimate_prompt_tokens_chain,
truncate_text_to_tokens,
)
class _NoCounterProvider:
@@ -35,6 +40,57 @@ def test_estimate_prompt_tokens_chain_falls_back_when_provider_counter_fails() -
assert source == "tiktoken"
def test_estimate_prompt_tokens_uses_conservative_fallback_when_tiktoken_fails(
monkeypatch,
) -> None:
monkeypatch.setattr(
helpers,
"_get_token_encoding",
lambda: (_ for _ in ()).throw(RuntimeError("encoding unavailable")),
)
content = "" * 1_000
messages = [{"role": "user", "content": content}]
tokens = estimate_prompt_tokens(messages)
chain_tokens, source = estimate_prompt_tokens_chain(
_NoCounterProvider(),
"test-model",
messages,
)
actual_tokens = len(helpers.tiktoken.get_encoding("cl100k_base").encode(content)) + 4
assert tokens == len(content.encode("utf-8")) + 4
assert tokens >= actual_tokens
assert chain_tokens == tokens
assert source == "heuristic"
def test_estimate_message_tokens_uses_utf8_byte_fallback(monkeypatch) -> None:
monkeypatch.setattr(
helpers,
"_get_token_encoding",
lambda: (_ for _ in ()).throw(RuntimeError("encoding unavailable")),
)
content = "🙂你" * 100
assert estimate_message_tokens({"role": "user", "content": content}) == (
len(content.encode("utf-8")) + 4
)
def test_truncate_text_to_tokens_uses_utf8_byte_budget_fallback(monkeypatch) -> None:
monkeypatch.setattr(
helpers,
"_get_token_encoding",
lambda: (_ for _ in ()).throw(RuntimeError("encoding unavailable")),
)
result = truncate_text_to_tokens("🙂你" * 100, 40)
assert result.endswith("\n... (truncated)")
assert len(result.encode("utf-8")) <= 40
def test_estimate_prompt_tokens_caches_tools_encoding(monkeypatch) -> None:
helpers._get_token_encoding.cache_clear()
helpers._TOOLS_TOKEN_CACHE.clear()