test_idf_tools_python_env.py 20 KB

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