@@ -172,6 +172,19 @@ def test__all__(self):
172172 not_exported = {'KSDATAFORMAT_SUBTYPE_PCM' }
173173 support .check__all__ (self , wave , not_exported = not_exported )
174174
175+ def test_getfp (self ):
176+ fp = io .BytesIO ()
177+ with wave .open (fp , 'wb' ) as w :
178+ w .setnchannels (1 )
179+ w .setsampwidth (1 )
180+ w .setframerate (11025 )
181+ fp .seek (0 )
182+ with wave .open (fp ) as r :
183+ chunk = r .getfp ()
184+ self .assertIsNotNone (chunk )
185+ self .assertIs (chunk .file , fp )
186+ self .assertEqual (chunk .chunkname , b'RIFF' )
187+
175188
176189class WaveLowLevelTest (unittest .TestCase ):
177190
@@ -474,6 +487,184 @@ def test_open_pathlike(self):
474487 with wave .open (fake_path , 'rb' ) as f :
475488 pass
476489
490+ def test_open_invalid_mode (self ):
491+ with self .assertRaisesRegex (wave .Error , "mode must be" ):
492+ wave .open (io .BytesIO (), 'xb' )
493+
494+
495+ class WaveReadErrorTest (unittest .TestCase ):
496+ """Cover error and edge paths of Wave_read, and wave.open()."""
497+
498+ FMT_PCM = struct .pack ('<HHLLHH' , wave .WAVE_FORMAT_PCM , 1 , 11025 , 11025 , 1 , 8 )
499+
500+ @staticmethod
501+ def _wave_file (* chunks ):
502+ """Build in-memory WAVE bytes from (name, payload) chunks.
503+
504+ Each chunk stores its real payload length and is padded to an even
505+ number of bytes, and the RIFF size is computed to match.
506+ """
507+ body = b'WAVE'
508+ for name , payload in chunks :
509+ body += name + struct .pack ('<L' , len (payload )) + payload
510+ if len (payload ) & 1 :
511+ body += b'\x00 '
512+ return b'RIFF' + struct .pack ('<L' , len (body )) + body
513+
514+ def test_read_unknown_extensible_subformat (self ):
515+ # A WAVE_FORMAT_EXTENSIBLE fmt chunk whose SubFormat GUID is not
516+ # KSDATAFORMAT_SUBTYPE_PCM must be rejected.
517+ fmt = struct .pack ('<HHLLH' , wave .WAVE_FORMAT_EXTENSIBLE , 2 , 11025 ,
518+ 11025 * 2 * 3 , 6 )
519+ fmt += struct .pack ('<H' , 24 ) # bits per sample
520+ fmt += struct .pack ('<HHL' , 22 , 24 , 3 ) # cbSize, valid bits, channel mask
521+ fmt += b'\xff ' * 16 # bogus SubFormat GUID
522+ b = self ._wave_file ((b'fmt ' , fmt ), (b'data' , b'' ))
523+ with self .assertRaisesRegex (wave .Error , 'unknown extended format' ):
524+ wave .open (io .BytesIO (b ))
525+
526+ def test_read_truncated_fmt_chunk_header (self ):
527+ # fmt chunk too short for the fixed 14-byte header.
528+ fmt = struct .pack ('<H' , wave .WAVE_FORMAT_PCM ) + b'\x00 ' * 8
529+ b = self ._wave_file ((b'fmt ' , fmt ))
530+ with self .assertRaises (EOFError ):
531+ wave .open (io .BytesIO (b ))
532+
533+ def test_read_truncated_fmt_chunk_sampwidth (self ):
534+ # fmt chunk holds the 14-byte header but is missing the sample width.
535+ fmt = struct .pack ('<HHLLH' , wave .WAVE_FORMAT_PCM , 1 , 11025 , 11025 , 1 )
536+ b = self ._wave_file ((b'fmt ' , fmt ))
537+ with self .assertRaises (EOFError ):
538+ wave .open (io .BytesIO (b ))
539+
540+ def test_read_skips_unknown_chunk (self ):
541+ # An unknown, odd-sized chunk between fmt and data must be skipped
542+ # (including its pad byte) so the data chunk is still found.
543+ data = b'\x01 \x02 \x03 \x04 '
544+ b = self ._wave_file ((b'fmt ' , self .FMT_PCM ),
545+ (b'LIST' , b'abc' ), # odd size, forces alignment
546+ (b'data' , data ))
547+ with wave .open (io .BytesIO (b )) as r :
548+ self .assertEqual (r .getnframes (), 4 )
549+ self .assertEqual (r .readframes (4 ), data )
550+
551+
552+ class WaveWriteValidationTest (unittest .TestCase ):
553+ """Cover parameter-validation paths of Wave_write."""
554+
555+ @staticmethod
556+ def _close (w ):
557+ try :
558+ # Make sure that all parameters are set
559+ w .setnchannels (1 )
560+ w .setsampwidth (2 )
561+ w .setframerate (44100 )
562+ except wave .Error :
563+ # Ignore "cannot change parameters after starting to write" error
564+ pass
565+
566+ w .close ()
567+
568+ def open_writer (self ):
569+ w = wave .open (io .BytesIO (), 'wb' )
570+ self .addCleanup (self ._close , w )
571+ return w
572+
573+ def test_get (self ):
574+ w = self .open_writer ()
575+ self .assertEqual (w .getformat (), wave .WAVE_FORMAT_PCM )
576+ self .assertEqual (w .getnframes (), 0 )
577+ # getcomptype() and getcompname() raise AttributeError
578+ # until setcomptype() is called
579+
580+ with self .assertRaisesRegex (wave .Error , 'number of channels not set' ):
581+ w .getnchannels ()
582+ with self .assertRaisesRegex (wave .Error , 'sample width not set' ):
583+ w .getsampwidth ()
584+ with self .assertRaisesRegex (wave .Error , 'frame rate not set' ):
585+ w .getframerate ()
586+ with self .assertRaisesRegex (wave .Error , 'not all parameters set' ):
587+ w .getparams ()
588+
589+ def test_set (self ):
590+ w = self .open_writer ()
591+
592+ w .setnchannels (1 )
593+ self .assertEqual (w .getnchannels (), 1 )
594+ with self .assertRaisesRegex (wave .Error , 'bad # of channels' ):
595+ w .setnchannels (0 )
596+
597+ w .setsampwidth (2 )
598+ self .assertEqual (w .getsampwidth (), 2 )
599+ for width in (0 , 5 ):
600+ with self .subTest (width = width ):
601+ with self .assertRaisesRegex (wave .Error , 'bad sample width' ):
602+ w .setsampwidth (width )
603+
604+ w .setframerate (44100 )
605+ self .assertEqual (w .getframerate (), 44100 )
606+ with self .assertRaisesRegex (wave .Error , 'bad frame rate' ):
607+ w .setframerate (0 )
608+
609+ w .setnframes (10 )
610+ self .assertEqual (w .getnframes (), 0 )
611+
612+ w .setcomptype ('NONE' , 'not compressed' )
613+ self .assertEqual (w .getcomptype (), 'NONE' )
614+ self .assertEqual (w .getcompname (), 'not compressed' )
615+ with self .assertRaisesRegex (wave .Error , 'unsupported compression type' ):
616+ w .setcomptype ('ADPCM' , 'unsupported' )
617+
618+ w .setformat (wave .WAVE_FORMAT_PCM )
619+ self .assertEqual (w .getformat (), wave .WAVE_FORMAT_PCM )
620+ with self .assertRaisesRegex (wave .Error , 'unsupported wave format' ):
621+ w .setformat (0x1234 )
622+
623+ w .setparams ((1 , 2 , 44100 , 0 , 'NONE' , 'not compressed' ))
624+ self .assertEqual (w .getparams (),
625+ (1 , 2 , 44100 , 0 , 'NONE' , 'not compressed' ))
626+ with self .assertRaisesRegex (wave .Error , 'bad # of channels' ):
627+ w .setparams ((0 , 2 , 44100 , 0 , 'NONE' , 'not compressed' ))
628+
629+ def test_tell (self ):
630+ def check_nframes (nframes ):
631+ self .assertEqual (w .tell (), nframes )
632+ self .assertEqual (w .getnframes (), nframes )
633+
634+ w = self .open_writer ()
635+ w .setnchannels (1 )
636+ w .setsampwidth (2 )
637+ w .setframerate (44100 )
638+ check_nframes (0 )
639+
640+ frame = b'\x00 \x00 '
641+ w .writeframes (frame * 5 )
642+ check_nframes (5 )
643+
644+ w .writeframes (frame * 3 )
645+ check_nframes (8 )
646+
647+ def test_cannot_change_params_after_write (self ):
648+ w = self .open_writer ()
649+ w .setnchannels (1 )
650+ w .setsampwidth (2 )
651+ w .setframerate (44100 )
652+ w .writeframes (b'\x00 \x00 ' )
653+
654+ setters = (
655+ ('setnchannels' , (1 ,)),
656+ ('setsampwidth' , (2 ,)),
657+ ('setframerate' , (44100 ,)),
658+ ('setnframes' , (10 ,)),
659+ ('setcomptype' , ('NONE' , 'not compressed' )),
660+ ('setformat' , (wave .WAVE_FORMAT_PCM ,)),
661+ ('setparams' , ((1 , 2 , 44100 , 0 , 'NONE' , 'not compressed' ),)),
662+ )
663+ for name , args in setters :
664+ with self .subTest (setter = name ):
665+ with self .assertRaisesRegex (wave .Error ,
666+ 'cannot change parameters' ):
667+ getattr (w , name )(* args )
477668
478669if __name__ == '__main__' :
479670 unittest .main ()
0 commit comments