Skip to content

gh-153953: Increase test coverage for the wave module#153954

Open
fedonman wants to merge 13 commits into
python:mainfrom
fedonman:fix-gh-153953-wave-test-coverage
Open

gh-153953: Increase test coverage for the wave module#153954
fedonman wants to merge 13 commits into
python:mainfrom
fedonman:fix-gh-153953-wave-test-coverage

Conversation

@fedonman

@fedonman fedonman commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Adds tests for previously-uncovered code paths in Lib/wave.py, all reachable through the public API and deterministic across platforms.

Wave_write parameter validation

  • setnchannels() rejecting a non-positive channel count
  • getnchannels() / getsampwidth() / getframerate() / getparams() raising when parameters are unset
  • setsampwidth() rejecting out-of-range widths
  • setcomptype() / setformat() rejecting unsupported values
  • the "cannot change parameters after starting to write" guard on every setter (setnchannels, setsampwidth, setframerate, setnframes, setcomptype, setformat, setparams)
  • tell()

Wave_read error handling

  • rejecting a WAVE_FORMAT_EXTENSIBLE file whose SubFormat GUID is not PCM
  • raising EOFError on a truncated fmt chunk (missing header / missing sample width)
  • skipping an unknown, odd-sized chunk between fmt and data
  • getfp()
  • closing the underlying file when opening a malformed file by path fails

wave.open()

  • rejecting an invalid mode

Measured with the stdlib trace module while running test_wave, line coverage of Lib/wave.py rises from 317/449 to 345/449 executable lines.

This is a test-only change with no behavior change, so no Misc/NEWS.d entry is included. ./python -m test -R 3:3 test_wave reports no reference leaks.

Fixes #153953.

Add tests for previously-uncovered paths in Lib/wave.py, all reachable
through the public API:

* Wave_write parameter validation: rejecting bad channel counts, sample
  widths, compression types and formats; the "not set" errors from the
  getters; the "cannot change parameters after starting to write" guards
  on every setter; and tell().
* Wave_read error handling: rejecting an unknown WAVE_FORMAT_EXTENSIBLE
  subformat, raising EOFError on a truncated fmt chunk, skipping unknown
  chunks, getfp(), and closing the file when opening a malformed path
  fails.
* wave.open() rejecting an invalid mode.

This raises line coverage of Lib/wave.py under test_wave from 317 to 345
of 449 executable lines. Test-only change; no behavior change.
@bedevere-app bedevere-app Bot added the tests Tests in the Lib/test dir label Jul 18, 2026
Comment thread Lib/test/test_wave.py Outdated
Comment thread Lib/test/test_wave.py Outdated
Comment thread Lib/test/test_wave.py Outdated
@fedonman
fedonman requested a review from vstinner July 18, 2026 23:37
@fedonman

Copy link
Copy Markdown
Contributor Author

@vstinner comments addressed, you may review again.

Comment thread Lib/test/test_wave.py
Comment on lines +555 to +567
def open_writer(self):
w = wave.open(io.BytesIO(), 'wb')
self.addCleanup(self._close_quietly, w)
return w

@staticmethod
def _close_quietly(w):
# A writer whose required parameters are never set raises on close;
# swallow that so it does not mask the behaviour under test.
try:
w.close()
except wave.Error:
pass

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to not ignore error on close(). I suggest setting all parameters so close() doesn't fail. Nitpick: I also prefer declaring the close function before it's being used by open_writer():

Suggested change
def open_writer(self):
w = wave.open(io.BytesIO(), 'wb')
self.addCleanup(self._close_quietly, w)
return w
@staticmethod
def _close_quietly(w):
# A writer whose required parameters are never set raises on close;
# swallow that so it does not mask the behaviour under test.
try:
w.close()
except wave.Error:
pass
@staticmethod
def _close(w):
try:
# Make sure that all parameters are set
w.setnchannels(1)
w.setsampwidth(2)
w.setframerate(44100)
except wave.Error:
# Ignore "cannot change parameters after starting to write" error
pass
w.close()
def open_writer(self):
w = wave.open(io.BytesIO(), 'wb')
self.addCleanup(self._close, w)
return w

