Skip to content

Commit 37c8e04

Browse files
committed
[add] tests to increase the code coverage.
1 parent a4d7064 commit 37c8e04

File tree

8 files changed

+1317
-390
lines changed

8 files changed

+1317
-390
lines changed

crates/lambda-rs-platform/src/audio/cpal/device.rs

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,160 @@ mod tests {
991991

992992
use super::*;
993993

994+
/// Normalized sample clamping MUST clamp to the nominal output range.
995+
#[test]
996+
fn clamp_normalized_sample_clamps_to_nominal_range() {
997+
assert_eq!(clamp_normalized_sample(2.0), 1.0);
998+
assert_eq!(clamp_normalized_sample(-2.0), -1.0);
999+
assert_eq!(clamp_normalized_sample(0.25), 0.25);
1000+
return;
1001+
}
1002+
1003+
/// Sample format priority MUST prefer `f32`, then `i16`, then `u16`.
1004+
#[test]
1005+
fn sample_format_priority_orders_supported_formats() {
1006+
assert!(
1007+
sample_format_priority(cpal_backend::SampleFormat::F32)
1008+
> sample_format_priority(cpal_backend::SampleFormat::I16)
1009+
);
1010+
assert!(
1011+
sample_format_priority(cpal_backend::SampleFormat::I16)
1012+
> sample_format_priority(cpal_backend::SampleFormat::U16)
1013+
);
1014+
assert_eq!(sample_format_priority(cpal_backend::SampleFormat::I8), 0);
1015+
return;
1016+
}
1017+
1018+
/// Platform audio error display MUST cover each error variant.
1019+
#[test]
1020+
fn platform_audio_error_display_covers_each_variant() {
1021+
let cases = [
1022+
AudioError::InvalidSampleRate { requested: 0 }.to_string(),
1023+
AudioError::InvalidChannels { requested: 0 }.to_string(),
1024+
AudioError::HostUnavailable {
1025+
details: "missing".to_string(),
1026+
}
1027+
.to_string(),
1028+
AudioError::NoDefaultDevice.to_string(),
1029+
AudioError::DeviceNameUnavailable {
1030+
details: "name".to_string(),
1031+
}
1032+
.to_string(),
1033+
AudioError::DeviceEnumerationFailed {
1034+
details: "enum".to_string(),
1035+
}
1036+
.to_string(),
1037+
AudioError::SupportedConfigsUnavailable {
1038+
details: "configs".to_string(),
1039+
}
1040+
.to_string(),
1041+
AudioError::UnsupportedConfig {
1042+
requested_sample_rate: Some(44_100),
1043+
requested_channels: Some(2),
1044+
}
1045+
.to_string(),
1046+
AudioError::UnsupportedSampleFormat {
1047+
details: "fmt".to_string(),
1048+
}
1049+
.to_string(),
1050+
AudioError::Platform {
1051+
details: "backend".to_string(),
1052+
}
1053+
.to_string(),
1054+
AudioError::StreamBuildFailed {
1055+
details: "build".to_string(),
1056+
}
1057+
.to_string(),
1058+
AudioError::StreamPlayFailed {
1059+
details: "play".to_string(),
1060+
}
1061+
.to_string(),
1062+
];
1063+
1064+
assert!(cases.iter().all(|value| !value.trim().is_empty()));
1065+
return;
1066+
}
1067+
1068+
/// Output buffer helpers MUST report length and format.
1069+
#[test]
1070+
fn output_buffer_reports_length_and_format() {
1071+
let mut samples_f32 = [0.0f32, 0.0];
1072+
let buffer = AudioOutputBuffer::F32(&mut samples_f32);
1073+
assert_eq!(buffer.len(), 2);
1074+
assert_eq!(buffer.sample_format(), AudioSampleFormat::F32);
1075+
1076+
let mut samples_i16 = [0i16, 0];
1077+
let buffer = AudioOutputBuffer::I16(&mut samples_i16);
1078+
assert_eq!(buffer.len(), 2);
1079+
assert_eq!(buffer.sample_format(), AudioSampleFormat::I16);
1080+
1081+
let mut samples_u16 = [0u16, 0];
1082+
let buffer = AudioOutputBuffer::U16(&mut samples_u16);
1083+
assert_eq!(buffer.len(), 2);
1084+
assert_eq!(buffer.sample_format(), AudioSampleFormat::U16);
1085+
return;
1086+
}
1087+
1088+
/// Writer construction MUST handle invalid channel counts without panicking.
1089+
#[test]
1090+
fn writer_new_handles_zero_channels() {
1091+
let mut samples_f32 = [0.0f32, 0.0];
1092+
let writer = InterleavedAudioOutputWriter::new(
1093+
0,
1094+
AudioOutputBuffer::F32(&mut samples_f32),
1095+
);
1096+
assert_eq!(writer.frames(), 0);
1097+
assert_eq!(writer.channels(), 0);
1098+
assert_eq!(writer.sample_format(), AudioSampleFormat::F32);
1099+
return;
1100+
}
1101+
1102+
/// Config selection MUST reject devices that only expose unsupported formats.
1103+
#[test]
1104+
fn select_output_stream_config_rejects_all_unsupported_formats() {
1105+
let supported_configs = [cpal_backend::SupportedStreamConfigRange::new(
1106+
2,
1107+
44_100,
1108+
48_000,
1109+
SupportedBufferSize::Unknown,
1110+
cpal_backend::SampleFormat::I8,
1111+
)];
1112+
1113+
let result = select_output_stream_config(&supported_configs, None, None);
1114+
assert!(matches!(
1115+
result,
1116+
Err(AudioError::UnsupportedSampleFormat { .. })
1117+
));
1118+
return;
1119+
}
1120+
1121+
/// Config selection MUST pick the sample rate closest to 48_000 when
1122+
/// priorities tie.
1123+
#[test]
1124+
fn select_output_stream_config_prefers_closest_sample_rate_when_tied() {
1125+
let supported_configs = [
1126+
cpal_backend::SupportedStreamConfigRange::new(
1127+
2,
1128+
44_100,
1129+
44_100,
1130+
SupportedBufferSize::Unknown,
1131+
cpal_backend::SampleFormat::F32,
1132+
),
1133+
cpal_backend::SupportedStreamConfigRange::new(
1134+
2,
1135+
48_000,
1136+
48_000,
1137+
SupportedBufferSize::Unknown,
1138+
cpal_backend::SampleFormat::F32,
1139+
),
1140+
];
1141+
1142+
let selected =
1143+
select_output_stream_config(&supported_configs, None, None).unwrap();
1144+
assert_eq!(selected.sample_rate(), 48_000);
1145+
return;
1146+
}
1147+
9941148
/// Builder MUST reject invalid sample rates.
9951149
#[test]
9961150
fn build_rejects_zero_sample_rate() {

crates/lambda-rs-platform/src/audio/symphonia/mod.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,8 +569,128 @@ pub fn decode_ogg_vorbis_bytes(
569569

570570
#[cfg(test)]
571571
mod tests {
572+
use std::io;
573+
574+
use symphonia::core::audio::{
575+
AsAudioBufferRef,
576+
AudioBuffer,
577+
Channels,
578+
SignalSpec,
579+
};
580+
572581
use super::*;
573582

583+
/// Probe-time error mapping MUST produce stable vendor-free variants.
584+
#[test]
585+
fn probe_error_mapping_produces_vendor_free_errors() {
586+
let unsupported = map_probe_error("WAV", Error::Unsupported("container"));
587+
assert!(matches!(
588+
unsupported,
589+
AudioDecodeError::UnsupportedFormat { .. }
590+
));
591+
592+
let io_error = map_probe_error(
593+
"WAV",
594+
Error::IoError(io::Error::new(io::ErrorKind::UnexpectedEof, "eof")),
595+
);
596+
assert!(matches!(io_error, AudioDecodeError::InvalidData { .. }));
597+
598+
let other = map_probe_error("WAV", Error::LimitError("limit"));
599+
assert!(matches!(other, AudioDecodeError::DecodeFailed { .. }));
600+
return;
601+
}
602+
603+
/// Decode-time error mapping MUST produce stable vendor-free variants.
604+
#[test]
605+
fn decode_error_mapping_produces_vendor_free_errors() {
606+
let unsupported =
607+
map_read_or_decode_error("OGG Vorbis", Error::Unsupported("codec"));
608+
assert!(matches!(
609+
unsupported,
610+
AudioDecodeError::UnsupportedFormat { .. }
611+
));
612+
613+
let decode_error =
614+
map_read_or_decode_error("OGG Vorbis", Error::DecodeError("bad"));
615+
assert!(matches!(decode_error, AudioDecodeError::InvalidData { .. }));
616+
617+
let io_error = map_read_or_decode_error(
618+
"OGG Vorbis",
619+
Error::IoError(io::Error::new(io::ErrorKind::UnexpectedEof, "eof")),
620+
);
621+
assert!(matches!(io_error, AudioDecodeError::InvalidData { .. }));
622+
623+
let other = map_read_or_decode_error("OGG Vorbis", Error::LimitError("x"));
624+
assert!(matches!(other, AudioDecodeError::DecodeFailed { .. }));
625+
return;
626+
}
627+
628+
/// Sample reservation MUST be a no-op when metadata is unavailable.
629+
#[test]
630+
fn reserve_samples_is_noop_without_metadata() {
631+
let mut samples = Vec::new();
632+
try_reserve_samples(&mut samples, "WAV", None, None)
633+
.expect("reserve should succeed");
634+
return;
635+
}
636+
637+
/// Sample reservation MUST skip sizes that cannot fit on 32-bit targets.
638+
#[cfg(target_pointer_width = "32")]
639+
#[test]
640+
fn reserve_samples_skips_overflowing_sizes_on_32_bit_targets() {
641+
let mut samples = Vec::new();
642+
try_reserve_samples(&mut samples, "WAV", Some(u64::MAX), Some(u16::MAX))
643+
.expect("reserve should skip overflow");
644+
return;
645+
}
646+
647+
/// Sample reservation MUST succeed for reasonable metadata on 64-bit targets.
648+
#[cfg(target_pointer_width = "64")]
649+
#[test]
650+
fn reserve_samples_reserves_for_reasonable_metadata_on_64_bit_targets() {
651+
let mut samples = Vec::new();
652+
try_reserve_samples(&mut samples, "WAV", Some(4), Some(2))
653+
.expect("reserve should succeed");
654+
return;
655+
}
656+
657+
/// WAV decoded sample format names MUST be stable.
658+
#[test]
659+
fn wav_decoded_sample_format_name_is_stable() {
660+
let spec = SignalSpec::new(44_100, Channels::FRONT_LEFT);
661+
let buffer_u8 = AudioBuffer::<u8>::new(1, spec);
662+
let buffer = buffer_u8.as_audio_buffer_ref();
663+
assert_eq!(wav_decoded_sample_format_name(&buffer), "U8");
664+
665+
let buffer_i16 = AudioBuffer::<i16>::new(1, spec);
666+
let buffer = buffer_i16.as_audio_buffer_ref();
667+
assert_eq!(wav_decoded_sample_format_name(&buffer), "S16");
668+
669+
let buffer_f32 = AudioBuffer::<f32>::new(1, spec);
670+
let buffer = buffer_f32.as_audio_buffer_ref();
671+
assert_eq!(wav_decoded_sample_format_name(&buffer), "F32");
672+
return;
673+
}
674+
675+
/// WAV decoded sample format validation MUST reject unsupported formats.
676+
#[test]
677+
fn wav_decoded_sample_format_validation_rejects_unsupported_formats() {
678+
let spec = SignalSpec::new(44_100, Channels::FRONT_LEFT);
679+
let unsupported_u8 = AudioBuffer::<u8>::new(1, spec);
680+
let unsupported = unsupported_u8.as_audio_buffer_ref();
681+
let result = validate_wav_decoded_sample_format(&unsupported);
682+
assert!(matches!(
683+
result,
684+
Err(AudioDecodeError::UnsupportedFormat { .. })
685+
));
686+
687+
let supported_i16 = AudioBuffer::<i16>::new(1, spec);
688+
let supported = supported_i16.as_audio_buffer_ref();
689+
validate_wav_decoded_sample_format(&supported)
690+
.expect("format should be supported");
691+
return;
692+
}
693+
574694
/// Fixture: 44100 Hz, mono, 16-bit integer PCM.
575695
#[cfg(feature = "audio-decode-wav")]
576696
const TONE_S16_MONO_44100_WAV: &[u8] = include_bytes!(concat!(

crates/lambda-rs/src/audio/buffer.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,69 @@ mod tests {
242242
return;
243243
}
244244

245+
/// Frames MUST return 0 when channel metadata is invalid.
246+
#[test]
247+
fn frames_returns_zero_when_channels_is_zero() {
248+
let buffer = SoundBuffer {
249+
samples: vec![0.0, 0.0],
250+
sample_rate: 48_000,
251+
channels: 0,
252+
};
253+
254+
assert_eq!(buffer.frames(), 0);
255+
assert_eq!(buffer.duration_seconds(), 0.0);
256+
return;
257+
}
258+
259+
/// Decoded audio conversion MUST reject invalid metadata.
260+
#[test]
261+
fn from_decoded_rejects_invalid_metadata() {
262+
let result = SoundBuffer::from_decoded(
263+
lambda_platform::audio::symphonia::DecodedAudio {
264+
samples: vec![0.0],
265+
sample_rate: 0,
266+
channels: 1,
267+
},
268+
);
269+
assert!(matches!(result, Err(AudioError::InvalidData { .. })));
270+
271+
let result = SoundBuffer::from_decoded(
272+
lambda_platform::audio::symphonia::DecodedAudio {
273+
samples: vec![0.0],
274+
sample_rate: 44_100,
275+
channels: 0,
276+
},
277+
);
278+
assert!(matches!(result, Err(AudioError::InvalidData { .. })));
279+
return;
280+
}
281+
282+
/// Decode error mapping MUST preserve stable variants.
283+
#[test]
284+
fn map_decode_error_preserves_variants() {
285+
let unsupported = map_decode_error(
286+
lambda_platform::audio::symphonia::AudioDecodeError::UnsupportedFormat {
287+
details: "wav".to_string(),
288+
},
289+
);
290+
assert!(matches!(unsupported, AudioError::UnsupportedFormat { .. }));
291+
292+
let invalid = map_decode_error(
293+
lambda_platform::audio::symphonia::AudioDecodeError::InvalidData {
294+
details: "bad".to_string(),
295+
},
296+
);
297+
assert!(matches!(invalid, AudioError::InvalidData { .. }));
298+
299+
let failed = map_decode_error(
300+
lambda_platform::audio::symphonia::AudioDecodeError::DecodeFailed {
301+
details: "boom".to_string(),
302+
},
303+
);
304+
assert!(matches!(failed, AudioError::DecodeFailed { .. }));
305+
return;
306+
}
307+
245308
/// WAV decode from bytes MUST succeed for the bundled fixture.
246309
#[cfg(feature = "audio-sound-buffer-wav")]
247310
#[test]

0 commit comments

Comments
 (0)