[test](trx-server): add Tier 6 unit tests for rig_task helpers

Existing 4 tests covered only invalidate_main_decoder_windows_on_freq_change for PKT/DIG/WFM. Add coverage for the remaining modes that the freq-change table cares about (USB, CW, CWR), the no-op modes (AM/LSB/FM/SAM), the S-meter offset table, lock-state precedence, TX meter extraction, RX/TX delta detection, and the desired_machine_state transitions (Disconnected / Initializing / PoweredOff / Ready / Transmitting).

19 new tests; trx-server suite now reports 134 passed (was 130).

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-05-03 20:19:23 +02:00
parent 5919d4d4c0
commit 72b3c02770
+294
View File
@@ -1400,4 +1400,298 @@ mod tests {
assert_eq!(state.reset_seqs.ft2_decode_reset_seq, 7); assert_eq!(state.reset_seqs.ft2_decode_reset_seq, 7);
assert_eq!(state.reset_seqs.wspr_decode_reset_seq, 8); assert_eq!(state.reset_seqs.wspr_decode_reset_seq, 8);
} }
#[test]
fn usb_freq_change_invalidates_ftx_and_wspr_only() {
let mut state = RigState::new_uninitialized();
state.apply_mode(RigMode::USB);
let prev_freq_hz = state.status.freq.hz;
state.apply_freq(Freq {
hz: prev_freq_hz + 1_500,
});
invalidate_main_decoder_windows_on_freq_change(&mut state, prev_freq_hz);
// No effect on PKT, HF-APRS, CW.
assert_eq!(state.reset_seqs.aprs_decode_reset_seq, 0);
assert_eq!(state.reset_seqs.hf_aprs_decode_reset_seq, 0);
assert_eq!(state.reset_seqs.cw_decode_reset_seq, 0);
// FT8/FT4/FT2/WSPR all bumped.
assert_eq!(state.reset_seqs.ft8_decode_reset_seq, 1);
assert_eq!(state.reset_seqs.ft4_decode_reset_seq, 1);
assert_eq!(state.reset_seqs.ft2_decode_reset_seq, 1);
assert_eq!(state.reset_seqs.wspr_decode_reset_seq, 1);
}
#[test]
fn cw_freq_change_invalidates_cw_only() {
for mode in [RigMode::CW, RigMode::CWR] {
let mut state = RigState::new_uninitialized();
state.apply_mode(mode.clone());
let prev_freq_hz = state.status.freq.hz;
state.apply_freq(Freq {
hz: prev_freq_hz + 100,
});
invalidate_main_decoder_windows_on_freq_change(&mut state, prev_freq_hz);
assert_eq!(state.reset_seqs.cw_decode_reset_seq, 1, "mode={:?}", mode);
assert_eq!(state.reset_seqs.aprs_decode_reset_seq, 0);
assert_eq!(state.reset_seqs.hf_aprs_decode_reset_seq, 0);
assert_eq!(state.reset_seqs.ft8_decode_reset_seq, 0);
assert_eq!(state.reset_seqs.ft4_decode_reset_seq, 0);
assert_eq!(state.reset_seqs.ft2_decode_reset_seq, 0);
assert_eq!(state.reset_seqs.wspr_decode_reset_seq, 0);
}
}
#[test]
fn am_lsb_fm_freq_change_does_not_touch_main_decoders() {
// Modes the freq-change table intentionally ignores: any change leaves
// every decoder reset_seq untouched.
for mode in [RigMode::AM, RigMode::LSB, RigMode::FM, RigMode::SAM] {
let mut state = RigState::new_uninitialized();
state.apply_mode(mode.clone());
state.reset_seqs.cw_decode_reset_seq = 99;
state.reset_seqs.ft8_decode_reset_seq = 99;
let prev_freq_hz = state.status.freq.hz;
state.apply_freq(Freq {
hz: prev_freq_hz + 5_000,
});
invalidate_main_decoder_windows_on_freq_change(&mut state, prev_freq_hz);
assert_eq!(state.reset_seqs.cw_decode_reset_seq, 99, "mode={:?}", mode);
assert_eq!(state.reset_seqs.ft8_decode_reset_seq, 99, "mode={:?}", mode);
}
}
#[test]
fn map_signal_strength_uses_fm_offset_for_fm_modes() {
// FM-family modes start at -120 dBm at raw=0; +6 dBm per raw step.
for mode in [RigMode::FM, RigMode::WFM, RigMode::AIS, RigMode::VDES] {
assert_eq!(map_signal_strength(&mode, 0), -120.0, "mode={:?}", mode);
assert_eq!(map_signal_strength(&mode, 5), -90.0, "mode={:?}", mode);
assert_eq!(map_signal_strength(&mode, 15), -30.0, "mode={:?}", mode);
}
}
#[test]
fn map_signal_strength_uses_default_offset_for_ssb_and_cw() {
// Everything else starts at -127 dBm at raw=0.
for mode in [
RigMode::USB,
RigMode::LSB,
RigMode::CW,
RigMode::CWR,
RigMode::AM,
RigMode::SAM,
RigMode::DIG,
RigMode::PKT,
] {
assert_eq!(map_signal_strength(&mode, 0), -127.0, "mode={:?}", mode);
assert_eq!(map_signal_strength(&mode, 1), -121.0, "mode={:?}", mode);
assert_eq!(map_signal_strength(&mode, 15), -37.0, "mode={:?}", mode);
}
}
#[test]
fn lock_state_falls_back_to_status_when_no_control_override() {
let mut state = RigState::new_uninitialized();
state.control.lock = None;
state.status.lock = Some(true);
assert!(lock_state_from(&state));
state.status.lock = Some(false);
assert!(!lock_state_from(&state));
}
#[test]
fn lock_state_control_override_takes_precedence() {
let mut state = RigState::new_uninitialized();
state.control.lock = Some(false);
state.status.lock = Some(true);
// Control wins over status.
assert!(!lock_state_from(&state));
state.control.lock = Some(true);
state.status.lock = Some(false);
assert!(lock_state_from(&state));
}
#[test]
fn lock_state_defaults_to_false_when_neither_set() {
let mut state = RigState::new_uninitialized();
state.control.lock = None;
state.status.lock = None;
assert!(!lock_state_from(&state));
}
#[test]
fn tx_meter_parts_returns_quad_of_nones_when_no_tx_status() {
assert_eq!(tx_meter_parts(None), (None, None, None, None));
}
#[test]
fn tx_meter_parts_extracts_fields_in_order() {
let tx = RigTxStatus {
power: Some(50),
limit: Some(100),
swr: Some(1.5),
alc: Some(7),
};
assert_eq!(
tx_meter_parts(Some(&tx)),
(Some(50), Some(100), Some(1.5), Some(7))
);
}
#[test]
fn meters_changed_detects_rx_signal_change() {
let mut a = RigState::new_uninitialized();
let mut b = a.clone();
a.status.rx = Some(RigRxStatus { sig: Some(-90.0) });
b.status.rx = Some(RigRxStatus { sig: Some(-50.0) });
assert!(meters_changed(&a, &b));
}
#[test]
fn meters_changed_detects_tx_power_change() {
let mut a = RigState::new_uninitialized();
let mut b = a.clone();
a.status.tx = Some(RigTxStatus {
power: Some(10),
limit: None,
swr: None,
alc: None,
});
b.status.tx = Some(RigTxStatus {
power: Some(80),
limit: None,
swr: None,
alc: None,
});
assert!(meters_changed(&a, &b));
}
#[test]
fn meters_changed_false_when_unchanged() {
let mut a = RigState::new_uninitialized();
a.status.rx = Some(RigRxStatus { sig: Some(-70.0) });
a.status.tx = Some(RigTxStatus {
power: Some(25),
limit: Some(100),
swr: Some(1.2),
alc: None,
});
let b = a.clone();
assert!(!meters_changed(&a, &b));
}
fn sample_rig_info() -> trx_core::rig::RigInfo {
use trx_core::radio::freq::Band;
use trx_core::rig::{RigAccessMethod, RigCapabilities, RigInfo};
RigInfo {
manufacturer: "Test".into(),
model: "Dummy".into(),
revision: "0".into(),
capabilities: RigCapabilities {
min_freq_step_hz: 1,
supported_bands: vec![Band {
low_hz: 7_000_000,
high_hz: 7_200_000,
tx_allowed: true,
}],
supported_modes: vec![RigMode::USB],
num_vfos: 1,
lock: false,
lockable: true,
attenuator: false,
preamp: false,
rit: false,
rpt: false,
split: false,
tx: true,
tx_limit: false,
vfo_switch: false,
filter_controls: false,
signal_meter: true,
},
access: RigAccessMethod::Tcp {
addr: "127.0.0.1:0".into(),
},
}
}
#[test]
fn desired_machine_state_disconnected_when_no_rig_info() {
let state = RigState::new_uninitialized();
assert!(state.rig_info.is_none());
assert!(matches!(
desired_machine_state(&state),
RigMachineState::Disconnected
));
}
#[test]
fn desired_machine_state_initializing_when_rig_info_but_not_initialized() {
let mut state = RigState::new_uninitialized();
state.rig_info = Some(sample_rig_info());
// initialized stays false.
assert!(matches!(
desired_machine_state(&state),
RigMachineState::Initializing { rig_info: Some(_) }
));
}
#[test]
fn desired_machine_state_powered_off_when_control_disabled() {
let mut state = RigState::new_uninitialized();
state.rig_info = Some(sample_rig_info());
state.initialized = true;
state.control.enabled = Some(false);
assert!(matches!(
desired_machine_state(&state),
RigMachineState::PoweredOff { .. }
));
}
#[test]
fn desired_machine_state_ready_when_initialized_and_not_transmitting() {
let mut state = RigState::new_uninitialized();
state.rig_info = Some(sample_rig_info());
state.initialized = true;
// Default control.enabled is Some(false) → PoweredOff. Lift it to make
// the ready path observable.
state.control.enabled = Some(true);
state.status.tx_en = false;
assert!(matches!(
desired_machine_state(&state),
RigMachineState::Ready(_)
));
}
#[test]
fn desired_machine_state_transmitting_when_tx_en() {
let mut state = RigState::new_uninitialized();
state.rig_info = Some(sample_rig_info());
state.initialized = true;
state.control.enabled = Some(true);
state.status.tx_en = true;
assert!(matches!(
desired_machine_state(&state),
RigMachineState::Transmitting(_)
));
}
#[test]
fn desired_machine_state_powered_off_is_default_for_initialized_rig() {
// Documents the default-Some(false) behaviour of RigControl::enabled.
let mut state = RigState::new_uninitialized();
state.rig_info = Some(sample_rig_info());
state.initialized = true;
// control.enabled stays at its default Some(false).
assert!(matches!(
desired_machine_state(&state),
RigMachineState::PoweredOff { .. }
));
}
} }