5e96b4bf80
CI / build (pull_request) Successful in 16s
CI / helm (pull_request) Successful in 21s
CI / lint (pull_request) Successful in 3m18s
CI / quality (pull_request) Successful in 3m47s
CI / typecheck (pull_request) Successful in 3m57s
CI / security (pull_request) Successful in 4m6s
CI / benchmark-publish (pull_request) Has been skipped
CI / integration_tests (pull_request) Successful in 7m6s
CI / unit_tests (pull_request) Successful in 7m18s
CI / docker (pull_request) Successful in 1m19s
CI / coverage (pull_request) Successful in 11m49s
CI / e2e_tests (pull_request) Successful in 20m44s
CI / status-check (pull_request) Successful in 1s
CI / benchmark-regression (pull_request) Successful in 55m9s
Implement the spec's 6-level execution environment precedence chain (spec lines 19324-19386): 1. Plan override (priority=override) — always wins 2. Project override (priority=override) — wins over devcontainer 3. Nearest-ancestor devcontainer — auto-discovered 4. Plan fallback (priority=fallback) — defers to devcontainer 5. Project fallback (priority=fallback) — defers to closer scopes 6. Host default — final fallback - New resolve_with_precedence() API on ExecutionEnvironmentResolver - Added execution_env_priority field to ContextConfig (project model) - has_devcontainer() helper for devcontainer-instance detection - Legacy 4-level resolve() preserved for backward compatibility - _parse_priority() defaults missing priority to FALLBACK - 13 new Behave scenarios testing all 6 levels + edge cases - Updated CHANGELOG ISSUES CLOSED: #877
175 lines
5.4 KiB
Python
175 lines
5.4 KiB
Python
"""Step definitions for exec_env_precedence.feature.
|
|
|
|
Self-contained step file for the 6-level execution environment
|
|
precedence chain. All imports are top-level and guarded.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from behave import then, when
|
|
|
|
from cleveragents.application.services.execution_environment_resolver import (
|
|
ExecutionEnvironmentResolver,
|
|
)
|
|
|
|
|
|
def _resolver() -> ExecutionEnvironmentResolver:
|
|
return ExecutionEnvironmentResolver()
|
|
|
|
|
|
# ── 6-Level Precedence Chain ─────────────────────────────
|
|
|
|
|
|
@when(
|
|
'I resolve precedence with plan_env "{plan}" plan_priority "{pp}" '
|
|
'project_env "{proj}" project_priority "{projp}" '
|
|
"devcontainer {dc}"
|
|
)
|
|
def step_resolve_full(
|
|
context: object,
|
|
plan: str,
|
|
pp: str,
|
|
proj: str,
|
|
projp: str,
|
|
dc: str,
|
|
) -> None:
|
|
context.prec_resolved = _resolver().resolve_with_precedence(
|
|
plan_env=plan,
|
|
plan_priority=pp,
|
|
project_env=proj,
|
|
project_priority=projp,
|
|
devcontainer_available=dc.lower() == "true",
|
|
)
|
|
|
|
|
|
@when(
|
|
'I resolve precedence with tool_env "{tool}" plan_env "{plan}" '
|
|
'plan_priority "{pp}" devcontainer {dc}'
|
|
)
|
|
def step_resolve_tool(
|
|
context: object,
|
|
tool: str,
|
|
plan: str,
|
|
pp: str,
|
|
dc: str,
|
|
) -> None:
|
|
context.prec_resolved = _resolver().resolve_with_precedence(
|
|
tool_env=tool,
|
|
plan_env=plan,
|
|
plan_priority=pp,
|
|
devcontainer_available=dc.lower() == "true",
|
|
)
|
|
|
|
|
|
@when(
|
|
'I resolve precedence with project_env "{proj}" '
|
|
'project_priority "{projp}" devcontainer {dc}'
|
|
)
|
|
def step_resolve_proj(context: object, proj: str, projp: str, dc: str) -> None:
|
|
context.prec_resolved = _resolver().resolve_with_precedence(
|
|
project_env=proj,
|
|
project_priority=projp,
|
|
devcontainer_available=dc.lower() == "true",
|
|
)
|
|
|
|
|
|
@when("I resolve precedence with devcontainer {dc}")
|
|
def step_resolve_default(context: object, dc: str) -> None:
|
|
context.prec_resolved = _resolver().resolve_with_precedence(
|
|
devcontainer_available=dc.lower() == "true",
|
|
)
|
|
|
|
|
|
@when(
|
|
'I resolve precedence with plan_env "{plan}" project_env "{proj}" devcontainer {dc}'
|
|
)
|
|
def step_resolve_both_fb(context: object, plan: str, proj: str, dc: str) -> None:
|
|
context.prec_resolved = _resolver().resolve_with_precedence(
|
|
plan_env=plan,
|
|
project_env=proj,
|
|
devcontainer_available=dc.lower() == "true",
|
|
)
|
|
|
|
|
|
@then('the precedence resolved environment should be "{expected}"')
|
|
def step_check_resolved(context: object, expected: str) -> None:
|
|
actual = context.prec_resolved.value
|
|
assert actual == expected, f"Expected '{expected}', got '{actual}'"
|
|
|
|
|
|
# ── Devcontainer helpers ─────────────────────────────────
|
|
|
|
|
|
@when('I check has_devcontainer with types "{types}" for precedence')
|
|
def step_has_dc(context: object, types: str) -> None:
|
|
type_list = [t.strip() for t in types.split(",")]
|
|
context.prec_has_dc = _resolver().has_devcontainer(type_list)
|
|
|
|
|
|
@then("has_devcontainer should be {expected} for precedence")
|
|
def step_has_dc_check(context: object, expected: str) -> None:
|
|
assert context.prec_has_dc == (expected.lower() == "true")
|
|
|
|
|
|
# ── ContextConfig priority field ─────────────────────────
|
|
|
|
|
|
@when('I create a ContextConfig with priority "{priority}" for precedence')
|
|
def step_ctx_config(context: object, priority: str) -> None:
|
|
from cleveragents.domain.models.core.project import ContextConfig
|
|
|
|
context.prec_ctx_config = ContextConfig(
|
|
execution_environment="container",
|
|
execution_env_priority=priority,
|
|
)
|
|
|
|
|
|
@then('the context config priority should be "{priority}" for precedence')
|
|
def step_ctx_config_check(context: object, priority: str) -> None:
|
|
assert context.prec_ctx_config.execution_env_priority == priority
|
|
|
|
|
|
# ── DAG walk ─────────────────────────────────────────────
|
|
|
|
|
|
@when('I resolve with DAG walk from "{rid}" with devcontainer ancestor for precedence')
|
|
def step_dag_with_dc(context: object, rid: str) -> None:
|
|
context.prec_resolved = _resolver().resolve_with_dag(
|
|
resource_id=rid,
|
|
linked_resource_types={
|
|
rid: "fs-file",
|
|
"res-dir": "fs-directory",
|
|
"res-dc": "devcontainer-instance",
|
|
},
|
|
parent_map={rid: ["res-dir"], "res-dir": ["res-dc"]},
|
|
)
|
|
|
|
|
|
@when(
|
|
'I resolve with DAG walk from "{rid}" without devcontainer ancestor for precedence'
|
|
)
|
|
def step_dag_no_dc(context: object, rid: str) -> None:
|
|
context.prec_resolved = _resolver().resolve_with_dag(
|
|
resource_id=rid,
|
|
linked_resource_types={
|
|
rid: "fs-file",
|
|
"res-dir": "fs-directory",
|
|
"res-checkout": "git-checkout",
|
|
},
|
|
parent_map={rid: ["res-dir"], "res-dir": ["res-checkout"]},
|
|
)
|
|
|
|
|
|
# ── Container reference ──────────────────────────────────
|
|
|
|
|
|
@when('I parse container ref "{ref}" for precedence')
|
|
def step_parse_ref(context: object, ref: str) -> None:
|
|
_, name = _resolver().parse_container_ref(ref)
|
|
context.prec_ref_name = name
|
|
|
|
|
|
@then('the container ref name should be "{name}" for precedence')
|
|
def step_ref_name(context: object, name: str) -> None:
|
|
assert context.prec_ref_name == name
|