summaryrefslogtreecommitdiff
path: root/lib/ansible/compat
diff options
context:
space:
mode:
authorAdrian Likins <alikins@redhat.com>2016-08-19 18:11:24 -0400
committerGitHub <noreply@github.com>2016-08-19 18:11:24 -0400
commit7d41f623ddabdccdcdb4ea46877cb0c5de67abed (patch)
tree99d975b31e8db71e4898ea50dba157053d738c4e /lib/ansible/compat
parent235eab6609ed42fde171e45be266619be6e2a9fd (diff)
downloadansible-7d41f623ddabdccdcdb4ea46877cb0c5de67abed.tar.gz
Move py34 mock_open compat to compat/test/mock (#17157)
test/units/plugins/action/test_action.py had code for handling a bug in python 3.4's mock_open that causes errors when reading binary data. Moved to compat/tests/mock.py so other tests can use it by default.
Diffstat (limited to 'lib/ansible/compat')
-rw-r--r--lib/ansible/compat/tests/mock.py80
1 files changed, 80 insertions, 0 deletions
diff --git a/lib/ansible/compat/tests/mock.py b/lib/ansible/compat/tests/mock.py
index 0614391c4b..c4bda61ccb 100644
--- a/lib/ansible/compat/tests/mock.py
+++ b/lib/ansible/compat/tests/mock.py
@@ -22,6 +22,7 @@ __metaclass__ = type
'''
Compat module for Python3.x's unittest.mock module
'''
+import sys
# Python 2.7
@@ -36,3 +37,82 @@ except ImportError:
from mock import *
except ImportError:
print('You need the mock library installed on python2.x to run tests')
+
+
+# Prior to 3.4.4, mock_open cannot handle binary read_data
+if sys.version_info >= (3,) and sys.version_info < (3, 4, 4):
+ 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 mock_open(mock=None, read_data=''):
+ """
+ A helper function to create a mock to replace the use of `open`. It works
+ for `open` called directly or used as a context manager.
+
+ The `mock` argument is the mock object to configure. If `None` (the
+ default) then a `MagicMock` will be created for you, with the API limited
+ to methods or attributes available on standard file handles.
+
+ `read_data` is a string for the `read` methoddline`, and `readlines` of the
+ file handle to return. This is an empty string by default.
+ """
+ def _readlines_side_effect(*args, **kwargs):
+ if handle.readlines.return_value is not None:
+ return handle.readlines.return_value
+ return list(_data)
+
+ def _read_side_effect(*args, **kwargs):
+ if handle.read.return_value is not None:
+ return handle.read.return_value
+ return type(read_data)().join(_data)
+
+ def _readline_side_effect():
+ if handle.readline.return_value is not None:
+ while True:
+ yield handle.readline.return_value
+ for line in _data:
+ yield line
+
+ global file_spec
+ if file_spec is None:
+ import _io
+ file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO))))
+
+ if mock is None:
+ mock = MagicMock(name='open', spec=open)
+
+ handle = MagicMock(spec=file_spec)
+ handle.__enter__.return_value = handle
+
+ _data = _iterate_read_data(read_data)
+
+ handle.write.return_value = None
+ handle.read.return_value = None
+ handle.readline.return_value = None
+ handle.readlines.return_value = None
+
+ handle.read.side_effect = _read_side_effect
+ handle.readline.side_effect = _readline_side_effect()
+ handle.readlines.side_effect = _readlines_side_effect
+
+ mock.return_value = handle
+ return mock