Files
odysseus/tests/test_cookbook_error_tail_lines.py
Carles Siles 3e65326c3f fix: expand cookbook error output tail from 12 to 50 lines (#1538)
* fix: expand cookbook error output tail from 12 to 50 lines

When a task reaches status 'error', the status endpoint was returning
only the last 12 lines of the subprocess log. The existing context-menu
'Copy last 50 lines' action was therefore copying the same 12 lines,
making it useless for diagnosing failures that produce long stack traces
or build output.

- Set _tail_lines = 50 when status == 'error', keep 12 for running tasks
- Initialise exit_code = None before the status-classification block so
  it is always defined in the result dict (was only set inside the
  is_alive branch, potential NameError in the dead-session path)
- Include exit_code in the task-status response dict
- JS poller captures exit_code from live data into local task state

The frontend output panel and 'Copy last 50 lines' now show the actual
error context without any UI changes.

* refactor: extract output-tail logic to testable helper + behavioral tests

Addresses review feedback on #1538: the previous tests were source-level
string guards. Extract the tail-slicing into a dependency-free helper
(routes/cookbook_output.error_aware_output_tail) and replace the guards
with behavioral tests that exercise the actual logic:

- error status with a 200-line snapshot -> exactly the last 50 lines
- running/ready/completed/stopped/unknown -> last 12 lines
- short snapshot -> all lines, no padding
- empty snapshot -> empty string
- error tail is a strict superset (suffix-compatible) of the non-error tail

The helper has no FastAPI/SQLAlchemy imports so it unit-tests without
standing up the app.

---------

Co-authored-by: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com>
2026-06-11 17:55:33 +01:00

57 lines
2.1 KiB
Python

"""Behavioral guard for the cookbook error output-tail expansion.
When a task reaches status "error" the status endpoint previously returned
only the last 12 lines of the subprocess log. The "Copy last 50 lines"
context-menu action was therefore copying the same 12 lines — useless for
diagnosing failures that emit long stack traces or build output.
`error_aware_output_tail` now returns the last 50 lines on error and keeps
the cheaper 12-line tail for running/other tasks.
"""
from routes.cookbook_output import error_aware_output_tail
def _snapshot(n):
return "\n".join(f"line {i}" for i in range(n))
def test_error_status_returns_last_50_lines():
snap = _snapshot(200)
tail = error_aware_output_tail(snap, "error")
lines = tail.splitlines()
assert len(lines) == 50, f"error tail should be 50 lines, got {len(lines)}"
assert lines[0] == "line 150"
assert lines[-1] == "line 199"
def test_non_error_status_returns_last_12_lines():
snap = _snapshot(200)
for status in ("running", "ready", "completed", "stopped", "unknown"):
tail = error_aware_output_tail(snap, status)
lines = tail.splitlines()
assert len(lines) == 12, f"{status} tail should be 12 lines, got {len(lines)}"
assert lines[-1] == "line 199"
def test_short_snapshot_returns_all_lines():
# Fewer lines than the cap — return everything, no padding.
snap = _snapshot(5)
assert error_aware_output_tail(snap, "error").splitlines() == [
"line 0", "line 1", "line 2", "line 3", "line 4",
]
assert len(error_aware_output_tail(snap, "running").splitlines()) == 5
def test_empty_snapshot_returns_empty_string():
assert error_aware_output_tail("", "error") == ""
assert error_aware_output_tail("", "running") == ""
def test_error_tail_is_wider_than_non_error():
snap = _snapshot(100)
err = error_aware_output_tail(snap, "error").splitlines()
run = error_aware_output_tail(snap, "running").splitlines()
assert len(err) > len(run)
# The non-error tail is a strict suffix of the error tail.
assert err[-len(run):] == run