From 47df4c5b5efc801cfbef5a4429f7b7a4caca7399 Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Sun, 15 Jun 2014 17:35:59 +0200 Subject: [PATCH 01/14] began to implement pytest tests. - converted tests for read - converted tests for seek - converted tests for write --- tests/test_0.5.wav | Bin 0 -> 64 bytes tests/test_pysoundfile.py | 441 ++++++++++++++++++++------------------ 2 files changed, 232 insertions(+), 209 deletions(-) create mode 100644 tests/test_0.5.wav diff --git a/tests/test_0.5.wav b/tests/test_0.5.wav new file mode 100644 index 0000000000000000000000000000000000000000..bad9ffbe3df1984b927c7cbe1552fcc02bc69a1f GIT binary patch literal 64 zcmWIYbaS&{U| Date: Tue, 17 Jun 2014 21:20:23 +0200 Subject: [PATCH 02/14] refactored test fixtures this simplifies the fixture setup some. A lot less repetition now. --- tests/test_pysoundfile.py | 95 +++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 53 deletions(-) diff --git a/tests/test_pysoundfile.py b/tests/test_pysoundfile.py index eba5858..f131638 100644 --- a/tests/test_pysoundfile.py +++ b/tests/test_pysoundfile.py @@ -9,70 +9,59 @@ file_05 = 'tests/test_0.5.wav' file_w = 'tests/test_w.wav' -@pytest.fixture(params=['filename', 'filehandle', 'bytestream']) -def wavefile_r(request): - if request.param == 'filename': - file = sf.SoundFile(file_05) - elif request.param == 'filehandle': - handle = os.open(file_05, os.O_RDONLY) - file = sf.SoundFile(handle) - # TODO: does sf.SoundFile auto-close the handle??? - # request.addfinalizer(lambda: os.close(handle)) - elif request.param =='bytestream': - bytesio = open(file_05, 'rb') +def open_filename(filename, rw, _): + if rw == 'r': + return sf.SoundFile(filename) + elif rw == 'w': + return sf.SoundFile(filename, mode='w', sample_rate=44100, channels=2) + +def open_filehandle(filename, rw, _): + # TODO: does sf.SoundFile auto-close the handle??? + # request.addfinalizer(lambda: os.close(handle)) + if rw == 'r': + handle = os.open(filename, os.O_RDONLY) + return sf.SoundFile(handle) + elif rw == 'w': + handle = os.open(filename, os.O_CREAT | os.O_WRONLY) + return sf.SoundFile(handle, mode='w', sample_rate=44100, channels=2, format='wav') + +def open_bytestream(filename, rw, request): + if rw == 'r': + bytesio = open(filename, 'rb') file = sf.SoundFile(bytesio) - request.addfinalizer(bytesio.close) + elif rw == 'w': + bytesio = open(filename, 'wb') + file = sf.SoundFile(bytesio, mode='w', sample_rate=44100, channels=2, format='wav') + request.addfinalizer(bytesio.close) + return file + +@pytest.fixture(params=[open_filename, open_filehandle, open_bytestream]) +def wavefile_r(request): + file = request.param(file_05, 'r', request) request.addfinalizer(file.close) return file -@pytest.fixture(params=['filename', 'filehandle', 'bytestream']) +@pytest.fixture(params=[open_filename, open_filehandle, open_bytestream]) def wavefile_w(request): - if request.param == 'filename': - file = sf.SoundFile(file_w, mode='w', sample_rate=44100, channels=2) - elif request.param == 'filehandle': - handle = os.open(file_w, os.O_CREAT | os.O_WRONLY) - file = sf.SoundFile(handle, mode='w', sample_rate=44100, channels=2, format='wav') - # TODO: does sf.SoundFile auto-close the handle??? - # request.addfinalizer(lambda: os.close(handle)) - elif request.param =='bytestream': - bytesio = open(file_w, 'wb') - file = sf.SoundFile(bytesio, mode='w', sample_rate=44100, channels=2, format='wav') - request.addfinalizer(bytesio.close) + file = request.param(file_w, 'w', request) request.addfinalizer(file.close) request.addfinalizer(lambda: os.remove(file_w)) return file -@pytest.fixture(params=[('r', 'filename'), - ('w', 'filename'), - ('r', 'filehandle'), - ('w', 'filehandle'), - ('r', 'bytestream'), - ('w', 'bytestream')]) +@pytest.fixture(params=[('r', open_filename), + ('w', open_filename), + ('r', open_filehandle), + ('w', open_filehandle), + ('r', open_bytestream), + ('w', open_bytestream)]) def wavefile_all(request): - if request.param == ('r', 'filename'): - file = sf.SoundFile(file_05) - elif request.param == ('r', 'filehandle'): - handle = os.open(file_05, os.O_RDONLY) - file = sf.SoundFile(handle) - # TODO: does sf.SoundFile auto-close the handle??? - # request.addfinalizer(lambda: os.close(handle)) - elif request.param == ('r', 'bytestream'): - bytesio = open(file_05, 'rb') - file = sf.SoundFile(bytesio) - request.addfinalizer(bytesio.close) - if request.param == ('w', 'filename'): - file = sf.SoundFile(file_w, mode='w', sample_rate=44100, channels=2) - elif request.param == ('w', 'filehandle'): - handle = os.open(file_w, os.O_CREAT | os.O_WRONLY) - file = sf.SoundFile(handle, mode='w', sample_rate=44100, channels=2, format='wav') - # TODO: does sf.SoundFile auto-close the handle??? - # request.addfinalizer(lambda: os.close(handle)) - elif request.param ==('w', 'bytestream'): - bytesio = open(file_w, 'wb') - file = sf.SoundFile(bytesio, mode='w', sample_rate=44100, channels=2, format='wav') - request.addfinalizer(bytesio.close) + rw, open_func = request.param + if rw == 'r': + file = open_func(file_05, rw, request) + elif rw == 'w': + file = open_func(file_w, rw, request) request.addfinalizer(file.close) - if request.param[0] == 'w': + if rw == 'w': request.addfinalizer(lambda: os.remove(file_w)) return file From 3fda6b2a62f7d5e07eb51ab77c2825ce7b220e11 Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Tue, 17 Jun 2014 21:34:18 +0200 Subject: [PATCH 03/14] converted a few more tests --- tests/test_pysoundfile.py | 56 +++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/tests/test_pysoundfile.py b/tests/test_pysoundfile.py index f131638..b65587f 100644 --- a/tests/test_pysoundfile.py +++ b/tests/test_pysoundfile.py @@ -221,6 +221,34 @@ def test_write_should_write_and_advance_write_pointer(wavefile_w): wavefile_w.write(data_05) assert wavefile_w.seek(0, sf.SEEK_CUR) == 5 +# ------------------------------------------------------------------------------ +# Other tests +# ------------------------------------------------------------------------------ + +def test_context_manager_should_open_and_close_file(): + with open_filename(file_05, 'r', None) as f: + assert not f.closed + assert f.closed + +def test_closing_should_close_file(): + f = open_filename(file_05, 'r', None) + assert not f.closed + f.close() + assert f.closed + +def test_file_attributes_should_save_to_disk(): + with open_filename(file_w, 'w', None) as f: + f.title = 'testing' + with open_filename(file_w, 'r', None) as f: + assert f.title == 'testing' + +def test_non_file_attributes_should_not_save_to_disk(): + with open_filename(file_w, 'w', None) as f: + f.foobar = 'testing' + with open_filename(file_w, 'r', None) as f: + with pytest.raises(AttributeError): + f.foobar + # ------------------------------------------------------------------------------ # Legacy tests # ------------------------------------------------------------------------------ @@ -247,34 +275,6 @@ def test_rw_mode(self): self.assertEqual(f.mode, 'rw') self.assertEqual(f.seek(0, sf.SEEK_CUR), len(f)) - def test_context_manager(self): - """The context manager should close the file""" - with sf.SoundFile(self.filename) as f: - pass - self.assertTrue(f.closed) - - def test_closing(self): - """Closing a file should close it""" - f = sf.SoundFile(self.filename) - self.assertFalse(f.closed) - f.close() - self.assertTrue(f.closed) - - def test_file_attributes(self): - """Changing a file attribute should save it on disk""" - with sf.SoundFile(self.filename, 'rw') as f: - f.title = 'testing' - with sf.SoundFile(self.filename) as f: - self.assertEqual(f.title, 'testing') - - def test_non_file_attributes(self): - """Changing a non-file attribute should not save to disk""" - with sf.SoundFile(self.filename, 'rw') as f: - f.foobar = 'testing' - with sf.SoundFile(self.filename) as f: - with self.assertRaises(AttributeError): - f.foobar - class TestSeekWaveFile(TestWaveFile): def test_seek_write(self): From 0a99d400ea294102c44ed2cad5c7cc85696d60ef Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Wed, 18 Jun 2014 08:28:08 +0200 Subject: [PATCH 04/14] renamed read test file to test_r now symmetric with test_w --- tests/test_pysoundfile.py | 30 ++++++++++++++--------------- tests/{test_0.5.wav => test_r.wav} | Bin 2 files changed, 15 insertions(+), 15 deletions(-) rename tests/{test_0.5.wav => test_r.wav} (100%) diff --git a/tests/test_pysoundfile.py b/tests/test_pysoundfile.py index b65587f..2c94c4e 100644 --- a/tests/test_pysoundfile.py +++ b/tests/test_pysoundfile.py @@ -5,8 +5,8 @@ import io import pytest -data_05 = np.ones((5,2))*0.5 -file_05 = 'tests/test_0.5.wav' +data_r = np.ones((5,2))*0.5 +file_r = 'tests/test_r.wav' file_w = 'tests/test_w.wav' def open_filename(filename, rw, _): @@ -37,7 +37,7 @@ def open_bytestream(filename, rw, request): @pytest.fixture(params=[open_filename, open_filehandle, open_bytestream]) def wavefile_r(request): - file = request.param(file_05, 'r', request) + file = request.param(file_r, 'r', request) request.addfinalizer(file.close) return file @@ -57,7 +57,7 @@ def wavefile_w(request): def wavefile_all(request): rw, open_func = request.param if rw == 'r': - file = open_func(file_05, rw, request) + file = open_func(file_r, rw, request) elif rw == 'w': file = open_func(file_w, rw, request) request.addfinalizer(file.close) @@ -70,7 +70,7 @@ def wavefile_all(request): # ------------------------------------------------------------------------------ def test_file_content(wavefile_r): - assert np.all(data_05 == wavefile_r[:]) + assert np.all(data_r == wavefile_r[:]) def test_mode_should_be_in_read_mode(wavefile_r): assert wavefile_r.mode == 'r' @@ -95,7 +95,7 @@ def test_format_metadata(wavefile_all): assert wavefile_all.subtype_info == 'Signed 16 bit PCM' def test_data_length(wavefile_r): - assert len(wavefile_r) == len(data_05) + assert len(wavefile_r) == len(data_r) def test_data_length(wavefile_w): assert len(wavefile_w) == 0 @@ -133,7 +133,7 @@ def test_read_write_only(wavefile_w): def test_read_should_read_data_and_advance_read_pointer(wavefile_r): data = wavefile_r.read(2) - assert np.all(data == data_05[:2]) + assert np.all(data == data_r[:2]) assert wavefile_r.seek(0, sf.SEEK_CUR) == 2 def test_read_should_read_float64_data(wavefile_r): @@ -149,7 +149,7 @@ def test_read_float32_should_read_float32_data(wavefile_r): assert wavefile_r.read(2, dtype='float32').dtype == np.float32 def test_read_by_indexing_should_read_but_not_advance_read_pointer(wavefile_r): - assert np.all(wavefile_r[:2] == data_05[:2]) + assert np.all(wavefile_r[:2] == data_r[:2]) assert wavefile_r.seek(0, sf.SEEK_CUR) == 0 def test_read_n_frames_should_return_n_frames(wavefile_r): @@ -157,16 +157,16 @@ def test_read_n_frames_should_return_n_frames(wavefile_r): def test_read_all_frames_should_read_all_remaining_frames(wavefile_r): wavefile_r.seek(-2, sf.SEEK_END) - assert np.all(wavefile_r.read() == data_05[-2:]) + assert np.all(wavefile_r.read() == data_r[-2:]) def test_read_over_end_should_return_only_remaining_frames(wavefile_r): wavefile_r.seek(-2, sf.SEEK_END) - assert np.all(wavefile_r.read(4) == data_05[-2:]) + assert np.all(wavefile_r.read(4) == data_r[-2:]) def test_read_over_end_with_fill_should_reaturn_asked_frames(wavefile_r): wavefile_r.seek(-2, sf.SEEK_END) data = wavefile_r.read(4, fill_value=0) - assert np.all(data[:2] == data_05[-2:]) + assert np.all(data[:2] == data_r[-2:]) assert np.all(data[2:] == 0) assert len(data) == 4 @@ -215,10 +215,10 @@ def test_read_into_out_over_end_with_fill_should_return_full_data_and_write_into def test_write_to_read_only_file_should_fail(wavefile_r): with pytest.raises(RuntimeError): - wavefile_r.write(data_05) + wavefile_r.write(data_r) def test_write_should_write_and_advance_write_pointer(wavefile_w): - wavefile_w.write(data_05) + wavefile_w.write(data_r) assert wavefile_w.seek(0, sf.SEEK_CUR) == 5 # ------------------------------------------------------------------------------ @@ -226,12 +226,12 @@ def test_write_should_write_and_advance_write_pointer(wavefile_w): # ------------------------------------------------------------------------------ def test_context_manager_should_open_and_close_file(): - with open_filename(file_05, 'r', None) as f: + with open_filename(file_r, 'r', None) as f: assert not f.closed assert f.closed def test_closing_should_close_file(): - f = open_filename(file_05, 'r', None) + f = open_filename(file_r, 'r', None) assert not f.closed f.close() assert f.closed diff --git a/tests/test_0.5.wav b/tests/test_r.wav similarity index 100% rename from tests/test_0.5.wav rename to tests/test_r.wav From f8e29ba52a3d4cd7d380a08f0cfe1f3ce6125e75 Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Wed, 18 Jun 2014 09:34:09 +0200 Subject: [PATCH 05/14] added preparation for rw mode tests --- tests/test_pysoundfile.py | 69 +++++++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/tests/test_pysoundfile.py b/tests/test_pysoundfile.py index 2c94c4e..3e2e472 100644 --- a/tests/test_pysoundfile.py +++ b/tests/test_pysoundfile.py @@ -13,50 +13,87 @@ def open_filename(filename, rw, _): if rw == 'r': return sf.SoundFile(filename) elif rw == 'w': - return sf.SoundFile(filename, mode='w', sample_rate=44100, channels=2) + return sf.SoundFile(filename, mode=rw, sample_rate=44100, channels=2) + elif rw == 'rw' and filename == file_r: + return sf.SoundFile(filename, mode=rw) + elif rw == 'rw' and filename == file_w: + return sf.SoundFile(filename, mode=rw, sample_rate=44100, channels=2) def open_filehandle(filename, rw, _): # TODO: does sf.SoundFile auto-close the handle??? # request.addfinalizer(lambda: os.close(handle)) if rw == 'r': handle = os.open(filename, os.O_RDONLY) - return sf.SoundFile(handle) elif rw == 'w': handle = os.open(filename, os.O_CREAT | os.O_WRONLY) - return sf.SoundFile(handle, mode='w', sample_rate=44100, channels=2, format='wav') + elif rw == 'rw' and filename == file_r: + handle = os.open(filename, os.O_RDWR) + elif rw =='rw' and filename == file_w: + handle = os.open(filename, os.O_CREAT | os.O_RDWR) + if filename == file_r: + return sf.SoundFile(handle, mode=rw) + elif filename == file_w: + return sf.SoundFile(handle, mode=rw, sample_rate=44100, channels=2, format='wav') def open_bytestream(filename, rw, request): if rw == 'r': bytesio = open(filename, 'rb') - file = sf.SoundFile(bytesio) elif rw == 'w': bytesio = open(filename, 'wb') - file = sf.SoundFile(bytesio, mode='w', sample_rate=44100, channels=2, format='wav') + elif rw == 'rw' and filename == file_r: + bytesio = open(filename, 'a+b') + elif rw == 'rw' and filename == file_w: + bytesio = open(filename, 'w+b') + if filename == file_r: + file = sf.SoundFile(bytesio, mode=rw) + elif filename == file_w: + file = sf.SoundFile(bytesio, mode=rw, sample_rate=44100, channels=2, format='wav') request.addfinalizer(bytesio.close) return file -@pytest.fixture(params=[open_filename, open_filehandle, open_bytestream]) +@pytest.fixture(params=[('r', open_filename), + ('r', open_filehandle), + ('r', open_bytestream)]) def wavefile_r(request): - file = request.param(file_r, 'r', request) + rw, open_func = request.param + file = open_func(file_r, rw, request) request.addfinalizer(file.close) return file -@pytest.fixture(params=[open_filename, open_filehandle, open_bytestream]) +@pytest.fixture(params=[('w', open_filename), + ('w', open_filehandle), + ('w', open_bytestream)]) def wavefile_w(request): - file = request.param(file_w, 'w', request) + rw, open_func = request.param + file = open_func(file_w, rw, request) request.addfinalizer(file.close) request.addfinalizer(lambda: os.remove(file_w)) return file +@pytest.fixture(params=[('rw', open_filename), + ('rw', open_filehandle), + # rw is not permissable with bytestreams + ]) +def wavefile_rw(request): + rw, open_func = request.param + file = open_func(file_r, rw, request) + request.addfinalizer(file.close) + return file + + @pytest.fixture(params=[('r', open_filename), ('w', open_filename), + ('rw', open_filename), ('r', open_filehandle), ('w', open_filehandle), + ('rw', open_filehandle), ('r', open_bytestream), - ('w', open_bytestream)]) + ('w', open_bytestream), + # rw is not permissable with bytestreams + ]) def wavefile_all(request): rw, open_func = request.param - if rw == 'r': + if 'r' in rw: file = open_func(file_r, rw, request) elif rw == 'w': file = open_func(file_w, rw, request) @@ -78,8 +115,14 @@ def test_mode_should_be_in_read_mode(wavefile_r): def test_mode_should_be_in_read_mode(wavefile_w): assert wavefile_w.mode == 'w' -def test_mode_should_start_at_beginning(wavefile_all): - assert wavefile_all.seek(0, sf.SEEK_CUR) == 0 +def test_mode_read_should_start_at_beginning(wavefile_r): + assert wavefile_r.seek(0, sf.SEEK_CUR) == 0 + +def test_mode_write_should_start_at_beginning(wavefile_w): + assert wavefile_w.seek(0, sf.SEEK_CUR) == 0 + +def test_mode_rw_should_start_at_end(wavefile_rw): + assert wavefile_rw.seek(0, sf.SEEK_CUR) == 5 def test_number_of_channels(wavefile_all): assert wavefile_all.channels == 2 From 4970fc3cc45c004f7b3e3600f7f4b3d73a985850 Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Wed, 18 Jun 2014 10:54:58 +0200 Subject: [PATCH 06/14] added tests for rw mode and fixed related bugs There are some peculiarities with how to set the file format when the file is in rw mode. Now, file format is optional for rw mode. --- pysoundfile.py | 21 +++++-- tests/test_pysoundfile.py | 124 +++++++++++++++++++++----------------- 2 files changed, 84 insertions(+), 61 deletions(-) diff --git a/pysoundfile.py b/pysoundfile.py index e6fbb1a..e56a5a9 100644 --- a/pysoundfile.py +++ b/pysoundfile.py @@ -333,13 +333,17 @@ def __init__(self, file, mode='r', sample_rate=None, channels=None, raise ValueError("Invalid mode: %s" % repr(mode)) original_format = format - if format is None: + if format is None and 'w' in self.mode: filename = getattr(file, 'name', file) format = str(filename).rsplit('.', 1)[-1].upper() - if self.mode == 'w' and format not in _formats: - raise TypeError( - "No format specified and unable to get format from " - "file extension: %s" % repr(filename)) + if format not in _formats: + if self.mode == 'w': + raise TypeError( + "No format specified and unable to get format from " + "file extension: %s" % repr(filename)) + else: + # format is optional for self.mode == 'rw' + format = original_format self._info = _ffi.new("SF_INFO*") if self.mode == 'w' or str(format).upper() == 'RAW': @@ -350,6 +354,13 @@ def __init__(self, file, mode='r', sample_rate=None, channels=None, raise TypeError("channels must be specified") self._info.channels = channels self._info.format = _format_int(format, subtype, endian) + elif self.mode == 'rw': + if sample_rate is not None: + self._info.samplerate = sample_rate + if channels is not None: + self._info.channels = channels + if format is not None: + self._info.format = _format_int(format, subtype, endian) else: if [sample_rate, channels, original_format, subtype, endian] != \ [None] * 5: diff --git a/tests/test_pysoundfile.py b/tests/test_pysoundfile.py index 3e2e472..0415a4c 100644 --- a/tests/test_pysoundfile.py +++ b/tests/test_pysoundfile.py @@ -74,12 +74,22 @@ def wavefile_w(request): ('rw', open_filehandle), # rw is not permissable with bytestreams ]) -def wavefile_rw(request): +def wavefile_rw_existing(request): rw, open_func = request.param file = open_func(file_r, rw, request) request.addfinalizer(file.close) return file +@pytest.fixture(params=[('rw', open_filename), + ('rw', open_filehandle), + # rw is not permissable with bytestreams + ]) +def wavefile_rw_new(request): + rw, open_func = request.param + file = open_func(file_w, rw, request) + request.addfinalizer(file.close) + request.addfinalizer(lambda: os.remove(file_w)) + return file @pytest.fixture(params=[('r', open_filename), ('w', open_filename), @@ -115,14 +125,17 @@ def test_mode_should_be_in_read_mode(wavefile_r): def test_mode_should_be_in_read_mode(wavefile_w): assert wavefile_w.mode == 'w' +def test_mode_should_be_in_read_mode(wavefile_rw_existing): + assert wavefile_rw_existing.mode == 'rw' + def test_mode_read_should_start_at_beginning(wavefile_r): assert wavefile_r.seek(0, sf.SEEK_CUR) == 0 def test_mode_write_should_start_at_beginning(wavefile_w): assert wavefile_w.seek(0, sf.SEEK_CUR) == 0 -def test_mode_rw_should_start_at_end(wavefile_rw): - assert wavefile_rw.seek(0, sf.SEEK_CUR) == 5 +def test_mode_rw_should_start_at_end(wavefile_rw_existing): + assert wavefile_rw_existing.seek(0, sf.SEEK_CUR) == 5 def test_number_of_channels(wavefile_all): assert wavefile_all.channels == 2 @@ -261,8 +274,57 @@ def test_write_to_read_only_file_should_fail(wavefile_r): wavefile_r.write(data_r) def test_write_should_write_and_advance_write_pointer(wavefile_w): + position_w = wavefile_w.seek(0, sf.SEEK_CUR, which='w') + position_r = wavefile_w.seek(0, sf.SEEK_CUR, which='r') + wavefile_w.write(data_r) + assert wavefile_w.seek(0, sf.SEEK_CUR, which='w') == position_w+len(data_r) + assert wavefile_w.seek(0, sf.SEEK_CUR, which='r') == position_r + +def test_write_flush_should_write_to_disk(wavefile_w): + wavefile_w.flush() + size = os.path.getsize(file_w) wavefile_w.write(data_r) - assert wavefile_w.seek(0, sf.SEEK_CUR) == 5 + wavefile_w.flush() + assert os.path.getsize(file_w) == size + data_r.size*2 # 16 bit integer + +# ------------------------------------------------------------------------------ +# Test read/write +# ------------------------------------------------------------------------------ + +def test_rw_initial_read_and_write_pointer(wavefile_rw_existing): + assert wavefile_rw_existing.seek(0, sf.SEEK_CUR, which='w') == 5 + assert wavefile_rw_existing.seek(0, sf.SEEK_CUR, which='r') == 0 + +def test_rw_seek_write_should_advance_write_pointer(wavefile_rw_existing): + assert wavefile_rw_existing.seek(2, which='w') == 2 + assert wavefile_rw_existing.seek(0, sf.SEEK_CUR, which='r') == 0 + +def test_rw_seek_read_should_advance_read_pointer(wavefile_rw_existing): + assert wavefile_rw_existing.seek(2, which='r') == 2 + assert wavefile_rw_existing.seek(0, sf.SEEK_CUR, which='w') == 5 + +def test_rw_writing_float_should_be_written_approximately_correct(wavefile_rw_new): + data = np.ones((5,2), dtype='float64') + wavefile_rw_new.seek(0, which='w') + wavefile_rw_new.write(data) + written_data = wavefile_rw_new[-len(data):] + assert np.allclose(data, written_data, atol=2**-15) + +def test_rw_writing_int_should_be_written_exactly_correct(wavefile_rw_new): + data = np.zeros((5,2)) + 2**15-1 # full scale int16 + wavefile_rw_new.seek(0, which='w') + wavefile_rw_new.write(np.array(data, dtype='int16')) + written_data = wavefile_rw_new.read(dtype='int16') + assert np.all(data == written_data) + +def test_rw_writing_using_indexing_should_write_but_not_advance_write_pointer(wavefile_rw_new): + data = np.ones((5,2)) + wavefile_rw_new.write(np.zeros((5,2))) # grow file to make room for indexing + position = wavefile_rw_new.seek(0, sf.SEEK_CUR, which='w') + wavefile_rw_new[:len(data)] = data + written_data = wavefile_rw_new[:len(data)] + assert np.allclose(data, written_data, atol=2**-15) + assert position == wavefile_rw_new.seek(0, sf.SEEK_CUR, which='w') # ------------------------------------------------------------------------------ # Other tests @@ -284,6 +346,7 @@ def test_file_attributes_should_save_to_disk(): f.title = 'testing' with open_filename(file_w, 'r', None) as f: assert f.title == 'testing' + os.remove(file_w) def test_non_file_attributes_should_not_save_to_disk(): with open_filename(file_w, 'w', None) as f: @@ -291,6 +354,7 @@ def test_non_file_attributes_should_not_save_to_disk(): with open_filename(file_w, 'r', None) as f: with pytest.raises(AttributeError): f.foobar + os.remove(file_w) # ------------------------------------------------------------------------------ # Legacy tests @@ -310,30 +374,6 @@ def tearDown(self): os.remove(self.filename) -class TestBasicAttributesOfWaveFile(TestWaveFile): - - def test_rw_mode(self): - """Opening the file in rw mode should open in rw mode from end""" - with sf.SoundFile(self.filename, 'rw') as f: - self.assertEqual(f.mode, 'rw') - self.assertEqual(f.seek(0, sf.SEEK_CUR), len(f)) - - -class TestSeekWaveFile(TestWaveFile): - def test_seek_write(self): - """write-seeking should advance the write pointer""" - with sf.SoundFile(self.filename, 'rw') as f: - self.assertEqual(f.seek(100, which='w'), 100) - - def test_flush(self): - """After flushing, data should be written to disk""" - with sf.SoundFile(self.filename, 'rw') as f: - size = os.path.getsize(self.filename) - f.write(np.zeros((10,2))) - f.flush() - self.assertEqual(os.path.getsize(self.filename), size+40) - - class TestSeekWaveFile(TestWaveFile): def test_read_mono_into_out(self): """Reading mono signal into out should return data and write into out""" @@ -363,31 +403,3 @@ def test_read_mono_as_array(self): with sf.SoundFile(self.filename) as f: data = f.read(100, always_2d=False) self.assertEqual(data.shape, (100,)) - -class TestWriteWaveFile(TestWaveFile): - def test_write_float_precision(self): - """Written float data should be written at most 2**-15 off""" - with sf.SoundFile(self.filename, 'rw') as f: - data = np.ones((100,2)) - f.write(data) - written_data = f[-100:] - self.assertTrue(np.allclose(data, written_data, atol=2**-15)) - - def test_write_int_precision(self): - """Written int data should be written""" - with sf.SoundFile(self.filename, 'rw') as f: - data = np.zeros((100,2)) + 2**15-1 # full scale int16 - data = np.array(data, dtype='int16') - f.write(data) - f.seek(-100, sf.SEEK_CUR) - written_data = f.read(dtype='int16') - self.assertTrue(np.all(data == written_data)) - - def test_write_indexing(self): - """Writing using indexing should write but not advance write pointer""" - with sf.SoundFile(self.filename, 'rw') as f: - position = f.seek(0, sf.SEEK_CUR) - data = np.zeros((100,2)) - f[:100] = data - self.assertEqual(position, f.seek(0, sf.SEEK_CUR)) - self.assertTrue(np.all(data == f[:100])) From b5d7a0af5405289afb921d8e9cc6f22174d04d4d Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Wed, 18 Jun 2014 11:18:41 +0200 Subject: [PATCH 07/14] added mono tests --- tests/test_pysoundfile.py | 91 ++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 53 deletions(-) diff --git a/tests/test_pysoundfile.py b/tests/test_pysoundfile.py index 0415a4c..f5cb6fb 100644 --- a/tests/test_pysoundfile.py +++ b/tests/test_pysoundfile.py @@ -7,6 +7,8 @@ data_r = np.ones((5,2))*0.5 file_r = 'tests/test_r.wav' +data_r_mono = np.ones(5)*0.5 +file_r_mono = 'tests/test_r_mono.wav' file_w = 'tests/test_w.wav' def open_filename(filename, rw, _): @@ -14,9 +16,9 @@ def open_filename(filename, rw, _): return sf.SoundFile(filename) elif rw == 'w': return sf.SoundFile(filename, mode=rw, sample_rate=44100, channels=2) - elif rw == 'rw' and filename == file_r: + elif rw == 'rw' and 'test_r' in filename: return sf.SoundFile(filename, mode=rw) - elif rw == 'rw' and filename == file_w: + elif rw == 'rw' and 'test_w' in filename: return sf.SoundFile(filename, mode=rw, sample_rate=44100, channels=2) def open_filehandle(filename, rw, _): @@ -30,9 +32,9 @@ def open_filehandle(filename, rw, _): handle = os.open(filename, os.O_RDWR) elif rw =='rw' and filename == file_w: handle = os.open(filename, os.O_CREAT | os.O_RDWR) - if filename == file_r: + if 'test_r' in filename: return sf.SoundFile(handle, mode=rw) - elif filename == file_w: + elif 'test_w' in filename: return sf.SoundFile(handle, mode=rw, sample_rate=44100, channels=2, format='wav') def open_bytestream(filename, rw, request): @@ -44,9 +46,9 @@ def open_bytestream(filename, rw, request): bytesio = open(filename, 'a+b') elif rw == 'rw' and filename == file_w: bytesio = open(filename, 'w+b') - if filename == file_r: + if 'test_r' in filename: file = sf.SoundFile(bytesio, mode=rw) - elif filename == file_w: + elif 'test_w' in filename: file = sf.SoundFile(bytesio, mode=rw, sample_rate=44100, channels=2, format='wav') request.addfinalizer(bytesio.close) return file @@ -60,6 +62,15 @@ def wavefile_r(request): request.addfinalizer(file.close) return file +@pytest.fixture(params=[('r', open_filename), + ('r', open_filehandle), + ('r', open_bytestream)]) +def wavefile_r_mono(request): + rw, open_func = request.param + file = open_func(file_r_mono, rw, request) + request.addfinalizer(file.close) + return file + @pytest.fixture(params=[('w', open_filename), ('w', open_filehandle), ('w', open_bytestream)]) @@ -356,50 +367,24 @@ def test_non_file_attributes_should_not_save_to_disk(): f.foobar os.remove(file_w) -# ------------------------------------------------------------------------------ -# Legacy tests -# ------------------------------------------------------------------------------ - -class TestWaveFile(unittest.TestCase): - def setUp(self): - """create a dummy wave file""" - self.sample_rate = 44100 - self.channels = 2 - self.filename = 'test.wav' - self.data = np.ones((self.sample_rate, self.channels))*0.5 - with sf.SoundFile(self.filename, 'w', self.sample_rate, self.channels) as f: - f.write(self.data) - - def tearDown(self): - os.remove(self.filename) - - -class TestSeekWaveFile(TestWaveFile): - def test_read_mono_into_out(self): - """Reading mono signal into out should return data and write into out""" - # create a dummy mono wave file - self.sample_rate = 44100 - self.channels = 1 - self.filename = 'test.wav' - self.data = np.ones((self.sample_rate, self.channels))*0.5 - with sf.SoundFile(self.filename, 'w', self.sample_rate, self.channels) as f: - f.write(self.data) - - with sf.SoundFile(self.filename) as f: - data = np.empty((100, f.channels), dtype='float64') - out_data = f.read(out=data) - self.assertTrue(np.all(data == out_data)) - - def test_read_mono_as_array(self): - """Reading with always_2d=False should return array""" - # create a dummy mono wave file - self.sample_rate = 44100 - self.channels = 1 - self.filename = 'test.wav' - self.data = np.ones((self.sample_rate, self.channels))*0.5 - with sf.SoundFile(self.filename, 'w', self.sample_rate, self.channels) as f: - f.write(self.data) - - with sf.SoundFile(self.filename) as f: - data = f.read(100, always_2d=False) - self.assertEqual(data.shape, (100,)) +def test_read_mono_without_always2d_should_read_array(wavefile_r_mono): + out_data = wavefile_r_mono.read(always_2d=False) + assert np.all(out_data == data_r_mono) + assert out_data.ndim == 1 + +def test_read_mono_should_read_matrix(wavefile_r_mono): + out_data = wavefile_r_mono.read() + assert np.all(out_data == data_r_mono) + assert out_data.ndim == 2 + +def test_read_mono_into_mono_out_should_read_into_out(wavefile_r_mono): + data = np.empty(5, dtype='float64') + out_data = wavefile_r_mono.read(out=data) + assert np.all(data == out_data) + assert id(data) == id(out_data) + +def test_read_mono_into_out_should_read_into_out(wavefile_r_mono): + data = np.empty((5, 1), dtype='float64') + out_data = wavefile_r_mono.read(out=data) + assert np.all(data == out_data) + assert id(data) == id(out_data) From 88bd737f49c85912095483329b3011aff35da98d Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Wed, 18 Jun 2014 11:20:11 +0200 Subject: [PATCH 08/14] add another test file. This should have been part of the last commit. --- tests/test_r_mono.wav | Bin 0 -> 54 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/test_r_mono.wav diff --git a/tests/test_r_mono.wav b/tests/test_r_mono.wav new file mode 100644 index 0000000000000000000000000000000000000000..5df13c4bc6748d8bbd28697e1214af5fb165efb8 GIT binary patch literal 54 ycmWIYbaT^VU| Date: Wed, 18 Jun 2014 11:45:27 +0200 Subject: [PATCH 09/14] Remove obsolete imports --- tests/test_pysoundfile.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_pysoundfile.py b/tests/test_pysoundfile.py index f5cb6fb..720e8e4 100644 --- a/tests/test_pysoundfile.py +++ b/tests/test_pysoundfile.py @@ -1,8 +1,6 @@ -import unittest import pysoundfile as sf import numpy as np import os -import io import pytest data_r = np.ones((5,2))*0.5 From 1db46c00df85d82f634d8718594d3ab690d02c3e Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Wed, 18 Jun 2014 11:48:15 +0200 Subject: [PATCH 10/14] Rename duplicate test functions --- tests/test_pysoundfile.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/test_pysoundfile.py b/tests/test_pysoundfile.py index 720e8e4..f22b32a 100644 --- a/tests/test_pysoundfile.py +++ b/tests/test_pysoundfile.py @@ -131,10 +131,12 @@ def test_file_content(wavefile_r): def test_mode_should_be_in_read_mode(wavefile_r): assert wavefile_r.mode == 'r' -def test_mode_should_be_in_read_mode(wavefile_w): + +def test_mode_should_be_in_write_mode(wavefile_w): assert wavefile_w.mode == 'w' -def test_mode_should_be_in_read_mode(wavefile_rw_existing): + +def test_mode_should_be_in_readwrite_mode(wavefile_rw_existing): assert wavefile_rw_existing.mode == 'rw' def test_mode_read_should_start_at_beginning(wavefile_r): @@ -159,10 +161,12 @@ def test_format_metadata(wavefile_all): assert wavefile_all.format_info == 'WAV (Microsoft)' assert wavefile_all.subtype_info == 'Signed 16 bit PCM' -def test_data_length(wavefile_r): + +def test_data_length_r(wavefile_r): assert len(wavefile_r) == len(data_r) -def test_data_length(wavefile_w): + +def test_data_length_w(wavefile_w): assert len(wavefile_w) == 0 def test_file_exists(wavefile_w): @@ -185,7 +189,8 @@ def test_seek_to_end_should_advance_read_pointer_to_end(wavefile_r): def test_seek_read_pointer_should_advance_read_pointer(wavefile_r): assert wavefile_r.seek(2, which='r') == 2 -def test_seek_read_pointer_should_advance_read_pointer(wavefile_w): + +def test_seek_write_pointer_should_advance_write_pointer(wavefile_w): assert wavefile_w.seek(2, which='w') == 2 # ------------------------------------------------------------------------------ From 48163156580003e1d686806259d7c5bd2c157ecd Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Wed, 18 Jun 2014 11:49:48 +0200 Subject: [PATCH 11/14] Some (mainly automatic) PEP 8 formatting changes --- tests/test_pysoundfile.py | 121 ++++++++++++++++++++++++++++++-------- 1 file changed, 95 insertions(+), 26 deletions(-) diff --git a/tests/test_pysoundfile.py b/tests/test_pysoundfile.py index f22b32a..553a6c9 100644 --- a/tests/test_pysoundfile.py +++ b/tests/test_pysoundfile.py @@ -3,11 +3,12 @@ import os import pytest -data_r = np.ones((5,2))*0.5 +data_r = np.ones((5, 2))*0.5 file_r = 'tests/test_r.wav' data_r_mono = np.ones(5)*0.5 file_r_mono = 'tests/test_r_mono.wav' -file_w = 'tests/test_w.wav' +file_w = 'tests/test_w.wav' + def open_filename(filename, rw, _): if rw == 'r': @@ -19,6 +20,7 @@ def open_filename(filename, rw, _): elif rw == 'rw' and 'test_w' in filename: return sf.SoundFile(filename, mode=rw, sample_rate=44100, channels=2) + def open_filehandle(filename, rw, _): # TODO: does sf.SoundFile auto-close the handle??? # request.addfinalizer(lambda: os.close(handle)) @@ -28,12 +30,14 @@ def open_filehandle(filename, rw, _): handle = os.open(filename, os.O_CREAT | os.O_WRONLY) elif rw == 'rw' and filename == file_r: handle = os.open(filename, os.O_RDWR) - elif rw =='rw' and filename == file_w: + elif rw == 'rw' and filename == file_w: handle = os.open(filename, os.O_CREAT | os.O_RDWR) if 'test_r' in filename: return sf.SoundFile(handle, mode=rw) elif 'test_w' in filename: - return sf.SoundFile(handle, mode=rw, sample_rate=44100, channels=2, format='wav') + return sf.SoundFile(handle, mode=rw, sample_rate=44100, + channels=2, format='wav') + def open_bytestream(filename, rw, request): if rw == 'r': @@ -47,10 +51,12 @@ def open_bytestream(filename, rw, request): if 'test_r' in filename: file = sf.SoundFile(bytesio, mode=rw) elif 'test_w' in filename: - file = sf.SoundFile(bytesio, mode=rw, sample_rate=44100, channels=2, format='wav') + file = sf.SoundFile(bytesio, mode=rw, sample_rate=44100, + channels=2, format='wav') request.addfinalizer(bytesio.close) return file + @pytest.fixture(params=[('r', open_filename), ('r', open_filehandle), ('r', open_bytestream)]) @@ -60,6 +66,7 @@ def wavefile_r(request): request.addfinalizer(file.close) return file + @pytest.fixture(params=[('r', open_filename), ('r', open_filehandle), ('r', open_bytestream)]) @@ -69,6 +76,7 @@ def wavefile_r_mono(request): request.addfinalizer(file.close) return file + @pytest.fixture(params=[('w', open_filename), ('w', open_filehandle), ('w', open_bytestream)]) @@ -79,6 +87,7 @@ def wavefile_w(request): request.addfinalizer(lambda: os.remove(file_w)) return file + @pytest.fixture(params=[('rw', open_filename), ('rw', open_filehandle), # rw is not permissable with bytestreams @@ -89,6 +98,7 @@ def wavefile_rw_existing(request): request.addfinalizer(file.close) return file + @pytest.fixture(params=[('rw', open_filename), ('rw', open_filehandle), # rw is not permissable with bytestreams @@ -100,6 +110,7 @@ def wavefile_rw_new(request): request.addfinalizer(lambda: os.remove(file_w)) return file + @pytest.fixture(params=[('r', open_filename), ('w', open_filename), ('rw', open_filename), @@ -121,13 +132,16 @@ def wavefile_all(request): request.addfinalizer(lambda: os.remove(file_w)) return file -# ------------------------------------------------------------------------------ + +# ----------------------------------------------------------------------------- # Test file metadata -# ------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- + def test_file_content(wavefile_r): assert np.all(data_r == wavefile_r[:]) + def test_mode_should_be_in_read_mode(wavefile_r): assert wavefile_r.mode == 'r' @@ -139,21 +153,27 @@ def test_mode_should_be_in_write_mode(wavefile_w): def test_mode_should_be_in_readwrite_mode(wavefile_rw_existing): assert wavefile_rw_existing.mode == 'rw' + def test_mode_read_should_start_at_beginning(wavefile_r): assert wavefile_r.seek(0, sf.SEEK_CUR) == 0 + def test_mode_write_should_start_at_beginning(wavefile_w): assert wavefile_w.seek(0, sf.SEEK_CUR) == 0 + def test_mode_rw_should_start_at_end(wavefile_rw_existing): assert wavefile_rw_existing.seek(0, sf.SEEK_CUR) == 5 + def test_number_of_channels(wavefile_all): assert wavefile_all.channels == 2 + def test_sample_rate(wavefile_all): assert wavefile_all.sample_rate == 44100 + def test_format_metadata(wavefile_all): assert wavefile_all.format == 'WAV' assert wavefile_all.subtype == 'PCM_16' @@ -169,23 +189,29 @@ def test_data_length_r(wavefile_r): def test_data_length_w(wavefile_w): assert len(wavefile_w) == 0 + def test_file_exists(wavefile_w): assert os.path.isfile(file_w) -# ------------------------------------------------------------------------------ + +# ----------------------------------------------------------------------------- # Test seek -# ------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- + def test_seek_should_advance_read_pointer(wavefile_r): assert wavefile_r.seek(2) == 2 + def test_seek_multiple_times_should_advance_read_pointer(wavefile_r): wavefile_r.seek(2) assert wavefile_r.seek(2, whence=sf.SEEK_CUR) == 4 + def test_seek_to_end_should_advance_read_pointer_to_end(wavefile_r): assert wavefile_r.seek(-2, whence=sf.SEEK_END) == 3 + def test_seek_read_pointer_should_advance_read_pointer(wavefile_r): assert wavefile_r.seek(2, which='r') == 2 @@ -193,46 +219,58 @@ def test_seek_read_pointer_should_advance_read_pointer(wavefile_r): def test_seek_write_pointer_should_advance_write_pointer(wavefile_w): assert wavefile_w.seek(2, which='w') == 2 -# ------------------------------------------------------------------------------ + +# ----------------------------------------------------------------------------- # Test read -# ------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- + def test_read_write_only(wavefile_w): with pytest.raises(RuntimeError): wavefile_w.read(2) + def test_read_should_read_data_and_advance_read_pointer(wavefile_r): data = wavefile_r.read(2) assert np.all(data == data_r[:2]) assert wavefile_r.seek(0, sf.SEEK_CUR) == 2 + def test_read_should_read_float64_data(wavefile_r): assert wavefile_r[:].dtype == np.float64 + def test_read_int16_should_read_int16_data(wavefile_r): assert wavefile_r.read(2, dtype='int16').dtype == np.int16 + def test_read_int32_should_read_int32_data(wavefile_r): assert wavefile_r.read(2, dtype='int32').dtype == np.int32 + def test_read_float32_should_read_float32_data(wavefile_r): assert wavefile_r.read(2, dtype='float32').dtype == np.float32 + def test_read_by_indexing_should_read_but_not_advance_read_pointer(wavefile_r): assert np.all(wavefile_r[:2] == data_r[:2]) assert wavefile_r.seek(0, sf.SEEK_CUR) == 0 + def test_read_n_frames_should_return_n_frames(wavefile_r): assert len(wavefile_r.read(2)) == 2 + def test_read_all_frames_should_read_all_remaining_frames(wavefile_r): wavefile_r.seek(-2, sf.SEEK_END) assert np.all(wavefile_r.read() == data_r[-2:]) + def test_read_over_end_should_return_only_remaining_frames(wavefile_r): wavefile_r.seek(-2, sf.SEEK_END) assert np.all(wavefile_r.read(4) == data_r[-2:]) + def test_read_over_end_with_fill_should_reaturn_asked_frames(wavefile_r): wavefile_r.seek(-2, sf.SEEK_END) data = wavefile_r.read(4, fill_value=0) @@ -240,21 +278,25 @@ def test_read_over_end_with_fill_should_reaturn_asked_frames(wavefile_r): assert np.all(data[2:] == 0) assert len(data) == 4 + def test_read_into_out_should_return_data_and_write_into_out(wavefile_r): out = np.empty((2, wavefile_r.channels), dtype='float64') data = wavefile_r.read(out=out) assert np.all(data == out) + def test_read_into_malformed_out_should_fail(wavefile_r): out = np.empty((2, wavefile_r.channels+1), dtype='float64') with pytest.raises(ValueError): wavefile_r.read(out=out) + def test_read_into_out_with_too_many_dimensions_should_fail(wavefile_r): out = np.empty((2, wavefile_r.channels, 1), dtype='float64') with pytest.raises(ValueError): wavefile_r.read(out=out) + def test_read_into_zero_len_out_should_not_read_anything(wavefile_r): out = np.empty((0, wavefile_r.channels), dtype='float64') data = wavefile_r.read(out=out) @@ -262,7 +304,9 @@ def test_read_into_zero_len_out_should_not_read_anything(wavefile_r): assert len(out) == 0 assert wavefile_r.seek(0, sf.SEEK_CUR) == 0 -def test_read_into_out_over_end_should_return_shorter_data_and_write_into_out(wavefile_r): + +def test_read_into_out_over_end_should_return_shorter_data_and_write_into_out( + wavefile_r): out = np.ones((4, wavefile_r.channels), dtype='float64') wavefile_r.seek(-2, sf.SEEK_END) data = wavefile_r.read(out=out) @@ -271,7 +315,9 @@ def test_read_into_out_over_end_should_return_shorter_data_and_write_into_out(wa assert out.shape == (4, wavefile_r.channels) assert data.shape == (2, wavefile_r.channels) -def test_read_into_out_over_end_with_fill_should_return_full_data_and_write_into_out(wavefile_r): + +def test_read_into_out_over_end_with_fill_should_return_full_data_and_write_into_out( + wavefile_r): out = np.ones((4, wavefile_r.channels), dtype='float64') wavefile_r.seek(-2, sf.SEEK_END) data = wavefile_r.read(out=out, fill_value=0) @@ -279,14 +325,17 @@ def test_read_into_out_over_end_with_fill_should_return_full_data_and_write_into assert np.all(data[2:] == 0) assert out.shape == (4, wavefile_r.channels) -# ------------------------------------------------------------------------------ + +# ----------------------------------------------------------------------------- # Test write -# ------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- + def test_write_to_read_only_file_should_fail(wavefile_r): with pytest.raises(RuntimeError): wavefile_r.write(data_r) + def test_write_should_write_and_advance_write_pointer(wavefile_w): position_w = wavefile_w.seek(0, sf.SEEK_CUR, which='w') position_r = wavefile_w.seek(0, sf.SEEK_CUR, which='r') @@ -294,67 +343,82 @@ def test_write_should_write_and_advance_write_pointer(wavefile_w): assert wavefile_w.seek(0, sf.SEEK_CUR, which='w') == position_w+len(data_r) assert wavefile_w.seek(0, sf.SEEK_CUR, which='r') == position_r + def test_write_flush_should_write_to_disk(wavefile_w): wavefile_w.flush() size = os.path.getsize(file_w) wavefile_w.write(data_r) wavefile_w.flush() - assert os.path.getsize(file_w) == size + data_r.size*2 # 16 bit integer + assert os.path.getsize(file_w) == size + data_r.size*2 # 16 bit integer + -# ------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # Test read/write -# ------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- + def test_rw_initial_read_and_write_pointer(wavefile_rw_existing): assert wavefile_rw_existing.seek(0, sf.SEEK_CUR, which='w') == 5 assert wavefile_rw_existing.seek(0, sf.SEEK_CUR, which='r') == 0 + def test_rw_seek_write_should_advance_write_pointer(wavefile_rw_existing): assert wavefile_rw_existing.seek(2, which='w') == 2 assert wavefile_rw_existing.seek(0, sf.SEEK_CUR, which='r') == 0 + def test_rw_seek_read_should_advance_read_pointer(wavefile_rw_existing): assert wavefile_rw_existing.seek(2, which='r') == 2 assert wavefile_rw_existing.seek(0, sf.SEEK_CUR, which='w') == 5 -def test_rw_writing_float_should_be_written_approximately_correct(wavefile_rw_new): - data = np.ones((5,2), dtype='float64') + +def test_rw_writing_float_should_be_written_approximately_correct( + wavefile_rw_new): + data = np.ones((5, 2), dtype='float64') wavefile_rw_new.seek(0, which='w') wavefile_rw_new.write(data) written_data = wavefile_rw_new[-len(data):] assert np.allclose(data, written_data, atol=2**-15) + def test_rw_writing_int_should_be_written_exactly_correct(wavefile_rw_new): - data = np.zeros((5,2)) + 2**15-1 # full scale int16 + data = np.zeros((5, 2)) + 2**15 - 1 # full scale int16 wavefile_rw_new.seek(0, which='w') wavefile_rw_new.write(np.array(data, dtype='int16')) written_data = wavefile_rw_new.read(dtype='int16') assert np.all(data == written_data) -def test_rw_writing_using_indexing_should_write_but_not_advance_write_pointer(wavefile_rw_new): - data = np.ones((5,2)) - wavefile_rw_new.write(np.zeros((5,2))) # grow file to make room for indexing + +def test_rw_writing_using_indexing_should_write_but_not_advance_write_pointer( + wavefile_rw_new): + data = np.ones((5, 2)) + # grow file to make room for indexing + wavefile_rw_new.write(np.zeros((5, 2))) position = wavefile_rw_new.seek(0, sf.SEEK_CUR, which='w') wavefile_rw_new[:len(data)] = data written_data = wavefile_rw_new[:len(data)] assert np.allclose(data, written_data, atol=2**-15) assert position == wavefile_rw_new.seek(0, sf.SEEK_CUR, which='w') -# ------------------------------------------------------------------------------ + +# ----------------------------------------------------------------------------- # Other tests -# ------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- + def test_context_manager_should_open_and_close_file(): with open_filename(file_r, 'r', None) as f: assert not f.closed assert f.closed + def test_closing_should_close_file(): f = open_filename(file_r, 'r', None) assert not f.closed f.close() assert f.closed + def test_file_attributes_should_save_to_disk(): with open_filename(file_w, 'w', None) as f: f.title = 'testing' @@ -362,6 +426,7 @@ def test_file_attributes_should_save_to_disk(): assert f.title == 'testing' os.remove(file_w) + def test_non_file_attributes_should_not_save_to_disk(): with open_filename(file_w, 'w', None) as f: f.foobar = 'testing' @@ -370,22 +435,26 @@ def test_non_file_attributes_should_not_save_to_disk(): f.foobar os.remove(file_w) + def test_read_mono_without_always2d_should_read_array(wavefile_r_mono): out_data = wavefile_r_mono.read(always_2d=False) assert np.all(out_data == data_r_mono) assert out_data.ndim == 1 + def test_read_mono_should_read_matrix(wavefile_r_mono): out_data = wavefile_r_mono.read() assert np.all(out_data == data_r_mono) assert out_data.ndim == 2 + def test_read_mono_into_mono_out_should_read_into_out(wavefile_r_mono): data = np.empty(5, dtype='float64') out_data = wavefile_r_mono.read(out=data) assert np.all(data == out_data) assert id(data) == id(out_data) + def test_read_mono_into_out_should_read_into_out(wavefile_r_mono): data = np.empty((5, 1), dtype='float64') out_data = wavefile_r_mono.read(out=data) From 15eac9e5ed713fdc2a38624de88f3411d815cdf8 Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Wed, 18 Jun 2014 18:25:11 +0200 Subject: [PATCH 12/14] fix for reading RAW files also added regression test for this --- pysoundfile.py | 20 +++++++++++--------- tests/test_pysoundfile.py | 24 ++++++++++++++++++++++++ tests/test_r.raw | Bin 0 -> 20 bytes 3 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 tests/test_r.raw diff --git a/pysoundfile.py b/pysoundfile.py index e56a5a9..d6c018f 100644 --- a/pysoundfile.py +++ b/pysoundfile.py @@ -333,26 +333,28 @@ def __init__(self, file, mode='r', sample_rate=None, channels=None, raise ValueError("Invalid mode: %s" % repr(mode)) original_format = format - if format is None and 'w' in self.mode: - filename = getattr(file, 'name', file) - format = str(filename).rsplit('.', 1)[-1].upper() - if format not in _formats: + filename = getattr(file, 'name', file) + file_extension = str(filename).rsplit('.', 1)[-1].upper() + if format is None and ('w' in self.mode or + file_extension == 'RAW'): + if file_extension not in _formats: if self.mode == 'w': raise TypeError( "No format specified and unable to get format from " "file extension: %s" % repr(filename)) - else: - # format is optional for self.mode == 'rw' - format = original_format + else: + format = file_extension self._info = _ffi.new("SF_INFO*") if self.mode == 'w' or str(format).upper() == 'RAW': if sample_rate is None: - raise TypeError("sample_rate must be specified") + raise ValueError("sample_rate must be specified") self._info.samplerate = sample_rate if channels is None: - raise TypeError("channels must be specified") + raise ValueError("channels must be specified") self._info.channels = channels + if str(format).upper() == 'RAW' and subtype is None: + raise ValueError("RAW files must specify a subtype") self._info.format = _format_int(format, subtype, endian) elif self.mode == 'rw': if sample_rate is not None: diff --git a/tests/test_pysoundfile.py b/tests/test_pysoundfile.py index 553a6c9..676fe36 100644 --- a/tests/test_pysoundfile.py +++ b/tests/test_pysoundfile.py @@ -7,6 +7,8 @@ file_r = 'tests/test_r.wav' data_r_mono = np.ones(5)*0.5 file_r_mono = 'tests/test_r_mono.wav' +data_r_raw = np.ones((5, 2))*0.5 +file_r_raw = 'tests/test_r.raw' file_w = 'tests/test_w.wav' @@ -460,3 +462,25 @@ def test_read_mono_into_out_should_read_into_out(wavefile_r_mono): out_data = wavefile_r_mono.read(out=data) assert np.all(data == out_data) assert id(data) == id(out_data) + + +# ----------------------------------------------------------------------------- +# RAW tests +# ----------------------------------------------------------------------------- + + +def test_read_raw_files_should_read_data(): + with sf.SoundFile(file_r_raw, sample_rate=44100, + channels=2, subtype='PCM_16') as f: + assert np.all(f.read() == data_r_raw) + + +def test_read_raw_files_with_too_few_arguments_should_fail(): + with pytest.raises(ValueError): # missing everything + sf.SoundFile(file_r_raw) + with pytest.raises(ValueError): # missing subtype + sf.SoundFile(file_r_raw, sample_rate=44100, channels=2) + with pytest.raises(ValueError): # missing channels + sf.SoundFile(file_r_raw, sample_rate=44100, subtype='PCM_16') + with pytest.raises(ValueError): # missing sample_rate + sf.SoundFile(file_r_raw, channels=2, subtype='PCM_16') diff --git a/tests/test_r.raw b/tests/test_r.raw new file mode 100644 index 0000000000000000000000000000000000000000..1ad6a7cec3db9752bd3df4d3b2ea2dc7ab016790 GIT binary patch literal 20 LcmZQ@V8DO?859D6 literal 0 HcmV?d00001 From 4f32ae16094616d28d51dbe475ace0874e7487ed Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Wed, 18 Jun 2014 19:03:29 +0200 Subject: [PATCH 13/14] changed test files to contain unique data thus testing for data permutations --- tests/test_pysoundfile.py | 44 ++++++++++++++++++++++---------------- tests/test_r.raw | Bin 20 -> 20 bytes tests/test_r.wav | Bin 64 -> 64 bytes tests/test_r_mono.wav | Bin 54 -> 54 bytes 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/tests/test_pysoundfile.py b/tests/test_pysoundfile.py index 676fe36..1c219d2 100644 --- a/tests/test_pysoundfile.py +++ b/tests/test_pysoundfile.py @@ -3,15 +3,23 @@ import os import pytest -data_r = np.ones((5, 2))*0.5 +data_r = np.array([[1.0, -1.0], + [0.8, -0.8], + [0.6, -0.6], + [0.4, -0.4], + [0.2, -0.2]]) file_r = 'tests/test_r.wav' -data_r_mono = np.ones(5)*0.5 +data_r_mono = np.array([1.0, 0.8, 0.6, 0.4, 0.2]) file_r_mono = 'tests/test_r_mono.wav' -data_r_raw = np.ones((5, 2))*0.5 +data_r_raw = np.array(data_r, copy=True) file_r_raw = 'tests/test_r.raw' file_w = 'tests/test_w.wav' +def allclose(x, y): + return np.allclose(x, y, atol=2**-15) + + def open_filename(filename, rw, _): if rw == 'r': return sf.SoundFile(filename) @@ -141,7 +149,7 @@ def wavefile_all(request): def test_file_content(wavefile_r): - assert np.all(data_r == wavefile_r[:]) + assert allclose(data_r, wavefile_r[:]) def test_mode_should_be_in_read_mode(wavefile_r): @@ -234,7 +242,7 @@ def test_read_write_only(wavefile_w): def test_read_should_read_data_and_advance_read_pointer(wavefile_r): data = wavefile_r.read(2) - assert np.all(data == data_r[:2]) + assert allclose(data, data_r[:2]) assert wavefile_r.seek(0, sf.SEEK_CUR) == 2 @@ -255,7 +263,7 @@ def test_read_float32_should_read_float32_data(wavefile_r): def test_read_by_indexing_should_read_but_not_advance_read_pointer(wavefile_r): - assert np.all(wavefile_r[:2] == data_r[:2]) + assert allclose(wavefile_r[:2], data_r[:2]) assert wavefile_r.seek(0, sf.SEEK_CUR) == 0 @@ -265,18 +273,18 @@ def test_read_n_frames_should_return_n_frames(wavefile_r): def test_read_all_frames_should_read_all_remaining_frames(wavefile_r): wavefile_r.seek(-2, sf.SEEK_END) - assert np.all(wavefile_r.read() == data_r[-2:]) + assert allclose(wavefile_r.read(), data_r[-2:]) def test_read_over_end_should_return_only_remaining_frames(wavefile_r): wavefile_r.seek(-2, sf.SEEK_END) - assert np.all(wavefile_r.read(4) == data_r[-2:]) + assert allclose(wavefile_r.read(4), data_r[-2:]) def test_read_over_end_with_fill_should_reaturn_asked_frames(wavefile_r): wavefile_r.seek(-2, sf.SEEK_END) data = wavefile_r.read(4, fill_value=0) - assert np.all(data[:2] == data_r[-2:]) + assert allclose(data[:2], data_r[-2:]) assert np.all(data[2:] == 0) assert len(data) == 4 @@ -380,7 +388,7 @@ def test_rw_writing_float_should_be_written_approximately_correct( wavefile_rw_new.seek(0, which='w') wavefile_rw_new.write(data) written_data = wavefile_rw_new[-len(data):] - assert np.allclose(data, written_data, atol=2**-15) + assert allclose(data, written_data) def test_rw_writing_int_should_be_written_exactly_correct(wavefile_rw_new): @@ -399,7 +407,7 @@ def test_rw_writing_using_indexing_should_write_but_not_advance_write_pointer( position = wavefile_rw_new.seek(0, sf.SEEK_CUR, which='w') wavefile_rw_new[:len(data)] = data written_data = wavefile_rw_new[:len(data)] - assert np.allclose(data, written_data, atol=2**-15) + assert allclose(data, written_data) assert position == wavefile_rw_new.seek(0, sf.SEEK_CUR, which='w') @@ -440,13 +448,13 @@ def test_non_file_attributes_should_not_save_to_disk(): def test_read_mono_without_always2d_should_read_array(wavefile_r_mono): out_data = wavefile_r_mono.read(always_2d=False) - assert np.all(out_data == data_r_mono) + assert allclose(out_data, data_r_mono) assert out_data.ndim == 1 def test_read_mono_should_read_matrix(wavefile_r_mono): out_data = wavefile_r_mono.read() - assert np.all(out_data == data_r_mono) + assert allclose(out_data, [[x] for x in data_r_mono]) assert out_data.ndim == 2 @@ -472,15 +480,15 @@ def test_read_mono_into_out_should_read_into_out(wavefile_r_mono): def test_read_raw_files_should_read_data(): with sf.SoundFile(file_r_raw, sample_rate=44100, channels=2, subtype='PCM_16') as f: - assert np.all(f.read() == data_r_raw) + assert allclose(f.read(), data_r_raw) def test_read_raw_files_with_too_few_arguments_should_fail(): - with pytest.raises(ValueError): # missing everything + with pytest.raises(ValueError): # missing everything sf.SoundFile(file_r_raw) - with pytest.raises(ValueError): # missing subtype + with pytest.raises(ValueError): # missing subtype sf.SoundFile(file_r_raw, sample_rate=44100, channels=2) - with pytest.raises(ValueError): # missing channels + with pytest.raises(ValueError): # missing channels sf.SoundFile(file_r_raw, sample_rate=44100, subtype='PCM_16') - with pytest.raises(ValueError): # missing sample_rate + with pytest.raises(ValueError): # missing sample_rate sf.SoundFile(file_r_raw, channels=2, subtype='PCM_16') diff --git a/tests/test_r.raw b/tests/test_r.raw index 1ad6a7cec3db9752bd3df4d3b2ea2dc7ab016790..d33622ac8d761e9b26e0083ad5b74735946c4f6e 100644 GIT binary patch literal 20 ccmexg&)ATbHf!b?ACt|-#%IsWluUmH0B73?`~Uy| literal 20 LcmZQ@V8DO?859D6 diff --git a/tests/test_r.wav b/tests/test_r.wav index bad9ffbe3df1984b927c7cbe1552fcc02bc69a1f..99ad77c6e5065d96903ff10d0289ddb7a87bc03e 100644 GIT binary patch delta 25 hcmZ>8n4lx_zn-xnEp67!Gd?Dpjg8NqnJJn63;>3F3rqk2 delta 25 PcmZ>8n4lxV;D8AMH?;%N diff --git a/tests/test_r_mono.wav b/tests/test_r_mono.wav index 5df13c4bc6748d8bbd28697e1214af5fb165efb8..c7e90c135847b6249266af60cb72df38fa35f094 100644 GIT binary patch delta 15 WcmXpro1nw>zdkMPjE}MLOi2JORt2p9 delta 15 PcmXpro1nwR-~fXF7CQoS From 0982d6029ff70e28efe0ca765b90bd8a69d6f280 Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Fri, 20 Jun 2014 16:17:34 +0200 Subject: [PATCH 14/14] Revert ValueError to TypeError for missing args Sorry for the mess. --- pysoundfile.py | 6 +++--- tests/test_pysoundfile.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pysoundfile.py b/pysoundfile.py index d6c018f..76a245c 100644 --- a/pysoundfile.py +++ b/pysoundfile.py @@ -348,13 +348,13 @@ def __init__(self, file, mode='r', sample_rate=None, channels=None, self._info = _ffi.new("SF_INFO*") if self.mode == 'w' or str(format).upper() == 'RAW': if sample_rate is None: - raise ValueError("sample_rate must be specified") + raise TypeError("sample_rate must be specified") self._info.samplerate = sample_rate if channels is None: - raise ValueError("channels must be specified") + raise TypeError("channels must be specified") self._info.channels = channels if str(format).upper() == 'RAW' and subtype is None: - raise ValueError("RAW files must specify a subtype") + raise TypeError("RAW files must specify a subtype") self._info.format = _format_int(format, subtype, endian) elif self.mode == 'rw': if sample_rate is not None: diff --git a/tests/test_pysoundfile.py b/tests/test_pysoundfile.py index 1c219d2..9bd0fe3 100644 --- a/tests/test_pysoundfile.py +++ b/tests/test_pysoundfile.py @@ -484,11 +484,11 @@ def test_read_raw_files_should_read_data(): def test_read_raw_files_with_too_few_arguments_should_fail(): - with pytest.raises(ValueError): # missing everything + with pytest.raises(TypeError): # missing everything sf.SoundFile(file_r_raw) - with pytest.raises(ValueError): # missing subtype + with pytest.raises(TypeError): # missing subtype sf.SoundFile(file_r_raw, sample_rate=44100, channels=2) - with pytest.raises(ValueError): # missing channels + with pytest.raises(TypeError): # missing channels sf.SoundFile(file_r_raw, sample_rate=44100, subtype='PCM_16') - with pytest.raises(ValueError): # missing sample_rate + with pytest.raises(TypeError): # missing sample_rate sf.SoundFile(file_r_raw, channels=2, subtype='PCM_16')