use serde::{Deserialize, Serialize};
use crate::baseline::BaselineComparison;
use crate::budget::PolicyResult;
use crate::confidence::Confidence;
use crate::diagnostics::Diagnostic;
use crate::metadata::RunMetadata;
use crate::parser::{CallNode, ScopeResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Status {
Pass,
Warn,
Fail,
Unknown,
}
impl Status {
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::Pass => "PASS",
Self::Warn => "WARN",
Self::Fail => "FAIL",
Self::Unknown => "UNKNOWN",
}
}
#[must_use]
pub fn from_policy(p: crate::budget::PolicyStatus) -> Self {
match p {
crate::budget::PolicyStatus::Pass => Self::Pass,
crate::budget::PolicyStatus::Warn => Self::Warn,
crate::budget::PolicyStatus::Fail => Self::Fail,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct InstructionMeasurement {
pub index: usize,
pub program_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub consumed: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Measurement {
pub total_cu: u64,
pub consumed: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub requested_limit: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub over_requested: Option<u64>,
pub cpi_count: u32,
pub cpi_depth: u32,
pub unattributed_pct: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub instrumentation_overhead_pct: Option<f64>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub per_instruction: Vec<InstructionMeasurement>,
pub simulation_success: bool,
}
impl Measurement {
#[must_use]
pub fn empty() -> Self {
Self {
total_cu: 0,
consumed: 0,
requested_limit: None,
over_requested: None,
cpi_count: 0,
cpi_depth: 0,
unattributed_pct: 0.0,
instrumentation_overhead_pct: None,
per_instruction: Vec::new(),
simulation_success: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ScenarioReport {
pub name: String,
pub status: Status,
pub measurement: Measurement,
#[serde(skip_serializing_if = "Option::is_none")]
pub call_tree: Option<CallNode>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub scopes: Vec<ScopeResult>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub policy_results: Vec<PolicyResult>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub diagnostics: Vec<Diagnostic>,
pub confidence: Confidence,
#[serde(skip_serializing_if = "Option::is_none")]
pub baseline_comparison: Option<BaselineComparison>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub parser_warnings: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub raw_logs: Option<Vec<String>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Summary {
pub total_scenarios: usize,
pub passed: usize,
pub warned: usize,
pub failed: usize,
pub total_cu: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Report {
pub summary: Summary,
pub scenarios: Vec<ScenarioReport>,
pub metadata: RunMetadata,
}
impl Report {
#[must_use]
pub fn new(scenarios: Vec<ScenarioReport>, metadata: RunMetadata) -> Self {
let mut summary = Summary {
total_scenarios: scenarios.len(),
passed: 0,
warned: 0,
failed: 0,
total_cu: 0,
};
for s in &scenarios {
summary.total_cu = summary.total_cu.saturating_add(s.measurement.total_cu);
match s.status {
Status::Pass => summary.passed += 1,
Status::Warn => summary.warned += 1,
Status::Fail | Status::Unknown => summary.failed += 1,
}
}
Self {
summary,
scenarios,
metadata,
}
}
#[must_use]
pub fn has_failures(&self) -> bool {
self.summary.failed > 0
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::confidence::Confidence;
use crate::metadata::RunMetadata;
fn scenario(name: &str, status: Status, cu: u64) -> ScenarioReport {
ScenarioReport {
name: name.into(),
status,
measurement: Measurement {
total_cu: cu,
..Measurement::empty()
},
call_tree: None,
scopes: Vec::new(),
policy_results: Vec::new(),
diagnostics: Vec::new(),
confidence: Confidence::high(),
baseline_comparison: None,
parser_warnings: Vec::new(),
raw_logs: None,
}
}
#[test]
fn summary_counts_and_totals() {
let r = Report::new(
vec![
scenario("a", Status::Pass, 100),
scenario("b", Status::Warn, 200),
scenario("c", Status::Fail, 300),
],
RunMetadata::recorded("0.1.0"),
);
assert_eq!(r.summary.passed, 1);
assert_eq!(r.summary.warned, 1);
assert_eq!(r.summary.failed, 1);
assert_eq!(r.summary.total_cu, 600);
assert!(r.has_failures());
}
}