#[cfg(test)]
mod tests {
use crate::parser::swss_parser::*;
use crate::record::LogRecord;
use std::sync::Arc;
fn parse_line(line: &str) -> Option<LogRecord> {
let source = Arc::from("test");
let loader = Arc::from("test-loader");
SwssParser::parse_shared(line, &source, &loader, 1)
}
#[test]
fn test_pure_message() {
let r = parse_line("2025-11-13.22:19:03.248563|recording started").unwrap();
assert_eq!(r.message, "recording started");
assert!(r.component_name.is_none());
assert!(r.context.is_none());
assert!(r.function.is_none());
assert_eq!(
r.timestamp.format("%Y-%m-%d %H:%M:%S%.6f").to_string(),
"2025-11-13 22:19:03.248563"
);
}
#[test]
fn test_table_key_set_kv() {
let r = parse_line("2025-11-13.22:19:35.512358|SWITCH_TABLE:switch|SET|ecmp_hash_offset:0|ecmp_hash_seed:0|fdb_aging_time:600").unwrap();
assert_eq!(r.component_name.as_deref(), Some("SWITCH_TABLE"));
assert_eq!(r.context.as_deref(), Some("switch"));
assert_eq!(r.function.as_deref(), Some("SET"));
assert_eq!(
r.message,
"ecmp_hash_offset:0|ecmp_hash_seed:0|fdb_aging_time:600"
);
}
#[test]
fn test_table_key_port() {
let r = parse_line("2025-11-13.22:19:35.523199|PORT_TABLE:Ethernet248|SET|admin_status:up|alias:etp31|index:31").unwrap();
assert_eq!(r.component_name.as_deref(), Some("PORT_TABLE"));
assert_eq!(r.context.as_deref(), Some("Ethernet248"));
assert_eq!(r.function.as_deref(), Some("SET"));
assert_eq!(r.message, "admin_status:up|alias:etp31|index:31");
}
#[test]
fn test_table_subkey_set() {
let r = parse_line(
"2025-11-13.22:19:38.096435|FLEX_COUNTER_TABLE|PG_DROP|SET|FLEX_COUNTER_STATUS:enable",
)
.unwrap();
assert_eq!(r.component_name.as_deref(), Some("FLEX_COUNTER_TABLE"));
assert_eq!(r.context.as_deref(), Some("PG_DROP"));
assert_eq!(r.function.as_deref(), Some("SET"));
assert_eq!(r.message, "FLEX_COUNTER_STATUS:enable");
}
#[test]
fn test_route_del_ipv6() {
let r = parse_line("2025-11-13.22:23:57.885443|ROUTE_TABLE:fd00::/80|DEL").unwrap();
assert_eq!(r.component_name.as_deref(), Some("ROUTE_TABLE"));
assert_eq!(r.context.as_deref(), Some("fd00::/80"));
assert_eq!(r.function.as_deref(), Some("DEL"));
assert_eq!(r.message, "");
}
#[test]
fn test_neigh_table_key_with_colons() {
let r = parse_line("2025-11-13.23:31:35.533798|NEIGH_TABLE:eth0:192.168.0.221|SET|neigh:00:15:5d:a6:3c:09|family:IPv4").unwrap();
assert_eq!(r.component_name.as_deref(), Some("NEIGH_TABLE"));
assert_eq!(r.context.as_deref(), Some("eth0:192.168.0.221"));
assert_eq!(r.function.as_deref(), Some("SET"));
assert_eq!(r.message, "neigh:00:15:5d:a6:3c:09|family:IPv4");
}
#[test]
fn test_route_set_with_kv() {
let r = parse_line("2025-11-13.23:29:44.847720|ROUTE_TABLE:fe80::/64|SET|protocol:kernel|nexthop:::|ifname:Bridge|weight:1").unwrap();
assert_eq!(r.component_name.as_deref(), Some("ROUTE_TABLE"));
assert_eq!(r.context.as_deref(), Some("fe80::/64"));
assert_eq!(r.function.as_deref(), Some("SET"));
assert!(r.message.contains("protocol:kernel"));
}
#[test]
fn test_invalid_timestamp() {
assert!(parse_line("not-a-timestamp|something").is_none());
assert!(parse_line("").is_none());
assert!(parse_line("2025").is_none());
assert!(parse_line("2025-11-13.22:19:03.248563").is_none());
assert!(parse_line("2025-13-13.22:19:03.248563|test").is_none());
assert!(parse_line("2025/11/13.22:19:03.248563|test").is_none());
}
#[test]
fn test_timestamp_parsing() {
let r = parse_line("2025-11-13.22:19:03.248563|test").unwrap();
assert_eq!(r.timestamp.year(), 2025);
assert_eq!(r.timestamp.month(), 11);
assert_eq!(r.timestamp.day(), 13);
assert_eq!(r.timestamp.hour(), 22);
assert_eq!(r.timestamp.minute(), 19);
assert_eq!(r.timestamp.second(), 3);
}
#[test]
fn test_parser_trait() {
let parser = SwssParser::new();
let r = parser
.parse(
"2025-11-13.22:19:35.512358|SWITCH_TABLE:switch|SET|k:v",
"src",
"loader",
42,
)
.unwrap();
assert_eq!(r.id, 42);
assert_eq!(r.component_name.as_deref(), Some("SWITCH_TABLE"));
}
#[test]
fn test_perf_parse_100k() {
let line = "2025-11-13.22:19:35.523199|PORT_TABLE:Ethernet248|SET|admin_status:up|alias:etp31|index:31|lanes:528,529,530,531|mtu:9100|speed:400000";
let source = Arc::from("bench");
let loader = Arc::from("bench");
let start = std::time::Instant::now();
let n = 100_000;
for i in 0..n {
let _ = SwssParser::parse_shared(line, &source, &loader, i);
}
let elapsed = start.elapsed();
let per_record_ns = elapsed.as_nanos() / n as u128;
let throughput = n as f64 / elapsed.as_secs_f64();
println!(
"SWSS parser: {}ns/record, {:.0} records/sec ({} records in {:?})",
per_record_ns, throughput, n, elapsed
);
assert!(
throughput > 50_000.0,
"Throughput {:.0}/sec below 50K/sec minimum (release target: 1M/sec)",
throughput
);
}
use chrono::{Datelike, Timelike};
#[test]
fn test_fractional_micros() {
assert_eq!(parse_fractional_micros("248563"), 248563);
assert_eq!(parse_fractional_micros("1"), 100000);
assert_eq!(parse_fractional_micros("12"), 120000);
assert_eq!(parse_fractional_micros("123456789"), 123456); assert_eq!(parse_fractional_micros(""), 0); assert_eq!(parse_fractional_micros("abc"), 0); assert_eq!(parse_fractional_micros("12abc"), 120000); }
#[test]
fn test_is_known_op() {
assert!(is_known_op("SET"));
assert!(is_known_op("DEL"));
assert!(is_known_op("HSET"));
assert!(!is_known_op("UNKNOWN"));
assert!(!is_known_op("PG_DROP"));
}
#[test]
fn test_expanded_table_key_op_kv() {
let r = parse_line("2025-01-15.10:30:45.123456|ROUTE_TABLE:10.0.0.0/24|SET|nexthop:10.1.1.1|ifname:Ethernet0").unwrap();
let expanded = r.expanded.as_ref().unwrap();
assert_eq!(expanded[0].label, "Operation");
assert_eq!(
expanded[0].value,
crate::record::ExpandedValue::Text("SET".to_string())
);
assert_eq!(expanded[1].label, "Table");
assert_eq!(
expanded[1].value,
crate::record::ExpandedValue::Text("ROUTE_TABLE".to_string())
);
assert_eq!(expanded[2].label, "Key");
assert_eq!(
expanded[2].value,
crate::record::ExpandedValue::Text("10.0.0.0/24".to_string())
);
assert_eq!(expanded[3].label, "Attributes");
if let crate::record::ExpandedValue::KeyValue(pairs) = &expanded[3].value {
assert_eq!(pairs.len(), 2);
assert_eq!(pairs[0].0, "nexthop");
assert_eq!(
pairs[0].1,
crate::record::ExpandedValue::Text("10.1.1.1".to_string())
);
assert_eq!(pairs[1].0, "ifname");
assert_eq!(
pairs[1].1,
crate::record::ExpandedValue::Text("Ethernet0".to_string())
);
} else {
panic!("expected KeyValue for Attributes");
}
}
#[test]
fn test_expanded_del_no_kv() {
let r = parse_line("2025-01-15.10:30:45.123456|ROUTE_TABLE:10.0.0.0/24|DEL").unwrap();
let expanded = r.expanded.as_ref().unwrap();
assert_eq!(expanded.len(), 3); assert_eq!(expanded[0].label, "Operation");
assert_eq!(
expanded[0].value,
crate::record::ExpandedValue::Text("DEL".to_string())
);
}
#[test]
fn test_expanded_pure_message_none() {
let r = parse_line("2025-01-15.10:30:45.123456|recording started").unwrap();
assert!(r.expanded.is_none());
}
#[test]
fn test_expanded_table_subkey_op() {
let r =
parse_line("2025-01-15.10:30:45.123456|FLEX_COUNTER_TABLE|PG_DROP|SET|k:v").unwrap();
let expanded = r.expanded.as_ref().unwrap();
assert_eq!(
expanded[0].value,
crate::record::ExpandedValue::Text("SET".to_string())
);
assert_eq!(
expanded[1].value,
crate::record::ExpandedValue::Text("FLEX_COUNTER_TABLE".to_string())
);
assert_eq!(
expanded[2].value,
crate::record::ExpandedValue::Text("PG_DROP".to_string())
);
}
}