diff --git a/Cargo.lock b/Cargo.lock index 63e434e6..7f797444 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -312,6 +312,7 @@ dependencies = [ "sysinfo", "temp-env", "tempfile", + "test-with", "tokio", "tokio-tar", "url", @@ -1540,6 +1541,28 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "proc-macro2" version = "1.0.93" @@ -2154,6 +2177,19 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-with" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb2a43a245ab793321d71ff9f2be21785999c9aa5b2ea93dc507e97572be843" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "regex", + "syn 2.0.96", +] + [[package]] name = "thiserror" version = "1.0.69" diff --git a/Cargo.toml b/Cargo.toml index 92ff82a5..ebd17773 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,7 @@ procfs = "0.17.0" [dev-dependencies] temp-env = { version = "0.3.6", features = ["async_closure"] } insta = { version = "1.29.0", features = ["json", "redactions"] } - +test-with = { version = "0.15", default-features = false, features = [] } [workspace.metadata.release] sign-tag = true diff --git a/src/run/runner/valgrind/helpers/venv_compat.rs b/src/run/runner/valgrind/helpers/venv_compat.rs index c5d696ee..c0f9ba8d 100644 --- a/src/run/runner/valgrind/helpers/venv_compat.rs +++ b/src/run/runner/valgrind/helpers/venv_compat.rs @@ -51,6 +51,8 @@ pub fn symlink_libpython(cwd: Option<&String>) -> anyhow::Result<()> { mod tests { use super::*; + // Only run in Github Actions, to ensure python is dynamically linked. + #[test_with::env(GITHUB_ACTIONS)] #[test] fn test_venv_compat_no_crash() { assert!(symlink_libpython(None).is_ok()); diff --git a/src/run/runner/wall_time/perf/mod.rs b/src/run/runner/wall_time/perf/mod.rs index d52dd8bf..ce2f3568 100644 --- a/src/run/runner/wall_time/perf/mod.rs +++ b/src/run/runner/wall_time/perf/mod.rs @@ -12,7 +12,7 @@ use metadata::PerfMetadata; use perf_map::ProcessSymbols; use shared::Command as FifoCommand; use std::collections::HashSet; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::process::Command; use std::time::Duration; use std::{cell::OnceCell, collections::HashMap, process::ExitStatus}; @@ -31,6 +31,55 @@ pub mod unwind_data; const PERF_DATA_PREFIX: &str = "perf.data."; +struct EnvGuard { + post_bench_script: PathBuf, +} + +impl EnvGuard { + fn execute_script_from_path>(path: P) -> anyhow::Result<()> { + let path = path.as_ref(); + if !path.exists() || !path.is_file() { + warn!("Script not found or not a file: {}", path.display()); + return Ok(()); + } + + let output = Command::new("bash").args([&path]).output()?; + if !output.status.success() { + info!("stdout: {}", String::from_utf8_lossy(&output.stdout)); + error!("stderr: {}", String::from_utf8_lossy(&output.stderr)); + bail!("Failed to execute script: {}", path.display()); + } + + Ok(()) + } + + pub fn setup_with_scripts>(pre_bench_script: P, post_bench_script: P) -> Self { + if let Err(e) = Self::execute_script_from_path(pre_bench_script.as_ref()) { + warn!("Failed to execute pre-bench script: {}", e); + println!("asdf: {e}"); + } + + Self { + post_bench_script: post_bench_script.as_ref().to_path_buf(), + } + } + + pub fn setup() -> Self { + Self::setup_with_scripts( + "/usr/local/bin/codspeed-pre-bench", + "/usr/local/bin/codspeed-post-bench", + ) + } +} + +impl Drop for EnvGuard { + fn drop(&mut self) { + if let Err(e) = Self::execute_script_from_path(&self.post_bench_script) { + warn!("Failed to execute post-bench script: {}", e); + } + } +} + pub struct PerfRunner { perf_dir: TempDir, benchmark_data: OnceCell, @@ -126,7 +175,11 @@ impl PerfRunner { Ok(()) }; - run_command_with_log_pipe_and_callback(cmd, on_process_started).await + + { + let _guard = EnvGuard::setup(); + run_command_with_log_pipe_and_callback(cmd, on_process_started).await + } } pub async fn save_files_to(&self, profile_folder: &PathBuf) -> anyhow::Result<()> { @@ -389,3 +442,49 @@ impl BenchmarkData { self.bench_order_by_pid.values().map(|v| v.len()).sum() } } +#[cfg(test)] +mod tests { + use tempfile::NamedTempFile; + + use super::*; + use std::{ + io::{Read, Write}, + os::unix::fs::PermissionsExt, + }; + + #[test] + fn test_env_guard_no_crash() { + fn create_run_script(content: &str) -> anyhow::Result { + let rwx = std::fs::Permissions::from_mode(0o777); + let mut script_file = tempfile::Builder::new() + .suffix(".sh") + .permissions(rwx) + .keep(true) + .tempfile()?; + script_file.write_all(content.as_bytes())?; + + Ok(script_file) + } + + let mut tmp_dst = tempfile::NamedTempFile::new().unwrap(); + + let pre_script = create_run_script(&format!( + "#!/usr/bin/env bash\necho \"pre\" >> {}", + tmp_dst.path().display() + )) + .unwrap(); + let post_script = create_run_script(&format!( + "#!/usr/bin/env bash\necho \"post\" >> {}", + tmp_dst.path().display() + )) + .unwrap(); + + { + let _guard = EnvGuard::setup_with_scripts(pre_script.path(), post_script.path()); + } + + let mut result = String::new(); + tmp_dst.read_to_string(&mut result).unwrap(); + assert_eq!(result, "pre\npost\n"); + } +}