Skip to content

Commit 54498b5

Browse files
committed
feat: add Docker container cleanup to prevent hanging containers
- Add cleanup_hanging_docker_containers function to handle interrupted test runs - Check for existing containers with instance name before starting new tests - Stop and remove hanging containers automatically during preflight cleanup - Add structured logging to track container cleanup operations - Handle graceful cleanup when containers are not running vs completely missing - Prevent 'container name already in use' errors in E2E tests This addresses the issue where testcontainers doesn't clean up containers when test execution is interrupted abruptly (Ctrl+C, system crashes, etc). Normal test completion still relies on testcontainers automatic cleanup.
1 parent 0ef5fef commit 54498b5

File tree

1 file changed

+112
-6
lines changed

1 file changed

+112
-6
lines changed

src/e2e/tasks/container/preflight_cleanup.rs

Lines changed: 112 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ use crate::e2e::environment::TestEnvironment;
99
use crate::e2e::tasks::preflight_cleanup::{
1010
cleanup_build_directory, cleanup_templates_directory, PreflightCleanupError,
1111
};
12-
use tracing::info;
12+
use crate::shared::executor::CommandExecutor;
13+
use tracing::{info, warn};
1314

1415
/// Performs pre-flight cleanup for Docker-based E2E tests
1516
///
1617
/// This function is specifically designed for Docker-based E2E tests that use
17-
/// testcontainers for container lifecycle management. It only cleans directories
18-
/// since Docker containers are automatically cleaned up when testcontainer objects
19-
/// are dropped.
18+
/// testcontainers for container lifecycle management. It cleans up directories
19+
/// and any hanging Docker containers from previous interrupted test runs.
2020
///
2121
/// # Arguments
2222
///
@@ -41,8 +41,8 @@ pub fn cleanup_lingering_resources(env: &TestEnvironment) -> Result<(), Prefligh
4141
// Clean the templates directory to ensure fresh embedded template extraction for E2E tests
4242
cleanup_templates_directory(env)?;
4343

44-
// Note: Docker containers are automatically cleaned up by testcontainers when objects are dropped
45-
// No need for explicit container cleanup like with LXD/OpenTofu
44+
// Clean up any hanging Docker containers from interrupted test runs
45+
cleanup_hanging_docker_containers(env);
4646

4747
info!(
4848
operation = "preflight_cleanup_docker",
@@ -51,3 +51,109 @@ pub fn cleanup_lingering_resources(env: &TestEnvironment) -> Result<(), Prefligh
5151
);
5252
Ok(())
5353
}
54+
55+
/// Clean up hanging Docker containers from interrupted test runs
56+
///
57+
/// This function handles the case where testcontainers didn't clean up properly
58+
/// due to abrupt test termination. It removes containers with the instance name
59+
/// to prevent container name conflicts in subsequent test runs.
60+
///
61+
/// # Safety
62+
///
63+
/// This function is only intended for E2E test environments and should never
64+
/// be called in production code paths. It specifically targets test containers.
65+
///
66+
/// # Arguments
67+
///
68+
/// * `env` - The test environment containing the instance name
69+
fn cleanup_hanging_docker_containers(env: &TestEnvironment) {
70+
let instance_name = env.config.instance_name.as_str();
71+
let command_executor = CommandExecutor::new();
72+
73+
info!(
74+
operation = "hanging_container_cleanup",
75+
container_name = instance_name,
76+
"Checking for hanging Docker containers from previous test runs"
77+
);
78+
79+
// First, check if the container exists
80+
let check_result = command_executor.run_command(
81+
"docker",
82+
&["ps", "-aq", "--filter", &format!("name={}", instance_name)],
83+
None,
84+
);
85+
86+
match check_result {
87+
Ok(output) => {
88+
if output.trim().is_empty() {
89+
info!(
90+
operation = "hanging_container_cleanup",
91+
container_name = instance_name,
92+
status = "clean",
93+
"No hanging containers found"
94+
);
95+
return;
96+
}
97+
98+
info!(
99+
operation = "hanging_container_cleanup",
100+
container_name = instance_name,
101+
"Found hanging container, attempting cleanup"
102+
);
103+
104+
// Try to stop the container (in case it's running)
105+
match command_executor.run_command("docker", &["stop", instance_name], None) {
106+
Ok(_) => {
107+
info!(
108+
operation = "hanging_container_cleanup",
109+
container_name = instance_name,
110+
action = "stop",
111+
status = "success",
112+
"Container stopped successfully"
113+
);
114+
}
115+
Err(e) => {
116+
// Container might not be running, which is okay
117+
warn!(
118+
operation = "hanging_container_cleanup",
119+
container_name = instance_name,
120+
action = "stop",
121+
status = "skipped",
122+
error = %e,
123+
"Could not stop container (probably not running)"
124+
);
125+
}
126+
}
127+
128+
// Remove the container
129+
match command_executor.run_command("docker", &["rm", instance_name], None) {
130+
Ok(_) => {
131+
info!(
132+
operation = "hanging_container_cleanup",
133+
container_name = instance_name,
134+
status = "success",
135+
"Hanging container cleaned up successfully"
136+
);
137+
}
138+
Err(e) => {
139+
warn!(
140+
operation = "hanging_container_cleanup",
141+
container_name = instance_name,
142+
status = "failed",
143+
error = %e,
144+
"Failed to remove hanging container (this may cause test failures)"
145+
);
146+
}
147+
}
148+
}
149+
Err(e) => {
150+
warn!(
151+
operation = "hanging_container_cleanup",
152+
container_name = instance_name,
153+
status = "check_failed",
154+
error = %e,
155+
"Could not check for hanging containers"
156+
);
157+
}
158+
}
159+
}

0 commit comments

Comments
 (0)