//! Utility functions for the test harness. use anyhow::Result; use descape::UnescapeExt; /// Get the OS ID from /etc/os-release file. /// Returns the value of the ID field, which is the canonical OS identifier. /// For example: "ubuntu", "opensuse-tumbleweed", "fedora", etc. pub fn get_host_os_id() -> Option { os_release::OsRelease::new().ok().and_then(|info| { if info.id.is_empty() { None } else { Some(info.id) } }) } /// Reads and processes the expectrl log output. #[cfg(unix)] pub fn read_expectrl_log(log: Vec) -> Result { let output_str = String::from_utf8(log)?; let output: String = output_str .lines() .filter(|line| line.starts_with("read:")) .map(|line| { line.strip_prefix("read: \"") .unwrap() .strip_suffix('"') .unwrap() }) .collect(); Ok(output) } /// Makes expectrl output human-readable by unescaping and stripping ANSI codes. pub fn make_expectrl_output_readable>(output: S) -> String { // Unescape the escaping done by expectrl's logging mechanism. let unescaped = output.as_ref().to_unescaped().unwrap().to_string(); // Remove VT escape sequences. strip_ansi_escapes::strip_str(unescaped) } /// Writes a diff between two strings to a writer. pub fn write_diff( writer: &mut impl std::io::Write, indent: usize, left: &str, right: &str, ) -> Result<()> { use colored::Colorize; let indent_str = " ".repeat(indent); let diff = diff::lines(left, right); for d in diff { let formatted = match d { diff::Result::Left(l) => std::format!("{indent_str}- {l}").red(), diff::Result::Both(l, _) => std::format!("{indent_str} {l}").bright_black(), diff::Result::Right(r) => std::format!("{indent_str}+ {r}").green(), }; writeln!(writer, "{formatted}")?; } Ok(()) } /// Resolves the first element of the given launcher token list to an absolute path. /// /// This is needed because the test harness clears env vars (including `PATH`) /// before spawning child processes, so a launcher binary referenced by name /// (e.g., `wasmtime`) would fail to resolve at exec time. pub fn resolve_launcher_path(tokens: &mut [String]) -> Result<()> { let Some(first) = tokens.first() else { return Ok(()); }; if std::path::Path::new(first.as_str()).is_absolute() { return Ok(()); } let resolved = std::env::var_os("PATH") .into_iter() .flat_map(|p| std::env::split_paths(&p).collect::>()) .map(|d| d.join(first.as_str())) .find(|p| p.is_file()) .ok_or_else(|| anyhow::anyhow!("could not resolve launcher binary '{first}' in PATH"))?; tokens[0] = resolved.to_string_lossy().into_owned(); Ok(()) } /// Gets the bash version string from the given bash path. pub fn get_bash_version_str(bash_path: &std::path::Path) -> Result { use anyhow::Context; let output = std::process::Command::new(bash_path) .arg("--norc") .arg("--noprofile") .arg("-c") .arg("echo -n ${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}.${BASH_VERSINFO[2]}") .output() .context("failed to retrieve bash version")? .stdout; let ver_str = String::from_utf8(output)?; Ok(ver_str) }