forked from cleveragents/cleveragents-core
cf67ba0a86
Implemented lazy container activation for devcontainer-instance resources with ContainerLifecycleState enum tracking six states (inactive, starting, active, stopping, stopped, error) with validated transitions. Extended DevcontainerHandler with devcontainer up CLI integration and JSON output parsing for container start. Added periodic health checking via devcontainer exec ping with configurable interval. Added agents resource stop and agents resource rebuild CLI commands for manual lifecycle control. Wired session close and plan completion hooks to automatic container cleanup. Includes lifecycle state persistence in resource registry with timestamped transitions. Added Behave BDD tests, Robot integration tests, and ASV activation latency benchmarks. - Added remoteWorkspaceFolder absolute-path validation - Aligned spec: handler name, rebuild types, --yes flag on stop/rebuild - Added registry re-read in stop_container success path for consistency - Added session_id field to ContainerLifecycleTracker for scoped cleanup - Scoped stop_all_active_containers to session_id when provided - Wired _cleanup_devcontainers into fail_apply and fail_execute - Wired start_health_check into activate_container success path - Restructured facade session close to always run container cleanup even without session service (F4) - Re-read tracker from registry in activate_container success path - Added evict_terminal_trackers to cap registry growth - Updated devcontainer_resources.md: health check auto-start, scoped cleanup hooks, known limitations for eviction and sandbox_strategy - Wired evict_terminal_trackers into stop_all_active_containers so terminal-state trackers are actually evicted in production - Made stop_container idempotent: returns early when container is already in a terminal state instead of raising ValueError - Fixed benchmark health check thread leak in TimeActivationLatency by clearing registry after each timing loop - Added rebuild pass-through (--reset-container flag to devcontainer up) - Added host_workspace_path field on ContainerLifecycleTracker so health probes use the host-side path for devcontainer exec - Wired lazy activation into DevcontainerHandler.resolve() for devcontainer-instance resources in non-running states - Changed _default_strategy from SNAPSHOT to NONE (container itself provides isolation; SandboxFactory raises NotImplementedError for snapshot) - Restricted _STOPPABLE_TYPES to devcontainer-instance only (container-instance is not directly stoppable via CLI) ISSUES CLOSED: #514
238 lines
14 KiB
Gherkin
238 lines
14 KiB
Gherkin
Feature: Devcontainer Session Cleanup and CLI Commands
|
|
As a CleverAgents developer
|
|
I want session-scoped container cleanup and CLI stop/rebuild commands
|
|
So that containers are cleaned up when sessions end and can be managed via CLI
|
|
|
|
# ── Session cleanup ────────────────────────────────────────
|
|
|
|
Scenario: Active containers stopped on session cleanup
|
|
Given a mock devcontainer CLI runner
|
|
And an active container "01TESTLIFECYCLE0000000040" with container ID "ctr-a"
|
|
And an active container "01TESTLIFECYCLE0000000041" with container ID "ctr-b"
|
|
When I run session cleanup for session "session-001"
|
|
Then the container state for "01TESTLIFECYCLE0000000040" should be "stopped"
|
|
And the container state for "01TESTLIFECYCLE0000000041" should be "stopped"
|
|
|
|
# ── Cleanup service integration ────────────────────────────
|
|
|
|
Scenario: CleanupService stop_active_devcontainers is callable and returns list
|
|
When I call CleanupService stop_active_devcontainers with no active containers
|
|
Then the cleanup result should be an empty list
|
|
And CleanupService should have a stop_active_devcontainers method
|
|
|
|
Scenario: CleanupService stop_active_devcontainers calls handler cleanup
|
|
When I call CleanupService stop_active_devcontainers with no active containers
|
|
Then the cleanup result should be an empty list
|
|
|
|
# ── stop_all_active_containers error path ──────────────────
|
|
|
|
Scenario: Session cleanup handles stop failure gracefully
|
|
Given a mock devcontainer CLI runner
|
|
And the runner configured to raise exception on stop
|
|
And an active container "01TESTLIFECYCLE0000000130" with container ID "ctr-fail"
|
|
When I run session cleanup for session "session-err"
|
|
Then the cleanup should return 0 stopped containers
|
|
|
|
# ── CLI stop/rebuild commands (F6) ─────────────────────────
|
|
|
|
Scenario: CLI stop succeeds for active devcontainer-instance
|
|
Given a mock resource service with devcontainer "local/test-dc" id "01TESTLIFECYCLE0000000200" at "/workspace/project"
|
|
And an active container "01TESTLIFECYCLE0000000200" with container ID "ctr-cli-stop"
|
|
When I invoke CLI resource stop "local/test-dc"
|
|
Then the CLI exit code should be 0
|
|
And the CLI output should contain "Stopped"
|
|
|
|
Scenario: CLI stop rejects non-devcontainer resource type
|
|
Given a mock resource service with git-checkout "local/my-repo" id "01TESTLIFECYCLE0000000201"
|
|
When I invoke CLI resource stop "local/my-repo"
|
|
Then the devcontainer CLI exit code should be non-zero
|
|
And the CLI output should contain "not a stoppable container type"
|
|
|
|
Scenario: CLI rebuild succeeds for stopped devcontainer-instance
|
|
Given a mock resource service with devcontainer "local/test-dc-rb" id "01TESTLIFECYCLE0000000210" at "/workspace/project"
|
|
And a stopped container "01TESTLIFECYCLE0000000210"
|
|
When I invoke CLI resource rebuild "local/test-dc-rb"
|
|
Then the CLI exit code should be 0
|
|
And the CLI output should contain "Rebuilt"
|
|
|
|
Scenario: CLI rebuild rejects resource with no location
|
|
Given a mock resource service with devcontainer "local/dc-no-loc" id "01TESTLIFECYCLE0000000211" with no location
|
|
And a stopped container "01TESTLIFECYCLE0000000211"
|
|
When I invoke CLI resource rebuild "local/dc-no-loc"
|
|
Then the devcontainer CLI exit code should be non-zero
|
|
And the CLI output should contain "no location"
|
|
|
|
Scenario: CLI rebuild rejects non-devcontainer resource type
|
|
Given a mock resource service with git-checkout "local/git-rb" id "01TESTLIFECYCLE0000000212"
|
|
When I invoke CLI resource rebuild "local/git-rb"
|
|
Then the devcontainer CLI exit code should be non-zero
|
|
And the CLI output should contain "rebuild requires devcontainer"
|
|
|
|
# ── R7: CLI test mock assertions ───────────────────────────
|
|
|
|
Scenario: CLI stop calls stop_container with correct resource_id
|
|
Given a mock resource service with devcontainer "local/test-dc-r7" id "01TESTLIFECYCLE0000000220" at "/workspace/project"
|
|
And an active container "01TESTLIFECYCLE0000000220" with container ID "ctr-r7"
|
|
When I invoke CLI resource stop "local/test-dc-r7"
|
|
Then the CLI exit code should be 0
|
|
And the CLI stop mock should have been called with "01TESTLIFECYCLE0000000220"
|
|
|
|
Scenario: CLI rebuild calls rebuild_container with correct args
|
|
Given a mock resource service with devcontainer "local/test-dc-r7rb" id "01TESTLIFECYCLE0000000221" at "/workspace/project"
|
|
And a stopped container "01TESTLIFECYCLE0000000221"
|
|
When I invoke CLI resource rebuild "local/test-dc-r7rb"
|
|
Then the CLI exit code should be 0
|
|
And the CLI rebuild mock should have been called with "01TESTLIFECYCLE0000000221" and "/workspace/project"
|
|
|
|
# ── R8: DevcontainerHandler coverage ───────────────────────
|
|
|
|
Scenario: DevcontainerHandler has expected class attributes and is instantiable
|
|
Then DevcontainerHandler should have _default_strategy "none"
|
|
And DevcontainerHandler should have _type_label "devcontainer"
|
|
And DevcontainerHandler should be instantiable
|
|
|
|
Scenario: DevcontainerHandler extends BaseResourceHandler and inherits resolve interface
|
|
Then DevcontainerHandler should be a subclass of BaseResourceHandler
|
|
And DevcontainerHandler instance should have a resolve method
|
|
|
|
# ── R12: CLI state precondition validation ─────────────────
|
|
|
|
Scenario: CLI stop rejects container not in running state
|
|
Given a mock resource service with devcontainer "local/dc-det" id "01TESTLIFECYCLE0000000230" at "/workspace/project"
|
|
And a lifecycle tracker for resource "01TESTLIFECYCLE0000000230"
|
|
When I invoke CLI resource stop "local/dc-det"
|
|
Then the devcontainer CLI exit code should be non-zero
|
|
And the CLI output should contain "Cannot stop"
|
|
|
|
Scenario: CLI rebuild rejects container in running state
|
|
Given a mock resource service with devcontainer "local/dc-run" id "01TESTLIFECYCLE0000000231" at "/workspace/project"
|
|
And an active container "01TESTLIFECYCLE0000000231" with container ID "ctr-rb-state"
|
|
When I invoke CLI resource rebuild "local/dc-run"
|
|
Then the devcontainer CLI exit code should be non-zero
|
|
And the CLI output should contain "Cannot rebuild"
|
|
|
|
# ── R14: stop_all return list assertion ────────────────────
|
|
|
|
Scenario: Session cleanup returns list of stopped resource IDs
|
|
Given a mock devcontainer CLI runner
|
|
And an active container "01TESTLIFECYCLE0000000250" with container ID "ctr-r14a"
|
|
And an active container "01TESTLIFECYCLE0000000251" with container ID "ctr-r14b"
|
|
When I run session cleanup for session "session-r14"
|
|
Then the cleanup stopped list should contain "01TESTLIFECYCLE0000000250"
|
|
And the cleanup stopped list should contain "01TESTLIFECYCLE0000000251"
|
|
|
|
# ── A2: Stop and rebuild accept --yes flag ─────────────────
|
|
|
|
Scenario: CLI stop with --yes skips confirmation and calls stop_container
|
|
Given a CLI-mockable running devcontainer resource "test-dc-yes-stop"
|
|
When I invoke CLI resource stop "test-dc-yes-stop"
|
|
Then the devcontainer CLI exit code should be 0
|
|
And the CLI stop mock should have been called once
|
|
|
|
Scenario: CLI rebuild with --yes skips confirmation and calls rebuild_container
|
|
Given a CLI-mockable stopped devcontainer resource "test-dc-yes-rebuild"
|
|
When I invoke CLI resource rebuild "test-dc-yes-rebuild"
|
|
Then the devcontainer CLI exit code should be 0
|
|
And the CLI rebuild mock should have been called once
|
|
|
|
# ── F5/F19: container-instance is NOT stoppable via CLI ─────
|
|
|
|
Scenario: CLI stop rejects container-instance type (F19 fix)
|
|
Given a mock resource service with container-instance "local/ctr-stop" id "01TESTLIFECYCLE0000000370" at "/workspace/project"
|
|
And an active container "01TESTLIFECYCLE0000000370" with container ID "ctr-ci-stop"
|
|
When I invoke CLI resource stop "local/ctr-stop"
|
|
Then the devcontainer CLI exit code should be non-zero
|
|
And the CLI output should contain "not a stoppable"
|
|
|
|
# ── F6-r6: CLI handler-level error paths ────────────────────
|
|
|
|
Scenario: CLI stop handles NotFoundError from show_resource
|
|
Given a mock resource service that raises NotFoundError for "local/missing"
|
|
When I invoke CLI resource stop "local/missing"
|
|
Then the devcontainer CLI exit code should be non-zero
|
|
And the CLI output should contain "Resource not found"
|
|
|
|
Scenario: CLI rebuild handles NotFoundError from show_resource
|
|
Given a mock resource service that raises NotFoundError for "local/missing-rb"
|
|
When I invoke CLI resource rebuild "local/missing-rb"
|
|
Then the devcontainer CLI exit code should be non-zero
|
|
And the CLI output should contain "Resource not found"
|
|
|
|
Scenario: CLI stop handles RuntimeError from stop_container
|
|
Given a mock resource service with devcontainer "local/dc-rterr" id "01TESTLIFECYCLE0000000500" at "/workspace/project"
|
|
And an active container "01TESTLIFECYCLE0000000500" with container ID "ctr-rterr"
|
|
And CLI stop_container mock that raises RuntimeError
|
|
When I invoke CLI resource stop with error mock "local/dc-rterr"
|
|
Then the devcontainer CLI exit code should be non-zero
|
|
And the CLI output should contain "Stop failed"
|
|
|
|
Scenario: CLI rebuild handles RuntimeError from rebuild_container
|
|
Given a mock resource service with devcontainer "local/dc-rberr" id "01TESTLIFECYCLE0000000501" at "/workspace/project"
|
|
And a stopped container "01TESTLIFECYCLE0000000501"
|
|
And CLI rebuild_container mock that raises RuntimeError
|
|
When I invoke CLI resource rebuild with error mock "local/dc-rberr"
|
|
Then the devcontainer CLI exit code should be non-zero
|
|
And the CLI output should contain "Rebuild failed"
|
|
|
|
# ── R7-F1: Session-scoped container cleanup ────────────────
|
|
|
|
Scenario: Session cleanup only stops containers belonging to that session
|
|
Given a mock devcontainer CLI runner
|
|
And a session-scoped active container "01TESTLIFECYCLE0000000600" with ID "ctr-s1" session "sess-A"
|
|
And a session-scoped active container "01TESTLIFECYCLE0000000601" with ID "ctr-s2" session "sess-B"
|
|
When I run session cleanup for session "sess-A"
|
|
Then the container state for "01TESTLIFECYCLE0000000600" should be "stopped"
|
|
And the container state for "01TESTLIFECYCLE0000000601" should be "running"
|
|
|
|
Scenario: Session cleanup with no session_id stops all containers
|
|
Given a mock devcontainer CLI runner
|
|
And an active container "01TESTLIFECYCLE0000000610" with container ID "ctr-all1"
|
|
And an active container "01TESTLIFECYCLE0000000611" with container ID "ctr-all2"
|
|
When I run session cleanup with no session filter
|
|
Then the container state for "01TESTLIFECYCLE0000000610" should be "stopped"
|
|
And the container state for "01TESTLIFECYCLE0000000611" should be "stopped"
|
|
|
|
# ── R8-F1: Eviction wired into production cleanup ────────────
|
|
|
|
Scenario: Session cleanup triggers terminal tracker eviction
|
|
Given a mock devcontainer CLI runner
|
|
And 210 stopped container trackers in the registry
|
|
And an active container "01TESTLIFECYCLE0000000650" with container ID "ctr-evict"
|
|
When I run session cleanup for session "session-evict"
|
|
Then the container state for "01TESTLIFECYCLE0000000650" should be "stopped"
|
|
And the registry should have at most 200 terminal trackers
|
|
|
|
# ── TEST-3: Coverage gap scenarios ──────────────────────────
|
|
|
|
Scenario: list_active_containers_for_session returns only matching session
|
|
Given a mock devcontainer CLI runner
|
|
And a session-scoped active container "01TESTLIFECYCLE0000000700" with ID "ctr-ts3a" session "sess-X"
|
|
And a session-scoped active container "01TESTLIFECYCLE0000000701" with ID "ctr-ts3b" session "sess-Y"
|
|
When I list active containers for session "sess-X"
|
|
Then the session container list should contain "01TESTLIFECYCLE0000000700"
|
|
And the session container list should not contain "01TESTLIFECYCLE0000000701"
|
|
|
|
Scenario: list_active_containers_for_session with empty session returns all
|
|
Given a mock devcontainer CLI runner
|
|
And an active container "01TESTLIFECYCLE0000000710" with container ID "ctr-ts3c"
|
|
And an active container "01TESTLIFECYCLE0000000711" with container ID "ctr-ts3d"
|
|
When I list active containers for an empty session
|
|
Then the session container list should contain "01TESTLIFECYCLE0000000710"
|
|
And the session container list should contain "01TESTLIFECYCLE0000000711"
|
|
|
|
Scenario: list_active_containers_for_session includes unscoped containers
|
|
Given a mock devcontainer CLI runner
|
|
And an active container "01TESTLIFECYCLE0000000720" with container ID "ctr-unscoped"
|
|
And a session-scoped active container "01TESTLIFECYCLE0000000721" with ID "ctr-scoped" session "sess-Z"
|
|
When I list active containers for session "sess-Z"
|
|
Then the session container list should contain "01TESTLIFECYCLE0000000720"
|
|
And the session container list should contain "01TESTLIFECYCLE0000000721"
|
|
|
|
# ── R7-F4: Session close cleanup without session service ────
|
|
|
|
Scenario: Session close cleans up containers even without session service
|
|
Given a facade with no session service
|
|
And a session-scoped active container "01TESTLIFECYCLE0000000630" with no docker ID session "sess-f4"
|
|
When I close session "sess-f4" via the facade
|
|
Then the container state for "01TESTLIFECYCLE0000000630" should be "stopped"
|