use super::*;
use crate::execution_control::CommandRunOutput;
use crate::execution_store::ExecutionStoreOptions;
use crate::isolation::IsolationResult;
use std::cell::RefCell;
use tempfile::TempDir;
fn make_record() -> ExecutionRecord {
let mut record = ExecutionRecord::new("npm test");
record.uuid = "11111111-2222-3333-4444-555555555555".to_string();
record.log_path = "/tmp/session.log".to_string();
for (key, value) in [
("isolated", json!("docker")),
("isolationMode", json!("detached")),
("sessionName", json!("box")),
("image", json!("ubuntu:24.04")),
] {
record.options.insert(key.to_string(), value);
}
record
}
fn probe_with(state: SessionState, container_status: Option<&str>) -> SessionProbe {
SessionProbe {
backend: Some("docker".to_string()),
session_name: Some("box".to_string()),
state,
alive: state == SessionState::Running,
container_status: container_status.map(str::to_string),
}
}
#[test]
fn build_snapshot_image_name_produces_a_lowercase_docker_safe_tag() {
assert_eq!(
build_snapshot_image_name("My_Session Name", 2),
"start-command-resume/my_session-name:2"
);
}
#[test]
fn build_resumed_session_name_appends_the_resume_counter() {
assert_eq!(build_resumed_session_name("box", 3), "box-resume-3");
}
#[test]
fn refuses_to_resume_a_live_session_and_points_at_attach() {
let error = build_resume_plan(
&make_record(),
None,
&probe_with(SessionState::Running, Some("running")),
)
.unwrap_err();
assert!(error.contains("still running"), "{}", error);
assert!(error.contains("--attach"), "{}", error);
}
#[test]
fn restarts_a_stopped_container_with_the_stored_command() {
let plan = build_resume_plan(
&make_record(),
None,
&probe_with(SessionState::Stopped, Some("exited")),
)
.unwrap();
assert_eq!(plan.mode, ResumeMode::DockerStart);
assert_eq!(plan.session_name, "box");
assert_eq!(plan.steps[0].args, vec!["start", "box"]);
assert_eq!(plan.command, "npm test");
}
#[test]
fn snapshots_a_stopped_container_to_run_a_new_command() {
let plan = build_resume_plan(
&make_record(),
Some("npm run build"),
&probe_with(SessionState::Stopped, Some("exited")),
)
.unwrap();
assert_eq!(plan.mode, ResumeMode::DockerSnapshot);
assert_eq!(
plan.snapshot_image.as_deref(),
Some("start-command-resume/box:1")
);
assert_eq!(plan.new_session_name.as_deref(), Some("box-resume-1"));
assert_eq!(
plan.steps[0].args,
vec!["commit", "box", "start-command-resume/box:1"]
);
let run_args = &plan.steps[1].args;
assert_eq!(run_args[0..4], ["run", "-d", "--name", "box-resume-1"]);
assert_eq!(
run_args[run_args.len() - 3..],
["sh", "-c", "npm run build"]
);
}
#[test]
fn carries_stored_docker_runtime_options_into_the_snapshot_run() {
let mut record = make_record();
for (key, value) in [
("privileged", json!(true)),
("env", json!(["A=1"])),
("volumes", json!(["/host:/container"])),
("networks", json!(["testnet"])),
] {
record.options.insert(key.to_string(), value);
}
let plan = build_resume_plan(
&record,
Some("echo hi"),
&probe_with(SessionState::Stopped, Some("exited")),
)
.unwrap();
let run_args = &plan.steps[1].args;
for expected in ["--privileged", "/host:/container", "A=1", "testnet"] {
assert!(
run_args.iter().any(|arg| arg == expected),
"missing {} in {:?}",
expected,
run_args
);
}
}
#[test]
fn rebuilds_a_multi_network_snapshot_with_create_connect_start() {
let mut record = make_record();
record.options.insert(
"networks".to_string(),
json!(["frontend", "backend", "metrics"]),
);
let plan = build_resume_plan(
&record,
Some("echo hi"),
&probe_with(SessionState::Stopped, Some("exited")),
)
.unwrap();
let steps: Vec<&[String]> = plan.steps.iter().map(|step| step.args.as_slice()).collect();
assert_eq!(steps.len(), 5);
assert_eq!(steps[0][0], "commit");
assert_eq!(steps[1][0..3], ["create", "--name", "box-resume-1"]);
assert!(
steps[1].iter().any(|arg| arg == "frontend"),
"the first network still goes to --network: {:?}",
steps[1]
);
assert_eq!(steps[2], ["network", "connect", "backend", "box-resume-1"]);
assert_eq!(steps[3], ["network", "connect", "metrics", "box-resume-1"]);
assert_eq!(steps[4], ["start", "box-resume-1"]);
}
#[test]
fn increments_the_counter_across_repeated_resumes() {
let mut record = make_record();
record.options.insert("resumeCount".to_string(), json!(2));
let plan = build_resume_plan(
&record,
Some("echo hi"),
&probe_with(SessionState::Stopped, Some("exited")),
)
.unwrap();
assert_eq!(
plan.snapshot_image.as_deref(),
Some("start-command-resume/box:3")
);
assert_eq!(plan.new_session_name.as_deref(), Some("box-resume-3"));
}
#[test]
fn relaunches_when_the_container_is_gone() {
let plan = build_resume_plan(
&make_record(),
None,
&probe_with(SessionState::Missing, None),
)
.unwrap();
assert_eq!(plan.mode, ResumeMode::Relaunch);
assert_eq!(plan.session_name, "box");
assert_eq!(plan.command, "npm test");
let launch = plan.launch_options.unwrap();
assert_eq!(launch.image.as_deref(), Some("ubuntu:24.04"));
assert!(launch.detached);
assert_eq!(
launch
.log_path
.as_deref()
.map(std::path::Path::to_string_lossy),
Some(std::borrow::Cow::Borrowed("/tmp/session.log"))
);
}
#[test]
fn relaunches_a_dead_screen_session() {
let mut record = make_record();
record
.options
.insert("isolated".to_string(), json!("screen"));
record
.options
.insert("sessionName".to_string(), json!("work"));
let probe = SessionProbe {
backend: Some("screen".to_string()),
session_name: Some("work".to_string()),
state: SessionState::Missing,
alive: false,
container_status: None,
};
let plan = build_resume_plan(&record, None, &probe).unwrap();
assert_eq!(plan.mode, ResumeMode::Relaunch);
assert_eq!(plan.backend, "screen");
let with_command = build_resume_plan(&record, Some("echo hi"), &probe).unwrap();
assert_eq!(with_command.command, "echo hi");
}
#[test]
fn rejects_records_without_a_session_name() {
let mut record = make_record();
record.options.remove("sessionName");
let error =
build_resume_plan(&record, None, &probe_with(SessionState::Missing, None)).unwrap_err();
assert!(
error.contains("does not contain an isolation session name"),
"{}",
error
);
}
#[test]
fn rejects_non_detached_executions() {
let mut record = make_record();
record
.options
.insert("isolationMode".to_string(), json!("attached"));
let error =
build_resume_plan(&record, None, &probe_with(SessionState::Stopped, None)).unwrap_err();
assert!(
error.contains("Only detached isolated executions"),
"{}",
error
);
}
#[test]
fn rejects_records_with_no_command_to_resume() {
let mut record = make_record();
record.command = String::new();
let error =
build_resume_plan(&record, None, &probe_with(SessionState::Missing, None)).unwrap_err();
assert!(error.contains("no stored command"), "{}", error);
}
#[test]
fn format_resume_result_emits_a_nested_links_notation_block() {
let output = format_resume_result_as_links_notation(&ResumeResultFields {
identifier: "box",
uuid: "u-1",
mode: ResumeMode::DockerStart,
backend: "docker",
session_name: "box",
previous_session_name: None,
snapshot_image: None,
resource_limits: &[],
command: "npm test",
message: "Resumed detached docker container: box",
});
assert!(output.starts_with("executionResume\n"));
assert!(output.contains("\n mode docker-start\n"));
}
struct ScriptedRunner {
container_status: &'static str,
step_success: bool,
calls: RefCell<Vec<Vec<String>>>,
}
impl ScriptedRunner {
fn new(container_status: &'static str) -> Self {
ScriptedRunner {
container_status,
step_success: true,
calls: RefCell::new(Vec::new()),
}
}
fn failing(container_status: &'static str) -> Self {
ScriptedRunner {
container_status,
step_success: false,
calls: RefCell::new(Vec::new()),
}
}
}
impl CommandRunner for ScriptedRunner {
fn run(&self, command: &str, args: &[String]) -> CommandRunOutput {
if args.iter().any(|arg| arg == "--size") {
return CommandRunOutput {
success: true,
stdout: "1024".into(),
..Default::default()
};
}
if args.first().map(String::as_str) == Some("info") {
return CommandRunOutput {
success: true,
stdout: "{\"DockerRootDir\":\"/docker\"}".into(),
..Default::default()
};
}
if command == "df" {
return CommandRunOutput { success: true, stdout: "Filesystem 1024-blocks Used Available Capacity Mounted on\n/dev/mock 999999999 0 999999999 0% /docker".into(), ..Default::default() };
}
if args.first().map(String::as_str) == Some("inspect") {
let found = self.container_status != "missing";
return CommandRunOutput {
success: found,
stdout: if found {
format!("{}\n", self.container_status)
} else {
String::new()
},
stderr: if found {
String::new()
} else {
"No such object: box".to_string()
},
status: Some(if found { 0 } else { 1 }),
error: None,
};
}
let mut call = vec![command.to_string()];
call.extend(args.iter().cloned());
self.calls.borrow_mut().push(call);
if self.step_success {
CommandRunOutput {
success: true,
stdout: "deadbeef\n".to_string(),
stderr: String::new(),
status: Some(0),
error: None,
}
} else {
CommandRunOutput {
success: false,
stdout: String::new(),
stderr: "no such container".to_string(),
status: Some(1),
error: None,
}
}
}
}
#[derive(Default)]
struct RecordingHooks {
watchers: RefCell<Vec<String>>,
launches: RefCell<Vec<(String, String, IsolationOptions)>>,
relaunch_result: Option<IsolationResult>,
}
impl ResumeHooks for RecordingHooks {
fn start_watcher(&self, session_name: &str, _record: &ExecutionRecord) {
self.watchers.borrow_mut().push(session_name.to_string());
}
fn relaunch(
&self,
backend: &str,
command: &str,
options: &IsolationOptions,
) -> IsolationResult {
self.launches.borrow_mut().push((
backend.to_string(),
command.to_string(),
options.clone(),
));
self.relaunch_result
.clone()
.unwrap_or_else(|| IsolationResult {
success: true,
container_id: Some("newid".to_string()),
..Default::default()
})
}
fn reconcile(&self, record: &ExecutionRecord) -> ExecutionRecord {
record.clone()
}
}
fn with_test_lock(temp: &TempDir, mut overrides: serde_json::Value) -> serde_json::Value {
overrides["snapshotLockPath"] = json!(temp.path().join("snapshot.lock"));
overrides
}
fn store_with(record: &ExecutionRecord) -> (TempDir, ExecutionStore) {
let temp = TempDir::new().unwrap();
let store = ExecutionStore::with_options(ExecutionStoreOptions {
app_folder: Some(temp.path().to_path_buf()),
..Default::default()
});
store.save(record).unwrap();
(temp, store)
}
#[test]
fn resume_execution_reports_disabled_tracking() {
let result = resume_execution_with(
None,
"box",
None,
None,
&ScriptedRunner::new("exited"),
&RecordingHooks::default(),
);
assert!(!result.success);
assert!(result
.error
.unwrap()
.contains("Execution tracking is disabled"));
}
#[test]
fn resume_execution_reports_an_unknown_identifier() {
let temp = TempDir::new().unwrap();
let store = ExecutionStore::with_options(ExecutionStoreOptions {
app_folder: Some(temp.path().to_path_buf()),
..Default::default()
});
let result = resume_execution_with(
Some(&store),
"nope",
None,
None,
&ScriptedRunner::new("exited"),
&RecordingHooks::default(),
);
assert!(!result.success);
assert!(result.error.unwrap().contains("No execution found"));
}
#[test]
fn resume_execution_starts_a_stopped_container_and_reattaches_the_watcher() {
let record = make_record();
let (_temp, store) = store_with(&record);
let runner = ScriptedRunner::new("exited");
let hooks = RecordingHooks::default();
let result = resume_execution_with(Some(&store), "box", None, None, &runner, &hooks);
assert!(result.success, "{:?}", result.error);
assert_eq!(
runner.calls.borrow().clone(),
vec![vec![
"docker".to_string(),
"start".to_string(),
"box".to_string()
]]
);
assert_eq!(hooks.watchers.borrow().clone(), vec!["box".to_string()]);
let saved = store.get(&record.uuid).unwrap();
assert_eq!(saved.status, ExecutionStatus::Executing);
assert_eq!(
saved.options.get("resumeCount").and_then(|v| v.as_u64()),
Some(1)
);
}
#[test]
fn resume_execution_keeps_the_same_uuid_and_remembers_the_previous_session_name() {
let record = make_record();
let (temp, store) = store_with(&record);
let result = resume_execution_with_resources(
Some(&store),
"box",
Some("npm run build"),
None,
&ScriptedRunner::new("exited"),
&RecordingHooks::default(),
&with_test_lock(&temp, json!({})),
);
assert!(result.success, "{:?}", result.error);
let saved = store.get(&record.uuid).unwrap();
assert_eq!(saved.uuid, record.uuid);
assert_eq!(saved.command, "npm run build");
assert_eq!(
saved.options.get("sessionName").and_then(|v| v.as_str()),
Some("box-resume-1")
);
assert_eq!(
saved.options.get("sessionNameHistory"),
Some(&json!(["box"]))
);
assert_eq!(
saved.options.get("containerId").and_then(|v| v.as_str()),
Some("deadbeef")
);
assert_eq!(saved.exit_code, None);
assert_eq!(saved.end_time, None);
assert_eq!(store.get("box").map(|r| r.uuid), Some(record.uuid));
}
#[test]
fn resume_execution_fails_when_a_docker_step_fails() {
let record = make_record();
let (_temp, store) = store_with(&record);
let result = resume_execution_with(
Some(&store),
"box",
None,
None,
&ScriptedRunner::failing("exited"),
&RecordingHooks::default(),
);
assert!(!result.success);
assert!(result.error.unwrap().contains("no such container"));
assert_eq!(
store
.get(&record.uuid)
.and_then(|r| r.options.get("resumeCount").cloned()),
None
);
}
#[test]
fn resume_execution_relaunches_through_the_isolation_backend_when_the_container_is_gone() {
let record = make_record();
let (_temp, store) = store_with(&record);
let runner = ScriptedRunner::failing("missing");
let hooks = RecordingHooks::default();
let result = resume_execution_with(Some(&store), "box", None, None, &runner, &hooks);
assert!(result.success, "{:?}", result.error);
let launches = hooks.launches.borrow();
assert_eq!(launches.len(), 1);
assert_eq!(launches[0].0, "docker");
assert_eq!(launches[0].1, "npm test");
assert_eq!(launches[0].2.session.as_deref(), Some("box"));
assert_eq!(store.get(&record.uuid).map(|r| r.uuid), Some(record.uuid));
}
#[test]
fn resume_execution_surfaces_relaunch_failures() {
let record = make_record();
let (_temp, store) = store_with(&record);
let hooks = RecordingHooks {
relaunch_result: Some(IsolationResult {
success: false,
message: "docker is not running".to_string(),
..Default::default()
}),
..Default::default()
};
let result = resume_execution_with(
Some(&store),
"box",
None,
None,
&ScriptedRunner::failing("missing"),
&hooks,
);
assert!(!result.success);
assert!(result.error.unwrap().contains("docker is not running"));
}
#[test]
fn handoff_resume_keeps_container_identity_and_never_snapshots() {
let mut record = make_record();
record.options.insert("commandHandoff".into(), json!(true));
record
.options
.insert("containerId".into(), json!("original-id"));
let (_temp, store) = store_with(&record);
let runner = ScriptedRunner::new("exited");
let result = resume_execution_with(
Some(&store),
"box",
Some("printf replacement"),
None,
&runner,
&RecordingHooks::default(),
);
assert!(result.success, "{:?}", result.error);
let calls = runner.calls.borrow();
assert!(calls
.iter()
.any(|c| c.get(1).map(String::as_str) == Some("cp")));
assert!(calls
.iter()
.any(|c| c.get(1).map(String::as_str) == Some("start")));
assert!(!calls.iter().any(|c| matches!(
c.get(1).map(String::as_str),
Some("commit" | "run" | "create")
)));
let saved = store.get(&record.uuid).unwrap();
assert_eq!(
saved.options.get("containerId"),
Some(&json!("original-id"))
);
assert_eq!(saved.options.get("sessionName"), Some(&json!("box")));
}
#[test]
fn changed_labels_force_a_guarded_snapshot_for_the_stored_command() {
let mut record = make_record();
record.options.insert("commandHandoff".into(), json!(true));
record
.options
.insert("labels".into(), json!(["role=old", "team=runtime"]));
let (temp, store) = store_with(&record);
let runner = ScriptedRunner::new("exited");
let result = resume_execution_with_resources(
Some(&store),
"box",
None,
None,
&runner,
&RecordingHooks::default(),
&with_test_lock(&temp, json!({"labels":["role=new"]})),
);
assert!(result.success, "{:?}", result.error);
let calls = runner.calls.borrow();
let launch = calls
.iter()
.find(|c| c.get(1).map(String::as_str) == Some("run"))
.unwrap();
assert!(launch.contains(&"role=new".into()));
assert!(launch.contains(&"team=runtime".into()));
assert!(!launch.contains(&"role=old".into()));
assert_eq!(
store
.get(&record.uuid)
.unwrap()
.options
.get("commandHandoff"),
Some(&json!(true))
);
}
struct SafetyRunner {
base: ScriptedRunner,
low_disk: bool,
}
impl CommandRunner for SafetyRunner {
fn run(&self, command: &str, args: &[String]) -> CommandRunOutput {
if command == "df" && self.low_disk {
return CommandRunOutput { success: true, stdout: "Filesystem 1024-blocks Used Available Capacity Mounted on\n/dev/mock 1 1 0 100% /docker".into(), ..Default::default() };
}
if args.iter().any(|arg| arg == "{{.State.Running}}") {
return CommandRunOutput {
success: true,
stdout: "true".into(),
..Default::default()
};
}
self.base.run(command, args)
}
}
#[test]
fn snapshot_disk_shortage_restores_the_original_without_commit_or_remove() {
let record = make_record();
let (temp, store) = store_with(&record);
let runner = SafetyRunner {
base: ScriptedRunner::new("exited"),
low_disk: true,
};
let result = resume_execution_with_resources(
Some(&store),
"box",
Some("new"),
None,
&runner,
&RecordingHooks::default(),
&with_test_lock(&temp, json!({"removeOriginal":true})),
);
assert!(!result.success);
assert!(result.error.unwrap().contains("Insufficient disk"));
assert!(runner.base.calls.borrow().is_empty());
assert_eq!(
store.get(&record.uuid).unwrap().options.get("sessionName"),
Some(&json!("box"))
);
}
#[test]
fn remove_original_is_non_forced_and_runs_only_after_successor_start() {
let record = make_record();
let (temp, store) = store_with(&record);
let runner = SafetyRunner {
base: ScriptedRunner::new("exited"),
low_disk: false,
};
let result = resume_execution_with_resources(
Some(&store),
"box",
Some("new"),
None,
&runner,
&RecordingHooks::default(),
&with_test_lock(&temp, json!({"removeOriginal":true})),
);
assert!(result.success, "{:?}", result.error);
let calls = runner.base.calls.borrow();
let launch = calls
.iter()
.position(|c| c.get(1).map(String::as_str) == Some("run"))
.unwrap();
let removal = calls
.iter()
.position(|c| c.get(1).map(String::as_str) == Some("rm"))
.unwrap();
assert!(launch < removal);
assert_eq!(calls[removal], ["docker", "rm", "box"]);
}
#[test]
fn repeated_snapshot_successors_retain_original_root_session_attribution() {
let record = make_record();
let (temp, store) = store_with(&record);
for role in ["first", "second"] {
let runner = ScriptedRunner::new("exited");
let result = resume_execution_with_resources(
Some(&store),
&record.uuid,
Some("new"),
None,
&runner,
&RecordingHooks::default(),
&with_test_lock(&temp, json!({"labels":[format!("role={}", role)]})),
);
assert!(result.success, "{:?}", result.error);
let calls = runner.calls.borrow();
let launch = calls
.iter()
.find(|c| c.get(1).map(String::as_str) == Some("run"))
.unwrap();
assert!(launch.contains(&"start-command.root-session=box".into()));
assert_eq!(
store.get(&record.uuid).unwrap().options.get("rootSession"),
Some(&json!("box"))
);
}
assert_eq!(
store.get(&record.uuid).unwrap().options.get("resumeCount"),
Some(&json!(2))
);
}