mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 03:00:34 +03:00
ci: apply idf-ci 1.x
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.',
|
||||
|
||||
@@ -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