__init__.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. # Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http:#www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import functools
  15. import json
  16. import logging
  17. import os
  18. import re
  19. from collections import defaultdict
  20. from copy import deepcopy
  21. import junit_xml
  22. from tiny_test_fw import TinyFW, Utility
  23. from .DebugUtils import CustomProcess, GDBBackend, OCDBackend # noqa: export DebugUtils for users
  24. from .IDFApp import UT, ComponentUTApp, Example, IDFApp, LoadableElfTestApp, TestApp # noqa: export all Apps for users
  25. from .IDFDUT import (ESP32C3DUT, ESP32C3FPGADUT, ESP32DUT, ESP32QEMUDUT, ESP32S2DUT, # noqa: export DUTs for users
  26. ESP32S3DUT, ESP32S3FPGADUT, ESP8266DUT, IDFDUT)
  27. from .unity_test_parser import TestFormat, TestResults
  28. # pass TARGET_DUT_CLS_DICT to Env.py to avoid circular dependency issue.
  29. TARGET_DUT_CLS_DICT = {
  30. 'ESP32': ESP32DUT,
  31. 'ESP32S2': ESP32S2DUT,
  32. 'ESP32S3': ESP32S3DUT,
  33. 'ESP32C3': ESP32C3DUT,
  34. 'ESP32C3FPGA': ESP32C3FPGADUT,
  35. 'ESP32S3FPGA': ESP32S3FPGADUT,
  36. }
  37. try:
  38. string_type = basestring # type: ignore
  39. except NameError:
  40. string_type = str
  41. def upper_list_or_str(text):
  42. """
  43. Return the uppercase of list of string or string. Return itself for other
  44. data types
  45. :param text: list or string, other instance will be returned immediately
  46. :return: uppercase of list of string
  47. """
  48. if isinstance(text, string_type):
  49. return [text.upper()]
  50. elif isinstance(text, list):
  51. return [item.upper() for item in text]
  52. else:
  53. return text
  54. def local_test_check(decorator_target):
  55. # Try to get the sdkconfig.json to read the IDF_TARGET value.
  56. # If not set, will set to ESP32.
  57. # For CI jobs, this is a fake procedure, the true target and dut will be
  58. # overwritten by the job config YAML file.
  59. idf_target = 'ESP32' # default if sdkconfig not found or not readable
  60. if os.getenv('CI_JOB_ID'): # Only auto-detect target when running locally
  61. return idf_target
  62. decorator_target = upper_list_or_str(decorator_target)
  63. expected_json_path = os.path.join('build', 'config', 'sdkconfig.json')
  64. if os.path.exists(expected_json_path):
  65. sdkconfig = json.load(open(expected_json_path))
  66. try:
  67. idf_target = sdkconfig['IDF_TARGET'].upper()
  68. except KeyError:
  69. logging.debug('IDF_TARGET not in {}. IDF_TARGET set to esp32'.format(os.path.abspath(expected_json_path)))
  70. else:
  71. logging.debug('IDF_TARGET: {}'.format(idf_target))
  72. else:
  73. logging.debug('{} not found. IDF_TARGET set to esp32'.format(os.path.abspath(expected_json_path)))
  74. if isinstance(decorator_target, list):
  75. if idf_target not in decorator_target:
  76. fpga_target = ''.join((idf_target, 'FPGA'))
  77. if fpga_target not in decorator_target:
  78. raise ValueError('IDF_TARGET set to {}, not in decorator target value'.format(idf_target))
  79. else:
  80. idf_target = fpga_target
  81. else:
  82. if idf_target != decorator_target:
  83. raise ValueError('IDF_TARGET set to {}, not equal to decorator target value'.format(idf_target))
  84. return idf_target
  85. def get_dut_class(target, dut_class_dict, erase_nvs=None):
  86. if target not in dut_class_dict:
  87. raise Exception('target can only be {%s} (case insensitive)' % ', '.join(dut_class_dict.keys()))
  88. dut = dut_class_dict[target.upper()]
  89. if erase_nvs:
  90. dut.ERASE_NVS = 'erase_nvs'
  91. return dut
  92. def ci_target_check(func):
  93. @functools.wraps(func)
  94. def wrapper(**kwargs):
  95. target = upper_list_or_str(kwargs.get('target', []))
  96. ci_target = upper_list_or_str(kwargs.get('ci_target', []))
  97. if not set(ci_target).issubset(set(target)):
  98. raise ValueError('ci_target must be a subset of target')
  99. return func(**kwargs)
  100. return wrapper
  101. def test_func_generator(func, app, target, ci_target, module, execution_time, level, erase_nvs, **kwargs):
  102. target = upper_list_or_str(target)
  103. test_target = local_test_check(target)
  104. if 'additional_duts' in kwargs:
  105. dut_classes = deepcopy(TARGET_DUT_CLS_DICT)
  106. dut_classes.update(kwargs['additional_duts'])
  107. else:
  108. dut_classes = TARGET_DUT_CLS_DICT
  109. dut = get_dut_class(test_target, dut_classes, erase_nvs)
  110. original_method = TinyFW.test_method(
  111. app=app, dut=dut, target=target, ci_target=upper_list_or_str(ci_target),
  112. module=module, execution_time=execution_time, level=level, erase_nvs=erase_nvs,
  113. dut_dict=dut_classes, **kwargs
  114. )
  115. test_func = original_method(func)
  116. return test_func
  117. @ci_target_check
  118. def idf_example_test(app=Example, target='ESP32', ci_target=None, module='examples', execution_time=1,
  119. level='example', erase_nvs=True, config_name=None, **kwargs):
  120. """
  121. decorator for testing idf examples (with default values for some keyword args).
  122. :param app: test application class
  123. :param target: target supported, string or list
  124. :param ci_target: target auto run in CI, if None than all target will be tested, None, string or list
  125. :param module: module, string
  126. :param execution_time: execution time in minutes, int
  127. :param level: test level, could be used to filter test cases, string
  128. :param erase_nvs: if need to erase_nvs in DUT.start_app()
  129. :param config_name: if specified, name of the app configuration
  130. :param kwargs: other keyword args
  131. :return: test method
  132. """
  133. def test(func):
  134. return test_func_generator(func, app, target, ci_target, module, execution_time, level, erase_nvs, **kwargs)
  135. return test
  136. @ci_target_check
  137. def idf_unit_test(app=UT, target='ESP32', ci_target=None, module='unit-test', execution_time=1,
  138. level='unit', erase_nvs=True, **kwargs):
  139. """
  140. decorator for testing idf unit tests (with default values for some keyword args).
  141. :param app: test application class
  142. :param target: target supported, string or list
  143. :param ci_target: target auto run in CI, if None than all target will be tested, None, string or list
  144. :param module: module, string
  145. :param execution_time: execution time in minutes, int
  146. :param level: test level, could be used to filter test cases, string
  147. :param erase_nvs: if need to erase_nvs in DUT.start_app()
  148. :param kwargs: other keyword args
  149. :return: test method
  150. """
  151. def test(func):
  152. return test_func_generator(func, app, target, ci_target, module, execution_time, level, erase_nvs, **kwargs)
  153. return test
  154. @ci_target_check
  155. def idf_custom_test(app=TestApp, target='ESP32', ci_target=None, module='misc', execution_time=1,
  156. level='integration', erase_nvs=True, config_name=None, **kwargs):
  157. """
  158. decorator for idf custom tests (with default values for some keyword args).
  159. :param app: test application class
  160. :param target: target supported, string or list
  161. :param ci_target: target auto run in CI, if None than all target will be tested, None, string or list
  162. :param module: module, string
  163. :param execution_time: execution time in minutes, int
  164. :param level: test level, could be used to filter test cases, string
  165. :param erase_nvs: if need to erase_nvs in DUT.start_app()
  166. :param config_name: if specified, name of the app configuration
  167. :param kwargs: other keyword args
  168. :return: test method
  169. """
  170. def test(func):
  171. return test_func_generator(func, app, target, ci_target, module, execution_time, level, erase_nvs, **kwargs)
  172. return test
  173. @ci_target_check
  174. def idf_component_unit_test(app=ComponentUTApp, target='ESP32', ci_target=None, module='misc', execution_time=1,
  175. level='integration', erase_nvs=True, config_name=None, **kwargs):
  176. """
  177. decorator for idf custom tests (with default values for some keyword args).
  178. :param app: test application class
  179. :param target: target supported, string or list
  180. :param ci_target: target auto run in CI, if None than all target will be tested, None, string or list
  181. :param module: module, string
  182. :param execution_time: execution time in minutes, int
  183. :param level: test level, could be used to filter test cases, string
  184. :param erase_nvs: if need to erase_nvs in DUT.start_app()
  185. :param config_name: if specified, name of the app configuration
  186. :param kwargs: other keyword args
  187. :return: test method
  188. """
  189. def test(func):
  190. return test_func_generator(func, app, target, ci_target, module, execution_time, level, erase_nvs, **kwargs)
  191. return test
  192. class ComponentUTResult:
  193. """
  194. Function Class, parse component unit test results
  195. """
  196. results_list = defaultdict(list) # type: dict[str, list[junit_xml.TestSuite]]
  197. """
  198. For origin unity test cases with macro "TEST", please set "test_format" to "TestFormat.UNITY_FIXTURE_VERBOSE".
  199. For IDF unity test cases with macro "TEST CASE", please set "test_format" to "TestFormat.UNITY_BASIC".
  200. """
  201. @staticmethod
  202. def parse_result(stdout, test_format=TestFormat.UNITY_FIXTURE_VERBOSE):
  203. try:
  204. results = TestResults(stdout, test_format)
  205. except (ValueError, TypeError) as e:
  206. raise ValueError('Error occurs when parsing the component unit test stdout to JUnit report: ' + str(e))
  207. group_name = results.tests()[0].group()
  208. ComponentUTResult.results_list[group_name].append(results.to_junit())
  209. with open(os.path.join(os.getenv('LOG_PATH', ''), '{}_XUNIT_RESULT.xml'.format(group_name)), 'w') as fw:
  210. junit_xml.to_xml_report_file(fw, ComponentUTResult.results_list[group_name])
  211. if results.num_failed():
  212. # raise exception if any case fails
  213. err_msg = 'Failed Cases:\n'
  214. for test_case in results.test_iter():
  215. if test_case.result() == 'FAIL':
  216. err_msg += '\t{}: {}'.format(test_case.name(), test_case.message())
  217. raise AssertionError(err_msg)
  218. def log_performance(item, value):
  219. """
  220. do print performance with pre-defined format to console
  221. :param item: performance item name
  222. :param value: performance value
  223. """
  224. performance_msg = '[Performance][{}]: {}'.format(item, value)
  225. Utility.console_log(performance_msg, 'orange')
  226. # update to junit test report
  227. current_junit_case = TinyFW.JunitReport.get_current_test_case()
  228. current_junit_case.stdout += performance_msg + '\r\n'
  229. def check_performance(item, value, target):
  230. """
  231. check if idf performance meet pass standard
  232. :param item: performance item name
  233. :param value: performance item value
  234. :param target: target chip
  235. :raise: AssertionError: if check fails
  236. """
  237. def _find_perf_item(path):
  238. with open(path, 'r') as f:
  239. data = f.read()
  240. match = re.search(r'#define\s+IDF_PERFORMANCE_(MIN|MAX)_{}\s+([\d.]+)'.format(item.upper()), data)
  241. return match.group(1), float(match.group(2))
  242. def _check_perf(op, standard_value):
  243. if op == 'MAX':
  244. ret = value <= standard_value
  245. else:
  246. ret = value >= standard_value
  247. if not ret:
  248. raise AssertionError("[Performance] {} value is {}, doesn't meet pass standard {}"
  249. .format(item, value, standard_value))
  250. path_prefix = os.path.join(IDFApp.get_sdk_path(), 'components', 'idf_test', 'include')
  251. performance_files = (os.path.join(path_prefix, target, 'idf_performance_target.h'),
  252. os.path.join(path_prefix, 'idf_performance.h'))
  253. for performance_file in performance_files:
  254. try:
  255. op, standard = _find_perf_item(performance_file)
  256. except (IOError, AttributeError):
  257. # performance file doesn't exist or match is not found in it
  258. continue
  259. _check_perf(op, standard)
  260. # if no exception was thrown then the performance is met and no need to continue
  261. break
  262. else:
  263. raise AssertionError('Failed to get performance standard for {}'.format(item))
  264. MINIMUM_FREE_HEAP_SIZE_RE = re.compile(r'Minimum free heap size: (\d+) bytes')
  265. def print_heap_size(app_name, config_name, target, minimum_free_heap_size):
  266. """
  267. Do not change the print output in case you really need to.
  268. The result is parsed by ci-dashboard project
  269. """
  270. print('------ heap size info ------\n'
  271. '[app_name] {}\n'
  272. '[config_name] {}\n'
  273. '[target] {}\n'
  274. '[minimum_free_heap_size] {} Bytes\n'
  275. '------ heap size end ------'.format(app_name,
  276. '' if not config_name else config_name,
  277. target,
  278. minimum_free_heap_size))