1use std::env;
10use std::path::PathBuf;
11use std::process::{Command, Stdio};
12
13use crate::args_parser::generate_session_name;
14
15#[derive(Debug, Default)]
17pub struct IsolationResult {
18 pub success: bool,
20 pub session_name: Option<String>,
22 pub container_id: Option<String>,
24 pub message: String,
26 pub exit_code: Option<i32>,
28 pub output: Option<String>,
30}
31
32#[derive(Debug, Clone)]
34pub struct IsolationOptions {
35 pub session: Option<String>,
37 pub image: Option<String>,
39 pub volumes: Vec<String>,
41 pub mounts: Vec<String>,
43 pub env: Vec<String>,
45 pub privileged: bool,
47 pub endpoint: Option<String>,
49 pub detached: bool,
51 pub user: Option<String>,
53 pub keep_alive: bool,
55 pub auto_remove_docker_container: bool,
57 pub shell: String,
59 pub log_path: Option<PathBuf>,
61}
62
63impl Default for IsolationOptions {
64 fn default() -> Self {
65 IsolationOptions {
66 session: None,
67 image: None,
68 volumes: Vec::new(),
69 mounts: Vec::new(),
70 env: Vec::new(),
71 privileged: false,
72 endpoint: None,
73 detached: false,
74 user: None,
75 keep_alive: false,
76 auto_remove_docker_container: false,
77 shell: "auto".to_string(),
78 log_path: None,
79 }
80 }
81}
82
83pub fn is_command_available(command: &str) -> bool {
85 let check_cmd = if cfg!(windows) { "where" } else { "which" };
86 Command::new(check_cmd)
87 .arg(command)
88 .stdout(Stdio::null())
89 .stderr(Stdio::null())
90 .status()
91 .map(|s| s.success())
92 .unwrap_or(false)
93}
94
95pub fn get_shell() -> (String, String) {
97 if cfg!(windows) {
98 ("cmd.exe".to_string(), "/c".to_string())
99 } else {
100 let shell = env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
101 (shell, "-c".to_string())
102 }
103}
104
105pub fn has_tty() -> bool {
107 atty::is(atty::Stream::Stdin) && atty::is(atty::Stream::Stdout)
108}
109
110pub fn wrap_command_with_user(command: &str, user: Option<&str>) -> String {
112 match user {
113 Some(u) => {
114 let escaped = command.replace('\'', "'\\''");
116 format!("sudo -n -u {} sh -c '{}'", u, escaped)
117 }
118 None => command.to_string(),
119 }
120}
121
122const SHELL_NAMES: [&str; 8] = ["bash", "zsh", "sh", "fish", "ksh", "csh", "tcsh", "dash"];
125
126pub fn is_interactive_shell_command(command: &str) -> bool {
132 let parts: Vec<&str> = command.split_whitespace().collect();
133 if parts.is_empty() {
134 return false;
135 }
136 let basename = parts[0].rsplit('/').next().unwrap_or(parts[0]);
137 SHELL_NAMES.contains(&basename) && !parts.contains(&"-c")
138}
139
140pub fn is_shell_invocation_with_args(command: &str) -> bool {
143 let parts: Vec<&str> = command.split_whitespace().collect();
144 if parts.is_empty() {
145 return false;
146 }
147 let basename = parts[0].rsplit('/').next().unwrap_or(parts[0]);
148 SHELL_NAMES.contains(&basename) && parts.contains(&"-c")
149}
150
151pub fn build_shell_with_args_cmd_args(command: &str) -> Vec<String> {
155 let parts: Vec<&str> = command.split_whitespace().collect();
156 let c_idx = parts.iter().position(|&p| p == "-c");
157 match c_idx {
158 None => parts.iter().map(|s| s.to_string()).collect(),
159 Some(idx) => {
160 let script_arg = parts[idx + 1..].join(" ");
161 let mut result: Vec<String> = parts[..idx + 1].iter().map(|s| s.to_string()).collect();
162 if !script_arg.is_empty() {
163 result.push(script_arg);
164 }
165 result
166 }
167 }
168}
169
170fn get_shell_interactive_flag(shell_path: &str) -> Option<&'static str> {
172 let shell_name = shell_path.rsplit('/').next().unwrap_or(shell_path);
173 match shell_name {
174 "bash" => Some("-i"),
175 "zsh" => Some("-i"),
176 _ => None,
177 }
178}
179
180pub fn detect_shell_in_environment(environment: &str, options: &IsolationOptions) -> String {
184 let shell_preference = &options.shell;
185
186 if !shell_preference.is_empty() && shell_preference != "auto" {
188 if is_debug() {
189 eprintln!("[DEBUG] Using forced shell: {}", shell_preference);
190 }
191 return shell_preference.clone();
192 }
193
194 let shells_to_try = ["bash", "zsh", "sh"];
196
197 if environment == "docker" {
198 let image = match &options.image {
199 Some(i) => i.clone(),
200 None => return "sh".to_string(),
201 };
202
203 for shell in &shells_to_try {
204 let result = Command::new("docker")
205 .args([
206 "run",
207 "--rm",
208 &image,
209 "sh",
210 "-c",
211 &format!("command -v {}", shell),
212 ])
213 .stdout(Stdio::piped())
214 .stderr(Stdio::null())
215 .output();
216
217 if let Ok(output) = result {
218 if output.status.success() {
219 let detected = String::from_utf8_lossy(&output.stdout).trim().to_string();
220 if !detected.is_empty() {
221 if is_debug() {
222 eprintln!(
223 "[DEBUG] Detected shell in docker image {}: {}",
224 image, detected
225 );
226 }
227 return detected;
228 }
229 }
230 }
231 }
232
233 if is_debug() {
234 eprintln!(
235 "[DEBUG] Could not detect shell in docker image {}, falling back to sh",
236 image
237 );
238 }
239 return "sh".to_string();
240 }
241
242 if environment == "ssh" {
243 let endpoint = match &options.endpoint {
244 Some(e) => e.clone(),
245 None => return "sh".to_string(),
246 };
247
248 let check_cmd: Vec<String> = shells_to_try
250 .iter()
251 .map(|s| format!("command -v {}", s))
252 .collect();
253 let check_cmd_str = check_cmd.join(" || ");
254
255 let result = Command::new("ssh")
256 .args([&endpoint, &check_cmd_str])
257 .stdout(Stdio::piped())
258 .stderr(Stdio::null())
259 .output();
260
261 if let Ok(output) = result {
262 if output.status.success() {
263 let detected = String::from_utf8_lossy(&output.stdout).trim().to_string();
264 if !detected.is_empty() {
265 if is_debug() {
266 eprintln!(
267 "[DEBUG] Detected shell on SSH host {}: {}",
268 endpoint, detected
269 );
270 }
271 return detected;
272 }
273 }
274 }
275
276 if is_debug() {
277 eprintln!(
278 "[DEBUG] Could not detect shell on SSH host {}, falling back to sh",
279 endpoint
280 );
281 }
282 return "sh".to_string();
283 }
284
285 "sh".to_string()
286}
287
288#[path = "isolation_screen.rs"]
289pub mod isolation_screen;
290pub use self::isolation_screen::{get_screen_version, supports_logfile_option};
291
292pub fn run_in_screen(command: &str, options: &IsolationOptions) -> IsolationResult {
294 if !is_command_available("screen") {
295 return IsolationResult {
296 success: false,
297 message: "screen is not installed. Install it with: sudo apt-get install screen (Debian/Ubuntu) or brew install screen (macOS)".to_string(),
298 ..Default::default()
299 };
300 }
301
302 let session_name = options
303 .session
304 .clone()
305 .unwrap_or_else(|| generate_session_name(Some("screen")));
306
307 if options.detached {
308 isolation_screen::start_detached_screen_with_log_capture(
309 command,
310 &session_name,
311 options.user.as_deref(),
312 options.keep_alive,
313 options.log_path.as_deref(),
314 )
315 } else {
316 isolation_screen::run_screen_with_log_capture(
318 command,
319 &session_name,
320 options.user.as_deref(),
321 options.log_path.as_deref(),
322 )
323 }
324}
325
326pub fn run_in_tmux(command: &str, options: &IsolationOptions) -> IsolationResult {
328 if !is_command_available("tmux") {
329 return IsolationResult {
330 success: false,
331 message: "tmux is not installed. Install it with: sudo apt-get install tmux (Debian/Ubuntu) or brew install tmux (macOS)".to_string(),
332 ..Default::default()
333 };
334 }
335
336 let session_name = options
337 .session
338 .clone()
339 .unwrap_or_else(|| generate_session_name(Some("tmux")));
340
341 let (shell, _) = get_shell();
342 let effective_command = wrap_command_with_user(command, options.user.as_deref());
343
344 if options.detached {
345 let final_command = if options.log_path.is_some() {
346 crate::isolation::isolation_log::wrap_command_with_log_footer(
347 &effective_command,
348 &shell,
349 options.keep_alive,
350 )
351 } else if options.keep_alive {
352 format!("{}; exec {}", effective_command, shell)
353 } else {
354 effective_command.clone()
355 };
356
357 let status = if let Some(log_path) = options.log_path.as_ref() {
358 let start_status = Command::new("tmux")
359 .args(["new-session", "-d", "-s", &session_name, &shell])
360 .status();
361 if start_status.as_ref().is_ok_and(|s| s.success()) {
362 let pipe_command = format!(
363 "cat >> {}",
364 crate::isolation::isolation_log::shell_quote(&log_path.to_string_lossy())
365 );
366 let _ = Command::new("tmux")
367 .args(["pipe-pane", "-t", &session_name, "-o", &pipe_command])
368 .status();
369 Command::new("tmux")
370 .args(["send-keys", "-t", &session_name, &final_command, "C-m"])
371 .status()
372 } else {
373 start_status
374 }
375 } else {
376 Command::new("tmux")
377 .args(["new-session", "-d", "-s", &session_name, &final_command])
378 .status()
379 };
380
381 match status {
382 Ok(s) if s.success() => {
383 let mut message =
384 format!("Command started in detached tmux session: {}", session_name);
385 if options.keep_alive {
386 message.push_str("\nSession will stay alive after command completes.");
387 } else {
388 message.push_str("\nSession will exit automatically after command completes.");
389 }
390 message.push_str(&format!("\nReattach with: tmux attach -t {}", session_name));
391 if let Some(log_path) = options.log_path.as_ref() {
392 message.push_str(&format!("\nLive log: {}", log_path.display()));
393 }
394
395 IsolationResult {
396 success: true,
397 session_name: Some(session_name),
398 message,
399 ..Default::default()
400 }
401 }
402 _ => IsolationResult {
403 success: false,
404 session_name: Some(session_name),
405 message: "Failed to start tmux session".to_string(),
406 ..Default::default()
407 },
408 }
409 } else {
410 let output = Command::new("tmux")
412 .args(["new-session", "-s", &session_name, &effective_command])
413 .status();
414
415 match output {
416 Ok(status) => IsolationResult {
417 success: status.success(),
418 session_name: Some(session_name.clone()),
419 message: format!(
420 "Tmux session \"{}\" exited with code {}",
421 session_name,
422 status.code().unwrap_or(-1)
423 ),
424 exit_code: status.code(),
425 ..Default::default()
426 },
427 Err(e) => IsolationResult {
428 success: false,
429 session_name: Some(session_name),
430 message: format!("Failed to start tmux: {}", e),
431 ..Default::default()
432 },
433 }
434 }
435}
436
437pub fn run_in_ssh(command: &str, options: &IsolationOptions) -> IsolationResult {
439 if !is_command_available("ssh") {
440 return IsolationResult {
441 success: false,
442 message: "ssh is not installed".to_string(),
443 ..Default::default()
444 };
445 }
446
447 let endpoint = match &options.endpoint {
448 Some(e) => e.clone(),
449 None => {
450 return IsolationResult {
451 success: false,
452 message: "SSH isolation requires --endpoint option".to_string(),
453 ..Default::default()
454 };
455 }
456 };
457
458 let session_name = options
459 .session
460 .clone()
461 .unwrap_or_else(|| generate_session_name(Some("ssh")));
462
463 let shell_to_use = detect_shell_in_environment("ssh", options);
465 let shell_interactive_flag = get_shell_interactive_flag(&shell_to_use);
468
469 if options.detached {
470 let shell_invocation = if let Some(flag) = shell_interactive_flag {
473 format!("{} {}", shell_to_use, flag)
474 } else {
475 shell_to_use.clone()
476 };
477 let remote_command = format!(
478 "mkdir -p /tmp/start-command/logs/isolation/ssh && nohup {} -c {} > /tmp/start-command/logs/isolation/ssh/{}.log 2>&1 &",
479 shell_invocation,
480 shell_escape(command),
481 session_name
482 );
483 let ssh_args = vec![endpoint.as_str(), remote_command.as_str()];
484
485 if is_debug() {
486 eprintln!("[DEBUG] Running: ssh {:?}", ssh_args);
487 eprintln!("[DEBUG] shell: {}", shell_invocation);
488 }
489
490 let status = Command::new("ssh").args(&ssh_args).status();
491
492 match status {
493 Ok(s) if s.success() => IsolationResult {
494 success: true,
495 session_name: Some(session_name.clone()),
496 message: format!(
497 "Command started in detached SSH session on {}\nSession: {}\nView logs: ssh {} \"tail -f /tmp/start-command/logs/isolation/ssh/{}.log\"",
498 endpoint, session_name, endpoint, session_name
499 ),
500 ..Default::default()
501 },
502 _ => IsolationResult {
503 success: false,
504 session_name: Some(session_name),
505 message: "Failed to start SSH session".to_string(),
506 ..Default::default()
507 },
508 }
509 } else {
510 let mut ssh_cmd_args = vec![endpoint.clone(), shell_to_use.clone()];
513 if let Some(flag) = shell_interactive_flag {
514 ssh_cmd_args.push(flag.to_string());
515 }
516 ssh_cmd_args.push("-c".to_string());
517 ssh_cmd_args.push(command.to_string());
518
519 if is_debug() {
520 eprintln!("[DEBUG] Running: ssh {:?}", ssh_cmd_args);
521 eprintln!("[DEBUG] shell: {}", shell_to_use);
522 }
523
524 let status = Command::new("ssh").args(&ssh_cmd_args).status();
525
526 match status {
527 Ok(s) => IsolationResult {
528 success: s.success(),
529 session_name: Some(session_name.clone()),
530 message: format!(
531 "SSH session \"{}\" on {} exited with code {}",
532 session_name,
533 endpoint,
534 s.code().unwrap_or(-1)
535 ),
536 exit_code: s.code(),
537 ..Default::default()
538 },
539 Err(e) => IsolationResult {
540 success: false,
541 session_name: Some(session_name),
542 message: format!("Failed to start SSH: {}", e),
543 ..Default::default()
544 },
545 }
546 }
547}
548
549pub fn docker_image_exists(image: &str) -> bool {
551 Command::new("docker")
552 .args(["image", "inspect", image])
553 .stdout(Stdio::null())
554 .stderr(Stdio::null())
555 .status()
556 .map(|s| s.success())
557 .unwrap_or(false)
558}
559
560pub fn docker_pull_image(image: &str) -> (bool, String) {
563 use std::io::{BufRead, BufReader};
564
565 println!(
567 "{}",
568 crate::output_blocks::create_virtual_command_block(&format!("docker pull {}", image))
569 );
570 println!();
571
572 let mut child = match Command::new("docker")
573 .args(["pull", image])
574 .stdout(Stdio::piped())
575 .stderr(Stdio::piped())
576 .spawn()
577 {
578 Ok(c) => c,
579 Err(e) => {
580 let error_msg = format!("Failed to run docker pull: {}", e);
581 eprintln!("{}", error_msg);
582 println!();
583 println!(
584 "{}",
585 crate::output_blocks::create_virtual_command_result(false)
586 );
587 return (false, error_msg);
588 }
589 };
590
591 let mut output = String::new();
592
593 if let Some(stdout) = child.stdout.take() {
595 let reader = BufReader::new(stdout);
596 for line in reader.lines().map_while(Result::ok) {
597 println!("{}", line);
598 output.push_str(&line);
599 output.push('\n');
600 }
601 }
602
603 if let Some(stderr) = child.stderr.take() {
605 let reader = BufReader::new(stderr);
606 for line in reader.lines().map_while(Result::ok) {
607 eprintln!("{}", line);
608 output.push_str(&line);
609 output.push('\n');
610 }
611 }
612
613 let success = child.wait().map(|s| s.success()).unwrap_or(false);
614
615 println!();
618 println!(
619 "{}",
620 crate::output_blocks::create_virtual_command_result(success)
621 );
622 println!("{}", crate::output_blocks::create_timeline_separator());
623
624 (success, output)
625}
626
627fn build_docker_runtime_args(options: &IsolationOptions) -> Vec<&str> {
631 let mut args: Vec<&str> = Vec::new();
632 if options.privileged {
633 args.push("--privileged");
634 }
635 for env_var in &options.env {
636 args.push("-e");
637 args.push(env_var);
638 }
639 for volume in &options.volumes {
640 args.push("-v");
641 args.push(volume);
642 }
643 for mount in &options.mounts {
644 args.push("--mount");
645 args.push(mount);
646 }
647 args
648}
649
650pub fn run_in_docker(command: &str, options: &IsolationOptions) -> IsolationResult {
652 if !is_command_available("docker") {
653 return IsolationResult {
654 success: false,
655 message:
656 "docker is not installed. Install Docker from https://docs.docker.com/get-docker/"
657 .to_string(),
658 ..Default::default()
659 };
660 }
661
662 let image = match &options.image {
663 Some(i) => i.clone(),
664 None => {
665 return IsolationResult {
666 success: false,
667 message: "Docker isolation requires --image option".to_string(),
668 ..Default::default()
669 };
670 }
671 };
672
673 if !docker_image_exists(&image) {
675 let (pull_success, _pull_output) = docker_pull_image(&image);
676 if !pull_success {
677 return IsolationResult {
678 success: false,
679 message: format!("Failed to pull Docker image: {}", image),
680 exit_code: Some(1),
681 ..Default::default()
682 };
683 }
684 }
685
686 let container_name = options
687 .session
688 .clone()
689 .unwrap_or_else(|| generate_session_name(Some("docker")));
690
691 let shell_to_use = detect_shell_in_environment("docker", options);
693 let shell_interactive_flag = get_shell_interactive_flag(&shell_to_use);
696
697 println!("{}", crate::output_blocks::create_command_line(command));
699 println!();
700
701 if options.detached {
702 let effective_command = if options.keep_alive {
703 format!("{}; exec {}", command, shell_to_use)
704 } else {
705 command.to_string()
706 };
707
708 let mut args = vec!["run", "-d", "--name", &container_name];
709
710 if options.auto_remove_docker_container {
711 args.push("--rm");
712 }
713
714 if let Some(ref user) = options.user {
715 args.push("--user");
716 args.push(user);
717 }
718
719 args.extend(build_docker_runtime_args(options));
720
721 args.push(&image);
722 args.push(&shell_to_use);
723 if let Some(flag) = shell_interactive_flag {
724 args.push(flag);
725 }
726 args.extend(&["-c", &effective_command]);
727
728 if is_debug() {
729 eprintln!("[DEBUG] Running: docker {:?}", args);
730 eprintln!("[DEBUG] shell: {}", shell_to_use);
731 }
732
733 match Command::new("docker").args(&args).output() {
734 Ok(output) if output.status.success() => {
735 let container_id = String::from_utf8_lossy(&output.stdout).trim().to_string();
736
737 if let Some(log_path) = options.log_path.as_ref() {
738 let logger_script = format!(
739 "docker logs -f {} >> {} 2>&1; __start_command_exit=$(docker inspect -f '{{{{.State.ExitCode}}}}' {} 2>/dev/null || printf '%s' '-1'); {} >> {}",
740 crate::isolation::isolation_log::shell_quote(&container_name),
741 crate::isolation::isolation_log::shell_quote(&log_path.to_string_lossy()),
742 crate::isolation::isolation_log::shell_quote(&container_name),
743 crate::isolation::isolation_log::create_shell_log_footer_snippet(),
744 crate::isolation::isolation_log::shell_quote(&log_path.to_string_lossy())
745 );
746 let _ = Command::new("sh")
747 .args(["-c", &logger_script])
748 .stdout(Stdio::null())
749 .stderr(Stdio::null())
750 .spawn();
751 }
752
753 let mut message = format!(
754 "Command started in detached docker container: {}",
755 container_name
756 );
757 message.push_str(&format!(
758 "\nContainer ID: {}",
759 &container_id[..12.min(container_id.len())]
760 ));
761 if options.keep_alive {
762 message.push_str("\nContainer will stay alive after command completes.");
763 } else {
764 message
765 .push_str("\nContainer will exit automatically after command completes.");
766 }
767 if options.auto_remove_docker_container {
768 message.push_str("\nContainer will be automatically removed after exit.");
769 } else {
770 message.push_str("\nContainer filesystem will be preserved after exit.");
771 }
772 message.push_str(&format!("\nAttach with: docker attach {}", container_name));
773 message.push_str(&format!("\nView logs: docker logs {}", container_name));
774 if let Some(log_path) = options.log_path.as_ref() {
775 message.push_str(&format!("\nLive log: {}", log_path.display()));
776 }
777
778 IsolationResult {
779 success: true,
780 session_name: Some(container_name),
781 container_id: Some(container_id),
782 message,
783 ..Default::default()
784 }
785 }
786 Ok(output) => {
787 let stderr = String::from_utf8_lossy(&output.stderr);
788 IsolationResult {
789 success: false,
790 session_name: Some(container_name),
791 message: format!("Failed to start docker container: {}", stderr),
792 ..Default::default()
793 }
794 }
795 Err(e) => IsolationResult {
796 success: false,
797 session_name: Some(container_name),
798 message: format!("Failed to run docker: {}", e),
799 ..Default::default()
800 },
801 }
802 } else {
803 let mut args = vec!["run", "-it", "--rm", "--name", &container_name];
805
806 if let Some(ref user) = options.user {
807 args.push("--user");
808 args.push(user);
809 }
810
811 args.extend(build_docker_runtime_args(options));
812
813 if is_debug() {
814 eprintln!("[DEBUG] shell: {}", shell_to_use);
815 }
816
817 args.push(&image);
818 args.push(&shell_to_use);
819 if let Some(flag) = shell_interactive_flag {
820 args.push(flag);
821 }
822 args.extend(&["-c", command]);
823
824 let status = Command::new("docker").args(&args).status();
825
826 match status {
827 Ok(s) => IsolationResult {
828 success: s.success(),
829 session_name: Some(container_name.clone()),
830 message: format!(
831 "Docker container \"{}\" exited with code {}",
832 container_name,
833 s.code().unwrap_or(-1)
834 ),
835 exit_code: s.code(),
836 ..Default::default()
837 },
838 Err(e) => IsolationResult {
839 success: false,
840 session_name: Some(container_name),
841 message: format!("Failed to start docker: {}", e),
842 ..Default::default()
843 },
844 }
845 }
846}
847
848pub fn run_isolated(backend: &str, command: &str, options: &IsolationOptions) -> IsolationResult {
850 match backend {
851 "screen" => run_in_screen(command, options),
852 "tmux" => run_in_tmux(command, options),
853 "docker" => run_in_docker(command, options),
854 "ssh" => run_in_ssh(command, options),
855 _ => IsolationResult {
856 success: false,
857 message: format!("Unknown isolation backend: {}", backend),
858 ..Default::default()
859 },
860 }
861}
862
863pub fn run_as_isolated_user(command: &str, username: &str) -> IsolationResult {
865 let status = Command::new("sudo")
866 .args(["-n", "-u", username, "sh", "-c", command])
867 .status();
868
869 match status {
870 Ok(s) => IsolationResult {
871 success: s.success(),
872 message: format!(
873 "Command completed as user \"{}\" with exit code {}",
874 username,
875 s.code().unwrap_or(-1)
876 ),
877 exit_code: s.code(),
878 ..Default::default()
879 },
880 Err(e) => IsolationResult {
881 success: false,
882 message: format!("Failed to run as user \"{}\": {}", username, e),
883 exit_code: Some(1),
884 ..Default::default()
885 },
886 }
887}
888
889#[path = "isolation_log.rs"]
890pub mod isolation_log;
891pub use self::isolation_log::{
892 append_log_file, create_log_footer, create_log_header, create_log_path,
893 create_log_path_for_execution, generate_log_filename, get_default_docker_image, get_log_dir,
894 get_temp_dir, get_temp_root, get_timestamp, write_log_file, LogHeaderParams,
895};
896
897fn is_debug() -> bool {
898 env::var("START_DEBUG").is_ok_and(|v| v == "1" || v == "true")
899}
900
901fn shell_escape(command: &str) -> String {
902 format!("'{}'", command.replace('\'', "'\\''"))
903}
904
905#[path = "atty.rs"]
906mod atty;
907
908#[cfg(test)]
909#[path = "isolation_cases.rs"]
910mod tests;