mirror of
https://github.com/cjfranko/NTP-Timeturner.git
synced 2025-11-08 18:32:02 +00:00
test: add tests for serial input processing
Co-authored-by: aider (gemini/gemini-2.5-pro-preview-05-06) <aider@aider.chat>
This commit is contained in:
parent
0b8fa0fbf8
commit
3cbe95bd6a
1 changed files with 177 additions and 56 deletions
|
|
@ -1,56 +1,177 @@
|
||||||
// src/serial_input.rs
|
// src/serial_input.rs
|
||||||
|
|
||||||
use std::io::BufRead;
|
use std::io::BufRead;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use crate::sync_logic::{LtcFrame, LtcState};
|
use crate::sync_logic::{LtcFrame, LtcState};
|
||||||
|
|
||||||
pub fn start_serial_thread(
|
pub fn start_serial_thread(
|
||||||
port_path: &str,
|
port_path: &str,
|
||||||
baud_rate: u32,
|
baud_rate: u32,
|
||||||
sender: Sender<LtcFrame>,
|
sender: Sender<LtcFrame>,
|
||||||
state: Arc<Mutex<LtcState>>,
|
state: Arc<Mutex<LtcState>>,
|
||||||
_hardware_offset_ms: i64, // no longer used here
|
_hardware_offset_ms: i64, // no longer used here
|
||||||
) {
|
) {
|
||||||
println!("📡 Opening serial port {} @ {} baud", port_path, baud_rate);
|
println!("📡 Opening serial port {} @ {} baud", port_path, baud_rate);
|
||||||
|
|
||||||
let port = match serialport::new(port_path, baud_rate)
|
let port = match serialport::new(port_path, baud_rate)
|
||||||
.timeout(std::time::Duration::from_millis(1000))
|
.timeout(std::time::Duration::from_millis(1000))
|
||||||
.open()
|
.open()
|
||||||
{
|
{
|
||||||
Ok(p) => {
|
Ok(p) => {
|
||||||
println!("✅ Serial port opened");
|
println!("✅ Serial port opened");
|
||||||
p
|
p
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("❌ Serial open failed: {}", e);
|
eprintln!("❌ Serial open failed: {}", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let reader = std::io::BufReader::new(port);
|
let reader = std::io::BufReader::new(port);
|
||||||
let re = Regex::new(
|
let re = Regex::new(
|
||||||
r"\[(LOCK|FREE)\]\s+(\d{2}):(\d{2}):(\d{2})[:;](\d{2})\s+\|\s+([\d.]+)fps",
|
r"\[(LOCK|FREE)\]\s+(\d{2}):(\d{2}):(\d{2})[:;](\d{2})\s+\|\s+([\d.]+)fps",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
println!("🔄 Entering LTC read loop…");
|
println!("🔄 Entering LTC read loop…");
|
||||||
for line in reader.lines() {
|
for line in reader.lines() {
|
||||||
if let Ok(text) = line {
|
if let Ok(text) = line {
|
||||||
if let Some(caps) = re.captures(&text) {
|
if let Some(caps) = re.captures(&text) {
|
||||||
let arrival = Utc::now();
|
let arrival = Utc::now();
|
||||||
if let Some(frame) = LtcFrame::from_regex(&caps, arrival) {
|
if let Some(frame) = LtcFrame::from_regex(&caps, arrival) {
|
||||||
// update LOCK/FREE counts & timestamp
|
// update LOCK/FREE counts & timestamp
|
||||||
{
|
{
|
||||||
let mut st = state.lock().unwrap();
|
let mut st = state.lock().unwrap();
|
||||||
st.update(frame.clone());
|
st.update(frame.clone());
|
||||||
}
|
}
|
||||||
// forward raw frame
|
// forward raw frame
|
||||||
let _ = sender.send(frame);
|
let _ = sender.send(frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use crate::sync_logic::LtcState;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
fn get_ltc_regex() -> Regex {
|
||||||
|
Regex::new(
|
||||||
|
r"\[(LOCK|FREE)\]\s+(\d{2}):(\d{2}):(\d{2})[:;](\d{2})\s+\|\s+([\d.]+)fps",
|
||||||
|
).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_lock_line() {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
let state = Arc::new(Mutex::new(LtcState::new()));
|
||||||
|
let re = get_ltc_regex();
|
||||||
|
let line = "[LOCK] 10:20:30:00 | 25.00fps";
|
||||||
|
|
||||||
|
// Simulate the processing logic from start_serial_thread
|
||||||
|
if let Some(caps) = re.captures(line) {
|
||||||
|
let arrival = Utc::now();
|
||||||
|
if let Some(frame) = LtcFrame::from_regex(&caps, arrival) {
|
||||||
|
{
|
||||||
|
let mut st = state.lock().unwrap();
|
||||||
|
st.update(frame.clone());
|
||||||
|
}
|
||||||
|
let _ = tx.send(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let st = state.lock().unwrap();
|
||||||
|
assert_eq!(st.lock_count, 1);
|
||||||
|
assert_eq!(st.free_count, 0);
|
||||||
|
let received_frame = rx.try_recv().unwrap();
|
||||||
|
assert_eq!(received_frame.status, "LOCK");
|
||||||
|
assert_eq!(received_frame.hours, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_free_line() {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
let state = Arc::new(Mutex::new(LtcState::new()));
|
||||||
|
let re = get_ltc_regex();
|
||||||
|
let line = "[FREE] 01:02:03:04 | 29.97fps";
|
||||||
|
|
||||||
|
// Simulate the processing logic
|
||||||
|
if let Some(caps) = re.captures(line) {
|
||||||
|
let arrival = Utc::now();
|
||||||
|
if let Some(frame) = LtcFrame::from_regex(&caps, arrival) {
|
||||||
|
{
|
||||||
|
let mut st = state.lock().unwrap();
|
||||||
|
st.update(frame.clone());
|
||||||
|
}
|
||||||
|
let _ = tx.send(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let st = state.lock().unwrap();
|
||||||
|
assert_eq!(st.lock_count, 0);
|
||||||
|
assert_eq!(st.free_count, 1);
|
||||||
|
let received_frame = rx.try_recv().unwrap();
|
||||||
|
assert_eq!(received_frame.status, "FREE");
|
||||||
|
assert_eq!(received_frame.frame_rate, 29.97);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ignore_non_matching_line() {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
let state = Arc::new(Mutex::new(LtcState::new()));
|
||||||
|
let re = get_ltc_regex();
|
||||||
|
let line = "this is not a valid ltc line";
|
||||||
|
|
||||||
|
// Simulate the processing logic
|
||||||
|
if let Some(caps) = re.captures(line) {
|
||||||
|
let arrival = Utc::now();
|
||||||
|
if let Some(frame) = LtcFrame::from_regex(&caps, arrival) {
|
||||||
|
{
|
||||||
|
let mut st = state.lock().unwrap();
|
||||||
|
st.update(frame.clone());
|
||||||
|
}
|
||||||
|
let _ = tx.send(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let st = state.lock().unwrap();
|
||||||
|
assert_eq!(st.lock_count, 0);
|
||||||
|
assert_eq!(st.free_count, 0);
|
||||||
|
assert!(rx.try_recv().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ignore_line_with_bad_parseable_data() {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
let state = Arc::new(Mutex::new(LtcState::new()));
|
||||||
|
let re = get_ltc_regex();
|
||||||
|
// The regex will match, but `from_regex` should fail to parse "1.2.3.4" as f64
|
||||||
|
let line = "[LOCK] 10:20:30:00 | 1.2.3.4fps";
|
||||||
|
|
||||||
|
// Simulate the processing logic
|
||||||
|
if let Some(caps) = re.captures(line) {
|
||||||
|
let arrival = Utc::now();
|
||||||
|
if let Some(frame) = LtcFrame::from_regex(&caps, arrival) {
|
||||||
|
{
|
||||||
|
let mut st = state.lock().unwrap();
|
||||||
|
st.update(frame.clone());
|
||||||
|
}
|
||||||
|
let _ = tx.send(frame);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Regex should have matched");
|
||||||
|
}
|
||||||
|
|
||||||
|
let st = state.lock().unwrap();
|
||||||
|
assert_eq!(st.lock_count, 0);
|
||||||
|
assert_eq!(st.free_count, 0);
|
||||||
|
assert!(rx.try_recv().is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue