mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-28 01:04:48 +03:00
ci: apply idf-ci 1.x
(cherry picked from commit 21d772a24da646bfb672418cf056199cad9b17e4)
This commit is contained in:
+45
-1
@@ -270,7 +270,11 @@ variables:
|
||||
- source tools/ci/utils.sh
|
||||
- source tools/ci/configure_ci_environment.sh
|
||||
- *show_ccache_statistics
|
||||
- *upload_failed_job_log_artifacts
|
||||
- run_cmd idf-ci gitlab upload-artifacts --type junit
|
||||
- |
|
||||
if [ $CI_JOB_STATUS = "failed" ]; then
|
||||
run_cmd idf-ci gitlab upload-artifacts --type log
|
||||
fi
|
||||
|
||||
.brew-macos-settings:
|
||||
variables:
|
||||
@@ -283,6 +287,46 @@ variables:
|
||||
after_script: [] # ccache now is disabled for macos brew runners
|
||||
timeout: 30m
|
||||
|
||||
.brew-windows-settings:
|
||||
image: windows_11_idf_v6_0
|
||||
tags: [windows-vm, brew]
|
||||
variables:
|
||||
GIT_STRATEGY: fetch
|
||||
LC_ALL: C.UTF-8
|
||||
# git clean at the final cleanup stage stuck on Windows VM
|
||||
# a new VM for each job anyway, so we can skip cleaning the workspace
|
||||
GIT_CLEAN_FLAGS: none
|
||||
# IDF_CCACHE_ENABLE: "1"
|
||||
# CCACHE_DIR: "C:/cache/idf_ccache"
|
||||
before_script:
|
||||
- if ($env:IDF_DONT_USE_MIRRORS) { $env:IDF_MIRROR_PREFIX_MAP = '' }
|
||||
- ./install.ps1 --enable-ci
|
||||
- ./export.ps1
|
||||
- $env:PYTHONPATH = "$env:PYTHONPATH;$env:IDF_PATH\tools;$env:IDF_PATH\tools\esp_app_trace;$env:IDF_PATH\components\partition_table;$env:IDF_PATH\tools\ci\python_packages"
|
||||
- python "${SUBMODULE_FETCH_TOOL}" -s "${SUBMODULES_TO_FETCH}"
|
||||
# Sync VM clock. net start/stop exit 2 when the service is already in that state; do not fail CI.
|
||||
- |
|
||||
function Invoke-NetService($Action, $Name) {
|
||||
$output = net $Action $Name 2>&1
|
||||
if ($LASTEXITCODE -eq 2) {
|
||||
Write-Host "net $Action $Name already in requested state: $output"
|
||||
$global:LASTEXITCODE = 0
|
||||
return
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host $output
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
}
|
||||
Invoke-NetService start w32time
|
||||
w32tm /config /manualpeerlist:"time.google.com,0x9 pool.ntp.org,0x9" /syncfromflags:manual /reliable:yes /update
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
Invoke-NetService stop w32time
|
||||
Invoke-NetService start w32time
|
||||
w32tm /resync
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
after_script: []
|
||||
|
||||
#############
|
||||
# `default` #
|
||||
#############
|
||||
|
||||
@@ -373,7 +373,6 @@ test_pytest_qemu:
|
||||
- run_cmd pytest
|
||||
--target $IDF_TARGET
|
||||
-m qemu
|
||||
--embedded-services idf,qemu
|
||||
--junitxml=XUNIT_RESULT.xml
|
||||
--ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
--qemu-extra-args \"-global driver=timer.$IDF_TARGET.timg,property=wdt_disable,value=true\"
|
||||
@@ -398,7 +397,7 @@ test_pytest_linux:
|
||||
- run_cmd idf-ci gitlab download-known-failure-cases-file ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
- run_cmd pytest
|
||||
--target linux
|
||||
--embedded-services idf
|
||||
-m \"not macos\"
|
||||
--junitxml=XUNIT_RESULT.xml
|
||||
--ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
- "tools/ci/check_esp_memory_utils_headers.sh"
|
||||
- "tools/ci/check_blobs.sh"
|
||||
- "tools/ci/check_public_headers.py"
|
||||
- "tools/ci/check_public_headers_exceptions.txt"
|
||||
- "tools/ci/check_register_rw_half_word.cmake"
|
||||
- "tools/ci/check_register_rw_half_word.py"
|
||||
- "examples/build_system/**/*"
|
||||
@@ -102,6 +103,7 @@
|
||||
- "tools/idf_tools.py"
|
||||
- "tools/test_idf_tools/**/*"
|
||||
- "tools/install_util.py"
|
||||
- "tools/idf_extra_components.yml"
|
||||
|
||||
- "tools/export_utils/utils.py"
|
||||
- "tools/export_utils/shell_types.py"
|
||||
|
||||
+23
-67
@@ -1,9 +1,8 @@
|
||||
# Host tests
|
||||
.host_test_win_template:
|
||||
extends: .rules:test:host_test
|
||||
extends:
|
||||
- .brew-windows-settings
|
||||
- .rules:test:host_test
|
||||
stage: host_test
|
||||
image: $ESP_ENV_IMAGE
|
||||
tags: [windows-build, brew]
|
||||
dependencies: # set dependencies to null to avoid missing artifacts issue
|
||||
# run host_test jobs immediately, only after upload cache
|
||||
needs:
|
||||
@@ -13,13 +12,8 @@
|
||||
- job: upload-submodules-cache
|
||||
optional: true
|
||||
artifacts: false
|
||||
variables:
|
||||
GIT_STRATEGY: fetch # use brew local mirror first
|
||||
before_script:
|
||||
- if ($env:IDF_DONT_USE_MIRRORS) {
|
||||
$env:IDF_MIRROR_PREFIX_MAP = ""
|
||||
}
|
||||
after_script: []
|
||||
- job: manual_gate
|
||||
optional: true
|
||||
|
||||
test_cli_installer_win:
|
||||
rules:
|
||||
@@ -34,15 +28,12 @@ test_cli_installer_win:
|
||||
- tools/tools.new.json
|
||||
- tools/test_idf_tools/test_python_env_logs.txt
|
||||
expire_in: 1 week
|
||||
variables:
|
||||
IDF_PATH: "$CI_PROJECT_DIR"
|
||||
timeout: 3h
|
||||
script:
|
||||
# Tools must be downloaded for testing
|
||||
# cmake@version that is supported
|
||||
- python ${IDF_PATH}\tools\idf_tools.py download required qemu-riscv32 qemu-xtensa cmake cmake@3.22.1
|
||||
- cd ${IDF_PATH}\tools\test_idf_tools
|
||||
- python -m pip install jsonschema
|
||||
- python tools\idf_tools.py download required qemu-riscv32 qemu-xtensa cmake cmake@3.22.1
|
||||
- cd tools\test_idf_tools
|
||||
- python .\test_idf_tools.py
|
||||
- python .\test_idf_tools_python_env.py
|
||||
|
||||
@@ -53,81 +44,47 @@ test_tools_win:
|
||||
parallel: 4
|
||||
artifacts:
|
||||
paths:
|
||||
- ${IDF_PATH}/*.out
|
||||
- ${IDF_PATH}/XUNIT_*.xml
|
||||
- "*.out"
|
||||
- "XUNIT_*.xml"
|
||||
reports:
|
||||
junit: ${IDF_PATH}/XUNIT_*.xml
|
||||
junit: "XUNIT_*.xml"
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
variables:
|
||||
LC_ALL: C.UTF-8
|
||||
PYTHONPATH: "$PYTHONPATH;$IDF_PATH\\tools;$IDF_PATH\\tools\\esp_app_trace;$IDF_PATH\\components\\partition_table;$IDF_PATH\\tools\\ci\\python_packages"
|
||||
script:
|
||||
- python -m pip install jsonschema
|
||||
- .\install.ps1 --enable-ci
|
||||
- .\export.ps1
|
||||
- python "${SUBMODULE_FETCH_TOOL}" -s "all"
|
||||
- cd ${IDF_PATH}/tools/test_idf_py
|
||||
- cd tools\test_idf_py
|
||||
- idf-ci gitlab download-known-failure-cases-file ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
- pytest --parallel-count ${CI_NODE_TOTAL} --parallel-index ${CI_NODE_INDEX} --junitxml=${IDF_PATH}/XUNIT_RESULT.xml --ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
- pytest --parallel-count ${CI_NODE_TOTAL} --parallel-index ${CI_NODE_INDEX} --junitxml ${CI_PROJECT_DIR}\XUNIT_RESULT.xml --ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
|
||||
# Build tests
|
||||
.test_build_system_template_win:
|
||||
stage: host_test
|
||||
variables:
|
||||
# Enable ccache for all build jobs. See configure_ci_environment.sh for more ccache related settings.
|
||||
IDF_CCACHE_ENABLE: "1"
|
||||
PYTHONPATH: "$PYTHONPATH;$IDF_PATH\\tools;$IDF_PATH\\tools\\esp_app_trace;$IDF_PATH\\components\\partition_table;$IDF_PATH\\tools\\ci\\python_packages"
|
||||
before_script: []
|
||||
after_script: []
|
||||
extends: .host_test_win_template
|
||||
timeout: 4 hours
|
||||
artifacts:
|
||||
paths:
|
||||
- XUNIT_RESULT.xml
|
||||
- test_build_system
|
||||
expire_in: 2 days
|
||||
when: always
|
||||
reports:
|
||||
junit: XUNIT_RESULT.xml
|
||||
script:
|
||||
- .\install.ps1 --enable-ci
|
||||
- . .\export.ps1
|
||||
- python "${SUBMODULE_FETCH_TOOL}" -s "all"
|
||||
- cd ${IDF_PATH}\tools\test_build_system
|
||||
- cd tools\test_build_system
|
||||
- idf-ci gitlab download-known-failure-cases-file ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
- pytest --parallel-count ${CI_NODE_TOTAL} --parallel-index ${CI_NODE_INDEX} --junitxml=${CI_PROJECT_DIR}\XUNIT_RESULT.xml --ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
- pytest --parallel-count ${CI_NODE_TOTAL} --parallel-index ${CI_NODE_INDEX} --junitxml ${CI_PROJECT_DIR}\XUNIT_RESULT.xml --ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
|
||||
pytest_build_system_win:
|
||||
extends:
|
||||
- .test_build_system_template_win
|
||||
- .rules:labels:windows_pytest_build_system
|
||||
parallel: 2
|
||||
needs:
|
||||
- job: manual_gate
|
||||
optional: true
|
||||
tags: [windows-build, brew]
|
||||
artifacts:
|
||||
paths:
|
||||
- XUNIT_RESULT.xml
|
||||
- test_build_system
|
||||
expire_in: 2 days
|
||||
reports:
|
||||
junit: XUNIT_RESULT.xml
|
||||
when: always
|
||||
|
||||
pytest_build_system_win_minimal_cmake:
|
||||
extends:
|
||||
- .test_build_system_template_win
|
||||
- .rules:labels:windows_pytest_build_system
|
||||
needs:
|
||||
- job: manual_gate
|
||||
optional: true
|
||||
tags: [windows-build, brew]
|
||||
artifacts:
|
||||
paths:
|
||||
- XUNIT_RESULT.xml
|
||||
- test_build_system
|
||||
expire_in: 2 days
|
||||
reports:
|
||||
junit: XUNIT_RESULT.xml
|
||||
when: always
|
||||
variables:
|
||||
MINIMAL_CMAKE_VERSION: "3.16.3"
|
||||
script:
|
||||
- .\install.ps1 --enable-ci
|
||||
- . .\export.ps1
|
||||
- python ${IDF_PATH}\tools\idf_tools.py install cmake@${MINIMAL_CMAKE_VERSION}
|
||||
- $Env:PATH = "$Env:USERPROFILE\.espressif\tools\cmake\${MINIMAL_CMAKE_VERSION}\bin;$Env:PATH"
|
||||
- |
|
||||
@@ -136,7 +93,6 @@ pytest_build_system_win_minimal_cmake:
|
||||
Write-Error "ERROR: Wrong CMake version! Detected: $actualVersion, but expected: $Env:MINIMAL_CMAKE_VERSION"
|
||||
exit 1
|
||||
}
|
||||
- python "${SUBMODULE_FETCH_TOOL}" -s "all"
|
||||
- cd ${IDF_PATH}\tools\test_build_system
|
||||
- idf-ci gitlab download-known-failure-cases-file ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
- pytest -k cmake --junitxml=${CI_PROJECT_DIR}\XUNIT_RESULT.xml --ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
|
||||
@@ -31,8 +31,8 @@ additional_build_targets = [
|
||||
]
|
||||
|
||||
# collect
|
||||
collect_app_info_filename = "app_info_${CI_JOB_NAME_SLUG}.txt"
|
||||
junitxml = "build_summary_${CI_JOB_NAME_SLUG}.xml"
|
||||
collect_app_info_filename = "app_info_${CI_JOB_ID}.txt"
|
||||
junitxml = "build_summary_${CI_JOB_ID}.xml"
|
||||
|
||||
# manifest
|
||||
check_manifest_rules = true
|
||||
|
||||
+33
-24
@@ -1,4 +1,6 @@
|
||||
preserve_non_test_related_apps = false
|
||||
filter_non_test_related_apps_by_modified_files = true
|
||||
filter_apps_by_component_target = true
|
||||
exclude_dirs = [
|
||||
'tools/test_mkdfu',
|
||||
'tools/test_idf_size',
|
||||
@@ -20,7 +22,7 @@ IDF_CI_BUILD = "1"
|
||||
|
||||
[gitlab.build_pipeline]
|
||||
workflow_name = "build_child_pipeline"
|
||||
presigned_json_job_name = 'generate_pytest_build_report'
|
||||
runs_per_job = 30
|
||||
|
||||
job_tags = ['build', 'shiny']
|
||||
job_template_name = '.dynamic_build_template'
|
||||
@@ -51,37 +53,44 @@ include:
|
||||
- tools/ci/dynamic_pipelines/templates/.dynamic_jobs.yml
|
||||
"""
|
||||
|
||||
[gitlab.artifacts.s3.debug]
|
||||
[gitlab.artifacts.s3]
|
||||
enable = true
|
||||
|
||||
[gitlab.artifacts.s3.configs.debug]
|
||||
bucket = "idf-artifacts"
|
||||
zip_first = true
|
||||
build_dir_pattern = "**/build*/"
|
||||
patterns = [
|
||||
'**/build*/bootloader/*.map',
|
||||
'**/build*/bootloader/*.elf',
|
||||
'**/build*/*.map',
|
||||
'**/build*/*.elf',
|
||||
'bootloader/*.map',
|
||||
'bootloader/*.elf',
|
||||
'*.map',
|
||||
'*.elf',
|
||||
# customized
|
||||
'**/build*/esp_tee/*.map',
|
||||
'**/build*/esp_tee/*.elf',
|
||||
'**/build*/gdbinit/*',
|
||||
'esp_tee/*.map',
|
||||
'esp_tee/*.elf',
|
||||
'gdbinit/*',
|
||||
]
|
||||
if_clause = 'CI_JOB_GROUP_NAME != "build_non_test_related_apps"'
|
||||
|
||||
[gitlab.artifacts.s3.flash]
|
||||
[gitlab.artifacts.s3.configs.flash]
|
||||
bucket = "idf-artifacts"
|
||||
zip_first = true
|
||||
build_dir_pattern = "**/build*/"
|
||||
patterns = [
|
||||
'**/build*/bootloader/*.bin',
|
||||
'**/build*/*.bin',
|
||||
'**/build*/partition_table/*.bin',
|
||||
'**/build*/flasher_args.json',
|
||||
'**/build*/flash_project_args',
|
||||
'**/build*/config/sdkconfig.json',
|
||||
'**/build*/sdkconfig',
|
||||
'**/build*/project_description.json',
|
||||
'bootloader/*.bin',
|
||||
'*.bin',
|
||||
'partition_table/*.bin',
|
||||
'flasher_args.json',
|
||||
'flash_project_args',
|
||||
'config/sdkconfig.json',
|
||||
'sdkconfig',
|
||||
'project_description.json',
|
||||
# customized
|
||||
'**/build*/esp_tee/*.bin',
|
||||
'esp_tee/*.bin',
|
||||
]
|
||||
if_clause = 'CI_JOB_GROUP_NAME != "build_non_test_related_apps"'
|
||||
|
||||
[gitlab.artifacts.s3.app_extra]
|
||||
[gitlab.artifacts.s3.configs.app_extra]
|
||||
bucket = "idf-artifacts"
|
||||
# App-dir files needed at target-test time but outside build_* (uploaded without --build-dir).
|
||||
# Add patterns here as needed, e.g. coverage info produced during build.
|
||||
@@ -90,27 +99,27 @@ patterns = [
|
||||
]
|
||||
if_clause = 'CI_JOB_GROUP_NAME != "build_non_test_related_apps"'
|
||||
|
||||
[gitlab.artifacts.s3.log]
|
||||
[gitlab.artifacts.s3.configs.log]
|
||||
bucket = "idf-artifacts"
|
||||
patterns = [
|
||||
'**/build*/build_log.txt',
|
||||
'**/build*/size*.json',
|
||||
]
|
||||
|
||||
[gitlab.artifacts.s3.junit]
|
||||
[gitlab.artifacts.s3.configs.junit]
|
||||
bucket = "idf-artifacts"
|
||||
patterns = [
|
||||
'**/XUNIT_RESULT_*.xml',
|
||||
'**/build_summary_*.xml',
|
||||
]
|
||||
|
||||
[gitlab.artifacts.s3.env]
|
||||
[gitlab.artifacts.s3.configs.env]
|
||||
bucket = "idf-artifacts"
|
||||
patterns = [
|
||||
'**/pipeline.env',
|
||||
]
|
||||
|
||||
[gitlab.artifacts.s3.longterm]
|
||||
[gitlab.artifacts.s3.configs.longterm]
|
||||
bucket = "longterm"
|
||||
if_clause = 'CI_COMMIT_REF_NAME == "master"'
|
||||
patterns = [
|
||||
|
||||
@@ -229,7 +229,7 @@ repos:
|
||||
- id: check-kconfig-files
|
||||
- id: check-deprecated-kconfig-options
|
||||
- repo: https://github.com/espressif/idf-ci
|
||||
rev: v1.0.0b4
|
||||
rev: v1.2.1
|
||||
hooks:
|
||||
- id: check-tests-missing-config
|
||||
files: 'pytest_.*\.py$|sdkconfig(\..*)?$'
|
||||
|
||||
-2
@@ -11,14 +11,12 @@ def test_bootloader_support(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.qemu
|
||||
@idf_parametrize('target', ['esp32'], indirect=['target'])
|
||||
def test_bootloader_support_qemu_esp32(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.qemu
|
||||
@idf_parametrize('target', ['esp32c3'], indirect=['target'])
|
||||
def test_bootloader_support_qemu_esp32c3(dut: Dut) -> None:
|
||||
|
||||
@@ -12,6 +12,11 @@ import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
# Add scripts/ to sys.path so test modules can import the production code
|
||||
SCRIPTS_DIR = Path(__file__).resolve().parent.parent / 'scripts'
|
||||
@@ -61,11 +66,11 @@ def make_compressor() -> Any:
|
||||
|
||||
def write_yaml_config(
|
||||
tmp_dir: str,
|
||||
tags: list[str],
|
||||
script_path: str | Path,
|
||||
tags: List[str],
|
||||
script_path: Union[str, Path],
|
||||
module_name: str = 'BLE_HOST',
|
||||
log_index_file: str = 'test_log_index.h',
|
||||
tags_with_preserve: list[str] | None = None,
|
||||
tags_with_preserve: Optional[List[str]] = None,
|
||||
) -> Path:
|
||||
"""Write a module_info.yml and return its path."""
|
||||
import yaml
|
||||
@@ -140,9 +145,8 @@ class PipelineContext:
|
||||
shutil.copy2(src, dst)
|
||||
return str(rel_path)
|
||||
|
||||
def run_compression(self, src_list: list[str]) -> dict[str, list[tuple[int, str]]]:
|
||||
"""Run mirror + prepare + compress + header generation. Returns generated macros."""
|
||||
self.compressor.mirror_local_includes()
|
||||
def run_compression(self, src_list: List[str]) -> Dict[str, List[Tuple[int, str]]]:
|
||||
"""Run prepare + compress + header generation. Returns generated macros."""
|
||||
self.compressor.prepare_source_files(src_list)
|
||||
|
||||
files_to_process = []
|
||||
@@ -150,7 +154,7 @@ class PipelineContext:
|
||||
files_to_process.extend([(module, path) for path in info['files_to_process']])
|
||||
files_to_process.sort(key=lambda x: x[1])
|
||||
|
||||
all_macros: dict[str, list[tuple[int, str]]] = {}
|
||||
all_macros: Dict[str, List[Tuple[int, str]]] = {}
|
||||
for file_info in files_to_process:
|
||||
file_macros = self.compressor.compress_file(file_info)
|
||||
for module, log_id, macro in file_macros:
|
||||
@@ -186,8 +190,8 @@ class PipelineContext:
|
||||
|
||||
def assert_header_matches_golden(
|
||||
test_case: unittest.TestCase,
|
||||
generated_path: str | Path,
|
||||
golden_path: str | Path,
|
||||
generated_path: Union[str, Path],
|
||||
golden_path: Union[str, Path],
|
||||
) -> None:
|
||||
"""Compare generated header to golden file, normalizing copyright year."""
|
||||
gen_text = Path(generated_path).read_text()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
@@ -50,63 +50,59 @@ def do_test_help_quit(dut: Dut) -> None:
|
||||
dut.expect(r'quit\s+Quit REPL environment\s+esp>')
|
||||
|
||||
|
||||
@idf_parametrize('config', ['defaults'], indirect=['config'])
|
||||
@idf_parametrize(
|
||||
'target,test_on,markers',
|
||||
'target,config,embedded_services,markers',
|
||||
[
|
||||
('linux', 'host', (pytest.mark.host_test,)),
|
||||
('esp32', 'target', (pytest.mark.generic,)),
|
||||
('esp32c3', 'target', (pytest.mark.generic,)),
|
||||
('esp32', 'qemu', (pytest.mark.host_test, pytest.mark.qemu)),
|
||||
('linux', 'defaults', 'idf', ()),
|
||||
('esp32', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32c3', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32', 'defaults', 'idf,qemu', (pytest.mark.qemu,)),
|
||||
],
|
||||
indirect=['target'],
|
||||
indirect=['target', 'config', 'embedded_services'],
|
||||
)
|
||||
def test_console(dut: Dut, test_on: str) -> None:
|
||||
def test_console(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases(group='!ignore', timeout=120)
|
||||
|
||||
|
||||
@idf_parametrize('config', ['defaults'], indirect=['config'])
|
||||
@idf_parametrize(
|
||||
'target,test_on,markers',
|
||||
'target,config,embedded_services,markers',
|
||||
[
|
||||
('linux', 'host', (pytest.mark.host_test,)),
|
||||
('esp32', 'target', (pytest.mark.generic,)),
|
||||
('esp32c3', 'target', (pytest.mark.generic,)),
|
||||
('esp32', 'qemu', (pytest.mark.host_test, pytest.mark.qemu)),
|
||||
('linux', 'defaults', 'idf', ()),
|
||||
('esp32', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32c3', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32', 'defaults', 'idf,qemu', (pytest.mark.qemu,)),
|
||||
],
|
||||
indirect=['target'],
|
||||
indirect=['target', 'config', 'embedded_services'],
|
||||
)
|
||||
def test_console_repl(dut: Dut, test_on: str) -> None:
|
||||
def test_console_repl(dut: Dut) -> None:
|
||||
do_test_quit(dut)
|
||||
|
||||
|
||||
@idf_parametrize('config', ['defaults'], indirect=['config'])
|
||||
@idf_parametrize(
|
||||
'target,test_on,markers',
|
||||
'target,config,embedded_services,markers',
|
||||
[
|
||||
('linux', 'host', (pytest.mark.host_test,)),
|
||||
('esp32', 'target', (pytest.mark.generic,)),
|
||||
('esp32c3', 'target', (pytest.mark.generic,)),
|
||||
('esp32', 'qemu', (pytest.mark.host_test, pytest.mark.qemu)),
|
||||
('linux', 'defaults', 'idf', ()),
|
||||
('esp32', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32c3', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32', 'defaults', 'idf,qemu', (pytest.mark.qemu,)),
|
||||
],
|
||||
indirect=['target'],
|
||||
indirect=['target', 'config', 'embedded_services'],
|
||||
)
|
||||
def test_console_help_sorted_registration(dut: Dut, test_on: str) -> None:
|
||||
def test_console_help_sorted_registration(dut: Dut) -> None:
|
||||
do_test_help_generic(dut, 'sorted')
|
||||
|
||||
|
||||
@idf_parametrize('config', ['defaults'], indirect=['config'])
|
||||
@idf_parametrize(
|
||||
'target,test_on,markers',
|
||||
'target,config,embedded_services,markers',
|
||||
[
|
||||
('linux', 'host', (pytest.mark.host_test,)),
|
||||
('esp32', 'target', (pytest.mark.generic,)),
|
||||
('esp32c3', 'target', (pytest.mark.generic,)),
|
||||
('esp32', 'qemu', (pytest.mark.host_test, pytest.mark.qemu)),
|
||||
('linux', 'defaults', 'idf', ()),
|
||||
('esp32', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32c3', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32', 'defaults', 'idf,qemu', (pytest.mark.qemu,)),
|
||||
],
|
||||
indirect=['target'],
|
||||
indirect=['target', 'config', 'embedded_services'],
|
||||
)
|
||||
def test_console_help_reverse_registration(dut: Dut, test_on: str) -> None:
|
||||
def test_console_help_reverse_registration(dut: Dut) -> None:
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.confirm_write('"esp console help command - reverse registration"', expect_str='esp>')
|
||||
|
||||
@@ -131,45 +127,41 @@ def test_console_help_reverse_registration(dut: Dut, test_on: str) -> None:
|
||||
|
||||
@idf_parametrize('config', ['sorted'], indirect=['config'])
|
||||
@idf_parametrize('target', ['linux'], indirect=['target'])
|
||||
@idf_parametrize('test_on,markers', [('host', (pytest.mark.host_test,))])
|
||||
def test_console_sorted_help_sorted_registration(dut: Dut, test_on: str) -> None:
|
||||
def test_console_sorted_help_sorted_registration(dut: Dut) -> None:
|
||||
do_test_help_generic(dut, 'sorted')
|
||||
|
||||
|
||||
@idf_parametrize('config', ['sorted'], indirect=['config'])
|
||||
@idf_parametrize('target', ['linux'], indirect=['target'])
|
||||
@idf_parametrize('test_on,markers', [('host', (pytest.mark.host_test,))])
|
||||
def test_console_sorted_help_reverse_registration(dut: Dut, test_on: str) -> None:
|
||||
def test_console_sorted_help_reverse_registration(dut: Dut) -> None:
|
||||
do_test_help_generic(dut, 'reverse')
|
||||
|
||||
|
||||
@idf_parametrize('config', ['defaults'], indirect=['config'])
|
||||
@idf_parametrize(
|
||||
'target,test_on,markers',
|
||||
'target,config,embedded_services,markers',
|
||||
[
|
||||
('linux', 'host', (pytest.mark.host_test,)),
|
||||
('esp32', 'target', (pytest.mark.generic,)),
|
||||
('esp32c3', 'target', (pytest.mark.generic,)),
|
||||
('esp32', 'qemu', (pytest.mark.host_test, pytest.mark.qemu)),
|
||||
('linux', 'defaults', 'idf', ()),
|
||||
('esp32', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32c3', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32', 'defaults', 'idf,qemu', (pytest.mark.qemu,)),
|
||||
],
|
||||
indirect=['target'],
|
||||
indirect=['target', 'config', 'embedded_services'],
|
||||
)
|
||||
def test_console_help_quit(dut: Dut, test_on: str) -> None:
|
||||
def test_console_help_quit(dut: Dut) -> None:
|
||||
do_test_help_quit(dut)
|
||||
|
||||
|
||||
@idf_parametrize('config', ['defaults'], indirect=['config'])
|
||||
@idf_parametrize(
|
||||
'target,test_on,markers',
|
||||
'target,config,embedded_services,markers',
|
||||
[
|
||||
('linux', 'host', (pytest.mark.host_test,)),
|
||||
('esp32', 'target', (pytest.mark.generic,)),
|
||||
('esp32c3', 'target', (pytest.mark.generic,)),
|
||||
('esp32', 'qemu', (pytest.mark.host_test, pytest.mark.qemu)),
|
||||
('linux', 'defaults', 'idf', ()),
|
||||
('esp32', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32c3', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32', 'defaults', 'idf,qemu', (pytest.mark.qemu,)),
|
||||
],
|
||||
indirect=['target'],
|
||||
indirect=['target', 'config', 'embedded_services'],
|
||||
)
|
||||
def test_console_help_verbose_level_0(dut: Dut, test_on: str) -> None:
|
||||
def test_console_help_verbose_level_0(dut: Dut) -> None:
|
||||
help_verbose_info = 'Print the summary of all registered commands if no arguments are given,'
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.confirm_write('"esp console help command - set verbose level = 0"', expect_str='esp>')
|
||||
@@ -180,18 +172,17 @@ def test_console_help_verbose_level_0(dut: Dut, test_on: str) -> None:
|
||||
dut.expect_exact('help', not_matching=help_verbose_info)
|
||||
|
||||
|
||||
@idf_parametrize('config', ['defaults'], indirect=['config'])
|
||||
@idf_parametrize(
|
||||
'target,test_on,markers',
|
||||
'target,config,embedded_services,markers',
|
||||
[
|
||||
('linux', 'host', (pytest.mark.host_test,)),
|
||||
('esp32', 'target', (pytest.mark.generic,)),
|
||||
('esp32c3', 'target', (pytest.mark.generic,)),
|
||||
('esp32', 'qemu', (pytest.mark.host_test, pytest.mark.qemu)),
|
||||
('linux', 'defaults', 'idf', ()),
|
||||
('esp32', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32c3', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32', 'defaults', 'idf,qemu', (pytest.mark.qemu,)),
|
||||
],
|
||||
indirect=['target'],
|
||||
indirect=['target', 'config', 'embedded_services'],
|
||||
)
|
||||
def test_console_help_verbose_level_1(dut: Dut, test_on: str) -> None:
|
||||
def test_console_help_verbose_level_1(dut: Dut) -> None:
|
||||
help_verbose_info = 'Print the summary of all registered commands if no arguments are given,'
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.confirm_write('"esp console help command - set verbose level = 1"', expect_str='esp>')
|
||||
@@ -201,18 +192,17 @@ def test_console_help_verbose_level_1(dut: Dut, test_on: str) -> None:
|
||||
dut.expect_exact(help_verbose_info)
|
||||
|
||||
|
||||
@idf_parametrize('config', ['defaults'], indirect=['config'])
|
||||
@idf_parametrize(
|
||||
'target,test_on,markers',
|
||||
'target,config,embedded_services,markers',
|
||||
[
|
||||
('linux', 'host', (pytest.mark.host_test,)),
|
||||
('esp32', 'target', (pytest.mark.generic,)),
|
||||
('esp32c3', 'target', (pytest.mark.generic,)),
|
||||
('esp32', 'qemu', (pytest.mark.host_test, pytest.mark.qemu)),
|
||||
('linux', 'defaults', 'idf', ()),
|
||||
('esp32', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32c3', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32', 'defaults', 'idf,qemu', (pytest.mark.qemu,)),
|
||||
],
|
||||
indirect=['target'],
|
||||
indirect=['target', 'config', 'embedded_services'],
|
||||
)
|
||||
def test_console_help_verbose_subcommand(dut: Dut, test_on: str) -> None:
|
||||
def test_console_help_verbose_subcommand(dut: Dut) -> None:
|
||||
help_verbose_info = 'Print the summary of all registered commands if no arguments are given,'
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.confirm_write('"esp console help command - --verbose sub command"', expect_str='esp>')
|
||||
@@ -227,18 +217,17 @@ def test_console_help_verbose_subcommand(dut: Dut, test_on: str) -> None:
|
||||
dut.expect_exact(help_verbose_info)
|
||||
|
||||
|
||||
@idf_parametrize('config', ['defaults'], indirect=['config'])
|
||||
@idf_parametrize(
|
||||
'target,test_on,markers',
|
||||
'target,config,embedded_services,markers',
|
||||
[
|
||||
('linux', 'host', (pytest.mark.host_test,)),
|
||||
('esp32', 'target', (pytest.mark.generic,)),
|
||||
('esp32c3', 'target', (pytest.mark.generic,)),
|
||||
('esp32', 'qemu', (pytest.mark.host_test, pytest.mark.qemu)),
|
||||
('linux', 'defaults', 'idf', ()),
|
||||
('esp32', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32c3', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32', 'defaults', 'idf,qemu', (pytest.mark.qemu,)),
|
||||
],
|
||||
indirect=['target'],
|
||||
indirect=['target', 'config', 'embedded_services'],
|
||||
)
|
||||
def test_console_help_deregister(dut: Dut, test_on: str) -> None:
|
||||
def test_console_help_deregister(dut: Dut) -> None:
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.confirm_write('"esp console deregister commands"', expect_str='esp>')
|
||||
|
||||
@@ -250,18 +239,17 @@ def test_console_help_deregister(dut: Dut, test_on: str) -> None:
|
||||
dut.expect_exact(cmd_z_description, not_matching=cmd_a_description)
|
||||
|
||||
|
||||
@idf_parametrize('config', ['defaults'], indirect=['config'])
|
||||
@idf_parametrize(
|
||||
'target,test_on,markers',
|
||||
'target,config,embedded_services,markers',
|
||||
[
|
||||
('linux', 'host', (pytest.mark.host_test,)),
|
||||
('esp32', 'target', (pytest.mark.generic,)),
|
||||
('esp32c3', 'target', (pytest.mark.generic,)),
|
||||
('esp32', 'qemu', (pytest.mark.host_test, pytest.mark.qemu)),
|
||||
('linux', 'defaults', 'idf', ()),
|
||||
('esp32', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32c3', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32', 'defaults', 'idf,qemu', (pytest.mark.qemu,)),
|
||||
],
|
||||
indirect=['target'],
|
||||
indirect=['target', 'config', 'embedded_services'],
|
||||
)
|
||||
def test_console_help_re_register(dut: Dut, test_on: str) -> None:
|
||||
def test_console_help_re_register(dut: Dut) -> None:
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.confirm_write('"esp console re-register commands"', expect_str='esp>')
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ def test_efuse(dut: Dut) -> None:
|
||||
|
||||
|
||||
@pytest.mark.qemu
|
||||
@pytest.mark.host_test
|
||||
@idf_parametrize('target', ['esp32'], indirect=['target'])
|
||||
def test_efuse_qemu(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
|
||||
@@ -19,7 +19,6 @@ def test_esp_event(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.qemu
|
||||
@pytest.mark.xfail('config.getvalue("target") == "esp32c3"', reason='Unstable on QEMU, needs investigation')
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -24,7 +24,6 @@ def test_esp_ringbuf(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.qemu
|
||||
@pytest.mark.parametrize(
|
||||
'target',
|
||||
|
||||
@@ -15,7 +15,6 @@ def test_esp_rom(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.qemu
|
||||
@idf_parametrize('target', ['esp32c3'], indirect=['target'])
|
||||
def test_esp_rom_qemu(dut: Dut) -> None:
|
||||
|
||||
@@ -20,7 +20,6 @@ def test_heap_poisoning(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.qemu
|
||||
@pytest.mark.parametrize(
|
||||
'config, embedded_services',
|
||||
|
||||
@@ -46,7 +46,6 @@ def test_pthread_single_core_tls(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases(group='thread-specific', timeout=300)
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.qemu
|
||||
@idf_parametrize('target', ['esp32'], indirect=['target'])
|
||||
def test_pthread_qemu(dut: Dut) -> None:
|
||||
|
||||
+24
-11
@@ -44,6 +44,7 @@ from idf_pytest.plugin import IDF_LOCAL_PLUGIN_KEY
|
||||
from idf_pytest.plugin import IdfLocalPlugin
|
||||
from idf_pytest.plugin import requires_elf_or_map
|
||||
from idf_pytest.utils import format_case_id
|
||||
from pytest_embedded.plugin import _request_param_or_config_option_or_default
|
||||
from pytest_embedded.plugin import multi_dut_fixture
|
||||
from pytest_embedded.utils import to_bytes
|
||||
from pytest_embedded.utils import to_str
|
||||
@@ -127,7 +128,7 @@ class AppDownloader:
|
||||
self.commit_sha = commit_sha
|
||||
self.pipeline_id = pipeline_id
|
||||
|
||||
def download_app(self, app_build_path: str, artifact_type: t.Optional[str] = None) -> None:
|
||||
def download_app(self, app_dir: str, build_dir: str, artifact_type: t.Optional[str] = None) -> None:
|
||||
args = [
|
||||
'idf-ci',
|
||||
'gitlab',
|
||||
@@ -137,15 +138,26 @@ class AppDownloader:
|
||||
]
|
||||
if artifact_type:
|
||||
args.extend(['--type', artifact_type])
|
||||
|
||||
if self.pipeline_id:
|
||||
args.extend(['--pipeline-id', self.pipeline_id])
|
||||
args.append(app_build_path)
|
||||
|
||||
subprocess.run(
|
||||
args,
|
||||
stdout=sys.stdout,
|
||||
stderr=sys.stderr,
|
||||
args.extend(
|
||||
[
|
||||
app_dir,
|
||||
'--build-dir',
|
||||
build_dir,
|
||||
]
|
||||
)
|
||||
result = subprocess.run(
|
||||
args,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=IDF_PATH,
|
||||
)
|
||||
logging.info(result.stdout)
|
||||
if result.stderr:
|
||||
logging.info(result.stderr)
|
||||
|
||||
def download_app_extra(self, app_dir: str) -> None:
|
||||
"""Download app-dir artifacts defined under app_extra in .idf_ci.toml."""
|
||||
@@ -348,12 +360,13 @@ def build_dir(
|
||||
downloader = app_downloader
|
||||
|
||||
if downloader:
|
||||
app_dir = idf_relpath(app_path)
|
||||
build_dir = f'build_{target}_{config}'
|
||||
# somehow hardcoded...
|
||||
app_build_path = os.path.join(idf_relpath(app_path), f'build_{target}_{config}')
|
||||
if requires_elf_or_map(case):
|
||||
downloader.download_app(app_build_path)
|
||||
downloader.download_app(app_dir, build_dir)
|
||||
else:
|
||||
downloader.download_app(app_build_path, 'flash')
|
||||
downloader.download_app(app_dir, build_dir, 'flash')
|
||||
check_dirs = [f'build_{target}_{config}']
|
||||
else:
|
||||
check_dirs = []
|
||||
@@ -537,12 +550,12 @@ def log_minimum_free_heap_size(dut: IdfDut, config: str, idf_path: str) -> t.Cal
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def dev_password(request: FixtureRequest) -> str:
|
||||
return request.config.getoption('dev_passwd') or ''
|
||||
return _request_param_or_config_option_or_default(request, 'dev_password', '') # type: ignore
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def dev_user(request: FixtureRequest) -> str:
|
||||
return request.config.getoption('dev_user') or ''
|
||||
return _request_param_or_config_option_or_default(request, 'dev_user', '') # type: ignore
|
||||
|
||||
|
||||
##################
|
||||
|
||||
@@ -45,6 +45,8 @@ Inside the ``IDF_TOOLS_PATH`` directory, the tools installation scripts create t
|
||||
- ``python_env`` — not related to the tools; virtual Python environments are installed in the sub-directories. Note that the Python environment directory can be placed elsewhere by setting the ``IDF_PYTHON_ENV_PATH`` environment variable.
|
||||
|
||||
- ``idf_version.txt`` — located within each specific Python environment sub-directory under ``python_env``, this file records the ESP-IDF version corresponding to that environment. The version is stored in a format like ``5.3`` to represent ESP-IDF version ``v5.3``.
|
||||
|
||||
- ``root_managed_components`` — directory managed by ``idf-component-manager`` for components installed globally.
|
||||
- ``espidf.constraints.*.txt`` — one constraint file for each ESP-IDF release containing Python package version requirements.
|
||||
|
||||
GitHub Assets Mirror
|
||||
|
||||
@@ -120,6 +120,52 @@ Next, we have the environment marker:
|
||||
|
||||
Finally, we have the test function. With a ``dut`` fixture. In single-dut test cases, the ``dut`` fixture is an instance of ``IdfDut`` class, for multi-dut test cases, it is a tuple of ``IdfDut`` instances. For more details regarding the ``IdfDut`` class, please refer to `pytest-embedded IdfDut API reference <https://docs.espressif.com/projects/pytest-embedded/en/latest/api.html#pytest_embedded_idf.dut.IdfDut>`__.
|
||||
|
||||
Running Tests on Linux
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To execute a pytest case on the Linux host, set ``target`` to ``linux``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@idf_parametrize('target', ['linux'], indirect=['target'])
|
||||
def test_hello_world_linux(dut) -> None:
|
||||
dut.expect('Hello world!')
|
||||
|
||||
This is the simplest way to run the same test flow on the Linux host instead of on physical hardware.
|
||||
|
||||
For a simple Linux-only test, setting ``target`` to ``linux`` is enough and the ``idf`` embedded services will be selected automatically. The ``pytest.mark.host_test`` marker is not required.
|
||||
|
||||
For a mixed environment matrix, specify ``embedded_services`` manually for each case. See :ref:`Same App With Different Running Environments <pytest-same-app-different-running-environments>` for a more complex example.
|
||||
|
||||
.. only:: TARGET_SUPPORT_QEMU
|
||||
|
||||
Running Tests in QEMU
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To execute a pytest case in QEMU, add the ``@pytest.mark.qemu`` marker to the test function.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.qemu
|
||||
@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target'])
|
||||
def test_hello_world_qemu(dut) -> None:
|
||||
dut.expect('Hello world!')
|
||||
|
||||
This is the simplest way to run the same test flow in QEMU instead of on physical hardware.
|
||||
|
||||
For a simple QEMU-only test, adding ``pytest.mark.qemu`` is enough and the ``idf,qemu`` embedded services will be selected automatically.
|
||||
|
||||
For a mixed environment matrix, specify ``embedded_services`` manually for each case. See the later section in this guide for a more complex example.
|
||||
|
||||
For QEMU installation and setup, refer to page :doc:`/api-guides/tools/qemu`.
|
||||
|
||||
Deprecation of ``pytest.mark.host_test``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``pytest.mark.host_test`` is no longer needed and should not be added to new test cases.
|
||||
|
||||
For Linux target test cases and QEMU test cases, the required behavior is handled dynamically by the test framework. In particular, the embedded services are selected automatically for simple Linux-only and QEMU-only cases.
|
||||
|
||||
Same App With Different sdkconfig Files
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -208,6 +254,49 @@ Now this test function would be replicated to 2 test cases (represented as test
|
||||
* ``esp32.foo.test_foo_bar``
|
||||
* ``esp32s2.bar.test_foo_bar``
|
||||
|
||||
.. _pytest-same-app-different-running-environments:
|
||||
|
||||
Same App With Different Running Environments
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Sometimes the same app should be validated in different running environments, for example on the host with the Linux target, on real hardware, or in QEMU. If a single ``@pytest.mark.qemu`` test is not enough, combine ``target``, ``config``, and ``embedded_services`` in a single ``idf_parametrize`` decorator, and attach the required marker for each case.
|
||||
|
||||
The following example is adapted from :idf_file:`components/console/test_apps/console/pytest_console.py`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@idf_parametrize(
|
||||
'target,config,embedded_services,markers',
|
||||
[
|
||||
('linux', 'defaults', 'idf', ()),
|
||||
('esp32', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32c3', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32', 'defaults', 'idf,qemu', (pytest.mark.qemu,)),
|
||||
],
|
||||
indirect=['target', 'config', 'embedded_services'],
|
||||
)
|
||||
def test_console_repl(dut) -> None:
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
|
||||
This creates four test cases for the same app:
|
||||
|
||||
* Linux host execution with the ``idf`` service
|
||||
* ESP32 hardware execution with the ``esp,idf`` services
|
||||
* ESP32-C3 hardware execution with the ``esp,idf`` services
|
||||
* ESP32 execution in QEMU with the ``idf,qemu`` services
|
||||
|
||||
When running locally, you can select only the environment you want:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ pytest --target linux
|
||||
$ pytest -m qemu
|
||||
$ pytest -m qemu --target esp32
|
||||
|
||||
``pytest --target linux`` selects Linux target cases only. ``pytest -m qemu`` selects all QEMU-marked cases. ``pytest -m qemu --target esp32`` further limits the selection to QEMU cases for the ESP32 target.
|
||||
|
||||
Use this pattern when the test logic is the same but the execution environment changes.
|
||||
|
||||
Testing Serial Output (Expecting)
|
||||
---------------------------------
|
||||
|
||||
|
||||
@@ -120,6 +120,52 @@ ESP-IDF 在主机端使用 pytest 框架(以及一些 pytest 插件)来自
|
||||
|
||||
关于测试函数,使用了一个 ``dut`` fixture。在单一 DUT 测试用例中,``dut`` fixture 是 ``IdfDut`` 类的一个实例,对于多个 DUT 测试用例,它是 ``IdfDut`` 实例的一个元组。有关 ``IdfDut`` 类的更多详细信息,请参阅 `pytest-embedded IdfDut API 参考 <https://docs.espressif.com/projects/pytest-embedded/en/latest/api.html#pytest_embedded_idf.dut.IdfDut>`__。
|
||||
|
||||
在 Linux 上运行测试
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
要在 Linux 主机上执行 pytest 测试用例,请将 ``target`` 设置为 ``linux``。
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@idf_parametrize('target', ['linux'], indirect=['target'])
|
||||
def test_hello_world_linux(dut) -> None:
|
||||
dut.expect('Hello world!')
|
||||
|
||||
这是在 Linux 主机上运行与物理硬件相同测试流程的最简单方式。
|
||||
|
||||
对于简单的纯 Linux 测试,只需将 ``target`` 设置为 ``linux``,系统会自动选择 ``idf`` 对应的 embedded services。``pytest.mark.host_test`` marker 不再需要。
|
||||
|
||||
对于混合运行环境矩阵,则需要为每个用例手动指定 ``embedded_services``。更复杂的示例请参阅 :ref:`在不同运行环境中运行相同的应用程序 <pytest-same-app-different-running-environments>` 小节。
|
||||
|
||||
.. only:: TARGET_SUPPORT_QEMU
|
||||
|
||||
在 QEMU 中运行测试
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
要在 QEMU 中执行 pytest 测试用例,请将 ``@pytest.mark.qemu`` 添加到测试函数上。
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.qemu
|
||||
@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target'])
|
||||
def test_hello_world_qemu(dut) -> None:
|
||||
dut.expect('Hello world!')
|
||||
|
||||
这是在 QEMU 中运行与物理硬件相同测试流程的最简单方式。
|
||||
|
||||
对于简单的纯 QEMU 测试,只需添加 ``pytest.mark.qemu``,系统会自动选择 ``idf,qemu`` 对应的 embedded services。
|
||||
|
||||
对于混合运行环境矩阵,则需要为每个用例手动指定 ``embedded_services``。更复杂的示例请参阅本指南后面的对应小节。
|
||||
|
||||
有关 QEMU 的安装和配置,请参阅页面 :doc:`../api-guides/tools/qemu`。
|
||||
|
||||
``pytest.mark.host_test`` 的弃用说明
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``pytest.mark.host_test`` 已不再需要,也不应再添加到新的测试用例中。
|
||||
|
||||
对于 Linux target 测试用例和 QEMU 测试用例,相关行为会由测试框架动态处理。尤其是在简单的纯 Linux 或纯 QEMU 场景下,embedded services 会被自动选择。
|
||||
|
||||
使用不同的 sdkconfig 文件运行相同的应用程序
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -208,6 +254,49 @@ ESP-IDF 在主机端使用 pytest 框架(以及一些 pytest 插件)来自
|
||||
* ``esp32.foo.test_foo_bar``
|
||||
* ``esp32s2.bar.test_foo_bar``
|
||||
|
||||
.. _pytest-same-app-different-running-environments:
|
||||
|
||||
在不同运行环境中运行相同的应用程序
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
有时,同一个应用程序需要在不同的运行环境中进行验证,例如在 Linux target 的主机上、真实硬件上,或在 QEMU 中运行。如果单独使用 ``@pytest.mark.qemu`` 测试还不够,可以在一个 ``idf_parametrize`` 装饰器中组合 ``target``、``config`` 和 ``embedded_services``,并为每种情况附加所需的 marker。
|
||||
|
||||
下面的示例改编自 :idf_file:`components/console/test_apps/console/pytest_console.py`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@idf_parametrize(
|
||||
'target,config,embedded_services,markers',
|
||||
[
|
||||
('linux', 'defaults', 'idf', ()),
|
||||
('esp32', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32c3', 'defaults', 'esp,idf', (pytest.mark.generic,)),
|
||||
('esp32', 'defaults', 'idf,qemu', (pytest.mark.qemu,)),
|
||||
],
|
||||
indirect=['target', 'config', 'embedded_services'],
|
||||
)
|
||||
def test_console_repl(dut) -> None:
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
|
||||
这会为同一个应用程序生成 4 个测试用例:
|
||||
|
||||
* 在 Linux 主机上使用 ``idf`` service 运行
|
||||
* 在 ESP32 硬件上使用 ``esp,idf`` services 运行
|
||||
* 在 ESP32-C3 硬件上使用 ``esp,idf`` services 运行
|
||||
* 在 QEMU 中以 ESP32 为目标,使用 ``idf,qemu`` services 运行
|
||||
|
||||
在本地运行时,可以按需只选择某一种运行环境:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ pytest --target linux
|
||||
$ pytest -m qemu
|
||||
$ pytest -m qemu --target esp32
|
||||
|
||||
``pytest --target linux`` 只选择 Linux target 的测试用例。``pytest -m qemu`` 选择所有带有 QEMU marker 的测试用例。``pytest -m qemu --target esp32`` 会进一步把范围限制为目标芯片为 ESP32 的 QEMU 测试用例。
|
||||
|
||||
当测试逻辑相同,但执行环境不同的时候,可使用此模式。
|
||||
|
||||
测试串行输出
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ from pytest_embedded_idf.utils import idf_parametrize
|
||||
from pytest_embedded_qemu.dut import QemuDut
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.qemu
|
||||
@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target'])
|
||||
def test_pytest_host(dut: QemuDut) -> None:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
import hashlib
|
||||
import logging
|
||||
from typing import Callable
|
||||
from collections.abc import Callable
|
||||
|
||||
import pytest
|
||||
from pytest_embedded_idf.dut import IdfDut
|
||||
@@ -45,7 +45,6 @@ def verify_elf_sha256_embedding(app: QemuApp, sha256_reported: str) -> None:
|
||||
raise ValueError('ELF file SHA256 mismatch')
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.qemu
|
||||
@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target'])
|
||||
def test_hello_world_host(app: QemuApp, dut: QemuDut) -> None:
|
||||
|
||||
@@ -29,6 +29,8 @@ from pytest_embedded_idf.utils import idf_parametrize
|
||||
# Testbed configuration
|
||||
|
||||
ETHVM_ENDNODE_USER = 'ci.ethvm'
|
||||
ETHERNET_TEST_USER = os.getenv('ETHERNET_TEST_USER')
|
||||
ETHERNET_TEST_PASSWORD = os.getenv('ETHERNET_TEST_PASSWORD')
|
||||
|
||||
BR_PORTS_NUM = 2
|
||||
IPERF_BW_LIM = 6
|
||||
@@ -636,5 +638,10 @@ def setup_test_environment() -> Generator[None, None, None]:
|
||||
indirect=True,
|
||||
)
|
||||
@idf_parametrize('target', ['esp32'], indirect=['target'])
|
||||
@pytest.mark.parametrize(
|
||||
'dev_user, dev_password',
|
||||
[(ETHERNET_TEST_USER, ETHERNET_TEST_PASSWORD)],
|
||||
indirect=True,
|
||||
)
|
||||
def test_esp_eth_bridge(dut: Dut, dev_user: str, dev_password: str) -> None:
|
||||
eth_bridge_test(dut, dev_user, dev_password)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import logging
|
||||
import os
|
||||
@@ -52,7 +52,6 @@ def test_examples_protocol_https_x509_bundle_dynamic_buffer(dut: Dut) -> None:
|
||||
|
||||
|
||||
@pytest.mark.qemu
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
|
||||
@@ -900,7 +900,6 @@ def test_examples_efuse_with_virt_sb_v2_and_fe(dut: Dut) -> None:
|
||||
dut.expect('example: Done')
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.qemu
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
|
||||
@@ -1110,7 +1110,6 @@ def test_examples_protocol_advanced_https_ota_example_openssl_aligned_bin(dut: D
|
||||
|
||||
@pytest.mark.qemu
|
||||
@pytest.mark.nightly_run
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.parametrize(
|
||||
'qemu_extra_args',
|
||||
[
|
||||
@@ -1166,7 +1165,6 @@ def test_examples_protocol_advanced_https_ota_example_verify_min_chip_revision(d
|
||||
|
||||
@pytest.mark.qemu
|
||||
@pytest.mark.nightly_run
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.parametrize(
|
||||
'qemu_extra_args',
|
||||
[
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# Checks all public headers in IDF in the ci
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
import argparse
|
||||
@@ -309,6 +309,10 @@ class PublicHeaderChecker:
|
||||
idf_path = os.getenv('IDF_PATH')
|
||||
if idf_path is None:
|
||||
raise RuntimeError("Environment variable 'IDF_PATH' wasn't set.")
|
||||
|
||||
idf_tools_path = os.getenv('IDF_TOOLS_PATH') or os.path.expanduser(os.path.join('~', '.espressif'))
|
||||
idf_root_dep_path = os.path.join(idf_tools_path, 'root_managed_components')
|
||||
|
||||
project_dir = os.path.join(idf_path, 'examples', 'get-started', 'blink')
|
||||
sdkconfig = os.path.join(self.build_dir, 'sdkconfig')
|
||||
if self.libc_type == 'picolibc':
|
||||
@@ -371,7 +375,11 @@ class PublicHeaderChecker:
|
||||
if os.path.relpath(d, idf_path).startswith(tuple(ignore_dirs)):
|
||||
self.log('{} - directory ignored'.format(d))
|
||||
continue
|
||||
for root, dirnames, filenames in os.walk(d):
|
||||
for root, _, filenames in os.walk(d):
|
||||
if root.startswith(idf_root_dep_path):
|
||||
self.log(f'{root} - directory ignored (inside IDF_TOOLS_PATH/root_managed_components)')
|
||||
continue
|
||||
|
||||
for filename in fnmatch.filter(filenames, '*.h'):
|
||||
all_include_files.append(os.path.join(root, filename))
|
||||
self.main_c = main_c
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
# set while generating the pipeline
|
||||
nodes: ""
|
||||
INSTALL_EXTRA_TOOLS: "xtensa-esp-elf-gdb riscv32-esp-elf-gdb openocd-esp32 esp-rom-elfs"
|
||||
PYTEST_EXTRA_FLAGS: "--dev-passwd ${ETHERNET_TEST_PASSWORD} --dev-user ${ETHERNET_TEST_USER} --capture=fd --verbosity=0 --unity-test-report-mode merge"
|
||||
PYTEST_EXTRA_FLAGS: "--capture=fd --verbosity=0 --unity-test-report-mode merge"
|
||||
needs:
|
||||
- pipeline: $PARENT_PIPELINE_ID
|
||||
job: pipeline_variables
|
||||
@@ -77,7 +77,6 @@
|
||||
- run_cmd python $CHECKOUT_REF_SCRIPT ci-test-runner-configs ci-test-runner-configs
|
||||
# CI specific options start from "--known-failure-cases-file xxx". could ignore when running locally
|
||||
- run_cmd pytest $nodes
|
||||
--pipeline-id $PARENT_PIPELINE_ID
|
||||
--junitxml=XUNIT_RESULT_${CI_JOB_ID}.xml
|
||||
--ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
--parallel-count ${CI_NODE_TOTAL:-1}
|
||||
|
||||
@@ -4,17 +4,16 @@ import os
|
||||
import subprocess
|
||||
import sys
|
||||
import typing as t
|
||||
from pathlib import Path
|
||||
|
||||
from dynamic_pipelines.constants import BINARY_SIZE_METRIC_NAME
|
||||
from idf_build_apps import App
|
||||
from idf_build_apps import CMakeApp
|
||||
from idf_build_apps.constants import BuildStatus
|
||||
from idf_build_apps.utils import rmdir
|
||||
from idf_ci_utils import APP_EXTRA_S3_ARTIFACT_TYPE
|
||||
from idf_ci_utils import idf_relpath
|
||||
from metrics.size_metrics import collect_build_metrics
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
pass
|
||||
_SIZE_METRICS_CONFIG_PATH = Path(__file__).parent.parent / 'metrics' / 'size_metrics' / 'size_metrics_config.yml'
|
||||
|
||||
|
||||
class IdfCMakeApp(CMakeApp):
|
||||
@@ -37,6 +36,8 @@ class IdfCMakeApp(CMakeApp):
|
||||
'gitlab',
|
||||
'upload-artifacts',
|
||||
self.app_dir,
|
||||
'--build-dir',
|
||||
self.build_dir,
|
||||
],
|
||||
[
|
||||
'idf-ci',
|
||||
@@ -63,103 +64,3 @@ class IdfCMakeApp(CMakeApp):
|
||||
self.build_path,
|
||||
exclude_file_patterns=['build_log.txt', 'size*.json'],
|
||||
)
|
||||
|
||||
|
||||
class Metrics:
|
||||
"""
|
||||
Represents a metric and its values for source, target, and the differences.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
source_value: t.Optional[float] = None,
|
||||
target_value: t.Optional[float] = None,
|
||||
difference: t.Optional[float] = None,
|
||||
difference_percentage: t.Optional[float] = None,
|
||||
) -> None:
|
||||
self.source_value = source_value or 0.0
|
||||
self.target_value = target_value or 0.0
|
||||
self.difference = difference or 0.0
|
||||
self.difference_percentage = difference_percentage or 0.0
|
||||
|
||||
def to_dict(self) -> t.Dict[str, t.Any]:
|
||||
"""
|
||||
Converts the Metrics object to a dictionary.
|
||||
"""
|
||||
return {
|
||||
'source_value': self.source_value,
|
||||
'target_value': self.target_value,
|
||||
'difference': self.difference,
|
||||
'difference_percentage': self.difference_percentage,
|
||||
}
|
||||
|
||||
|
||||
class AppWithMetricsInfo(IdfCMakeApp):
|
||||
metrics: t.Dict[str, Metrics]
|
||||
is_new_app: bool
|
||||
|
||||
def __init__(self, **kwargs: t.Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.metrics = {metric_name: metric_data for metric_name, metric_data in kwargs.get('metrics', {}).items()}
|
||||
self.is_new_app = kwargs.get('is_new_app', False)
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
|
||||
def enrich_apps_with_metrics_info(
|
||||
app_metrics_info_map: t.Dict[str, t.Dict[str, t.Any]], apps: t.List[App]
|
||||
) -> t.List[AppWithMetricsInfo]:
|
||||
def _get_full_attributes(obj: App) -> t.Dict[str, t.Any]:
|
||||
"""
|
||||
Retrieves all attributes of an object, including properties and computed fields.
|
||||
"""
|
||||
attributes: t.Dict[str, t.Any] = obj.__dict__.copy()
|
||||
for attr in dir(obj):
|
||||
if not attr.startswith('_'): # Skip private/internal attributes
|
||||
try:
|
||||
value = getattr(obj, attr)
|
||||
# Include only if it's not already in __dict__
|
||||
if attr not in attributes:
|
||||
attributes[attr] = value
|
||||
except Exception:
|
||||
# Skip attributes that raise exceptions (e.g., methods needing args)
|
||||
pass
|
||||
return attributes
|
||||
|
||||
default_metrics_structure = {
|
||||
BINARY_SIZE_METRIC_NAME: Metrics(
|
||||
source_value=0,
|
||||
target_value=0,
|
||||
difference=0,
|
||||
difference_percentage=0.0,
|
||||
),
|
||||
}
|
||||
|
||||
apps_with_metrics_info = []
|
||||
for app in apps:
|
||||
app.app_dir = idf_relpath(app.app_dir)
|
||||
key = f'{app.app_dir}_{app.config_name}_{app.target}'
|
||||
app_attributes = _get_full_attributes(app)
|
||||
|
||||
metrics = {metric_name: default_metric for metric_name, default_metric in default_metrics_structure.items()}
|
||||
is_new_app = False
|
||||
|
||||
if key in app_metrics_info_map:
|
||||
info = app_metrics_info_map[key]
|
||||
for metric_name, metric_data in info.get('metrics', {}).items():
|
||||
metrics[metric_name] = Metrics(
|
||||
source_value=metric_data.get('source_value', 0),
|
||||
target_value=metric_data.get('target_value', 0),
|
||||
difference=metric_data.get('difference', 0),
|
||||
difference_percentage=metric_data.get('difference_percentage', 0.0),
|
||||
)
|
||||
|
||||
is_new_app = info.get('is_new_app', False)
|
||||
|
||||
app_attributes.update({'metrics': metrics, 'is_new_app': is_new_app})
|
||||
|
||||
apps_with_metrics_info.append(AppWithMetricsInfo(**app_attributes))
|
||||
|
||||
return apps_with_metrics_info
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import os
|
||||
import typing as t
|
||||
@@ -9,6 +9,7 @@ import pytest
|
||||
import yaml
|
||||
from _pytest.config import Config
|
||||
from _pytest.python import Function
|
||||
from _pytest.python import Metafunc
|
||||
from _pytest.runner import CallInfo
|
||||
from dynamic_pipelines.constants import KNOWN_GENERATE_TEST_CHILD_PIPELINE_WARNINGS_FILEPATH
|
||||
from idf_ci import IdfPytestPlugin
|
||||
@@ -114,6 +115,55 @@ class IdfLocalPlugin:
|
||||
|
||||
return item.callspec.params.get(key, default) or default
|
||||
|
||||
@staticmethod
|
||||
def _has_parametrized_arg(metafunc: Metafunc, arg_name: str) -> bool:
|
||||
for marker in metafunc.definition.iter_markers(name='parametrize'):
|
||||
if not marker.args:
|
||||
continue
|
||||
|
||||
argnames = marker.args[0]
|
||||
if isinstance(argnames, str):
|
||||
names = [name.strip() for name in argnames.split(',')]
|
||||
else:
|
||||
names = list(argnames)
|
||||
|
||||
if arg_name in names:
|
||||
return True
|
||||
|
||||
for callspec in getattr(metafunc, '_calls', []):
|
||||
if arg_name in callspec.params:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _is_linux_target_run(config: Config) -> bool:
|
||||
target = config.getoption('target')
|
||||
if not target:
|
||||
return False
|
||||
|
||||
if isinstance(target, str):
|
||||
targets = [_t.strip() for _t in target.split(',')]
|
||||
else:
|
||||
targets = [str(_t).strip() for _t in target]
|
||||
|
||||
return 'linux' in targets
|
||||
|
||||
@pytest.hookimpl(trylast=True)
|
||||
def pytest_generate_tests(self, metafunc: Metafunc) -> None:
|
||||
if 'embedded_services' not in metafunc.fixturenames:
|
||||
return
|
||||
|
||||
if self._has_parametrized_arg(metafunc, 'embedded_services'):
|
||||
return
|
||||
|
||||
if metafunc.definition.get_closest_marker('qemu') is not None:
|
||||
metafunc.parametrize('embedded_services', ['idf,qemu'], indirect=True)
|
||||
return
|
||||
|
||||
if self._is_linux_target_run(metafunc.config):
|
||||
metafunc.parametrize('embedded_services', ['idf'], indirect=True)
|
||||
|
||||
@pytest.hookimpl(wrapper=True)
|
||||
def pytest_collection_modifyitems(self, config: Config, items: t.List[Function]) -> t.Generator[None, None, None]:
|
||||
yield # throw it back to idf-ci
|
||||
|
||||
@@ -661,6 +661,21 @@ macro(idf_build_process target)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
idf_build_get_property(prefix __PREFIX)
|
||||
|
||||
file(GLOB root_dep_component_dirs
|
||||
${IDF_TOOLS_PATH}/root_managed_components/idf${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}.${IDF_VERSION_PATCH}/*)
|
||||
list(SORT root_dep_component_dirs)
|
||||
foreach(component_dir ${root_dep_component_dirs})
|
||||
# A potential component must be a directory
|
||||
if(IS_DIRECTORY ${component_dir})
|
||||
__component_dir_quick_check(is_component ${component_dir})
|
||||
if(is_component)
|
||||
__component_add(${component_dir} ${prefix} "idf_managed_components")
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Perform early expansion of component CMakeLists.txt in CMake scripting mode.
|
||||
# It is here we retrieve the public and private requirements of each component.
|
||||
# It is also here we add the common component requirements to each component's
|
||||
|
||||
@@ -52,6 +52,13 @@ if(NOT __idf_env_set)
|
||||
include(prefix_map)
|
||||
include(openocd)
|
||||
|
||||
# ESP-IDF extra dependencies defined in tools/idf_extra_components.yml
|
||||
if(WIN32)
|
||||
set_default(IDF_TOOLS_PATH "$ENV{USERPROFILE}/.espressif")
|
||||
else()
|
||||
set_default(IDF_TOOLS_PATH "$ENV{HOME}/.espressif")
|
||||
endif()
|
||||
|
||||
__build_init("${idf_path}")
|
||||
|
||||
# Check if IDF_ENV_FPGA environment is set
|
||||
|
||||
@@ -131,6 +131,8 @@ foreach(__component_target ${__component_targets})
|
||||
|
||||
if("${__component_source}" STREQUAL "idf_components")
|
||||
list(APPEND __TARGETS_IDF_COMPONENTS ${__component_target})
|
||||
elseif("${__component_source}" STREQUAL "idf_managed_components")
|
||||
list(APPEND __TARGETS_IDF_MANAGED_COMPONENTS ${__component_target})
|
||||
elseif("${__component_source}" STREQUAL "project_managed_components")
|
||||
list(APPEND __TARGETS_PROJECT_MANAGED_COMPONENTS ${__component_target})
|
||||
elseif("${__component_source}" STREQUAL "project_extra_components")
|
||||
@@ -147,6 +149,7 @@ set(__sorted_component_targets "")
|
||||
foreach(__target IN LISTS __TARGETS_PROJECT_COMPONENTS
|
||||
__TARGETS_PROJECT_EXTRA_COMPONENTS
|
||||
__TARGETS_PROJECT_MANAGED_COMPONENTS
|
||||
__TARGETS_IDF_MANAGED_COMPONENTS
|
||||
__TARGETS_IDF_COMPONENTS)
|
||||
__component_get_property(__component_name ${__target} COMPONENT_NAME)
|
||||
list(APPEND __sorted_component_targets ${__target})
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# This file defines extra dependencies for ESP-IDF
|
||||
# the dependencies defined here will be downloaded to
|
||||
# $IDF_TOOLS_PATH/root_managed_components
|
||||
# Each major.minor version of ESP-IDF can have its own subdirectory
|
||||
# For example, for ESP-IDF v6.0, the dependencies will be installed to
|
||||
# $IDF_TOOLS_PATH/root_managed_components/idf6.0
|
||||
|
||||
# The syntax is defined in:
|
||||
# https://docs.espressif.com/projects/idf-component-manager/en/latest/reference/manifest_file.html#dependencies
|
||||
|
||||
#dependencies:
|
||||
@@ -6,7 +6,7 @@
|
||||
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/tools/idf-tools.html
|
||||
|
||||
# ci
|
||||
idf-ci>=0.3,<1
|
||||
idf-ci
|
||||
|
||||
coverage
|
||||
jsonschema
|
||||
|
||||
@@ -166,7 +166,6 @@ def test_examples_security_secure_boot_ecdsa(dut: Dut) -> None:
|
||||
|
||||
# Test secure boot flow.
|
||||
# Correctly signed bootloader + correctly signed app should work
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.qemu
|
||||
@pytest.mark.parametrize(
|
||||
'qemu_extra_args',
|
||||
|
||||
@@ -6,7 +6,6 @@ from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.qemu
|
||||
@pytest.mark.parametrize('config', ['secure_update_with_fe'], indirect=True)
|
||||
@idf_parametrize('target', ['esp32c3'], indirect=['target'])
|
||||
|
||||
@@ -6,7 +6,6 @@ from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
|
||||
@pytest.mark.qemu
|
||||
@pytest.mark.host_test
|
||||
@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target'])
|
||||
def test_std_filesystem(dut: Dut) -> None:
|
||||
dut.expect_exact('All tests passed', timeout=200)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
import os
|
||||
|
||||
@@ -10,7 +10,6 @@ PROMPT = 'test_intr_dump>'
|
||||
|
||||
|
||||
@pytest.mark.qemu
|
||||
@pytest.mark.host_test
|
||||
@idf_parametrize('target', ['esp32'], indirect=['target'])
|
||||
def test_esp_intr_dump_nonshared(dut: Dut) -> None:
|
||||
dut.expect_exact(PROMPT, timeout=30)
|
||||
@@ -24,7 +23,6 @@ def test_esp_intr_dump_nonshared(dut: Dut) -> None:
|
||||
|
||||
|
||||
@pytest.mark.qemu
|
||||
@pytest.mark.host_test
|
||||
@idf_parametrize('target', ['esp32'], indirect=['target'])
|
||||
def test_esp_intr_dump_shared(dut: Dut) -> None:
|
||||
dut.expect_exact(PROMPT, timeout=30)
|
||||
@@ -54,5 +52,5 @@ def test_esp_intr_dump_expected_output(dut: Dut) -> None:
|
||||
dut.expect_exact(PROMPT, timeout=30)
|
||||
dut.write('intr_dump\n')
|
||||
exp_out_file = os.path.join(os.path.dirname(__file__), 'expected_output', f'{dut.target}.txt')
|
||||
for line in open(exp_out_file, 'r').readlines():
|
||||
for line in open(exp_out_file).readlines():
|
||||
dut.expect_exact(line.strip())
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from idf_ci_utils import IDF_PATH
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
@@ -16,12 +15,12 @@ def test_app_mmu_page_size_32k_and_bootloader_mmu_page_size_64k(dut: Dut, app_do
|
||||
|
||||
assert '32K' in config
|
||||
app_config = config.replace('32K', '64K')
|
||||
build_dir = f'build_{dut.target}_{app_config}'
|
||||
|
||||
path_to_mmu_page_size_64k_build = os.path.join(dut.app.app_path, f'build_{dut.target}_{app_config}')
|
||||
if app_downloader:
|
||||
app_downloader.download_app(os.path.relpath(path_to_mmu_page_size_64k_build, IDF_PATH), 'flash')
|
||||
app_downloader.download_app(dut.app.app_path, build_dir, 'flash')
|
||||
|
||||
dut.serial.bootloader_flash(path_to_mmu_page_size_64k_build)
|
||||
dut.serial.bootloader_flash(os.path.join(dut.app.app_path, build_dir))
|
||||
dut.expect('MMU page size mismatch')
|
||||
dut.expect('App is running')
|
||||
dut.expect('Partition test done')
|
||||
@@ -36,12 +35,12 @@ def test_app_mmu_page_size_64k_and_bootloader_mmu_page_size_32k(dut: Dut, app_do
|
||||
|
||||
assert '64K' in config
|
||||
app_config = config.replace('64K', '32K')
|
||||
build_dir = f'build_{dut.target}_{app_config}'
|
||||
|
||||
path_to_mmu_page_size_32k_build = os.path.join(dut.app.app_path, f'build_{dut.target}_{app_config}')
|
||||
if app_downloader:
|
||||
app_downloader.download_app(os.path.relpath(path_to_mmu_page_size_32k_build, IDF_PATH), 'flash')
|
||||
app_downloader.download_app(dut.app.app_path, build_dir, 'flash')
|
||||
|
||||
dut.serial.bootloader_flash(path_to_mmu_page_size_32k_build)
|
||||
dut.serial.bootloader_flash(os.path.join(dut.app.app_path, build_dir))
|
||||
dut.expect('MMU page size mismatch')
|
||||
dut.expect('App is running')
|
||||
dut.expect('Partition test done')
|
||||
|
||||
@@ -4,7 +4,6 @@ import os
|
||||
import re
|
||||
|
||||
import pytest
|
||||
from idf_ci_utils import IDF_PATH
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
@@ -19,12 +18,12 @@ def test_multicore_app_and_unicore_bootloader(dut: Dut, app_downloader, config)
|
||||
|
||||
assert 'multicore' in config
|
||||
app_config = config.replace('multicore', 'unicore')
|
||||
build_dir = f'build_{dut.target}_{app_config}'
|
||||
|
||||
path_to_unicore_build = os.path.join(dut.app.app_path, f'build_{dut.target}_{app_config}')
|
||||
if app_downloader:
|
||||
app_downloader.download_app(os.path.relpath(path_to_unicore_build, IDF_PATH), 'flash')
|
||||
app_downloader.download_app(dut.app.app_path, build_dir, 'flash')
|
||||
|
||||
dut.serial.bootloader_flash(path_to_unicore_build)
|
||||
dut.serial.bootloader_flash(os.path.join(dut.app.app_path, build_dir))
|
||||
dut.expect('Unicore bootloader')
|
||||
dut.expect('Multicore app')
|
||||
if 'psram' in config:
|
||||
@@ -43,12 +42,12 @@ def test_unicore_app_and_multicore_bootloader(dut: Dut, app_downloader, config)
|
||||
|
||||
assert 'unicore' in config
|
||||
app_config = config.replace('unicore', 'multicore')
|
||||
build_dir = f'build_{dut.target}_{app_config}'
|
||||
|
||||
path_to_multicore_build = os.path.join(dut.app.app_path, f'build_{dut.target}_{app_config}')
|
||||
if app_downloader:
|
||||
app_downloader.download_app(os.path.relpath(path_to_multicore_build, IDF_PATH), 'flash')
|
||||
app_downloader.download_app(dut.app.app_path, build_dir, 'flash')
|
||||
|
||||
dut.serial.bootloader_flash(path_to_multicore_build)
|
||||
dut.serial.bootloader_flash(os.path.join(dut.app.app_path, build_dir))
|
||||
dut.expect('Multicore bootloader')
|
||||
dut.expect('Unicore app')
|
||||
if 'psram' in config:
|
||||
|
||||
@@ -14,6 +14,9 @@ junit_family = xunit1
|
||||
junit_logging = stdout
|
||||
junit_log_passing_tests = False
|
||||
|
||||
filterwarnings =
|
||||
ignore::pytest.PytestExperimentalApiWarning
|
||||
|
||||
## !! When adding new markers, don't forget to update also the tools\test_build_system\README.md !!
|
||||
markers =
|
||||
test_app_copy: specify relative path of the app to copy, and the prefix of the destination directory name
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import json
|
||||
import os.path
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from test_build_system_helpers import EXT_IDF_PATH
|
||||
from test_build_system_helpers import IdfPyFunc
|
||||
from test_build_system_helpers import replace_in_file
|
||||
|
||||
@@ -192,3 +195,84 @@ class TestOptionalDependencyWithKconfig:
|
||||
data = json.load(open(test_app_copy / 'build' / 'project_description.json'))
|
||||
assert ['example__cmp'] == data['build_component_info']['foo']['priv_reqs']
|
||||
assert ['espressif__mdns'] == data['build_component_info']['foo']['reqs']
|
||||
|
||||
|
||||
@pytest.mark.revert_later(['tools/idf_extra_components.yml'])
|
||||
class TestIdfRootDependency:
|
||||
def test_basic_build(self, idf_py: IdfPyFunc, test_app_copy: Path) -> None:
|
||||
with open(os.path.join(EXT_IDF_PATH, 'tools', 'idf_extra_components.yml'), 'w') as fw:
|
||||
fw.write(
|
||||
textwrap.dedent("""
|
||||
dependencies:
|
||||
espressif/mdns: "*"
|
||||
""")
|
||||
)
|
||||
|
||||
replace_in_file(
|
||||
(test_app_copy / 'main' / 'build_test_app.c'),
|
||||
'// placeholder_before_main',
|
||||
'#include "mdns.h"',
|
||||
)
|
||||
|
||||
replace_in_file(
|
||||
(test_app_copy / 'main' / 'CMakeLists.txt'),
|
||||
'# placeholder_inside_idf_component_register',
|
||||
'REQUIRES mdns',
|
||||
)
|
||||
|
||||
idf_py('build')
|
||||
|
||||
def test_build_only_when_required(self, idf_py: IdfPyFunc, test_app_copy: Path) -> None:
|
||||
with open(os.path.join(EXT_IDF_PATH, 'tools', 'idf_extra_components.yml'), 'w') as fw:
|
||||
fw.write(
|
||||
textwrap.dedent("""
|
||||
dependencies:
|
||||
espressif/mdns: "*"
|
||||
example/cmp: "*"
|
||||
""")
|
||||
)
|
||||
|
||||
idf_py('reconfigure')
|
||||
|
||||
data = json.load(open(test_app_copy / 'build' / 'project_description.json'))
|
||||
assert 'espressif__mdns' not in data['build_components']
|
||||
assert 'example__cmp' not in data['build_components']
|
||||
|
||||
replace_in_file(
|
||||
(test_app_copy / 'main' / 'CMakeLists.txt'),
|
||||
'# placeholder_inside_idf_component_register',
|
||||
'REQUIRES mdns',
|
||||
)
|
||||
|
||||
idf_py('reconfigure')
|
||||
data = json.load(open(test_app_copy / 'build' / 'project_description.json'))
|
||||
assert 'espressif__mdns' in data['build_components']
|
||||
assert 'example__cmp' not in data['build_components']
|
||||
|
||||
def test_cleanup_unused(self, idf_py: IdfPyFunc, test_app_copy: Path) -> None:
|
||||
with open(os.path.join(EXT_IDF_PATH, 'tools', 'idf_extra_components.yml'), 'w') as fw:
|
||||
fw.write(
|
||||
textwrap.dedent("""
|
||||
dependencies:
|
||||
espressif/mdns: "*"
|
||||
""")
|
||||
)
|
||||
|
||||
idf_py('reconfigure')
|
||||
data = json.load(open(test_app_copy / 'build' / 'project_description.json'))
|
||||
assert 'espressif__mdns' in data['all_component_info']
|
||||
|
||||
with open(os.path.join(EXT_IDF_PATH, 'tools', 'idf_extra_components.yml'), 'w') as fw:
|
||||
fw.write(
|
||||
textwrap.dedent("""
|
||||
dependencies:
|
||||
espressif/led_strip: "*"
|
||||
example/cmp: "*"
|
||||
""")
|
||||
)
|
||||
|
||||
idf_py('reconfigure')
|
||||
data = json.load(open(test_app_copy / 'build' / 'project_description.json'))
|
||||
assert 'espressif__led_strip' in data['all_component_info']
|
||||
assert 'example__cmp' in data['all_component_info']
|
||||
assert 'espressif__mdns' not in data['all_component_info']
|
||||
|
||||
@@ -27,9 +27,9 @@ except ImportError:
|
||||
import idf
|
||||
|
||||
current_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
idf_py_path = os.path.join(current_dir, '..', 'idf.py')
|
||||
idf_py_path = os.path.normpath(os.path.join(current_dir, '..', 'idf.py'))
|
||||
extension_path = os.path.join(current_dir, 'test_idf_extensions', 'test_ext')
|
||||
py_actions_path = os.path.join(current_dir, '..', 'idf_py_actions')
|
||||
py_actions_path = os.path.normpath(os.path.join(current_dir, '..', 'idf_py_actions'))
|
||||
link_path = os.path.join(py_actions_path, 'test_ext')
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user