test_mock.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785
  1. # -*- coding: utf-8 -*-
  2. '''
  3. Tests for our mock_open helper
  4. '''
  5. # Import Python Libs
  6. from __future__ import absolute_import, unicode_literals, print_function
  7. import errno
  8. import logging
  9. import textwrap
  10. # Import Salt libs
  11. import salt.utils.data
  12. import salt.utils.files
  13. import salt.utils.stringutils
  14. from salt.ext import six
  15. # Import Salt Testing Libs
  16. from tests.support.mock import patch, mock_open, NO_MOCK, NO_MOCK_REASON
  17. from tests.support.unit import TestCase, skipIf
  18. log = logging.getLogger(__name__)
  19. class MockOpenMixin(object):
  20. def _get_values(self, binary=False, multifile=False, split=False):
  21. if split:
  22. questions = (self.questions_bytes_lines if binary
  23. else self.questions_str_lines)
  24. answers = (self.answers_bytes_lines if binary
  25. else self.answers_str_lines)
  26. else:
  27. questions = self.questions_bytes if binary else self.questions_str
  28. answers = self.answers_bytes if binary else self.answers_str
  29. mode = 'rb' if binary else 'r'
  30. if multifile:
  31. read_data = self.contents_bytes if binary else self.contents
  32. else:
  33. read_data = self.questions_bytes if binary else self.questions
  34. return questions, answers, mode, read_data
  35. def _test_read(self, binary=False, multifile=False):
  36. questions, answers, mode, read_data = \
  37. self._get_values(binary=binary, multifile=multifile)
  38. with patch('salt.utils.files.fopen', mock_open(read_data=read_data)):
  39. with salt.utils.files.fopen('foo.txt', mode) as self.fh:
  40. result = self.fh.read()
  41. assert result == questions, result
  42. if multifile:
  43. with salt.utils.files.fopen('bar.txt', mode) as self.fh2:
  44. result = self.fh2.read()
  45. assert result == answers, result
  46. with salt.utils.files.fopen('baz.txt', mode) as self.fh3:
  47. result = self.fh3.read()
  48. assert result == answers, result
  49. try:
  50. with salt.utils.files.fopen('helloworld.txt'):
  51. raise Exception('No patterns should have matched')
  52. except IOError:
  53. # An IOError is expected here
  54. pass
  55. def _test_read_explicit_size(self, binary=False, multifile=False):
  56. questions, answers, mode, read_data = \
  57. self._get_values(binary=binary, multifile=multifile)
  58. with patch('salt.utils.files.fopen', mock_open(read_data=read_data)):
  59. with salt.utils.files.fopen('foo.txt', mode) as self.fh:
  60. # Read 10 bytes
  61. result = self.fh.read(10)
  62. assert result == questions[:10], result
  63. # Read another 10 bytes
  64. result = self.fh.read(10)
  65. assert result == questions[10:20], result
  66. # Read the rest
  67. result = self.fh.read()
  68. assert result == questions[20:], result
  69. if multifile:
  70. with salt.utils.files.fopen('bar.txt', mode) as self.fh2:
  71. # Read 10 bytes
  72. result = self.fh2.read(10)
  73. assert result == answers[:10], result
  74. # Read another 10 bytes
  75. result = self.fh2.read(10)
  76. assert result == answers[10:20], result
  77. # Read the rest
  78. result = self.fh2.read()
  79. assert result == answers[20:], result
  80. with salt.utils.files.fopen('baz.txt', mode) as self.fh3:
  81. # Read 10 bytes
  82. result = self.fh3.read(10)
  83. assert result == answers[:10], result
  84. # Read another 10 bytes
  85. result = self.fh3.read(10)
  86. assert result == answers[10:20], result
  87. # Read the rest
  88. result = self.fh3.read()
  89. assert result == answers[20:], result
  90. try:
  91. with salt.utils.files.fopen('helloworld.txt'):
  92. raise Exception('No globs should have matched')
  93. except IOError:
  94. # An IOError is expected here
  95. pass
  96. def _test_read_explicit_size_larger_than_file_size(self,
  97. binary=False,
  98. multifile=False):
  99. questions, answers, mode, read_data = \
  100. self._get_values(binary=binary, multifile=multifile)
  101. with patch('salt.utils.files.fopen', mock_open(read_data=read_data)):
  102. with salt.utils.files.fopen('foo.txt', mode) as self.fh:
  103. result = self.fh.read(999999)
  104. assert result == questions, result
  105. if multifile:
  106. with salt.utils.files.fopen('bar.txt', mode) as self.fh2:
  107. result = self.fh2.read(999999)
  108. assert result == answers, result
  109. with salt.utils.files.fopen('baz.txt', mode) as self.fh3:
  110. result = self.fh3.read(999999)
  111. assert result == answers, result
  112. try:
  113. with salt.utils.files.fopen('helloworld.txt'):
  114. raise Exception('No globs should have matched')
  115. except IOError:
  116. # An IOError is expected here
  117. pass
  118. def _test_read_for_loop(self, binary=False, multifile=False):
  119. questions, answers, mode, read_data = \
  120. self._get_values(binary=binary, multifile=multifile, split=True)
  121. with patch('salt.utils.files.fopen', mock_open(read_data=read_data)):
  122. with salt.utils.files.fopen('foo.txt', mode) as self.fh:
  123. index = 0
  124. for line in self.fh:
  125. assert line == questions[index], \
  126. 'Line {0}: {1}'.format(index, line)
  127. index += 1
  128. if multifile:
  129. with salt.utils.files.fopen('bar.txt', mode) as self.fh2:
  130. index = 0
  131. for line in self.fh2:
  132. assert line == answers[index], \
  133. 'Line {0}: {1}'.format(index, line)
  134. index += 1
  135. with salt.utils.files.fopen('baz.txt', mode) as self.fh3:
  136. index = 0
  137. for line in self.fh3:
  138. assert line == answers[index], \
  139. 'Line {0}: {1}'.format(index, line)
  140. index += 1
  141. try:
  142. with salt.utils.files.fopen('helloworld.txt'):
  143. raise Exception('No globs should have matched')
  144. except IOError:
  145. # An IOError is expected here
  146. pass
  147. def _test_read_readline(self, binary=False, multifile=False):
  148. questions, answers, mode, read_data = \
  149. self._get_values(binary=binary, multifile=multifile, split=True)
  150. with patch('salt.utils.files.fopen', mock_open(read_data=read_data)):
  151. with salt.utils.files.fopen('foo.txt', mode) as self.fh:
  152. size = 8
  153. result = self.fh.read(size)
  154. assert result == questions[0][:size], result
  155. # Use .readline() to read the remainder of the line
  156. result = self.fh.readline()
  157. assert result == questions[0][size:], result
  158. # Read and check the other two lines
  159. result = self.fh.readline()
  160. assert result == questions[1], result
  161. result = self.fh.readline()
  162. assert result == questions[2], result
  163. if multifile:
  164. with salt.utils.files.fopen('bar.txt', mode) as self.fh2:
  165. size = 20
  166. result = self.fh2.read(size)
  167. assert result == answers[0][:size], result
  168. # Use .readline() to read the remainder of the line
  169. result = self.fh2.readline()
  170. assert result == answers[0][size:], result
  171. # Read and check the other two lines
  172. result = self.fh2.readline()
  173. assert result == answers[1], result
  174. result = self.fh2.readline()
  175. assert result == answers[2], result
  176. with salt.utils.files.fopen('baz.txt', mode) as self.fh3:
  177. size = 20
  178. result = self.fh3.read(size)
  179. assert result == answers[0][:size], result
  180. # Use .readline() to read the remainder of the line
  181. result = self.fh3.readline()
  182. assert result == answers[0][size:], result
  183. # Read and check the other two lines
  184. result = self.fh3.readline()
  185. assert result == answers[1], result
  186. result = self.fh3.readline()
  187. assert result == answers[2], result
  188. try:
  189. with salt.utils.files.fopen('helloworld.txt'):
  190. raise Exception('No globs should have matched')
  191. except IOError:
  192. # An IOError is expected here
  193. pass
  194. def _test_readline_readlines(self, binary=False, multifile=False):
  195. questions, answers, mode, read_data = \
  196. self._get_values(binary=binary, multifile=multifile, split=True)
  197. with patch('salt.utils.files.fopen', mock_open(read_data=read_data)):
  198. with salt.utils.files.fopen('foo.txt', mode) as self.fh:
  199. # Read the first line
  200. result = self.fh.readline()
  201. assert result == questions[0], result
  202. # Use .readlines() to read the remainder of the file
  203. result = self.fh.readlines()
  204. assert result == questions[1:], result
  205. if multifile:
  206. with salt.utils.files.fopen('bar.txt', mode) as self.fh2:
  207. # Read the first line
  208. result = self.fh2.readline()
  209. assert result == answers[0], result
  210. # Use .readlines() to read the remainder of the file
  211. result = self.fh2.readlines()
  212. assert result == answers[1:], result
  213. with salt.utils.files.fopen('baz.txt', mode) as self.fh3:
  214. # Read the first line
  215. result = self.fh3.readline()
  216. assert result == answers[0], result
  217. # Use .readlines() to read the remainder of the file
  218. result = self.fh3.readlines()
  219. assert result == answers[1:], result
  220. try:
  221. with salt.utils.files.fopen('helloworld.txt'):
  222. raise Exception('No globs should have matched')
  223. except IOError:
  224. # An IOError is expected here
  225. pass
  226. def _test_readlines_multifile(self, binary=False, multifile=False):
  227. questions, answers, mode, read_data = \
  228. self._get_values(binary=binary, multifile=multifile, split=True)
  229. with patch('salt.utils.files.fopen', mock_open(read_data=read_data)):
  230. with salt.utils.files.fopen('foo.txt', mode) as self.fh:
  231. result = self.fh.readlines()
  232. assert result == questions, result
  233. if multifile:
  234. with salt.utils.files.fopen('bar.txt', mode) as self.fh2:
  235. result = self.fh2.readlines()
  236. assert result == answers, result
  237. with salt.utils.files.fopen('baz.txt', mode) as self.fh3:
  238. result = self.fh3.readlines()
  239. assert result == answers, result
  240. try:
  241. with salt.utils.files.fopen('helloworld.txt'):
  242. raise Exception('No globs should have matched')
  243. except IOError:
  244. # An IOError is expected here
  245. pass
  246. @skipIf(NO_MOCK, NO_MOCK_REASON)
  247. class MockOpenTestCase(TestCase, MockOpenMixin):
  248. '''
  249. Tests for our mock_open helper to ensure that it behaves as closely as
  250. possible to a real filehandle.
  251. '''
  252. # Cyrllic characters used to test unicode handling
  253. questions = textwrap.dedent('''\
  254. Шнат is your name?
  255. Шнат is your quest?
  256. Шнат is the airspeed velocity of an unladen swallow?
  257. ''')
  258. answers = textwrap.dedent('''\
  259. It is Аятнця, King of the Britons.
  260. To seek тне Holy Grail.
  261. Шнат do you mean? An African or European swallow?
  262. ''')
  263. @classmethod
  264. def setUpClass(cls):
  265. cls.questions_lines = cls.questions.splitlines(True)
  266. cls.answers_lines = cls.answers.splitlines(True)
  267. cls.questions_str = salt.utils.stringutils.to_str(cls.questions)
  268. cls.answers_str = salt.utils.stringutils.to_str(cls.answers)
  269. cls.questions_str_lines = cls.questions_str.splitlines(True)
  270. cls.answers_str_lines = cls.answers_str.splitlines(True)
  271. cls.questions_bytes = salt.utils.stringutils.to_bytes(cls.questions)
  272. cls.answers_bytes = salt.utils.stringutils.to_bytes(cls.answers)
  273. cls.questions_bytes_lines = cls.questions_bytes.splitlines(True)
  274. cls.answers_bytes_lines = cls.answers_bytes.splitlines(True)
  275. # When this is used as the read_data, Python 2 should normalize
  276. # cls.questions and cls.answers to str types.
  277. cls.contents = {'foo.txt': cls.questions,
  278. 'b*.txt': cls.answers}
  279. cls.contents_bytes = {'foo.txt': cls.questions_bytes,
  280. 'b*.txt': cls.answers_bytes}
  281. cls.read_data_as_list = [
  282. 'foo', 'bar', 'спам',
  283. IOError(errno.EACCES, 'Permission denied')
  284. ]
  285. cls.normalized_read_data_as_list = salt.utils.data.decode(
  286. cls.read_data_as_list,
  287. to_str=True
  288. )
  289. cls.read_data_as_list_bytes = salt.utils.data.encode(cls.read_data_as_list)
  290. def tearDown(self):
  291. '''
  292. Each test should read the entire contents of the mocked filehandle(s).
  293. This confirms that the other read functions return empty strings/lists,
  294. to simulate being at EOF.
  295. '''
  296. for handle_name in ('fh', 'fh2', 'fh3'):
  297. try:
  298. fh = getattr(self, handle_name)
  299. except AttributeError:
  300. continue
  301. log.debug('Running tearDown tests for self.%s', handle_name)
  302. try:
  303. result = fh.read(5)
  304. assert not result, result
  305. result = fh.read()
  306. assert not result, result
  307. result = fh.readline()
  308. assert not result, result
  309. result = fh.readlines()
  310. assert not result, result
  311. # Last but not least, try to read using a for loop. This should not
  312. # read anything as we should hit EOF immediately, before the generator
  313. # in the mocked filehandle has a chance to yield anything. So the
  314. # exception will only be raised if we aren't at EOF already.
  315. for line in fh:
  316. raise Exception(
  317. 'Instead of EOF, read the following from {0}: {1}'.format(
  318. handle_name,
  319. line
  320. )
  321. )
  322. except IOError as exc:
  323. if six.text_type(exc) != 'File not open for reading':
  324. raise
  325. del fh
  326. def test_read(self):
  327. '''
  328. Test reading the entire file
  329. '''
  330. self._test_read(binary=False, multifile=False)
  331. self._test_read(binary=True, multifile=False)
  332. self._test_read(binary=False, multifile=True)
  333. self._test_read(binary=True, multifile=True)
  334. def test_read_explicit_size(self):
  335. '''
  336. Test reading with explicit sizes
  337. '''
  338. self._test_read_explicit_size(binary=False, multifile=False)
  339. self._test_read_explicit_size(binary=True, multifile=False)
  340. self._test_read_explicit_size(binary=False, multifile=True)
  341. self._test_read_explicit_size(binary=True, multifile=True)
  342. def test_read_explicit_size_larger_than_file_size(self):
  343. '''
  344. Test reading with an explicit size larger than the size of read_data.
  345. This ensures that we just return the contents up until EOF and that we
  346. don't raise any errors due to the desired size being larger than the
  347. mocked file's size.
  348. '''
  349. self._test_read_explicit_size_larger_than_file_size(
  350. binary=False, multifile=False)
  351. self._test_read_explicit_size_larger_than_file_size(
  352. binary=True, multifile=False)
  353. self._test_read_explicit_size_larger_than_file_size(
  354. binary=False, multifile=True)
  355. self._test_read_explicit_size_larger_than_file_size(
  356. binary=True, multifile=True)
  357. def test_read_for_loop(self):
  358. '''
  359. Test reading the contents of the file line by line in a for loop
  360. '''
  361. self._test_read_for_loop(binary=False, multifile=False)
  362. self._test_read_for_loop(binary=True, multifile=False)
  363. self._test_read_for_loop(binary=False, multifile=True)
  364. self._test_read_for_loop(binary=True, multifile=True)
  365. def test_read_readline(self):
  366. '''
  367. Test reading part of a line using .read(), then reading the rest of the
  368. line (and subsequent lines) using .readline().
  369. '''
  370. self._test_read_readline(binary=False, multifile=False)
  371. self._test_read_readline(binary=True, multifile=False)
  372. self._test_read_readline(binary=False, multifile=True)
  373. self._test_read_readline(binary=True, multifile=True)
  374. def test_readline_readlines(self):
  375. '''
  376. Test reading the first line using .readline(), then reading the rest of
  377. the file using .readlines().
  378. '''
  379. self._test_readline_readlines(binary=False, multifile=False)
  380. self._test_readline_readlines(binary=True, multifile=False)
  381. self._test_readline_readlines(binary=False, multifile=True)
  382. self._test_readline_readlines(binary=True, multifile=True)
  383. def test_readlines(self):
  384. '''
  385. Test reading the entire file using .readlines
  386. '''
  387. self._test_readlines_multifile(binary=False, multifile=False)
  388. self._test_readlines_multifile(binary=True, multifile=False)
  389. self._test_readlines_multifile(binary=False, multifile=True)
  390. self._test_readlines_multifile(binary=True, multifile=True)
  391. def test_read_data_converted_to_dict(self):
  392. '''
  393. Test that a non-dict value for read_data is converted to a dict mapping
  394. '*' to that value.
  395. '''
  396. contents = 'спам'
  397. normalized = salt.utils.stringutils.to_str(contents)
  398. with patch('salt.utils.files.fopen',
  399. mock_open(read_data=contents)) as m_open:
  400. assert m_open.read_data == {'*': normalized}, m_open.read_data
  401. with patch('salt.utils.files.fopen',
  402. mock_open(read_data=self.read_data_as_list)) as m_open:
  403. assert m_open.read_data == {
  404. '*': self.normalized_read_data_as_list,
  405. }, m_open.read_data
  406. def test_read_data_list(self):
  407. '''
  408. Test read_data when it is a list
  409. '''
  410. with patch('salt.utils.files.fopen',
  411. mock_open(read_data=self.read_data_as_list)):
  412. for value in self.normalized_read_data_as_list:
  413. try:
  414. with salt.utils.files.fopen('foo.txt') as self.fh:
  415. result = self.fh.read()
  416. assert result == value, result
  417. except IOError:
  418. # Only raise the caught exception if it wasn't expected
  419. # (i.e. if value is not an exception)
  420. if not isinstance(value, IOError):
  421. raise
  422. def test_read_data_list_bytes(self):
  423. '''
  424. Test read_data when it is a list and the value is a bytestring
  425. '''
  426. with patch('salt.utils.files.fopen',
  427. mock_open(read_data=self.read_data_as_list_bytes)):
  428. for value in self.read_data_as_list_bytes:
  429. try:
  430. with salt.utils.files.fopen('foo.txt', 'rb') as self.fh:
  431. result = self.fh.read()
  432. assert result == value, result
  433. except IOError:
  434. # Only raise the caught exception if it wasn't expected
  435. # (i.e. if value is not an exception)
  436. if not isinstance(value, IOError):
  437. raise
  438. def test_tell(self):
  439. '''
  440. Test the implementation of tell
  441. '''
  442. with patch('salt.utils.files.fopen',
  443. mock_open(read_data=self.contents)):
  444. # Try with reading explicit sizes and then reading the rest of the
  445. # file.
  446. with salt.utils.files.fopen('foo.txt') as self.fh:
  447. self.fh.read(5)
  448. loc = self.fh.tell()
  449. assert loc == 5, loc
  450. self.fh.read(12)
  451. loc = self.fh.tell()
  452. assert loc == 17, loc
  453. self.fh.read()
  454. loc = self.fh.tell()
  455. assert loc == len(self.questions_str), loc
  456. # Try reading way more content then actually exists in the file,
  457. # tell() should return a value equal to the length of the content
  458. with salt.utils.files.fopen('foo.txt') as self.fh:
  459. self.fh.read(999999)
  460. loc = self.fh.tell()
  461. assert loc == len(self.questions_str), loc
  462. # Try reading a few bytes using .read(), then the rest of the line
  463. # using .readline(), then the rest of the file using .readlines(),
  464. # and check the location after each read.
  465. with salt.utils.files.fopen('foo.txt') as self.fh:
  466. # Read a few bytes
  467. self.fh.read(5)
  468. loc = self.fh.tell()
  469. assert loc == 5, loc
  470. # Read the rest of the line. Location should then be at the end
  471. # of the first line.
  472. self.fh.readline()
  473. loc = self.fh.tell()
  474. assert loc == len(self.questions_str_lines[0]), loc
  475. # Read the rest of the file using .readlines()
  476. self.fh.readlines()
  477. loc = self.fh.tell()
  478. assert loc == len(self.questions_str), loc
  479. # Check location while iterating through the filehandle
  480. with salt.utils.files.fopen('foo.txt') as self.fh:
  481. index = 0
  482. for _ in self.fh:
  483. index += 1
  484. loc = self.fh.tell()
  485. assert loc == sum(
  486. len(x) for x in self.questions_str_lines[:index]
  487. ), loc
  488. def test_write(self):
  489. '''
  490. Test writing to a filehandle using .write()
  491. '''
  492. # Test opening for non-binary writing
  493. with patch('salt.utils.files.fopen', mock_open()):
  494. with salt.utils.files.fopen('foo.txt', 'w') as self.fh:
  495. for line in self.questions_str_lines:
  496. self.fh.write(line)
  497. assert self.fh.write_calls == self.questions_str_lines, self.fh.write_calls
  498. # Test opening for binary writing using "wb"
  499. with patch('salt.utils.files.fopen', mock_open(read_data=b'')):
  500. with salt.utils.files.fopen('foo.txt', 'wb') as self.fh:
  501. for line in self.questions_bytes_lines:
  502. self.fh.write(line)
  503. assert self.fh.write_calls == self.questions_bytes_lines, self.fh.write_calls
  504. # Test opening for binary writing using "ab"
  505. with patch('salt.utils.files.fopen', mock_open(read_data=b'')):
  506. with salt.utils.files.fopen('foo.txt', 'ab') as self.fh:
  507. for line in self.questions_bytes_lines:
  508. self.fh.write(line)
  509. assert self.fh.write_calls == self.questions_bytes_lines, self.fh.write_calls
  510. # Test opening for read-and-write using "r+b"
  511. with patch('salt.utils.files.fopen', mock_open(read_data=b'')):
  512. with salt.utils.files.fopen('foo.txt', 'r+b') as self.fh:
  513. for line in self.questions_bytes_lines:
  514. self.fh.write(line)
  515. assert self.fh.write_calls == self.questions_bytes_lines, self.fh.write_calls
  516. # Test trying to write str types to a binary filehandle
  517. with patch('salt.utils.files.fopen', mock_open(read_data=b'')):
  518. with salt.utils.files.fopen('foo.txt', 'wb') as self.fh:
  519. try:
  520. self.fh.write('foo\n')
  521. except TypeError:
  522. # This exception is expected on Python 3
  523. if not six.PY3:
  524. raise
  525. else:
  526. # This write should work fine on Python 2
  527. if six.PY3:
  528. raise Exception(
  529. 'Should not have been able to write a str to a '
  530. 'binary filehandle'
  531. )
  532. if six.PY2:
  533. # Try with non-ascii unicode. Note that the write above
  534. # should work because the mocked filehandle should attempt
  535. # a .encode() to convert it to a str type. But when writing
  536. # a string with non-ascii unicode, it should raise a
  537. # UnicodeEncodeError, which is what we are testing here.
  538. try:
  539. self.fh.write(self.questions)
  540. except UnicodeEncodeError:
  541. pass
  542. else:
  543. raise Exception(
  544. 'Should not have been able to write non-ascii '
  545. 'unicode to a binary filehandle'
  546. )
  547. # Test trying to write bytestrings to a non-binary filehandle
  548. with patch('salt.utils.files.fopen', mock_open()):
  549. with salt.utils.files.fopen('foo.txt', 'w') as self.fh:
  550. try:
  551. self.fh.write(b'foo\n')
  552. except TypeError:
  553. # This exception is expected on Python 3
  554. if not six.PY3:
  555. raise
  556. else:
  557. # This write should work fine on Python 2
  558. if six.PY3:
  559. raise Exception(
  560. 'Should not have been able to write a bytestring '
  561. 'to a non-binary filehandle'
  562. )
  563. if six.PY2:
  564. # Try with non-ascii unicode. Note that the write above
  565. # should work because the mocked filehandle should attempt
  566. # a .encode() to convert it to a str type. But when writing
  567. # a string with non-ascii unicode, it should raise a
  568. # UnicodeEncodeError, which is what we are testing here.
  569. try:
  570. self.fh.write(self.questions)
  571. except UnicodeEncodeError:
  572. pass
  573. else:
  574. raise Exception(
  575. 'Should not have been able to write non-ascii '
  576. 'unicode to a binary filehandle'
  577. )
  578. def test_writelines(self):
  579. '''
  580. Test writing to a filehandle using .writelines()
  581. '''
  582. # Test opening for non-binary writing
  583. with patch('salt.utils.files.fopen', mock_open()):
  584. with salt.utils.files.fopen('foo.txt', 'w') as self.fh:
  585. self.fh.writelines(self.questions_str_lines)
  586. assert self.fh.writelines_calls == [self.questions_str_lines], self.fh.writelines_calls
  587. # Test opening for binary writing using "wb"
  588. with patch('salt.utils.files.fopen', mock_open(read_data=b'')):
  589. with salt.utils.files.fopen('foo.txt', 'wb') as self.fh:
  590. self.fh.writelines(self.questions_bytes_lines)
  591. assert self.fh.writelines_calls == [self.questions_bytes_lines], self.fh.writelines_calls
  592. # Test opening for binary writing using "ab"
  593. with patch('salt.utils.files.fopen', mock_open(read_data=b'')):
  594. with salt.utils.files.fopen('foo.txt', 'ab') as self.fh:
  595. self.fh.writelines(self.questions_bytes_lines)
  596. assert self.fh.writelines_calls == [self.questions_bytes_lines], self.fh.writelines_calls
  597. # Test opening for read-and-write using "r+b"
  598. with patch('salt.utils.files.fopen', mock_open(read_data=b'')):
  599. with salt.utils.files.fopen('foo.txt', 'r+b') as self.fh:
  600. self.fh.writelines(self.questions_bytes_lines)
  601. assert self.fh.writelines_calls == [self.questions_bytes_lines], self.fh.writelines_calls
  602. # Test trying to write str types to a binary filehandle
  603. with patch('salt.utils.files.fopen', mock_open(read_data=b'')):
  604. with salt.utils.files.fopen('foo.txt', 'wb') as self.fh:
  605. try:
  606. self.fh.writelines(['foo\n'])
  607. except TypeError:
  608. # This exception is expected on Python 3
  609. if not six.PY3:
  610. raise
  611. else:
  612. # This write should work fine on Python 2
  613. if six.PY3:
  614. raise Exception(
  615. 'Should not have been able to write a str to a '
  616. 'binary filehandle'
  617. )
  618. if six.PY2:
  619. # Try with non-ascii unicode. Note that the write above
  620. # should work because the mocked filehandle should attempt
  621. # a .encode() to convert it to a str type. But when writing
  622. # a string with non-ascii unicode, it should raise a
  623. # UnicodeEncodeError, which is what we are testing here.
  624. try:
  625. self.fh.writelines(self.questions_lines)
  626. except UnicodeEncodeError:
  627. pass
  628. else:
  629. raise Exception(
  630. 'Should not have been able to write non-ascii '
  631. 'unicode to a binary filehandle'
  632. )
  633. # Test trying to write bytestrings to a non-binary filehandle
  634. with patch('salt.utils.files.fopen', mock_open()):
  635. with salt.utils.files.fopen('foo.txt', 'w') as self.fh:
  636. try:
  637. self.fh.write([b'foo\n'])
  638. except TypeError:
  639. # This exception is expected on Python 3
  640. if not six.PY3:
  641. raise
  642. else:
  643. # This write should work fine on Python 2
  644. if six.PY3:
  645. raise Exception(
  646. 'Should not have been able to write a bytestring '
  647. 'to a non-binary filehandle'
  648. )
  649. if six.PY2:
  650. # Try with non-ascii unicode. Note that the write above
  651. # should work because the mocked filehandle should attempt
  652. # a .encode() to convert it to a str type. But when writing
  653. # a string with non-ascii unicode, it should raise a
  654. # UnicodeEncodeError, which is what we are testing here.
  655. try:
  656. self.fh.writelines(self.questions_lines)
  657. except UnicodeEncodeError:
  658. pass
  659. else:
  660. raise Exception(
  661. 'Should not have been able to write non-ascii '
  662. 'unicode to a binary filehandle'
  663. )
  664. def test_open(self):
  665. '''
  666. Test that opening a file for binary reading with string read_data
  667. fails, and that the same thing happens for non-binary filehandles and
  668. bytestring read_data.
  669. NOTE: This test should always pass on PY2 since MockOpen will normalize
  670. unicode types to str types.
  671. '''
  672. try:
  673. with patch('salt.utils.files.fopen', mock_open()):
  674. try:
  675. with salt.utils.files.fopen('foo.txt', 'rb') as self.fh:
  676. self.fh.read()
  677. except TypeError:
  678. pass
  679. else:
  680. if six.PY3:
  681. raise Exception(
  682. 'Should not have been able open for binary read with '
  683. 'non-bytestring read_data'
  684. )
  685. with patch('salt.utils.files.fopen', mock_open(read_data=b'')):
  686. try:
  687. with salt.utils.files.fopen('foo.txt', 'r') as self.fh2:
  688. self.fh2.read()
  689. except TypeError:
  690. pass
  691. else:
  692. if six.PY3:
  693. raise Exception(
  694. 'Should not have been able open for non-binary read '
  695. 'with bytestring read_data'
  696. )
  697. finally:
  698. # Make sure we destroy the filehandles before the teardown, as they
  699. # will also try to read and this will generate another exception
  700. delattr(self, 'fh')
  701. delattr(self, 'fh2')