1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
use std::path::{Path, PathBuf};
use std::process::Command;
use std::fs;
use tokio::sync::Mutex;
use once_cell::sync::Lazy;
use crate::runtime::{
container::{ContainerRuntime, ContainerOutput, ContainerError},
emulation::{self, EmulationRuntime},
};
#[cfg(test)]
mod emulation_cleanup_tests {
use super::*;
/// Create a process and workspace that need to be tracked for cleanup
async fn setup_emulation_resources() -> (Option<u32>, Option<PathBuf>) {
// Create an emulation runtime to generate a workspace
let runtime = EmulationRuntime::new();
// Get the workspace path (normally this is tracked automatically)
let workspaces = emulation::get_tracked_workspaces();
let workspace_path = if !workspaces.is_empty() {
Some(workspaces[0].clone())
} else {
None
};
// Try to spawn a long-running background process for testing
let process_id = if cfg!(unix) {
// Use sleep on Unix to create a long-running process
let child = Command::new("sh")
.arg("-c")
.arg("sleep 300 &") // Run sleep for 300 seconds in background
.spawn();
match child {
Ok(child) => {
// Get the PID and track it
let pid = child.id();
emulation::track_process(pid);
Some(pid)
},
Err(_) => None
}
} else if cfg!(windows) {
// Use timeout on Windows (equivalent to sleep)
let child = Command::new("cmd")
.arg("/C")
.arg("start /b timeout /t 300") // Run timeout for 300 seconds
.spawn();
match child {
Ok(child) => {
// Get the PID and track it
let pid = child.id();
emulation::track_process(pid);
Some(pid)
},
Err(_) => None
}
} else {
None
};
(process_id, workspace_path)
}
/// Check if a process with the given PID is still running
fn is_process_running(pid: u32) -> bool {
if cfg!(unix) {
// On Unix, use kill -0 to check if process exists
let output = Command::new("kill")
.arg("-0")
.arg(&pid.to_string())
.output();
matches!(output, Ok(output) if output.status.success())
} else if cfg!(windows) {
// On Windows, use tasklist to find the process
let output = Command::new("tasklist")
.arg("/FI")
.arg(format!("PID eq {}", pid))
.arg("/NH")
.output();
matches!(output, Ok(output) if String::from_utf8_lossy(&output.stdout).contains(&pid.to_string()))
} else {
false
}
}
#[tokio::test]
async fn test_emulation_process_cleanup() {
// Skip tests on CI or environments where spawning processes might be restricted
if std::env::var("CI").is_ok() {
println!("Running in CI environment, skipping test");
return;
}
// Set up test resources
let (process_id, _) = setup_emulation_resources().await;
// Skip if we couldn't create a process
let process_id = match process_id {
Some(id) => id,
None => {
println!("Could not create test process, skipping test");
return;
}
};
// Verify process is tracked
let processes = emulation::get_tracked_processes();
let is_tracked = processes.contains(&process_id);
assert!(is_tracked, "Process should be tracked for cleanup");
// Run cleanup
emulation::cleanup_resources().await;
// Verify process is removed from tracking
let processes = emulation::get_tracked_processes();
let still_tracked = processes.contains(&process_id);
assert!(!still_tracked, "Process should be removed from tracking after cleanup");
// Verify process is no longer running
assert!(!is_process_running(process_id), "Process should be terminated after cleanup");
}
#[tokio::test]
async fn test_emulation_workspace_cleanup() {
// Create an emulation runtime instance which will automatically create and track a workspace
let runtime = EmulationRuntime::new();
// Get the workspace path
let workspaces = emulation::get_tracked_workspaces();
if workspaces.is_empty() {
println!("No workspace was tracked, skipping test");
return;
}
let workspace_path = &workspaces[0];
// Verify workspace exists
assert!(workspace_path.exists(), "Workspace should exist before cleanup");
// Run cleanup
emulation::cleanup_resources().await;
// Verify workspace is removed from tracking
let workspaces = emulation::get_tracked_workspaces();
let still_tracked = workspaces.iter().any(|w| w == workspace_path);
assert!(!still_tracked, "Workspace should be removed from tracking after cleanup");
// Verify workspace directory is deleted
assert!(!workspace_path.exists(), "Workspace directory should be deleted after cleanup");
}
#[tokio::test]
async fn test_run_container_with_emulation() {
// Create an emulation runtime
let runtime = EmulationRuntime::new();
// Run a simple command in emulation mode
let result = runtime
.run_container(
"alpine:latest", // In emulation mode, image is just for logging
&["echo", "test cleanup"],
&[],
Path::new("/"),
&[(Path::new("."), Path::new("/github/workspace"))],
)
.await;
// Verify command executed successfully
match result {
Ok(output) => {
assert!(output.stdout.contains("test cleanup"), "Command output should contain test message");
assert_eq!(output.exit_code, 0, "Command should exit with status 0");
},
Err(e) => {
panic!("Failed to run command in emulation mode: {}", e);
}
}
// Count resources before cleanup
let workspaces_count = emulation::get_tracked_workspaces().len();
assert!(workspaces_count > 0, "At least one workspace should be tracked");
// Run cleanup
emulation::cleanup_resources().await;
// Verify all resources are cleaned up
let remaining_workspaces = emulation::get_tracked_workspaces().len();
assert_eq!(remaining_workspaces, 0, "All workspaces should be cleaned up");
}
#[tokio::test]
async fn test_full_resource_cleanup() {
// Skip tests on CI or environments where spawning processes might be restricted
if std::env::var("CI").is_ok() {
println!("Running in CI environment, skipping test");
return;
}
// Set up test resources
let (process_id, _) = setup_emulation_resources().await;
// Create an additional emulation runtime to have more workspaces
let runtime = EmulationRuntime::new();
// Count resources before cleanup
let process_count = emulation::get_tracked_processes().len();
let workspace_count = emulation::get_tracked_workspaces().len();
// Ensure we have at least one resource to clean up
assert!(process_count > 0 || workspace_count > 0,
"At least one process or workspace should be tracked");
// Run full cleanup
emulation::cleanup_resources().await;
// Verify all resources are cleaned up
let remaining_processes = emulation::get_tracked_processes().len();
let remaining_workspaces = emulation::get_tracked_workspaces().len();
assert_eq!(remaining_processes, 0, "All processes should be cleaned up");
assert_eq!(remaining_workspaces, 0, "All workspaces should be cleaned up");
// If we had a process, verify it's not running anymore
if let Some(pid) = process_id {
assert!(!is_process_running(pid), "Process should be terminated after cleanup");
}
}
}