use chrony_confile::ast::*;
use chrony_confile::ChronyConfig;
#[test]
fn parse_rtconutc() {
let config = ChronyConfig::parse("rtconutc").unwrap();
match &config.nodes[0] {
chrony_confile::ast::ConfigNode::Directive(d) => {
assert!(matches!(d.kind, DirectiveKind::RtcOnUtc));
}
_ => panic!("expected directive"),
}
}
#[test]
fn parse_lock_all() {
let config = ChronyConfig::parse("lock_all").unwrap();
match &config.nodes[0] {
chrony_confile::ast::ConfigNode::Directive(d) => {
assert!(matches!(d.kind, DirectiveKind::LockAll));
}
_ => panic!("expected directive"),
}
}
#[test]
fn parse_port() {
let config = ChronyConfig::parse("port 123").unwrap();
match &config.nodes[0] {
chrony_confile::ast::ConfigNode::Directive(d) => {
if let DirectiveKind::Port(c) = &d.kind { assert_eq!(c.port.get(), 123); }
else { panic!("wrong variant"); }
}
_ => panic!("expected directive"),
}
}
#[test]
fn parse_stratumweight() {
let config = ChronyConfig::parse("stratumweight 0.001").unwrap();
match &config.nodes[0] {
chrony_confile::ast::ConfigNode::Directive(d) => {
if let DirectiveKind::StratumWeight(c) = &d.kind { assert_eq!(c.distance, 0.001); }
else { panic!("wrong variant"); }
}
_ => panic!("expected directive"),
}
}
#[test]
fn parse_leapsecmode() {
let config = ChronyConfig::parse("leapsecmode system").unwrap();
match &config.nodes[0] {
chrony_confile::ast::ConfigNode::Directive(d) => {
assert!(matches!(d.kind, DirectiveKind::LeapSecMode(LeapSecMode::System)));
}
_ => panic!("expected directive"),
}
}
#[test]
fn parse_invalid_directive() {
assert!(ChronyConfig::parse("nonexistent").is_err());
}
#[test]
fn parse_makestep() {
let config = ChronyConfig::parse("makestep 1.0 3").unwrap();
match &config.nodes[0] {
chrony_confile::ast::ConfigNode::Directive(d) => {
if let DirectiveKind::MakeStep(c) = &d.kind {
assert_eq!(c.threshold, 1.0);
assert_eq!(c.limit, 3);
} else { panic!("wrong variant"); }
}
_ => panic!("expected directive"),
}
}
#[test]
fn parse_fallbackdrift() {
let config = ChronyConfig::parse("fallbackdrift 16 19").unwrap();
match &config.nodes[0] {
chrony_confile::ast::ConfigNode::Directive(d) => {
if let DirectiveKind::FallbackDrift(c) = &d.kind {
assert_eq!(c.min, 16);
assert_eq!(c.max, 19);
} else { panic!("wrong variant"); }
}
_ => panic!("expected directive"),
}
}
#[test]
fn parse_driftfile() {
let config = ChronyConfig::parse("driftfile /var/lib/chrony/drift").unwrap();
match &config.nodes[0] {
chrony_confile::ast::ConfigNode::Directive(d) => {
if let DirectiveKind::DriftFile(c) = &d.kind {
assert_eq!(c.path, "/var/lib/chrony/drift");
assert!(c.interval.is_none());
} else { panic!("wrong variant"); }
}
_ => panic!("expected directive"),
}
}
#[test]
fn parse_driftfile_with_interval() {
let config = ChronyConfig::parse("driftfile /var/lib/chrony/drift interval 3600").unwrap();
match &config.nodes[0] {
chrony_confile::ast::ConfigNode::Directive(d) => {
if let DirectiveKind::DriftFile(c) = &d.kind {
assert_eq!(c.interval, Some(3600));
} else { panic!("wrong variant"); }
}
_ => panic!("expected directive"),
}
}
#[test]
fn parse_comments_and_blank_lines() {
let input = "# Header\n\nserver ntp.example.com iburst # primary\n\npool pool.ntp.org";
let config = ChronyConfig::parse(input).unwrap();
assert!(config.nodes.len() >= 4);
assert!(matches!(config.nodes[0], chrony_confile::ast::ConfigNode::Comment(_)));
assert!(matches!(config.nodes[1], chrony_confile::ast::ConfigNode::BlankLine));
}
#[test]
fn parse_server_with_options() {
let config = ChronyConfig::parse("server ntp.example.com iburst prefer minpoll 4 maxpoll 8").unwrap();
match &config.nodes[0] {
chrony_confile::ast::ConfigNode::Directive(d) => {
if let DirectiveKind::Server(c) = &d.kind {
assert_eq!(c.hostname, "ntp.example.com");
assert!(c.iburst);
assert!(c.prefer());
assert_eq!(c.minpoll.get(), 4);
assert_eq!(c.maxpoll.get(), 8);
} else { panic!("wrong variant"); }
}
_ => panic!("expected directive"),
}
}
#[test]
fn parse_pool() {
let config = ChronyConfig::parse("pool pool.ntp.org iburst maxsources 6").unwrap();
match &config.nodes[0] {
chrony_confile::ast::ConfigNode::Directive(d) => {
if let DirectiveKind::Pool(c) = &d.kind {
assert_eq!(c.source.hostname, "pool.ntp.org");
assert!(c.source.iburst);
} else { panic!("wrong variant"); }
}
_ => panic!("expected directive"),
}
}
#[test]
fn parse_tempcomp_coefficients() {
let config = ChronyConfig::parse("tempcomp /sys/class/hwmon 4.0 25.0 0.0 1.0 0.0").unwrap();
match &config.nodes[0] {
chrony_confile::ast::ConfigNode::Directive(d) => {
assert!(matches!(d.kind, DirectiveKind::TempComp(TempCompConfig::Coefficients { .. })));
}
_ => panic!("expected directive"),
}
}
#[test]
fn parse_tempcomp_pointfile() {
let config = ChronyConfig::parse("tempcomp /sys/class/hwmon 4.0 /etc/points.txt").unwrap();
match &config.nodes[0] {
chrony_confile::ast::ConfigNode::Directive(d) => {
assert!(matches!(d.kind, DirectiveKind::TempComp(TempCompConfig::PointFile { .. })));
}
_ => panic!("expected directive"),
}
}
#[test]
fn parse_allow() {
let config = ChronyConfig::parse("allow 192.168.0.0/16").unwrap();
match &config.nodes[0] {
chrony_confile::ast::ConfigNode::Directive(d) => {
if let DirectiveKind::Allow(c) = &d.kind {
assert_eq!(c.subnet.as_deref(), Some("192.168.0.0/16"));
} else { panic!("wrong variant"); }
}
_ => panic!("expected directive"),
}
}
#[test]
fn parse_ratelimit() {
let config = ChronyConfig::parse("ratelimit interval 4 burst 16 leak 2").unwrap();
match &config.nodes[0] {
chrony_confile::ast::ConfigNode::Directive(d) => {
if let DirectiveKind::RateLimit(c) = &d.kind {
assert_eq!(c.interval, 4);
assert_eq!(c.burst, 16);
assert_eq!(c.leak, 2);
} else { panic!("wrong variant"); }
}
_ => panic!("expected directive"),
}
}
#[test]
fn parse_log() {
let config = ChronyConfig::parse("log tracking measurements statistics").unwrap();
match &config.nodes[0] {
chrony_confile::ast::ConfigNode::Directive(d) => {
if let DirectiveKind::Log(c) = &d.kind {
assert!(c.tracking);
assert!(c.measurements);
assert!(c.statistics);
} else { panic!("wrong variant"); }
}
_ => panic!("expected directive"),
}
}
#[test]
fn parse_smoothtime() {
let config = ChronyConfig::parse("smoothtime 400.0 0.001 leaponly").unwrap();
match &config.nodes[0] {
chrony_confile::ast::ConfigNode::Directive(d) => {
if let DirectiveKind::SmoothTime(c) = &d.kind {
assert_eq!(c.max_freq, 400.0);
assert_eq!(c.max_wander, 0.001);
assert!(c.leap_only);
} else { panic!("wrong variant"); }
}
_ => panic!("expected directive"),
}
}
#[test]
fn roundtrip_simple() {
let input = "rtconutc\n";
let config = ChronyConfig::parse(input).unwrap();
assert_eq!(config.to_string(), input);
}
#[test]
fn roundtrip_server() {
let input = "server ntp.example.com iburst prefer\n";
let config = ChronyConfig::parse(input).unwrap();
assert_eq!(config.to_string(), input);
}
#[test]
fn parse_real_world_sample() {
let input = "\
# Use public NTP servers
server 0.pool.ntp.org iburst
server 1.pool.ntp.org iburst
# Drift file
driftfile /var/lib/chrony/drift
# Allow NTP client access from local network
allow 192.168.0.0/16
# Serve time even if not synchronized
local stratum 10
";
let config = ChronyConfig::parse(input).unwrap();
let output = config.to_string();
assert!(output.contains("server 0.pool.ntp.org iburst"));
assert!(output.contains("driftfile /var/lib/chrony/drift"));
assert!(output.contains("allow 192.168.0.0/16"));
assert!(output.contains("# Use public NTP servers"));
}
#[test]
fn roundtrip_full() {
let input = "\
server ntp1.example.com iburst prefer minpoll 4
server ntp2.example.com iburst
pool pool.ntp.org maxsources 6
driftfile /var/lib/chrony/drift interval 3600
makestep 1.0 3
maxdistance 5.0
allow 10.0.0.0/8
rtconutc
log tracking measurements statistics
";
let config1 = ChronyConfig::parse(input).unwrap();
let output = config1.to_string();
let config2 = ChronyConfig::parse(&output).unwrap();
let kinds1: Vec<&DirectiveKind> = config1
.nodes
.iter()
.filter_map(|n| match n {
ConfigNode::Directive(d) => Some(&d.kind),
_ => None,
})
.collect();
let kinds2: Vec<&DirectiveKind> = config2
.nodes
.iter()
.filter_map(|n| match n {
ConfigNode::Directive(d) => Some(&d.kind),
_ => None,
})
.collect();
assert_eq!(kinds1, kinds2);
}
#[test]
fn test_builder() {
let directive = chrony_confile::builder::ServerBuilder::new("ntp.example.com")
.iburst()
.prefer()
.minpoll(chrony_confile::values::PollInterval::new(4).unwrap())
.build();
if let DirectiveKind::Server(c) = &directive.kind {
assert_eq!(c.hostname, "ntp.example.com");
assert!(c.iburst);
assert!(c.prefer());
assert_eq!(c.minpoll.get(), 4);
} else {
panic!("wrong variant");
}
}
#[test]
fn test_validator_nts_certs() {
let input = "ntsservercert a.crt\nntsservercert b.crt\nntsserverkey a.key\n";
let config = ChronyConfig::parse(input).unwrap();
let errors = chrony_confile::validator::Validate::validate(&config);
assert!(!errors.is_empty()); }
#[test]
fn test_source_file_parsing() {
use chrony_confile::ast::source_file::*;
let input = "\
server ntp1.example.com iburst
server ntp2.example.com
# comment
pool pool.ntp.org iburst maxsources 6
badline this should be skipped
server ntp3.example.com prefer
";
let sf = SourceFile::parse(input);
let entries: Vec<_> = sf
.nodes
.iter()
.filter(|n| matches!(n, SourceNode::Entry(_)))
.collect();
assert!(entries.len() >= 3); }