diff --git a/backend/tests/test_utils_bash_exit_boundaries.py b/backend/tests/test_utils_bash_exit_boundaries.py new file mode 100644 index 0000000000..3327ad2f38 --- /dev/null +++ b/backend/tests/test_utils_bash_exit_boundaries.py @@ -0,0 +1,51 @@ +"""Boundary coverage for ``deerflow.utils.bash_exit``. + +``backend/tests/test_tool_result_meta.py`` already drives the same markers +through ``normalize_tool_message``. This file pins the direct return contract +of ``bash_exit_status`` -- the exact ``(status, marker)`` tuple -- for the +inputs where string handling, sign, or unbounded length matters. The few cases +that overlap the existing suite are kept as the baseline for those assertions. +""" + +from __future__ import annotations + +import sys + +from deerflow.utils.bash_exit import bash_exit_status + + +def test_exit_code_tail_is_recognised_only_at_the_end() -> None: + assert bash_exit_status("output\nExit Code: 0") == ("success", "Exit Code: 0") + assert bash_exit_status("output\nExit Code: 1") == ("error", "Exit Code: 1") + + +def test_embedded_phrase_in_ordinary_output_is_not_a_marker() -> None: + assert bash_exit_status("he said Exit Code: 0 then left") is None + + +def test_signed_zero_is_success() -> None: + assert bash_exit_status("Command exited with code -0") == ( + "success", + "Command exited with code -0", + ) + + +def test_negative_and_timeout_codes_are_errors() -> None: + for code in ("-1", "124", "137"): + assert bash_exit_status(f"Exit Code: {code}") == ("error", f"Exit Code: {code}") + + +def test_unbounded_digits_are_compared_as_strings() -> None: + # CPython >= 3.11 refuses int() on digit strings longer than + # sys.get_int_max_str_digits() (4300 by default). Exceeding that cap makes + # an implementation regressed to int(match.group(2)) raise ValueError here + # instead of silently returning the same ("error", marker) tuple. + # A disabled cap (0) falls back to the default so the test still uses a + # length far beyond the standard conversion limit. + limit = sys.get_int_max_str_digits() or 4300 + huge = "9" * (limit + 1) + assert bash_exit_status(f"Exit Code: {huge}") == ("error", f"Exit Code: {huge}") + + +def test_trailing_whitespace_after_tail_is_allowed() -> None: + assert bash_exit_status("done\nExit Code: 0 \n") == ("success", "Exit Code: 0") diff --git a/backend/tests/test_utils_goal_objective_boundaries.py b/backend/tests/test_utils_goal_objective_boundaries.py new file mode 100644 index 0000000000..f5a8d26415 --- /dev/null +++ b/backend/tests/test_utils_goal_objective_boundaries.py @@ -0,0 +1,32 @@ +"""Boundary coverage for ``deerflow.utils.goal_objective``.""" + +from __future__ import annotations + +import pytest + +from deerflow.utils.goal_objective import MAX_GOAL_OBJECTIVE_CHARS, normalize_goal_objective + + +def test_normalize_goal_objective_collapses_all_whitespace() -> None: + assert normalize_goal_objective(" a \n\t b c ") == "a b c" + + +def test_normalize_goal_objective_rejects_blank_only_input() -> None: + for blank in ("", " ", "\n\t\r "): + with pytest.raises(ValueError, match="must not be empty"): + normalize_goal_objective(blank) + + +def test_normalize_goal_objective_accepts_exact_limit() -> None: + text = "x" * MAX_GOAL_OBJECTIVE_CHARS + assert normalize_goal_objective(text) == text + + +def test_normalize_goal_objective_rejects_one_over_limit() -> None: + with pytest.raises(ValueError, match="at most"): + normalize_goal_objective("x" * (MAX_GOAL_OBJECTIVE_CHARS + 1)) + + +def test_normalize_goal_objective_counts_characters_not_bytes() -> None: + text = "\u4e2d" * MAX_GOAL_OBJECTIVE_CHARS + assert normalize_goal_objective(text) == text