test_idf_py.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. #!/usr/bin/env python
  2. #
  3. # SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
  4. # SPDX-License-Identifier: Apache-2.0
  5. import json
  6. import os
  7. import subprocess
  8. import sys
  9. from unittest import TestCase, main, mock
  10. import elftools.common.utils as ecu
  11. import jsonschema
  12. from elftools.elf.elffile import ELFFile
  13. try:
  14. from StringIO import StringIO
  15. except ImportError:
  16. from io import StringIO
  17. try:
  18. import idf
  19. except ImportError:
  20. sys.path.append('..')
  21. import idf
  22. current_dir = os.path.dirname(os.path.realpath(__file__))
  23. idf_py_path = os.path.join(current_dir, '..', 'idf.py')
  24. extension_path = os.path.join(current_dir, 'test_idf_extensions', 'test_ext')
  25. py_actions_path = os.path.join(current_dir, '..', 'idf_py_actions')
  26. link_path = os.path.join(py_actions_path, 'test_ext')
  27. class TestWithoutExtensions(TestCase):
  28. @classmethod
  29. def setUpClass(cls):
  30. # Disable the component manager and extra extensions for these tests
  31. cls.env_patcher = mock.patch.dict(os.environ, {
  32. 'IDF_COMPONENT_MANAGER': '0',
  33. 'IDF_EXTRA_ACTIONS_PATH': '',
  34. })
  35. cls.env_patcher.start()
  36. super().setUpClass()
  37. class TestExtensions(TestWithoutExtensions):
  38. def test_extension_loading(self):
  39. try:
  40. os.symlink(extension_path, link_path)
  41. os.environ['IDF_EXTRA_ACTIONS_PATH'] = os.path.join(current_dir, 'extra_path')
  42. output = subprocess.check_output([sys.executable, idf_py_path, '--help'],
  43. env=os.environ).decode('utf-8', 'ignore')
  44. self.assertIn('--test-extension-option', output)
  45. self.assertIn('test_subcommand', output)
  46. self.assertIn('--some-extension-option', output)
  47. self.assertIn('extra_subcommand', output)
  48. finally:
  49. os.remove(link_path)
  50. def test_extension_execution(self):
  51. try:
  52. os.symlink(extension_path, link_path)
  53. os.environ['IDF_EXTRA_ACTIONS_PATH'] = ';'.join([os.path.join(current_dir, 'extra_path')])
  54. output = subprocess.check_output(
  55. [sys.executable, idf_py_path, '--some-extension-option=awesome', 'test_subcommand', 'extra_subcommand'],
  56. env=os.environ).decode('utf-8', 'ignore')
  57. self.assertIn('!!! From some global callback: awesome', output)
  58. self.assertIn('!!! From some subcommand', output)
  59. self.assertIn('!!! From test global callback: test', output)
  60. self.assertIn('!!! From some subcommand', output)
  61. finally:
  62. os.remove(link_path)
  63. def test_hidden_commands(self):
  64. try:
  65. os.symlink(extension_path, link_path)
  66. os.environ['IDF_EXTRA_ACTIONS_PATH'] = ';'.join([os.path.join(current_dir, 'extra_path')])
  67. output = subprocess.check_output([sys.executable, idf_py_path, '--help'],
  68. env=os.environ).decode('utf-8', 'ignore')
  69. self.assertIn('test_subcommand', output)
  70. self.assertNotIn('hidden_one', output)
  71. finally:
  72. os.remove(link_path)
  73. class TestDependencyManagement(TestWithoutExtensions):
  74. def test_dependencies(self):
  75. result = idf.init_cli()(
  76. args=['--dry-run', 'flash'],
  77. standalone_mode=False,
  78. )
  79. self.assertEqual(['flash'], list(result.keys()))
  80. def test_order_only_dependencies(self):
  81. result = idf.init_cli()(
  82. args=['--dry-run', 'build', 'fullclean', 'all'],
  83. standalone_mode=False,
  84. )
  85. self.assertEqual(['fullclean', 'all'], list(result.keys()))
  86. def test_repeated_dependencies(self):
  87. result = idf.init_cli()(
  88. args=['--dry-run', 'fullclean', 'app', 'fullclean', 'fullclean'],
  89. standalone_mode=False,
  90. )
  91. self.assertEqual(['fullclean', 'app'], list(result.keys()))
  92. def test_complex_case(self):
  93. result = idf.init_cli()(
  94. args=['--dry-run', 'clean', 'monitor', 'clean', 'fullclean', 'flash'],
  95. standalone_mode=False,
  96. )
  97. self.assertEqual(['fullclean', 'clean', 'flash', 'monitor'], list(result.keys()))
  98. def test_dupplicated_commands_warning(self):
  99. capturedOutput = StringIO()
  100. sys.stderr = capturedOutput
  101. idf.init_cli()(
  102. args=['--dry-run', 'clean', 'monitor', 'build', 'clean', 'fullclean', 'all'],
  103. standalone_mode=False,
  104. )
  105. sys.stderr = sys.__stderr__
  106. self.assertIn(
  107. 'WARNING: Commands "all", "clean" are found in the list of commands more than once.',
  108. capturedOutput.getvalue())
  109. sys.stderr = capturedOutput
  110. idf.init_cli()(
  111. args=['--dry-run', 'clean', 'clean'],
  112. standalone_mode=False,
  113. )
  114. sys.stderr = sys.__stderr__
  115. self.assertIn(
  116. 'WARNING: Command "clean" is found in the list of commands more than once.', capturedOutput.getvalue())
  117. class TestVerboseFlag(TestWithoutExtensions):
  118. def test_verbose_messages(self):
  119. output = subprocess.check_output(
  120. [
  121. sys.executable,
  122. idf_py_path,
  123. '-C%s' % current_dir,
  124. '-v',
  125. 'test-verbose',
  126. ], env=os.environ).decode('utf-8', 'ignore')
  127. self.assertIn('Verbose mode on', output)
  128. def test_verbose_messages_not_shown_by_default(self):
  129. output = subprocess.check_output(
  130. [
  131. sys.executable,
  132. idf_py_path,
  133. '-C%s' % current_dir,
  134. 'test-verbose',
  135. ], env=os.environ).decode('utf-8', 'ignore')
  136. self.assertIn('Output from test-verbose', output)
  137. self.assertNotIn('Verbose mode on', output)
  138. class TestGlobalAndSubcommandParameters(TestWithoutExtensions):
  139. def test_set_twice_same_value(self):
  140. """Can set -D twice: globally and for subcommand if values are the same"""
  141. idf.init_cli()(
  142. args=['--dry-run', '-DAAA=BBB', '-DCCC=EEE', 'build', '-DAAA=BBB', '-DCCC=EEE'],
  143. standalone_mode=False,
  144. )
  145. def test_set_twice_different_values(self):
  146. """Cannot set -D twice: for command and subcommand of idf.py (with different values)"""
  147. with self.assertRaises(idf.FatalError):
  148. idf.init_cli()(
  149. args=['--dry-run', '-DAAA=BBB', 'build', '-DAAA=EEE', '-DCCC=EEE'],
  150. standalone_mode=False,
  151. )
  152. class TestDeprecations(TestWithoutExtensions):
  153. def test_exit_with_error_for_subcommand(self):
  154. try:
  155. subprocess.check_output(
  156. [sys.executable, idf_py_path, '-C%s' % current_dir, 'test-2'], env=os.environ, stderr=subprocess.STDOUT)
  157. except subprocess.CalledProcessError as e:
  158. self.assertIn('Error: Command "test-2" is deprecated and was removed.', e.output.decode('utf-8', 'ignore'))
  159. def test_exit_with_error_for_option(self):
  160. try:
  161. subprocess.check_output(
  162. [sys.executable, idf_py_path, '-C%s' % current_dir, '--test-5=asdf'],
  163. env=os.environ,
  164. stderr=subprocess.STDOUT)
  165. except subprocess.CalledProcessError as e:
  166. self.assertIn(
  167. 'Error: Option "test_5" is deprecated since v2.0 and was removed in v3.0.',
  168. e.output.decode('utf-8', 'ignore'))
  169. def test_deprecation_messages(self):
  170. output = subprocess.check_output(
  171. [
  172. sys.executable,
  173. idf_py_path,
  174. '-C%s' % current_dir,
  175. '--test-0=a',
  176. '--test-1=b',
  177. '--test-2=c',
  178. '--test-3=d',
  179. 'test-0',
  180. '--test-sub-0=sa',
  181. '--test-sub-1=sb',
  182. 'ta',
  183. 'test-1',
  184. ],
  185. env=os.environ,
  186. stderr=subprocess.STDOUT).decode('utf-8', 'ignore')
  187. self.assertIn('Warning: Option "test_sub_1" is deprecated and will be removed in future versions.', output)
  188. self.assertIn(
  189. 'Warning: Command "test-1" is deprecated and will be removed in future versions. '
  190. 'Please use alternative command.', output)
  191. self.assertIn('Warning: Option "test_1" is deprecated and will be removed in future versions.', output)
  192. self.assertIn(
  193. 'Warning: Option "test_2" is deprecated and will be removed in future versions. '
  194. 'Please update your parameters.', output)
  195. self.assertIn('Warning: Option "test_3" is deprecated and will be removed in future versions.', output)
  196. self.assertNotIn('"test-0" is deprecated', output)
  197. self.assertNotIn('"test_0" is deprecated', output)
  198. class TestHelpOutput(TestWithoutExtensions):
  199. def test_output(self):
  200. def action_test(commands, schema):
  201. output_file = 'idf_py_help_output.json'
  202. with open(output_file, 'w') as outfile:
  203. subprocess.run(commands, env=os.environ, stdout=outfile)
  204. with open(output_file, 'r') as outfile:
  205. help_obj = json.load(outfile)
  206. self.assertIsNone(jsonschema.validate(help_obj, schema))
  207. with open(os.path.join(current_dir, 'idf_py_help_schema.json'), 'r') as schema_file:
  208. schema_json = json.load(schema_file)
  209. action_test(['idf.py', 'help', '--json'], schema_json)
  210. action_test(['idf.py', 'help', '--json', '--add-options'], schema_json)
  211. class TestROMs(TestWithoutExtensions):
  212. def get_string_from_elf_by_addr(self, filename: str, address: int) -> str:
  213. result = ''
  214. with open(filename, 'rb') as stream:
  215. elf_file = ELFFile(stream)
  216. ro = elf_file.get_section_by_name('.rodata')
  217. ro_addr_delta = ro['sh_addr'] - ro['sh_offset']
  218. cstring = ecu.parse_cstring_from_stream(ro.stream, address - ro_addr_delta)
  219. if cstring:
  220. result = str(cstring.decode('utf-8'))
  221. return result
  222. def test_roms_validate_json(self):
  223. with open(os.path.join(py_actions_path, 'roms.json'), 'r') as f:
  224. roms_json = json.load(f)
  225. with open(os.path.join(py_actions_path, 'roms_schema.json'), 'r') as f:
  226. schema_json = json.load(f)
  227. jsonschema.validate(roms_json, schema_json)
  228. def test_roms_check_supported_chips(self):
  229. from idf_py_actions.constants import SUPPORTED_TARGETS
  230. with open(os.path.join(py_actions_path, 'roms.json'), 'r') as f:
  231. roms_json = json.load(f)
  232. for chip in SUPPORTED_TARGETS:
  233. self.assertTrue(chip in roms_json, msg=f'Have no ROM data for chip {chip}')
  234. def test_roms_validate_build_date(self):
  235. sys.path.append(py_actions_path)
  236. rom_elfs_dir = os.getenv('ESP_ROM_ELF_DIR')
  237. with open(os.path.join(py_actions_path, 'roms.json'), 'r') as f:
  238. roms_json = json.load(f)
  239. for chip in roms_json:
  240. for k in roms_json[chip]:
  241. rom_file = os.path.join(rom_elfs_dir, f'{chip}_rev{k["rev"]}_rom.elf')
  242. build_date_str = self.get_string_from_elf_by_addr(rom_file, int(k['build_date_str_addr'], base=16))
  243. self.assertTrue(len(build_date_str) == 11)
  244. self.assertTrue(build_date_str == k['build_date_str'])
  245. if __name__ == '__main__':
  246. main()