Merge branch 'ci/migrate_panic_test_to_pytest_embedded' into 'master'

CI: migrate panic test to pytest embedded

See merge request espressif/esp-idf!17119
This commit is contained in:
Fu Hanxi 2022-02-23 03:00:07 +00:00
commit fac13c5c3d
14 changed files with 756 additions and 887 deletions

View File

@ -73,7 +73,7 @@ variables:
TEST_ENV_CONFIG_REPO: "https://gitlab-ci-token:${BOT_TOKEN}@${CI_SERVER_HOST}:${CI_SERVER_PORT}/qa/ci-test-runner-configs.git" TEST_ENV_CONFIG_REPO: "https://gitlab-ci-token:${BOT_TOKEN}@${CI_SERVER_HOST}:${CI_SERVER_PORT}/qa/ci-test-runner-configs.git"
CI_AUTO_TEST_SCRIPT_REPO_URL: "https://gitlab-ci-token:${BOT_TOKEN}@${CI_SERVER_HOST}:${CI_SERVER_PORT}/qa/auto_test_script.git" CI_AUTO_TEST_SCRIPT_REPO_URL: "https://gitlab-ci-token:${BOT_TOKEN}@${CI_SERVER_HOST}:${CI_SERVER_PORT}/qa/auto_test_script.git"
CI_AUTO_TEST_SCRIPT_REPO_BRANCH: "ci/v4.1" CI_AUTO_TEST_SCRIPT_REPO_BRANCH: "ci/v4.1"
PYTEST_EMBEDDED_TAG: "v0.5.1" PYTEST_EMBEDDED_TAG: "v0.6.0rc0"
# cache python dependencies # cache python dependencies
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"

View File

@ -93,6 +93,20 @@ build_pytest_components_esp32c3:
script: script:
- run_cmd python tools/ci/build_pytest_apps.py components --target esp32c3 --size-info $SIZE_INFO_LOCATION -vv - run_cmd python tools/ci/build_pytest_apps.py components --target esp32c3 --size-info $SIZE_INFO_LOCATION -vv
build_pytest_test_apps_esp32:
extends:
- .build_pytest_template
- .rules:build:custom_test-esp32
script:
- run_cmd python tools/ci/build_pytest_apps.py tools/test_apps --target esp32 --size-info $SIZE_INFO_LOCATION -vv
build_pytest_test_apps_esp32s2:
extends:
- .build_pytest_template
- .rules:build:custom_test-esp32s2
script:
- run_cmd python tools/ci/build_pytest_apps.py tools/test_apps --target esp32s2 --size-info $SIZE_INFO_LOCATION -vv
build_non_test_components_apps: build_non_test_components_apps:
extends: extends:
- .build_template - .build_template

View File

@ -63,6 +63,7 @@
patterns: patterns:
- build_components - build_components
- build_system - build_system
- build_target_test
included_in: included_in:
- "build:{0}" - "build:{0}"
- build:target_test - build:target_test

View File

@ -26,12 +26,6 @@
- "tools/ci/python_packages/tiny_test_fw/**/*" - "tools/ci/python_packages/tiny_test_fw/**/*"
- "tools/ci/python_packages/ttfw_idf/**/*" - "tools/ci/python_packages/ttfw_idf/**/*"
- "tools/ci/find_apps_build_apps.sh"
- "tools/ci/build_pytest_apps.py"
- "tools/build_apps.py"
- "tools/find_apps.py"
- "tools/find_build_apps/**/*"
- "tools/esp_prov/**/*" - "tools/esp_prov/**/*"
- "examples/**/*" - "examples/**/*"
@ -43,6 +37,14 @@
- "components/**/*" - "components/**/*"
- "examples/cxx/experimental/experimental_cpp_component/*" - "examples/cxx/experimental/experimental_cpp_component/*"
.patterns-build_target_test: &patterns-build_target_test
- "tools/ci/find_apps_build_apps.sh"
- "tools/build_apps.py"
- "tools/find_apps.py"
- "tools/find_build_apps/**/*"
- "tools/ci/build_pytest_apps.py"
.patterns-build_system: &patterns-build_system .patterns-build_system: &patterns-build_system
- "tools/cmake/**/*" - "tools/cmake/**/*"
- "tools/kconfig_new/**/*" - "tools/kconfig_new/**/*"
@ -437,6 +439,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-component_ut changes: *patterns-component_ut
@ -455,6 +459,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-component_ut changes: *patterns-component_ut
@ -473,6 +479,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-component_ut changes: *patterns-component_ut
@ -491,6 +499,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-component_ut changes: *patterns-component_ut
@ -509,6 +519,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-component_ut changes: *patterns-component_ut
@ -527,6 +539,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-component_ut changes: *patterns-component_ut
@ -545,6 +559,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-component_ut changes: *patterns-component_ut
@ -567,6 +583,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-custom_test changes: *patterns-custom_test
@ -584,6 +602,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-custom_test changes: *patterns-custom_test
@ -600,6 +620,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-custom_test changes: *patterns-custom_test
@ -616,6 +638,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-custom_test changes: *patterns-custom_test
@ -632,6 +656,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-custom_test changes: *patterns-custom_test
@ -648,6 +674,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-custom_test changes: *patterns-custom_test
@ -664,6 +692,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-custom_test changes: *patterns-custom_test
@ -698,6 +728,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-example_test changes: *patterns-example_test
@ -717,6 +749,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-example_test changes: *patterns-example_test
@ -735,6 +769,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-example_test changes: *patterns-example_test
@ -753,6 +789,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-example_test changes: *patterns-example_test
@ -771,6 +809,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-example_test changes: *patterns-example_test
@ -789,6 +829,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-example_test changes: *patterns-example_test
@ -807,6 +849,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-example_test changes: *patterns-example_test
@ -880,6 +924,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-component_ut changes: *patterns-component_ut
- <<: *if-dev-push - <<: *if-dev-push
@ -909,6 +955,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-unit_test changes: *patterns-unit_test
@ -925,6 +973,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-unit_test changes: *patterns-unit_test
@ -941,6 +991,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-unit_test changes: *patterns-unit_test
@ -957,6 +1009,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-unit_test changes: *patterns-unit_test
@ -973,6 +1027,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-unit_test changes: *patterns-unit_test
@ -989,6 +1045,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-unit_test changes: *patterns-unit_test
@ -1005,6 +1063,8 @@
changes: *patterns-build_components changes: *patterns-build_components
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-build_system changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-build_target_test
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-unit_test changes: *patterns-unit_test

View File

@ -167,6 +167,39 @@ component_ut_pytest_esp32c3_generic:
- ESP32C3 - ESP32C3
- COMPONENT_UT_GENERIC - COMPONENT_UT_GENERIC
.pytest_test_apps_dir_template:
extends: .pytest_template
variables:
TEST_DIR: tools/test_apps
test_app_test_pytest_esp32_generic:
extends:
- .pytest_test_apps_dir_template
- .rules:test:custom_test-esp32
needs:
- build_pytest_test_apps_esp32
variables:
TARGET: esp32
ENV_MARKER: generic
SETUP_TOOLS: "1" # need gdb
tags:
- ESP32
- Example_GENERIC
test_app_test_pytest_esp32s2_generic:
extends:
- .pytest_test_apps_dir_template
- .rules:test:custom_test-esp32s2
needs:
- build_pytest_test_apps_esp32s2
variables:
TARGET: esp32s2
ENV_MARKER: generic
SETUP_TOOLS: "1" # need gdb
tags:
- ESP32S2
- Example_GENERIC
# for parallel jobs, CI_JOB_NAME will be "job_name index/total" (for example, "IT_001 1/2") # for parallel jobs, CI_JOB_NAME will be "job_name index/total" (for example, "IT_001 1/2")
# we need to convert to pattern "job_name_index.yml" # we need to convert to pattern "job_name_index.yml"
.define_config_file_name: &define_config_file_name | .define_config_file_name: &define_config_file_name |
@ -538,12 +571,9 @@ test_app_test_005:
test_app_test_esp32_generic: test_app_test_esp32_generic:
extends: .test_app_esp32_template extends: .test_app_esp32_template
parallel: 5
tags: tags:
- ESP32 - ESP32
- Example_GENERIC - Example_GENERIC
variables:
SETUP_TOOLS: "1"
test_app_test_flash_psram_f4r4: test_app_test_flash_psram_f4r4:
extends: .test_app_esp32s3_template extends: .test_app_esp32s3_template

