Skip to content

Commit ac4c59d

Browse files
committed
feat: parse golang test output
1 parent 2af536f commit ac4c59d

3 files changed

Lines changed: 112 additions & 19 deletions

File tree

src/run/runner/helpers/run_command_with_log_pipe.rs

Lines changed: 97 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,37 @@ use std::future::Future;
55
use std::io::{Read, Write};
66
use std::process::Command;
77
use std::process::ExitStatus;
8+
use std::sync::{Arc, Mutex};
89
use std::thread;
910

11+
struct CmdRunnerOptions<F> {
12+
on_process_spawned: Option<F>,
13+
capture_stdout: bool,
14+
}
15+
16+
impl<F> Default for CmdRunnerOptions<F> {
17+
fn default() -> Self {
18+
Self {
19+
on_process_spawned: None,
20+
capture_stdout: false,
21+
}
22+
}
23+
}
24+
1025
/// Run a command and log its output to stdout and stderr
1126
///
1227
/// # Arguments
1328
/// - `cmd`: The command to run.
14-
/// - `cb`: A callback function that takes the process ID and returns a result.
29+
/// - `options`: Configuration options for the runner (e.g. capture output, run callback)
1530
///
1631
/// # Returns
17-
///
18-
/// The exit status of the command.
19-
///
20-
pub async fn run_command_with_log_pipe_and_callback<F, Fut>(
32+
/// A tuple containing:
33+
/// - `ExitStatus`: The exit status of the executed command
34+
/// - `Option<String>`: Captured stdout if `capture_stdout` was true, otherwise None
35+
async fn run_command_with_log_pipe_and_options<F, Fut>(
2136
mut cmd: Command,
22-
cb: F,
23-
) -> Result<ExitStatus>
37+
options: CmdRunnerOptions<F>,
38+
) -> Result<(ExitStatus, Option<String>)>
2439
where
2540
F: FnOnce(u32) -> Fut,
2641
Fut: Future<Output = anyhow::Result<()>>,
@@ -29,14 +44,23 @@ where
2944
mut reader: impl Read,
3045
mut writer: impl Write,
3146
log_prefix: Option<&str>,
47+
captured_output: Option<Arc<Mutex<Vec<u8>>>>,
3248
) -> Result<()> {
3349
let prefix = log_prefix.unwrap_or("");
3450
let mut buffer = [0; 1024];
51+
let mut capture_guard = captured_output
52+
.as_ref()
53+
.map(|capture| capture.lock().unwrap());
3554
loop {
3655
let bytes_read = reader.read(&mut buffer)?;
3756
if bytes_read == 0 {
3857
break;
3958
}
59+
60+
if let Some(ref mut output) = capture_guard {
61+
output.extend_from_slice(&buffer[..bytes_read]);
62+
}
63+
4064
suspend_progress_bar(|| {
4165
writer.write_all(&buffer[..bytes_read]).unwrap();
4266
trace!(
@@ -57,19 +81,76 @@ where
5781
.context("failed to spawn the process")?;
5882
let stdout = process.stdout.take().expect("unable to get stdout");
5983
let stderr = process.stderr.take().expect("unable to get stderr");
60-
thread::spawn(move || {
61-
log_tee(stdout, std::io::stdout(), None).unwrap();
62-
});
6384

64-
thread::spawn(move || {
65-
log_tee(stderr, std::io::stderr(), Some("[stderr]")).unwrap();
66-
});
85+
let captured_stdout = if options.capture_stdout {
86+
Some(Arc::new(Mutex::new(Vec::new())))
87+
} else {
88+
None
89+
};
90+
let (stdout_handle, stderr_handle) = {
91+
let stdout_capture = captured_stdout.clone();
92+
let stdout_handle = thread::spawn(move || {
93+
log_tee(stdout, std::io::stdout(), None, stdout_capture).unwrap();
94+
});
95+
let stderr_handle = thread::spawn(move || {
96+
log_tee(stderr, std::io::stderr(), Some("[stderr]"), None).unwrap();
97+
});
6798

68-
cb(process.id()).await?;
99+
(stdout_handle, stderr_handle)
100+
};
69101

70-
process.wait().context("failed to wait for the process")
102+
if let Some(cb) = options.on_process_spawned {
103+
cb(process.id()).await?;
104+
}
105+
106+
let exit_status = process.wait().context("failed to wait for the process")?;
107+
let _ = (stdout_handle.join().unwrap(), stderr_handle.join().unwrap());
108+
109+
let stdout_output = captured_stdout
110+
.map(|capture| String::from_utf8_lossy(&capture.lock().unwrap()).to_string());
111+
Ok((exit_status, stdout_output))
112+
}
113+
114+
pub async fn run_command_with_log_pipe_and_callback<F, Fut>(
115+
cmd: Command,
116+
cb: F,
117+
) -> Result<(ExitStatus, Option<String>)>
118+
where
119+
F: FnOnce(u32) -> Fut,
120+
Fut: Future<Output = anyhow::Result<()>>,
121+
{
122+
run_command_with_log_pipe_and_options(
123+
cmd,
124+
CmdRunnerOptions {
125+
on_process_spawned: Some(cb),
126+
capture_stdout: false,
127+
},
128+
)
129+
.await
71130
}
72131

73132
pub async fn run_command_with_log_pipe(cmd: Command) -> Result<ExitStatus> {
74-
run_command_with_log_pipe_and_callback(cmd, async |_| Ok(())).await
133+
let (exit_status, _) = run_command_with_log_pipe_and_options(
134+
cmd,
135+
CmdRunnerOptions::<fn(u32) -> futures::future::Ready<anyhow::Result<()>>> {
136+
on_process_spawned: None,
137+
capture_stdout: false,
138+
},
139+
)
140+
.await?;
141+
Ok(exit_status)
142+
}
143+
144+
pub async fn run_command_with_log_pipe_capture_stdout(
145+
cmd: Command,
146+
) -> Result<(ExitStatus, String)> {
147+
let (exit_status, stdout) = run_command_with_log_pipe_and_options(
148+
cmd,
149+
CmdRunnerOptions::<fn(u32) -> futures::future::Ready<anyhow::Result<()>>> {
150+
on_process_spawned: None,
151+
capture_stdout: true,
152+
},
153+
)
154+
.await?;
155+
Ok((exit_status, stdout.unwrap_or_default()))
75156
}

src/run/runner/wall_time/executor.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ use crate::run::instruments::mongo_tracer::MongoTracer;
55
use crate::run::runner::executor::Executor;
66
use crate::run::runner::helpers::env::{get_base_injected_env, is_codspeed_debug_enabled};
77
use crate::run::runner::helpers::get_bench_command::get_bench_command;
8-
use crate::run::runner::helpers::run_command_with_log_pipe::run_command_with_log_pipe;
8+
use crate::run::runner::helpers::run_command_with_log_pipe::run_command_with_log_pipe_capture_stdout;
9+
use crate::run::runner::wall_time::golang;
910
use crate::run::runner::{ExecutorName, RunData};
1011
use crate::run::{check_system::SystemInfo, config::Config};
1112
use async_trait::async_trait;
@@ -181,7 +182,16 @@ impl Executor for WallTimeExecutor {
181182
cmd.args(["sh", "-c", &bench_cmd]);
182183
debug!("cmd: {cmd:?}");
183184

184-
run_command_with_log_pipe(cmd).await
185+
let (status, stdout) = run_command_with_log_pipe_capture_stdout(cmd).await?;
186+
187+
if config.command.trim().starts_with("go test") {
188+
let results_folder = run_data.profile_folder.join("results");
189+
std::fs::create_dir_all(&results_folder)?;
190+
191+
golang::collect_walltime_results(&stdout, &results_folder)?;
192+
}
193+
194+
Ok(status)
185195
}
186196
};
187197

src/run/runner/wall_time/perf/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ impl PerfRunner {
133133

134134
Ok(())
135135
};
136-
run_command_with_log_pipe_and_callback(cmd, on_process_started).await
136+
run_command_with_log_pipe_and_callback(cmd, on_process_started)
137+
.await
138+
.map(|(exit_status, _)| exit_status)
137139
}
138140

139141
pub async fn save_files_to(&self, profile_folder: &PathBuf) -> anyhow::Result<()> {

0 commit comments

Comments
 (0)