use goose::metrics::coordinated_omission::*;
use goose::metrics::GooseCoordinatedOmissionMitigation;
use std::thread::sleep;
use std::time::Duration;
mod coordinated_omission_metrics_tests {
use super::*;
#[test]
fn test_co_metrics_initialization() {
let metrics = CoordinatedOmissionMetrics::new(GooseCoordinatedOmissionMitigation::Average);
assert_eq!(metrics.actual_requests, 0);
assert_eq!(metrics.synthetic_requests, 0);
assert_eq!(metrics.synthetic_percentage, 0.0);
assert!(metrics.co_events.is_empty());
assert!(metrics.severity_histogram.is_empty());
assert_eq!(
metrics.mitigation_strategy,
GooseCoordinatedOmissionMitigation::Average
);
assert!(metrics.started_secs > 0);
let metrics_min =
CoordinatedOmissionMetrics::new(GooseCoordinatedOmissionMitigation::Minimum);
assert_eq!(
metrics_min.mitigation_strategy,
GooseCoordinatedOmissionMitigation::Minimum
);
let metrics_max =
CoordinatedOmissionMetrics::new(GooseCoordinatedOmissionMitigation::Maximum);
assert_eq!(
metrics_max.mitigation_strategy,
GooseCoordinatedOmissionMitigation::Maximum
);
let metrics_disabled =
CoordinatedOmissionMetrics::new(GooseCoordinatedOmissionMitigation::Disabled);
assert_eq!(
metrics_disabled.mitigation_strategy,
GooseCoordinatedOmissionMitigation::Disabled
);
}
#[test]
fn test_record_actual_request() {
let mut metrics =
CoordinatedOmissionMetrics::new(GooseCoordinatedOmissionMitigation::Average);
assert_eq!(metrics.actual_requests, 0);
assert_eq!(metrics.synthetic_requests, 0);
assert_eq!(metrics.synthetic_percentage, 0.0);
metrics.record_actual_request();
assert_eq!(metrics.actual_requests, 1);
assert_eq!(metrics.synthetic_requests, 0);
assert_eq!(metrics.synthetic_percentage, 0.0);
metrics.record_actual_request();
metrics.record_actual_request();
assert_eq!(metrics.actual_requests, 3);
assert_eq!(metrics.synthetic_requests, 0);
assert_eq!(metrics.synthetic_percentage, 0.0);
assert!(metrics.co_events.is_empty());
assert!(metrics.severity_histogram.is_empty());
}
#[test]
fn test_record_synthetic_requests() {
let mut metrics =
CoordinatedOmissionMetrics::new(GooseCoordinatedOmissionMitigation::Average);
assert_eq!(metrics.actual_requests, 0);
assert_eq!(metrics.synthetic_requests, 0);
assert_eq!(metrics.synthetic_percentage, 0.0);
metrics.record_synthetic_requests(5);
assert_eq!(metrics.actual_requests, 0);
assert_eq!(metrics.synthetic_requests, 5);
assert_eq!(metrics.synthetic_percentage, 100.0);
metrics.record_synthetic_requests(3);
assert_eq!(metrics.actual_requests, 0);
assert_eq!(metrics.synthetic_requests, 8);
assert_eq!(metrics.synthetic_percentage, 100.0);
assert!(metrics.co_events.is_empty());
assert!(metrics.severity_histogram.is_empty());
}
#[test]
fn test_synthetic_percentage_calculation() {
let mut metrics =
CoordinatedOmissionMetrics::new(GooseCoordinatedOmissionMitigation::Average);
assert_eq!(metrics.synthetic_percentage, 0.0);
for _ in 0..100 {
metrics.record_actual_request();
}
assert_eq!(metrics.synthetic_percentage, 0.0);
metrics.record_synthetic_requests(10);
assert!((metrics.synthetic_percentage - 9.090909).abs() < 0.001);
metrics.record_synthetic_requests(40); assert!((metrics.synthetic_percentage - 33.333333).abs() < 0.001);
metrics.record_synthetic_requests(50); assert_eq!(metrics.synthetic_percentage, 50.0); }
#[test]
fn test_record_co_event() {
let mut metrics =
CoordinatedOmissionMetrics::new(GooseCoordinatedOmissionMitigation::Average);
let expected = Duration::from_millis(100);
let actual = Duration::from_millis(300); metrics.record_co_event(expected, actual, 2, 1, "TestScenario".to_string());
assert_eq!(metrics.co_events.len(), 1);
assert_eq!(metrics.synthetic_requests, 2);
assert_eq!(metrics.severity_histogram.get(&CoSeverity::Minor), Some(&1));
let event = &metrics.co_events[0];
assert_eq!(event.expected_cadence, expected);
assert_eq!(event.actual_duration, actual);
assert_eq!(event.synthetic_injected, 2);
assert_eq!(event.user_id, 1);
assert_eq!(event.scenario_name, "TestScenario");
assert_eq!(event.severity, CoSeverity::Minor);
assert!(event.timestamp_secs > 0);
let actual_moderate = Duration::from_millis(700); metrics.record_co_event(expected, actual_moderate, 5, 2, "TestScenario2".to_string());
assert_eq!(metrics.co_events.len(), 2);
assert_eq!(metrics.synthetic_requests, 7); assert_eq!(metrics.severity_histogram.get(&CoSeverity::Minor), Some(&1));
assert_eq!(
metrics.severity_histogram.get(&CoSeverity::Moderate),
Some(&1)
);
let actual_severe = Duration::from_millis(1500); metrics.record_co_event(expected, actual_severe, 10, 3, "TestScenario3".to_string());
assert_eq!(metrics.co_events.len(), 3);
assert_eq!(metrics.synthetic_requests, 17); assert_eq!(
metrics.severity_histogram.get(&CoSeverity::Severe),
Some(&1)
);
let actual_critical = Duration::from_millis(2500); metrics.record_co_event(
expected,
actual_critical,
20,
4,
"TestScenario4".to_string(),
);
assert_eq!(metrics.co_events.len(), 4);
assert_eq!(metrics.synthetic_requests, 37); assert_eq!(
metrics.severity_histogram.get(&CoSeverity::Critical),
Some(&1)
);
let critical_event = &metrics.co_events[3];
assert_eq!(critical_event.severity, CoSeverity::Critical);
}
}
mod severity_classification_tests {
use super::*;
#[test]
fn test_calculate_severity_boundaries() {
let mut metrics =
CoordinatedOmissionMetrics::new(GooseCoordinatedOmissionMitigation::Average);
let expected = Duration::from_millis(100);
metrics.record_co_event(
expected,
Duration::from_millis(300),
2,
1,
"Test".to_string(),
); assert_eq!(metrics.co_events[0].severity, CoSeverity::Minor);
metrics.record_co_event(
expected,
Duration::from_millis(700),
5,
2,
"Test".to_string(),
); assert_eq!(metrics.co_events[1].severity, CoSeverity::Moderate);
metrics.record_co_event(
expected,
Duration::from_millis(1500),
10,
3,
"Test".to_string(),
); assert_eq!(metrics.co_events[2].severity, CoSeverity::Severe);
metrics.record_co_event(
expected,
Duration::from_millis(2500),
20,
4,
"Test".to_string(),
); assert_eq!(metrics.co_events[3].severity, CoSeverity::Critical);
assert_eq!(metrics.severity_histogram.get(&CoSeverity::Minor), Some(&1));
assert_eq!(
metrics.severity_histogram.get(&CoSeverity::Moderate),
Some(&1)
);
assert_eq!(
metrics.severity_histogram.get(&CoSeverity::Severe),
Some(&1)
);
assert_eq!(
metrics.severity_histogram.get(&CoSeverity::Critical),
Some(&1)
);
}
#[test]
fn test_severity_edge_cases() {
let mut metrics =
CoordinatedOmissionMetrics::new(GooseCoordinatedOmissionMitigation::Average);
let small_expected = Duration::from_millis(1);
metrics.record_co_event(
small_expected,
Duration::from_millis(3),
1,
1,
"Test".to_string(),
); assert_eq!(metrics.co_events[0].severity, CoSeverity::Minor);
let zero_expected = Duration::from_millis(0);
metrics.record_co_event(
zero_expected,
Duration::from_millis(100),
1,
2,
"Test".to_string(),
);
assert_eq!(metrics.co_events[1].severity, CoSeverity::Critical);
let expected = Duration::from_millis(100);
metrics.record_co_event(expected, Duration::from_millis(0), 1, 3, "Test".to_string());
assert_eq!(metrics.co_events[2].severity, CoSeverity::Minor);
let large_expected = Duration::from_secs(10);
let large_actual = Duration::from_secs(300); metrics.record_co_event(large_expected, large_actual, 1, 4, "Test".to_string());
assert_eq!(metrics.co_events[3].severity, CoSeverity::Critical);
}
}
mod cadence_calculator_tests {
use super::*;
#[test]
fn test_minimum_cadence_calculator() {
let mut calc = MinimumCadence::new(3);
assert_eq!(calc.name(), "minimum");
assert!(calc.describe_approach().contains("minimum"));
let measurements = vec![Duration::from_millis(100), Duration::from_millis(200)];
let baseline = calc.calculate_baseline(&measurements);
assert_eq!(baseline, Duration::from_secs(3600));
let measurements = vec![
Duration::from_millis(100),
Duration::from_millis(50),
Duration::from_millis(200),
];
let baseline = calc.calculate_baseline(&measurements);
assert_eq!(baseline, Duration::from_millis(50));
assert!(
!calc.should_inject_synthetic(Duration::from_millis(100), Duration::from_millis(50))
); assert!(!calc.should_inject_synthetic(Duration::from_millis(99), Duration::from_millis(50))); assert!(calc.should_inject_synthetic(Duration::from_millis(101), Duration::from_millis(50)));
}
#[test]
fn test_average_cadence_calculator() {
let mut calc = AverageCadence::new(2, 2.5);
assert_eq!(calc.name(), "average");
assert!(calc.describe_approach().contains("average"));
let measurements = vec![Duration::from_millis(100)];
let baseline = calc.calculate_baseline(&measurements);
assert_eq!(baseline, Duration::from_secs(3600));
let measurements = vec![
Duration::from_millis(100),
Duration::from_millis(200),
Duration::from_millis(300),
];
let baseline = calc.calculate_baseline(&measurements);
assert_eq!(baseline, Duration::from_millis(200));
let baseline = Duration::from_millis(100);
assert!(!calc.should_inject_synthetic(Duration::from_millis(250), baseline)); assert!(calc.should_inject_synthetic(Duration::from_millis(251), baseline)); assert!(!calc.should_inject_synthetic(Duration::from_millis(249), baseline));
let baseline = calc.calculate_baseline(&[]);
assert_eq!(baseline, Duration::from_secs(1)); }
#[test]
fn test_maximum_cadence_calculator() {
let mut calc = MaximumCadence::new(2);
assert_eq!(calc.name(), "maximum");
assert!(calc.describe_approach().contains("maximum"));
let measurements = vec![Duration::from_millis(100)];
let baseline = calc.calculate_baseline(&measurements);
assert_eq!(baseline, Duration::from_secs(3600));
let measurements = vec![
Duration::from_millis(100),
Duration::from_millis(50),
Duration::from_millis(200),
];
let baseline = calc.calculate_baseline(&measurements);
assert_eq!(baseline, Duration::from_millis(200));
assert!(
!calc.should_inject_synthetic(Duration::from_millis(400), Duration::from_millis(200))
); assert!(
!calc.should_inject_synthetic(Duration::from_millis(399), Duration::from_millis(200))
); assert!(
calc.should_inject_synthetic(Duration::from_millis(401), Duration::from_millis(200))
); }
#[test]
fn test_percentile_cadence_calculator() {
let mut calc = PercentileCadence::new(0.95, 2);
assert_eq!(calc.name(), "percentile");
assert!(calc.describe_approach().contains("percentile"));
let measurements = vec![Duration::from_millis(100)];
let baseline = calc.calculate_baseline(&measurements);
assert_eq!(baseline, Duration::from_secs(3600));
let measurements: Vec<Duration> =
(1..=100).map(|i| Duration::from_millis(i as u64)).collect();
let baseline = calc.calculate_baseline(&measurements);
assert!(baseline >= Duration::from_millis(94) && baseline <= Duration::from_millis(96));
assert!(
!calc.should_inject_synthetic(Duration::from_millis(200), Duration::from_millis(100))
); assert!(
!calc.should_inject_synthetic(Duration::from_millis(199), Duration::from_millis(100))
); assert!(
calc.should_inject_synthetic(Duration::from_millis(201), Duration::from_millis(100))
);
let baseline = calc.calculate_baseline(&[]);
assert_eq!(baseline, Duration::from_secs(1)); }
#[test]
fn test_disabled_cadence_calculator() {
let mut calc = create_cadence_calculator(&GooseCoordinatedOmissionMitigation::Disabled, 3);
assert_eq!(calc.name(), "disabled");
assert!(calc.describe_approach().contains("disabled"));
let baseline = calc.calculate_baseline(&[Duration::from_millis(100)]);
assert_eq!(baseline, Duration::from_secs(u64::MAX));
assert!(!calc.should_inject_synthetic(Duration::from_secs(1000), Duration::from_millis(1)));
assert!(
!calc.should_inject_synthetic(Duration::from_secs(u64::MAX), Duration::from_millis(1))
);
}
#[test]
fn test_cadence_calculator_factory() {
let warmup_iterations = 5;
let calc = create_cadence_calculator(
&GooseCoordinatedOmissionMitigation::Average,
warmup_iterations,
);
assert_eq!(calc.name(), "average");
let calc = create_cadence_calculator(
&GooseCoordinatedOmissionMitigation::Minimum,
warmup_iterations,
);
assert_eq!(calc.name(), "minimum");
let calc = create_cadence_calculator(
&GooseCoordinatedOmissionMitigation::Maximum,
warmup_iterations,
);
assert_eq!(calc.name(), "maximum");
let calc = create_cadence_calculator(
&GooseCoordinatedOmissionMitigation::Disabled,
warmup_iterations,
);
assert_eq!(calc.name(), "disabled");
}
}
mod co_summary_tests {
use super::*;
#[test]
fn test_get_summary_basic() {
let mut metrics =
CoordinatedOmissionMetrics::new(GooseCoordinatedOmissionMitigation::Average);
metrics.record_actual_request();
metrics.record_actual_request();
metrics.record_synthetic_requests(3);
sleep(Duration::from_secs(1));
let summary = metrics.get_summary();
assert_eq!(summary.total_co_events, 0); assert_eq!(summary.actual_requests, 2);
assert_eq!(summary.synthetic_requests, 3);
assert!((summary.synthetic_percentage - 60.0).abs() < 0.001); assert!(summary.duration_secs > 0); assert_eq!(summary.events_per_minute, 0.0);
assert_eq!(summary.minor_count, 0);
assert_eq!(summary.moderate_count, 0);
assert_eq!(summary.severe_count, 0);
assert_eq!(summary.critical_count, 0);
assert!(summary.per_user_events.is_empty());
assert!(summary.per_scenario_events.is_empty());
}
#[test]
fn test_get_summary_per_user_breakdown() {
let mut metrics =
CoordinatedOmissionMetrics::new(GooseCoordinatedOmissionMitigation::Average);
let expected = Duration::from_millis(100);
metrics.record_co_event(
expected,
Duration::from_millis(300),
2,
1,
"Scenario1".to_string(),
);
metrics.record_co_event(
expected,
Duration::from_millis(400),
3,
1,
"Scenario1".to_string(),
);
metrics.record_co_event(
expected,
Duration::from_millis(600),
5,
2,
"Scenario2".to_string(),
);
metrics.record_co_event(
expected,
Duration::from_millis(1200),
10,
2,
"Scenario2".to_string(),
);
metrics.record_co_event(
expected,
Duration::from_millis(3000),
25,
3,
"Scenario3".to_string(),
);
let summary = metrics.get_summary();
assert_eq!(summary.total_co_events, 5);
assert_eq!(summary.per_user_events.len(), 3);
let user1_data = summary
.per_user_events
.iter()
.find(|(user_id, _, _)| *user_id == 1)
.expect("User 1 should be present");
assert_eq!(user1_data.1, 2); assert!(user1_data.2.contains("Minor: 2"));
let user2_data = summary
.per_user_events
.iter()
.find(|(user_id, _, _)| *user_id == 2)
.expect("User 2 should be present");
assert_eq!(user2_data.1, 2); assert!(user2_data.2.contains("Moderate: 1"));
assert!(user2_data.2.contains("Severe: 1"));
let user3_data = summary
.per_user_events
.iter()
.find(|(user_id, _, _)| *user_id == 3)
.expect("User 3 should be present");
assert_eq!(user3_data.1, 1); assert!(user3_data.2.contains("Critical: 1"));
}
#[test]
fn test_get_summary_per_scenario_breakdown() {
let mut metrics =
CoordinatedOmissionMetrics::new(GooseCoordinatedOmissionMitigation::Average);
let expected = Duration::from_millis(100);
metrics.record_co_event(
expected,
Duration::from_millis(300),
2,
1,
"Scenario1".to_string(),
);
metrics.record_co_event(
expected,
Duration::from_millis(400),
3,
2,
"Scenario1".to_string(),
);
metrics.record_co_event(
expected,
Duration::from_millis(1200),
10,
3,
"Scenario2".to_string(),
);
let summary = metrics.get_summary();
assert_eq!(summary.per_scenario_events.len(), 2);
let scenario1_data = summary
.per_scenario_events
.iter()
.find(|(scenario, _, _)| scenario == "Scenario1")
.expect("Scenario1 should be present");
assert_eq!(scenario1_data.1, 2); assert_eq!(scenario1_data.2, 5);
let scenario2_data = summary
.per_scenario_events
.iter()
.find(|(scenario, _, _)| scenario == "Scenario2")
.expect("Scenario2 should be present");
assert_eq!(scenario2_data.1, 1); assert_eq!(scenario2_data.2, 10); }
#[test]
fn test_synthetic_threshold_detection() {
let mut metrics =
CoordinatedOmissionMetrics::new(GooseCoordinatedOmissionMitigation::Average);
metrics.record_actual_request(); metrics.record_actual_request(); metrics.record_synthetic_requests(1); for _ in 0..197 {
metrics.record_actual_request(); }
assert!(!metrics.is_synthetic_threshold_exceeded(1.0));
metrics.record_synthetic_requests(1); assert!(!metrics.is_synthetic_threshold_exceeded(1.0));
metrics.record_synthetic_requests(1); assert!(metrics.is_synthetic_threshold_exceeded(1.0)); }
}
mod display_formatting_tests {
use super::*;
#[test]
fn test_co_metrics_display() {
let mut metrics =
CoordinatedOmissionMetrics::new(GooseCoordinatedOmissionMitigation::Average);
metrics.record_actual_request();
metrics.record_synthetic_requests(2);
let expected = Duration::from_millis(100);
metrics.record_co_event(
expected,
Duration::from_millis(300),
2,
1,
"TestScenario".to_string(),
);
let display_output = format!("{metrics}");
assert!(display_output.contains("COORDINATED OMISSION METRICS"));
assert!(display_output.contains("Total CO Events: 1"));
assert!(display_output.contains("Actual requests: 1"));
assert!(display_output.contains("Synthetic requests: 4")); assert!(display_output.contains("Minor: 1"));
assert!(display_output.contains("Events per minute:"));
}
#[test]
fn test_co_summary_display() {
let mut metrics =
CoordinatedOmissionMetrics::new(GooseCoordinatedOmissionMitigation::Average);
let expected = Duration::from_millis(100);
metrics.record_co_event(
expected,
Duration::from_millis(300),
2,
1,
"Test1".to_string(),
); metrics.record_co_event(
expected,
Duration::from_millis(700),
5,
2,
"Test2".to_string(),
); metrics.record_co_event(
expected,
Duration::from_millis(1500),
10,
3,
"Test3".to_string(),
); metrics.record_co_event(
expected,
Duration::from_millis(2500),
20,
4,
"Test4".to_string(),
);
let summary = metrics.get_summary();
let display_output = format!("{summary}");
assert!(display_output.contains("Minor: 1"));
assert!(display_output.contains("Moderate: 1"));
assert!(display_output.contains("Severe: 1"));
assert!(display_output.contains("Critical: 1"));
assert!(display_output.contains("Total CO Events: 4"));
assert!(display_output.contains("Synthetic requests: 37"));
assert!(display_output.contains("Events per minute:"));
}
#[test]
fn test_display_handles_zero_values() {
let metrics = CoordinatedOmissionMetrics::new(GooseCoordinatedOmissionMitigation::Average);
let display_output = format!("{metrics}");
assert!(display_output.contains("Total CO Events: 0"));
assert!(display_output.contains("Actual requests: 0"));
assert!(display_output.contains("Synthetic requests: 0 (0.0%)"));
assert!(display_output.contains("Events per minute: 0.00"));
}
}