Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/chrys/service/hooks/run_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,19 @@ def indented_json(value: object) -> str:
def _clip_text(text: str, limit: int = HOOK_RUN_TEXT_MAX_BYTES) -> tuple[str, int]:
"""*text* cut to at most *limit* UTF-8 bytes, and its full size in them.

A cut drops the character it splits, and every lone surrogate of the text.
A cut drops the character it splits; lone surrogates, which a path that is not
UTF-8 holds, are kept as they are.
"""
if text.isascii():
return text[:limit], len(text)
data = text.encode("utf-8", "surrogatepass")
if len(data) <= limit:
return text, len(data)
return data[:limit].decode("utf-8", "ignore"), len(data)
cut = limit
# Back to the first byte of the character the limit splits: its others are 10xxxxxx.
while cut and data[cut] & 0xC0 == 0x80:
cut -= 1
return data[:cut].decode("utf-8", "surrogatepass"), len(data)


class HookRunLog:
Expand Down
3 changes: 1 addition & 2 deletions tests/orchestration/engine/test_hook_run_reporting.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from chrys.service.hooks.run_log import HookRunLog
from chrys.service.hooks.schema import HookConfig, HookExecution, HookMatch, HookRun, HookSettings, HooksFile
from chrys.service.trajectory.hooks import HookOutcome
from tests.support.waiting import ENGINE_TURN_TIMEOUT, wait_for, wait_until
from tests.support.waiting import ENGINE_TURN_TIMEOUT, wait_for

_SESSION = "s1"

Expand Down Expand Up @@ -267,7 +267,6 @@ async def explode(_event: HookRunsChanged) -> None:
assert decision.blocked is True
(record,) = log.records()
assert (record.state, record.effect) == ("ended", "blocked")
assert not await wait_until(lambda: len(log.records()) != 1, timeout=0.2)


def test_the_notice_does_not_reach_headless_or_acp_output() -> None:
Expand Down
6 changes: 3 additions & 3 deletions tests/service/hooks/test_run_log_retention.py
Original file line number Diff line number Diff line change
Expand Up @@ -541,10 +541,10 @@ def test_changed_arguments_past_the_cap_are_cut_with_their_size_kept(unit: str)
detail = log.detail("rewrite")
assert detail is not None and detail.applied is not None
kept = detail.applied.args
assert len(kept.encode("utf-8", "surrogatepass")) <= HOOK_RUN_TEXT_MAX_BYTES
# Cut short of the limit by the character it would split, at most: a path that is not UTF-8 included.
assert HOOK_RUN_TEXT_MAX_BYTES - 4 < len(kept.encode("utf-8", "surrogatepass")) <= HOOK_RUN_TEXT_MAX_BYTES
assert detail.applied.args_size == len(full.encode("utf-8", "surrogatepass"))
if unit != "\udc80":
assert full.startswith(kept)
assert full.startswith(kept)


def test_changed_arguments_within_the_cap_are_kept_whole() -> None:
Expand Down
Loading