From 68e008dedd9e01e19751dabdb5d9b59bc009aa13 Mon Sep 17 00:00:00 2001 From: Tony Flury Date: Sun, 4 Mar 2018 11:52:59 +0000 Subject: [PATCH 01/13] Implement and testing for Issue 32933 - support for dunder iter on mock_open --- Lib/unittest/mock.py | 1 + Lib/unittest/test/testmock/testmock.py | 8 ++++++++ Lib/unittest/test/testmock/testwith.py | 18 ++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 9302dedae7fdad..7e1ebb37374229 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -2387,6 +2387,7 @@ def _readline_side_effect(): _state[1] = _readline_side_effect() handle.readline.side_effect = _state[1] handle.readlines.side_effect = _readlines_side_effect + handle.__iter__.side_effect = _readline_side_effect def reset_data(*args, **kwargs): _state[0] = _iterate_read_data(read_data) diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index b64c8663d21226..7277636887a6db 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -1450,6 +1450,14 @@ def test_mock_open_reuse_issue_21750(self): f2_data = f2.read() self.assertEqual(f1_data, f2_data) + def test_mock_open_dunder_iter_issue_32933(self): + mocked_open = mock.mock_open(read_data='Remarkable Bird\nThe Norwegian Blue\nBeautiful Plumage') + f1 = mocked_open('a-name') + lines = [line for line in f1] + self.assertEqual(lines[0], 'Remarkable Bird') + self.assertEqual(lines[1], 'The Norwegian Blue') + self.assertEqual(lines[2], 'Beautiful Plumage') + def test_mock_open_write(self): # Test exception in file writing write() mock_namedtemp = mock.mock_open(mock.MagicMock(name='JLV')) diff --git a/Lib/unittest/test/testmock/testwith.py b/Lib/unittest/test/testmock/testwith.py index a7bee730030190..6df2b4456eba6f 100644 --- a/Lib/unittest/test/testmock/testwith.py +++ b/Lib/unittest/test/testmock/testwith.py @@ -205,6 +205,24 @@ def test_readline_data(self): result = h.readline() self.assertEqual(result, 'foo') + def test_dunder_iter_data(self): + # Check that dunder_iter will return all the lines from the fake file + # Added to test Issue 32933 + mock = mock_open(read_data='foo\nbar\nbaz\n') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + lines = [l for l in h] + self.assertEqual(lines[0], 'foo\n') + self.assertEqual(lines[1], 'bar\n') + self.assertEqual(lines[2], 'baz\n') + + # Check that we properly emulate a file that doesn't end in a newline + mock = mock_open(read_data='foo') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + result = h.readline() + self.assertEqual(result, 'foo') + def test_readlines_data(self): # Test that emulating a file that ends in a newline character works From 3e65f559da16f2e6d5e77977c7b2534c372dec82 Mon Sep 17 00:00:00 2001 From: Tony Flury Date: Sun, 4 Mar 2018 14:39:02 +0000 Subject: [PATCH 02/13] Implement and testing for Issue 32933 - support for dunder iter on mock_open --- Lib/unittest/mock.py | 8 +++++++- Lib/unittest/test/testmock/testmock.py | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 7e1ebb37374229..150cbdb5a8d8d2 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -2364,6 +2364,12 @@ def _readline_side_effect(): while True: yield type(read_data)() + def _iter_side_effect(): + if handle.readline.return_value is not None: + while True: + yield handle.readline.return_value + for line in _state[0]: + yield line global file_spec if file_spec is None: @@ -2387,7 +2393,7 @@ def _readline_side_effect(): _state[1] = _readline_side_effect() handle.readline.side_effect = _state[1] handle.readlines.side_effect = _readlines_side_effect - handle.__iter__.side_effect = _readline_side_effect + handle.__iter__.side_effect = _iter_side_effect def reset_data(*args, **kwargs): _state[0] = _iterate_read_data(read_data) diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index 7277636887a6db..39c63a29bf1207 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -1454,8 +1454,8 @@ def test_mock_open_dunder_iter_issue_32933(self): mocked_open = mock.mock_open(read_data='Remarkable Bird\nThe Norwegian Blue\nBeautiful Plumage') f1 = mocked_open('a-name') lines = [line for line in f1] - self.assertEqual(lines[0], 'Remarkable Bird') - self.assertEqual(lines[1], 'The Norwegian Blue') + self.assertEqual(lines[0], 'Remarkable Bird\n') + self.assertEqual(lines[1], 'The Norwegian Blue\n') self.assertEqual(lines[2], 'Beautiful Plumage') def test_mock_open_write(self): From 1701b79f47a0c0f2f26c423c6b628f9a7222806b Mon Sep 17 00:00:00 2001 From: Tony Flury Date: Sun, 29 Apr 2018 18:47:21 +0100 Subject: [PATCH 03/13] Changes as per Pull request review --- Lib/unittest/mock.py | 6 +----- Lib/unittest/test/testmock/testmock.py | 7 +++---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 150cbdb5a8d8d2..070fed60e768d1 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -2356,11 +2356,7 @@ def _read_side_effect(*args, **kwargs): return type(read_data)().join(_state[0]) def _readline_side_effect(): - if handle.readline.return_value is not None: - while True: - yield handle.readline.return_value - for line in _state[0]: - yield line + yield from _iter_side_effect() while True: yield type(read_data)() diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index 39c63a29bf1207..d6bed6eb1ebb2a 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -1451,12 +1451,11 @@ def test_mock_open_reuse_issue_21750(self): self.assertEqual(f1_data, f2_data) def test_mock_open_dunder_iter_issue_32933(self): - mocked_open = mock.mock_open(read_data='Remarkable Bird\nThe Norwegian Blue\nBeautiful Plumage') + mocked_open = mock.mock_open(read_data='Remarkable\nNorwegian Blue') f1 = mocked_open('a-name') lines = [line for line in f1] - self.assertEqual(lines[0], 'Remarkable Bird\n') - self.assertEqual(lines[1], 'The Norwegian Blue\n') - self.assertEqual(lines[2], 'Beautiful Plumage') + self.assertEqual(lines[0], 'Remarkable\n') + self.assertEqual(lines[1], 'Norwegian Blue\n') def test_mock_open_write(self): # Test exception in file writing write() From 5af978ec858f5fc431f94e8d1a916abfb059e451 Mon Sep 17 00:00:00 2001 From: Tony Flury Date: Mon, 30 Apr 2018 22:52:06 +0100 Subject: [PATCH 04/13] Changes as per Pull request review - added NEWS entry and a small update to official documentation. --- Doc/library/unittest.mock.rst | 5 +++++ .../next/Library/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst | 1 + 2 files changed, 6 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index ac9dd3b6f793a2..5719aec9e52781 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -2095,6 +2095,10 @@ mock_open .. versionchanged:: 3.5 *read_data* is now reset on each call to the *mock*. + .. versionchanged:: 3.8 + Added :meth:`__iter__` to implementation so that iteration (such as in for + loops) correctly consumes *read_data*. + Using :func:`open` as a context manager is a great way to ensure your file handles are closed properly and is becoming common:: @@ -2132,6 +2136,7 @@ And for reading files: >>> assert result == 'bibble' + .. _auto-speccing: Autospeccing diff --git a/Misc/NEWS.d/next/Library/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst b/Misc/NEWS.d/next/Library/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst new file mode 100644 index 00000000000000..d21cf3768b345c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst @@ -0,0 +1 @@ +unittest.mock.mock_open now supports iteration over the file contents. From c1e0ddb4d9040e40f7e025b04c8cafa8503b9597 Mon Sep 17 00:00:00 2001 From: Tony Flury Date: Mon, 28 May 2018 23:45:20 +0100 Subject: [PATCH 05/13] Update testmock.py --- Lib/unittest/test/testmock/testmock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index d6bed6eb1ebb2a..2f21f85c2e2d55 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -1455,7 +1455,7 @@ def test_mock_open_dunder_iter_issue_32933(self): f1 = mocked_open('a-name') lines = [line for line in f1] self.assertEqual(lines[0], 'Remarkable\n') - self.assertEqual(lines[1], 'Norwegian Blue\n') + self.assertEqual(lines[1], 'Norwegian Blue') def test_mock_open_write(self): # Test exception in file writing write() From a8c1d4ff564495242b75ec8716d3295f4822a112 Mon Sep 17 00:00:00 2001 From: Tony Flury Date: Mon, 9 Jul 2018 17:46:29 +0100 Subject: [PATCH 06/13] Update unittest.mock.rst --- Doc/library/unittest.mock.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index 5719aec9e52781..a980f338a443c3 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -2136,7 +2136,6 @@ And for reading files: >>> assert result == 'bibble' - .. _auto-speccing: Autospeccing From e62f05a2a1831d2e37841a47bb2988fae70bdccd Mon Sep 17 00:00:00 2001 From: Tony Flury Date: Mon, 9 Jul 2018 17:55:37 +0100 Subject: [PATCH 07/13] Some small changes Style changes to be consistent with rest of file Added extra readline call to check empty string returned after file is consumed. --- Lib/unittest/test/testmock/testwith.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Lib/unittest/test/testmock/testwith.py b/Lib/unittest/test/testmock/testwith.py index 6df2b4456eba6f..417e617a258995 100644 --- a/Lib/unittest/test/testmock/testwith.py +++ b/Lib/unittest/test/testmock/testwith.py @@ -188,6 +188,7 @@ def test_read_data(self): def test_readline_data(self): # Check that readline will return all the lines from the fake file + # And that once fully consumed, readline will return an empty string. mock = mock_open(read_data='foo\nbar\nbaz\n') with patch('%s.open' % __name__, mock, create=True): h = open('bar') @@ -197,6 +198,7 @@ def test_readline_data(self): self.assertEqual(line1, 'foo\n') self.assertEqual(line2, 'bar\n') self.assertEqual(line3, 'baz\n') + self.assertEqual(h.readline(),'') # Check that we properly emulate a file that doesn't end in a newline mock = mock_open(read_data='foo') @@ -204,10 +206,11 @@ def test_readline_data(self): h = open('bar') result = h.readline() self.assertEqual(result, 'foo') - + self.assertEqual(h.readline(),'') + + def test_dunder_iter_data(self): # Check that dunder_iter will return all the lines from the fake file - # Added to test Issue 32933 mock = mock_open(read_data='foo\nbar\nbaz\n') with patch('%s.open' % __name__, mock, create=True): h = open('bar') @@ -215,13 +218,7 @@ def test_dunder_iter_data(self): self.assertEqual(lines[0], 'foo\n') self.assertEqual(lines[1], 'bar\n') self.assertEqual(lines[2], 'baz\n') - - # Check that we properly emulate a file that doesn't end in a newline - mock = mock_open(read_data='foo') - with patch('%s.open' % __name__, mock, create=True): - h = open('bar') - result = h.readline() - self.assertEqual(result, 'foo') + self.assertEqual(h.readline(),'') def test_readlines_data(self): From a927fd29bdad26b215c5393da7bf1acce2c67b95 Mon Sep 17 00:00:00 2001 From: Tony Flury Date: Mon, 9 Jul 2018 17:57:01 +0100 Subject: [PATCH 08/13] Update 2018-04-30-22-43-31.bpo-32933.M3iI_y.rst --- .../next/Library/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst b/Misc/NEWS.d/next/Library/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst index d21cf3768b345c..783c062afdd998 100644 --- a/Misc/NEWS.d/next/Library/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst +++ b/Misc/NEWS.d/next/Library/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst @@ -1 +1 @@ -unittest.mock.mock_open now supports iteration over the file contents. +func:`unittest.mock.mock_open` now supports iteration over the file contents. Patch by Tony Flury From 646e9cd3700d169c531270689e81062ffd22d74d Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Mon, 9 Jul 2018 20:45:24 +0300 Subject: [PATCH 09/13] fix style --- Lib/unittest/test/testmock/testwith.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/unittest/test/testmock/testwith.py b/Lib/unittest/test/testmock/testwith.py index 417e617a258995..2dd5f1a2605d94 100644 --- a/Lib/unittest/test/testmock/testwith.py +++ b/Lib/unittest/test/testmock/testwith.py @@ -198,7 +198,7 @@ def test_readline_data(self): self.assertEqual(line1, 'foo\n') self.assertEqual(line2, 'bar\n') self.assertEqual(line3, 'baz\n') - self.assertEqual(h.readline(),'') + self.assertEqual(h.readline(), '') # Check that we properly emulate a file that doesn't end in a newline mock = mock_open(read_data='foo') @@ -206,11 +206,11 @@ def test_readline_data(self): h = open('bar') result = h.readline() self.assertEqual(result, 'foo') - self.assertEqual(h.readline(),'') + self.assertEqual(h.readline(), '') def test_dunder_iter_data(self): - # Check that dunder_iter will return all the lines from the fake file + # Check that __iter__ will return all the lines from the fake file. mock = mock_open(read_data='foo\nbar\nbaz\n') with patch('%s.open' % __name__, mock, create=True): h = open('bar') @@ -218,7 +218,7 @@ def test_dunder_iter_data(self): self.assertEqual(lines[0], 'foo\n') self.assertEqual(lines[1], 'bar\n') self.assertEqual(lines[2], 'baz\n') - self.assertEqual(h.readline(),'') + self.assertEqual(h.readline(), '') def test_readlines_data(self): From 7803a7c88049ee908492b278118f0d9b42a085b6 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Mon, 9 Jul 2018 20:47:05 +0300 Subject: [PATCH 10/13] fix reST markup --- .../next/Library/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst b/Misc/NEWS.d/next/Library/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst index 783c062afdd998..4de7a8f927d58b 100644 --- a/Misc/NEWS.d/next/Library/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst +++ b/Misc/NEWS.d/next/Library/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst @@ -1 +1,2 @@ -func:`unittest.mock.mock_open` now supports iteration over the file contents. Patch by Tony Flury +:func:`unittest.mock.mock_open` now supports iteration over the file +contents. Patch by Tony Flury. From a043f171d813e3455292fe3d408b3256f2c9711b Mon Sep 17 00:00:00 2001 From: Tony Flury Date: Mon, 9 Jul 2018 22:57:01 +0100 Subject: [PATCH 11/13] Update testmock.py --- Lib/unittest/test/testmock/testmock.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index 2f21f85c2e2d55..aa1af1c227cd63 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -1450,7 +1450,8 @@ def test_mock_open_reuse_issue_21750(self): f2_data = f2.read() self.assertEqual(f1_data, f2_data) - def test_mock_open_dunder_iter_issue_32933(self): + def test_mock_open_dunder_iter_issue(self): + """Test dunder_iter method generates the expected result and consumes the iterator""" mocked_open = mock.mock_open(read_data='Remarkable\nNorwegian Blue') f1 = mocked_open('a-name') lines = [line for line in f1] From 75a5d187141de02b020415bea090447fb57d356b Mon Sep 17 00:00:00 2001 From: Tony Flury Date: Tue, 10 Jul 2018 00:16:05 +0100 Subject: [PATCH 12/13] Fixed whitespace issues in testwith.py --- Lib/unittest/test/testmock/testwith.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/unittest/test/testmock/testwith.py b/Lib/unittest/test/testmock/testwith.py index 2dd5f1a2605d94..43b36a11995261 100644 --- a/Lib/unittest/test/testmock/testwith.py +++ b/Lib/unittest/test/testmock/testwith.py @@ -207,10 +207,10 @@ def test_readline_data(self): result = h.readline() self.assertEqual(result, 'foo') self.assertEqual(h.readline(), '') - - + + def test_dunder_iter_data(self): - # Check that __iter__ will return all the lines from the fake file. + # Check that dunder_iter will return all the lines from the fake file. mock = mock_open(read_data='foo\nbar\nbaz\n') with patch('%s.open' % __name__, mock, create=True): h = open('bar') From 624e75fd93fa5707e8fe5f45d8dc35df2725298a Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Thu, 13 Sep 2018 00:34:22 +0300 Subject: [PATCH 13/13] tweak test_mock_open_dunder_iter_issue --- Lib/unittest/test/testmock/testmock.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index aa1af1c227cd63..c7bfa277b511ca 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -1451,12 +1451,14 @@ def test_mock_open_reuse_issue_21750(self): self.assertEqual(f1_data, f2_data) def test_mock_open_dunder_iter_issue(self): - """Test dunder_iter method generates the expected result and consumes the iterator""" + # Test dunder_iter method generates the expected result and + # consumes the iterator. mocked_open = mock.mock_open(read_data='Remarkable\nNorwegian Blue') f1 = mocked_open('a-name') lines = [line for line in f1] self.assertEqual(lines[0], 'Remarkable\n') self.assertEqual(lines[1], 'Norwegian Blue') + self.assertEqual(list(f1), []) def test_mock_open_write(self): # Test exception in file writing write()