test_pip_state.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. # -*- coding: utf-8 -*-
  2. '''
  3. :codeauthor: Pedro Algarvio (pedro@algarvio.me)
  4. tests.integration.states.pip_state
  5. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  6. '''
  7. # Import python libs
  8. from __future__ import absolute_import, print_function, unicode_literals
  9. import errno
  10. import os
  11. import glob
  12. import pprint
  13. import shutil
  14. import sys
  15. try:
  16. import pwd
  17. HAS_PWD = True
  18. except ImportError:
  19. HAS_PWD = False
  20. # Import Salt Testing libs
  21. from tests.support.case import ModuleCase
  22. from tests.support.helpers import (
  23. requires_system_grains,
  24. with_system_user,
  25. with_tempdir,
  26. patched_environ
  27. )
  28. from tests.support.mixins import SaltReturnAssertsMixin
  29. from tests.support.runtests import RUNTIME_VARS
  30. from tests.support.unit import skipIf
  31. # Import salt libs
  32. import salt.utils.files
  33. import salt.utils.path
  34. import salt.utils.platform
  35. import salt.utils.versions
  36. import salt.utils.win_dacl
  37. import salt.utils.win_functions
  38. import salt.utils.win_runas
  39. from salt.modules.virtualenv_mod import KNOWN_BINARY_NAMES
  40. from salt.exceptions import CommandExecutionError
  41. # Import 3rd-party libs
  42. import pytest
  43. from salt.ext import six
  44. class VirtualEnv(object):
  45. def __init__(self, test, venv_dir):
  46. self.venv_dir = venv_dir
  47. self.test = test
  48. self.test.addCleanup(shutil.rmtree, self.venv_dir, ignore_errors=True)
  49. def __enter__(self):
  50. ret = self.test._create_virtualenv(self.venv_dir)
  51. self.test.assertEqual(
  52. ret['retcode'], 0,
  53. msg='Expected \'retcode\' key did not match. Full return dictionary:\n{}'.format(
  54. pprint.pformat(ret)
  55. )
  56. )
  57. def __exit__(self, *args):
  58. pass
  59. @skipIf(salt.utils.path.which_bin(KNOWN_BINARY_NAMES) is None, 'virtualenv not installed')
  60. @pytest.mark.windows_whitelisted
  61. class PipStateTest(ModuleCase, SaltReturnAssertsMixin):
  62. def _create_virtualenv(self, path, **kwargs):
  63. '''
  64. The reason why the virtualenv creation is proxied by this function is mostly
  65. because under windows, we can't seem to properly create a virtualenv off of
  66. another virtualenv(we can on linux) and also because, we really don't want to
  67. test virtualenv creation off of another virtualenv, we want a virtualenv created
  68. from the original python.
  69. Also, one windows, we must also point to the virtualenv binary outside the existing
  70. virtualenv because it will fail otherwise
  71. '''
  72. self.addCleanup(shutil.rmtree, path, ignore_errors=True)
  73. try:
  74. if salt.utils.platform.is_windows():
  75. python = os.path.join(sys.real_prefix, os.path.basename(sys.executable))
  76. else:
  77. python_binary_names = [
  78. 'python{}.{}'.format(*sys.version_info),
  79. 'python{}'.format(*sys.version_info),
  80. 'python'
  81. ]
  82. for binary_name in python_binary_names:
  83. python = os.path.join(sys.real_prefix, 'bin', binary_name)
  84. if os.path.exists(python):
  85. break
  86. else:
  87. self.fail(
  88. 'Couldn\'t find a python binary name under \'{}\' matching: {}'.format(
  89. os.path.join(sys.real_prefix, 'bin'),
  90. python_binary_names
  91. )
  92. )
  93. # We're running off a virtualenv, and we don't want to create a virtualenv off of
  94. # a virtualenv, let's point to the actual python that created the virtualenv
  95. kwargs['python'] = python
  96. except AttributeError:
  97. # We're running off of the system python
  98. pass
  99. return self.run_function('virtualenv.create', [path], **kwargs)
  100. def test_pip_installed_removed(self):
  101. '''
  102. Tests installed and removed states
  103. '''
  104. name = 'pudb'
  105. if name in self.run_function('pip.list'):
  106. self.skipTest('{0} is already installed, uninstall to run this test'.format(name))
  107. ret = self.run_state('pip.installed', name=name)
  108. self.assertSaltTrueReturn(ret)
  109. ret = self.run_state('pip.removed', name=name)
  110. self.assertSaltTrueReturn(ret)
  111. def test_pip_installed_removed_venv(self):
  112. venv_dir = os.path.join(
  113. RUNTIME_VARS.TMP, 'pip_installed_removed'
  114. )
  115. with VirtualEnv(self, venv_dir):
  116. name = 'pudb'
  117. ret = self.run_state('pip.installed', name=name, bin_env=venv_dir)
  118. self.assertSaltTrueReturn(ret)
  119. ret = self.run_state('pip.removed', name=name, bin_env=venv_dir)
  120. self.assertSaltTrueReturn(ret)
  121. def test_pip_installed_errors(self):
  122. venv_dir = os.path.join(
  123. RUNTIME_VARS.TMP, 'pip-installed-errors'
  124. )
  125. self.addCleanup(shutil.rmtree, venv_dir, ignore_errors=True)
  126. # Since we don't have the virtualenv created, pip.installed will
  127. # throw an error.
  128. # Example error strings:
  129. # * "Error installing 'pep8': /tmp/pip-installed-errors: not found"
  130. # * "Error installing 'pep8': /bin/sh: 1: /tmp/pip-installed-errors: not found"
  131. # * "Error installing 'pep8': /bin/bash: /tmp/pip-installed-errors: No such file or directory"
  132. with patched_environ(SHELL='/bin/sh'):
  133. ret = self.run_function('state.sls', mods='pip-installed-errors')
  134. self.assertSaltFalseReturn(ret)
  135. self.assertSaltCommentRegexpMatches(
  136. ret,
  137. 'Error installing \'pep8\':'
  138. )
  139. # We now create the missing virtualenv
  140. ret = self.run_function('virtualenv.create', [venv_dir])
  141. self.assertEqual(ret['retcode'], 0)
  142. # The state should not have any issues running now
  143. ret = self.run_function('state.sls', mods='pip-installed-errors')
  144. self.assertSaltTrueReturn(ret)
  145. @skipIf(six.PY3, 'Issue is specific to carbon module, which is PY2-only')
  146. @skipIf(salt.utils.platform.is_windows(), "Carbon does not install in Windows")
  147. @requires_system_grains
  148. def test_pip_installed_weird_install(self, grains=None):
  149. # First, check to see if this is running on CentOS 5 or MacOS.
  150. # If so, skip this test.
  151. if grains['os'] in ('CentOS',) and grains['osrelease_info'][0] in (5,):
  152. self.skipTest('This test does not run reliably on CentOS 5')
  153. if grains['os'] in ('MacOS',):
  154. self.skipTest('This test does not run reliably on MacOS')
  155. ographite = '/opt/graphite'
  156. if os.path.isdir(ographite):
  157. self.skipTest(
  158. 'You already have \'{0}\'. This test would overwrite this '
  159. 'directory'.format(ographite)
  160. )
  161. try:
  162. os.makedirs(ographite)
  163. except OSError as err:
  164. if err.errno == errno.EACCES:
  165. # Permission denied
  166. self.skipTest(
  167. 'You don\'t have the required permissions to run this test'
  168. )
  169. finally:
  170. if os.path.isdir(ographite):
  171. shutil.rmtree(ographite, ignore_errors=True)
  172. venv_dir = os.path.join(RUNTIME_VARS.TMP, 'pip-installed-weird-install')
  173. try:
  174. # We may be able to remove this, I had to add it because the custom
  175. # modules from the test suite weren't available in the jinja
  176. # context when running the call to state.sls that comes after.
  177. self.run_function('saltutil.sync_modules')
  178. # Since we don't have the virtualenv created, pip.installed will
  179. # throw an error.
  180. ret = self.run_function(
  181. 'state.sls', mods='pip-installed-weird-install'
  182. )
  183. self.assertSaltTrueReturn(ret)
  184. # We cannot use assertInSaltComment here because we need to skip
  185. # some of the state return parts
  186. for key in six.iterkeys(ret):
  187. self.assertTrue(ret[key]['result'])
  188. if ret[key]['name'] != 'carbon < 1.1':
  189. continue
  190. self.assertEqual(
  191. ret[key]['comment'],
  192. 'There was no error installing package \'carbon < 1.1\' '
  193. 'although it does not show when calling \'pip.freeze\'.'
  194. )
  195. break
  196. else:
  197. raise Exception('Expected state did not run')
  198. finally:
  199. if os.path.isdir(ographite):
  200. shutil.rmtree(ographite, ignore_errors=True)
  201. def test_issue_2028_pip_installed_state(self):
  202. ret = self.run_function('state.sls', mods='issue-2028-pip-installed')
  203. venv_dir = os.path.join(
  204. RUNTIME_VARS.TMP, 'issue-2028-pip-installed'
  205. )
  206. self.addCleanup(shutil.rmtree, venv_dir, ignore_errors=True)
  207. pep8_bin = os.path.join(venv_dir, 'bin', 'pep8')
  208. if salt.utils.platform.is_windows():
  209. pep8_bin = os.path.join(venv_dir, 'Scripts', 'pep8.exe')
  210. self.assertSaltTrueReturn(ret)
  211. self.assertTrue(
  212. os.path.isfile(pep8_bin)
  213. )
  214. def test_issue_2087_missing_pip(self):
  215. venv_dir = os.path.join(
  216. RUNTIME_VARS.TMP, 'issue-2087-missing-pip'
  217. )
  218. # Let's create the testing virtualenv
  219. ret = self._create_virtualenv(venv_dir)
  220. self.assertEqual(
  221. ret['retcode'], 0,
  222. msg='Expected \'retcode\' key did not match. Full return dictionary:\n{}'.format(
  223. pprint.pformat(ret)
  224. )
  225. )
  226. # Let's remove the pip binary
  227. pip_bin = os.path.join(venv_dir, 'bin', 'pip')
  228. site_dir = self.run_function('virtualenv.get_distribution_path', [venv_dir, 'pip'])
  229. if salt.utils.platform.is_windows():
  230. pip_bin = os.path.join(venv_dir, 'Scripts', 'pip.exe')
  231. site_dir = os.path.join(venv_dir, 'lib', 'site-packages')
  232. if not os.path.isfile(pip_bin):
  233. self.skipTest(
  234. 'Failed to find the pip binary to the test virtualenv'
  235. )
  236. os.remove(pip_bin)
  237. # Also remove the pip dir from site-packages
  238. # This is needed now that we're using python -m pip instead of the
  239. # pip binary directly. python -m pip will still work even if the
  240. # pip binary is missing
  241. shutil.rmtree(os.path.join(site_dir, 'pip'))
  242. # Let's run the state which should fail because pip is missing
  243. ret = self.run_function('state.sls', mods='issue-2087-missing-pip')
  244. self.assertSaltFalseReturn(ret)
  245. self.assertInSaltComment(
  246. 'Error installing \'pep8\': Could not find a `pip` binary',
  247. ret
  248. )
  249. def test_issue_5940_multiple_pip_mirrors(self):
  250. '''
  251. Test multiple pip mirrors. This test only works with pip < 7.0.0
  252. '''
  253. ret = self.run_function(
  254. 'state.sls', mods='issue-5940-multiple-pip-mirrors'
  255. )
  256. venv_dir = os.path.join(
  257. RUNTIME_VARS.TMP, '5940-multiple-pip-mirrors'
  258. )
  259. self.addCleanup(shutil.rmtree, venv_dir, ignore_errors=True)
  260. try:
  261. self.assertSaltTrueReturn(ret)
  262. self.assertTrue(
  263. os.path.isfile(os.path.join(venv_dir, 'bin', 'pep8'))
  264. )
  265. except (AssertionError, CommandExecutionError):
  266. pip_version = self.run_function('pip.version', [venv_dir])
  267. if salt.utils.versions.compare(ver1=pip_version, oper='>=', ver2='7.0.0'):
  268. self.skipTest('the --mirrors arg has been deprecated and removed in pip==7.0.0')
  269. @pytest.mark.destructive_test
  270. @pytest.mark.skip_if_not_root
  271. @with_system_user('issue-6912', on_existing='delete', delete=True,
  272. password='PassWord1!')
  273. @with_tempdir()
  274. def test_issue_6912_wrong_owner(self, temp_dir, username):
  275. # Setup virtual environment directory to be used throughout the test
  276. venv_dir = os.path.join(temp_dir, '6912-wrong-owner')
  277. # The virtual environment needs to be in a location that is accessible
  278. # by both the user running the test and the runas user
  279. if salt.utils.platform.is_windows():
  280. salt.utils.win_dacl.set_permissions(temp_dir, username, 'full_control')
  281. else:
  282. uid = self.run_function('file.user_to_uid', [username])
  283. os.chown(temp_dir, uid, -1)
  284. # Create the virtual environment
  285. venv_create = self._create_virtualenv(venv_dir, user=username, password='PassWord1!')
  286. if venv_create['retcode'] > 0:
  287. self.skipTest('Failed to create testcase virtual environment: {0}'
  288. ''.format(venv_create))
  289. # pip install passing the package name in `name`
  290. ret = self.run_state(
  291. 'pip.installed', name='pep8', user=username, bin_env=venv_dir,
  292. password='PassWord1!')
  293. self.assertSaltTrueReturn(ret)
  294. if HAS_PWD:
  295. uid = pwd.getpwnam(username).pw_uid
  296. for globmatch in (os.path.join(venv_dir, '**', 'pep8*'),
  297. os.path.join(venv_dir, '*', '**', 'pep8*'),
  298. os.path.join(venv_dir, '*', '*', '**', 'pep8*')):
  299. for path in glob.glob(globmatch):
  300. if HAS_PWD:
  301. self.assertEqual(uid, os.stat(path).st_uid)
  302. elif salt.utils.platform.is_windows():
  303. self.assertEqual(
  304. salt.utils.win_dacl.get_owner(path), username)
  305. @pytest.mark.destructive_test
  306. @pytest.mark.skip_if_not_root
  307. @skipIf(salt.utils.platform.is_darwin(), 'Test is flaky on macosx')
  308. @with_system_user('issue-6912', on_existing='delete', delete=True,
  309. password='PassWord1!')
  310. @with_tempdir()
  311. def test_issue_6912_wrong_owner_requirements_file(self, temp_dir, username):
  312. # Setup virtual environment directory to be used throughout the test
  313. venv_dir = os.path.join(temp_dir, '6912-wrong-owner')
  314. # The virtual environment needs to be in a location that is accessible
  315. # by both the user running the test and the runas user
  316. if salt.utils.platform.is_windows():
  317. salt.utils.win_dacl.set_permissions(temp_dir, username, 'full_control')
  318. else:
  319. uid = self.run_function('file.user_to_uid', [username])
  320. os.chown(temp_dir, uid, -1)
  321. # Create the virtual environment again as it should have been removed
  322. venv_create = self._create_virtualenv(venv_dir, user=username, password='PassWord1!')
  323. if venv_create['retcode'] > 0:
  324. self.skipTest('failed to create testcase virtual environment: {0}'
  325. ''.format(venv_create))
  326. # pip install using a requirements file
  327. req_filename = os.path.join(
  328. RUNTIME_VARS.TMP_STATE_TREE, 'issue-6912-requirements.txt'
  329. )
  330. with salt.utils.files.fopen(req_filename, 'wb') as reqf:
  331. reqf.write(b'pep8\n')
  332. ret = self.run_state(
  333. 'pip.installed', name='', user=username, bin_env=venv_dir,
  334. requirements='salt://issue-6912-requirements.txt',
  335. password='PassWord1!')
  336. self.assertSaltTrueReturn(ret)
  337. if HAS_PWD:
  338. uid = pwd.getpwnam(username).pw_uid
  339. for globmatch in (os.path.join(venv_dir, '**', 'pep8*'),
  340. os.path.join(venv_dir, '*', '**', 'pep8*'),
  341. os.path.join(venv_dir, '*', '*', '**', 'pep8*')):
  342. for path in glob.glob(globmatch):
  343. if HAS_PWD:
  344. self.assertEqual(uid, os.stat(path).st_uid)
  345. elif salt.utils.platform.is_windows():
  346. self.assertEqual(
  347. salt.utils.win_dacl.get_owner(path), username)
  348. def test_issue_6833_pip_upgrade_pip(self):
  349. # Create the testing virtualenv
  350. venv_dir = os.path.join(
  351. RUNTIME_VARS.TMP, '6833-pip-upgrade-pip'
  352. )
  353. ret = self._create_virtualenv(venv_dir)
  354. self.assertEqual(
  355. ret['retcode'], 0,
  356. msg='Expected \'retcode\' key did not match. Full return dictionary:\n{}'.format(
  357. pprint.pformat(ret)
  358. )
  359. )
  360. self.assertIn(
  361. 'New python executable',
  362. ret['stdout'],
  363. msg='Expected STDOUT did not match. Full return dictionary:\n{}'.format(
  364. pprint.pformat(ret)
  365. )
  366. )
  367. # Let's install a fixed version pip over whatever pip was
  368. # previously installed
  369. ret = self.run_function(
  370. 'pip.install', ['pip==8.0'], upgrade=True,
  371. bin_env=venv_dir
  372. )
  373. if not isinstance(ret, dict):
  374. self.fail(
  375. 'The \'pip.install\' command did not return the excepted dictionary. Output:\n{}'.format(ret)
  376. )
  377. self.assertEqual(ret['retcode'], 0)
  378. self.assertIn(
  379. 'Successfully installed pip',
  380. ret['stdout']
  381. )
  382. # Let's make sure we have pip 8.0 installed
  383. self.assertEqual(
  384. self.run_function('pip.list', ['pip'], bin_env=venv_dir),
  385. {'pip': '8.0.0'}
  386. )
  387. # Now the actual pip upgrade pip test
  388. ret = self.run_state(
  389. 'pip.installed', name='pip==8.0.1', upgrade=True,
  390. bin_env=venv_dir
  391. )
  392. if not isinstance(ret, dict):
  393. self.fail(
  394. 'The \'pip.install\' command did not return the excepted dictionary. Output:\n{}'.format(ret)
  395. )
  396. self.assertSaltTrueReturn(ret)
  397. self.assertSaltStateChangesEqual(ret, {'pip==8.0.1': 'Installed'})
  398. def test_pip_installed_specific_env(self):
  399. # Create the testing virtualenv
  400. venv_dir = os.path.join(
  401. RUNTIME_VARS.TMP, 'pip-installed-specific-env'
  402. )
  403. # Let's write a requirements file
  404. requirements_file = os.path.join(
  405. RUNTIME_VARS.TMP_PRODENV_STATE_TREE, 'prod-env-requirements.txt'
  406. )
  407. with salt.utils.files.fopen(requirements_file, 'wb') as reqf:
  408. reqf.write(b'pep8\n')
  409. try:
  410. self._create_virtualenv(venv_dir)
  411. # The requirements file should not be found the base environment
  412. ret = self.run_state(
  413. 'pip.installed', name='', bin_env=venv_dir,
  414. requirements='salt://prod-env-requirements.txt'
  415. )
  416. self.assertSaltFalseReturn(ret)
  417. self.assertInSaltComment(
  418. "'salt://prod-env-requirements.txt' not found", ret
  419. )
  420. # The requirements file must be found in the prod environment
  421. ret = self.run_state(
  422. 'pip.installed', name='', bin_env=venv_dir, saltenv='prod',
  423. requirements='salt://prod-env-requirements.txt'
  424. )
  425. self.assertSaltTrueReturn(ret)
  426. self.assertInSaltComment(
  427. 'Successfully processed requirements file '
  428. 'salt://prod-env-requirements.txt', ret
  429. )
  430. # We're using the base environment but we're passing the prod
  431. # environment as an url arg to salt://
  432. ret = self.run_state(
  433. 'pip.installed', name='', bin_env=venv_dir,
  434. requirements='salt://prod-env-requirements.txt?saltenv=prod'
  435. )
  436. self.assertSaltTrueReturn(ret)
  437. self.assertInSaltComment(
  438. 'Requirements were already installed.',
  439. ret
  440. )
  441. finally:
  442. if os.path.isfile(requirements_file):
  443. os.unlink(requirements_file)
  444. @skipIf(salt.utils.platform.is_darwin() and six.PY2, 'This test hangs on OS X on Py2')
  445. def test_22359_pip_installed_unless_does_not_trigger_warnings(self):
  446. # This test case should be moved to a format_call unit test specific to
  447. # the state internal keywords
  448. venv_dir = os.path.join(RUNTIME_VARS.TMP, 'pip-installed-unless')
  449. venv_create = self._create_virtualenv(venv_dir)
  450. if venv_create['retcode'] > 0:
  451. self.skipTest(
  452. 'Failed to create testcase virtual environment: {0}'.format(
  453. venv_create
  454. )
  455. )
  456. false_cmd = RUNTIME_VARS.SHELL_FALSE_PATH
  457. if salt.utils.platform.is_windows():
  458. false_cmd = 'exit 1 >nul'
  459. try:
  460. ret = self.run_state(
  461. 'pip.installed', name='pep8', bin_env=venv_dir, unless=false_cmd, timeout=600
  462. )
  463. self.assertSaltTrueReturn(ret)
  464. self.assertNotIn('warnings', next(six.itervalues(ret)))
  465. finally:
  466. if os.path.isdir(venv_dir):
  467. shutil.rmtree(venv_dir, ignore_errors=True)
  468. @skipIf(sys.version_info[:2] >= (3, 6), 'Old version of virtualenv too old for python3.6')
  469. @skipIf(salt.utils.platform.is_windows(), "Carbon does not install in Windows")
  470. def test_46127_pip_env_vars(self):
  471. '''
  472. Test that checks if env_vars passed to pip.installed are also passed
  473. to pip.freeze while checking for existing installations
  474. '''
  475. # This issue is most easily checked while installing carbon
  476. # Much of the code here comes from the test_weird_install function above
  477. ographite = '/opt/graphite'
  478. if os.path.isdir(ographite):
  479. self.skipTest(
  480. 'You already have \'{0}\'. This test would overwrite this '
  481. 'directory'.format(ographite)
  482. )
  483. try:
  484. os.makedirs(ographite)
  485. except OSError as err:
  486. if err.errno == errno.EACCES:
  487. # Permission denied
  488. self.skipTest(
  489. 'You don\'t have the required permissions to run this test'
  490. )
  491. finally:
  492. if os.path.isdir(ographite):
  493. shutil.rmtree(ographite, ignore_errors=True)
  494. venv_dir = os.path.join(RUNTIME_VARS.TMP, 'issue-46127-pip-env-vars')
  495. try:
  496. # We may be able to remove this, I had to add it because the custom
  497. # modules from the test suite weren't available in the jinja
  498. # context when running the call to state.sls that comes after.
  499. self.run_function('saltutil.sync_modules')
  500. # Since we don't have the virtualenv created, pip.installed will
  501. # throw an error.
  502. ret = self.run_function(
  503. 'state.sls', mods='issue-46127-pip-env-vars'
  504. )
  505. self.assertSaltTrueReturn(ret)
  506. for key in six.iterkeys(ret):
  507. self.assertTrue(ret[key]['result'])
  508. if ret[key]['name'] != 'carbon < 1.3':
  509. continue
  510. self.assertEqual(
  511. ret[key]['comment'],
  512. 'All packages were successfully installed'
  513. )
  514. break
  515. else:
  516. raise Exception('Expected state did not run')
  517. # Run the state again. Now the already installed message should
  518. # appear
  519. ret = self.run_function(
  520. 'state.sls', mods='issue-46127-pip-env-vars'
  521. )
  522. self.assertSaltTrueReturn(ret)
  523. # We cannot use assertInSaltComment here because we need to skip
  524. # some of the state return parts
  525. for key in six.iterkeys(ret):
  526. self.assertTrue(ret[key]['result'])
  527. # As we are re-running the formula, some states will not be run
  528. # and "name" may or may not be present, so we use .get() pattern
  529. if ret[key].get('name', '') != 'carbon < 1.3':
  530. continue
  531. self.assertEqual(
  532. ret[key]['comment'],
  533. ('All packages were successfully installed'))
  534. break
  535. else:
  536. raise Exception('Expected state did not run')
  537. finally:
  538. if os.path.isdir(ographite):
  539. shutil.rmtree(ographite, ignore_errors=True)
  540. if os.path.isdir(venv_dir):
  541. shutil.rmtree(venv_dir)
  542. @pytest.mark.windows_whitelisted
  543. class PipStateInRequisiteTest(ModuleCase, SaltReturnAssertsMixin):
  544. @with_tempdir()
  545. def test_issue_54755(self, tmpdir):
  546. '''
  547. Verify github issue 54755 is resolved. This only fails when there is no
  548. pip module in the python environment. Since the test suite normally has
  549. a pip module this test will pass and is here for posterity. See also
  550. unit.states.test_pip_state.PipStateUtilsTest.test_pip_purge_method_with_pip
  551. and
  552. unit.states.test_pip_state.PipStateUtilsTest.test_pip_purge_method_without_pip
  553. Which also validate this issue and will pass/fail regardless of whether
  554. or not pip is installed.
  555. '''
  556. file_path = os.path.join(tmpdir, 'issue-54755')
  557. ret = self.run_function('state.sls', mods='issue-54755', pillar={'file_path': file_path})
  558. key = 'file_|-issue-54755_|-{}_|-managed'.format(file_path)
  559. assert key in ret
  560. assert ret[key]['result'] is True
  561. with salt.utils.files.fopen(file_path, 'r') as fp:
  562. assert fp.read().strip() == 'issue-54755'