test_mock.py 34 KB

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