mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-08-29 02:26:59 +08:00
Add tests for job-log excerpting
Covers the three call sites that show a job stream to a model: get_job_details, _format_job_context (which re-slices an already-excerpted value down to 500 characters of head), and the get_job_errors tool result.
This commit is contained in:
@@ -2,6 +2,7 @@ from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from galaxy.agents.base import JOB_LOG_EXCERPT_CHARS
|
||||
from galaxy.agents.operations import AgentOperationsManager
|
||||
from galaxy.schema.fields import Security
|
||||
from .base import BaseTestCase
|
||||
@@ -157,6 +158,61 @@ class TestAgentOperationsManagerWithMockedServices(BaseTestCase):
|
||||
assert result["collection"]["elements_truncated"] is True
|
||||
assert result["collection"]["total_elements"] == 10
|
||||
|
||||
def test_get_job_errors_keeps_both_ends_of_a_long_log(self):
|
||||
"""The failure is at the tail, so a head slice would drop the useful part."""
|
||||
job = mock.MagicMock()
|
||||
job.stderr = "HEAD_MARKER\n" + ("noise\n" * 5000) + "TAIL_MARKER: out of memory"
|
||||
job.stdout = "short stdout"
|
||||
job.info = "short info"
|
||||
job.tool_id = "ngm"
|
||||
job.tool_version = "1.0"
|
||||
job.state = "error"
|
||||
job.exit_code = 137
|
||||
job.id = 42
|
||||
|
||||
hda = mock.MagicMock()
|
||||
hda.creating_job = job
|
||||
|
||||
with (
|
||||
mock.patch.object(self.agent_ops.hda_manager, "get_accessible", return_value=hda),
|
||||
mock.patch.object(self.trans.security, "decode_id", return_value=123),
|
||||
mock.patch.object(self.trans.security, "encode_id", return_value="enc42"),
|
||||
):
|
||||
result = self.agent_ops.get_job_errors("encoded_dataset_id")
|
||||
|
||||
assert len(result["stderr"]) <= JOB_LOG_EXCERPT_CHARS
|
||||
assert "HEAD_MARKER" in result["stderr"]
|
||||
assert "TAIL_MARKER" in result["stderr"]
|
||||
assert result["truncated"] is True
|
||||
# Streams that fit are passed through untouched.
|
||||
assert result["stdout"] == "short stdout"
|
||||
assert result["info"] == "short info"
|
||||
|
||||
def test_get_job_errors_counts_info_toward_the_truncated_flag(self):
|
||||
"""Job.info is a TrimmedString(255), but that only trims on the way to the DB."""
|
||||
job = mock.MagicMock()
|
||||
job.stderr = "short stderr"
|
||||
job.stdout = "short stdout"
|
||||
job.info = "I" * 50000
|
||||
job.tool_id = "ngm"
|
||||
job.tool_version = "1.0"
|
||||
job.state = "error"
|
||||
job.exit_code = 1
|
||||
job.id = 42
|
||||
|
||||
hda = mock.MagicMock()
|
||||
hda.creating_job = job
|
||||
|
||||
with (
|
||||
mock.patch.object(self.agent_ops.hda_manager, "get_accessible", return_value=hda),
|
||||
mock.patch.object(self.trans.security, "decode_id", return_value=123),
|
||||
mock.patch.object(self.trans.security, "encode_id", return_value="enc42"),
|
||||
):
|
||||
result = self.agent_ops.get_job_errors("encoded_dataset_id")
|
||||
|
||||
assert len(result["info"]) <= JOB_LOG_EXCERPT_CHARS
|
||||
assert result["truncated"] is True
|
||||
|
||||
def test_get_workflow_details_with_version(self):
|
||||
mock_workflow = mock.MagicMock()
|
||||
|
||||
|
||||
@@ -551,6 +551,38 @@ class TestAgentUnitMocked:
|
||||
assert response.metadata["query_truncated"] is True
|
||||
assert response.metadata["original_query_length"] == 40000
|
||||
|
||||
def test_format_job_context_does_not_reslice_an_excerpted_log(self):
|
||||
"""get_job_details already budgeted this stream; slicing again drops its tail.
|
||||
|
||||
The old 500-char head slice made that budget dead code -- only the first few
|
||||
lines of a failing tool's banner ever reached the model.
|
||||
"""
|
||||
agent = ErrorAnalysisAgent(self.deps)
|
||||
stderr = "HEAD_MARKER" + ("x" * 1500) + "TAIL_MARKER"
|
||||
|
||||
rendered = agent._format_job_context({"tool_id": "ngm", "state": "error", "stderr": stderr})
|
||||
|
||||
assert "HEAD_MARKER" in rendered
|
||||
assert "TAIL_MARKER" in rendered
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_job_details_keeps_the_tail_of_a_long_stderr(self):
|
||||
"""A head slice here would drop the traceback before the prompt is ever built."""
|
||||
agent = ErrorAnalysisAgent(self.deps)
|
||||
job = mock.MagicMock()
|
||||
job.stderr = "HEAD_MARKER\n" + ("noise\n" * 5000) + "TAIL_MARKER: killed"
|
||||
job.stdout = ""
|
||||
job.id = 42
|
||||
|
||||
self.deps.job_manager = mock.Mock()
|
||||
self.deps.job_manager.get_accessible_job.return_value = job
|
||||
|
||||
details = await agent.get_job_details(42)
|
||||
|
||||
assert len(details["stderr"]) <= agents_base.JOB_LOG_EXCERPT_CHARS
|
||||
assert "HEAD_MARKER" in details["stderr"]
|
||||
assert "TAIL_MARKER" in details["stderr"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_error_analysis_trims_oversized_stderr_instead_of_rejecting(self):
|
||||
"""A huge stderr dump gets trimmed and analyzed rather than refused for length.
|
||||
|
||||
Reference in New Issue
Block a user