|
1 | 1 | use std::{
|
2 |
| - env, fs, |
| 2 | + env, |
| 3 | + fmt::Write, |
| 4 | + fs, |
3 | 5 | path::{Path, PathBuf},
|
| 6 | + process::{self, Stdio}, |
| 7 | + str, |
4 | 8 | };
|
5 | 9 |
|
6 | 10 | use once_cell::sync::Lazy;
|
@@ -47,3 +51,119 @@ macro_rules! required_multi_thread_feature {
|
47 | 51 | compile_error!("please rerun `cargo bench` with `--features multi-thread`");
|
48 | 52 | };
|
49 | 53 | }
|
| 54 | + |
| 55 | +#[macro_export] |
| 56 | +macro_rules! aggregate_bench_main { |
| 57 | + () => { |
| 58 | + fn main() { |
| 59 | + common::__aggregate_bench_main_impl( |
| 60 | + &std::path::Path::new(env!("CARGO_MANIFEST_DIR")) |
| 61 | + .parent() |
| 62 | + .unwrap() |
| 63 | + .join(file!()), |
| 64 | + ); |
| 65 | + } |
| 66 | + }; |
| 67 | +} |
| 68 | + |
| 69 | +// Some log crates are based on `log` crate, which has only one global logger |
| 70 | +// instance, meaning that the logger is only allowed to be configured once. In |
| 71 | +// order to bench multiple different configurations, we need multiple child |
| 72 | +// processes to bench, and this function is used as a launcher for those child |
| 73 | +// processes. |
| 74 | +pub fn __aggregate_bench_main_impl(source_file: &Path) { |
| 75 | + let args = env::args().collect::<Vec<_>>(); |
| 76 | + if args.len() != 2 || args.get(1).unwrap() != "--bench" { |
| 77 | + eprintln!( |
| 78 | + "error: this is an aggregate bench and is supposed to be run only by `cargo bench`" |
| 79 | + ); |
| 80 | + process::exit(1); |
| 81 | + } |
| 82 | + |
| 83 | + let current_exe = env::current_exe().unwrap(); |
| 84 | + let current_dir = current_exe.parent().unwrap(); |
| 85 | + |
| 86 | + let name = format!("{}_", env!("CARGO_CRATE_NAME")); |
| 87 | + |
| 88 | + let mut sub_benches = fs::read_dir(current_dir) |
| 89 | + .unwrap() |
| 90 | + .filter_map(|p| p.ok()) |
| 91 | + .filter(|p| { |
| 92 | + #[cfg(unix)] |
| 93 | + let is_executable = p.path().metadata().is_ok_and(|m| { |
| 94 | + use std::os::unix::fs::PermissionsExt; |
| 95 | + m.is_file() && m.permissions().mode() & 0o111 != 0 |
| 96 | + }); |
| 97 | + #[cfg(windows)] |
| 98 | + let is_executable = p.path().extension().is_some_and(|ext| ext == "exe"); |
| 99 | + |
| 100 | + p.file_name().to_string_lossy().starts_with(&name) && is_executable |
| 101 | + // TODO: Test it on Windows |
| 102 | + }) |
| 103 | + .collect::<Vec<_>>(); |
| 104 | + sub_benches.sort_by_key(|p| p.file_name()); |
| 105 | + |
| 106 | + let mut sub_bench_sources = fs::read_dir(Path::new(source_file).parent().unwrap()) |
| 107 | + .unwrap() |
| 108 | + .filter_map(|p| p.ok()) |
| 109 | + .filter(|p| { |
| 110 | + p.file_name() |
| 111 | + .to_string_lossy() |
| 112 | + .chars() |
| 113 | + .next() |
| 114 | + .is_some_and(|ch| ch.is_ascii_digit()) |
| 115 | + && p.path().extension().is_some_and(|ext| ext == "rs") |
| 116 | + }) |
| 117 | + .collect::<Vec<_>>(); |
| 118 | + sub_bench_sources.sort_by_key(|p| p.file_name()); |
| 119 | + |
| 120 | + fn exit_as_files_mismatch() { |
| 121 | + eprintln!( |
| 122 | + "error: not all expected sub-benches have been built. try running `cargo bench` directly instead of specifying a `--bench` option." |
| 123 | + ); |
| 124 | + process::exit(1); |
| 125 | + } |
| 126 | + if sub_benches.len() != sub_bench_sources.len() { |
| 127 | + exit_as_files_mismatch(); |
| 128 | + } |
| 129 | + sub_bench_sources |
| 130 | + .into_iter() |
| 131 | + .zip(sub_benches.iter()) |
| 132 | + .for_each(|(source, bin)| { |
| 133 | + let expected_start = format!( |
| 134 | + "{}_{}-", |
| 135 | + env!("CARGO_CRATE_NAME"), |
| 136 | + source.path().file_stem().unwrap().to_string_lossy() |
| 137 | + ); |
| 138 | + if !bin |
| 139 | + .file_name() |
| 140 | + .to_string_lossy() |
| 141 | + .starts_with(&expected_start) |
| 142 | + { |
| 143 | + exit_as_files_mismatch(); |
| 144 | + } |
| 145 | + }); |
| 146 | + |
| 147 | + let mut captured_stdout = String::new(); |
| 148 | + for sub_bench in sub_benches { |
| 149 | + let output = process::Command::new(sub_bench.path()) |
| 150 | + .arg("--bench") |
| 151 | + .stdout(Stdio::piped()) |
| 152 | + .stderr(Stdio::inherit()) |
| 153 | + .output() |
| 154 | + .unwrap(); |
| 155 | + assert!(output.status.success()); |
| 156 | + |
| 157 | + captured_stdout |
| 158 | + .write_str(str::from_utf8(&output.stdout).unwrap()) |
| 159 | + .unwrap(); |
| 160 | + } |
| 161 | + |
| 162 | + let results = captured_stdout |
| 163 | + .lines() |
| 164 | + .filter(|line| line.contains("test ") && line.contains(" ... ")) |
| 165 | + .collect::<Vec<_>>() |
| 166 | + .join("\n"); |
| 167 | + |
| 168 | + println!("{}", results); |
| 169 | +} |
0 commit comments