4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
73 lines
2.8 KiB
Python
73 lines
2.8 KiB
Python
"""Tests for :class:`IncrementalJsonlSource` rotation detection (#2023)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from tools.system.fleet_monitoring.token_sources import IncrementalJsonlSource, _PerPidState
|
|
|
|
|
|
def test_detect_rotation_returns_none_on_transient_stat_error(tmp_path: Path) -> None:
|
|
"""A transient stat failure must leave the cached state intact.
|
|
|
|
Regression: an earlier version reset ``offset`` to ``0`` whenever
|
|
stat raised, so the next time the file reappeared on the same
|
|
inode the source would re-read every historical byte from 0 and
|
|
inflate the 60 s token window. The fix returns ``None`` so the
|
|
caller keeps the cached offset and only re-cold-starts on
|
|
positive rotation evidence (inode change, size regression, or
|
|
mtime regression).
|
|
"""
|
|
missing = tmp_path / "vanished.jsonl" # never created → stat raises
|
|
cached = _PerPidState(log_path=missing, inode=42, mtime=1.0, offset=100)
|
|
|
|
assert IncrementalJsonlSource._detect_rotation(cached) is None
|
|
|
|
|
|
def test_detect_rotation_returns_new_state_on_inode_change(tmp_path: Path) -> None:
|
|
"""Same-path delete+recreate must still trigger a cold-start.
|
|
|
|
The OSError fix above must not regress the inode-change branch:
|
|
that one is positive evidence of rotation, and the new state
|
|
should seek to the new file's EOF.
|
|
"""
|
|
session = tmp_path / "session.jsonl"
|
|
session.write_text("first\n", encoding="utf-8")
|
|
fake_inode = session.stat().st_ino - 1 # any value different from the real one
|
|
cached = _PerPidState(
|
|
log_path=session,
|
|
inode=fake_inode,
|
|
mtime=session.stat().st_mtime,
|
|
offset=0,
|
|
)
|
|
|
|
rotated = IncrementalJsonlSource._detect_rotation(cached)
|
|
assert rotated is not None
|
|
# ``offset`` set to the real file size = "seek to EOF" so we
|
|
# never retro-price the content already on disk.
|
|
assert rotated.offset == session.stat().st_size
|
|
assert rotated.inode == session.stat().st_ino
|
|
|
|
|
|
def test_detect_rotation_returns_new_state_on_size_regression(tmp_path: Path) -> None:
|
|
"""Inode-reused truncate-and-rewrite must also trigger a cold-start.
|
|
|
|
ext4 and APFS happily reuse a freshly-freed inode within the
|
|
same second; without the size-regression signal the inode-change
|
|
branch would miss it. The cached offset is past the new EOF,
|
|
which would otherwise yield negative deltas.
|
|
"""
|
|
session = tmp_path / "session.jsonl"
|
|
session.write_text("hi\n", encoding="utf-8")
|
|
stat = session.stat()
|
|
cached = _PerPidState(
|
|
log_path=session,
|
|
inode=stat.st_ino,
|
|
mtime=stat.st_mtime,
|
|
offset=stat.st_size + 1000, # cached offset > current size
|
|
)
|
|
|
|
rotated = IncrementalJsonlSource._detect_rotation(cached)
|
|
assert rotated is not None
|
|
assert rotated.offset == stat.st_size
|