mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-26 18:35:05 +00:00
fix: handle classifier parse failures at CLI boundary
This commit is contained in:
3 files changed
+106
-2
No files matched your search
@@ -10,7 +10,7 @@
|
|||||||
"id": "hand-seeded",
|
"id": "hand-seeded",
|
||||||
"source": "manual-curation-pending-regeneration"
|
"source": "manual-curation-pending-regeneration"
|
||||||
},
|
},
|
||||||
"registryDigest": "2542269fda9f4e608786d0d9ecbe3dc9598220319054eb66d7d45c351959effd",
|
"registryDigest": "c3c0f6e6d1dcfbad7794ae46911da79ed9fffa69678df77bde28e1c3d5b41678",
|
||||||
"schemaVersion": 1,
|
"schemaVersion": 1,
|
||||||
"triggers": {
|
"triggers": {
|
||||||
"skill:agent-runtime-gateway-smoke-test": [
|
"skill:agent-runtime-gateway-smoke-test": [
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from typing import Any
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
from scripts.classifier import ClassifierParseError
|
||||||
from scripts.grader import grade
|
from scripts.grader import grade
|
||||||
from scripts.report import generate_report
|
from scripts.report import generate_report
|
||||||
from scripts.runner import run_scenario
|
from scripts.runner import run_scenario
|
||||||
@@ -95,7 +96,14 @@ def main() -> None:
|
|||||||
for scenario in scenarios:
|
for scenario in scenarios:
|
||||||
logger.info(" Running %s...", scenario.level_name)
|
logger.info(" Running %s...", scenario.level_name)
|
||||||
run = run_scenario(scenario, model=args.model)
|
run = run_scenario(scenario, model=args.model)
|
||||||
result = grade(spec, list(run.observations))
|
try:
|
||||||
|
result = grade(spec, list(run.observations))
|
||||||
|
except ClassifierParseError:
|
||||||
|
logger.error(
|
||||||
|
"ClassifierParseError: Classification failed; "
|
||||||
|
"invalid classifier output. No report generated."
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
graded_results.append((scenario.level_name, result, list(run.observations)))
|
graded_results.append((scenario.level_name, result, list(run.observations)))
|
||||||
logger.info(" %s: %.0f%%", scenario.level_name, result.compliance_rate * 100)
|
logger.info(" %s: %.0f%%", scenario.level_name, result.compliance_rate * 100)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
"""Tests for the CLI's classification failure boundary."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from types import SimpleNamespace
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from scripts import run
|
||||||
|
from scripts.classifier import ClassifierParseError
|
||||||
|
from scripts.parser import parse_spec, parse_trace
|
||||||
|
|
||||||
|
FIXTURES = Path(__file__).parent.parent / "fixtures"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def cli(monkeypatch, tmp_path):
|
||||||
|
skill = tmp_path / "fixture.md"
|
||||||
|
skill.write_text("Read a file before editing it.\n")
|
||||||
|
output = tmp_path / "report.md"
|
||||||
|
spec = parse_spec(FIXTURES / "tdd_spec.yaml")
|
||||||
|
observations = tuple(parse_trace(FIXTURES / "compliant_trace.jsonl"))
|
||||||
|
scenarios = [
|
||||||
|
SimpleNamespace(level_name=name, description="Controlled scenario")
|
||||||
|
for name in ("supportive", "neutral")
|
||||||
|
]
|
||||||
|
monkeypatch.setattr("sys.argv", ["skill-comply", str(skill), "--output", str(output)])
|
||||||
|
monkeypatch.setattr(run, "__file__", str(tmp_path / "scripts" / "run.py"))
|
||||||
|
monkeypatch.setattr(run, "generate_spec", Mock(return_value=spec))
|
||||||
|
monkeypatch.setattr(run, "generate_scenarios", Mock(return_value=scenarios))
|
||||||
|
execute = Mock(return_value=SimpleNamespace(observations=observations))
|
||||||
|
monkeypatch.setattr(run, "run_scenario", execute)
|
||||||
|
report = Mock(return_value="Controlled compliance report\n")
|
||||||
|
monkeypatch.setattr(run, "generate_report", report)
|
||||||
|
return SimpleNamespace(output=output, report=report, execute=execute)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("failure_index", [0, 1])
|
||||||
|
def test_classifier_failure_exits_without_report_or_raw_output(
|
||||||
|
cli, monkeypatch, caplog, failure_index
|
||||||
|
):
|
||||||
|
error = ClassifierParseError("raw-classifier-private-marker")
|
||||||
|
results = [SimpleNamespace(compliance_rate=1.0)] * failure_index + [error]
|
||||||
|
monkeypatch.setattr(run, "grade", Mock(side_effect=results))
|
||||||
|
|
||||||
|
with pytest.raises(SystemExit) as exited:
|
||||||
|
run.main()
|
||||||
|
|
||||||
|
assert exited.value.code == 1
|
||||||
|
assert "ClassifierParseError" in caplog.text
|
||||||
|
assert "Classification failed" in caplog.text
|
||||||
|
assert caplog.records[-1].message == (
|
||||||
|
"ClassifierParseError: Classification failed; "
|
||||||
|
"invalid classifier output. No report generated."
|
||||||
|
)
|
||||||
|
assert "raw-classifier-private-marker" not in caplog.text
|
||||||
|
assert "Traceback" not in caplog.text
|
||||||
|
assert "Overall Compliance" not in caplog.text
|
||||||
|
assert not cli.output.exists()
|
||||||
|
cli.report.assert_not_called()
|
||||||
|
assert cli.execute.call_count == failure_index + 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("error_type", [RuntimeError, ValueError, OSError])
|
||||||
|
def test_unrelated_grading_exception_propagates(cli, monkeypatch, error_type):
|
||||||
|
error = error_type("unrelated failure")
|
||||||
|
monkeypatch.setattr(run, "grade", Mock(side_effect=error))
|
||||||
|
|
||||||
|
with pytest.raises(error_type) as raised:
|
||||||
|
run.main()
|
||||||
|
|
||||||
|
assert raised.value is error
|
||||||
|
assert not cli.output.exists()
|
||||||
|
cli.report.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
def test_classifier_error_outside_grading_propagates(cli):
|
||||||
|
error = ClassifierParseError("execution failure")
|
||||||
|
cli.execute.side_effect = error
|
||||||
|
|
||||||
|
with pytest.raises(ClassifierParseError) as raised:
|
||||||
|
run.main()
|
||||||
|
|
||||||
|
assert raised.value is error
|
||||||
|
cli.report.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
def test_valid_positive_grading_writes_report(cli, monkeypatch, caplog):
|
||||||
|
monkeypatch.setattr(run, "grade", Mock(return_value=SimpleNamespace(compliance_rate=1.0)))
|
||||||
|
caplog.set_level("INFO")
|
||||||
|
|
||||||
|
run.main()
|
||||||
|
|
||||||
|
assert cli.output.read_text() == "Controlled compliance report\n"
|
||||||
|
cli.report.assert_called_once()
|
||||||
|
assert cli.execute.call_count == 2
|
||||||
|
assert "Overall Compliance: 100%" in caplog.text
|
||||||
Reference in new issue
Block a user