iai_callgrind_runner/
error.rs

1//! The module containing the crate main [`Error`] type
2
3use std::fmt::Display;
4use std::io::stderr;
5use std::os::unix::process::ExitStatusExt;
6use std::path::PathBuf;
7use std::process::{ExitStatus, Output};
8
9use version_compare::Cmp;
10
11use crate::api::ValgrindTool;
12use crate::runner::common::ModulePath;
13use crate::runner::format::Header;
14use crate::runner::tool::path::ToolOutputPath;
15use crate::util::write_all_to_stderr;
16
17/// The main Iai-Callgrind error type
18#[derive(Debug, PartialEq, Clone, Eq)]
19pub enum Error {
20    /// A error during setup of a benchmark.
21    ///
22    /// `BenchmarkError(ValgrindTool, ModulePath, message)`
23    BenchmarkError(ValgrindTool, ModulePath, String),
24    /// An error within the UI configuration structs but transpiring in the runner
25    ///
26    /// `ConfigurationError(ModulePath, benchmark_id, message)`
27    ConfigurationError(ModulePath, Option<String>, String),
28    /// An error during the initialization of the runner
29    ///
30    /// `InitError(message)`
31    InitError(String),
32    /// An invalid command-line argument value when only `yes` or `no` is allowed
33    ///
34    /// `InvalidBoolArgument(option_name, value)`
35    InvalidBoolArgument(String, String),
36    /// The error when trying to start an external [`std::process::Command`] fails
37    ///
38    /// `LaunchError(executable_path, message)`
39    LaunchError(PathBuf, String),
40    /// The generic error when parsing of a tools log- or output file fails
41    ///
42    /// `ParseError(file_path, message)`
43    ParseError(PathBuf, String),
44    /// The error after a successful launch of an external [`std::process::Command`]
45    ///
46    /// ```text
47    /// ProcessError(
48    ///     process_name,
49    ///     std::process::Output,
50    ///     std::process::ExitStatus,
51    ///     ToolOutputPath
52    /// )
53    /// ```
54    ProcessError(String, Option<Output>, ExitStatus, Option<ToolOutputPath>),
55    /// If a regression check fails a `RegressionError` is issued
56    ///
57    /// `RegressionError(is_fatal)`, `is_fatal` needs to be true if the error should lead to an
58    /// immediate exit of the runner
59    RegressionError(bool),
60    /// The error when setting up the [`crate::runner::common::Sandbox`] fails
61    ///
62    /// `SandboxError(message)`
63    SandboxError(String),
64    /// A version mismatch between the runner and the UI
65    ///
66    /// `VersionMismatch(Cmp, runner_version, library_version)`
67    VersionMismatch(version_compare::Cmp, String, String),
68}
69
70impl Display for Error {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        match self {
73            Self::InitError(message) => {
74                let runner_version = env!("CARGO_PKG_VERSION").to_owned();
75                write!(
76                    f,
77                    "Failed to initialize iai-callgrind-runner: {message}\n\nDetected version of \
78                     iai-callgrind-runner is {runner_version}. This error can be caused by a \
79                     version mismatch between iai-callgrind and iai-callgrind-runner. If you \
80                     updated the library (iai-callgrind) in your Cargo.toml file, the binary \
81                     (iai-callgrind-runner) needs to be updated to the same version and vice \
82                     versa."
83                )
84            }
85            Self::VersionMismatch(cmp, runner_version, library_version) => match cmp {
86                Cmp::Lt => write!(
87                    f,
88                    "iai-callgrind-runner ({runner_version}) is older than iai-callgrind \
89                     ({library_version}). Please update iai-callgrind-runner by calling 'cargo \
90                     install --version {library_version} iai-callgrind-runner'"
91                ),
92                Cmp::Gt => write!(
93                    f,
94                    "iai-callgrind-runner ({runner_version}) is newer than iai-callgrind \
95                     ({library_version}). Please update iai-callgrind to '{runner_version}' in \
96                     your Cargo.toml file"
97                ),
98                Cmp::Ne => write!(
99                    f,
100                    "No version information found for iai-callgrind but iai-callgrind-runner \
101                     ({runner_version}) is >= '0.3.0'. Please update iai-callgrind to \
102                     '{runner_version}' in your Cargo.toml file"
103                ),
104                _ => unreachable!(),
105            },
106            Self::LaunchError(exec, message) => {
107                write!(f, "Error launching '{}': {message}", exec.display())
108            }
109            Self::ProcessError(process, output, status, output_path) => {
110                if let Some(output_path) = output_path {
111                    let _ = output_path.dump_log(log::Level::Error, &mut stderr());
112                }
113                if let Some(output) = output {
114                    write_all_to_stderr(&output.stderr);
115                }
116
117                if let Some(code) = status.code() {
118                    write!(f, "Error running '{process}': Exit code was: '{code}'")
119                } else if let Some(signal) = status.signal() {
120                    write!(
121                        f,
122                        "Error running '{process}': Terminated by a signal '{signal}'"
123                    )
124                } else {
125                    write!(f, "Error running '{process}': Terminated abnormally")
126                }
127            }
128            Self::InvalidBoolArgument(option, value) => {
129                write!(
130                    f,
131                    "Invalid argument for {option}: '{value}'. Valid values are 'yes' or 'no'"
132                )
133            }
134            Self::ParseError(path, message) => {
135                write!(f, "Error parsing file '{}': {message}", path.display())
136            }
137            Self::RegressionError(is_fatal) => {
138                if *is_fatal {
139                    write!(
140                        f,
141                        "Performance has regressed. Fail-fast is set. Aborting ...",
142                    )
143                } else {
144                    write!(f, "Performance has regressed.",)
145                }
146            }
147            Self::SandboxError(message) => {
148                write!(f, "Error in sandbox: {message}")
149            }
150            Self::BenchmarkError(tool, module_path, message) => {
151                write!(f, "Error in {tool} benchmark {module_path}: {message}")
152            }
153            Self::ConfigurationError(module_path, id, message) => {
154                let header = Header::without_description(module_path, id.clone());
155                write!(f, "Misconfiguration in: {header}\nCaused by:\n  {message}",)
156            }
157        }
158    }
159}
160
161impl std::error::Error for Error {}