1use std::io::Write;
7use std::process::{Command, Stdio};
8use std::time::{Duration, Instant};
9
10use crate::{Error, Language, Result};
11
12use super::ExecutionResult;
13
14pub trait Executor: Send + Sync {
16 fn execute(&self, code: &str, input: &str, timeout_ms: u64) -> Result<ExecutionResult>;
22
23 fn language(&self) -> Language;
25}
26
27#[derive(Debug, Default)]
29pub struct PythonExecutor {
30 interpreter: String,
32}
33
34impl PythonExecutor {
35 #[must_use]
37 pub fn new() -> Self {
38 Self {
39 interpreter: "python3".to_string(),
40 }
41 }
42
43 #[must_use]
45 pub fn with_interpreter(interpreter: impl Into<String>) -> Self {
46 Self {
47 interpreter: interpreter.into(),
48 }
49 }
50
51 #[must_use]
53 pub fn is_available(&self) -> bool {
54 Command::new(&self.interpreter)
55 .arg("--version")
56 .stdout(Stdio::null())
57 .stderr(Stdio::null())
58 .status()
59 .is_ok()
60 }
61}
62
63impl Executor for PythonExecutor {
64 fn execute(&self, code: &str, input: &str, timeout_ms: u64) -> Result<ExecutionResult> {
65 use std::sync::atomic::{AtomicU64, Ordering};
66 static COUNTER: AtomicU64 = AtomicU64::new(0);
67
68 let start = Instant::now();
69
70 let temp_dir = std::env::temp_dir();
72 let unique_id = COUNTER.fetch_add(1, Ordering::SeqCst);
73 let temp_file = temp_dir.join(format!("verificar_{}_{}.py", std::process::id(), unique_id));
74
75 std::fs::write(&temp_file, code)
76 .map_err(|e| Error::Verification(format!("Failed to write temp file: {e}")))?;
77
78 let mut cmd = Command::new(&self.interpreter);
79 cmd.arg(&temp_file)
80 .stdin(Stdio::piped())
81 .stdout(Stdio::piped())
82 .stderr(Stdio::piped());
83
84 let mut child = cmd
86 .spawn()
87 .map_err(|e| Error::Verification(format!("Failed to spawn Python: {e}")))?;
88
89 if let Some(mut stdin) = child.stdin.take() {
91 let _ = stdin.write_all(input.as_bytes());
92 }
93
94 let timeout = Duration::from_millis(timeout_ms);
96 let output = match wait_with_timeout(child, timeout) {
97 Ok(output) => output,
98 Err(e) => {
99 let _ = std::fs::remove_file(&temp_file);
100 return Err(e);
101 }
102 };
103
104 let _ = std::fs::remove_file(&temp_file);
105
106 let duration_ms = start.elapsed().as_millis() as u64;
107
108 Ok(ExecutionResult {
109 stdout: String::from_utf8_lossy(&output.stdout).to_string(),
110 stderr: String::from_utf8_lossy(&output.stderr).to_string(),
111 exit_code: output.status.code().unwrap_or(-1),
112 duration_ms,
113 })
114 }
115
116 fn language(&self) -> Language {
117 Language::Python
118 }
119}
120
121#[derive(Debug, Default)]
123pub struct RustExecutor {
124 compiler: String,
126}
127
128impl RustExecutor {
129 #[must_use]
131 pub fn new() -> Self {
132 Self {
133 compiler: "rustc".to_string(),
134 }
135 }
136
137 #[must_use]
139 pub fn is_available(&self) -> bool {
140 Command::new(&self.compiler)
141 .arg("--version")
142 .stdout(Stdio::null())
143 .stderr(Stdio::null())
144 .status()
145 .is_ok()
146 }
147}
148
149impl Executor for RustExecutor {
150 fn execute(&self, code: &str, input: &str, timeout_ms: u64) -> Result<ExecutionResult> {
151 use std::sync::atomic::{AtomicU64, Ordering};
152 static COUNTER: AtomicU64 = AtomicU64::new(0);
153
154 let start = Instant::now();
155
156 let temp_dir = std::env::temp_dir();
158 let unique_id = COUNTER.fetch_add(1, Ordering::SeqCst);
159 let source_file =
160 temp_dir.join(format!("verificar_{}_{}.rs", std::process::id(), unique_id));
161 let binary_file = temp_dir.join(format!("verificar_{}_{}", std::process::id(), unique_id));
162
163 std::fs::write(&source_file, code)
164 .map_err(|e| Error::Verification(format!("Failed to write temp file: {e}")))?;
165
166 let compile_output = Command::new(&self.compiler)
167 .arg(&source_file)
168 .arg("-o")
169 .arg(&binary_file)
170 .output()
171 .map_err(|e| Error::Verification(format!("Failed to compile: {e}")))?;
172
173 if !compile_output.status.success() {
174 let _ = std::fs::remove_file(&source_file);
175 return Ok(ExecutionResult {
176 stdout: String::new(),
177 stderr: String::from_utf8_lossy(&compile_output.stderr).to_string(),
178 exit_code: compile_output.status.code().unwrap_or(-1),
179 duration_ms: start.elapsed().as_millis() as u64,
180 });
181 }
182
183 let mut cmd = Command::new(&binary_file);
185 cmd.stdin(Stdio::piped())
186 .stdout(Stdio::piped())
187 .stderr(Stdio::piped());
188
189 let mut child = cmd
190 .spawn()
191 .map_err(|e| Error::Verification(format!("Failed to run binary: {e}")))?;
192
193 if let Some(mut stdin) = child.stdin.take() {
195 let _ = stdin.write_all(input.as_bytes());
196 }
197
198 let timeout = Duration::from_millis(timeout_ms);
200 let output = match wait_with_timeout(child, timeout) {
201 Ok(output) => output,
202 Err(e) => {
203 let _ = std::fs::remove_file(&source_file);
204 let _ = std::fs::remove_file(&binary_file);
205 return Err(e);
206 }
207 };
208
209 let _ = std::fs::remove_file(&source_file);
211 let _ = std::fs::remove_file(&binary_file);
212
213 Ok(ExecutionResult {
214 stdout: String::from_utf8_lossy(&output.stdout).to_string(),
215 stderr: String::from_utf8_lossy(&output.stderr).to_string(),
216 exit_code: output.status.code().unwrap_or(-1),
217 duration_ms: start.elapsed().as_millis() as u64,
218 })
219 }
220
221 fn language(&self) -> Language {
222 Language::Rust
223 }
224}
225
226fn wait_with_timeout(
228 mut child: std::process::Child,
229 timeout: Duration,
230) -> Result<std::process::Output> {
231 use std::io::Read;
232 use std::sync::mpsc;
233 use std::thread;
234
235 let stdout_handle = child.stdout.take();
237 let stderr_handle = child.stderr.take();
238
239 let stdout_thread = thread::spawn(move || {
241 let mut buf = Vec::new();
242 if let Some(mut stdout) = stdout_handle {
243 let _ = stdout.read_to_end(&mut buf);
244 }
245 buf
246 });
247
248 let stderr_thread = thread::spawn(move || {
249 let mut buf = Vec::new();
250 if let Some(mut stderr) = stderr_handle {
251 let _ = stderr.read_to_end(&mut buf);
252 }
253 buf
254 });
255
256 let (tx, rx) = mpsc::channel();
258 let wait_thread = thread::spawn(move || {
259 let result = child.wait();
260 let _ = tx.send(result);
261 child
262 });
263
264 match rx.recv_timeout(timeout) {
266 Ok(Ok(status)) => {
267 let _ = wait_thread.join();
271
272 let stdout = stdout_thread.join().unwrap_or_default();
273 let stderr = stderr_thread.join().unwrap_or_default();
274
275 Ok(std::process::Output {
276 status,
277 stdout,
278 stderr,
279 })
280 }
281 Ok(Err(e)) => {
282 let _ = wait_thread.join();
283 let _ = stdout_thread.join();
284 let _ = stderr_thread.join();
285 Err(Error::Verification(format!("Wait error: {e}")))
286 }
287 Err(mpsc::RecvTimeoutError::Timeout) => {
288 if let Ok(mut child) = wait_thread.join() {
290 let _ = child.kill();
291 let _ = child.wait(); }
293 let _ = stdout_thread.join();
295 let _ = stderr_thread.join();
296 Err(Error::Verification("Execution timed out".to_string()))
297 }
298 Err(mpsc::RecvTimeoutError::Disconnected) => {
299 let _ = wait_thread.join();
300 let _ = stdout_thread.join();
301 let _ = stderr_thread.join();
302 Err(Error::Verification(
303 "Process wait thread disconnected".to_string(),
304 ))
305 }
306 }
307}
308
309#[must_use]
311pub fn executor_for(language: Language) -> Option<Box<dyn Executor>> {
312 match language {
313 Language::Python => Some(Box::new(PythonExecutor::new())),
314 Language::Rust => Some(Box::new(RustExecutor::new())),
315 _ => None,
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322
323 #[test]
324 fn test_python_executor_simple() {
325 let executor = PythonExecutor::new();
326 if !executor.is_available() {
327 eprintln!("Python not available, skipping test");
328 return;
329 }
330
331 let result = executor
332 .execute("print('hello')", "", 5000)
333 .expect("execution should succeed");
334
335 assert_eq!(result.stdout.trim(), "hello");
336 assert_eq!(result.exit_code, 0);
337 }
338
339 #[test]
340 fn test_python_executor_with_input() {
341 let executor = PythonExecutor::new();
342 if !executor.is_available() {
343 eprintln!("Python not available, skipping test");
344 return;
345 }
346
347 let code = "x = input()\nprint(f'got: {x}')";
348 let result = executor
349 .execute(code, "test", 5000)
350 .expect("execution should succeed");
351
352 assert_eq!(result.stdout.trim(), "got: test");
353 }
354
355 #[test]
356 fn test_python_executor_error() {
357 let executor = PythonExecutor::new();
358 if !executor.is_available() {
359 eprintln!("Python not available, skipping test");
360 return;
361 }
362
363 let result = executor
364 .execute("raise ValueError('oops')", "", 5000)
365 .expect("execution should succeed");
366
367 assert_ne!(result.exit_code, 0);
368 assert!(result.stderr.contains("ValueError"));
369 }
370
371 #[test]
372 fn test_python_executor_arithmetic() {
373 let executor = PythonExecutor::new();
374 if !executor.is_available() {
375 eprintln!("Python not available, skipping test");
376 return;
377 }
378
379 let code = "print(1 + 2 * 3)";
380 let result = executor
381 .execute(code, "", 5000)
382 .expect("execution should succeed");
383
384 assert_eq!(result.stdout.trim(), "7");
385 }
386
387 #[test]
388 fn test_executor_for_python() {
389 let executor = executor_for(Language::Python);
390 assert!(executor.is_some());
391 assert_eq!(executor.unwrap().language(), Language::Python);
392 }
393
394 #[test]
395 fn test_executor_for_rust() {
396 let executor = executor_for(Language::Rust);
397 assert!(executor.is_some());
398 assert_eq!(executor.unwrap().language(), Language::Rust);
399 }
400
401 #[test]
402 fn test_executor_for_unsupported() {
403 let executor = executor_for(Language::Bash);
404 assert!(executor.is_none());
405 }
406
407 #[test]
408 fn test_python_executor_with_interpreter() {
409 let executor = PythonExecutor::with_interpreter("python3");
410 assert_eq!(executor.interpreter, "python3");
411 }
412
413 #[test]
414 fn test_python_executor_default() {
415 let executor = PythonExecutor::default();
416 assert!(executor.interpreter.is_empty() || executor.interpreter == "python3");
417 }
418
419 #[test]
420 fn test_python_executor_language() {
421 let executor = PythonExecutor::new();
422 assert_eq!(executor.language(), Language::Python);
423 }
424
425 #[test]
426 fn test_python_executor_debug() {
427 let executor = PythonExecutor::new();
428 let debug = format!("{:?}", executor);
429 assert!(debug.contains("PythonExecutor"));
430 }
431
432 #[test]
433 fn test_rust_executor_new() {
434 let executor = RustExecutor::new();
435 assert_eq!(executor.compiler, "rustc");
436 }
437
438 #[test]
439 fn test_rust_executor_default() {
440 let executor = RustExecutor::default();
441 assert!(executor.compiler.is_empty() || executor.compiler == "rustc");
442 }
443
444 #[test]
445 fn test_rust_executor_language() {
446 let executor = RustExecutor::new();
447 assert_eq!(executor.language(), Language::Rust);
448 }
449
450 #[test]
451 fn test_rust_executor_debug() {
452 let executor = RustExecutor::new();
453 let debug = format!("{:?}", executor);
454 assert!(debug.contains("RustExecutor"));
455 }
456
457 #[test]
458 fn test_rust_executor_is_available() {
459 let executor = RustExecutor::new();
460 let _ = executor.is_available();
462 }
463
464 #[test]
465 fn test_rust_executor_simple() {
466 let executor = RustExecutor::new();
467 if !executor.is_available() {
468 eprintln!("rustc not available, skipping test");
469 return;
470 }
471
472 let code = r#"fn main() { println!("hello from rust"); }"#;
473 let result = executor
474 .execute(code, "", 10000)
475 .expect("execution should succeed");
476
477 assert_eq!(result.stdout.trim(), "hello from rust");
478 assert_eq!(result.exit_code, 0);
479 }
480
481 #[test]
482 fn test_rust_executor_compile_error() {
483 let executor = RustExecutor::new();
484 if !executor.is_available() {
485 eprintln!("rustc not available, skipping test");
486 return;
487 }
488
489 let code = "fn main() { invalid syntax }";
490 let result = executor
491 .execute(code, "", 10000)
492 .expect("execution should return compile error");
493
494 assert_ne!(result.exit_code, 0);
495 assert!(!result.stderr.is_empty());
496 }
497
498 #[test]
499 fn test_rust_executor_with_input() {
500 let executor = RustExecutor::new();
501 if !executor.is_available() {
502 eprintln!("rustc not available, skipping test");
503 return;
504 }
505
506 let code = r#"
507use std::io::{self, BufRead};
508fn main() {
509 let stdin = io::stdin();
510 let line = stdin.lock().lines().next().unwrap().unwrap();
511 println!("got: {}", line);
512}
513"#;
514 let result = executor
515 .execute(code, "test input", 10000)
516 .expect("execution should succeed");
517
518 assert!(result.stdout.contains("got: test input"));
519 }
520
521 #[test]
522 fn test_python_executor_timeout() {
523 let executor = PythonExecutor::new();
524 if !executor.is_available() {
525 eprintln!("Python not available, skipping test");
526 return;
527 }
528
529 let code = "import time; time.sleep(10)";
531 let result = executor.execute(code, "", 100); assert!(result.is_err());
534 let err = result.unwrap_err();
535 let err_str = err.to_string();
536 assert!(err_str.contains("timeout") || err_str.contains("timed out"));
537 }
538
539 #[test]
540 fn test_python_executor_multiple_lines_output() {
541 let executor = PythonExecutor::new();
542 if !executor.is_available() {
543 eprintln!("Python not available, skipping test");
544 return;
545 }
546
547 let code = "for i in range(3): print(i)";
548 let result = executor
549 .execute(code, "", 5000)
550 .expect("execution should succeed");
551
552 assert!(result.stdout.contains("0"));
553 assert!(result.stdout.contains("1"));
554 assert!(result.stdout.contains("2"));
555 }
556
557 #[test]
558 fn test_python_executor_syntax_error() {
559 let executor = PythonExecutor::new();
560 if !executor.is_available() {
561 eprintln!("Python not available, skipping test");
562 return;
563 }
564
565 let code = "def f(: pass"; let result = executor
567 .execute(code, "", 5000)
568 .expect("execution should succeed");
569
570 assert_ne!(result.exit_code, 0);
571 assert!(result.stderr.contains("SyntaxError"));
572 }
573}