@@ -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 ( ) {
0 commit comments