From 027364153a751a8d3b907ccbea627a9be896c902 Mon Sep 17 00:00:00 2001 From: "Michiel W. Beijen" Date: Wed, 18 Feb 2026 21:29:07 +0100 Subject: [PATCH 1/2] gh-144975: Fix wave.Wave_write.setframerate() validation order Validate the frame rate after rounding to an integer, not before. This prevents values like 0.5 from passing validation (0.5 > 0) but then rounding to 0, which would cause a confusing delayed error "sampling rate not specified" when writing frames. With this fix, setframerate(0.5) immediately raises "bad frame rate", providing clear feedback at the point of the error. --- Lib/test/test_wave.py | 33 +++++++++++++++++++ Lib/wave.py | 3 +- ...-02-18-21-45-00.gh-issue-144975.Ab3XyZ.rst | 3 ++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-02-18-21-45-00.gh-issue-144975.Ab3XyZ.rst diff --git a/Lib/test/test_wave.py b/Lib/test/test_wave.py index 4c21f16553775c..d6b875a5287e45 100644 --- a/Lib/test/test_wave.py +++ b/Lib/test/test_wave.py @@ -207,6 +207,39 @@ def test_open_in_write_raises(self): support.gc_collect() self.assertIsNone(cm.unraisable) + def test_setframerate_rounds_then_validates(self): + """Test that setframerate rounds before validation""" + # Test that framerates that round to 0 or negative are rejected + with wave.open(io.BytesIO(), 'wb') as f: + f.setnchannels(1) + f.setsampwidth(2) + # 0.5 rounds to 0, should raise + with self.assertRaises(wave.Error): + f.setframerate(0.5) + # 0.4 rounds to 0, should raise + with self.assertRaises(wave.Error): + f.setframerate(0.4) + # Negative values should still raise + with self.assertRaises(wave.Error): + f.setframerate(-1) + with self.assertRaises(wave.Error): + f.setframerate(-0.5) + # 0 should raise + with self.assertRaises(wave.Error): + f.setframerate(0) + + # Valid values that round to positive integers should work + f.setframerate(1.4) # rounds to 1 + self.assertEqual(f.getframerate(), 1) + f.setframerate(1.5) # rounds to 2 + self.assertEqual(f.getframerate(), 2) + f.setframerate(1.6) # rounds to 2 + self.assertEqual(f.getframerate(), 2) + f.setframerate(44100.4) # rounds to 44100 + self.assertEqual(f.getframerate(), 44100) + f.setframerate(44100.5) # rounds to 44100 + self.assertEqual(f.getframerate(), 44100) + class WaveOpen(unittest.TestCase): def test_open_pathlike(self): diff --git a/Lib/wave.py b/Lib/wave.py index 25ca9ef168e8a5..841da8c49e9a2f 100644 --- a/Lib/wave.py +++ b/Lib/wave.py @@ -493,9 +493,10 @@ def getsampwidth(self): def setframerate(self, framerate): if self._datawritten: raise Error('cannot change parameters after starting to write') + framerate = int(round(framerate)) if framerate <= 0: raise Error('bad frame rate') - self._framerate = int(round(framerate)) + self._framerate = framerate def getframerate(self): if not self._framerate: diff --git a/Misc/NEWS.d/next/Library/2026-02-18-21-45-00.gh-issue-144975.Ab3XyZ.rst b/Misc/NEWS.d/next/Library/2026-02-18-21-45-00.gh-issue-144975.Ab3XyZ.rst new file mode 100644 index 00000000000000..37658064c2c351 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-02-18-21-45-00.gh-issue-144975.Ab3XyZ.rst @@ -0,0 +1,3 @@ +:meth:`wave.Wave_write.setframerate` now validates the frame rate after +rounding to an integer, preventing values like ``0.5`` from being accepted +and causing confusing errors later. Patch by Michiel Beijen. From 925a17552d8343dcb9cdb149a88e548e5a28f657 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 17 Mar 2026 17:12:01 +0100 Subject: [PATCH 2/2] Use subTests for the tests --- Lib/test/test_wave.py | 58 +++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/Lib/test/test_wave.py b/Lib/test/test_wave.py index d6b875a5287e45..05e7d377517798 100644 --- a/Lib/test/test_wave.py +++ b/Lib/test/test_wave.py @@ -207,38 +207,42 @@ def test_open_in_write_raises(self): support.gc_collect() self.assertIsNone(cm.unraisable) - def test_setframerate_rounds_then_validates(self): - """Test that setframerate rounds before validation""" - # Test that framerates that round to 0 or negative are rejected + @support.subTests('arg', ( + # rounds to 0, should raise: + 0.5, + 0.4, + # Negative values should still raise: + -1, + -0.5, + -0.4, + # 0 should raise: + 0, + )) + def test_setframerate_validates_rounded_values(self, arg): + """Test that setframerate that round to 0 or negative are rejected""" with wave.open(io.BytesIO(), 'wb') as f: f.setnchannels(1) f.setsampwidth(2) - # 0.5 rounds to 0, should raise with self.assertRaises(wave.Error): - f.setframerate(0.5) - # 0.4 rounds to 0, should raise + f.setframerate(arg) with self.assertRaises(wave.Error): - f.setframerate(0.4) - # Negative values should still raise - with self.assertRaises(wave.Error): - f.setframerate(-1) - with self.assertRaises(wave.Error): - f.setframerate(-0.5) - # 0 should raise - with self.assertRaises(wave.Error): - f.setframerate(0) - - # Valid values that round to positive integers should work - f.setframerate(1.4) # rounds to 1 - self.assertEqual(f.getframerate(), 1) - f.setframerate(1.5) # rounds to 2 - self.assertEqual(f.getframerate(), 2) - f.setframerate(1.6) # rounds to 2 - self.assertEqual(f.getframerate(), 2) - f.setframerate(44100.4) # rounds to 44100 - self.assertEqual(f.getframerate(), 44100) - f.setframerate(44100.5) # rounds to 44100 - self.assertEqual(f.getframerate(), 44100) + f.close() + + @support.subTests(('arg', 'expected'), ( + (1.4, 1), + (1.5, 2), + (1.6, 2), + (44100.4, 44100), + (44100.5, 44100), + (44100.6, 44101), + )) + def test_setframerate_rounds(self, arg, expected): + """Test that setframerate is rounded""" + with wave.open(io.BytesIO(), 'wb') as f: + f.setnchannels(1) + f.setsampwidth(2) + f.setframerate(arg) + self.assertEqual(f.getframerate(), expected) class WaveOpen(unittest.TestCase):