View File

@ -29,6 +29,7 @@ from pytest_embedded.utils import find_by_suffix
SUPPORTED_TARGETS = ['esp32', 'esp32s2', 'esp32c3', 'esp32s3'] SUPPORTED_TARGETS = ['esp32', 'esp32s2', 'esp32c3', 'esp32s3']
PREVIEW_TARGETS = ['linux', 'esp32h2', 'esp32c2'] PREVIEW_TARGETS = ['linux', 'esp32h2', 'esp32c2']
DEFAULT_SDKCONFIG = 'default'
################## ##################
@ -57,7 +58,12 @@ def item_marker_names(item: Item) -> List[str]:
############ ############
@pytest.fixture @pytest.fixture
def config(request: FixtureRequest) -> str: def config(request: FixtureRequest) -> str:
return getattr(request, 'param', None) or request.config.getoption('config', 'default') # type: ignore return getattr(request, 'param', None) or DEFAULT_SDKCONFIG
@pytest.fixture
def test_func_name(request: FixtureRequest) -> str:
return request.node.function.__name__ # type: ignore
@pytest.fixture @pytest.fixture
@ -67,7 +73,9 @@ def test_case_name(request: FixtureRequest, target: str, config: str) -> str:
@pytest.fixture @pytest.fixture
@parse_configuration @parse_configuration
def build_dir(request: FixtureRequest, app_path: str, target: Optional[str], config: Optional[str]) -> str: def build_dir(
request: FixtureRequest, app_path: str, target: Optional[str], config: Optional[str]
) -> str:
""" """
Check local build dir with the following priority: Check local build dir with the following priority:
@ -85,8 +93,10 @@ def build_dir(request: FixtureRequest, app_path: str, target: Optional[str], con
Returns: Returns:
valid build directory valid build directory
""" """
param_or_cli: str = getattr(request, 'param', None) or request.config.option.__dict__.get('build_dir') param_or_cli: str = getattr(
if param_or_cli is not None: # respect the parametrize and the cli request, 'param', None
) or request.config.option.__dict__.get('build_dir')
if param_or_cli is not None: # respect the param and the cli
return param_or_cli return param_or_cli
check_dirs = [] check_dirs = []
@ -104,16 +114,21 @@ def build_dir(request: FixtureRequest, app_path: str, target: Optional[str], con
logging.info(f'find valid binary path: {binary_path}') logging.info(f'find valid binary path: {binary_path}')
return check_dir return check_dir
logging.warning(f'checking binary path: {binary_path}... missing... try another place') logging.warning(
'checking binary path: %s... missing... try another place', binary_path
)
recommend_place = check_dirs[0] recommend_place = check_dirs[0]
logging.error( logging.error(
f'no build dir valid. Please build the binary via "idf.py -B {recommend_place} build" and run pytest again') f'no build dir valid. Please build the binary via "idf.py -B {recommend_place} build" and run pytest again'
)
sys.exit(1) sys.exit(1)
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def junit_properties(test_case_name: str, record_xml_attribute: Callable[[str, object], None]) -> None: def junit_properties(
test_case_name: str, record_xml_attribute: Callable[[str, object], None]
) -> None:
""" """
This fixture is autoused and will modify the junit report test case name to <target>.<config>.<case_name> This fixture is autoused and will modify the junit report test case name to <target>.<config>.<case_name>
""" """
@ -123,12 +138,30 @@ def junit_properties(test_case_name: str, record_xml_attribute: Callable[[str, o
################## ##################
# Hook functions # # Hook functions #
################## ##################
def pytest_addoption(parser: pytest.Parser) -> None:
base_group = parser.getgroup('idf')
base_group.addoption(
'--sdkconfig',
help='sdkconfig postfix, like sdkconfig.ci.<config>. (Default: None, which would build all found apps)',
)
@pytest.hookimpl(tryfirst=True) @pytest.hookimpl(tryfirst=True)
def pytest_collection_modifyitems(config: Config, items: List[Item]) -> None: def pytest_collection_modifyitems(config: Config, items: List[Function]) -> None:
target = config.getoption('target', None) # use the `build` dir target = config.getoption('target', None) # use the `build` dir
if not target: if not target:
return return
# sort by file path and callspec.config
# implement like this since this is a limitation of pytest, couldn't get fixture values while collecting
# https://github.com/pytest-dev/pytest/discussions/9689
def _get_param_config(_item: Function) -> str:
if hasattr(_item, 'callspec'):
return _item.callspec.params.get('config', DEFAULT_SDKCONFIG) # type: ignore
return DEFAULT_SDKCONFIG
items.sort(key=lambda x: (os.path.dirname(x.path), _get_param_config(x)))
# add markers for special markers # add markers for special markers
for item in items: for item in items:
if 'supported_targets' in item_marker_names(item): if 'supported_targets' in item_marker_names(item):
@ -144,6 +177,14 @@ def pytest_collection_modifyitems(config: Config, items: List[Item]) -> None:
# filter all the test cases with "--target" # filter all the test cases with "--target"
items[:] = [item for item in items if target in item_marker_names(item)] items[:] = [item for item in items if target in item_marker_names(item)]
# filter all the test cases with cli option "config"
if config.getoption('sdkconfig'):
items[:] = [
item
for item in items
if _get_param_config(item) == config.getoption('sdkconfig')
]
@pytest.hookimpl(trylast=True) @pytest.hookimpl(trylast=True)
def pytest_runtest_teardown(item: Function) -> None: def pytest_runtest_teardown(item: Function) -> None:
@ -166,5 +207,7 @@ def pytest_runtest_teardown(item: Function) -> None:
for case in testcases: for case in testcases:
case.attrib['name'] = format_case_id(target, config, case.attrib['name']) case.attrib['name'] = format_case_id(target, config, case.attrib['name'])
if 'file' in case.attrib: if 'file' in case.attrib:
case.attrib['file'] = case.attrib['file'].replace('/IDF/', '') # our unity test framework case.attrib['file'] = case.attrib['file'].replace(
'/IDF/', ''
) # our unity test framework
xml.write(junit) xml.write(junit)

View File

@ -6,6 +6,7 @@ This file is used to generate binary files for the given path.
""" """
import argparse import argparse
import copy
import logging import logging
import os import os
import sys import sys
@ -58,17 +59,25 @@ def main(args: argparse.Namespace) -> None:
build_system='cmake', build_system='cmake',
config_rules=config_rules, config_rules=config_rules,
) )
logging.info(f'Found {len(build_items)} builds')
build_items.sort(key=lambda x: x.build_path) # type: ignore
modified_build_items = []
# auto clean up the binaries if no flag --preserve-all # auto clean up the binaries if no flag --preserve-all
if args.preserve_all is False: for item in build_items:
for item in build_items: is_test_related = item.config_name in app_configs[item.app_dir]
if item.config_name not in app_configs[item.app_dir]: if args.test_only and not is_test_related:
item.preserve = False logging.info(f'Skipping non-test app: {item}')
continue
copied_item = copy.deepcopy(item)
if not args.preserve_all and not is_test_related:
copied_item.preserve = False
modified_build_items.append(copied_item)
logging.info(f'Found {len(modified_build_items)} builds')
modified_build_items.sort(key=lambda x: x.build_path) # type: ignore
build_apps( build_apps(
build_items=build_items, build_items=modified_build_items,
parallel_count=args.parallel_count, parallel_count=args.parallel_count,
parallel_index=args.parallel_index, parallel_index=args.parallel_index,
dry_run=False, dry_run=False,
@ -128,7 +137,12 @@ if __name__ == '__main__':
parser.add_argument( parser.add_argument(
'--preserve-all', '--preserve-all',
action='store_true', action='store_true',
help='add this flag to preserve the binaries for all apps', help='Preserve the binaries for all apps when specified.',
)
parser.add_argument(
'--test-only',
action='store_true',
help='Build only test related app when specified.',
) )
arguments = parser.parse_args() arguments = parser.parse_args()
setup_logging(arguments) setup_logging(arguments)

View File

@ -52,6 +52,7 @@ examples_and_unit_tests:
- 'examples/' - 'examples/'
- 'components/**/test/**' - 'components/**/test/**'
- 'components/**/test_apps/**' - 'components/**/test_apps/**'
- 'tools/test_apps/**'
allowed_licenses: allowed_licenses:
- Apache-2.0 - Apache-2.0
- Unlicense - Unlicense

View File

@ -1,345 +0,0 @@
#!/usr/bin/env python
import sys
import panic_tests as test
from test_panic_util.test_panic_util import panic_test, run_all
# test_task_wdt
@panic_test(target=['ESP32', 'ESP32S2'])
def test_panic_task_wdt(env, _extra_data):
test.task_wdt_inner(env, 'panic')
@panic_test()
def test_coredump_task_wdt_uart_elf_crc(env, _extra_data):
test.task_wdt_inner(env, 'coredump_uart_elf_crc')
@panic_test()
def test_coredump_task_wdt_uart_bin_crc(env, _extra_data):
test.task_wdt_inner(env, 'coredump_uart_bin_crc')
@panic_test()
def test_coredump_task_wdt_flash_elf_sha(env, _extra_data):
test.task_wdt_inner(env, 'coredump_flash_elf_sha')
@panic_test()
def test_coredump_task_wdt_flash_bin_crc(env, _extra_data):
test.task_wdt_inner(env, 'coredump_flash_bin_crc')
@panic_test()
def test_gdbstub_task_wdt(env, _extra_data):
test.task_wdt_inner(env, 'gdbstub')
# test_int_wdt
@panic_test()
def test_panic_int_wdt(env, _extra_data):
test.int_wdt_inner(env, 'panic')
@panic_test()
def test_coredump_int_wdt_uart_elf_crc(env, _extra_data):
test.int_wdt_inner(env, 'coredump_uart_elf_crc')
@panic_test()
def test_coredump_int_wdt_uart_bin_crc(env, _extra_data):
test.int_wdt_inner(env, 'coredump_uart_bin_crc')
@panic_test()
def test_coredump_int_wdt_flash_elf_sha(env, _extra_data):
test.int_wdt_inner(env, 'coredump_flash_elf_sha')
@panic_test()
def test_coredump_int_wdt_flash_bin_crc(env, _extra_data):
test.int_wdt_inner(env, 'coredump_flash_bin_crc')
@panic_test()
def test_gdbstub_int_wdt(env, _extra_data):
test.int_wdt_inner(env, 'gdbstub')
# test_int_wdt_cache_disabled
@panic_test()
def test_panic_int_wdt_cache_disabled(env, _extra_data):
test.int_wdt_cache_disabled_inner(env, 'panic')
@panic_test()
def test_coredump_int_wdt_cache_disabled_uart_elf_crc(env, _extra_data):
test.int_wdt_cache_disabled_inner(env, 'coredump_uart_elf_crc')
@panic_test()
def test_coredump_int_wdt_cache_disabled_uart_bin_crc(env, _extra_data):
test.int_wdt_cache_disabled_inner(env, 'coredump_uart_bin_crc')
@panic_test()
def test_coredump_int_wdt_cache_disabled_flash_elf_sha(env, _extra_data):
test.int_wdt_cache_disabled_inner(env, 'coredump_flash_elf_sha')
@panic_test()
def test_coredump_int_wdt_cache_disabled_flash_bin_crc(env, _extra_data):
test.int_wdt_cache_disabled_inner(env, 'coredump_flash_bin_crc')
@panic_test()
def test_gdbstub_int_wdt_cache_disabled(env, _extra_data):
test.int_wdt_cache_disabled_inner(env, 'gdbstub')
# test_cache_error
@panic_test()
def test_panic_cache_error(env, _extra_data):
test.cache_error_inner(env, 'panic')
@panic_test()
def test_coredump_cache_error_uart_elf_crc(env, _extra_data):
test.cache_error_inner(env, 'coredump_uart_elf_crc')
@panic_test()
def test_coredump_cache_error_uart_bin_crc(env, _extra_data):
test.cache_error_inner(env, 'coredump_uart_bin_crc')
@panic_test()
def test_coredump_cache_error_flash_elf_sha(env, _extra_data):
test.cache_error_inner(env, 'coredump_flash_elf_sha')
@panic_test()
def test_coredump_cache_error_flash_bin_crc(env, _extra_data):
test.cache_error_inner(env, 'coredump_flash_bin_crc')
@panic_test()
def test_gdbstub_cache_error(env, _extra_data):
test.cache_error_inner(env, 'gdbstub')
# test_stack_overflow
@panic_test(target=['ESP32', 'ESP32S2'])
def test_panic_stack_overflow(env, _extra_data):
test.stack_overflow_inner(env, 'panic')
@panic_test()
def test_coredump_stack_overflow_uart_elf_crc(env, _extra_data):
test.stack_overflow_inner(env, 'coredump_uart_elf_crc')
@panic_test()
def test_coredump_stack_overflow_uart_bin_crc(env, _extra_data):
test.stack_overflow_inner(env, 'coredump_uart_bin_crc')
@panic_test()
def test_coredump_stack_overflow_flash_elf_sha(env, _extra_data):
test.stack_overflow_inner(env, 'coredump_flash_elf_sha')
@panic_test()
def test_coredump_stack_overflow_flash_bin_crc(env, _extra_data):
test.stack_overflow_inner(env, 'coredump_flash_bin_crc')
@panic_test()
def test_gdbstub_stack_overflow(env, _extra_data):
test.stack_overflow_inner(env, 'gdbstub')
# test_instr_fetch_prohibited
@panic_test(target=['ESP32', 'ESP32S2'])
def test_panic_instr_fetch_prohibited(env, _extra_data):
test.instr_fetch_prohibited_inner(env, 'panic')
@panic_test()
def test_coredump_instr_fetch_prohibited_uart_elf_crc(env, _extra_data):
test.instr_fetch_prohibited_inner(env, 'coredump_uart_elf_crc')
@panic_test()
def test_coredump_instr_fetch_prohibited_uart_bin_crc(env, _extra_data):
test.instr_fetch_prohibited_inner(env, 'coredump_uart_bin_crc')
@panic_test()
def test_coredump_instr_fetch_prohibited_flash_elf_sha(env, _extra_data):
test.instr_fetch_prohibited_inner(env, 'coredump_flash_elf_sha')
@panic_test()
def test_coredump_instr_fetch_prohibited_flash_bin_crc(env, _extra_data):
test.instr_fetch_prohibited_inner(env, 'coredump_flash_bin_crc')
@panic_test()
def test_gdbstub_instr_fetch_prohibited(env, _extra_data):
test.instr_fetch_prohibited_inner(env, 'gdbstub')
# test_illegal_instruction
@panic_test(target=['ESP32', 'ESP32S2'])
def test_panic_illegal_instruction(env, _extra_data):
test.illegal_instruction_inner(env, 'panic')
@panic_test()
def test_coredump_illegal_instruction_uart_elf_crc(env, _extra_data):
test.illegal_instruction_inner(env, 'coredump_uart_elf_crc')
@panic_test()
def test_coredump_illegal_instruction_uart_bin_crc(env, _extra_data):
test.illegal_instruction_inner(env, 'coredump_uart_bin_crc')
@panic_test()
def test_coredump_illegal_instruction_flash_elf_sha(env, _extra_data):
test.illegal_instruction_inner(env, 'coredump_flash_elf_sha')
@panic_test()
def test_coredump_illegal_instruction_flash_bin_crc(env, _extra_data):
test.illegal_instruction_inner(env, 'coredump_flash_bin_crc')
@panic_test()
def test_gdbstub_illegal_instruction(env, _extra_data):
test.illegal_instruction_inner(env, 'gdbstub')
# test_storeprohibited
@panic_test(target=['ESP32', 'ESP32S2'])
def test_panic_storeprohibited(env, _extra_data):
test.storeprohibited_inner(env, 'panic')
@panic_test()
def test_coredump_storeprohibited_uart_elf_crc(env, _extra_data):
test.storeprohibited_inner(env, 'coredump_uart_elf_crc')
@panic_test()
def test_coredump_storeprohibited_uart_bin_crc(env, _extra_data):
test.storeprohibited_inner(env, 'coredump_uart_bin_crc')
@panic_test()
def test_coredump_storeprohibited_flash_elf_sha(env, _extra_data):
test.storeprohibited_inner(env, 'coredump_flash_elf_sha')
@panic_test()
def test_coredump_storeprohibited_flash_bin_crc(env, _extra_data):
test.storeprohibited_inner(env, 'coredump_flash_bin_crc')
@panic_test()
def test_gdbstub_storeprohibited(env, _extra_data):
test.storeprohibited_inner(env, 'gdbstub')
# test_abort
@panic_test(target=['ESP32', 'ESP32S2'])
def test_panic_abort(env, _extra_data):
test.abort_inner(env, 'panic')
@panic_test(target=['ESP32'])
def test_panic_abort_cache_disabled(env, _extra_data):
test.abort_cached_disabled_inner(env, 'panic')
@panic_test()
def test_coredump_abort_uart_elf_crc(env, _extra_data):
test.abort_inner(env, 'coredump_uart_elf_crc')
@panic_test()
def test_coredump_abort_uart_bin_crc(env, _extra_data):
test.abort_inner(env, 'coredump_uart_bin_crc')
@panic_test()
def test_coredump_abort_flash_elf_sha(env, _extra_data):
test.abort_inner(env, 'coredump_flash_elf_sha')
@panic_test()
def test_coredump_abort_flash_bin_crc(env, _extra_data):
test.abort_inner(env, 'coredump_flash_bin_crc')
@panic_test()
def test_gdbstub_abort(env, _extra_data):
test.abort_inner(env, 'gdbstub')
# test_assert
@panic_test(target=['ESP32', 'ESP32S2'])
def test_panic_assert(env, _extra_data):
test.assert_inner(env, 'panic')
@panic_test(target=['ESP32'])
def test_panic_assert_cache_disabled(env, _extra_data):
test.assert_cached_disabled_inner(env, 'panic')
# test_ub
@panic_test()
def test_panic_ub(env, _extra_data):
test.ub_inner(env, 'panic')
@panic_test()
def test_coredump_ub_uart_elf_crc(env, _extra_data):
test.ub_inner(env, 'coredump_uart_elf_crc')
@panic_test()
def test_coredump_ub_uart_bin_crc(env, _extra_data):
test.ub_inner(env, 'coredump_uart_bin_crc')
@panic_test()
def test_coredump_ub_flash_elf_sha(env, _extra_data):
test.ub_inner(env, 'coredump_flash_elf_sha')
@panic_test()
def test_coredump_ub_flash_bin_crc(env, _extra_data):
test.ub_inner(env, 'coredump_flash_bin_crc')
@panic_test()
def test_gdbstub_ub(env, _extra_data):
test.ub_inner(env, 'gdbstub')
if __name__ == '__main__':
run_all(__file__, sys.argv[1:])

View File

@ -0,0 +1,279 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# pylint: disable=W0621 # redefined-outer-name
import hashlib
import logging
import os
import subprocess
import sys
from typing import Any, Dict, List, TextIO
import pexpect
import pytest
from _pytest.fixtures import FixtureRequest
from _pytest.monkeypatch import MonkeyPatch
from pygdbmi.gdbcontroller import GdbController, GdbTimeoutError, NoGdbProcessError
from pytest_embedded_idf.app import IdfApp
from pytest_embedded_idf.dut import IdfDut
from pytest_embedded_idf.serial import IdfSerial
def sha256(file: str) -> str:
res = hashlib.sha256()
with open(file, 'rb') as fr:
res.update(fr.read())
return res.hexdigest()
class PanicTestDut(IdfDut):
BOOT_CMD_ADDR = 0x9000
BOOT_CMD_SIZE = 0x1000
DEFAULT_EXPECT_TIMEOUT = 10
COREDUMP_UART_START = '================= CORE DUMP START ================='
COREDUMP_UART_END = '================= CORE DUMP END ================='
app: IdfApp
serial: IdfSerial
def __init__(self, *args, **kwargs) -> None: # type: ignore
super().__init__(*args, **kwargs)
self.gdb: GdbController = None # type: ignore
# record this since pygdbmi is using logging.debug to generate some single character mess
self.log_level = logging.getLogger().level
# pygdbmi is using logging.debug to generate some single character mess
if self.log_level <= logging.DEBUG:
logging.getLogger().setLevel(logging.INFO)
self.coredump_output: TextIO = None # type: ignore
def close(self) -> None:
if self.gdb:
self.gdb.exit()
super().close()
def revert_log_level(self) -> None:
logging.getLogger().setLevel(self.log_level)
def expect_test_func_name(self, test_func_name: str) -> None:
self.expect_exact('Enter test name:')
self.write(test_func_name)
self.expect_exact('Got test name: ' + test_func_name)
def expect_none(self, pattern, **kwargs) -> None: # type: ignore
"""like dut.expect_all, but with an inverse logic"""
if 'timeout' not in kwargs:
kwargs['timeout'] = 1
try:
res = self.expect(pattern, **kwargs)
raise AssertionError(f'Unexpected: {res.group().decode("utf8")}')
except pexpect.TIMEOUT:
pass
def expect_backtrace(self) -> None:
self.expect_exact('Backtrace:')
self.expect_none('CORRUPTED')
def expect_gme(self, reason: str) -> None:
"""Expect method for Guru Meditation Errors"""
self.expect_exact(f"Guru Meditation Error: Core 0 panic'ed ({reason})")
def expect_reg_dump(self, core: int = 0) -> None:
"""Expect method for the register dump"""
self.expect(r'Core\s+%d register dump:' % core)
def expect_elf_sha256(self) -> None:
"""Expect method for ELF SHA256 line"""
elf_sha256 = sha256(self.app.elf_file)
elf_sha256_len = int(
self.app.sdkconfig.get('CONFIG_APP_RETRIEVE_LEN_ELF_SHA', '16')
)
self.expect_exact('ELF file SHA256: ' + elf_sha256[0:elf_sha256_len])
def _call_espcoredump(
self, extra_args: List[str], coredump_file_name: str, output_file_name: str
) -> None:
# no "with" here, since we need the file to be open for later inspection by the test case
if not self.coredump_output:
self.coredump_output = open(output_file_name, 'w')
espcoredump_script = os.path.join(
os.environ['IDF_PATH'], 'components', 'espcoredump', 'espcoredump.py'
)
espcoredump_args = [
sys.executable,
espcoredump_script,
'info_corefile',
'--core',
coredump_file_name,
]
espcoredump_args += extra_args
espcoredump_args.append(self.app.elf_file)
logging.info('Running %s', ' '.join(espcoredump_args))
logging.info('espcoredump output is written to %s', self.coredump_output.name)
subprocess.check_call(espcoredump_args, stdout=self.coredump_output)
self.coredump_output.flush()
self.coredump_output.seek(0)
def process_coredump_uart(self) -> None:
"""Extract the core dump from UART output of the test, run espcoredump on it"""
self.expect(self.COREDUMP_UART_START)
res = self.expect('(.+)' + self.COREDUMP_UART_END)
coredump_base64 = res.group(1).decode('utf8')
with open(os.path.join(self.logdir, 'coredump_data.b64'), 'w') as coredump_file:
logging.info('Writing UART base64 core dump to %s', coredump_file.name)
coredump_file.write(coredump_base64)
output_file_name = os.path.join(self.logdir, 'coredump_uart_result.txt')
self._call_espcoredump(
['--core-format', 'b64'], coredump_file.name, output_file_name
)
def process_coredump_flash(self) -> None:
"""Extract the core dump from flash, run espcoredump on it"""
coredump_file_name = os.path.join(self.logdir, 'coredump_data.bin')
logging.info('Writing flash binary core dump to %s', coredump_file_name)
self.serial.dump_flash(coredump_file_name, partition='coredump')
output_file_name = os.path.join(self.logdir, 'coredump_flash_result.txt')
self._call_espcoredump(
['--core-format', 'raw'], coredump_file_name, output_file_name
)
def gdb_write(self, command: str) -> Any:
"""
Wrapper to write to gdb with a longer timeout, as test runner
host can be slow sometimes
"""
return self.gdb.write(command, timeout_sec=10)
def start_gdb(self) -> None:
"""
Runs GDB and connects it to the "serial" port of the DUT.
After this, the DUT expect methods can no longer be used to capture output.
"""
self.gdb = GdbController(gdb_path=self.toolchain_prefix + 'gdb')
# pygdbmi logs to console by default, make it log to a file instead
pygdbmi_log_file_name = os.path.join(self.logdir, 'pygdbmi_log.txt')
pygdbmi_logger = self.gdb.logger
pygdbmi_logger.setLevel(logging.DEBUG)
while pygdbmi_logger.hasHandlers():
pygdbmi_logger.removeHandler(pygdbmi_logger.handlers[0])
log_handler = logging.FileHandler(pygdbmi_log_file_name)
log_handler.setFormatter(
logging.Formatter('%(asctime)s %(levelname)s: %(message)s')
)
pygdbmi_logger.addHandler(log_handler)
logging.info('Running command: %s', self.gdb.get_subprocess_cmd())
for _ in range(10):
try:
# GdbController creates a process with subprocess.Popen(). Is it really running? It is probable that
# an RPI under high load will get non-responsive during creating a lot of processes.
resp = self.gdb.get_gdb_response(
timeout_sec=10
) # calls verify_valid_gdb_subprocess() internally
# it will be interesting to look up this response if the next GDB command fails (times out)
logging.info('GDB response: %s', resp)
break # success
except GdbTimeoutError:
logging.warning(
'GDB internal error: cannot get response from the subprocess'
)
except NoGdbProcessError:
logging.error('GDB internal error: process is not running')
break # failure - TODO: create another GdbController
except ValueError:
logging.error(
'GDB internal error: select() returned an unexpected file number'
)
# Set up logging for GDB remote protocol
gdb_remotelog_file_name = os.path.join(self.logdir, 'gdb_remote_log.txt')
self.gdb_write('-gdb-set remotelogfile ' + gdb_remotelog_file_name)
# Load the ELF file
self.gdb_write('-file-exec-and-symbols {}'.format(self.app.elf_file))
# Connect GDB to UART
self.serial.proc.close()
logging.info('Connecting to GDB Stub...')
self.gdb_write('-gdb-set serial baud 115200')
responses = self.gdb_write('-target-select remote ' + self.serial.port)
# Make sure we get the 'stopped' notification
stop_response = self.find_gdb_response('stopped', 'notify', responses)
if not stop_response:
responses = self.gdb_write('-exec-interrupt')
stop_response = self.find_gdb_response('stopped', 'notify', responses)
assert stop_response
frame = stop_response['payload']['frame']
if 'file' not in frame:
frame['file'] = '?'
if 'line' not in frame:
frame['line'] = '?'
logging.info('Stopped in {func} at {addr} ({file}:{line})'.format(**frame))
# Drain remaining responses
self.gdb.get_gdb_response(raise_error_on_timeout=False)
def gdb_backtrace(self) -> Any:
"""
Returns the list of stack frames for the current thread.
Each frame is a dictionary, refer to pygdbmi docs for the format.
"""
assert self.gdb
responses = self.gdb_write('-stack-list-frames')
return self.find_gdb_response('done', 'result', responses)['payload']['stack']
@staticmethod
def match_backtrace(
gdb_backtrace: List[Any], expected_functions_list: List[Any]
) -> bool:
"""
Returns True if the function names listed in expected_functions_list match the backtrace
given by gdb_backtrace argument. The latter is in the same format as returned by gdb_backtrace()
function.
"""
return all(
[
frame['func'] == expected_functions_list[i]
for i, frame in enumerate(gdb_backtrace)
]
)
@staticmethod
def find_gdb_response(
message: str, response_type: str, responses: List[Any]
) -> Any:
"""
Helper function which extracts one response from an array of GDB responses, filtering
by message and type. Returned message is a dictionary, refer to pygdbmi docs for the format.
"""
def match_response(response: Dict[str, Any]) -> bool:
return response['message'] == message and response['type'] == response_type # type: ignore
filtered_responses = [r for r in responses if match_response(r)]
if not filtered_responses:
return None
return filtered_responses[0]
@pytest.fixture(scope='module')
def monkeypatch_module(request: FixtureRequest) -> MonkeyPatch:
mp = MonkeyPatch()
request.addfinalizer(mp.undo)
return mp
@pytest.fixture(scope='module', autouse=True)
def replace_dut_class(monkeypatch_module: MonkeyPatch) -> None:
monkeypatch_module.setattr('pytest_embedded_idf.dut.IdfDut', PanicTestDut)

View File

@ -1,196 +0,0 @@
#!/usr/bin/env python
import re
from pprint import pformat
from test_panic_util.test_panic_util import get_dut
def get_default_backtrace(test_name):
return [
test_name,
'app_main',
'main_task',
'vPortTaskWrapper'
]
def test_common(dut, test_name, expected_backtrace=None):
if expected_backtrace is None:
expected_backtrace = get_default_backtrace(dut.test_name)
if 'gdbstub' in test_name:
dut.expect('Entering gdb stub now.')
dut.start_gdb()
frames = dut.gdb_backtrace()
if not dut.match_backtrace(frames, expected_backtrace):
raise AssertionError('Unexpected backtrace in test {}:\n{}'.format(test_name, pformat(frames)))
return
if 'uart' in test_name:
dut.expect(dut.COREDUMP_UART_END)
dut.expect('Rebooting...')
if 'uart' in test_name:
dut.process_coredump_uart()
# TODO: check backtrace
elif 'flash' in test_name:
dut.process_coredump_flash()
# TODO: check backtrace
elif 'panic' in test_name:
# TODO: check backtrace
pass
def task_wdt_inner(env, test_name):
with get_dut(env, test_name, 'test_task_wdt', qemu_wdt_enable=True) as dut:
dut.expect('Task watchdog got triggered. The following tasks did not reset the watchdog in time:')
dut.expect('CPU 0: main')
dut.expect(re.compile(r'abort\(\) was called at PC [0-9xa-f]+ on core 0'))
dut.expect_none('register dump:')
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation')
if ('gdbstub' in test_name):
test_common(dut, test_name, expected_backtrace=[
# Backtrace interrupted when abort is called, IDF-842
'panic_abort', 'esp_system_abort'
])
else:
test_common(dut, test_name)
def int_wdt_inner(env, test_name):
with get_dut(env, test_name, 'test_int_wdt', qemu_wdt_enable=True) as dut:
dut.expect_gme('Interrupt wdt timeout on CPU0')
dut.expect_reg_dump(0)
dut.expect_backtrace()
dut.expect_none('Guru Meditation')
dut.expect_reg_dump(1)
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation')
test_common(dut, test_name)
def int_wdt_cache_disabled_inner(env, test_name):
with get_dut(env, test_name, 'test_int_wdt_cache_disabled', qemu_wdt_enable=True) as dut:
dut.expect_gme('Interrupt wdt timeout on CPU0')
dut.expect_reg_dump(0)
dut.expect('Backtrace:')
dut.expect_none('Guru Meditation')
dut.expect_reg_dump(1)
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation')
test_common(dut, test_name)
def cache_error_inner(env, test_name):
with get_dut(env, test_name, 'test_cache_error') as dut:
dut.expect_gme('Cache disabled but cached memory region accessed')
dut.expect_reg_dump(0)
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation')
test_common(dut, test_name,
expected_backtrace=['die'] + get_default_backtrace(dut.test_name))
def abort_inner(env, test_name):
with get_dut(env, test_name, 'test_abort') as dut:
dut.expect(re.compile(r'abort\(\) was called at PC [0-9xa-f]+ on core 0'))
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation', 'Re-entered core dump')
if ('gdbstub' in test_name):
test_common(dut, test_name, expected_backtrace=[
# Backtrace interrupted when abort is called, IDF-842
'panic_abort', 'esp_system_abort'
])
else:
test_common(dut, test_name)
def abort_cached_disabled_inner(env, test_name):
with get_dut(env, test_name, 'test_abort_cache_disabled') as dut:
dut.expect(re.compile(r'abort\(\) was called at PC [0-9xa-f]+ on core 0'))
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation', 'Re-entered core dump')
test_common(dut, test_name)
def assert_inner(env, test_name):
with get_dut(env, test_name, 'test_assert') as dut:
dut.expect(re.compile(r'(assert failed:[\s\w\(\)]*?\s[\.\w\/]*\.(?:c|cpp|h|hpp):\d*.*)'))
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation', 'Re-entered core dump')
test_common(dut, test_name)
def assert_cached_disabled_inner(env, test_name):
with get_dut(env, test_name, 'test_assert_cache_disabled') as dut:
dut.expect(re.compile(r'(assert failed: [0-9xa-fA-F]+.*)'))
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation', 'Re-entered core dump')
test_common(dut, test_name)
def storeprohibited_inner(env, test_name):
with get_dut(env, test_name, 'test_storeprohibited') as dut:
dut.expect_gme('StoreProhibited')
dut.expect_reg_dump(0)
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation')
test_common(dut, test_name)
def stack_overflow_inner(env, test_name):
with get_dut(env, test_name, 'test_stack_overflow') as dut:
dut.expect_gme('Unhandled debug exception')
dut.expect('Stack canary watchpoint triggered (main)')
dut.expect_reg_dump(0)
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation')
test_common(dut, test_name)
def illegal_instruction_inner(env, test_name):
with get_dut(env, test_name, 'test_illegal_instruction') as dut:
dut.expect_gme('IllegalInstruction')
dut.expect_reg_dump(0)
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation')
test_common(dut, test_name)
def instr_fetch_prohibited_inner(env, test_name):
with get_dut(env, test_name, 'test_instr_fetch_prohibited') as dut:
dut.expect_gme('InstrFetchProhibited')
dut.expect_reg_dump(0)
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation')
test_common(dut, test_name,
expected_backtrace=['_init'] + get_default_backtrace(dut.test_name))
def ub_inner(env, test_name):
with get_dut(env, test_name, 'test_ub') as dut:
dut.expect(re.compile(r'Undefined behavior of type out_of_bounds'))
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation', 'Re-entered core dump')
if ('gdbstub' in test_name):
test_common(dut, test_name, expected_backtrace=[
# Backtrace interrupted when abort is called, IDF-842
'panic_abort', 'esp_system_abort'
])
else:
test_common(dut, test_name)

View File

@ -0,0 +1,287 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import re
from pprint import pformat
from typing import List, Optional
import pytest
from conftest import PanicTestDut
CONFIGS = [
pytest.param('coredump_flash_bin_crc', marks=[pytest.mark.esp32, pytest.mark.esp32s2]),
pytest.param('coredump_flash_elf_sha', marks=[pytest.mark.esp32]), # sha256 only supported on esp32
pytest.param('coredump_uart_bin_crc', marks=[pytest.mark.esp32, pytest.mark.esp32s2]),
pytest.param('coredump_uart_elf_crc', marks=[pytest.mark.esp32, pytest.mark.esp32s2]),
pytest.param('gdbstub', marks=[pytest.mark.esp32, pytest.mark.esp32s2]),
pytest.param('panic', marks=[pytest.mark.esp32, pytest.mark.esp32s2]),
]
def get_default_backtrace(config: str) -> List[str]:
return [config, 'app_main', 'main_task', 'vPortTaskWrapper']
def common_test(dut: PanicTestDut, config: str, expected_backtrace: Optional[List[str]] = None) -> None:
if 'gdbstub' in config:
dut.expect_exact('Entering gdb stub now.')
dut.start_gdb()
frames = dut.gdb_backtrace()
if not dut.match_backtrace(frames, expected_backtrace):
raise AssertionError(
'Unexpected backtrace in test {}:\n{}'.format(config, pformat(frames))
)
dut.revert_log_level()
return
if 'uart' in config:
dut.process_coredump_uart()
elif 'flash' in config:
dut.process_coredump_flash()
elif 'panic' in config:
pass
dut.expect('Rebooting...')
@pytest.mark.parametrize('config', CONFIGS, indirect=True)
@pytest.mark.generic
def test_task_wdt(dut: PanicTestDut, config: str, test_func_name: str) -> None:
dut.expect_test_func_name(test_func_name)
dut.expect_exact(
'Task watchdog got triggered. The following tasks did not reset the watchdog in time:'
)
dut.expect_exact('CPU 0: main')
dut.expect(r'abort\(\) was called at PC [0-9xa-f]+ on core 0')
dut.expect_none('register dump:')
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation')
if config == 'gdbstub':
common_test(
dut,
config,
expected_backtrace=[
# Backtrace interrupted when abort is called, IDF-842
'panic_abort',
'esp_system_abort',
],
)
else:
common_test(dut, config)
@pytest.mark.parametrize('config', CONFIGS, indirect=True)
@pytest.mark.generic
def test_int_wdt(
dut: PanicTestDut, target: str, config: str, test_func_name: str
) -> None:
dut.expect_test_func_name(test_func_name)
dut.expect_gme('Interrupt wdt timeout on CPU0')
dut.expect_reg_dump(0)
dut.expect_backtrace()
if target == 'esp32s2':
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation')
if target != 'esp32s2': # esp32s2 is single-core
dut.expect_reg_dump(1)
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation')
common_test(dut, config, expected_backtrace=get_default_backtrace(test_func_name))
@pytest.mark.parametrize('config', CONFIGS, indirect=True)
@pytest.mark.generic
def test_int_wdt_cache_disabled(
dut: PanicTestDut, target: str, config: str, test_func_name: str
) -> None:
dut.expect_test_func_name(test_func_name)
dut.expect_gme('Interrupt wdt timeout on CPU0')
dut.expect_reg_dump(0)
dut.expect_backtrace()
if target == 'esp32s2':
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation')
if target != 'esp32s2': # esp32s2 is single-core
dut.expect_reg_dump(1)
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation')
common_test(dut, config, expected_backtrace=get_default_backtrace(test_func_name))
@pytest.mark.parametrize('config', CONFIGS, indirect=True)
@pytest.mark.xfail('config.getvalue("target") == "esp32s2"', reason='raised IllegalInstruction instead')
@pytest.mark.generic
def test_cache_error(dut: PanicTestDut, config: str, test_func_name: str) -> None:
dut.expect_test_func_name(test_func_name)
dut.expect_gme('Cache disabled but cached memory region accessed')
dut.expect_reg_dump(0)
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation')
common_test(
dut, config, expected_backtrace=['die'] + get_default_backtrace(test_func_name)
)
@pytest.mark.parametrize('config', CONFIGS, indirect=True)
@pytest.mark.generic
def test_stack_overflow(dut: PanicTestDut, config: str, test_func_name: str) -> None:
dut.expect_test_func_name(test_func_name)
dut.expect_gme('Unhandled debug exception')
dut.expect_exact('Stack canary watchpoint triggered (main)')
dut.expect_reg_dump(0)
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation')
common_test(dut, config, expected_backtrace=get_default_backtrace(test_func_name))
@pytest.mark.parametrize('config', CONFIGS, indirect=True)
@pytest.mark.generic
def test_instr_fetch_prohibited(
dut: PanicTestDut, config: str, test_func_name: str
) -> None:
dut.expect_test_func_name(test_func_name)
dut.expect_gme('InstrFetchProhibited')
dut.expect_reg_dump(0)
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation')
common_test(
dut,
config,
expected_backtrace=['_init'] + get_default_backtrace(test_func_name),
)
@pytest.mark.parametrize('config', CONFIGS, indirect=True)
@pytest.mark.generic
def test_illegal_instruction(
dut: PanicTestDut, config: str, test_func_name: str
) -> None:
dut.expect_test_func_name(test_func_name)
dut.expect_gme('IllegalInstruction')
dut.expect_reg_dump(0)
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation')
common_test(dut, config, expected_backtrace=get_default_backtrace(test_func_name))
@pytest.mark.parametrize('config', CONFIGS, indirect=True)
@pytest.mark.generic
def test_storeprohibited(dut: PanicTestDut, config: str, test_func_name: str) -> None:
dut.expect_test_func_name(test_func_name)
dut.expect_gme('StoreProhibited')
dut.expect_reg_dump(0)
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation')
common_test(dut, config, expected_backtrace=get_default_backtrace(test_func_name))
@pytest.mark.parametrize('config', CONFIGS, indirect=True)
@pytest.mark.generic
def test_abort(dut: PanicTestDut, config: str, test_func_name: str) -> None:
dut.expect_test_func_name(test_func_name)
dut.expect(r'abort\(\) was called at PC [0-9xa-f]+ on core 0')
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
if config == 'gdbstub':
common_test(
dut,
config,
expected_backtrace=[
# Backtrace interrupted when abort is called, IDF-842
'panic_abort',
'esp_system_abort',
],
)
else:
common_test(dut, config)
@pytest.mark.parametrize('config', CONFIGS, indirect=True)
@pytest.mark.generic
def test_ub(dut: PanicTestDut, config: str, test_func_name: str) -> None:
dut.expect_test_func_name(test_func_name)
dut.expect('Undefined behavior of type out_of_bounds')
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
if config == 'gdbstub':
common_test(
dut,
config,
expected_backtrace=[
# Backtrace interrupted when abort is called, IDF-842
'panic_abort',
'esp_system_abort',
],
)
else:
common_test(dut, config)
#########################
# for config panic only #
#########################
@pytest.mark.esp32
@pytest.mark.esp32s2
@pytest.mark.xfail('config.getvalue("target") == "esp32s2"', reason='raised IllegalInstruction instead')
@pytest.mark.parametrize('config', ['panic'], indirect=True)
@pytest.mark.generic
def test_abort_cache_disabled(
dut: PanicTestDut, config: str, test_func_name: str
) -> None:
dut.expect_test_func_name(test_func_name)
dut.expect(r'abort\(\) was called at PC [0-9xa-f]+ on core 0')
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
common_test(dut, config, expected_backtrace=get_default_backtrace(test_func_name))
@pytest.mark.esp32
@pytest.mark.esp32s2
@pytest.mark.parametrize('config', ['panic'], indirect=True)
@pytest.mark.generic
def test_assert(dut: PanicTestDut, config: str, test_func_name: str) -> None:
dut.expect_test_func_name(test_func_name)
dut.expect(
re.compile(
rb'assert failed:[\s\w()]*?\s[.\w/]*\.(?:c|cpp|h|hpp):\d.*$', re.MULTILINE
)
)
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
common_test(dut, config, expected_backtrace=get_default_backtrace(test_func_name))
@pytest.mark.esp32
@pytest.mark.esp32s2
@pytest.mark.xfail('config.getvalue("target") == "esp32s2"', reason='raised IllegalInstruction instead')
@pytest.mark.parametrize('config', ['panic'], indirect=True)
@pytest.mark.generic
def test_assert_cache_disabled(
dut: PanicTestDut, config: str, test_func_name: str
) -> None:
dut.expect_test_func_name(test_func_name)
dut.expect(re.compile(rb'assert failed: [0-9xa-fA-F]+.*$', re.MULTILINE))
dut.expect_backtrace()
dut.expect_elf_sha256()
dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
common_test(dut, config, expected_backtrace=get_default_backtrace(test_func_name))

View File

@ -1,319 +0,0 @@
import logging
import os
import re
import subprocess
import sys
import ttfw_idf
from pygdbmi.gdbcontroller import GdbController, GdbTimeoutError, NoGdbProcessError
from tiny_test_fw import DUT, TinyFW, Utility
from tiny_test_fw.Utility import CaseConfig, SearchCases
# hard-coded to the path one level above - only intended to be used from the panic test app
TEST_PATH = os.path.relpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'), os.getenv('IDF_PATH'))
TEST_SUITE = 'Panic'
def ok(data):
""" Helper function used with dut.expect_any """
pass
def unexpected(data):
""" Helper function used with dut.expect_any """
raise AssertionError('Unexpected: {}'.format(data))
class PanicTestApp(ttfw_idf.TestApp):
pass
class PanicTestMixin(object):
""" Provides custom functionality for the panic test DUT """
BOOT_CMD_ADDR = 0x9000
BOOT_CMD_SIZE = 0x1000
DEFAULT_EXPECT_TIMEOUT = 10
COREDUMP_UART_START = '================= CORE DUMP START ================='
COREDUMP_UART_END = '================= CORE DUMP END ================='
def start_test(self, test_name):
""" Starts the app and sends it the test name """
self.test_name = test_name
# Start the app and verify that it has started up correctly
self.start_capture_raw_data()
self.start_app()
self.expect('Enter test name: ')
Utility.console_log('Setting boot command: ' + test_name)
self.write(test_name)
self.expect('Got test name: ' + test_name)
def expect_none(self, *patterns, **timeout_args):
""" like dut.expect_all, but with an inverse logic """
found_data = []
if 'timeout' not in timeout_args:
timeout_args['timeout'] = 1
def found(data):
raise AssertionError('Unexpected: {}'.format(data))
found_data.append(data)
try:
expect_items = [(pattern, found) for pattern in patterns]
self.expect_any(*expect_items, **timeout_args)
raise AssertionError('Unexpected: {}'.format(found_data))
except DUT.ExpectTimeout:
return True
def expect_gme(self, reason):
""" Expect method for Guru Meditation Errors """
self.expect(r"Guru Meditation Error: Core 0 panic'ed (%s)" % reason)
def expect_reg_dump(self, core=0):
""" Expect method for the register dump """
self.expect(re.compile(r'Core\s+%d register dump:' % core))
def expect_elf_sha256(self):
""" Expect method for ELF SHA256 line """
elf_sha256 = self.app.get_elf_sha256()
sdkconfig = self.app.get_sdkconfig()
elf_sha256_len = int(sdkconfig.get('CONFIG_APP_RETRIEVE_LEN_ELF_SHA', '16'))
self.expect('ELF file SHA256: ' + elf_sha256[0:elf_sha256_len])
def expect_backtrace(self):
self.expect('Backtrace:')
self.expect_none('CORRUPTED')
def __enter__(self):
self._raw_data = None
self.gdb = None
return self
def __exit__(self, type, value, traceback):
log_folder = self.app.get_log_folder(TEST_SUITE)
with open(os.path.join(log_folder, 'log_' + self.test_name + '.txt'), 'w') as log_file:
Utility.console_log('Writing output of {} to {}'.format(self.test_name, log_file.name))
log_file.write(self.get_raw_data())
if self.gdb:
self.gdb.exit()
self.close()
def get_raw_data(self):
if not self._raw_data:
self._raw_data = self.stop_capture_raw_data()
return self._raw_data
def _call_espcoredump(self, extra_args, coredump_file_name, output_file_name):
# no "with" here, since we need the file to be open for later inspection by the test case
self.coredump_output = open(output_file_name, 'w')
espcoredump_script = os.path.join(os.environ['IDF_PATH'], 'components', 'espcoredump', 'espcoredump.py')
espcoredump_args = [
sys.executable,
espcoredump_script,
'info_corefile',
'--core', coredump_file_name,
]
espcoredump_args += extra_args
espcoredump_args.append(self.app.elf_file)
Utility.console_log('Running ' + ' '.join(espcoredump_args))
Utility.console_log('espcoredump output is written to ' + self.coredump_output.name)
subprocess.check_call(espcoredump_args, stdout=self.coredump_output)
self.coredump_output.flush()
self.coredump_output.seek(0)
def process_coredump_uart(self):
""" Extract the core dump from UART output of the test, run espcoredump on it """
log_folder = self.app.get_log_folder(TEST_SUITE)
data = self.get_raw_data()
coredump_start = data.find(self.COREDUMP_UART_START)
coredump_end = data.find(self.COREDUMP_UART_END)
coredump_base64 = data[coredump_start + len(self.COREDUMP_UART_START):coredump_end]
with open(os.path.join(log_folder, 'coredump_data_' + self.test_name + '.b64'), 'w') as coredump_file:
Utility.console_log('Writing UART base64 core dump to ' + coredump_file.name)
coredump_file.write(coredump_base64)
output_file_name = os.path.join(log_folder, 'coredump_uart_result_' + self.test_name + '.txt')
self._call_espcoredump(['--core-format', 'b64'], coredump_file.name, output_file_name)
def process_coredump_flash(self):
""" Extract the core dump from flash, run espcoredump on it """
log_folder = self.app.get_log_folder(TEST_SUITE)
coredump_file_name = os.path.join(log_folder, 'coredump_data_' + self.test_name + '.bin')
Utility.console_log('Writing flash binary core dump to ' + coredump_file_name)
self.dump_flash(coredump_file_name, partition='coredump')
output_file_name = os.path.join(log_folder, 'coredump_flash_result_' + self.test_name + '.txt')
self._call_espcoredump(['--core-format', 'raw'], coredump_file_name, output_file_name)
def _gdb_write(self, command):
"""
Wrapper to write to gdb with a longer timeout, as test runner
host can be slow sometimes
"""
return self.gdb.write(command, timeout_sec=10)
def start_gdb(self):
"""
Runs GDB and connects it to the "serial" port of the DUT.
After this, the DUT expect methods can no longer be used to capture output.
"""
self.stop_receive()
self._port_close()
Utility.console_log('Starting GDB...', 'orange')
self.gdb = GdbController(gdb_path=self.TOOLCHAIN_PREFIX + 'gdb')
Utility.console_log('Running command: {}'.format(self.gdb.get_subprocess_cmd()), 'orange')
for _ in range(10):
try:
# GdbController creates a process with subprocess.Popen(). Is it really running? It is probable that
# an RPI under high load will get non-responsive during creating a lot of processes.
resp = self.gdb.get_gdb_response(timeout_sec=10) # calls verify_valid_gdb_subprocess() internally
# it will be interesting to look up this response if the next GDB command fails (times out)
Utility.console_log('GDB response: {}'.format(resp), 'orange')
break # success
except GdbTimeoutError:
Utility.console_log('GDB internal error: cannot get response from the subprocess', 'orange')
except NoGdbProcessError:
Utility.console_log('GDB internal error: process is not running', 'red')
break # failure - TODO: create another GdbController
except ValueError:
Utility.console_log('GDB internal error: select() returned an unexpected file number', 'red')
# pygdbmi logs to console by default, make it log to a file instead
log_folder = self.app.get_log_folder(TEST_SUITE)
pygdbmi_log_file_name = os.path.join(log_folder, 'pygdbmi_log_' + self.test_name + '.txt')
pygdbmi_logger = self.gdb.logger
pygdbmi_logger.setLevel(logging.DEBUG)
while pygdbmi_logger.hasHandlers():
pygdbmi_logger.removeHandler(pygdbmi_logger.handlers[0])
log_handler = logging.FileHandler(pygdbmi_log_file_name)
log_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s'))
pygdbmi_logger.addHandler(log_handler)
# Set up logging for GDB remote protocol
gdb_remotelog_file_name = os.path.join(log_folder, 'gdb_remote_log_' + self.test_name + '.txt')
self._gdb_write('-gdb-set remotelogfile ' + gdb_remotelog_file_name)
# Load the ELF file
self._gdb_write('-file-exec-and-symbols {}'.format(self.app.elf_file))
# Connect GDB to UART
Utility.console_log('Connecting to GDB Stub...', 'orange')
self._gdb_write('-gdb-set serial baud 115200')
responses = self._gdb_write('-target-select remote ' + self.get_gdb_remote())
# Make sure we get the 'stopped' notification
stop_response = self.find_gdb_response('stopped', 'notify', responses)
if not stop_response:
responses = self._gdb_write('-exec-interrupt')
stop_response = self.find_gdb_response('stopped', 'notify', responses)
assert stop_response
frame = stop_response['payload']['frame']
if 'file' not in frame:
frame['file'] = '?'
if 'line' not in frame:
frame['line'] = '?'
Utility.console_log('Stopped in {func} at {addr} ({file}:{line})'.format(**frame), 'orange')
# Drain remaining responses
self.gdb.get_gdb_response(raise_error_on_timeout=False)
def gdb_backtrace(self):
"""
Returns the list of stack frames for the current thread.
Each frame is a dictionary, refer to pygdbmi docs for the format.
"""
assert self.gdb
responses = self._gdb_write('-stack-list-frames')
return self.find_gdb_response('done', 'result', responses)['payload']['stack']
@staticmethod
def match_backtrace(gdb_backtrace, expected_functions_list):
"""
Returns True if the function names listed in expected_functions_list match the backtrace
given by gdb_backtrace argument. The latter is in the same format as returned by gdb_backtrace()
function.
"""
return all([frame['func'] == expected_functions_list[i] for i, frame in enumerate(gdb_backtrace)])
@staticmethod
def find_gdb_response(message, response_type, responses):
"""
Helper function which extracts one response from an array of GDB responses, filtering
by message and type. Returned message is a dictionary, refer to pygdbmi docs for the format.
"""
def match_response(response):
return (response['message'] == message and
response['type'] == response_type)
filtered_responses = [r for r in responses if match_response(r)]
if not filtered_responses:
return None
return filtered_responses[0]
class ESP32PanicTestDUT(ttfw_idf.ESP32DUT, PanicTestMixin):
def get_gdb_remote(self):
return self.port
class ESP32S2PanicTestDUT(ttfw_idf.ESP32S2DUT, PanicTestMixin):
def get_gdb_remote(self):
return self.port
PANIC_TEST_DUT_DICT = {
'ESP32': ESP32PanicTestDUT,
'ESP32S2': ESP32S2PanicTestDUT
}
def panic_test(**kwargs):
""" Decorator for the panic tests, sets correct App and DUT classes """
if 'target' not in kwargs:
kwargs['target'] = ['ESP32']
if 'additional_duts' not in kwargs:
kwargs['additional_duts'] = PANIC_TEST_DUT_DICT
return ttfw_idf.idf_custom_test(app=PanicTestApp, env_tag='Example_GENERIC', **kwargs)
def get_dut(env, app_config_name, test_name, qemu_wdt_enable=False):
dut = env.get_dut('panic', TEST_PATH, app_config_name=app_config_name, allow_dut_exception=True)
dut.qemu_wdt_enable = qemu_wdt_enable
""" Wrapper for getting the DUT and starting the test """
dut.start_test(test_name)
return dut
def run_all(filename, case_filter=[]):
""" Helper function to run test cases defined in a file; to be called from __main__.
case_filter is an optional list of case names to run.
If not specified, all test cases are run.
"""
TinyFW.set_default_config(env_config_file=None, test_suite_name=TEST_SUITE)
test_methods = SearchCases.Search.search_test_cases(filename)
test_methods = filter(lambda m: not m.case_info['ignore'], test_methods)
test_cases = CaseConfig.Parser.apply_config(test_methods, None)
tests_failed = []
for case in test_cases:
test_name = case.test_method.__name__
if case_filter:
if case_filter[0].endswith('*'):
if not test_name.startswith(case_filter[0][:-1]):
continue
else:
if test_name not in case_filter:
continue
result = case.run()
if not result:
tests_failed.append(case)
if tests_failed:
print('The following tests have failed:')
for case in tests_failed:
print(' - ' + case.test_method.__name__)
raise SystemExit(1)
print('Tests pass')