use colored::Colorize;
use crate::result::{Diagnostics, IssueCode, Outcome};
pub fn format_outcome(outcome: &Outcome) -> String {
tacet_core::formatting::format_outcome_plain(outcome)
}
pub fn format_debug_summary(outcome: &Outcome) -> String {
tacet_core::formatting::format_debug_summary_plain(outcome)
}
pub fn format_diagnostics_section(diagnostics: &Diagnostics) -> String {
let mut out = String::new();
let sep = "\u{2500}".repeat(62);
out.push('\n');
out.push_str(&sep);
out.push_str("\n\n");
out.push_str(" Measurement Diagnostics\n\n");
out.push_str(&format!(
" Dependence: block length {} (ESS: {} / {} raw)\n",
diagnostics.dependence_length,
diagnostics.effective_sample_size,
diagnostics.calibration_samples
));
out.push_str(&format!(
" Outliers: baseline {:.2}%, sample {:.2}%",
diagnostics.outlier_rate_baseline * 100.0,
diagnostics.outlier_rate_sample * 100.0,
));
if !diagnostics.outlier_asymmetry_ok {
out.push_str(&format!(" {}", "(asymmetric)".red()));
}
out.push('\n');
out.push_str(&format!(
" Calibration: {} samples\n",
diagnostics.calibration_samples
));
out.push_str(&format!(
" Runtime: {:.1}s\n",
diagnostics.total_time_secs
));
if !diagnostics.warnings.is_empty() {
out.push_str(&format!("\n {} Warnings\n", "\u{26A0}".yellow()));
for warning in &diagnostics.warnings {
out.push_str(&format!(" \u{2022} {}\n", warning));
}
}
if !diagnostics.quality_issues.is_empty() {
out.push_str(&format!("\n {} Quality Issues\n", "\u{26A0}".yellow()));
for issue in &diagnostics.quality_issues {
let code_str = format!("{}: ", format_issue_code(issue.code));
out.push_str(&format!(
" \u{2022} {}{}\n",
code_str.bold(),
issue.message
));
out.push_str(&format!(" \u{2192} {}\n", issue.guidance.dimmed()));
}
}
out
}
fn env_is_truthy(name: &str) -> bool {
std::env::var(name)
.map(|v| matches!(v.to_lowercase().as_str(), "1" | "true" | "yes"))
.unwrap_or(false)
}
pub fn is_verbose() -> bool {
env_is_truthy("TIMING_ORACLE_VERBOSE")
}
#[allow(dead_code)]
pub fn is_debug() -> bool {
env_is_truthy("TIMING_ORACLE_DEBUG")
}
fn format_issue_code(code: IssueCode) -> &'static str {
match code {
IssueCode::DependenceHigh => "DependenceHigh",
IssueCode::PrecisionLow => "PrecisionLow",
IssueCode::DiscreteMode => "DiscreteMode",
IssueCode::ThresholdIssue => "ThresholdIssue",
IssueCode::FilteringApplied => "FilteringApplied",
IssueCode::StationarityIssue => "StationarityIssue",
IssueCode::NumericalIssue => "NumericalIssue",
IssueCode::LikelihoodInflated => "LikelihoodInflated",
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::result::{Diagnostics, EffectEstimate, Exploitability, MeasurementQuality};
fn make_pass_outcome() -> Outcome {
Outcome::Pass {
leak_probability: 0.02,
effect: EffectEstimate {
max_effect_ns: 5.0,
credible_interval_ns: (0.0, 10.0),
top_quantiles: Vec::new(),
},
samples_used: 10000,
quality: MeasurementQuality::Good,
diagnostics: Diagnostics::all_ok(),
theta_user: 100.0,
theta_eff: 100.0,
theta_floor: 0.0,
}
}
fn make_fail_outcome() -> Outcome {
Outcome::Fail {
leak_probability: 0.98,
effect: EffectEstimate {
max_effect_ns: 150.0,
credible_interval_ns: (100.0, 200.0),
top_quantiles: Vec::new(),
},
exploitability: Exploitability::Http2Multiplexing,
samples_used: 10000,
quality: MeasurementQuality::Good,
diagnostics: Diagnostics::all_ok(),
theta_user: 100.0,
theta_eff: 100.0,
theta_floor: 0.0,
}
}
#[test]
fn test_format_pass_outcome() {
let outcome = make_pass_outcome();
let output = format_outcome(&outcome);
assert!(output.contains("tacet"));
assert!(output.contains("No timing leak detected"));
assert!(output.contains("2.0%")); }
#[test]
fn test_format_fail_outcome() {
let outcome = make_fail_outcome();
let output = format_outcome(&outcome);
assert!(output.contains("Timing leak detected"));
assert!(output.contains("98.0%")); assert!(output.contains("Max effect:"));
assert!(output.contains("Exploitability"));
}
#[test]
fn test_format_unmeasurable() {
let outcome = Outcome::Unmeasurable {
operation_ns: 0.5,
threshold_ns: 10.0,
platform: "macos (cntvct)".to_string(),
recommendation: "Run with sudo".to_string(),
};
let output = format_outcome(&outcome);
assert!(output.contains("too fast to measure"));
assert!(output.contains("unmeasurable"));
}
}