ci: apply idf-ci 1.x

This commit is contained in:
Fu Hanxi
2026-09-03 20:24:50 +02:00
parent aa69fb8119
commit ddb9fd017c
63 changed files with 499 additions and 292 deletions
@@ -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}
+7 -1
View File
@@ -8,6 +8,7 @@ import typing as t
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
@@ -36,6 +37,8 @@ class IdfCMakeApp(CMakeApp):
'gitlab',
'upload-artifacts',
self.app_dir,
'--build-dir',
self.build_dir,
],
[
'idf-ci',
@@ -48,11 +51,14 @@ class IdfCMakeApp(CMakeApp):
]
for command in upload_commands:
subprocess.run(
result = subprocess.run(
command,
stdout=sys.stdout,
stderr=sys.stderr,
)
if result.returncode != 0:
self.build_status = BuildStatus.FAILED
self.build_comment = 'Failed to upload artifacts'
rmdir(
self.build_path,
+51 -1
View File
@@ -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: list[Function]) -> t.Generator[None, None, None]:
yield # throw it back to idf-ci
+1 -1
View File
@@ -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
@@ -1,11 +1,9 @@
# SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import pytest
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
@pytest.mark.host_test
@idf_parametrize('target', ['linux'], indirect=['target'])
def test_linux_freertos_SMP(dut: Dut) -> None:
dut.expect_exact('Press ENTER to see the list of tests.')
@@ -164,7 +164,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())
@@ -1,9 +1,8 @@
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
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,15 +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')
@@ -39,15 +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')
@@ -1,10 +1,9 @@
# 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
import re
import pytest
from idf_ci_utils import IDF_PATH
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
from pytest_embedded_idf.utils import soc_filtered_targets
@@ -20,15 +19,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:
@@ -47,15 +43,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
View File
@@ -149,6 +149,20 @@ def test_target_guessing()
def test_target_guessing()
```
### `pytest.mark.revert_later` Marker
This marker reverts all files to their original state after the test is finished. should pass a list of file paths (absolute or relative to `IDF_PATH`) to the marker. The files will be reverted even if the test fails.
```python
@pytest.mark.revert_later(['tools/idf_extra_components.yml'])
def test_modify_file(idf_copy):
path = os.path.join(os.getenv('IDF_PATH'), 'tools', 'idf_extra_components.yml')
with open(path, 'a') as f:
f.write('# some changes\n')
# The changes to idf_extra_components.yml will be reverted after the test
```
### Build snapshots
`get_snapshot(list_of_globs)` function takes a list of glob expressions, finds the files matching these expressions, and returns a `Snapshot` instance. `Snapshot` instances record file names and their modification timestamps. Two `Snapshot` instances can be compared using `assert_same` and `assert_different` methods:
+3
View File
@@ -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
+2 -1
View File
@@ -33,7 +33,8 @@ def safe_cleanup_tmpdir(tmpdir: tempfile.TemporaryDirectory) -> None:
"""Safely cleanup temporary directory, handling specific errors on Windows."""
try:
tmpdir.cleanup()
except (PermissionError, NotADirectoryError):
except (PermissionError, NotADirectoryError, OSError):
# OSError: The directory is not empty
warnings.warn(
f'Failed to cleanup temporary directory {tmpdir.name}. '
'This is common on Windows when files are still in use.',
+2 -2
View File
@@ -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')