[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:
@@ -1400,4 +1400,298 @@ mod tests {
|
||||
assert_eq!(state.reset_seqs.ft2_decode_reset_seq, 7);
|
||||
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 { .. }
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user