test_thin.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  1. # -*- coding: utf-8 -*-
  2. '''
  3. :codeauthor: :email:`Bo Maryniuk <bo@suse.de>`
  4. '''
  5. from __future__ import absolute_import, print_function, unicode_literals
  6. import os
  7. import sys
  8. from tests.support.unit import TestCase, skipIf
  9. from tests.support.mock import (
  10. NO_MOCK,
  11. NO_MOCK_REASON,
  12. MagicMock,
  13. patch)
  14. import salt.exceptions
  15. from salt.utils import thin
  16. from salt.utils import json
  17. import salt.utils.stringutils
  18. import salt.utils.platform
  19. from salt.utils.stringutils import to_bytes as bts
  20. from salt.ext.six.moves import range
  21. try:
  22. import pytest
  23. except ImportError:
  24. pytest = None
  25. @skipIf(NO_MOCK, NO_MOCK_REASON)
  26. @skipIf(pytest is None, 'PyTest is missing')
  27. class SSHThinTestCase(TestCase):
  28. '''
  29. TestCase for SaltSSH-related parts.
  30. '''
  31. def _popen(self, return_value=None, side_effect=None, returncode=0):
  32. '''
  33. Fake subprocess.Popen
  34. :return:
  35. '''
  36. proc = MagicMock()
  37. proc.communicate = MagicMock(return_value=return_value, side_effect=side_effect)
  38. proc.returncode = returncode
  39. popen = MagicMock(return_value=proc)
  40. return popen
  41. def _version_info(self, major=None, minor=None):
  42. '''
  43. Fake version info.
  44. :param major:
  45. :param minor:
  46. :return:
  47. '''
  48. class VersionInfo(tuple):
  49. pass
  50. vi = VersionInfo([major, minor])
  51. vi.major = major or sys.version_info.major
  52. vi.minor = minor or sys.version_info.minor
  53. return vi
  54. def _tarfile(self, getinfo=False):
  55. '''
  56. Fake tarfile handler.
  57. :return:
  58. '''
  59. spec = ['add', 'close']
  60. if getinfo:
  61. spec.append('getinfo')
  62. tf = MagicMock()
  63. tf.open = MagicMock(return_value=MagicMock(spec=spec))
  64. return tf
  65. @patch('salt.exceptions.SaltSystemExit', Exception)
  66. @patch('salt.utils.thin.log', MagicMock())
  67. @patch('salt.utils.thin.os.path.isfile', MagicMock(return_value=False))
  68. def test_get_ext_tops_cfg_missing_dependencies(self):
  69. '''
  70. Test thin.get_ext_tops contains all required dependencies.
  71. :return:
  72. '''
  73. cfg = {'namespace': {'py-version': [0, 0], 'path': '/foo', 'dependencies': []}}
  74. with pytest.raises(Exception) as err:
  75. thin.get_ext_tops(cfg)
  76. self.assertIn('Missing dependencies', str(err.value))
  77. self.assertTrue(thin.log.error.called)
  78. self.assertIn('Missing dependencies', thin.log.error.call_args[0][0])
  79. self.assertIn('jinja2, yaml, tornado, msgpack',
  80. thin.log.error.call_args[0][0])
  81. @patch('salt.exceptions.SaltSystemExit', Exception)
  82. @patch('salt.utils.thin.log', MagicMock())
  83. @patch('salt.utils.thin.os.path.isfile', MagicMock(return_value=False))
  84. def test_get_ext_tops_cfg_missing_interpreter(self):
  85. '''
  86. Test thin.get_ext_tops contains interpreter configuration.
  87. :return:
  88. '''
  89. cfg = {'namespace': {'path': '/foo',
  90. 'dependencies': []}}
  91. with pytest.raises(salt.exceptions.SaltSystemExit) as err:
  92. thin.get_ext_tops(cfg)
  93. self.assertIn('missing specific locked Python version', str(err.value))
  94. @patch('salt.exceptions.SaltSystemExit', Exception)
  95. @patch('salt.utils.thin.log', MagicMock())
  96. @patch('salt.utils.thin.os.path.isfile', MagicMock(return_value=False))
  97. def test_get_ext_tops_cfg_wrong_interpreter(self):
  98. '''
  99. Test thin.get_ext_tops contains correct interpreter configuration.
  100. :return:
  101. '''
  102. cfg = {'namespace': {'path': '/foo',
  103. 'py-version': 2,
  104. 'dependencies': []}}
  105. with pytest.raises(salt.exceptions.SaltSystemExit) as err:
  106. thin.get_ext_tops(cfg)
  107. self.assertIn('specific locked Python version should be a list of '
  108. 'major/minor version', str(err.value))
  109. @patch('salt.exceptions.SaltSystemExit', Exception)
  110. @patch('salt.utils.thin.log', MagicMock())
  111. @patch('salt.utils.thin.os.path.isfile', MagicMock(return_value=False))
  112. def test_get_ext_tops_cfg_interpreter(self):
  113. '''
  114. Test thin.get_ext_tops interpreter configuration.
  115. :return:
  116. '''
  117. cfg = {'namespace': {'path': '/foo',
  118. 'py-version': [2, 6],
  119. 'dependencies': {'jinja2': '',
  120. 'yaml': '',
  121. 'tornado': '',
  122. 'msgpack': ''}}}
  123. with pytest.raises(salt.exceptions.SaltSystemExit):
  124. thin.get_ext_tops(cfg)
  125. assert len(thin.log.warning.mock_calls) == 4
  126. assert sorted([x[1][1] for x in thin.log.warning.mock_calls]) == ['jinja2', 'msgpack', 'tornado', 'yaml']
  127. assert 'Module test has missing configuration' == thin.log.warning.mock_calls[0][1][0] % 'test'
  128. @patch('salt.exceptions.SaltSystemExit', Exception)
  129. @patch('salt.utils.thin.log', MagicMock())
  130. @patch('salt.utils.thin.os.path.isfile', MagicMock(return_value=False))
  131. def test_get_ext_tops_dependency_config_check(self):
  132. '''
  133. Test thin.get_ext_tops dependencies are importable
  134. :return:
  135. '''
  136. cfg = {'namespace': {'path': '/foo',
  137. 'py-version': [2, 6],
  138. 'dependencies': {'jinja2': '/jinja/foo.py',
  139. 'yaml': '/yaml/',
  140. 'tornado': '/tornado/wrong.rb',
  141. 'msgpack': 'msgpack.sh'}}}
  142. with pytest.raises(salt.exceptions.SaltSystemExit) as err:
  143. thin.get_ext_tops(cfg)
  144. self.assertIn('Missing dependencies for the alternative version in the '
  145. 'external configuration', str(err.value))
  146. messages = {}
  147. for cl in thin.log.warning.mock_calls:
  148. messages[cl[1][1]] = cl[1][0] % (cl[1][1], cl[1][2])
  149. for mod in ['tornado', 'yaml', 'msgpack']:
  150. self.assertIn('not a Python importable module', messages[mod])
  151. self.assertIn('configured with not a file or does not exist',
  152. messages['jinja2'])
  153. @patch('salt.exceptions.SaltSystemExit', Exception)
  154. @patch('salt.utils.thin.log', MagicMock())
  155. @patch('salt.utils.thin.os.path.isfile', MagicMock(return_value=True))
  156. def test_get_ext_tops_config_pass(self):
  157. '''
  158. Test thin.get_ext_tops configuration
  159. :return:
  160. '''
  161. cfg = {'namespace': {'path': '/foo',
  162. 'py-version': [2, 6],
  163. 'dependencies': {'jinja2': '/jinja/foo.py',
  164. 'yaml': '/yaml/',
  165. 'tornado': '/tornado/tornado.py',
  166. 'msgpack': 'msgpack.py'}}}
  167. out = thin.get_ext_tops(cfg)
  168. assert out['namespace']['py-version'] == cfg['namespace']['py-version']
  169. assert out['namespace']['path'] == cfg['namespace']['path']
  170. assert sorted(out['namespace']['dependencies']) == sorted(['/tornado/tornado.py',
  171. '/jinja/foo.py', '/yaml/', 'msgpack.py'])
  172. @patch('salt.utils.thin.sys.argv', [None, '{"foo": "bar"}'])
  173. @patch('salt.utils.thin.get_tops', lambda **kw: kw)
  174. def test_gte(self):
  175. '''
  176. Test thin.gte external call for processing the info about tops per interpreter.
  177. :return:
  178. '''
  179. assert json.loads(thin.gte()).get('foo') == 'bar'
  180. def test_add_dep_path(self):
  181. '''
  182. Test thin._add_dependency function to setup dependency paths
  183. :return:
  184. '''
  185. container = []
  186. for pth in ['/foo/bar.py', '/something/else/__init__.py']:
  187. thin._add_dependency(container, type(str('obj'), (), {'__file__': pth})())
  188. assert '__init__' not in container[1]
  189. assert container == ['/foo/bar.py', '/something/else']
  190. def test_thin_path(self):
  191. '''
  192. Test thin.thin_path returns the expected path.
  193. :return:
  194. '''
  195. path = os.sep + os.path.join('path', 'to')
  196. expected = os.path.join(path, 'thin', 'thin.tgz')
  197. self.assertEqual(thin.thin_path(path), expected)
  198. def test_get_salt_call_script(self):
  199. '''
  200. Test get salt-call script rendered.
  201. :return:
  202. '''
  203. out = thin._get_salt_call('foo', 'bar', py26=[2, 6], py27=[2, 7], py34=[3, 4])
  204. for line in salt.utils.stringutils.to_str(out).split(os.linesep):
  205. if line.startswith('namespaces = {'):
  206. data = json.loads(line.replace('namespaces = ', '').strip())
  207. assert data.get('py26') == [2, 6]
  208. assert data.get('py27') == [2, 7]
  209. assert data.get('py34') == [3, 4]
  210. if line.startswith('syspaths = '):
  211. data = json.loads(line.replace('syspaths = ', ''))
  212. assert data == ['foo', 'bar']
  213. def test_get_ext_namespaces_empty(self):
  214. '''
  215. Test thin._get_ext_namespaces function returns an empty dictionary on nothing
  216. :return:
  217. '''
  218. for obj in [None, {}, []]:
  219. assert thin._get_ext_namespaces(obj) == {}
  220. def test_get_ext_namespaces(self):
  221. '''
  222. Test thin._get_ext_namespaces function returns namespaces properly out of the config.
  223. :return:
  224. '''
  225. cfg = {'ns': {'py-version': [2, 7]}}
  226. assert thin._get_ext_namespaces(cfg).get('ns') == (2, 7,)
  227. assert isinstance(thin._get_ext_namespaces(cfg).get('ns'), tuple)
  228. def test_get_ext_namespaces_failure(self):
  229. '''
  230. Test thin._get_ext_namespaces function raises an exception
  231. if python major/minor version is not configured.
  232. :return:
  233. '''
  234. with pytest.raises(salt.exceptions.SaltSystemExit):
  235. thin._get_ext_namespaces({'ns': {}})
  236. @patch('salt.utils.thin.salt', type(str('salt'), (), {'__file__': '/site-packages/salt'}))
  237. @patch('salt.utils.thin.jinja2', type(str('jinja2'), (), {'__file__': '/site-packages/jinja2'}))
  238. @patch('salt.utils.thin.yaml', type(str('yaml'), (), {'__file__': '/site-packages/yaml'}))
  239. @patch('salt.utils.thin.tornado', type(str('tornado'), (), {'__file__': '/site-packages/tornado'}))
  240. @patch('salt.utils.thin.msgpack', type(str('msgpack'), (), {'__file__': '/site-packages/msgpack'}))
  241. @patch('salt.utils.thin.certifi', type(str('certifi'), (), {'__file__': '/site-packages/certifi'}))
  242. @patch('salt.utils.thin.singledispatch', type(str('singledispatch'), (), {'__file__': '/site-packages/sdp'}))
  243. @patch('salt.utils.thin.singledispatch_helpers', type(str('singledispatch_helpers'), (), {'__file__': '/site-packages/sdp_hlp'}))
  244. @patch('salt.utils.thin.ssl_match_hostname', type(str('ssl_match_hostname'), (), {'__file__': '/site-packages/ssl_mh'}))
  245. @patch('salt.utils.thin.markupsafe', type(str('markupsafe'), (), {'__file__': '/site-packages/markupsafe'}))
  246. @patch('salt.utils.thin.backports_abc', type(str('backports_abc'), (), {'__file__': '/site-packages/backports_abc'}))
  247. @patch('salt.utils.thin.concurrent', type(str('concurrent'), (), {'__file__': '/site-packages/concurrent'}))
  248. @patch('salt.utils.thin.log', MagicMock())
  249. def test_get_tops(self):
  250. '''
  251. Test thin.get_tops to get top directories, based on the interpreter.
  252. :return:
  253. '''
  254. base_tops = ['/site-packages/salt', '/site-packages/jinja2', '/site-packages/yaml',
  255. '/site-packages/tornado', '/site-packages/msgpack', '/site-packages/certifi',
  256. '/site-packages/sdp', '/site-packages/sdp_hlp', '/site-packages/ssl_mh',
  257. '/site-packages/markupsafe', '/site-packages/backports_abc', '/site-packages/concurrent']
  258. tops = thin.get_tops()
  259. assert len(tops) == len(base_tops)
  260. assert sorted(tops) == sorted(base_tops)
  261. @patch('salt.utils.thin.salt', type(str('salt'), (), {'__file__': '/site-packages/salt'}))
  262. @patch('salt.utils.thin.jinja2', type(str('jinja2'), (), {'__file__': '/site-packages/jinja2'}))
  263. @patch('salt.utils.thin.yaml', type(str('yaml'), (), {'__file__': '/site-packages/yaml'}))
  264. @patch('salt.utils.thin.tornado', type(str('tornado'), (), {'__file__': '/site-packages/tornado'}))
  265. @patch('salt.utils.thin.msgpack', type(str('msgpack'), (), {'__file__': '/site-packages/msgpack'}))
  266. @patch('salt.utils.thin.certifi', type(str('certifi'), (), {'__file__': '/site-packages/certifi'}))
  267. @patch('salt.utils.thin.singledispatch', type(str('singledispatch'), (), {'__file__': '/site-packages/sdp'}))
  268. @patch('salt.utils.thin.singledispatch_helpers', type(str('singledispatch_helpers'), (), {'__file__': '/site-packages/sdp_hlp'}))
  269. @patch('salt.utils.thin.ssl_match_hostname', type(str('ssl_match_hostname'), (), {'__file__': '/site-packages/ssl_mh'}))
  270. @patch('salt.utils.thin.markupsafe', type(str('markupsafe'), (), {'__file__': '/site-packages/markupsafe'}))
  271. @patch('salt.utils.thin.backports_abc', type(str('backports_abc'), (), {'__file__': '/site-packages/backports_abc'}))
  272. @patch('salt.utils.thin.concurrent', type(str('concurrent'), (), {'__file__': '/site-packages/concurrent'}))
  273. @patch('salt.utils.thin.log', MagicMock())
  274. def test_get_tops_extra_mods(self):
  275. '''
  276. Test thin.get_tops to get extra-modules alongside the top directories, based on the interpreter.
  277. :return:
  278. '''
  279. base_tops = ['/site-packages/salt',
  280. '/site-packages/jinja2',
  281. '/site-packages/yaml',
  282. '/site-packages/tornado',
  283. '/site-packages/msgpack',
  284. '/site-packages/certifi',
  285. '/site-packages/sdp',
  286. '/site-packages/sdp_hlp',
  287. '/site-packages/ssl_mh',
  288. '/site-packages/concurrent',
  289. '/site-packages/markupsafe',
  290. '/site-packages/backports_abc',
  291. os.sep + os.path.join('custom', 'foo'),
  292. os.sep + os.path.join('custom', 'bar.py')]
  293. builtins = sys.version_info.major == 3 and 'builtins' or '__builtin__'
  294. foo = {'__file__': os.sep + os.path.join('custom', 'foo', '__init__.py')}
  295. bar = {'__file__': os.sep + os.path.join('custom', 'bar')}
  296. with patch('{}.__import__'.format(builtins),
  297. MagicMock(side_effect=[type(str('foo'), (), foo),
  298. type(str('bar'), (), bar)])):
  299. tops = thin.get_tops(extra_mods='foo,bar')
  300. self.assertEqual(len(tops), len(base_tops))
  301. self.assertListEqual(sorted(tops), sorted(base_tops))
  302. @patch('salt.utils.thin.salt', type(str('salt'), (), {'__file__': '/site-packages/salt'}))
  303. @patch('salt.utils.thin.jinja2', type(str('jinja2'), (), {'__file__': '/site-packages/jinja2'}))
  304. @patch('salt.utils.thin.yaml', type(str('yaml'), (), {'__file__': '/site-packages/yaml'}))
  305. @patch('salt.utils.thin.tornado', type(str('tornado'), (), {'__file__': '/site-packages/tornado'}))
  306. @patch('salt.utils.thin.msgpack', type(str('msgpack'), (), {'__file__': '/site-packages/msgpack'}))
  307. @patch('salt.utils.thin.certifi', type(str('certifi'), (), {'__file__': '/site-packages/certifi'}))
  308. @patch('salt.utils.thin.singledispatch', type(str('singledispatch'), (), {'__file__': '/site-packages/sdp'}))
  309. @patch('salt.utils.thin.singledispatch_helpers', type(str('singledispatch_helpers'), (), {'__file__': '/site-packages/sdp_hlp'}))
  310. @patch('salt.utils.thin.ssl_match_hostname', type(str('ssl_match_hostname'), (), {'__file__': '/site-packages/ssl_mh'}))
  311. @patch('salt.utils.thin.markupsafe', type(str('markupsafe'), (), {'__file__': '/site-packages/markupsafe'}))
  312. @patch('salt.utils.thin.backports_abc', type(str('backports_abc'), (), {'__file__': '/site-packages/backports_abc'}))
  313. @patch('salt.utils.thin.concurrent', type(str('concurrent'), (), {'__file__': '/site-packages/concurrent'}))
  314. @patch('salt.utils.thin.log', MagicMock())
  315. def test_get_tops_so_mods(self):
  316. '''
  317. Test thin.get_tops to get extra-modules alongside the top directories, based on the interpreter.
  318. :return:
  319. '''
  320. base_tops = ['/site-packages/salt', '/site-packages/jinja2', '/site-packages/yaml',
  321. '/site-packages/tornado', '/site-packages/msgpack', '/site-packages/certifi',
  322. '/site-packages/sdp', '/site-packages/sdp_hlp', '/site-packages/ssl_mh', '/site-packages/concurrent',
  323. '/site-packages/markupsafe', '/site-packages/backports_abc', '/custom/foo.so', '/custom/bar.so']
  324. builtins = sys.version_info.major == 3 and 'builtins' or '__builtin__'
  325. with patch('{}.__import__'.format(builtins),
  326. MagicMock(side_effect=[type(str('salt'), (), {'__file__': '/custom/foo.so'}),
  327. type(str('salt'), (), {'__file__': '/custom/bar.so'})])):
  328. tops = thin.get_tops(so_mods='foo,bar')
  329. assert len(tops) == len(base_tops)
  330. assert sorted(tops) == sorted(base_tops)
  331. @patch('salt.utils.thin.gen_thin', MagicMock(return_value='/path/to/thin/thin.tgz'))
  332. @patch('salt.utils.hashutils.get_hash', MagicMock(return_value=12345))
  333. def test_thin_sum(self):
  334. '''
  335. Test thin.thin_sum function.
  336. :return:
  337. '''
  338. assert thin.thin_sum('/cachedir', form='sha256')[1] == 12345
  339. thin.salt.utils.hashutils.get_hash.assert_called()
  340. assert thin.salt.utils.hashutils.get_hash.call_count == 1
  341. path, form = thin.salt.utils.hashutils.get_hash.call_args[0]
  342. assert path == '/path/to/thin/thin.tgz'
  343. assert form == 'sha256'
  344. @patch('salt.utils.thin.gen_min', MagicMock(return_value='/path/to/thin/min.tgz'))
  345. @patch('salt.utils.hashutils.get_hash', MagicMock(return_value=12345))
  346. def test_min_sum(self):
  347. '''
  348. Test thin.thin_sum function.
  349. :return:
  350. '''
  351. assert thin.min_sum('/cachedir', form='sha256') == 12345
  352. thin.salt.utils.hashutils.get_hash.assert_called()
  353. assert thin.salt.utils.hashutils.get_hash.call_count == 1
  354. path, form = thin.salt.utils.hashutils.get_hash.call_args[0]
  355. assert path == '/path/to/thin/min.tgz'
  356. assert form == 'sha256'
  357. @patch('salt.utils.thin.sys.version_info', (2, 5))
  358. @patch('salt.exceptions.SaltSystemExit', Exception)
  359. def test_gen_thin_fails_ancient_python_version(self):
  360. '''
  361. Test thin.gen_thin function raises an exception
  362. if Python major/minor version is lower than 2.6
  363. :return:
  364. '''
  365. with pytest.raises(salt.exceptions.SaltSystemExit) as err:
  366. thin.sys.exc_clear = lambda: None
  367. thin.gen_thin('')
  368. self.assertIn('The minimum required python version to run salt-ssh is '
  369. '"2.6"', str(err.value))
  370. @skipIf(salt.utils.platform.is_windows() and thin._six.PY2,
  371. 'Dies on Python2 on Windows')
  372. @patch('salt.exceptions.SaltSystemExit', Exception)
  373. @patch('salt.utils.thin.log', MagicMock())
  374. @patch('salt.utils.thin.os.makedirs', MagicMock())
  375. @patch('salt.utils.files.fopen', MagicMock())
  376. @patch('salt.utils.thin._get_salt_call', MagicMock())
  377. @patch('salt.utils.thin._get_ext_namespaces', MagicMock())
  378. @patch('salt.utils.thin.get_tops', MagicMock(return_value=['/foo3', '/bar3']))
  379. @patch('salt.utils.thin.get_ext_tops', MagicMock(return_value={}))
  380. @patch('salt.utils.thin.os.path.isfile', MagicMock())
  381. @patch('salt.utils.thin.os.path.isdir', MagicMock(return_value=True))
  382. @patch('salt.utils.thin.log', MagicMock())
  383. @patch('salt.utils.thin.os.remove', MagicMock())
  384. @patch('salt.utils.thin.os.path.exists', MagicMock())
  385. @patch('salt.utils.path.os_walk', MagicMock(return_value=[]))
  386. @patch('salt.utils.thin.subprocess.Popen',
  387. _popen(None, side_effect=[(bts('2.7'), bts('')), (bts('["/foo27", "/bar27"]'), bts(''))]))
  388. @patch('salt.utils.thin.tarfile', MagicMock())
  389. @patch('salt.utils.thin.zipfile', MagicMock())
  390. @patch('salt.utils.thin.os.getcwd', MagicMock())
  391. @patch('salt.utils.thin.os.chdir', MagicMock())
  392. @patch('salt.utils.thin.tempfile.mkdtemp', MagicMock())
  393. @patch('salt.utils.thin.tempfile.mkstemp', MagicMock(return_value=(3, ".temporary")))
  394. @patch('salt.utils.thin.shutil', MagicMock())
  395. @patch('salt.utils.thin._six.PY3', True)
  396. @patch('salt.utils.thin._six.PY2', False)
  397. @patch('salt.utils.thin.sys.version_info', _version_info(None, 3, 6))
  398. def test_gen_thin_compression_fallback_py3(self):
  399. '''
  400. Test thin.gen_thin function if fallbacks to the gzip compression, once setup wrong.
  401. NOTE: Py2 version of this test is not required, as code shares the same spot across the versions.
  402. :return:
  403. '''
  404. thin.gen_thin('', compress='arj')
  405. thin.log.warning.assert_called()
  406. pt, msg = thin.log.warning.mock_calls[0][1]
  407. assert pt % msg == 'Unknown compression type: "arj". Falling back to "gzip" compression.'
  408. thin.zipfile.ZipFile.assert_not_called()
  409. thin.tarfile.open.assert_called()
  410. @patch('salt.exceptions.SaltSystemExit', Exception)
  411. @patch('salt.utils.thin.log', MagicMock())
  412. @patch('salt.utils.thin.os.makedirs', MagicMock())
  413. @patch('salt.utils.files.fopen', MagicMock())
  414. @patch('salt.utils.thin._get_salt_call', MagicMock())
  415. @patch('salt.utils.thin._get_ext_namespaces', MagicMock())
  416. @patch('salt.utils.thin.get_tops', MagicMock(return_value=['/foo3', '/bar3']))
  417. @patch('salt.utils.thin.get_ext_tops', MagicMock(return_value={}))
  418. @patch('salt.utils.thin.os.path.isfile', MagicMock())
  419. @patch('salt.utils.thin.os.path.isdir', MagicMock(return_value=False))
  420. @patch('salt.utils.thin.log', MagicMock())
  421. @patch('salt.utils.thin.os.remove', MagicMock())
  422. @patch('salt.utils.thin.os.path.exists', MagicMock())
  423. @patch('salt.utils.path.os_walk', MagicMock(return_value=[]))
  424. @patch('salt.utils.thin.subprocess.Popen',
  425. _popen(None, side_effect=[(bts('2.7'), bts('')), (bts('["/foo27", "/bar27"]'), bts(''))]))
  426. @patch('salt.utils.thin.tarfile', MagicMock())
  427. @patch('salt.utils.thin.zipfile', MagicMock())
  428. @patch('salt.utils.thin.os.getcwd', MagicMock())
  429. @patch('salt.utils.thin.os.chdir', MagicMock())
  430. @patch('salt.utils.thin.os.close', MagicMock())
  431. @patch('salt.utils.thin.tempfile.mkdtemp', MagicMock(return_value=''))
  432. @patch('salt.utils.thin.tempfile.mkstemp', MagicMock(return_value=(3, ".temporary")))
  433. @patch('salt.utils.thin.shutil', MagicMock())
  434. @patch('salt.utils.thin._six.PY3', True)
  435. @patch('salt.utils.thin._six.PY2', False)
  436. @patch('salt.utils.thin.sys.version_info', _version_info(None, 3, 6))
  437. def test_gen_thin_control_files_written_py3(self):
  438. '''
  439. Test thin.gen_thin function if control files are written (version, salt-call etc).
  440. NOTE: Py2 version of this test is not required, as code shares the same spot across the versions.
  441. :return:
  442. '''
  443. thin.gen_thin('')
  444. arc_name, arc_mode = thin.tarfile.method_calls[0][1]
  445. self.assertEqual(arc_name, ".temporary")
  446. self.assertEqual(arc_mode, 'w:gz')
  447. for idx, fname in enumerate(['version', '.thin-gen-py-version', 'salt-call', 'supported-versions']):
  448. name = thin.tarfile.open().method_calls[idx + 4][1][0]
  449. self.assertEqual(name, fname)
  450. thin.tarfile.open().close.assert_called()
  451. @patch('salt.exceptions.SaltSystemExit', Exception)
  452. @patch('salt.utils.thin.log', MagicMock())
  453. @patch('salt.utils.thin.os.makedirs', MagicMock())
  454. @patch('salt.utils.files.fopen', MagicMock())
  455. @patch('salt.utils.thin._get_salt_call', MagicMock())
  456. @patch('salt.utils.thin._get_ext_namespaces', MagicMock())
  457. @patch('salt.utils.thin.get_tops', MagicMock(return_value=['/salt', '/bar3']))
  458. @patch('salt.utils.thin.get_ext_tops', MagicMock(return_value={}))
  459. @patch('salt.utils.thin.os.path.isfile', MagicMock())
  460. @patch('salt.utils.thin.os.path.isdir', MagicMock(return_value=True))
  461. @patch('salt.utils.thin.log', MagicMock())
  462. @patch('salt.utils.thin.os.remove', MagicMock())
  463. @patch('salt.utils.thin.os.path.exists', MagicMock())
  464. @patch('salt.utils.path.os_walk',
  465. MagicMock(return_value=(('root', [], ['r1', 'r2', 'r3']), ('root2', [], ['r4', 'r5', 'r6']))))
  466. @patch('salt.utils.thin.subprocess.Popen',
  467. _popen(None, side_effect=[(bts('2.7'), bts('')), (bts('["/foo27", "/bar27"]'), bts(''))]))
  468. @patch('salt.utils.thin.tarfile', _tarfile(None))
  469. @patch('salt.utils.thin.zipfile', MagicMock())
  470. @patch('salt.utils.thin.os.getcwd', MagicMock())
  471. @patch('salt.utils.thin.os.chdir', MagicMock())
  472. @patch('salt.utils.thin.os.close', MagicMock())
  473. @patch('salt.utils.thin.tempfile.mkdtemp', MagicMock(return_value=''))
  474. @patch('salt.utils.thin.tempfile.mkstemp', MagicMock(return_value=(3, ".temporary")))
  475. @patch('salt.utils.thin.shutil', MagicMock())
  476. @patch('salt.utils.thin._six.PY3', True)
  477. @patch('salt.utils.thin._six.PY2', False)
  478. @patch('salt.utils.thin.sys.version_info', _version_info(None, 3, 6))
  479. @patch('salt.utils.hashutils.DigestCollector', MagicMock())
  480. def test_gen_thin_main_content_files_written_py3(self):
  481. '''
  482. Test thin.gen_thin function if main content files are written.
  483. NOTE: Py2 version of this test is not required, as code shares the same spot across the versions.
  484. :return:
  485. '''
  486. thin.gen_thin('')
  487. files = []
  488. for py in ('py2', 'py2', 'py3', 'pyall'):
  489. for i in range(1, 4):
  490. files.append(os.path.join(py, 'root', 'r{0}'.format(i)))
  491. for i in range(4, 7):
  492. files.append(os.path.join(py, 'root2', 'r{0}'.format(i)))
  493. for cl in thin.tarfile.open().method_calls[:-6]:
  494. arcname = cl[2].get('arcname')
  495. self.assertIn(arcname, files)
  496. files.pop(files.index(arcname))
  497. self.assertFalse(files)
  498. @patch('salt.exceptions.SaltSystemExit', Exception)
  499. @patch('salt.utils.thin.log', MagicMock())
  500. @patch('salt.utils.thin.os.makedirs', MagicMock())
  501. @patch('salt.utils.files.fopen', MagicMock())
  502. @patch('salt.utils.thin._get_salt_call', MagicMock())
  503. @patch('salt.utils.thin._get_ext_namespaces', MagicMock())
  504. @patch('salt.utils.thin.get_tops', MagicMock(return_value=[]))
  505. @patch('salt.utils.thin.get_ext_tops',
  506. MagicMock(return_value={'namespace': {'py-version': [2, 7],
  507. 'path': '/opt/2015.8/salt',
  508. 'dependencies': ['/opt/certifi', '/opt/whatever']}}))
  509. @patch('salt.utils.thin.os.path.isfile', MagicMock())
  510. @patch('salt.utils.thin.os.path.isdir', MagicMock(return_value=True))
  511. @patch('salt.utils.thin.log', MagicMock())
  512. @patch('salt.utils.thin.os.remove', MagicMock())
  513. @patch('salt.utils.thin.os.path.exists', MagicMock())
  514. @patch('salt.utils.path.os_walk',
  515. MagicMock(return_value=(('root', [], ['r1', 'r2', 'r3']), ('root2', [], ['r4', 'r5', 'r6']))))
  516. @patch('salt.utils.thin.subprocess.Popen',
  517. _popen(None, side_effect=[(bts('2.7'), bts('')), (bts('["/foo27", "/bar27"]'), bts(''))]))
  518. @patch('salt.utils.thin.tarfile', _tarfile(None))
  519. @patch('salt.utils.thin.zipfile', MagicMock())
  520. @patch('salt.utils.thin.os.getcwd', MagicMock())
  521. @patch('salt.utils.thin.os.chdir', MagicMock())
  522. @patch('salt.utils.thin.os.close', MagicMock())
  523. @patch('salt.utils.thin.tempfile.mkdtemp', MagicMock(return_value=''))
  524. @patch('salt.utils.thin.tempfile.mkstemp', MagicMock(return_value=(3, ".temporary")))
  525. @patch('salt.utils.thin.shutil', MagicMock())
  526. @patch('salt.utils.thin._six.PY3', True)
  527. @patch('salt.utils.thin._six.PY2', False)
  528. @patch('salt.utils.thin.sys.version_info', _version_info(None, 3, 6))
  529. @patch('salt.utils.hashutils.DigestCollector', MagicMock())
  530. def test_gen_thin_ext_alternative_content_files_written_py3(self):
  531. '''
  532. Test thin.gen_thin function if external alternative content files are written.
  533. NOTE: Py2 version of this test is not required, as code shares the same spot across the versions.
  534. :return:
  535. '''
  536. thin.gen_thin('')
  537. files = []
  538. for py in ('pyall', 'pyall', 'py2'):
  539. for i in range(1, 4):
  540. files.append(
  541. os.path.join('namespace', py, 'root', 'r{0}'.format(i)))
  542. for i in range(4, 7):
  543. files.append(
  544. os.path.join('namespace', py, 'root2', 'r{0}'.format(i)))
  545. for idx, cl in enumerate(thin.tarfile.open().method_calls[12:-6]):
  546. arcname = cl[2].get('arcname')
  547. self.assertIn(arcname, files)
  548. files.pop(files.index(arcname))
  549. self.assertFalse(files)
  550. def test_get_supported_py_config_typecheck(self):
  551. '''
  552. Test collecting proper py-versions. Should return bytes type.
  553. :return:
  554. '''
  555. tops = {}
  556. ext_cfg = {}
  557. out = thin._get_supported_py_config(tops=tops, extended_cfg=ext_cfg)
  558. assert type(salt.utils.stringutils.to_bytes('')) == type(out)
  559. def test_get_supported_py_config_base_tops(self):
  560. '''
  561. Test collecting proper py-versions. Should return proper base tops.
  562. :return:
  563. '''
  564. tops = {'3': ['/groundkeepers', '/stole'], '2': ['/the-root', '/password']}
  565. ext_cfg = {}
  566. out = salt.utils.stringutils.to_str(thin._get_supported_py_config(
  567. tops=tops, extended_cfg=ext_cfg)).strip().split(os.linesep)
  568. self.assertEqual(len(out), 2)
  569. for t_line in ['py3:3:0', 'py2:2:7']:
  570. self.assertIn(t_line, out)
  571. def test_get_supported_py_config_ext_tops(self):
  572. '''
  573. Test collecting proper py-versions. Should return proper ext conf tops.
  574. :return:
  575. '''
  576. tops = {}
  577. ext_cfg = {
  578. 'solar-interference': {
  579. 'py-version': [2, 6]},
  580. 'second-system-effect': {
  581. 'py-version': [2, 7]}}
  582. out = salt.utils.stringutils.to_str(thin._get_supported_py_config(
  583. tops=tops, extended_cfg=ext_cfg)).strip().split(os.linesep)
  584. for t_line in ['second-system-effect:2:7', 'solar-interference:2:6']:
  585. self.assertIn(t_line, out)