NTP-Timeturner/tests/ptp_integration_test.rs
John Rogers 28f18e6b20 style: remove unused Arc and Mutex imports
Co-authored-by: aider (openrouter/anthropic/claude-sonnet-4) <aider@aider.chat>
2025-07-10 19:38:11 +01:00

209 lines
6.5 KiB
Rust

use chrono::Utc;
use ntp_timeturner::config::Config;
use ntp_timeturner::sync_logic::{LtcFrame, LtcState};
#[test]
fn test_ptp_config_validation() {
// Test that PTP configuration is properly validated
let config = Config {
hardware_offset_ms: 0,
ptp_enabled: true,
ptp_interface: "eth0".to_string(),
};
assert!(config.ptp_enabled);
assert_eq!(config.ptp_interface, "eth0");
assert_eq!(config.hardware_offset_ms, 0);
}
#[test]
fn test_ptp_disabled_config() {
// Test that PTP can be disabled via config
let config = Config {
hardware_offset_ms: 0,
ptp_enabled: false,
ptp_interface: "eth0".to_string(),
};
assert!(!config.ptp_enabled);
// Even when disabled, interface should be preserved
assert_eq!(config.ptp_interface, "eth0");
}
#[test]
fn test_ltc_to_ptp_time_conversion() {
// Test the conversion logic from LTC timecode to PTP time
let mut state = LtcState::new();
// Create a test LTC frame
let ltc_frame = LtcFrame {
status: "LOCK".to_string(),
hours: 14,
minutes: 30,
seconds: 45,
frames: 12,
frame_rate: 25.0,
timestamp: Utc::now(),
};
// Update state with the frame
state.update(ltc_frame.clone());
// Verify the frame was stored
assert!(state.latest.is_some());
let stored_frame = state.latest.as_ref().unwrap();
assert_eq!(stored_frame.hours, 14);
assert_eq!(stored_frame.minutes, 30);
assert_eq!(stored_frame.seconds, 45);
assert_eq!(stored_frame.frames, 12);
assert_eq!(stored_frame.frame_rate, 25.0);
// Test frame-to-millisecond conversion
let ms_from_frames = ((stored_frame.frames as f64 / stored_frame.frame_rate) * 1000.0).round() as i64;
assert_eq!(ms_from_frames, 480); // 12/25 * 1000 = 480ms
// Test that LOCK status increments lock count
assert_eq!(state.lock_count, 1);
assert_eq!(state.free_count, 0);
}
#[test]
fn test_ltc_ptp_synchronization_accuracy() {
// Test that LTC timecode can be accurately converted for PTP synchronization
let test_cases = vec![
(0, 25.0, 0), // Frame 0 at 25fps = 0ms
(12, 25.0, 480), // Frame 12 at 25fps = 480ms
(24, 25.0, 960), // Frame 24 at 25fps = 960ms
(0, 30.0, 0), // Frame 0 at 30fps = 0ms
(15, 30.0, 500), // Frame 15 at 30fps = 500ms
(29, 30.0, 967), // Frame 29 at 30fps = 966.67ms ≈ 967ms
];
for (frame_num, fps, expected_ms) in test_cases {
let ltc_frame = LtcFrame {
status: "LOCK".to_string(),
hours: 12,
minutes: 0,
seconds: 0,
frames: frame_num,
frame_rate: fps,
timestamp: Utc::now(),
};
let ms_from_frames = ((ltc_frame.frames as f64 / ltc_frame.frame_rate) * 1000.0).round() as i64;
assert_eq!(ms_from_frames, expected_ms,
"Frame {} at {}fps should convert to {}ms, got {}ms",
frame_num, fps, expected_ms, ms_from_frames);
}
}
#[test]
fn test_ptp_offset_tracking_with_ltc() {
// Test that PTP offset tracking works correctly with LTC frames
let mut state = LtcState::new();
// Simulate receiving multiple LOCK frames with different arrival times
for i in 0..10 {
let ltc_frame = LtcFrame {
status: "LOCK".to_string(),
hours: 12,
minutes: 0,
seconds: i / 25, // Advance seconds every 25 frames
frames: i % 25,
frame_rate: 25.0,
timestamp: Utc::now(),
};
state.update(ltc_frame);
// Simulate some jitter measurements
let simulated_offset = (i as i64 - 5) * 2; // Range from -10 to +8ms
state.record_offset(simulated_offset);
}
// Check that we have recorded offsets
assert_eq!(state.offset_history.len(), 10);
// Check average calculation
let expected_avg = (-10 + -8 + -6 + -4 + -2 + 0 + 2 + 4 + 6 + 8) / 10; // = -1
assert_eq!(state.average_jitter(), expected_avg);
// Check frame conversion
let avg_frames = state.average_frames();
let expected_frames = (expected_avg as f64 / (1000.0 / 25.0)).round() as i64; // -1ms / 40ms per frame
assert_eq!(avg_frames, expected_frames);
}
#[test]
fn test_ptp_interface_configuration() {
// Test that PTP interface can be configured
let mut config = Config {
hardware_offset_ms: 0,
ptp_enabled: true,
ptp_interface: "eth0".to_string(),
};
assert_eq!(config.ptp_interface, "eth0");
// Test interface change
config.ptp_interface = "eth1".to_string();
assert_eq!(config.ptp_interface, "eth1");
// Test with different interface types
config.ptp_interface = "enp0s3".to_string();
assert_eq!(config.ptp_interface, "enp0s3");
}
#[test]
fn test_ptp_state_initialization() {
// Test that LtcState initializes with correct PTP defaults
let state = LtcState::new();
assert!(state.ptp_offset.is_none());
assert_eq!(state.ptp_state, "Initializing");
}
#[test]
fn test_ptp_offset_storage() {
// Test that PTP offset can be stored and retrieved
let mut state = LtcState::new();
// Initially no offset
assert!(state.ptp_offset.is_none());
// Simulate setting a PTP offset (this would normally be done by the PTP client)
state.ptp_offset = Some(123.456);
state.ptp_state = "Slave".to_string();
assert_eq!(state.ptp_offset, Some(123.456));
assert_eq!(state.ptp_state, "Slave");
}
#[test]
fn test_ltc_frame_timing_precision() {
// Test that LTC frame timing is precise enough for PTP synchronization
let base_time = Utc::now();
let ltc_frame = LtcFrame {
status: "LOCK".to_string(),
hours: 10,
minutes: 15,
seconds: 30,
frames: 20,
frame_rate: 25.0,
timestamp: base_time,
};
// Calculate the precise time this frame represents
let frame_duration_ms = 1000.0 / ltc_frame.frame_rate; // 40ms for 25fps
let frame_offset_ms = ltc_frame.frames as f64 * frame_duration_ms; // 20 * 40 = 800ms
// Verify precision is sufficient for PTP (sub-millisecond accuracy needed)
assert!(frame_duration_ms > 0.0);
assert!(frame_offset_ms >= 0.0 && frame_offset_ms < 1000.0);
// Test that we can represent frame timing with microsecond precision
let frame_offset_us = (ltc_frame.frames as f64 / ltc_frame.frame_rate * 1_000_000.0).round() as i64;
assert_eq!(frame_offset_us, 800_000); // 20/25 * 1,000,000 = 800,000 microseconds
}