Comment thread Lib/test/test_wave.py
Comment on lines +612 to +619
def test_tell_reports_frames_written(self):
w = self.open_writer()
w.setnchannels(1)
w.setsampwidth(2)
w.setframerate(44100)
self.assertEqual(w.tell(), 0)
w.writeframes(b'\x00\x00' * 5)
self.assertEqual(w.tell(), 5)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest testing getnframes() at the same time, use a shorter test name, and write twice (just in case):

Suggested change
def test_tell_reports_frames_written(self):
w = self.open_writer()
w.setnchannels(1)
w.setsampwidth(2)
w.setframerate(44100)
self.assertEqual(w.tell(), 0)
w.writeframes(b'\x00\x00' * 5)
self.assertEqual(w.tell(), 5)
def test_tell(self):
def check_nframes(nframes):
self.assertEqual(w.tell(), nframes)
self.assertEqual(w.getnframes(), nframes)
w = self.open_writer()
w.setnchannels(1)
w.setsampwidth(2)
w.setframerate(44100)
check_nframes(0)
frame = b'\x00\x00'
w.writeframes(frame * 5)
check_nframes(5)
w.writeframes(frame * 3)
check_nframes(8)

Comment thread Lib/test/test_wave.py
Comment on lines +621 to +640
def test_cannot_change_params_after_write(self):
setters = (
('setnchannels', (1,)),
('setsampwidth', (2,)),
('setframerate', (44100,)),
('setnframes', (10,)),
('setcomptype', ('NONE', 'not compressed')),
('setformat', (wave.WAVE_FORMAT_PCM,)),
('setparams', ((1, 2, 44100, 0, 'NONE', 'not compressed'),)),
)
for name, args in setters:
with self.subTest(setter=name):
w = self.open_writer()
w.setnchannels(1)
w.setsampwidth(2)
w.setframerate(44100)
w.writeframes(b'\x00\x00')
with self.assertRaisesRegex(wave.Error,
'cannot change parameters'):
getattr(w, name)(*args)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can reuse the same writer object for all tests, no need to create a fresh writer at each iteration:

Suggested change
def test_cannot_change_params_after_write(self):
setters = (
('setnchannels', (1,)),
('setsampwidth', (2,)),
('setframerate', (44100,)),
('setnframes', (10,)),
('setcomptype', ('NONE', 'not compressed')),
('setformat', (wave.WAVE_FORMAT_PCM,)),
('setparams', ((1, 2, 44100, 0, 'NONE', 'not compressed'),)),
)
for name, args in setters:
with self.subTest(setter=name):
w = self.open_writer()
w.setnchannels(1)
w.setsampwidth(2)
w.setframerate(44100)
w.writeframes(b'\x00\x00')
with self.assertRaisesRegex(wave.Error,
'cannot change parameters'):
getattr(w, name)(*args)
def test_cannot_change_params_after_write(self):
w = self.open_writer()
w.setnchannels(1)
w.setsampwidth(2)
w.setframerate(44100)
w.writeframes(b'\x00\x00')
setters = (
('setnchannels', (1,)),
('setsampwidth', (2,)),
('setframerate', (44100,)),
('setnframes', (10,)),
('setcomptype', ('NONE', 'not compressed')),
('setformat', (wave.WAVE_FORMAT_PCM,)),
('setparams', ((1, 2, 44100, 0, 'NONE', 'not compressed'),)),
)
for name, args in setters:
with self.subTest(setter=name):
with self.assertRaisesRegex(wave.Error,
'cannot change parameters'):
getattr(w, name)(*args)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

Increase test coverage for the wave module

3 participants