diff --git a/tools/ci/mypy_ignore_list.txt b/tools/ci/mypy_ignore_list.txt index 9b2e25921f..0f29610c4e 100644 --- a/tools/ci/mypy_ignore_list.txt +++ b/tools/ci/mypy_ignore_list.txt @@ -173,6 +173,7 @@ tools/ci/python_packages/tiny_test_fw/Utility/CaseConfig.py tools/ci/python_packages/tiny_test_fw/Utility/GitlabCIJob.py tools/ci/python_packages/tiny_test_fw/Utility/SearchCases.py tools/ci/python_packages/tiny_test_fw/Utility/TestCase.py +tools/ci/python_packages/tiny_test_fw/Utility/__init__.py tools/ci/python_packages/tiny_test_fw/bin/Runner.py tools/ci/python_packages/tiny_test_fw/bin/example.py tools/ci/python_packages/tiny_test_fw/docs/conf.py diff --git a/tools/ci/python_packages/tiny_test_fw/Utility/SearchCases.py b/tools/ci/python_packages/tiny_test_fw/Utility/SearchCases.py index 88de30eded..011465852e 100644 --- a/tools/ci/python_packages/tiny_test_fw/Utility/SearchCases.py +++ b/tools/ci/python_packages/tiny_test_fw/Utility/SearchCases.py @@ -28,7 +28,9 @@ class Search: print('Try to get cases from: ' + file_name) test_functions = [] try: - mod = load_source(file_name) + # search case no need to run the functions + # mock missing modules would help us get the test case function objects + mod = load_source(file_name, mock_missing=True) for func in [mod.__getattribute__(x) for x in dir(mod) if isinstance(mod.__getattribute__(x), types.FunctionType)]: try: @@ -38,7 +40,7 @@ class Search: except AttributeError: continue except ImportError as e: - warning_str = 'ImortError: \r\n\tFile:' + file_name + '\r\n\tError:' + str(e) + warning_str = 'ImportError: \r\n\tFile:' + file_name + '\r\n\tError:' + str(e) cls.missing_import_warnings.append(warning_str) test_functions_out = [] @@ -56,7 +58,7 @@ class Search: """ search all test case files recursively of a path """ if not os.path.exists(test_case): - raise OSError('test case path not exist') + raise OSError(f'test case path "{test_case}" not exist') if os.path.isdir(test_case): test_case_files = [] for root, _, file_names in os.walk(test_case): diff --git a/tools/ci/python_packages/tiny_test_fw/Utility/__init__.py b/tools/ci/python_packages/tiny_test_fw/Utility/__init__.py index 113455ef79..6de3f6067c 100644 --- a/tools/ci/python_packages/tiny_test_fw/Utility/__init__.py +++ b/tools/ci/python_packages/tiny_test_fw/Utility/__init__.py @@ -1,9 +1,14 @@ +# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + from __future__ import print_function +import logging import os.path import sys import time import traceback +from unittest.mock import MagicMock from .. import Env @@ -63,19 +68,20 @@ def console_log(data, color='white', end='\n'): pass -__LOADED_MODULES = dict() +__LOADED_MODULES = dict() # type: ignore # we should only load one module once. # if we load one module twice, # python will regard the same object loaded in the first time and second time as different objects. # it will lead to strange errors like `isinstance(object, type_of_this_object)` return False -def load_source(path): +def load_source(path, mock_missing=False): """ Dynamic loading python file. Note that this function SHOULD NOT be used to replace ``import``. It should only be used when the package path is only available in runtime. :param path: The path of python file + :param mock_missing: If True, will mock the module if the module is not found. :return: Loaded object """ path = os.path.realpath(path) @@ -84,17 +90,29 @@ def load_source(path): try: return __LOADED_MODULES[path] except KeyError: + folder = os.path.dirname(path) + sys.path.append(folder) + from importlib.machinery import SourceFileLoader try: - dir = os.path.dirname(path) - sys.path.append(dir) - from importlib.machinery import SourceFileLoader ret = SourceFileLoader(load_name, path).load_module() - except ImportError: - # importlib.machinery doesn't exists in Python 2 so we will use imp (deprecated in Python 3) - import imp - ret = imp.load_source(load_name, path) + except ModuleNotFoundError as e: + if not mock_missing: + raise + + # mock the missing third-party libs. Don't use it when real-testing + while True: + sys.modules[e.name] = MagicMock() + logging.warning('Mocking python module %s', e.name) + try: + ret = SourceFileLoader(load_name, path).load_module() + break + except ModuleNotFoundError as f: + e = f # another round + except SyntaxError: + # well, let's ignore it... non of our business + return None finally: - sys.path.remove(dir) + sys.path.remove(folder) __LOADED_MODULES[path] = ret return ret