Files
cleveragents-core/robot/helper_acms_budget_enforcement.py
T
HAL9000 de65e4408a fix(acms): satisfy architecture dataclass check and ruff format
- Use @dataclass(slots=True) on BudgetViolation, ContextFile, BudgetEnforcer
  so the features/architecture.feature "Type hints are used throughout"
  scenario passes (the AST check only flags bare @dataclass decorators,
  consistent with the project-wide convention used elsewhere in src/).
- Re-format features/steps/acms_budget_enforcement_steps.py and
  robot/helper_acms_budget_enforcement.py via ruff format to clear the
  lint gate's ruff format --check failure.

ISSUES CLOSED: #9583
2026-06-17 19:54:20 -04:00

258 lines
9.8 KiB
Python

"""Helper script for Robot Framework ACMS budget enforcement integration tests."""
from __future__ import annotations
import sys
from collections.abc import Callable
def _test_file_within_limit() -> None:
"""BudgetEnforcer includes a file within max_file_size limit."""
from cleveragents.acms.budget_enforcement import BudgetEnforcer
enforcer = BudgetEnforcer(max_file_size=1000, max_total_size=5000)
content = "x" * 500
included = enforcer.add_file("small.txt", content)
assert included is True, "Expected file to be included"
assert enforcer.get_total_size() == 500, (
f"Expected total size 500, got {enforcer.get_total_size()}"
)
assert len(enforcer.get_included_files()) == 1, (
f"Expected 1 included file, got {len(enforcer.get_included_files())}"
)
assert len(enforcer.get_violations()) == 0, (
f"Expected no violations, got {enforcer.get_violations()}"
)
print("file-within-limit-ok")
def _test_file_exceeds_limit() -> None:
"""BudgetEnforcer excludes a file that exceeds max_file_size."""
from cleveragents.acms.budget_enforcement import BudgetEnforcer
enforcer = BudgetEnforcer(max_file_size=1000, max_total_size=5000)
content = "x" * 1500
included = enforcer.add_file("large.txt", content)
assert included is False, "Expected file to be excluded"
assert enforcer.get_total_size() == 0, (
f"Expected total size 0, got {enforcer.get_total_size()}"
)
assert len(enforcer.get_included_files()) == 0, (
f"Expected 0 included files, got {len(enforcer.get_included_files())}"
)
violations = enforcer.get_violations()
assert len(violations) == 1, f"Expected 1 violation, got {len(violations)}"
assert violations[0].violation_type == "max_file_size", (
f"Expected max_file_size violation, got {violations[0].violation_type}"
)
assert violations[0].filename == "large.txt", (
f"Expected filename 'large.txt', got {violations[0].filename}"
)
print("file-exceeds-limit-ok")
def _test_total_size_cutoff() -> None:
"""BudgetEnforcer cuts off files when max_total_size is reached."""
from cleveragents.acms.budget_enforcement import BudgetEnforcer
enforcer = BudgetEnforcer(max_file_size=1000, max_total_size=2500)
assert enforcer.add_file("f1.txt", "x" * 1000) is True
assert enforcer.add_file("f2.txt", "x" * 1000) is True
assert enforcer.add_file("f3.txt", "x" * 1000) is False
assert enforcer.get_total_size() == 2000, (
f"Expected total size 2000, got {enforcer.get_total_size()}"
)
violations = enforcer.get_violations()
total_violations = [v for v in violations if v.violation_type == "max_total_size"]
assert len(total_violations) == 1, (
f"Expected 1 max_total_size violation, got {len(total_violations)}"
)
print("total-size-cutoff-ok")
def _test_violation_message() -> None:
"""BudgetViolation message clearly describes the constraint exceeded."""
from cleveragents.acms.budget_enforcement import BudgetEnforcer
enforcer = BudgetEnforcer(max_file_size=500, max_total_size=2000)
enforcer.add_file("big.txt", "x" * 600)
violations = enforcer.get_violations()
assert len(violations) == 1
msg = violations[0].message
assert "exceeds" in msg.lower(), f"Message should contain 'exceeds': {msg}"
assert "500" in msg, f"Message should contain limit '500': {msg}"
assert "600" in msg, f"Message should contain file size '600': {msg}"
assert "big.txt" in msg, f"Message should contain filename 'big.txt': {msg}"
print("violation-message-ok")
def _test_defensive_copies() -> None:
"""get_violations and get_included_files return defensive copies."""
from cleveragents.acms.budget_enforcement import BudgetEnforcer
enforcer = BudgetEnforcer(max_file_size=1000, max_total_size=5000)
enforcer.add_file("f1.txt", "x" * 500)
enforcer.add_file("too_big.txt", "x" * 1500)
violations = enforcer.get_violations()
violations.clear()
assert len(enforcer.get_violations()) == 1, (
"Clearing returned violations list should not affect enforcer state"
)
files = enforcer.get_included_files()
files.clear()
assert len(enforcer.get_included_files()) == 1, (
"Clearing returned files list should not affect enforcer state"
)
print("defensive-copies-ok")
def _test_constructor_validation() -> None:
"""BudgetEnforcer raises ValueError for invalid budget values."""
from cleveragents.acms.budget_enforcement import BudgetEnforcer
try:
BudgetEnforcer(max_file_size=-1, max_total_size=5000)
raise AssertionError("Expected ValueError for negative max_file_size")
except ValueError as e:
assert "max_file_size" in str(e), f"Error should mention max_file_size: {e}"
try:
BudgetEnforcer(max_file_size=100, max_total_size=0)
raise AssertionError("Expected ValueError for zero max_total_size")
except ValueError as e:
assert "max_total_size" in str(e), f"Error should mention max_total_size: {e}"
try:
BudgetEnforcer(max_file_size=5000, max_total_size=1000)
raise AssertionError("Expected ValueError for max_file_size > max_total_size")
except ValueError as e:
assert "cannot exceed" in str(e), f"Error should mention cannot exceed: {e}"
enforcer = BudgetEnforcer(max_file_size=1000, max_total_size=5000)
assert enforcer.max_file_size == 1000
assert enforcer.max_total_size == 5000
print("constructor-validation-ok")
def _test_add_file_validation() -> None:
"""add_file raises ValueError for empty name."""
from cleveragents.acms.budget_enforcement import BudgetEnforcer
enforcer = BudgetEnforcer(max_file_size=1000, max_total_size=5000)
try:
enforcer.add_file("", "some content")
raise AssertionError("Expected ValueError for empty name")
except ValueError as e:
assert "name" in str(e).lower(), f"Error should mention name: {e}"
try:
enforcer.add_file("file.txt", object())
raise AssertionError("Expected TypeError for non-string content")
except TypeError as e:
assert "content" in str(e).lower(), f"Error should mention content: {e}"
print("add-file-validation-ok")
def _test_multibyte_utf8() -> None:
"""BudgetEnforcer measures file size in bytes not characters."""
from cleveragents.acms.budget_enforcement import BudgetEnforcer
enforcer = BudgetEnforcer(max_file_size=1000, max_total_size=5000)
emoji_char = "\U0001f600" # U+1F600 -- 4 bytes in UTF-8
content = emoji_char * 251
assert len(content.encode("utf-8")) == 1004, "Byte count should be 1004"
included = enforcer.add_file("emoji.txt", content)
assert included is False, (
"File with 1004 bytes should be excluded (exceeds 1000 byte limit)"
)
violations = enforcer.get_violations()
assert len(violations) == 1
assert violations[0].violation_type == "max_file_size"
assert violations[0].file_size == 1004, (
f"Violation should report byte size 1004, got {violations[0].file_size}"
)
enforcer2 = BudgetEnforcer(max_file_size=1000, max_total_size=5000)
content_boundary = emoji_char * 250
assert len(content_boundary.encode("utf-8")) == 1000
included2 = enforcer2.add_file("boundary.txt", content_boundary)
assert included2 is True, "File with exactly 1000 bytes should be included"
print("multibyte-utf8-ok")
def _test_reset_state() -> None:
"""reset() clears all included files, violations, and total size."""
from cleveragents.acms.budget_enforcement import BudgetEnforcer
enforcer = BudgetEnforcer(max_file_size=1000, max_total_size=5000)
enforcer.add_file("f1.txt", "x" * 500)
enforcer.add_file("too_big.txt", "x" * 1500)
assert enforcer.get_total_size() == 500
assert len(enforcer.get_included_files()) == 1
assert len(enforcer.get_violations()) == 1
enforcer.reset()
assert enforcer.get_total_size() == 0, (
f"After reset, total size should be 0, got {enforcer.get_total_size()}"
)
assert len(enforcer.get_included_files()) == 0, (
"After reset, included files should be empty"
)
assert len(enforcer.get_violations()) == 0, (
"After reset, violations should be empty"
)
assert enforcer.add_file("new.txt", "y" * 300) is True
assert enforcer.get_total_size() == 300
print("reset-state-ok")
def _test_assembled_context() -> None:
"""get_assembled_context joins included file contents with newlines."""
from cleveragents.acms.budget_enforcement import BudgetEnforcer
enforcer = BudgetEnforcer(max_file_size=1000, max_total_size=5000)
enforcer.add_file("a.txt", "hello")
enforcer.add_file("b.txt", "world")
context = enforcer.get_assembled_context()
assert context == "hello\nworld", f"Expected 'hello\\nworld', got {context!r}"
enforcer2 = BudgetEnforcer(max_file_size=1000, max_total_size=5000)
assert enforcer2.get_assembled_context() == "", (
"Empty enforcer should return empty string"
)
print("assembled-context-ok")
_TESTS: dict[str, Callable[[], None]] = {
"file-within-limit": _test_file_within_limit,
"file-exceeds-limit": _test_file_exceeds_limit,
"total-size-cutoff": _test_total_size_cutoff,
"violation-message": _test_violation_message,
"defensive-copies": _test_defensive_copies,
"constructor-validation": _test_constructor_validation,
"add-file-validation": _test_add_file_validation,
"multibyte-utf8": _test_multibyte_utf8,
"reset-state": _test_reset_state,
"assembled-context": _test_assembled_context,
}
if __name__ == "__main__":
if len(sys.argv) < 2 or sys.argv[1] not in _TESTS:
print(
f"Usage: {sys.argv[0]} <{'|'.join(sorted(_TESTS))}>",
file=sys.stderr,
)
sys.exit(1)
_TESTS[sys.argv[1]]()