CIAssignUnitTest.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. """
  2. Command line tool to assign unit tests to CI test jobs.
  3. """
  4. import re
  5. import os
  6. import sys
  7. import argparse
  8. import yaml
  9. try:
  10. from yaml import CLoader as Loader
  11. except ImportError:
  12. from yaml import Loader as Loader
  13. try:
  14. from Utility import CIAssignTest
  15. except ImportError:
  16. test_fw_path = os.getenv("TEST_FW_PATH")
  17. if test_fw_path:
  18. sys.path.insert(0, test_fw_path)
  19. from Utility import CIAssignTest
  20. class Group(CIAssignTest.Group):
  21. SORT_KEYS = ["config", "test environment", "multi_device", "multi_stage", "tags", "chip_target"]
  22. MAX_CASE = 30
  23. ATTR_CONVERT_TABLE = {
  24. "execution_time": "execution time"
  25. }
  26. CI_JOB_MATCH_KEYS = ["test environment"]
  27. def __init__(self, case):
  28. super(Group, self).__init__(case)
  29. for tag in self._get_case_attr(case, "tags"):
  30. self.ci_job_match_keys.add(tag)
  31. @staticmethod
  32. def _get_case_attr(case, attr):
  33. if attr in Group.ATTR_CONVERT_TABLE:
  34. attr = Group.ATTR_CONVERT_TABLE[attr]
  35. return case[attr]
  36. def _create_extra_data(self, test_function):
  37. """
  38. For unit test case, we need to copy some attributes of test cases into config file.
  39. So unit test function knows how to run the case.
  40. """
  41. case_data = []
  42. for case in self.case_list:
  43. one_case_data = {
  44. "config": self._get_case_attr(case, "config"),
  45. "name": self._get_case_attr(case, "summary"),
  46. "reset": self._get_case_attr(case, "reset"),
  47. "timeout": self._get_case_attr(case, "timeout"),
  48. }
  49. if test_function in ["run_multiple_devices_cases", "run_multiple_stage_cases"]:
  50. try:
  51. one_case_data["child case num"] = self._get_case_attr(case, "child case num")
  52. except KeyError as e:
  53. print("multiple devices/stages cases must contains at least two test functions")
  54. print("case name: {}".format(one_case_data["name"]))
  55. raise e
  56. case_data.append(one_case_data)
  57. return case_data
  58. def _map_test_function(self):
  59. """
  60. determine which test function to use according to current test case
  61. :return: test function name to use
  62. """
  63. if self.filters["multi_device"] == "Yes":
  64. test_function = "run_multiple_devices_cases"
  65. elif self.filters["multi_stage"] == "Yes":
  66. test_function = "run_multiple_stage_cases"
  67. else:
  68. test_function = "run_unit_test_cases"
  69. return test_function
  70. def output(self):
  71. """
  72. output data for job configs
  73. :return: {"Filter": case filter, "CaseConfig": list of case configs for cases in this group}
  74. """
  75. test_function = self._map_test_function()
  76. output_data = {
  77. # we don't need filter for test function, as UT uses a few test functions for all cases
  78. "CaseConfig": [
  79. {
  80. "name": test_function,
  81. "extra_data": self._create_extra_data(test_function),
  82. }
  83. ],
  84. }
  85. target = self._get_case_attr(self.case_list[0], "chip_target")
  86. if target is not None:
  87. target_dut = {
  88. "esp32": "ESP32DUT",
  89. "esp32s2beta": "ESP32S2DUT",
  90. "esp8266": "ESP8266DUT",
  91. }[target]
  92. output_data.update({
  93. "Filter": {
  94. "overwrite": {
  95. "dut": {
  96. "path": "IDF/IDFDUT.py",
  97. "class": target_dut,
  98. }
  99. }
  100. }
  101. })
  102. return output_data
  103. class UnitTestAssignTest(CIAssignTest.AssignTest):
  104. CI_TEST_JOB_PATTERN = re.compile(r"^UT_.+")
  105. def __init__(self, test_case_path, ci_config_file):
  106. CIAssignTest.AssignTest.__init__(self, test_case_path, ci_config_file, case_group=Group)
  107. def _search_cases(self, test_case_path, case_filter=None):
  108. """
  109. For unit test case, we don't search for test functions.
  110. The unit test cases is stored in a yaml file which is created in job build-idf-test.
  111. """
  112. try:
  113. with open(test_case_path, "r") as f:
  114. raw_data = yaml.load(f, Loader=Loader)
  115. test_cases = raw_data["test cases"]
  116. except IOError:
  117. print("Test case path is invalid. Should only happen when use @bot to skip unit test.")
  118. test_cases = []
  119. # filter keys are lower case. Do map lower case keys with original keys.
  120. try:
  121. key_mapping = {x.lower(): x for x in test_cases[0].keys()}
  122. except IndexError:
  123. key_mapping = dict()
  124. if case_filter:
  125. for key in case_filter:
  126. filtered_cases = []
  127. for case in test_cases:
  128. try:
  129. mapped_key = key_mapping[key]
  130. # bot converts string to lower case
  131. if isinstance(case[mapped_key], str):
  132. _value = case[mapped_key].lower()
  133. else:
  134. _value = case[mapped_key]
  135. if _value in case_filter[key]:
  136. filtered_cases.append(case)
  137. except KeyError:
  138. # case don't have this key, regard as filter success
  139. filtered_cases.append(case)
  140. test_cases = filtered_cases
  141. return test_cases
  142. if __name__ == '__main__':
  143. parser = argparse.ArgumentParser()
  144. parser.add_argument("test_case",
  145. help="test case folder or file")
  146. parser.add_argument("ci_config_file",
  147. help="gitlab ci config file")
  148. parser.add_argument("output_path",
  149. help="output path of config files")
  150. args = parser.parse_args()
  151. assign_test = UnitTestAssignTest(args.test_case, args.ci_config_file)
  152. assign_test.assign_cases()
  153. assign_test.output_configs(args.output_path)