Files
cleveragents-core/features/devcontainer_cleanup.feature
CoreRasurae cf67ba0a86 feat(devcontainer): add lazy activation and lifecycle management
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
2026-03-10 12:17:51 +00:00

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"