use tacet::{
Diagnostics, EffectEstimate, Exploitability, InconclusiveReason, IssueCode, MeasurementQuality,
Outcome, QualityIssue, UnreliablePolicy,
};
#[test]
fn exploitability_shared_hardware_zero() {
assert_eq!(
Exploitability::from_effect_ns(0.0),
Exploitability::SharedHardwareOnly
);
}
#[test]
fn exploitability_shared_hardware_small() {
assert_eq!(
Exploitability::from_effect_ns(5.0),
Exploitability::SharedHardwareOnly
);
assert_eq!(
Exploitability::from_effect_ns(9.0),
Exploitability::SharedHardwareOnly
);
assert_eq!(
Exploitability::from_effect_ns(9.9),
Exploitability::SharedHardwareOnly
);
}
#[test]
fn exploitability_boundary_10ns() {
assert_eq!(
Exploitability::from_effect_ns(9.99),
Exploitability::SharedHardwareOnly
);
assert_eq!(
Exploitability::from_effect_ns(10.0),
Exploitability::Http2Multiplexing
);
assert_eq!(
Exploitability::from_effect_ns(10.01),
Exploitability::Http2Multiplexing
);
}
#[test]
fn exploitability_http2_multiplexing() {
assert_eq!(
Exploitability::from_effect_ns(50.0),
Exploitability::Http2Multiplexing
);
assert_eq!(
Exploitability::from_effect_ns(99.0),
Exploitability::Http2Multiplexing
);
}
#[test]
fn exploitability_boundary_100ns() {
assert_eq!(
Exploitability::from_effect_ns(99.99),
Exploitability::Http2Multiplexing
);
assert_eq!(
Exploitability::from_effect_ns(100.0),
Exploitability::StandardRemote
);
assert_eq!(
Exploitability::from_effect_ns(100.01),
Exploitability::StandardRemote
);
}
#[test]
fn exploitability_standard_remote() {
assert_eq!(
Exploitability::from_effect_ns(500.0),
Exploitability::StandardRemote
);
assert_eq!(
Exploitability::from_effect_ns(1_000.0),
Exploitability::StandardRemote
);
assert_eq!(
Exploitability::from_effect_ns(9_999.0),
Exploitability::StandardRemote
);
}
#[test]
fn exploitability_boundary_10us() {
assert_eq!(
Exploitability::from_effect_ns(9_999.99),
Exploitability::StandardRemote
);
assert_eq!(
Exploitability::from_effect_ns(10_000.0),
Exploitability::ObviousLeak
);
assert_eq!(
Exploitability::from_effect_ns(10_000.01),
Exploitability::ObviousLeak
);
}
#[test]
fn exploitability_obvious_leak() {
assert_eq!(
Exploitability::from_effect_ns(20_000.0),
Exploitability::ObviousLeak
);
assert_eq!(
Exploitability::from_effect_ns(50_000.0),
Exploitability::ObviousLeak
);
assert_eq!(
Exploitability::from_effect_ns(1_000_000.0),
Exploitability::ObviousLeak
);
}
#[test]
fn exploitability_negative_uses_abs() {
assert_eq!(
Exploitability::from_effect_ns(-5.0),
Exploitability::SharedHardwareOnly
);
assert_eq!(
Exploitability::from_effect_ns(-50.0),
Exploitability::Http2Multiplexing
);
assert_eq!(
Exploitability::from_effect_ns(-500.0),
Exploitability::StandardRemote
);
assert_eq!(
Exploitability::from_effect_ns(-10_000.0),
Exploitability::ObviousLeak
);
}
#[test]
fn exploitability_special_values() {
assert_eq!(
Exploitability::from_effect_ns(f64::NAN),
Exploitability::ObviousLeak
);
assert_eq!(
Exploitability::from_effect_ns(f64::INFINITY),
Exploitability::ObviousLeak
);
assert_eq!(
Exploitability::from_effect_ns(f64::NEG_INFINITY),
Exploitability::ObviousLeak
);
}
#[test]
fn quality_excellent() {
assert_eq!(
MeasurementQuality::from_mde_ns(0.5),
MeasurementQuality::Excellent
);
assert_eq!(
MeasurementQuality::from_mde_ns(1.0),
MeasurementQuality::Excellent
);
assert_eq!(
MeasurementQuality::from_mde_ns(4.9),
MeasurementQuality::Excellent
);
}
#[test]
fn quality_boundary_5ns() {
assert_eq!(
MeasurementQuality::from_mde_ns(4.99),
MeasurementQuality::Excellent
);
assert_eq!(
MeasurementQuality::from_mde_ns(5.0),
MeasurementQuality::Good
);
assert_eq!(
MeasurementQuality::from_mde_ns(5.01),
MeasurementQuality::Good
);
}
#[test]
fn quality_good() {
assert_eq!(
MeasurementQuality::from_mde_ns(10.0),
MeasurementQuality::Good
);
assert_eq!(
MeasurementQuality::from_mde_ns(19.9),
MeasurementQuality::Good
);
}
#[test]
fn quality_boundary_20ns() {
assert_eq!(
MeasurementQuality::from_mde_ns(19.99),
MeasurementQuality::Good
);
assert_eq!(
MeasurementQuality::from_mde_ns(20.0),
MeasurementQuality::Poor
);
assert_eq!(
MeasurementQuality::from_mde_ns(20.01),
MeasurementQuality::Poor
);
}
#[test]
fn quality_poor() {
assert_eq!(
MeasurementQuality::from_mde_ns(50.0),
MeasurementQuality::Poor
);
assert_eq!(
MeasurementQuality::from_mde_ns(99.9),
MeasurementQuality::Poor
);
}
#[test]
fn quality_boundary_100ns() {
assert_eq!(
MeasurementQuality::from_mde_ns(99.99),
MeasurementQuality::Poor
);
assert_eq!(
MeasurementQuality::from_mde_ns(100.0),
MeasurementQuality::TooNoisy
);
assert_eq!(
MeasurementQuality::from_mde_ns(100.01),
MeasurementQuality::TooNoisy
);
}
#[test]
fn quality_too_noisy() {
assert_eq!(
MeasurementQuality::from_mde_ns(500.0),
MeasurementQuality::TooNoisy
);
assert_eq!(
MeasurementQuality::from_mde_ns(1000.0),
MeasurementQuality::TooNoisy
);
}
#[test]
fn quality_near_zero_is_too_noisy() {
assert_eq!(
MeasurementQuality::from_mde_ns(0.01),
MeasurementQuality::TooNoisy
);
assert_eq!(
MeasurementQuality::from_mde_ns(0.009),
MeasurementQuality::TooNoisy
);
assert_eq!(
MeasurementQuality::from_mde_ns(0.0),
MeasurementQuality::TooNoisy
);
assert_eq!(
MeasurementQuality::from_mde_ns(-1.0),
MeasurementQuality::TooNoisy
);
}
#[test]
fn quality_just_above_threshold() {
assert_eq!(
MeasurementQuality::from_mde_ns(0.011),
MeasurementQuality::Excellent
);
assert_eq!(
MeasurementQuality::from_mde_ns(0.02),
MeasurementQuality::Excellent
);
}
#[test]
fn quality_special_values() {
assert_eq!(
MeasurementQuality::from_mde_ns(f64::NAN),
MeasurementQuality::TooNoisy
);
assert_eq!(
MeasurementQuality::from_mde_ns(f64::INFINITY),
MeasurementQuality::TooNoisy
);
assert_eq!(
MeasurementQuality::from_mde_ns(f64::NEG_INFINITY),
MeasurementQuality::TooNoisy
);
}
#[test]
fn diagnostics_all_ok_constructor() {
let diag = Diagnostics::all_ok();
assert!(diag.stationarity_ok);
assert!(diag.outlier_asymmetry_ok);
assert!(diag.preflight_ok);
assert!(diag.warnings.is_empty());
assert!(diag.all_checks_passed());
}
#[test]
fn diagnostics_all_checks_passed_all_true() {
let diag = Diagnostics {
dependence_length: 1,
effective_sample_size: 100,
stationarity_ratio: 1.0,
stationarity_ok: true,
outlier_rate_baseline: 0.001,
outlier_rate_sample: 0.001,
outlier_asymmetry_ok: true,
discrete_mode: false,
timer_resolution_ns: 1.0,
duplicate_fraction: 0.0,
preflight_ok: true,
calibration_samples: 5000,
total_time_secs: 1.0,
warnings: vec![],
quality_issues: vec![],
preflight_warnings: vec![],
..Diagnostics::all_ok()
};
assert!(diag.all_checks_passed());
}
#[test]
fn diagnostics_all_checks_passed_one_false() {
let diag1 = Diagnostics {
dependence_length: 1,
effective_sample_size: 100,
stationarity_ratio: 10.0,
stationarity_ok: false,
outlier_rate_baseline: 0.001,
outlier_rate_sample: 0.001,
outlier_asymmetry_ok: true,
discrete_mode: false,
timer_resolution_ns: 1.0,
duplicate_fraction: 0.0,
preflight_ok: true,
calibration_samples: 5000,
total_time_secs: 1.0,
warnings: vec![],
quality_issues: vec![],
preflight_warnings: vec![],
..Diagnostics::all_ok()
};
assert!(!diag1.all_checks_passed());
let diag2 = Diagnostics {
dependence_length: 1,
effective_sample_size: 100,
stationarity_ratio: 1.0,
stationarity_ok: true,
outlier_rate_baseline: 0.1,
outlier_rate_sample: 0.001,
outlier_asymmetry_ok: false,
discrete_mode: false,
timer_resolution_ns: 1.0,
duplicate_fraction: 0.0,
preflight_ok: true,
calibration_samples: 5000,
total_time_secs: 1.0,
warnings: vec![],
quality_issues: vec![],
preflight_warnings: vec![],
..Diagnostics::all_ok()
};
assert!(!diag2.all_checks_passed());
}
#[test]
fn diagnostics_all_checks_passed_all_false() {
let diag = Diagnostics {
dependence_length: 1,
effective_sample_size: 100,
stationarity_ratio: 10.0,
stationarity_ok: false,
outlier_rate_baseline: 0.1,
outlier_rate_sample: 0.001,
outlier_asymmetry_ok: false,
discrete_mode: false,
timer_resolution_ns: 1.0,
duplicate_fraction: 0.0,
preflight_ok: false,
calibration_samples: 5000,
total_time_secs: 1.0,
warnings: vec!["warning".to_string()],
quality_issues: vec![],
preflight_warnings: vec![],
..Diagnostics::all_ok()
};
assert!(!diag.all_checks_passed());
}
#[test]
fn effect_estimate_total_effect() {
let effect = EffectEstimate {
max_effect_ns: 5.0,
credible_interval_ns: (0.0, 10.0),
top_quantiles: Vec::new(),
};
assert!((effect.total_effect_ns() - 5.0).abs() < 0.001);
}
#[test]
fn effect_estimate_is_negligible() {
let effect = EffectEstimate {
max_effect_ns: 7.0,
credible_interval_ns: (0.0, 15.0),
top_quantiles: Vec::new(),
};
assert!(!effect.is_negligible(4.0)); assert!(effect.is_negligible(10.0)); }
#[test]
fn effect_estimate_default() {
let effect = EffectEstimate::default();
assert_eq!(effect.max_effect_ns, 0.0);
assert!(effect.top_quantiles.is_empty());
}
fn make_pass(leak_prob: f64, quality: MeasurementQuality) -> Outcome {
Outcome::Pass {
leak_probability: leak_prob,
effect: EffectEstimate::default(),
samples_used: 10000,
quality,
diagnostics: Diagnostics::all_ok(),
theta_user: 100.0,
theta_eff: 100.0,
theta_floor: 0.0,
}
}
fn make_fail(leak_prob: f64, quality: MeasurementQuality) -> Outcome {
Outcome::Fail {
leak_probability: leak_prob,
effect: EffectEstimate::default(),
exploitability: Exploitability::SharedHardwareOnly,
samples_used: 10000,
quality,
diagnostics: Diagnostics::all_ok(),
theta_user: 100.0,
theta_eff: 100.0,
theta_floor: 0.0,
}
}
fn make_inconclusive(leak_prob: f64, quality: MeasurementQuality) -> Outcome {
Outcome::Inconclusive {
reason: InconclusiveReason::DataTooNoisy {
message: "test".to_string(),
guidance: "test".to_string(),
},
leak_probability: leak_prob,
effect: EffectEstimate::default(),
samples_used: 5000,
quality,
diagnostics: Diagnostics::all_ok(),
theta_user: 100.0,
theta_eff: 100.0,
theta_floor: 0.0,
}
}
#[test]
fn outcome_is_reliable_unmeasurable() {
let outcome = Outcome::Unmeasurable {
operation_ns: 10.0,
threshold_ns: 100.0,
platform: "test".to_string(),
recommendation: "increase complexity".to_string(),
};
assert!(!outcome.is_reliable());
}
#[test]
fn outcome_is_reliable_inconclusive() {
let outcome = make_inconclusive(0.5, MeasurementQuality::Good);
assert!(!outcome.is_reliable());
}
#[test]
fn outcome_is_reliable_good_quality() {
let pass = make_pass(0.02, MeasurementQuality::Good);
assert!(pass.is_reliable());
let fail = make_fail(0.98, MeasurementQuality::Good);
assert!(fail.is_reliable());
}
#[test]
fn outcome_is_reliable_excellent_quality() {
let outcome = make_pass(0.03, MeasurementQuality::Excellent);
assert!(outcome.is_reliable());
}
#[test]
fn outcome_is_reliable_poor_quality() {
let outcome = make_fail(0.97, MeasurementQuality::Poor);
assert!(outcome.is_reliable());
}
#[test]
fn outcome_is_reliable_too_noisy_pass_conclusive() {
let outcome = make_pass(0.005, MeasurementQuality::TooNoisy);
assert!(outcome.is_reliable());
}
#[test]
fn outcome_is_reliable_too_noisy_fail_conclusive() {
let outcome = make_fail(0.995, MeasurementQuality::TooNoisy);
assert!(outcome.is_reliable());
}
#[test]
fn outcome_is_reliable_too_noisy_pass_not_conclusive() {
let outcome = make_pass(0.04, MeasurementQuality::TooNoisy);
assert!(!outcome.is_reliable());
}
#[test]
fn outcome_is_reliable_too_noisy_fail_not_conclusive() {
let outcome = make_fail(0.96, MeasurementQuality::TooNoisy);
assert!(!outcome.is_reliable());
}
#[test]
fn unreliable_policy_default_is_fail_open() {
assert_eq!(UnreliablePolicy::default(), UnreliablePolicy::FailOpen);
}
#[test]
fn unreliable_policy_from_env_missing() {
std::env::remove_var("TIMING_ORACLE_UNRELIABLE_POLICY");
let policy = UnreliablePolicy::from_env_or(UnreliablePolicy::FailOpen);
assert_eq!(policy, UnreliablePolicy::FailOpen);
let policy = UnreliablePolicy::from_env_or(UnreliablePolicy::FailClosed);
assert_eq!(policy, UnreliablePolicy::FailClosed);
}
#[test]
fn unreliable_policy_from_env_fail_open() {
std::env::set_var("TIMING_ORACLE_UNRELIABLE_POLICY", "fail_open");
let policy = UnreliablePolicy::from_env_or(UnreliablePolicy::FailClosed);
assert_eq!(policy, UnreliablePolicy::FailOpen);
std::env::remove_var("TIMING_ORACLE_UNRELIABLE_POLICY");
}
#[test]
fn unreliable_policy_from_env_fail_closed() {
std::env::set_var("TIMING_ORACLE_UNRELIABLE_POLICY", "fail_closed");
let policy = UnreliablePolicy::from_env_or(UnreliablePolicy::FailOpen);
assert_eq!(policy, UnreliablePolicy::FailClosed);
std::env::remove_var("TIMING_ORACLE_UNRELIABLE_POLICY");
}
#[test]
fn unreliable_policy_from_env_invalid() {
std::env::set_var("TIMING_ORACLE_UNRELIABLE_POLICY", "invalid_value");
let policy = UnreliablePolicy::from_env_or(UnreliablePolicy::FailOpen);
assert_eq!(policy, UnreliablePolicy::FailOpen);
std::env::remove_var("TIMING_ORACLE_UNRELIABLE_POLICY");
}
#[test]
fn unreliable_policy_from_env_empty() {
std::env::set_var("TIMING_ORACLE_UNRELIABLE_POLICY", "");
let policy = UnreliablePolicy::from_env_or(UnreliablePolicy::FailClosed);
assert_eq!(policy, UnreliablePolicy::FailClosed);
std::env::remove_var("TIMING_ORACLE_UNRELIABLE_POLICY");
}
#[test]
fn effect_estimate_json_roundtrip() {
let effect = EffectEstimate {
max_effect_ns: 15.5,
credible_interval_ns: (10.0, 20.0),
top_quantiles: Vec::new(),
};
let json = serde_json::to_string(&effect).unwrap();
let deserialized: EffectEstimate = serde_json::from_str(&json).unwrap();
assert_eq!(effect.max_effect_ns, deserialized.max_effect_ns);
assert_eq!(
effect.credible_interval_ns,
deserialized.credible_interval_ns
);
}
#[test]
fn exploitability_json_roundtrip() {
for variant in [
Exploitability::SharedHardwareOnly,
Exploitability::Http2Multiplexing,
Exploitability::StandardRemote,
Exploitability::ObviousLeak,
] {
let json = serde_json::to_string(&variant).unwrap();
let deserialized: Exploitability = serde_json::from_str(&json).unwrap();
assert_eq!(variant, deserialized);
}
}
#[test]
fn measurement_quality_json_roundtrip() {
for variant in [
MeasurementQuality::Excellent,
MeasurementQuality::Good,
MeasurementQuality::Poor,
MeasurementQuality::TooNoisy,
] {
let json = serde_json::to_string(&variant).unwrap();
let deserialized: MeasurementQuality = serde_json::from_str(&json).unwrap();
assert_eq!(variant, deserialized);
}
}
#[test]
fn outcome_pass_json_roundtrip() {
let outcome = make_pass(0.02, MeasurementQuality::Excellent);
let json = serde_json::to_string(&outcome).unwrap();
let deserialized: Outcome = serde_json::from_str(&json).unwrap();
match deserialized {
Outcome::Pass {
leak_probability,
quality,
..
} => {
assert!((leak_probability - 0.02).abs() < 0.001);
assert_eq!(quality, MeasurementQuality::Excellent);
}
_ => panic!("Expected Pass variant"),
}
}
#[test]
fn outcome_fail_json_roundtrip() {
let outcome = make_fail(0.98, MeasurementQuality::Good);
let json = serde_json::to_string(&outcome).unwrap();
let deserialized: Outcome = serde_json::from_str(&json).unwrap();
match deserialized {
Outcome::Fail {
leak_probability,
quality,
..
} => {
assert!((leak_probability - 0.98).abs() < 0.001);
assert_eq!(quality, MeasurementQuality::Good);
}
_ => panic!("Expected Fail variant"),
}
}
#[test]
fn outcome_inconclusive_json_roundtrip() {
let outcome = make_inconclusive(0.5, MeasurementQuality::Poor);
let json = serde_json::to_string(&outcome).unwrap();
let deserialized: Outcome = serde_json::from_str(&json).unwrap();
match deserialized {
Outcome::Inconclusive {
leak_probability,
quality,
..
} => {
assert!((leak_probability - 0.5).abs() < 0.001);
assert_eq!(quality, MeasurementQuality::Poor);
}
_ => panic!("Expected Inconclusive variant"),
}
}
#[test]
fn outcome_unmeasurable_json_roundtrip() {
let outcome = Outcome::Unmeasurable {
operation_ns: 5.5,
threshold_ns: 100.0,
platform: "test platform".to_string(),
recommendation: "do something".to_string(),
};
let json = serde_json::to_string(&outcome).unwrap();
let deserialized: Outcome = serde_json::from_str(&json).unwrap();
match deserialized {
Outcome::Unmeasurable {
operation_ns,
platform,
..
} => {
assert_eq!(operation_ns, 5.5);
assert_eq!(platform, "test platform");
}
_ => panic!("Expected Unmeasurable variant"),
}
}
use tacet::AttackerModel;
#[test]
fn attacker_model_default_is_adjacent_network() {
assert_eq!(AttackerModel::default(), AttackerModel::AdjacentNetwork);
}
#[test]
fn attacker_model_to_threshold_ns_presets() {
assert!((AttackerModel::SharedHardware.to_threshold_ns() - 0.4).abs() < 0.01);
assert_eq!(AttackerModel::AdjacentNetwork.to_threshold_ns(), 100.0);
assert_eq!(AttackerModel::RemoteNetwork.to_threshold_ns(), 50_000.0);
assert_eq!(AttackerModel::Research.to_threshold_ns(), 0.0);
}
#[test]
fn attacker_model_custom() {
let model = AttackerModel::Custom {
threshold_ns: 250.0,
};
assert_eq!(model.to_threshold_ns(), 250.0);
}
#[test]
fn attacker_model_description() {
assert!(AttackerModel::SharedHardware
.description()
.contains("hardware"));
assert!(
AttackerModel::AdjacentNetwork
.description()
.contains("network")
|| AttackerModel::AdjacentNetwork.description().contains("LAN")
);
assert!(
AttackerModel::RemoteNetwork
.description()
.contains("remote")
|| AttackerModel::RemoteNetwork
.description()
.contains("internet")
);
assert!(
AttackerModel::Research.description().contains("research")
|| AttackerModel::Research.description().contains("any")
);
}
#[test]
fn issue_code_numerical_issue_exists() {
let issue = QualityIssue {
code: IssueCode::NumericalIssue,
message: "MCMC chain mixing poor (CV=0.05, ESS=10)".to_string(),
guidance: "Posterior may be unreliable; consider longer time budget.".to_string(),
};
let json = serde_json::to_string(&issue).unwrap();
assert!(json.contains("NumericalIssue"));
let deserialized: QualityIssue = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.code, IssueCode::NumericalIssue);
}
#[test]
fn issue_code_likelihood_inflated_exists() {
let issue = QualityIssue {
code: IssueCode::LikelihoodInflated,
message: "Likelihood covariance inflated ~5.0x due to data/model mismatch".to_string(),
guidance: "Uncertainty was increased for robustness. Effect estimates remain valid."
.to_string(),
};
let json = serde_json::to_string(&issue).unwrap();
assert!(json.contains("LikelihoodInflated"));
let deserialized: QualityIssue = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.code, IssueCode::LikelihoodInflated);
}
#[test]
fn issue_code_json_roundtrip_all_variants() {
let variants = vec![
IssueCode::DependenceHigh,
IssueCode::PrecisionLow,
IssueCode::DiscreteMode,
IssueCode::ThresholdIssue,
IssueCode::FilteringApplied,
IssueCode::StationarityIssue,
IssueCode::NumericalIssue,
IssueCode::LikelihoodInflated,
];
for code in variants {
let issue = QualityIssue {
code,
message: "test message".to_string(),
guidance: "test guidance".to_string(),
};
let json = serde_json::to_string(&issue).unwrap();
let deserialized: QualityIssue = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.code, issue.code);
assert_eq!(deserialized.message, "test message");
}
}