test_idf_tools_python_env.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. # SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: Apache-2.0
  3. # NOTE: unittest is by default sorting tests based on their names,
  4. # so the order if which the tests are started may be different from
  5. # the order in which they are defined. Please make sure all tests
  6. # are entirely self contained and don't have affect on other tests,
  7. # for example by changing some global state, like system environment.
  8. # If test needs to change global state, it should return it to the
  9. # original state after it's finished. For more information please see
  10. # https://docs.python.org/3/library/unittest.html#organizing-test-code
  11. import inspect
  12. import os
  13. import shutil
  14. import subprocess
  15. import sys
  16. import tempfile
  17. import unittest
  18. from typing import List
  19. try:
  20. import idf_tools
  21. except ImportError:
  22. sys.path.append('..')
  23. import idf_tools
  24. IDF_PATH = os.environ.get('IDF_PATH', '../..')
  25. TOOLS_DIR = os.environ.get('IDF_TOOLS_PATH') or os.path.expanduser(idf_tools.IDF_TOOLS_PATH_DEFAULT)
  26. PYTHON_DIR = os.path.join(TOOLS_DIR, 'python_env')
  27. PYTHON_DIR_BACKUP = tempfile.mkdtemp()
  28. REQ_SATISFIED = 'Python requirements are satisfied'
  29. REQ_CORE = '- {}'.format(os.path.join(IDF_PATH, 'tools', 'requirements', 'requirements.core.txt'))
  30. REQ_GDBGUI = '- {}'.format(os.path.join(IDF_PATH, 'tools', 'requirements', 'requirements.gdbgui.txt'))
  31. CONSTR = 'Constraint file: {}/espidf.constraints'.format(TOOLS_DIR)
  32. # Set default global paths for idf_tools. If some test needs to
  33. # use functions from idf_tools with custom paths, it should
  34. # set it in setUp() and change them back to defaults in tearDown().
  35. idf_tools.global_idf_path = IDF_PATH
  36. idf_tools.global_idf_tools_path = TOOLS_DIR
  37. def setUpModule(): # type: () -> None
  38. shutil.rmtree(PYTHON_DIR_BACKUP)
  39. shutil.move(PYTHON_DIR, PYTHON_DIR_BACKUP)
  40. def tearDownModule(): # type: () -> None
  41. if os.path.isdir(PYTHON_DIR):
  42. shutil.rmtree(PYTHON_DIR)
  43. shutil.move(PYTHON_DIR_BACKUP, PYTHON_DIR)
  44. class BasePythonInstall(unittest.TestCase):
  45. def run_tool(self, cmd): # type: (List[str]) -> str
  46. ret = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=600)
  47. decoded_output = ret.stdout.decode('utf-8', 'ignore')
  48. with open(os.path.join(IDF_PATH, 'tools', 'test_idf_tools', 'test_python_env_logs.txt'), 'a+') as w:
  49. # stack() returns list of callers frame records. [1] represent caller of this function
  50. w.write('============================= ' + inspect.stack()[1].function + ' =============================\n')
  51. w.write(decoded_output)
  52. return decoded_output
  53. def run_idf_tools(self, args): # type: (List[str]) -> str
  54. cmd = [sys.executable, '../idf_tools.py'] + args
  55. return self.run_tool(cmd)
  56. def run_in_venv(self, args): # type: (List[str]) -> str
  57. _, _, python_venv, _ = idf_tools.get_python_env_path()
  58. cmd = [python_venv] + args
  59. return self.run_tool(cmd)
  60. def dump_package(self, whl, name): # type: (bytes, str) -> str
  61. tmpdir = tempfile.mkdtemp()
  62. foopackage_fn = os.path.join(tmpdir, name)
  63. with open(foopackage_fn, 'wb') as fd:
  64. fd.write(whl)
  65. self.addCleanup(shutil.rmtree, tmpdir)
  66. return foopackage_fn
  67. def dump_foopackage(self): # type: () -> str
  68. # Wheel for foopackage-0.99-py3-none-any.whl
  69. # This is dummy package for testing purposes created with
  70. # python -m build --wheel for the following package
  71. '''
  72. ├── foopackage
  73. │   └── __init__.py
  74. └── setup.py
  75. setup.py
  76. from setuptools import setup
  77. setup(
  78. name="foopackage",
  79. version="0.99",
  80. )
  81. __init__.py
  82. if __name__ == '__main__':
  83. return
  84. '''
  85. whl = (b'PK\x03\x04\x14\x00\x00\x00\x08\x00\x07fqVz|E\t&\x00\x00\x00&\x00\x00\x00\x16\x00\x00\x00'
  86. b'foopackage/__init__.py\xcbLS\x88\x8f\xcfK\xccM\x8d\x8fW\xb0\xb5UP\x8f\x8f\xcfM\xcc\xcc\x8b\x8fW'
  87. b'\xb7\xe2R\x00\x82\xa2\xd4\x92\xd2\xa2<.\x00PK\x03\x04\x14\x00\x00\x00\x08\x00%fqV\x8d\x90\x81\x05'
  88. b'1\x00\x00\x006\x00\x00\x00"\x00\x00\x00foopackage-0.99.dist-info/METADATA\xf3M-ILI,I\xd4\rK-*\xce'
  89. b'\xcc\xcf\xb3R0\xd23\xe4\xf2K\xccM\xb5RH\xcb\xcf/HL\xceNLO\xe5\x82\xcb\x1a\xe8YZrq\x01\x00PK\x03\x04'
  90. b'\x14\x00\x00\x00\x08\x00%fqVI\xa2!\xcb\\\x00\x00\x00\\\x00\x00\x00\x1f\x00\x00\x00foopackage-0.99'
  91. b'.dist-info/WHEEL\x0b\xcfHM\xcd\xd1\rK-*\xce\xcc\xcf\xb3R0\xd43\xe0rO\xcdK-J,\xc9/\xb2RHJ\xc9,.\x89/'
  92. b'\x07\xa9Q\xd00\xd031\xd03\xd0\xe4\n\xca\xcf/\xd1\xf5,\xd6\r(-J\xcd\xc9L\xb2R()*M\xe5\nIL\xb7R(\xa84'
  93. b'\xd6\xcd\xcb\xcfK\xd5M\xcc\xab\xe4\xe2\x02\x00PK\x03\x04\x14\x00\x00\x00\x08\x00%fqVI*\x9e\xa7\r\x00'
  94. b'\x00\x00\x0b\x00\x00\x00\'\x00\x00\x00foopackage-0.99.dist-info/top_level.txtK\xcb\xcf/HL\xceNLO\xe5'
  95. b'\x02\x00PK\x03\x04\x14\x00\x00\x00\x08\x00%fqV&\xdc\x9b\x88\xfd\x00\x00\x00}\x01\x00\x00 \x00\x00\x00'
  96. b'foopackage-0.99.dist-info/RECORD}\xcc;\x92\x820\x00\x00\xd0\xde\xb3\x04\xe4#\xbfb\x8b\xac\xb0\x0b,'
  97. b'\xa8\x83\x02#M&\x08\x81\x80\x02c\x02\x82\xa7\xb7rK\xdf\x01\x1e\xe9\xfb\x01_Z\\\x95k\x84hG9B\xe2\xb0'
  98. b'\x00VcE\xd3\xbf\xf4\xe6\xe1\t6a2\xc3\x16N\x06]1Bm\xb7\x17\xc2Z\xef\xaa\xed\xf6\x9c\xdaQ \xd0\xf6\xc6'
  99. b':\xec\x00\xd5\\\x91\xffL\x90D\xcb\x12\x0b\xca\xb8@;\xd2\xafC\xe7\x04mx\x82\xef\xb8\xf2\xc6"\xd9\xdd'
  100. b'\r\x18\xe4\xcd\xef=\xf7\n7\x9eg4?\xa7\x04V*gXI\xff\xcanD\xc1\xf1\xc0\x80\xb6\xf9\x10\xa7\xae\xe3\x04'
  101. b'\xefuh/<;?\xe3\xe3\x06\x9e\x93N/|\xc1Puc\xefgt\xfaQJ3\x82V\x8e\xb2\xef\x86\x12\xd9\x04\x96\xf2a\xe5'
  102. b'\xfd\x80\xae\xe5T^E>\xf3\xf7\x1eW\x122\xe4\x91\xfbi\x1f\xd6\xeem\x99\xd4\xec\x11Ju\x9d\'R\xc83R\x19>'
  103. b'jbO:\xb8\x8b\td\xf9\xc3\x1e9\xdb}d\x03\xb0z\x01PK\x01\x02\x14\x03\x14\x00\x00\x00\x08\x00\x07fqVz|E\t'
  104. b'&\x00\x00\x00&\x00\x00\x00\x16\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x00'
  105. b'foopackage/__init__.pyPK\x01\x02\x14\x03\x14\x00\x00\x00\x08\x00%fqV\x8d\x90\x81\x051\x00\x00\x006\x00'
  106. b'\x00\x00"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81Z\x00\x00\x00foopackage-0.99.dist-info'
  107. b'/METADATAPK\x01\x02\x14\x03\x14\x00\x00\x00\x08\x00%fqVI\xa2!\xcb\\\x00\x00\x00\\\x00\x00\x00\x1f\x00'
  108. b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xcb\x00\x00\x00foopackage-0.99.dist-info/WHEELPK\x01'
  109. b'\x02\x14\x03\x14\x00\x00\x00\x08\x00%fqVI*\x9e\xa7\r\x00\x00\x00\x0b\x00\x00\x00\'\x00\x00\x00\x00\x00'
  110. b'\x00\x00\x00\x00\x00\x00\xa4\x81d\x01\x00\x00foopackage-0.99.dist-info/top_level.txtPK\x01\x02\x14\x03'
  111. b'\x14\x00\x00\x00\x08\x00%fqV&\xdc\x9b\x88\xfd\x00\x00\x00}\x01\x00\x00 \x00\x00\x00\x00\x00\x00\x00'
  112. b'\x00\x00\x00\x00\xb4\x81\xb6\x01\x00\x00foopackage-0.99.dist-info/RECORDPK\x05\x06\x00\x00\x00\x00\x05'
  113. b'\x00\x05\x00\x84\x01\x00\x00\xf1\x02\x00\x00\x00\x00')
  114. return self.dump_package(whl, 'foopackage-0.99-py3-none-any.whl')
  115. def dump_foopackage_dev(self): # type: () -> str
  116. # similar to dump_foopackage, but using dev release version
  117. whl = (b'PK\x03\x04\x14\x00\x00\x00\x08\x00\nl\x03W !Z\xfc%\x00\x00\x00%\x00\x00\x00\x16\x00\x00\x00'
  118. b'foopackage/__init__.py\xcbLS\x88\x8f\xcfK\xccM\x8d\x8fW\xb0\xb5UP\x8f\x8f\xcfM\xcc\xcc\x8b\x8fW\xb7'
  119. b'\xe2R\x00\x82\xa2\xd4\x92\xd2\xa2<\x00PK\x03\x04\x14\x00\x00\x00\x08\x00Jl\x03W\xb4wO\x876\x00\x00'
  120. b'\x00;\x00\x00\x00\'\x00\x00\x00foopackage-0.99.dev0.dist-info/METADATA\xf3M-ILI,I\xd4\rK-*\xce\xcc'
  121. b'\xcf\xb3R0\xd23\xe4\xf2K\xccM\xb5RH\xcb\xcf/HL\xceNLO\xe5\x82\xcb\x1a\xe8YZ\xea\xa5\xa4\x96\x19pq'
  122. b'\x01\x00PK\x03\x04\x14\x00\x00\x00\x08\x00Jl\x03W\xda9\xe8\xb4[\x00\x00\x00\\\x00\x00\x00$\x00\x00'
  123. b'\x00foopackage-0.99.dev0.dist-info/WHEEL\x0b\xcfHM\xcd\xd1\rK-*\xce\xcc\xcf\xb3R0\xd43\xe0rO\xcdK-J,'
  124. b'\xc9/\xb2RHJ\xc9,.\x89/\x07\xa9Q\xd00\xd03\x01Jkr\x05\xe5\xe7\x97\xe8z\x16\xeb\x06\x94\x16\xa5\xe6'
  125. b'd&Y)\x94\x14\x95\xa6r\x85$\xa6[)\x14T\x1a\xeb\xe6\xe5\xe7\xa5\xea&\xe6Urq\x01\x00PK\x03\x04\x14\x00'
  126. b'\x00\x00\x08\x00Jl\x03WI*\x9e\xa7\r\x00\x00\x00\x0b\x00\x00\x00,\x00\x00\x00foopackage-0.99.dev0'
  127. b'.dist-info/top_level.txtK\xcb\xcf/HL\xceNLO\xe5\x02\x00PK\x03\x04\x14\x00\x00\x00\x08\x00Jl\x03W'
  128. b'\x1e\xbaW\xb5\x00\x01\x00\x00\x91\x01\x00\x00%\x00\x00\x00foopackage-0.99.dev0.dist-info/RECORD\x85'
  129. b'\xcd\xbbv\x820\x00\x00\xd0\xddo\t\x18\xe4\x08d\xe8\x80\x88"\xf2\xb0T\xe4\xb1\xe4\x08\x06B\xa1\x064F'
  130. b'\xe8\xd7w\xb2\xab?po\xc5X\x7f.\xdbsM\xe6\x187\xd7\x86c,\xf7\x13\xb8\xd3\xf3b\xa9}d\x98\x90\xc1\n\xbc'
  131. b'[m\xea\x0fI\x848\xda\xb1\x80)\xf5-D\xc7&\xcc\x9d\xe8\xa1\x1f\nj\x97\xbdZ\x02U\x9fU\xff\x98\x04e\x84'
  132. b'\xe4\x0b\x11P\xbe4w.5\xd7\x8a\xcd}\xfbh\xae\xcd\xa3\xf9\xd2]\xb1jQ4$^?\xe6\xd9\xe4C\xb6\xdfdE3\x89'
  133. b'\xb1m\x8dt0\xb2.6s[B\xbb_-\x03K\xf4NO\x1c\xdb\xf6^\xb4\xc9W[\xed+\xf5\xd4\xfd\x06\x0b\x18\x8c^\x05'
  134. b'\t\x9dN!\x85%\xeb.\x92[\xb8Y\x1al\xd9\xcd\xd2>\x01Z\xbc\xa39\xebqG\x04\xe9d>\xf2W\x11\xd7\x10\xeb'
  135. b'\xca\x83\xbb\t\xf3\xa9\xf33\t5\x7f\xfa\x90\xd2\xe2\x04}\x9eW\xb5\xee\xe2\xefx\x07\x0f\xced\x00EyWD'
  136. b'\xb6\x15Fk\x00f\x7fPK\x01\x02\x14\x03\x14\x00\x00\x00\x08\x00\nl\x03W !Z\xfc%\x00\x00\x00%\x00\x00'
  137. b'\x00\x16\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x00foopackage/__init__.py'
  138. b'PK\x01\x02\x14\x03\x14\x00\x00\x00\x08\x00Jl\x03W\xb4wO\x876\x00\x00\x00;\x00\x00\x00\'\x00\x00\x00'
  139. b'\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81Y\x00\x00\x00foopackage-0.99.dev0.dist-info/METADATAPK\x01'
  140. b'\x02\x14\x03\x14\x00\x00\x00\x08\x00Jl\x03W\xda9\xe8\xb4[\x00\x00\x00\\\x00\x00\x00$\x00\x00\x00\x00'
  141. b'\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd4\x00\x00\x00foopackage-0.99.dev0.dist-info/WHEELPK\x01\x02'
  142. b'\x14\x03\x14\x00\x00\x00\x08\x00Jl\x03WI*\x9e\xa7\r\x00\x00\x00\x0b\x00\x00\x00,\x00\x00\x00\x00'
  143. b'\x00\x00\x00\x00\x00\x00\x00\xa4\x81q\x01\x00\x00foopackage-0.99.dev0.dist-info/top_level.txtPK\x01'
  144. b'\x02\x14\x03\x14\x00\x00\x00\x08\x00Jl\x03W\x1e\xbaW\xb5\x00\x01\x00\x00\x91\x01\x00\x00%\x00\x00'
  145. b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb4\x81\xc8\x01\x00\x00foopackage-0.99.dev0.dist-info/RECORDPK'
  146. b'\x05\x06\x00\x00\x00\x00\x05\x00\x05\x00\x98\x01\x00\x00\x0b\x03\x00\x00\x00\x00')
  147. return self.dump_package(whl, 'foopackage-0.99.dev0-py3-none-any.whl')
  148. class TestPythonInstall(BasePythonInstall):
  149. def setUp(self): # type: () -> None
  150. if os.path.isdir(PYTHON_DIR):
  151. shutil.rmtree(PYTHON_DIR)
  152. if os.path.isfile(os.path.join(TOOLS_DIR, 'idf-env.json')):
  153. os.remove(os.path.join(TOOLS_DIR, 'idf-env.json'))
  154. def test_default_arguments(self): # type: () -> None
  155. output = self.run_idf_tools(['check-python-dependencies'])
  156. self.assertNotIn(REQ_SATISFIED, output)
  157. self.assertIn('bin/python doesn\'t exist', output)
  158. output = self.run_idf_tools(['install-python-env'])
  159. self.assertIn(CONSTR, output)
  160. self.assertIn(REQ_CORE, output)
  161. self.assertNotIn(REQ_GDBGUI, output)
  162. output = self.run_idf_tools(['check-python-dependencies'])
  163. self.assertIn(REQ_SATISFIED, output)
  164. def test_opt_argument(self): # type: () -> None
  165. output = self.run_idf_tools(['install-python-env', '--features', 'gdbgui'])
  166. self.assertIn(CONSTR, output)
  167. self.assertIn(REQ_CORE, output)
  168. self.assertIn(REQ_GDBGUI, output)
  169. output = self.run_idf_tools(['install-python-env'])
  170. # The gdbgui should be installed as well because the feature is is stored in the JSON file
  171. self.assertIn(CONSTR, output)
  172. self.assertIn(REQ_CORE, output)
  173. self.assertIn(REQ_GDBGUI, output)
  174. # Argument that begins with '-' can't stand alone to be parsed as value
  175. output = self.run_idf_tools(['install-python-env', '--features=-gdbgui'])
  176. # After removing the gdbgui should not be present
  177. self.assertIn(CONSTR, output)
  178. self.assertIn(REQ_CORE, output)
  179. self.assertNotIn(REQ_GDBGUI, output)
  180. def test_no_constraints(self): # type: () -> None
  181. output = self.run_idf_tools(['install-python-env', '--no-constraints'])
  182. self.assertNotIn(CONSTR, output)
  183. self.assertIn(REQ_CORE, output)
  184. class TestCustomPythonPathInstall(BasePythonInstall):
  185. def setUp(self): # type: () -> None
  186. self.CUSTOM_PYTHON_DIR = tempfile.mkdtemp()
  187. self.environ_old = os.environ.copy()
  188. os.environ['IDF_PYTHON_ENV_PATH'] = self.CUSTOM_PYTHON_DIR
  189. def tearDown(self): # type: () -> None
  190. os.environ.clear()
  191. os.environ.update(self.environ_old)
  192. shutil.rmtree(self.CUSTOM_PYTHON_DIR)
  193. def test_default_arguments(self): # type: () -> None
  194. output = self.run_idf_tools(['check-python-dependencies'])
  195. self.assertIn(f"{self.CUSTOM_PYTHON_DIR}/bin/python doesn't exist", output)
  196. self.assertNotIn(PYTHON_DIR, output)
  197. output = self.run_idf_tools(['install-python-env'])
  198. self.assertIn(self.CUSTOM_PYTHON_DIR, output)
  199. self.assertNotIn(PYTHON_DIR, output)
  200. output = self.run_idf_tools(['check-python-dependencies'])
  201. self.assertIn(self.CUSTOM_PYTHON_DIR, output)
  202. class TestCheckPythonDependencies(BasePythonInstall):
  203. """
  204. The constraint file name is available as the constraint_file attribute. The content of the file is changed by these
  205. tests. The backup_constraint_file is a temporary file with the content of the original constraint file. This is
  206. kept in order to restore the original content of the constraint file. Keeping the original constraint file is
  207. important for consequent tests which should not download a new one especially when the test was run with a custom
  208. constraint file different from the one on dl.espressif.com.
  209. """
  210. constraint_file: str
  211. backup_constraint_file: str
  212. @classmethod
  213. def setUpClass(cls): # type: () -> None
  214. cls.constraint_file = idf_tools.get_constraints(idf_tools.get_idf_version(), online=False)
  215. with tempfile.NamedTemporaryFile() as f:
  216. cls.backup_constraint_file = f.name
  217. shutil.copyfile(cls.constraint_file, cls.backup_constraint_file)
  218. @classmethod
  219. def tearDownClass(cls): # type: () -> None
  220. try:
  221. os.remove(cls.backup_constraint_file)
  222. except OSError:
  223. pass
  224. def setUp(self): # type: () -> None
  225. if os.path.isdir(PYTHON_DIR):
  226. shutil.rmtree(PYTHON_DIR)
  227. def tearDown(self): # type: () -> None
  228. shutil.copyfile(self.backup_constraint_file, self.constraint_file)
  229. def test_check_python_dependencies(self): # type: () -> None
  230. # Prepare artificial constraints file containing packages from
  231. # requirements.core.txt, which are also reported in pip-freeze output
  232. # for virtual env. The constraints file requires package versions higher
  233. # than currently installed in venv, so check_python_dependencies
  234. # should fail for all of them.
  235. self.run_idf_tools(['install-python-env'])
  236. freeze_output = self.run_in_venv(['-m', 'pip', 'freeze', '--all'])
  237. req_fn = os.path.join(IDF_PATH, 'tools', 'requirements', 'requirements.core.txt')
  238. with open(req_fn) as fd:
  239. req_list = [i for i in fd.read().splitlines() if i and i[0] != '#']
  240. # Create constrains list for packages in requirements.core.txt which
  241. # are also present in the freeze list.
  242. con_list = [r.replace('==', '>') for r in freeze_output.splitlines() if r.split('==')[0] in req_list]
  243. # Write the created constraints list into existing constraints file.
  244. # It will not be overwritten by subsequent idf_tools.py run, because
  245. # there is timestamp check.
  246. with open(self.constraint_file, 'w') as fd:
  247. fd.write(os.linesep.join(con_list))
  248. # Test that check_python_dependencies reports that requirements are not satisfied for
  249. # all packages in the artificially created constrains file.
  250. output = self.run_idf_tools(['check-python-dependencies'])
  251. for con in [c.split('>')[0] for c in con_list]:
  252. self.assertIn(con, output)
  253. def test_check_required_packages_only(self): # type: () -> None
  254. # Test for espressif/esp-idf/-/merge_requests/17917
  255. # Install python env with core requirements, plus foopackage.
  256. # Add foopackage to constraints file requiring higher version
  257. # than currently installed. Since foopackage is not a direct
  258. # requirement, the dependency check should ignore it and should
  259. # not fail.
  260. self.run_idf_tools(['install-python-env'])
  261. foo_pkg = self.dump_foopackage()
  262. self.run_in_venv(['-m', 'pip', 'install', foo_pkg])
  263. # append foopackage constraint to the existing constraints file
  264. with open(self.constraint_file, 'a') as fd:
  265. fd.write('foopackage>0.99')
  266. # check-python-dependencies should not complain about dummy_package
  267. output = self.run_idf_tools(['check-python-dependencies'])
  268. self.assertIn(REQ_SATISFIED, output)
  269. def test_dev_version(self): # type: () -> None
  270. # Install python env with core requirements, plus foopackage in dev version.
  271. # Add foopackage to constraints file meeting requirement
  272. # Dependency check should pass as the requirement was met
  273. # Change dependency to require dev version
  274. # Dependency check should pass again
  275. self.run_idf_tools(['install-python-env'])
  276. foo_pkg = self.dump_foopackage_dev()
  277. self.run_in_venv(['-m', 'pip', 'install', foo_pkg])
  278. # append foopackage constraint to the existing constraints file
  279. with open(self.constraint_file, 'r+') as fd:
  280. con_lines = fd.readlines()
  281. fd.write('foopackage~=0.98')
  282. output = self.run_idf_tools(['check-python-dependencies'])
  283. self.assertIn(REQ_SATISFIED, output)
  284. # append foopackage dev version constraint to the existing constraints file
  285. with open(self.constraint_file, 'r+') as fd:
  286. fd.writelines(con_lines + ['foopackage==0.99.dev0'])
  287. output = self.run_idf_tools(['check-python-dependencies'])
  288. self.assertIn(REQ_SATISFIED, output)
  289. if __name__ == '__main__':
  290. unittest.main()