asciidoc.rs (hyperfine-1.14.0) | : | asciidoc.rs (hyperfine-1.15.0) | ||
---|---|---|---|---|
use crate::benchmark::benchmark_result::BenchmarkResult; | use super::markup::Alignment; | |||
use crate::benchmark::relative_speed::{self, BenchmarkResultWithRelativeSpeed}; | use crate::export::markup::MarkupExporter; | |||
use crate::output::format::format_duration_value; | ||||
use crate::util::units::Unit; | ||||
use super::Exporter; | ||||
use anyhow::{anyhow, Result}; | ||||
#[derive(Default)] | #[derive(Default)] | |||
pub struct AsciidocExporter {} | pub struct AsciidocExporter {} | |||
impl Exporter for AsciidocExporter { | impl MarkupExporter for AsciidocExporter { | |||
fn serialize(&self, results: &[BenchmarkResult], unit: Option<Unit>) -> Resu | fn table_header(&self, cell_aligmnents: &[Alignment]) -> String { | |||
lt<Vec<u8>> { | format!( | |||
let unit = if let Some(unit) = unit { | "[cols=\"{}\"]\n|===", | |||
// Use the given unit for all entries. | cell_aligmnents | |||
unit | .iter() | |||
} else if let Some(first_result) = results.first() { | .map(|a| match a { | |||
// Use the first BenchmarkResult entry to determine the unit for all | Alignment::Left => "<", | |||
entries. | Alignment::Right => ">", | |||
format_duration_value(first_result.mean, None).1 | }) | |||
} else { | .collect::<Vec<&str>>() | |||
// Default to `Second`. | .join(",") | |||
Unit::Second | ) | |||
}; | ||||
if let Some(annotated_results) = relative_speed::compute(results) { | ||||
let mut res: Vec<u8> = Vec::new(); | ||||
res.append(&mut table_open()); | ||||
res.append(&mut table_startend()); | ||||
res.append(&mut table_header(unit)); | ||||
for result in annotated_results { | ||||
res.push(b'\n'); | ||||
res.append(&mut table_row(&result, unit)); | ||||
} | ||||
res.append(&mut table_startend()); | ||||
Ok(res) | ||||
} else { | ||||
Err(anyhow!( | ||||
"Relative speed comparison is not available for Asciidoctor expo | ||||
rt." | ||||
)) | ||||
} | ||||
} | } | |||
} | ||||
fn table_open() -> Vec<u8> { | fn table_footer(&self, _cell_aligmnents: &[Alignment]) -> String { | |||
"[cols=\"<,>,>,>,>\"]\n".bytes().collect() | "|===\n".to_string() | |||
} | } | |||
fn table_startend() -> Vec<u8> { | ||||
"|===\n".bytes().collect() | ||||
} | ||||
fn table_header(unittype: Unit) -> Vec<u8> { | fn table_row(&self, cells: &[&str]) -> String { | |||
let unit_short_name = unittype.short_name(); | format!("\n| {} \n", cells.join(" \n| ")) | |||
format!( | } | |||
"| Command \n| Mean [{unit}] \n| Min [{unit}] \n| Max [{unit}] \n| Relat | ||||
ive \n", | ||||
unit = unit_short_name | ||||
) | ||||
.into_bytes() | ||||
} | ||||
fn table_row(entry: &BenchmarkResultWithRelativeSpeed, unit: Unit) -> Vec<u8> { | fn table_divider(&self, _cell_aligmnents: &[Alignment]) -> String { | |||
let result = &entry.result; | "".to_string() | |||
let mean_str = format_duration_value(result.mean, Some(unit)).0; | } | |||
let stddev_str = if let Some(stddev) = result.stddev { | ||||
format!(" ± {}", format_duration_value(stddev, Some(unit)).0) | ||||
} else { | ||||
"".into() | ||||
}; | ||||
let min_str = format_duration_value(result.min, Some(unit)).0; | ||||
let max_str = format_duration_value(result.max, Some(unit)).0; | ||||
let rel_str = format!("{:.2}", entry.relative_speed); | ||||
let rel_stddev_str = if entry.is_fastest { | ||||
"".into() | ||||
} else if let Some(stddev) = entry.relative_speed_stddev { | ||||
format!(" ± {:.2}", stddev) | ||||
} else { | ||||
"".into() | ||||
}; | ||||
format!( | fn command(&self, cmd: &str) -> String { | |||
"| `{command}` \n\ | format!("`{}`", cmd) | |||
| {mean}{stddev} \n\ | } | |||
| {min} \n\ | ||||
| {max} \n\ | ||||
| {rel}{rel_stddev} \n", | ||||
command = result.command.replace("|", "\\|"), | ||||
mean = mean_str, | ||||
stddev = stddev_str, | ||||
min = min_str, | ||||
max = max_str, | ||||
rel = rel_str, | ||||
rel_stddev = rel_stddev_str | ||||
) | ||||
.into_bytes() | ||||
} | } | |||
/// Ensure various options for the header generate correct results | /// Check Asciidoc-based data row formatting | |||
#[test] | #[test] | |||
fn test_asciidoc_header() { | fn test_asciidoc_exporter_table_data() { | |||
let conms: Vec<u8> = "| Command \n| Mean [ms] \n| Min [ms] \n| Max [ms] \n| | let exporter = AsciidocExporter::default(); | |||
Relative \n" | let data = vec!["a", "b", "c"]; | |||
.bytes() | ||||
.collect(); | ||||
let cons: Vec<u8> = "| Command \n| Mean [s] \n| Min [s] \n| Max [s] \n| Rela | ||||
tive \n" | ||||
.bytes() | ||||
.collect(); | ||||
let genms = table_header(Unit::MilliSecond); | ||||
let gens = table_header(Unit::Second); | ||||
assert_eq!(conms, genms); | let actual = exporter.table_row(&data); | |||
assert_eq!(cons, gens); | let expect = "\n| a \n| b \n| c \n"; | |||
assert_eq!(expect, actual); | ||||
} | } | |||
/// Ensure each table row is generated properly | /// Check Asciidoc-based table header formatting | |||
#[test] | #[test] | |||
fn test_asciidoc_table_row() { | fn test_asciidoc_exporter_table_header() { | |||
use std::collections::BTreeMap; | let exporter = AsciidocExporter::default(); | |||
let cells_alignment = [ | ||||
Alignment::Left, | ||||
Alignment::Right, | ||||
Alignment::Right, | ||||
Alignment::Right, | ||||
Alignment::Right, | ||||
]; | ||||
let timing_result = BenchmarkResultWithRelativeSpeed { | let actual = exporter.table_header(&cells_alignment); | |||
result: &BenchmarkResult { | let expect = "[cols=\"<,>,>,>,>\"]\n|==="; | |||
command: String::from("sleep 1"), | ||||
mean: 0.10491992406666667, | ||||
stddev: Some(0.00397851689425097), | ||||
median: 0.10491992406666667, | ||||
user: 0.005182013333333333, | ||||
system: 0.0, | ||||
min: 0.1003342584, | ||||
max: 0.10745223440000001, | ||||
times: Some(vec![0.1003342584, 0.10745223440000001, 0.10697327940000 | ||||
001]), | ||||
exit_codes: vec![Some(0), Some(0), Some(0)], | ||||
parameters: BTreeMap::new(), | ||||
}, | ||||
relative_speed: 1.000, | ||||
relative_speed_stddev: Option::from(1.03), | ||||
is_fastest: true, | ||||
}; | ||||
let formatted = String::from_utf8(table_row(&timing_result, Unit::MilliSecon | ||||
d)).unwrap(); | ||||
let formatted_expected = "| `sleep 1` \n\ | ||||
| 104.9 ± 4.0 \n\ | ||||
| 100.3 \n\ | ||||
| 107.5 \n\ | ||||
| 1.00 \n"; | ||||
assert_eq!(formatted_expected, formatted); | ||||
let formatted_seconds = String::from_utf8(table_row(&timing_result, Unit::Se | ||||
cond)).unwrap(); | ||||
let formatted_expected_seconds = "| `sleep 1` \n\ | ||||
| 0.105 ± 0.004 \n\ | ||||
| 0.100 \n\ | ||||
| 0.107 \n\ | ||||
| 1.00 \n"; | ||||
assert_eq!(formatted_expected_seconds, formatted_seconds); | assert_eq!(expect, actual); | |||
} | } | |||
/// Ensure commands get properly escaped | /// Test helper function to create unit-based header and horizontal line | |||
#[test] | /// independently from the markup functionality for Asciidoc. | |||
fn test_asciidoc_table_row_command_escape() { | #[cfg(test)] | |||
use std::collections::BTreeMap; | fn cfg_test_table_header(unit_short_name: &str) -> String { | |||
let benchmark_result = BenchmarkResultWithRelativeSpeed { | format!( | |||
result: &BenchmarkResult { | "[cols=\"<,>,>,>,>\"]\n|===\n| Command \n| Mean [{unit}] \n| Min [{unit} | |||
command: String::from("sleep 1|"), | ] \n| Max [{unit}] \n| Relative \n", | |||
mean: 0.10491992406666667, | unit = unit_short_name | |||
stddev: Some(0.00397851689425097), | ) | |||
median: 0.10491992406666667, | ||||
user: 0.005182013333333333, | ||||
system: 0.0, | ||||
min: 0.1003342584, | ||||
max: 0.10745223440000001, | ||||
times: Some(vec![0.1003342584, 0.10745223440000001, 0.10697327940000 | ||||
001]), | ||||
exit_codes: vec![Some(0), Some(0), Some(0)], | ||||
parameters: BTreeMap::new(), | ||||
}, | ||||
relative_speed: 1.000, | ||||
relative_speed_stddev: Option::from(1.03), | ||||
is_fastest: true, | ||||
}; | ||||
let expected = String::from_utf8( | ||||
format!( | ||||
"| `sleep 1\\|` \n\ | ||||
| {} ± {} \n\ | ||||
| {} \n\ | ||||
| {} \n\ | ||||
| {:.2} \n", | ||||
Unit::Second.format(benchmark_result.result.mean), | ||||
Unit::Second.format(benchmark_result.result.stddev.unwrap()), | ||||
Unit::Second.format(benchmark_result.result.min), | ||||
Unit::Second.format(benchmark_result.result.max), | ||||
benchmark_result.relative_speed | ||||
) | ||||
.into_bytes(), | ||||
); | ||||
let generated_seconds = String::from_utf8(table_row(&benchmark_result, Unit: | ||||
:Second)); | ||||
assert_eq!(expected, generated_seconds); | ||||
} | } | |||
#[cfg(test)] | ||||
use crate::util::units::Unit; | ||||
#[cfg(test)] | ||||
use crate::export::BenchmarkResult; | ||||
#[cfg(test)] | ||||
use crate::export::Exporter; | ||||
/// Integration test | /// Integration test | |||
#[test] | #[test] | |||
fn test_asciidoc() { | fn test_asciidoc_format_s() { | |||
use std::collections::BTreeMap; | use std::collections::BTreeMap; | |||
let exporter = AsciidocExporter::default(); | let exporter = AsciidocExporter::default(); | |||
// NOTE: results are fabricated, unlike above | ||||
let results = vec![ | let results = vec![ | |||
BenchmarkResult { | BenchmarkResult { | |||
command: String::from("FOO=1 BAR=2 command | 1"), | command: String::from("FOO=1 BAR=2 command | 1"), | |||
mean: 1.0, | mean: 1.0, | |||
stddev: Some(2.0), | stddev: Some(2.0), | |||
median: 1.0, | median: 1.0, | |||
user: 3.0, | user: 3.0, | |||
system: 4.0, | system: 4.0, | |||
min: 5.0, | min: 5.0, | |||
max: 6.0, | max: 6.0, | |||
skipping to change at line 245 | skipping to change at line 132 | |||
times: Some(vec![17.0, 18.0, 19.0]), | times: Some(vec![17.0, 18.0, 19.0]), | |||
exit_codes: vec![Some(0), Some(0), Some(0)], | exit_codes: vec![Some(0), Some(0), Some(0)], | |||
parameters: { | parameters: { | |||
let mut params = BTreeMap::new(); | let mut params = BTreeMap::new(); | |||
params.insert("foo".into(), "1".into()); | params.insert("foo".into(), "1".into()); | |||
params.insert("bar".into(), "7".into()); | params.insert("bar".into(), "7".into()); | |||
params | params | |||
}, | }, | |||
}, | }, | |||
]; | ]; | |||
// NOTE: only testing with s, s/ms is tested elsewhere | ||||
let expected: String = String::from( | let actual = | |||
"[cols=\"<,>,>,>,>\"]\n\ | ||||
|===\n\ | ||||
| Command \n\ | ||||
| Mean [s] \n\ | ||||
| Min [s] \n\ | ||||
| Max [s] \n\ | ||||
| Relative \n\ | ||||
\n\ | ||||
| `FOO=1 BAR=2 command \\| 1` \n\ | ||||
| 1.000 ± 2.000 \n\ | ||||
| 5.000 \n\ | ||||
| 6.000 \n\ | ||||
| 1.00 \n\ | ||||
\n\ | ||||
| `FOO=1 BAR=7 command \\| 2` \n\ | ||||
| 11.000 ± 12.000 \n\ | ||||
| 15.000 \n\ | ||||
| 16.000 \n\ | ||||
| 11.00 ± 25.06 \n\ | ||||
|===\n\ | ||||
", | ||||
); | ||||
let given = | ||||
String::from_utf8(exporter.serialize(&results, Some(Unit::Second)).unwra p()).unwrap(); | String::from_utf8(exporter.serialize(&results, Some(Unit::Second)).unwra p()).unwrap(); | |||
let expect = format!( | ||||
"{} | ||||
| `FOO=1 BAR=2 command \\| 1` | ||||
| 1.000 ± 2.000 | ||||
| 5.000 | ||||
| 6.000 | ||||
| 1.00 | ||||
| `FOO=1 BAR=7 command \\| 2` | ||||
| 11.000 ± 12.000 | ||||
| 15.000 | ||||
| 16.000 | ||||
| 11.00 ± 25.06 | ||||
|=== | ||||
", | ||||
cfg_test_table_header("s") | ||||
); | ||||
assert_eq!(expect, actual); | ||||
} | ||||
/// This test demonstrates that the given unit (ms) is used to set | ||||
/// the units for all entries. | ||||
#[test] | ||||
fn test_asciidoc_format_ms() { | ||||
use std::collections::BTreeMap; | ||||
let exporter = AsciidocExporter::default(); | ||||
let results = vec![ | ||||
BenchmarkResult { | ||||
command: String::from("FOO=1 BAR=7 command | 2"), | ||||
mean: 0.011, | ||||
stddev: Some(0.012), | ||||
median: 0.011, | ||||
user: 0.013, | ||||
system: 0.014, | ||||
min: 0.015, | ||||
max: 0.016, | ||||
times: Some(vec![0.017, 0.018, 0.019]), | ||||
exit_codes: vec![Some(0), Some(0), Some(0)], | ||||
parameters: { | ||||
let mut params = BTreeMap::new(); | ||||
params.insert("foo".into(), "1".into()); | ||||
params.insert("bar".into(), "7".into()); | ||||
params | ||||
}, | ||||
}, | ||||
BenchmarkResult { | ||||
command: String::from("FOO=1 BAR=2 command | 1"), | ||||
mean: 1.0, | ||||
stddev: Some(2.0), | ||||
median: 1.0, | ||||
user: 3.0, | ||||
system: 4.0, | ||||
min: 5.0, | ||||
max: 6.0, | ||||
times: Some(vec![7.0, 8.0, 9.0]), | ||||
exit_codes: vec![Some(0), Some(0), Some(0)], | ||||
parameters: { | ||||
let mut params = BTreeMap::new(); | ||||
params.insert("foo".into(), "1".into()); | ||||
params.insert("bar".into(), "2".into()); | ||||
params | ||||
}, | ||||
}, | ||||
]; | ||||
let actual = String::from_utf8( | ||||
exporter | ||||
.serialize(&results, Some(Unit::MilliSecond)) | ||||
.unwrap(), | ||||
) | ||||
.unwrap(); | ||||
let expect = format!( | ||||
"{} | ||||
| `FOO=1 BAR=7 command \\| 2` | ||||
| 11.0 ± 12.0 | ||||
| 15.0 | ||||
| 16.0 | ||||
| 1.00 | ||||
| `FOO=1 BAR=2 command \\| 1` | ||||
| 1000.0 ± 2000.0 | ||||
| 5000.0 | ||||
| 6000.0 | ||||
| 90.91 ± 207.11 | ||||
|=== | ||||
", | ||||
cfg_test_table_header("ms") | ||||
); | ||||
assert_eq!(expected, given); | assert_eq!(expect, actual); | |||
} | } | |||
End of changes. 21 change blocks. | ||||
217 lines changed or deleted | 161 lines changed or added |