[chore](trx-rs): add Gitea Actions CI pipeline #2
@@ -638,7 +638,7 @@ mod tests {
|
||||
for (i, &ch) in b"N0CALL".iter().enumerate() {
|
||||
addr[i] = ch << 1;
|
||||
}
|
||||
addr[6] = (0 << 1) | 1; // SSID=0, last=true
|
||||
addr[6] = 1; // SSID=0, last=true
|
||||
|
||||
let decoded = decode_ax25_address(&addr, 0);
|
||||
assert_eq!(decoded.call, "N0CALL");
|
||||
@@ -652,7 +652,7 @@ mod tests {
|
||||
for (i, &ch) in b"SP2SJG".iter().enumerate() {
|
||||
addr[i] = ch << 1;
|
||||
}
|
||||
addr[6] = (5 << 1) | 0; // SSID=5, last=false
|
||||
addr[6] = 5 << 1; // SSID=5, last=false
|
||||
|
||||
let decoded = decode_ax25_address(&addr, 0);
|
||||
assert_eq!(decoded.call, "SP2SJG");
|
||||
@@ -667,7 +667,7 @@ mod tests {
|
||||
for (i, &ch) in b"W1AW ".iter().enumerate() {
|
||||
addr[i] = ch << 1;
|
||||
}
|
||||
addr[6] = (0 << 1) | 1;
|
||||
addr[6] = 1;
|
||||
|
||||
let decoded = decode_ax25_address(&addr, 0);
|
||||
assert_eq!(decoded.call, "W1AW");
|
||||
@@ -691,8 +691,8 @@ mod tests {
|
||||
for &ch in src_bytes.as_bytes().iter().take(6) {
|
||||
frame.push(ch << 1);
|
||||
}
|
||||
frame.push((0 << 1) | 1); // SSID=0, last=true
|
||||
// Control + PID
|
||||
frame.push(1); // SSID=0, last=true
|
||||
// Control + PID
|
||||
frame.push(0x03); // UI frame
|
||||
frame.push(0xF0); // No layer-3 protocol
|
||||
// Info field
|
||||
|
||||
@@ -632,32 +632,17 @@ impl Candidate {
|
||||
}
|
||||
let segment = usize::from((block_b & 0x0003) as u8);
|
||||
let di = ((block_b >> 2) & 0x1) != 0;
|
||||
match segment {
|
||||
0 => {
|
||||
if self.state.dynamic_pty != Some(di) {
|
||||
self.state.dynamic_pty = Some(di);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
if self.state.compressed != Some(di) {
|
||||
self.state.compressed = Some(di);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
if self.state.artificial_head != Some(di) {
|
||||
self.state.artificial_head = Some(di);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
if self.state.stereo != Some(di) {
|
||||
self.state.stereo = Some(di);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
let di_flag = Some(di);
|
||||
let slot = match segment {
|
||||
0 => &mut self.state.dynamic_pty,
|
||||
1 => &mut self.state.compressed,
|
||||
2 => &mut self.state.artificial_head,
|
||||
3 => &mut self.state.stereo,
|
||||
_ => unreachable!("segment is masked to two bits"),
|
||||
};
|
||||
if *slot != di_flag {
|
||||
*slot = di_flag;
|
||||
changed = true;
|
||||
}
|
||||
let [b0, b1] = block_d.to_be_bytes();
|
||||
self.ps_bytes[segment * 2] = sanitize_text_byte(b0);
|
||||
@@ -1458,9 +1443,9 @@ mod tests {
|
||||
}
|
||||
|
||||
// BPSK modulate onto the 57 kHz subcarrier.
|
||||
for t in 0..n {
|
||||
for (t, sample) in shaped.iter_mut().enumerate().take(n) {
|
||||
let phase = TAU * RDS_SUBCARRIER_HZ * t as f32 / sample_rate;
|
||||
shaped[t] *= phase.cos();
|
||||
*sample *= phase.cos();
|
||||
}
|
||||
shaped
|
||||
}
|
||||
|
||||
@@ -134,9 +134,7 @@ mod tests {
|
||||
.flat_map(|&b| (0..8).rev().map(move |i| (b >> i) & 1))
|
||||
.collect();
|
||||
// Append wrong CRC
|
||||
for _ in 0..16 {
|
||||
bits.push(0);
|
||||
}
|
||||
bits.resize(bits.len() + 16, 0);
|
||||
assert!(!check_crc16(&bits));
|
||||
}
|
||||
|
||||
|
||||
@@ -346,8 +346,8 @@ mod tests {
|
||||
write_bits(&mut bits, 12, 32, 123456); // source_id
|
||||
write_bits(&mut bits, 44, 11, 20); // data_count = 20
|
||||
// Fill some payload
|
||||
for i in 55..75 {
|
||||
bits[i] = (i % 2) as u8;
|
||||
for (i, bit) in bits.iter_mut().enumerate().take(75).skip(55) {
|
||||
*bit = (i % 2) as u8;
|
||||
}
|
||||
append_crc(&mut bits);
|
||||
|
||||
|
||||
@@ -374,8 +374,8 @@ mod tests {
|
||||
let (y, m, d, h, mi, _) = unix_to_utc(1775055000);
|
||||
assert_eq!(y, 2026);
|
||||
// Just verify reasonable values without asserting exact date.
|
||||
assert!(m >= 1 && m <= 12);
|
||||
assert!(d >= 1 && d <= 31);
|
||||
assert!((1..=12).contains(&m));
|
||||
assert!((1..=31).contains(&d));
|
||||
assert!(h < 24);
|
||||
assert!(mi < 60);
|
||||
}
|
||||
|
||||
@@ -161,10 +161,8 @@ mod tests {
|
||||
|
||||
for line_idx in 0..20 {
|
||||
let mut line = vec![1.0f32; spl];
|
||||
for j in pulse_start..pulse_start + pw {
|
||||
if j < spl {
|
||||
line[j] = 0.0;
|
||||
}
|
||||
for slot in line.iter_mut().skip(pulse_start).take(pw) {
|
||||
*slot = 0.0;
|
||||
}
|
||||
let result = det.process(&line);
|
||||
if let Some(offset) = result {
|
||||
|
||||
@@ -483,7 +483,7 @@ mod tests {
|
||||
let c4 = idx27(b'T');
|
||||
let c5 = idx27(b' ');
|
||||
let n1 = ((c0 * 36 + c1) * 10 + c2) * 27u32.pow(3) + c3 * 27u32.pow(2) + c4 * 27 + c5;
|
||||
let m1 = (179 - 10 * 5 - 2) * 180 + 10 * 13 + 0; // FN20
|
||||
let m1 = (179 - 10 * 5 - 2) * 180 + 10 * 13; // FN20 (final term is 0)
|
||||
let power_code = 37u32;
|
||||
|
||||
let mut input_bits = [0u8; NBITS];
|
||||
@@ -530,8 +530,8 @@ mod tests {
|
||||
fn interleave_deinterleave_roundtrip() {
|
||||
// Create a sequence of distinguishable values
|
||||
let mut original = [0u8; NSYMS];
|
||||
for i in 0..NSYMS {
|
||||
original[i] = (i % 256) as u8;
|
||||
for (i, slot) in original.iter_mut().enumerate() {
|
||||
*slot = (i % 256) as u8;
|
||||
}
|
||||
|
||||
let interleaved = interleave(&original);
|
||||
|
||||
@@ -356,59 +356,6 @@ async fn run_single_rig_audio_client(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{resolve_audio_addr, AudioConnectConfig};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn resolve_audio_addr_prefers_fixed_url() {
|
||||
let mut rig_connect = HashMap::new();
|
||||
rig_connect.insert(
|
||||
"home-hf".to_string(),
|
||||
AudioConnectConfig::fixed("audio.example.com:4700".to_string()),
|
||||
);
|
||||
|
||||
let addr = resolve_audio_addr(
|
||||
"home-hf",
|
||||
Some(4531),
|
||||
&rig_connect,
|
||||
&AudioConnectConfig::from_host_port("control.example.com".to_string(), 4531),
|
||||
);
|
||||
assert_eq!(addr, "audio.example.com:4700");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_audio_addr_uses_advertised_port_with_remote_host() {
|
||||
let mut rig_connect = HashMap::new();
|
||||
rig_connect.insert(
|
||||
"home-hf".to_string(),
|
||||
AudioConnectConfig::from_host_port("control.example.com".to_string(), 4531),
|
||||
);
|
||||
|
||||
let addr = resolve_audio_addr(
|
||||
"home-hf",
|
||||
Some(4600),
|
||||
&rig_connect,
|
||||
&AudioConnectConfig::from_host_port("fallback.example.com".to_string(), 4531),
|
||||
);
|
||||
assert_eq!(addr, "control.example.com:4600");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_audio_addr_falls_back_to_default_port() {
|
||||
let rig_connect = HashMap::new();
|
||||
|
||||
let addr = resolve_audio_addr(
|
||||
"home-hf",
|
||||
None,
|
||||
&rig_connect,
|
||||
&AudioConnectConfig::from_host_port("fallback.example.com".to_string(), 4531),
|
||||
);
|
||||
assert_eq!(addr, "fallback.example.com:4531");
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a single TCP connection for one rig. Similar to `handle_audio_connection`
|
||||
/// but publishes to per-rig channels directly and mirrors to global when selected.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -767,3 +714,56 @@ async fn handle_single_rig_connection(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{resolve_audio_addr, AudioConnectConfig};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn resolve_audio_addr_prefers_fixed_url() {
|
||||
let mut rig_connect = HashMap::new();
|
||||
rig_connect.insert(
|
||||
"home-hf".to_string(),
|
||||
AudioConnectConfig::fixed("audio.example.com:4700".to_string()),
|
||||
);
|
||||
|
||||
let addr = resolve_audio_addr(
|
||||
"home-hf",
|
||||
Some(4531),
|
||||
&rig_connect,
|
||||
&AudioConnectConfig::from_host_port("control.example.com".to_string(), 4531),
|
||||
);
|
||||
assert_eq!(addr, "audio.example.com:4700");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_audio_addr_uses_advertised_port_with_remote_host() {
|
||||
let mut rig_connect = HashMap::new();
|
||||
rig_connect.insert(
|
||||
"home-hf".to_string(),
|
||||
AudioConnectConfig::from_host_port("control.example.com".to_string(), 4531),
|
||||
);
|
||||
|
||||
let addr = resolve_audio_addr(
|
||||
"home-hf",
|
||||
Some(4600),
|
||||
&rig_connect,
|
||||
&AudioConnectConfig::from_host_port("fallback.example.com".to_string(), 4531),
|
||||
);
|
||||
assert_eq!(addr, "control.example.com:4600");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_audio_addr_falls_back_to_default_port() {
|
||||
let rig_connect = HashMap::new();
|
||||
|
||||
let addr = resolve_audio_addr(
|
||||
"home-hf",
|
||||
None,
|
||||
&rig_connect,
|
||||
&AudioConnectConfig::from_host_port("fallback.example.com".to_string(), 4531),
|
||||
);
|
||||
assert_eq!(addr, "fallback.example.com:4531");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1110,36 +1110,40 @@ url = "remote.example.com:4530"
|
||||
|
||||
#[test]
|
||||
fn test_validate_rejects_duplicate_remote_names() {
|
||||
let mut config = ClientConfig::default();
|
||||
config.remotes = vec![
|
||||
RemoteEntry {
|
||||
name: "dup".to_string(),
|
||||
url: "a:4530".to_string(),
|
||||
rig_id: None,
|
||||
auth: RemoteAuthConfig::default(),
|
||||
poll_interval_ms: 750,
|
||||
},
|
||||
RemoteEntry {
|
||||
name: "dup".to_string(),
|
||||
url: "b:4530".to_string(),
|
||||
rig_id: None,
|
||||
auth: RemoteAuthConfig::default(),
|
||||
poll_interval_ms: 750,
|
||||
},
|
||||
];
|
||||
let config = ClientConfig {
|
||||
remotes: vec![
|
||||
RemoteEntry {
|
||||
name: "dup".to_string(),
|
||||
url: "a:4530".to_string(),
|
||||
rig_id: None,
|
||||
auth: RemoteAuthConfig::default(),
|
||||
poll_interval_ms: 750,
|
||||
},
|
||||
RemoteEntry {
|
||||
name: "dup".to_string(),
|
||||
url: "b:4530".to_string(),
|
||||
rig_id: None,
|
||||
auth: RemoteAuthConfig::default(),
|
||||
poll_interval_ms: 750,
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
assert!(config.validate().unwrap_err().contains("duplicate name"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_rejects_empty_remote_name() {
|
||||
let mut config = ClientConfig::default();
|
||||
config.remotes = vec![RemoteEntry {
|
||||
name: "".to_string(),
|
||||
url: "a:4530".to_string(),
|
||||
rig_id: None,
|
||||
auth: RemoteAuthConfig::default(),
|
||||
poll_interval_ms: 750,
|
||||
}];
|
||||
let config = ClientConfig {
|
||||
remotes: vec![RemoteEntry {
|
||||
name: "".to_string(),
|
||||
url: "a:4530".to_string(),
|
||||
rig_id: None,
|
||||
auth: RemoteAuthConfig::default(),
|
||||
poll_interval_ms: 750,
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
assert!(config
|
||||
.validate()
|
||||
.unwrap_err()
|
||||
@@ -1148,14 +1152,16 @@ url = "remote.example.com:4530"
|
||||
|
||||
#[test]
|
||||
fn test_validate_rejects_empty_remote_url() {
|
||||
let mut config = ClientConfig::default();
|
||||
config.remotes = vec![RemoteEntry {
|
||||
name: "hf".to_string(),
|
||||
url: " ".to_string(),
|
||||
rig_id: None,
|
||||
auth: RemoteAuthConfig::default(),
|
||||
poll_interval_ms: 750,
|
||||
}];
|
||||
let config = ClientConfig {
|
||||
remotes: vec![RemoteEntry {
|
||||
name: "hf".to_string(),
|
||||
url: " ".to_string(),
|
||||
rig_id: None,
|
||||
auth: RemoteAuthConfig::default(),
|
||||
poll_interval_ms: 750,
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
assert!(config
|
||||
.validate()
|
||||
.unwrap_err()
|
||||
@@ -1164,14 +1170,16 @@ url = "remote.example.com:4530"
|
||||
|
||||
#[test]
|
||||
fn test_validate_rejects_zero_remote_poll_interval() {
|
||||
let mut config = ClientConfig::default();
|
||||
config.remotes = vec![RemoteEntry {
|
||||
name: "hf".to_string(),
|
||||
url: "a:4530".to_string(),
|
||||
rig_id: None,
|
||||
auth: RemoteAuthConfig::default(),
|
||||
poll_interval_ms: 0,
|
||||
}];
|
||||
let config = ClientConfig {
|
||||
remotes: vec![RemoteEntry {
|
||||
name: "hf".to_string(),
|
||||
url: "a:4530".to_string(),
|
||||
rig_id: None,
|
||||
auth: RemoteAuthConfig::default(),
|
||||
poll_interval_ms: 0,
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
assert!(config
|
||||
.validate()
|
||||
.unwrap_err()
|
||||
|
||||
@@ -1678,7 +1678,7 @@ mod tests {
|
||||
#[test]
|
||||
fn global_target_for_snapshot_skips_other_server_selection() {
|
||||
let snapshot = sample_snapshot();
|
||||
let rigs = vec![RigEntry {
|
||||
let rigs = [RigEntry {
|
||||
rig_id: "hf".to_string(),
|
||||
display_name: Some("Gdansk HF".to_string()),
|
||||
state: snapshot,
|
||||
|
||||
@@ -1596,7 +1596,7 @@ mod tests {
|
||||
let t = i as f32 / fs;
|
||||
adj_phase += adj_mod_index * adj_composite[i];
|
||||
let adj = Complex::from_polar(0.5, adj_phase + TAU * adj_freq_offset * t);
|
||||
*s = *s + adj;
|
||||
*s += adj;
|
||||
}
|
||||
|
||||
let mut decoder = WfmStereoDecoder::new(
|
||||
@@ -1673,7 +1673,7 @@ mod tests {
|
||||
// Mix at 70 % of the main signal's amplitude — strong enough to
|
||||
// overcome the FM capture effect and visibly degrade pilot coherence.
|
||||
for (s, intf) in iq.iter_mut().zip(intf_iq.iter()) {
|
||||
*s = *s + intf * 0.7;
|
||||
*s += intf * 0.7;
|
||||
}
|
||||
|
||||
let mut decoder = WfmStereoDecoder::new(
|
||||
|
||||
Reference in New Issue
Block a user