use std::path::Path;
use crate::config::{GuestOs, Result, TestbedError, VmProfile};
use crate::ssh::VmSession;
use crate::winrm::WinRM;
pub mod logs;
pub mod screenshot;
pub mod transfer;
pub mod ui;
pub mod validate;
#[derive(Debug)]
pub struct RunResult {
pub pid: Option<u32>,
pub log_path: String,
}
pub fn run_in_vm(
profile: &VmProfile,
session: &mut VmSession,
winrm: Option<&WinRM>,
bin_path: Option<&str>,
) -> Result<RunResult> {
let binary = match bin_path {
Some(p) => p.to_string(),
None => auto_detect_bin(session, profile)?,
};
match profile.os {
GuestOs::Linux => run_linux(session, &binary)?,
GuestOs::MacOS => run_linux(session, &binary)?,
GuestOs::Windows => run_windows(session, winrm, &binary)?,
}
Ok(RunResult {
pid: None, log_path: match profile.os {
GuestOs::Linux => "/home/vagrant/.testbed-run/run.log".to_string(),
GuestOs::MacOS => "/Users/vagrant/.testbed-run/run.log".to_string(),
GuestOs::Windows => "C:\\Users\\vagrant\\.testbed-run\\run.log".to_string(),
},
})
}
fn run_linux(session: &mut VmSession, binary: &str) -> Result<()> {
let bin_name = Path::new(binary)
.file_name()
.map(|n| n.to_string_lossy())
.unwrap_or_default();
crate::ssh::exec(session, &format!("pkill -f '{bin_name}' 2>/dev/null || true"))?;
crate::ssh::exec(session, "pkill Xvfb 2>/dev/null || true")?;
crate::ssh::exec(session, "pkill openbox 2>/dev/null || true")?;
crate::ssh::exec(session, "mkdir -p ~/.testbed-run")?;
crate::ssh::exec(
session,
"setsid -f Xvfb :99 -screen 0 1280x800x24 -nolisten tcp > ~/.testbed-run/xvfb.log 2>&1",
)?;
crate::ssh::exec(
session,
"sleep 1 && DISPLAY=:99 setsid -f openbox --replace > ~/.testbed-run/openbox.log 2>&1",
)?;
crate::ssh::exec(
session,
&format!("sleep 1 && DISPLAY=:99 setsid -f {binary} > ~/.testbed-run/run.log 2>&1"),
)?;
Ok(())
}
fn run_windows(session: &mut VmSession, winrm: Option<&WinRM>, binary: &str) -> Result<()> {
if let Some(winrm) = winrm {
crate::bootstrap::windows::run_interactive_windows(winrm, binary, &[])?;
return Ok(());
}
let escaped = binary.replace("'", "''");
let script = format!(
r#"
$run_dir = "$env:USERPROFILE\.testbed-run"
if (-not (Test-Path $run_dir)) {{ mkdir $run_dir -Force }}
$prior = Get-Process -ErrorAction SilentlyContinue | Where-Object {{ $_.Path -eq '{escaped}' }}
if ($prior) {{ $prior | Stop-Process -Force }}
Start-Process -FilePath '{escaped}' -RedirectStandardOutput "$run_dir\run.log" -RedirectStandardError "$run_dir\run-error.log"
"#
);
crate::ssh::exec(session, &script)?;
Ok(())
}
fn auto_detect_bin(session: &mut VmSession, profile: &VmProfile) -> Result<String> {
let target = crate::build::target_triple(profile.os);
let ext = match profile.os {
GuestOs::Windows => ".exe",
GuestOs::Linux | GuestOs::MacOS => "",
};
let candidates = [
format!("~/project/target/{target}/release"),
format!("~/project/src-tauri/target/{target}/release"),
];
for dir in &candidates {
let check_cmd = format!("test -d {dir} && echo EXISTS || echo MISSING");
if let Ok(output) = crate::ssh::exec(session, &check_cmd)
&& output.trim() == "EXISTS" {
let find_cmd = format!(
"find {dir} -maxdepth 1 -type f -name '*{ext}' -printf '%T@ %p\\n' 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2-"
);
if let Ok(output) = crate::ssh::exec(session, &find_cmd) {
let path = output.trim();
if !path.is_empty() {
return Ok(path.to_string());
}
}
}
}
Err(TestbedError::ArtifactNotFound {
path: "no compiled binary found in target/".to_string(),
})
}