Skip to content

Commit 95dae9c

Browse files
committed
feat: parse golang test output
1 parent 98eae9b commit 95dae9c

3 files changed

Lines changed: 110 additions & 19 deletions

File tree

src/run/runner/helpers/run_command_with_log_pipe.rs

Lines changed: 95 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,6 +44,7 @@ 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];
@@ -37,6 +53,12 @@ where
3753
if bytes_read == 0 {
3854
break;
3955
}
56+
57+
if let Some(ref capture) = captured_output {
58+
let mut output = capture.lock().unwrap();
59+
output.extend_from_slice(&buffer[..bytes_read]);
60+
}
61+
4062
suspend_progress_bar(|| {
4163
writer.write_all(&buffer[..bytes_read]).unwrap();
4264
trace!(
@@ -57,19 +79,76 @@ where
5779
.context("failed to spawn the process")?;
5880
let stdout = process.stdout.take().expect("unable to get stdout");
5981
let stderr = process.stderr.take().expect("unable to get stderr");
60-
thread::spawn(move || {
61-
log_tee(stdout, std::io::stdout(), None).unwrap();
62-
});
6382

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

68-
cb(process.id()).await?;
97+
(stdout_handle, stderr_handle)
98+
};
6999

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

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

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)