Export exit codes to JSON output

This commit is contained in:
Jordi Chauzi 2021-02-28 10:22:21 +01:00 committed by David Peter
parent 9e57bf613c
commit 3fcc6a187b
7 changed files with 53 additions and 6 deletions

View File

@ -1,6 +1,7 @@
# unreleased
- Add command exit code to output if it fails, see #342 (@KaindlJulian)
- Export command exit code to JSON output, see #371 (@JordiChauzi)
## Features

View File

@ -1,6 +1,6 @@
use std::cmp;
use std::io;
use std::process::Stdio;
use std::process::{ExitStatus, Stdio};
use colored::*;
use statistical::{mean, median, standard_deviation};
@ -46,7 +46,7 @@ pub fn time_shell_command(
show_output: bool,
failure_action: CmdFailureAction,
shell_spawning_time: Option<TimingResult>,
) -> io::Result<(TimingResult, bool)> {
) -> io::Result<(TimingResult, ExitStatus)> {
let (stdout, stderr) = if show_output {
(Stdio::inherit(), Stdio::inherit())
} else {
@ -88,7 +88,7 @@ pub fn time_shell_command(
time_user,
time_system,
},
result.status.success(),
result.status,
))
}
@ -201,6 +201,26 @@ fn run_cleanup_command(
run_intermediate_command(shell, command, show_output, error_output)
}
#[cfg(unix)]
fn extract_exit_code(status: ExitStatus) -> i32 {
use std::os::unix::process::ExitStatusExt;
/* From the ExitStatus::code documentation:
"On Unix, this will return None if the process was terminated by a signal."
In that case, ExitStatusExt::signal should never return None.
*/
status.code().unwrap_or_else(|| status.signal().unwrap())
}
#[cfg(not(unix))]
fn extract_exit_code(status: ExitStatus) -> i32 {
/* From the ExitStatus::code documentation:
"On Unix, this will return None if the process was terminated by a signal."
On the other configurations, ExitStatus::code should never return None.
*/
status.code().unwrap()
}
/// Run the benchmark for a single shell command
pub fn run_benchmark(
num: usize,
@ -228,6 +248,7 @@ pub fn run_benchmark(
let mut times_real: Vec<Second> = vec![];
let mut times_user: Vec<Second> = vec![];
let mut times_system: Vec<Second> = vec![];
let mut exit_codes: Vec<i32> = vec![];
let mut all_succeeded = true;
// Run init command
@ -280,13 +301,14 @@ pub fn run_benchmark(
let prepare_res = run_preparation_command(&options.shell, &prepare_cmd, options.show_output)?;
// Initial timing run
let (res, success) = time_shell_command(
let (res, status) = time_shell_command(
&options.shell,
cmd,
options.show_output,
options.failure_action,
Some(shell_spawning_time),
)?;
let success = status.success();
// Determine number of benchmark runs
let runs_in_min_time = (options.min_time_sec
@ -310,6 +332,7 @@ pub fn run_benchmark(
times_real.push(res.time_real);
times_user.push(res.time_user);
times_system.push(res.time_system);
exit_codes.push(extract_exit_code(status));
all_succeeded = all_succeeded && success;
@ -328,17 +351,19 @@ pub fn run_benchmark(
progress_bar.as_ref().map(|bar| bar.set_message(msg.to_owned()));
let (res, success) = time_shell_command(
let (res, status) = time_shell_command(
&options.shell,
cmd,
options.show_output,
options.failure_action,
Some(shell_spawning_time),
)?;
let success = status.success();
times_real.push(res.time_real);
times_user.push(res.time_user);
times_system.push(res.time_system);
exit_codes.push(extract_exit_code(status));
all_succeeded = all_succeeded && success;
@ -438,6 +463,7 @@ pub fn run_benchmark(
t_min,
t_max,
times_real,
exit_codes,
cmd.get_parameters()
.iter()
.map(|(name, value)| ((*name).to_string(), value.to_string()))

View File

@ -99,6 +99,7 @@ fn test_asciidoc_table_row() {
0.10745223440000001,
0.10697327940000001,
],
vec![0, 0, 0], // exit codes
BTreeMap::new(), // param
);
@ -151,6 +152,7 @@ fn test_asciidoc_table_row_command_escape() {
0.10745223440000001,
0.10697327940000001,
],
vec![0, 0, 0], // exit codes
BTreeMap::new(), // param
);
let exps = format!(
@ -185,6 +187,7 @@ fn test_asciidoc() {
5.0,
6.0,
vec![7.0, 8.0, 9.0],
vec![0, 0, 0],
{
let mut params = BTreeMap::new();
params.insert("foo".into(), "1".into());
@ -202,6 +205,7 @@ fn test_asciidoc() {
15.0,
16.0,
vec![17.0, 18.0, 19.0],
vec![0, 0, 0],
{
let mut params = BTreeMap::new();
params.insert("foo".into(), "1".into());

View File

@ -17,7 +17,7 @@ impl Exporter for CsvExporter {
{
let mut headers: Vec<Cow<[u8]>> = [
// The list of times cannot be exported to the CSV file - omit it.
// The list of times and exit codes cannot be exported to the CSV file - omit them.
"command", "mean", "stddev", "median", "user", "system", "min", "max",
]
.iter()
@ -68,6 +68,7 @@ fn test_csv() {
5.0,
6.0,
vec![7.0, 8.0, 9.0],
vec![0, 0, 0],
{
let mut params = BTreeMap::new();
params.insert("foo".into(), "one".into());
@ -85,6 +86,7 @@ fn test_csv() {
15.0,
16.5,
vec![17.0, 18.0, 19.0],
vec![0, 0, 0],
{
let mut params = BTreeMap::new();
params.insert("foo".into(), "one".into());

View File

@ -102,6 +102,7 @@ fn test_markdown_format_ms() {
0.1023, // min
0.1080, // max
vec![0.1, 0.1, 0.1], // times
vec![0, 0, 0], // exit codes
BTreeMap::new(), // parameter
));
@ -115,6 +116,7 @@ fn test_markdown_format_ms() {
2.0020, // min
2.0080, // max
vec![2.0, 2.0, 2.0], // times
vec![0, 0, 0], // exit codes
BTreeMap::new(), // parameter
));
@ -150,6 +152,7 @@ fn test_markdown_format_s() {
2.0020, // min
2.0080, // max
vec![2.0, 2.0, 2.0], // times
vec![0, 0, 0], // exit codes
BTreeMap::new(), // parameter
));
@ -163,6 +166,7 @@ fn test_markdown_format_s() {
0.1023, // min
0.1080, // max
vec![0.1, 0.1, 0.1], // times
vec![0, 0, 0], // exit codes
BTreeMap::new(), // parameter
));
@ -197,6 +201,7 @@ fn test_markdown_format_time_unit_s() {
0.1023, // min
0.1080, // max
vec![0.1, 0.1, 0.1], // times
vec![0, 0, 0], // exit codes
BTreeMap::new(), // parameter
));
@ -210,6 +215,7 @@ fn test_markdown_format_time_unit_s() {
2.0020, // min
2.0080, // max
vec![2.0, 2.0, 2.0], // times
vec![0, 0, 0], // exit codes
BTreeMap::new(), // parameter
));
@ -250,6 +256,7 @@ fn test_markdown_format_time_unit_ms() {
2.0020, // min
2.0080, // max
vec![2.0, 2.0, 2.0], // times
vec![0, 0, 0], // exit codes
BTreeMap::new(), // parameter
));
@ -263,6 +270,7 @@ fn test_markdown_format_time_unit_ms() {
0.1023, // min
0.1080, // max
vec![0.1, 0.1, 0.1], // times
vec![0, 0, 0], // exit codes
BTreeMap::new(), // parameter
));

View File

@ -157,6 +157,7 @@ fn create_result(name: &str, mean: Scalar) -> BenchmarkResult {
min: mean,
max: mean,
times: None,
exit_codes: Vec::new(),
parameters: BTreeMap::new(),
}
}

View File

@ -268,6 +268,9 @@ pub struct BenchmarkResult {
#[serde(skip_serializing_if = "Option::is_none")]
pub times: Option<Vec<Second>>,
/// All run exit codes
pub exit_codes: Vec<i32>,
/// Any parameter values used
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
pub parameters: BTreeMap<String, String>,
@ -285,6 +288,7 @@ impl BenchmarkResult {
min: Second,
max: Second,
times: Vec<Second>,
exit_codes: Vec<i32>,
parameters: BTreeMap<String, String>,
) -> Self {
BenchmarkResult {
@ -297,6 +301,7 @@ impl BenchmarkResult {
min,
max,
times: Some(times),
exit_codes,
parameters,
}
}