use cu_profiler_core::Error;
use cu_profiler_core::model::{Report, Status};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum ExitCode {
Success = 0,
BudgetOrRegression = 1,
Config = 2,
Simulation = 3,
Baseline = 4,
ParserReport = 5,
LowConfidence = 6,
}
impl ExitCode {
#[must_use]
pub fn code(self) -> i32 {
self as i32
}
}
#[must_use]
pub fn code_for_error(err: &Error) -> ExitCode {
match err {
Error::Config(_) | Error::Toml(_) => ExitCode::Config,
Error::Simulation(_) | Error::BackendUnimplemented(_) => ExitCode::Simulation,
Error::Baseline(_) => ExitCode::Baseline,
Error::Parse { .. } => ExitCode::ParserReport,
Error::Io(_) => ExitCode::ParserReport,
_ => ExitCode::ParserReport,
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct DecisionFlags {
pub fail_on_budget: bool,
pub fail_on_regression: bool,
pub fail_on_stale_baseline: bool,
pub fail_on_low_confidence: bool,
}
#[must_use]
pub fn code_for_report(report: &Report, flags: DecisionFlags) -> ExitCode {
use cu_profiler_core::budget::PolicyStatus;
use cu_profiler_core::confidence::ConfidenceLevel;
if report.scenarios.iter().any(|s| s.status == Status::Unknown) {
return ExitCode::Simulation;
}
let mut stale = false;
let mut low_conf = false;
for s in &report.scenarios {
for p in &s.policy_results {
if p.status != PolicyStatus::Fail {
continue;
}
if p.policy_id == "absolute_max_cu" && flags.fail_on_budget {
return ExitCode::BudgetOrRegression;
}
if p.policy_id.starts_with("max_regression") && flags.fail_on_regression {
return ExitCode::BudgetOrRegression;
}
}
if let Some(c) = &s.baseline_comparison {
if !c.matched {
stale = true;
}
}
if matches!(
s.confidence.level,
ConfidenceLevel::Low | ConfidenceLevel::Unknown
) {
low_conf = true;
}
}
if flags.fail_on_stale_baseline && stale {
return ExitCode::Baseline;
}
if flags.fail_on_low_confidence && low_conf {
return ExitCode::LowConfidence;
}
ExitCode::Success
}
#[cfg(test)]
mod tests {
use super::*;
use cu_profiler_core::Profiler;
use cu_profiler_core::backend::RecordedLogsBackend;
use cu_profiler_core::budget::BudgetPolicy;
use cu_profiler_core::metadata::RunMetadata;
use cu_profiler_core::scenario::Scenario;
fn report_with(total: u64, max: u64) -> Report {
let mut backend = RecordedLogsBackend::new();
backend.insert_blob(
"s",
&format!("Program P invoke [1]\nProgram P consumed {total} of 200000 compute units\nProgram P success"),
true,
);
let mut scenario = Scenario::new("s");
scenario.budget = BudgetPolicy {
absolute_max_cu: Some(max),
..Default::default()
};
Profiler::new().run(&backend, &[scenario], None, RunMetadata::recorded("0.1.0"))
}
#[test]
fn budget_failure_exits_one_when_enabled() {
let report = report_with(120_000, 100_000);
let flags = DecisionFlags {
fail_on_budget: true,
..Default::default()
};
assert_eq!(
code_for_report(&report, flags),
ExitCode::BudgetOrRegression
);
}
#[test]
fn budget_failure_ignored_when_disabled() {
let report = report_with(120_000, 100_000);
assert_eq!(
code_for_report(&report, DecisionFlags::default()),
ExitCode::Success
);
}
#[test]
fn missing_simulation_exits_three() {
let report = Profiler::new().run(
&RecordedLogsBackend::new(),
&[Scenario::new("ghost")],
None,
RunMetadata::recorded("0.1.0"),
);
assert_eq!(
code_for_report(&report, DecisionFlags::default()),
ExitCode::Simulation
);
}
}