SearchCases.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. # SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: Apache-2.0
  3. """ search test cases from a given file or path """
  4. import copy
  5. import fnmatch
  6. import os
  7. import types
  8. from typing import List
  9. from . import load_source
  10. class Search:
  11. """
  12. This class is used as a class singleton. all the member functions are `classmethod`
  13. """
  14. TEST_CASE_FILE_PATTERN = '*_test.py'
  15. SUPPORT_REPLICATE_CASES_KEY = ['target']
  16. # this attribute would be modified while running
  17. missing_import_warnings: List[str] = []
  18. @classmethod
  19. def _search_cases_from_file(cls, file_name):
  20. """ get test cases from test case .py file """
  21. print('Try to get cases from: ' + file_name)
  22. test_functions = []
  23. try:
  24. # search case no need to run the functions
  25. # mock missing modules would help us get the test case function objects
  26. mod = load_source(file_name, mock_missing=True)
  27. for func in [mod.__getattribute__(x) for x in dir(mod)
  28. if isinstance(mod.__getattribute__(x), types.FunctionType)]:
  29. try:
  30. # test method decorator will add test_method attribute to test function
  31. if func.test_method:
  32. test_functions.append(func)
  33. except AttributeError:
  34. continue
  35. except ImportError as e:
  36. warning_str = 'ImportError: \r\n\tFile:' + file_name + '\r\n\tError:' + str(e)
  37. cls.missing_import_warnings.append(warning_str)
  38. test_functions_out = []
  39. for case in test_functions:
  40. test_functions_out += cls.replicate_case(case)
  41. for i, test_function in enumerate(test_functions_out):
  42. print('\t{}. {} <{}>'.format(i + 1, test_function.case_info['name'], test_function.case_info['target']))
  43. test_function.case_info['app_dir'] = os.path.dirname(file_name)
  44. test_function.case_info['script_path'] = file_name
  45. return test_functions_out
  46. @classmethod
  47. def _search_test_case_files(cls, test_case, file_pattern):
  48. """ search all test case files recursively of a path """
  49. if not os.path.exists(test_case):
  50. raise OSError(f'test case path "{test_case}" not exist')
  51. if os.path.isdir(test_case):
  52. test_case_files = []
  53. for root, _, file_names in os.walk(test_case):
  54. for filename in fnmatch.filter(file_names, file_pattern):
  55. test_case_files.append(os.path.join(root, filename))
  56. else:
  57. test_case_files = [test_case]
  58. return test_case_files
  59. @classmethod
  60. def replicate_case(cls, case):
  61. """
  62. Replicate cases according to its filter values.
  63. If one case has specified filter chip=(ESP32, ESP32C),
  64. it will create 2 cases, one for ESP32 and on for ESP32C.
  65. Once the cases are replicated, it's easy to filter those we want to execute.
  66. :param case: the original case
  67. :return: a list of replicated cases
  68. """
  69. replicate_config = []
  70. for key in case.case_info:
  71. if key == 'ci_target': # ci_target is used to filter target, should not be duplicated.
  72. continue
  73. if isinstance(case.case_info[key], (list, tuple)):
  74. replicate_config.append(key)
  75. def _replicate_for_key(cases, replicate_key, replicate_list):
  76. def deepcopy_func(f, name=None):
  77. fn = types.FunctionType(f.__code__, f.__globals__, name if name else f.__name__,
  78. f.__defaults__, f.__closure__)
  79. fn.__dict__.update(copy.deepcopy(f.__dict__))
  80. return fn
  81. case_out = []
  82. for inner_case in cases:
  83. for value in replicate_list:
  84. new_case = deepcopy_func(inner_case)
  85. new_case.case_info[replicate_key] = value
  86. case_out.append(new_case)
  87. return case_out
  88. replicated_cases = [case]
  89. while replicate_config:
  90. if not replicate_config:
  91. break
  92. key = replicate_config.pop()
  93. if key in cls.SUPPORT_REPLICATE_CASES_KEY:
  94. replicated_cases = _replicate_for_key(replicated_cases, key, case.case_info[key])
  95. # mark the cases with targets not in ci_target
  96. for case in replicated_cases:
  97. ci_target = case.case_info['ci_target']
  98. if not ci_target or case.case_info['target'] in ci_target:
  99. case.case_info['supported_in_ci'] = True
  100. else:
  101. case.case_info['supported_in_ci'] = False
  102. return replicated_cases
  103. @classmethod
  104. def search_test_cases(cls, test_case_paths, test_case_file_pattern=None):
  105. """
  106. search all test cases from a folder or file, and then do case replicate.
  107. :param test_case_paths: test case file(s) paths
  108. :param test_case_file_pattern: unix filename pattern
  109. :return: a list of replicated test methods
  110. """
  111. if not isinstance(test_case_paths, list):
  112. test_case_paths = [test_case_paths]
  113. test_case_files = []
  114. for path in test_case_paths:
  115. test_case_files.extend(
  116. cls._search_test_case_files(path, test_case_file_pattern or cls.TEST_CASE_FILE_PATTERN))
  117. test_cases = []
  118. for test_case_file in test_case_files:
  119. test_cases += cls._search_cases_from_file(test_case_file)
  120. if cls.missing_import_warnings:
  121. raise ImportError('\n\n'.join(cls.missing_import_warnings))
  122. return test_cases