TinyFW.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  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. """ Interface for test cases. """
  15. import os
  16. import time
  17. import traceback
  18. import functools
  19. import junit_xml
  20. import Env
  21. import DUT
  22. import App
  23. import Utility
  24. class DefaultEnvConfig(object):
  25. """
  26. default test configs. There're 3 places to set configs, priority is (high -> low):
  27. 1. overwrite set by caller of test method
  28. 2. values set by test_method decorator
  29. 3. default env config get from this class
  30. """
  31. DEFAULT_CONFIG = {
  32. "app": App.BaseApp,
  33. "dut": DUT.BaseDUT,
  34. "env_tag": "default",
  35. "env_config_file": None,
  36. "test_suite_name": None,
  37. }
  38. @classmethod
  39. def set_default_config(cls, **kwargs):
  40. """
  41. :param kwargs: configs need to be updated
  42. :return: None
  43. """
  44. cls.DEFAULT_CONFIG.update(kwargs)
  45. @classmethod
  46. def get_default_config(cls):
  47. """
  48. :return: current default config
  49. """
  50. return cls.DEFAULT_CONFIG.copy()
  51. set_default_config = DefaultEnvConfig.set_default_config
  52. get_default_config = DefaultEnvConfig.get_default_config
  53. MANDATORY_INFO = {
  54. "execution_time": 1,
  55. "env_tag": "default",
  56. "category": "function",
  57. "ignore": False,
  58. }
  59. class JunitReport(object):
  60. # wrapper for junit test report
  61. # TODO: Don't support by multi-thread (although not likely to be used this way).
  62. JUNIT_FILE_NAME = "XUNIT_RESULT.xml"
  63. JUNIT_DEFAULT_TEST_SUITE = "test-suite"
  64. JUNIT_TEST_SUITE = junit_xml.TestSuite(JUNIT_DEFAULT_TEST_SUITE)
  65. JUNIT_CURRENT_TEST_CASE = None
  66. _TEST_CASE_CREATED_TS = 0
  67. @classmethod
  68. def output_report(cls, junit_file_path):
  69. """ Output current test result to file. """
  70. with open(os.path.join(junit_file_path, cls.JUNIT_FILE_NAME), "w") as f:
  71. cls.JUNIT_TEST_SUITE.to_file(f, [cls.JUNIT_TEST_SUITE], prettyprint=False)
  72. @classmethod
  73. def get_current_test_case(cls):
  74. """
  75. By default, the test framework will handle junit test report automatically.
  76. While some test case might want to update some info to test report.
  77. They can use this method to get current test case created by test framework.
  78. :return: current junit test case instance created by ``JunitTestReport.create_test_case``
  79. """
  80. return cls.JUNIT_CURRENT_TEST_CASE
  81. @classmethod
  82. def test_case_finish(cls, test_case):
  83. """
  84. Append the test case to test suite so it can be output to file.
  85. Execution time will be automatically updated (compared to ``create_test_case``).
  86. """
  87. test_case.elapsed_sec = time.time() - cls._TEST_CASE_CREATED_TS
  88. cls.JUNIT_TEST_SUITE.test_cases.append(test_case)
  89. @classmethod
  90. def create_test_case(cls, name):
  91. """
  92. Extend ``junit_xml.TestCase`` with:
  93. 1. save create test case so it can be get by ``get_current_test_case``
  94. 2. log create timestamp, so ``elapsed_sec`` can be auto updated in ``test_case_finish``.
  95. :param name: test case name
  96. :return: instance of ``junit_xml.TestCase``
  97. """
  98. # set stdout to empty string, so we can always append string to stdout.
  99. # It won't affect output logic. If stdout is empty, it won't be put to report.
  100. test_case = junit_xml.TestCase(name, stdout="")
  101. cls.JUNIT_CURRENT_TEST_CASE = test_case
  102. cls._TEST_CASE_CREATED_TS = time.time()
  103. return test_case
  104. def test_method(**kwargs):
  105. """
  106. decorator for test case function.
  107. The following keyword arguments are pre-defined.
  108. Any other keyword arguments will be regarded as filter for the test case,
  109. able to access them by ``case_info`` attribute of test method.
  110. :keyword app: class for test app. see :doc:`App <App>` for details
  111. :keyword dut: class for current dut. see :doc:`DUT <DUT>` for details
  112. :keyword env_tag: name for test environment, used to select configs from config file
  113. :keyword env_config_file: test env config file. usually will not set this keyword when define case
  114. :keyword test_suite_name: test suite name, used for generating log folder name and adding xunit format test result.
  115. usually will not set this keyword when define case
  116. :keyword junit_report_by_case: By default the test fw will handle junit report generation.
  117. In some cases, one test function might test many test cases.
  118. If this flag is set, test case can update junit report by its own.
  119. """
  120. def test(test_func):
  121. case_info = MANDATORY_INFO.copy()
  122. case_info["name"] = case_info["ID"] = test_func.__name__
  123. case_info["junit_report_by_case"] = False
  124. case_info.update(kwargs)
  125. @functools.wraps(test_func)
  126. def handle_test(extra_data=None, **overwrite):
  127. """
  128. create env, run test and record test results
  129. :param extra_data: extra data that runner or main passed to test case
  130. :param overwrite: args that runner or main want to overwrite
  131. :return: None
  132. """
  133. # create env instance
  134. env_config = DefaultEnvConfig.get_default_config()
  135. for key in kwargs:
  136. if key in env_config:
  137. env_config[key] = kwargs[key]
  138. env_config.update(overwrite)
  139. env_inst = Env.Env(**env_config)
  140. # prepare for xunit test results
  141. junit_file_path = env_inst.app_cls.get_log_folder(env_config["test_suite_name"])
  142. junit_test_case = JunitReport.create_test_case(case_info["name"])
  143. result = False
  144. try:
  145. Utility.console_log("starting running test: " + test_func.__name__, color="green")
  146. # execute test function
  147. test_func(env_inst, extra_data)
  148. # if finish without exception, test result is True
  149. result = True
  150. except Exception as e:
  151. # handle all the exceptions here
  152. traceback.print_exc()
  153. # log failure
  154. junit_test_case.add_failure_info(str(e) + ":\r\n" + traceback.format_exc())
  155. finally:
  156. if not case_info["junit_report_by_case"]:
  157. JunitReport.test_case_finish(junit_test_case)
  158. # do close all DUTs, if result is False then print DUT debug info
  159. env_inst.close(dut_debug=(not result))
  160. # end case and output result
  161. JunitReport.output_report(junit_file_path)
  162. if result:
  163. Utility.console_log("Test Succeed: " + test_func.__name__, color="green")
  164. else:
  165. Utility.console_log(("Test Fail: " + test_func.__name__), color="red")
  166. return result
  167. handle_test.case_info = case_info
  168. handle_test.test_method = True
  169. return handle_test
  170. return test