7aa50ac489
- Add NO_COLOR=1 to E2E Suite Setup and Test Environment to prevent Rich from injecting ANSI escape codes into JSON output, which breaks Extract JSON From Stdout and JSON assertions in M5/WF14 acceptance tests. - Add reset_global_state() calls to all M1/M2/M4/M5 E2E verification helpers to clear Settings singleton, DI container, provider registry, and SQLAlchemy engine cache between parallel pabot worker runs. - Propagate NO_COLOR=1 at suite-setup level in E2E and integration test resource files so all Run Process subprocesses inherit clean terminal output mode. Root causes: 1. Missing NO_COLOR=1 caused Rich ANSI codes in CLI JSON output, making json.loads() failures in robot Extract JSON From Stdout 2. Stale Settings singleton carried provider keys and DB paths between pabot workers, causing UNIQUE constraint violations and stale config lookups in context policy and server mode E2E tests. Related: PR #9912
152 lines
4.6 KiB
Python
152 lines
4.6 KiB
Python
"""Robot Framework helper for M5 ACMS pipeline smoke tests."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from helpers_common import reset_global_state
|
|
|
|
from cleveragents.domain.models.core.context_policy import (
|
|
ContextView,
|
|
ProjectContextPolicy,
|
|
)
|
|
|
|
_FIXTURES_DIR = Path(__file__).resolve().parents[1] / "features" / "fixtures" / "m5"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Robot keywords
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def load_acms_context_policy_fixture() -> dict[str, Any]:
|
|
"""Load the ACMS context policy fixture file."""
|
|
reset_global_state()
|
|
fixture_path = _FIXTURES_DIR / "acms_context_policy.json"
|
|
return json.loads(fixture_path.read_text(encoding="utf-8"))
|
|
|
|
|
|
def load_large_project_context_fixture() -> dict[str, Any]:
|
|
"""Load the large project context fixture file."""
|
|
reset_global_state()
|
|
fixture_path = _FIXTURES_DIR / "large_project_context.json"
|
|
return json.loads(fixture_path.read_text(encoding="utf-8"))
|
|
|
|
|
|
def load_context_analysis_fixture() -> dict[str, Any]:
|
|
"""Load the context analysis results fixture file."""
|
|
reset_global_state()
|
|
fixture_path = _FIXTURES_DIR / "context_analysis_results.json"
|
|
return json.loads(fixture_path.read_text(encoding="utf-8"))
|
|
|
|
|
|
def resolve_view_for_phase(phase: str) -> dict[str, Any]:
|
|
"""Resolve a view from an empty policy for the given phase."""
|
|
reset_global_state()
|
|
policy = ProjectContextPolicy()
|
|
view = policy.resolve_view(phase)
|
|
return {
|
|
"include_paths": view.include_paths,
|
|
"exclude_paths": view.exclude_paths,
|
|
"include_resources": view.include_resources,
|
|
"exclude_resources": view.exclude_resources,
|
|
"max_file_size": view.max_file_size,
|
|
"max_total_size": view.max_total_size,
|
|
}
|
|
|
|
|
|
def resolve_strategize_inheriting_default() -> dict[str, Any]:
|
|
"""Resolve strategize view that inherits from default."""
|
|
reset_global_state()
|
|
policy = ProjectContextPolicy(
|
|
default_view=ContextView(
|
|
include_paths=["src/**/*.py"],
|
|
exclude_paths=["**/__pycache__/**"],
|
|
max_file_size=262144,
|
|
),
|
|
)
|
|
view = policy.resolve_view("strategize")
|
|
return {
|
|
"include_paths": view.include_paths,
|
|
"max_file_size": view.max_file_size,
|
|
}
|
|
|
|
|
|
def resolve_strategize_with_override() -> dict[str, Any]:
|
|
"""Resolve strategize view with explicit override."""
|
|
reset_global_state()
|
|
policy = ProjectContextPolicy(
|
|
default_view=ContextView(
|
|
include_paths=["src/**/*.py"],
|
|
max_file_size=262144,
|
|
),
|
|
strategize_view=ContextView(
|
|
include_paths=["src/**/*.py", "docs/**/*.md"],
|
|
max_file_size=131072,
|
|
),
|
|
)
|
|
view = policy.resolve_view("strategize")
|
|
return {
|
|
"include_paths": view.include_paths,
|
|
"max_file_size": view.max_file_size,
|
|
}
|
|
|
|
|
|
def check_budget_enforcement(
|
|
max_size: int,
|
|
oversized_file: int,
|
|
within_file: int,
|
|
) -> dict[str, bool]:
|
|
"""Check if files exceed or fit within file size budget."""
|
|
reset_global_state()
|
|
view = ContextView(max_file_size=int(max_size))
|
|
limit = view.max_file_size
|
|
assert limit is not None
|
|
return {
|
|
"oversized": int(oversized_file) > limit,
|
|
"within_budget": int(within_file) <= limit,
|
|
}
|
|
|
|
|
|
def check_total_budget_enforcement(
|
|
max_total: int,
|
|
oversized_total: int,
|
|
within_total: int,
|
|
) -> dict[str, bool]:
|
|
"""Check if aggregate context exceeds total size budget."""
|
|
reset_global_state()
|
|
view = ContextView(max_total_size=int(max_total))
|
|
limit = view.max_total_size
|
|
assert limit is not None
|
|
return {
|
|
"oversized": int(oversized_total) > limit,
|
|
"within_budget": int(within_total) <= limit,
|
|
}
|
|
|
|
|
|
def attempt_resolve_invalid_phase(phase: str) -> dict[str, str]:
|
|
"""Try resolving an invalid phase and capture the error."""
|
|
reset_global_state()
|
|
policy = ProjectContextPolicy()
|
|
try:
|
|
policy.resolve_view(phase)
|
|
return {"error": "False", "message": ""}
|
|
except ValueError as exc:
|
|
return {"error": "True", "message": str(exc)}
|
|
|
|
|
|
def create_multi_project_context(
|
|
count_a: int,
|
|
count_b: int,
|
|
) -> dict[str, int]:
|
|
"""Simulate independent context for two projects."""
|
|
reset_global_state()
|
|
proj_a = [{"path": f"src/a_{i}.py", "size": 1024} for i in range(int(count_a))]
|
|
proj_b = [{"path": f"src/b_{i}.py", "size": 1024} for i in range(int(count_b))]
|
|
return {
|
|
"proj_a_count": len(proj_a),
|
|
"proj_b_count": len(proj_b),
|
|
}
|