From fe5c357e9c686edd566d9286f19475b91a0659ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Fri, 11 Jan 2019 17:10:10 +0100 Subject: [PATCH 1/4] unittest.mock.mock_open() results now respects the argument of read([size]) --- Lib/unittest/mock.py | 23 +++++++++++++------ Lib/unittest/test/testmock/testwith.py | 2 +- .../2019-01-11-17-09-15.bpo-31855.PlhfsX.rst | 2 ++ 3 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-01-11-17-09-15.bpo-31855.PlhfsX.rst diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 3a22a48c997f4c..6a4e190c5a411a 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -25,6 +25,7 @@ __version__ = '1.0' +import io import inspect import pprint import sys @@ -2379,20 +2380,27 @@ def mock_open(mock=None, read_data=''): `read_data` is a string for the `read`, `readline` and `readlines` of the file handle to return. This is an empty string by default. """ + if isinstance(read_data, bytes): + _read_data = io.BytesIO(read_data) + else: + _read_data = io.StringIO(read_data) + + _state = [_read_data, None] + def _readlines_side_effect(*args, **kwargs): if handle.readlines.return_value is not None: return handle.readlines.return_value - return list(_state[0]) + return _state[0].readlines(*args, **kwargs) def _read_side_effect(*args, **kwargs): if handle.read.return_value is not None: return handle.read.return_value - return type(read_data)().join(_state[0]) + return _state[0].read(*args, **kwargs) - def _readline_side_effect(): + def _readline_side_effect(*args, **kwargs): yield from _iter_side_effect() while True: - yield type(read_data)() + yield _state[0].readline(*args, **kwargs) def _iter_side_effect(): if handle.readline.return_value is not None: @@ -2412,8 +2420,6 @@ def _iter_side_effect(): handle = MagicMock(spec=file_spec) handle.__enter__.return_value = handle - _state = [_iterate_read_data(read_data), None] - handle.write.return_value = None handle.read.return_value = None handle.readline.return_value = None @@ -2426,7 +2432,10 @@ def _iter_side_effect(): handle.__iter__.side_effect = _iter_side_effect def reset_data(*args, **kwargs): - _state[0] = _iterate_read_data(read_data) + if isinstance(read_data, bytes): + _state[0] = io.BytesIO(read_data) + else: + _state[0] = io.StringIO(read_data) if handle.readline.side_effect == _state[1]: # Only reset the side effect if the user hasn't overridden it. _state[1] = _readline_side_effect() diff --git a/Lib/unittest/test/testmock/testwith.py b/Lib/unittest/test/testmock/testwith.py index ec4e540dcfd941..86d39d6c9a60ac 100644 --- a/Lib/unittest/test/testmock/testwith.py +++ b/Lib/unittest/test/testmock/testwith.py @@ -286,7 +286,7 @@ def test_mock_open_read_with_argument(self): # for mocks returned by mock_open some_data = 'foo\nbar\nbaz' mock = mock_open(read_data=some_data) - self.assertEqual(mock().read(10), some_data) + self.assertEqual(mock().read(10), some_data[:10]) def test_interleaved_reads(self): diff --git a/Misc/NEWS.d/next/Library/2019-01-11-17-09-15.bpo-31855.PlhfsX.rst b/Misc/NEWS.d/next/Library/2019-01-11-17-09-15.bpo-31855.PlhfsX.rst new file mode 100644 index 00000000000000..8d1dc187f3d1e5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-01-11-17-09-15.bpo-31855.PlhfsX.rst @@ -0,0 +1,2 @@ +unittest.mock.mock_open() results now respects the argument of read([size]). +Patch contributed by Rémi Lapeyre. From ee7d67ae261d43f7065f3ef7a89857f6e9528930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Mon, 6 May 2019 17:58:12 +0200 Subject: [PATCH 2/4] Add test for successive reads --- Lib/unittest/test/testmock/testwith.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Lib/unittest/test/testmock/testwith.py b/Lib/unittest/test/testmock/testwith.py index 86d39d6c9a60ac..0fa42e18eca6aa 100644 --- a/Lib/unittest/test/testmock/testwith.py +++ b/Lib/unittest/test/testmock/testwith.py @@ -287,6 +287,11 @@ def test_mock_open_read_with_argument(self): some_data = 'foo\nbar\nbaz' mock = mock_open(read_data=some_data) self.assertEqual(mock().read(10), some_data[:10]) + self.assertEqual(mock().read(10), some_data[:10]) + + f = mock() + self.assertEqual(f.read(10), some_data[:10]) + self.assertEqual(f.read(10), some_data[10:]) def test_interleaved_reads(self): From 59b08e65f96109049b20e51a6562911a879194b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Mon, 6 May 2019 17:58:29 +0200 Subject: [PATCH 3/4] Remove code duplication --- Lib/unittest/mock.py | 34 +++++++--------------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 6a4e190c5a411a..7a6f8ff144842c 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -2347,25 +2347,12 @@ def __init__(self, spec, spec_set=False, parent=None, file_spec = None -def _iterate_read_data(read_data): - # Helper for mock_open: - # Retrieve lines from read_data via a generator so that separate calls to - # readline, read, and readlines are properly interleaved - sep = b'\n' if isinstance(read_data, bytes) else '\n' - data_as_list = [l + sep for l in read_data.split(sep)] - - if data_as_list[-1] == sep: - # If the last line ended in a newline, the list comprehension will have an - # extra entry that's just a newline. Remove this. - data_as_list = data_as_list[:-1] - else: - # If there wasn't an extra newline by itself, then the file being - # emulated doesn't have a newline to end the last line remove the - # newline that our naive format() added - data_as_list[-1] = data_as_list[-1][:-1] - for line in data_as_list: - yield line +def _to_stream(read_data): + if isinstance(read_data, bytes): + return io.BytesIO(read_data) + else: + return io.StringIO(read_data) def mock_open(mock=None, read_data=''): @@ -2380,11 +2367,7 @@ def mock_open(mock=None, read_data=''): `read_data` is a string for the `read`, `readline` and `readlines` of the file handle to return. This is an empty string by default. """ - if isinstance(read_data, bytes): - _read_data = io.BytesIO(read_data) - else: - _read_data = io.StringIO(read_data) - + _read_data = _to_stream(read_data) _state = [_read_data, None] def _readlines_side_effect(*args, **kwargs): @@ -2432,10 +2415,7 @@ def _iter_side_effect(): handle.__iter__.side_effect = _iter_side_effect def reset_data(*args, **kwargs): - if isinstance(read_data, bytes): - _state[0] = io.BytesIO(read_data) - else: - _state[0] = io.StringIO(read_data) + _state[0] = _to_stream(read_data) if handle.readline.side_effect == _state[1]: # Only reset the side effect if the user hasn't overridden it. _state[1] = _readline_side_effect() From b48ed55f6173911ab51680c751f1731eb09e5709 Mon Sep 17 00:00:00 2001 From: Xtreak Date: Mon, 6 May 2019 19:58:57 +0200 Subject: [PATCH 4/4] Update Misc/NEWS.d/next/Library/2019-01-11-17-09-15.bpo-31855.PlhfsX.rst Co-Authored-By: remilapeyre --- .../next/Library/2019-01-11-17-09-15.bpo-31855.PlhfsX.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2019-01-11-17-09-15.bpo-31855.PlhfsX.rst b/Misc/NEWS.d/next/Library/2019-01-11-17-09-15.bpo-31855.PlhfsX.rst index 8d1dc187f3d1e5..0da9c4997e1aa1 100644 --- a/Misc/NEWS.d/next/Library/2019-01-11-17-09-15.bpo-31855.PlhfsX.rst +++ b/Misc/NEWS.d/next/Library/2019-01-11-17-09-15.bpo-31855.PlhfsX.rst @@ -1,2 +1,2 @@ -unittest.mock.mock_open() results now respects the argument of read([size]). +:func:`unittest.mock.mock_open` results now respects the argument of read([size]). Patch contributed by Rémi Lapeyre.