mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
ci: apply common-scripts CI refactor
Squashes these original commits for release/v5.4 backport traceability: - 9b289dc5ad6 ci: apply idf-ci 1.x - 381df980d57 ci: remove pip-cache and other unused jobs - 32375a0a15e ci: apply common-scripts CI refactor
This commit is contained in:
@@ -1,44 +1,26 @@
|
||||
workflow:
|
||||
rules:
|
||||
# Disable those non-protected push triggered pipelines
|
||||
- if: '$CI_COMMIT_REF_NAME != "master" && $CI_COMMIT_BRANCH !~ /^release\/v/ && $CI_COMMIT_TAG !~ /^v\d+\.\d+(\.\d+)?($|-)/ && $CI_COMMIT_TAG !~ /^qa-test/ && $CI_PIPELINE_SOURCE == "push"'
|
||||
when: never
|
||||
# when running merged result pipelines, CI_COMMIT_SHA represents the temp commit it created.
|
||||
# Please use PIPELINE_COMMIT_SHA at all places that require a commit sha of the original commit.
|
||||
- if: $CI_OPEN_MERGE_REQUESTS != null
|
||||
variables:
|
||||
PIPELINE_COMMIT_SHA: $CI_MERGE_REQUEST_SOURCE_BRANCH_SHA
|
||||
IS_MR_PIPELINE: 1
|
||||
- if: $CI_OPEN_MERGE_REQUESTS == null
|
||||
variables:
|
||||
PIPELINE_COMMIT_SHA: $CI_COMMIT_SHA
|
||||
IS_MR_PIPELINE: 0
|
||||
- when: always
|
||||
|
||||
# Place the default settings in `.gitlab/ci/common.yml` instead
|
||||
|
||||
include:
|
||||
- project: "ci/actions/common"
|
||||
ref: "v2"
|
||||
file:
|
||||
- "templates/idf/integration-test.yml"
|
||||
- '.gitlab/ci/danger.yml'
|
||||
- '.gitlab/ci/common.yml'
|
||||
- '.gitlab/ci/rules.yml'
|
||||
- ".gitlab/ci/manual_gate.yml"
|
||||
- '.gitlab/ci/upload_cache.yml'
|
||||
- '.gitlab/ci/static-code-analysis.yml'
|
||||
- '.gitlab/ci/pre_commit.yml'
|
||||
- '.gitlab/ci/pre_check.yml'
|
||||
- '.gitlab/ci/build.yml'
|
||||
- '.gitlab/ci/host-test.yml'
|
||||
- '.gitlab/ci/deploy.yml'
|
||||
- '.gitlab/ci/post_deploy.yml'
|
||||
- '.gitlab/ci/retry_failed_jobs.yml'
|
||||
- '.gitlab/ci/test-win.yml'
|
||||
|
||||
- project: "ci/actions/common"
|
||||
file:
|
||||
- "templates/stable/default-workflow.yml"
|
||||
- "templates/idf/pre_check.yml"
|
||||
- "templates/idf/build.yml"
|
||||
- "templates/idf/deploy-github.yml"
|
||||
- "templates/idf/deploy-docs.yml"
|
||||
- "templates/idf/integration-test.yml"
|
||||
- project: "ci/actions/common"
|
||||
ref: "v2"
|
||||
file: "templates/idf/sync-files.yml"
|
||||
inputs:
|
||||
job_name: "sync-pre-commit-config"
|
||||
remote_repo_url: "https://__token__:${CI_SYNC_REPO_TOKEN}@${CI_SERVER_FQDN}/ci/images.git"
|
||||
ref_name: "ci/sync_idf_pre_commit_config"
|
||||
file_mappings:
|
||||
- ".pre-commit-config.yaml:pre-commit/idf/.pre-commit-config.v5.4.yaml"
|
||||
changes:
|
||||
- ".pre-commit-config.yaml"
|
||||
- project: "ci/actions/common"
|
||||
file: "templates/idf/build-docs.yml"
|
||||
inputs:
|
||||
@@ -50,3 +32,16 @@ include:
|
||||
DOCTGT: "esp32"
|
||||
- DOCLANG: "zh_CN"
|
||||
DOCTGT: "esp32p4"
|
||||
- ".gitlab/ci/danger.yml"
|
||||
- ".gitlab/ci/common.yml" # has to be placed after danger.yml, since danger.yml defined stages.
|
||||
- ".gitlab/ci/rules.yml"
|
||||
# by stages
|
||||
- ".gitlab/ci/manual_gate.yml"
|
||||
- ".gitlab/ci/upload_cache.yml"
|
||||
- ".gitlab/ci/pre_check.yml"
|
||||
- ".gitlab/ci/pre_deploy.yml"
|
||||
- ".gitlab/ci/static-code-analysis.yml"
|
||||
- ".gitlab/ci/pre_commit.yml"
|
||||
- ".gitlab/ci/build.yml"
|
||||
- ".gitlab/ci/host-test.yml"
|
||||
- ".gitlab/ci/test-win.yml"
|
||||
|
||||
@@ -23,12 +23,7 @@
|
||||
- [Grammar](#grammar)
|
||||
- [Special Rules](#special-rules)
|
||||
- [Upload/Download Artifacts to Internal Minio Server](#uploaddownload-artifacts-to-internal-minio-server)
|
||||
- [Users Without Access to Minio](#users-without-access-to-minio)
|
||||
- [Users With Access to Minio](#users-with-access-to-minio)
|
||||
- [Env Vars for Minio](#env-vars-for-minio)
|
||||
- [Artifacts Types and File Patterns](#artifacts-types-and-file-patterns)
|
||||
- [Upload](#upload)
|
||||
- [Download](#download)
|
||||
- [Revision Marker Naming Rules](#revision-marker-naming-rules)
|
||||
|
||||
## General Workflow
|
||||
|
||||
@@ -145,11 +140,11 @@ check if there's a suitable `.if-<if-anchor-you-need>` anchor
|
||||
1. if there is, create a rule following [`rules` Template Naming Rules](#rules-template-naming-rules).For detail information, please refer to [GitLab Documentation `rules-if`](https://docs.gitlab.com/ee/ci/yaml/README.html#rulesif). Here's an example.
|
||||
|
||||
```yaml
|
||||
.rules:patterns:python-files:
|
||||
.rules:patterns:clang_tidy:
|
||||
rules:
|
||||
- <<: *if-protected
|
||||
- <<: *if-dev-push
|
||||
changes: *patterns-python-files
|
||||
changes: *patterns-c-files
|
||||
```
|
||||
|
||||
2. if there isn't
|
||||
@@ -250,76 +245,15 @@ We're using the latest version of [idf-build-apps][idf-build-apps]. Please refer
|
||||
In ESP-IDF CI, there's a few more special rules are additionally supported to disable the check app dependencies feature:
|
||||
|
||||
- Add MR labels `BUILD_AND_TEST_ALL_APPS`
|
||||
- Pipeline variable `IDF_CI_SELECT_ALL_PYTEST_CASES=1`
|
||||
- Run in protected branches
|
||||
|
||||
## Upload/Download Artifacts to Internal Minio Server
|
||||
|
||||
### Users Without Access to Minio
|
||||
Please refer to the documentation [here](https://docs.espressif.com/projects/idf-ci/en/latest/guides/cli.html)
|
||||
|
||||
If you don't have access to the internal Minio server, you can still download the artifacts from the shared link in the job log.
|
||||
## Revision Marker Naming Rules
|
||||
|
||||
The log will look like this:
|
||||
When specifying chip revisions for testing markers, configurations or filenames, strictly follow the format `esp32<series>_rev<number>` (or similarly `<series>_rev<number>` when brief).
|
||||
|
||||
```shell
|
||||
Pipeline ID : 587355
|
||||
Job name : build_clang_test_apps_esp32
|
||||
Job ID : 40272275
|
||||
Created archive file: 40272275.zip, uploading as 587355/build_dir_without_map_and_elf_files/build_clang_test_apps_esp32/40272275.zip
|
||||
Please download the archive file includes build_dir_without_map_and_elf_files from [INTERNAL_URL]
|
||||
```
|
||||
|
||||
### Users With Access to Minio
|
||||
|
||||
#### Env Vars for Minio
|
||||
|
||||
Minio takes these env vars to connect to the server:
|
||||
|
||||
- `IDF_S3_SERVER`
|
||||
- `IDF_S3_ACCESS_KEY`
|
||||
- `IDF_S3_SECRET_KEY`
|
||||
- `IDF_S3_BUCKET`
|
||||
|
||||
#### Artifacts Types and File Patterns
|
||||
|
||||
The artifacts types and corresponding file patterns are defined in tools/ci/artifacts_handler.py, inside `ArtifactType` and `TYPE_PATTERNS_DICT`.
|
||||
|
||||
#### Upload
|
||||
|
||||
```shell
|
||||
python tools/ci/artifacts_handler.py upload
|
||||
```
|
||||
|
||||
will upload the files that match the file patterns to minio object storage with name:
|
||||
|
||||
`<pipeline_id>/<artifact_type>/<job_name>/<job_id>.zip`
|
||||
|
||||
For example, job 39043328 will upload these four files:
|
||||
|
||||
- `575500/map_and_elf_files/build_pytest_examples_esp32/39043328.zip`
|
||||
- `575500/build_dir_without_map_and_elf_files/build_pytest_examples_esp32/39043328.zip`
|
||||
- `575500/logs/build_pytest_examples_esp32/39043328.zip`
|
||||
- `575500/size_reports/build_pytest_examples_esp32/39043328.zip`
|
||||
|
||||
#### Download
|
||||
|
||||
You may run
|
||||
|
||||
```shell
|
||||
python tools/ci/artifacts_handler.py download --pipeline_id <pipeline_id>
|
||||
```
|
||||
|
||||
to download all files of the pipeline, or
|
||||
|
||||
```shell
|
||||
python tools/ci/artifacts_handler.py download --pipeline_id <pipeline_id> --job_name <job_name_or_pattern>
|
||||
```
|
||||
|
||||
to download all files with the specified job name or pattern, or
|
||||
|
||||
```shell
|
||||
python tools/ci/artifacts_handler.py download --pipeline_id <pipeline_id> --job_name <job_name_or_pattern> --type <artifact_type> <artifact_type> ...
|
||||
```
|
||||
|
||||
to download all files with the specified job name or pattern and artifact type(s).
|
||||
|
||||
You may check all detailed documentation with `python tools/ci/artifacts_handler.py download -h`
|
||||
> e.g., `esp32_rev3`, `esp32c2_rev2`, `esp32c3_rev1`, `esp32p4_rev1`, `esp32c5_rev1`
|
||||
|
||||
@@ -1,55 +1,13 @@
|
||||
.build_template:
|
||||
stage: build
|
||||
extends:
|
||||
- .after_script:build:ccache-show-stats:upload-failed-job-logs
|
||||
- .after_script:build
|
||||
image: $ESP_ENV_IMAGE
|
||||
tags:
|
||||
- build
|
||||
# build only on shiny servers since shiny storage server is at the same location
|
||||
- shiny
|
||||
tags: [build, shiny]
|
||||
variables:
|
||||
# Enable ccache for all build jobs. See configure_ci_environment.sh for more ccache related settings.
|
||||
IDF_CCACHE_ENABLE: "1"
|
||||
dependencies: []
|
||||
|
||||
.build_cmake_clang_template:
|
||||
extends:
|
||||
- .build_template
|
||||
- .before_script:build
|
||||
- .after_script:build:ccache-show-stats
|
||||
dependencies: # set dependencies to null to avoid missing artifacts issue
|
||||
needs:
|
||||
- job: fast_template_app
|
||||
artifacts: false
|
||||
- pipeline_variables
|
||||
artifacts:
|
||||
paths:
|
||||
# The other artifacts patterns are defined under tools/ci/artifacts_handler.py
|
||||
# Now we're uploading/downloading the binary files from our internal storage server
|
||||
#
|
||||
# keep the log file to help debug
|
||||
- "**/build*/build_log.txt"
|
||||
# keep the size info to help track the binary size
|
||||
- size_info.txt
|
||||
- "**/build*/size.json"
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
variables:
|
||||
IDF_TOOLCHAIN: clang
|
||||
TEST_BUILD_OPTS_EXTRA: ""
|
||||
TEST_DIR: tools/test_apps/system/clang_build_test
|
||||
PYTEST_IGNORE_COLLECT_IMPORT_ERROR: "1"
|
||||
script:
|
||||
# CI specific options start from "--parallel-count xxx". could ignore when running locally
|
||||
- run_cmd python tools/ci/ci_build_apps.py $TEST_DIR -v
|
||||
-t $IDF_TARGET
|
||||
--copy-sdkconfig
|
||||
--parallel-count ${CI_NODE_TOTAL:-1}
|
||||
--parallel-index ${CI_NODE_INDEX:-1}
|
||||
--modified-components ${MR_MODIFIED_COMPONENTS}
|
||||
--modified-files ${MR_MODIFIED_FILES}
|
||||
$TEST_BUILD_OPTS_EXTRA
|
||||
- python tools/ci/artifacts_handler.py upload
|
||||
|
||||
######################
|
||||
# build_template_app #
|
||||
@@ -57,18 +15,11 @@
|
||||
.build_template_app_template:
|
||||
extends:
|
||||
- .build_template
|
||||
- .before_script:build
|
||||
variables:
|
||||
LOG_PATH: "${CI_PROJECT_DIR}/log_template_app"
|
||||
BUILD_PATH: "${CI_PROJECT_DIR}/build_template_app"
|
||||
BUILD_DIR: "${BUILD_PATH}/@t/@w"
|
||||
BUILD_LOG_CMAKE: "${LOG_PATH}/cmake_@t_@w.txt"
|
||||
BUILD_COMMAND_ARGS: ""
|
||||
artifacts:
|
||||
paths:
|
||||
- log_template_app/*
|
||||
- size_info.txt
|
||||
- build_template_app/**/size.json
|
||||
- build_template_app/**/size*.json
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
script:
|
||||
@@ -82,224 +33,15 @@
|
||||
# Only do the default cmake build for each target, remaining part are done in the build_template_app job
|
||||
- tools/ci/build_template_app.sh ${BUILD_COMMAND_ARGS}
|
||||
|
||||
# build-related-pre-check-jobs ------------------------------------------------
|
||||
# Build at least one project for each target at earliest stage to reduce build cost for obvious failing commits
|
||||
fast_template_app:
|
||||
extends:
|
||||
- .build_template_app_template
|
||||
- .rules:build:target_test
|
||||
- .rules:build
|
||||
stage: pre_check
|
||||
tags: [fast_run, shiny]
|
||||
variables:
|
||||
BUILD_COMMAND_ARGS: "-p"
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
#######################
|
||||
# gnu_static_analyzer #
|
||||
#######################
|
||||
gcc_static_analyzer:
|
||||
extends:
|
||||
- .build_template_app_template
|
||||
- .rules:build:target_test
|
||||
stage: pre_check
|
||||
tags: [build, shiny]
|
||||
variables:
|
||||
CI_CCACHE_DISABLE: 1
|
||||
ANALYZING_APP: "examples/get-started/hello_world"
|
||||
script:
|
||||
- echo "CONFIG_COMPILER_STATIC_ANALYZER=y" >> ${ANALYZING_APP}/sdkconfig.defaults
|
||||
- python -m idf_build_apps build -v -p ${ANALYZING_APP} -t all
|
||||
|
||||
########################################
|
||||
# Clang Build Apps Without Tests Cases #
|
||||
########################################
|
||||
build_clang_test_apps_esp32:
|
||||
extends:
|
||||
- .build_cmake_clang_template
|
||||
- .rules:build
|
||||
variables:
|
||||
IDF_TARGET: esp32
|
||||
|
||||
build_clang_test_apps_esp32s2:
|
||||
extends:
|
||||
- .build_cmake_clang_template
|
||||
- .rules:build
|
||||
variables:
|
||||
IDF_TARGET: esp32s2
|
||||
|
||||
build_clang_test_apps_esp32s3:
|
||||
extends:
|
||||
- .build_cmake_clang_template
|
||||
- .rules:build
|
||||
variables:
|
||||
IDF_TARGET: esp32s3
|
||||
|
||||
.build_clang_test_apps_riscv:
|
||||
extends:
|
||||
- .build_cmake_clang_template
|
||||
variables:
|
||||
# https://reviews.llvm.org/D90108.
|
||||
# GNU 'as' lets .weak override .globl since binutils-gdb
|
||||
# https://github.com/bminor/binutils-gdb/commit/5ca547dc2399a0a5d9f20626d4bf5547c3ccfddd (1996)
|
||||
# while MC lets the last directive win (PR38921).
|
||||
# For RISCV chips we use integrated assembler by default, so suppress this warning to pass CI pipeline.
|
||||
TEST_BUILD_OPTS_EXTRA: "--ignore-warning-str 'changed binding to STB_WEAK'"
|
||||
|
||||
build_clang_test_apps_esp32c3:
|
||||
extends:
|
||||
- .build_clang_test_apps_riscv
|
||||
- .rules:build
|
||||
variables:
|
||||
IDF_TARGET: esp32c3
|
||||
|
||||
build_clang_test_apps_esp32c2:
|
||||
extends:
|
||||
- .build_clang_test_apps_riscv
|
||||
- .rules:build
|
||||
variables:
|
||||
IDF_TARGET: esp32c2
|
||||
|
||||
build_clang_test_apps_esp32c6:
|
||||
extends:
|
||||
- .build_clang_test_apps_riscv
|
||||
- .rules:build
|
||||
variables:
|
||||
IDF_TARGET: esp32c6
|
||||
|
||||
build_clang_test_apps_esp32c5:
|
||||
extends:
|
||||
- .build_clang_test_apps_riscv
|
||||
- .rules:build
|
||||
variables:
|
||||
IDF_TARGET: esp32c5
|
||||
|
||||
build_clang_test_apps_esp32h2:
|
||||
extends:
|
||||
- .build_clang_test_apps_riscv
|
||||
- .rules:build
|
||||
variables:
|
||||
IDF_TARGET: esp32h2
|
||||
|
||||
build_clang_test_apps_esp32p4:
|
||||
extends:
|
||||
- .build_clang_test_apps_riscv
|
||||
- .rules:build
|
||||
variables:
|
||||
IDF_TARGET: esp32p4
|
||||
|
||||
######################
|
||||
# Build System Tests #
|
||||
######################
|
||||
.test_build_system_template:
|
||||
stage: host_test
|
||||
extends:
|
||||
- .build_template
|
||||
- .rules:build:check
|
||||
dependencies: # set dependencies to null to avoid missing artifacts issue
|
||||
needs:
|
||||
- job: fast_template_app
|
||||
artifacts: false
|
||||
optional: true
|
||||
artifacts:
|
||||
reports:
|
||||
junit: XUNIT_RESULT.xml
|
||||
paths:
|
||||
- XUNIT_RESULT.xml
|
||||
- test_build_system
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
script:
|
||||
- ${IDF_PATH}/tools/ci/test_configure_ci_environment.sh
|
||||
- cd ${IDF_PATH}/tools/test_build_system
|
||||
- python ${IDF_PATH}/tools/ci/get_known_failure_cases_file.py
|
||||
- pytest
|
||||
--cleanup-idf-copy
|
||||
--parallel-count ${CI_NODE_TOTAL:-1}
|
||||
--parallel-index ${CI_NODE_INDEX:-1}
|
||||
--work-dir ${CI_PROJECT_DIR}/test_build_system
|
||||
--junitxml ${CI_PROJECT_DIR}/XUNIT_RESULT.xml
|
||||
--ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
|
||||
pytest_build_system:
|
||||
extends: .test_build_system_template
|
||||
parallel: 3
|
||||
|
||||
pytest_build_system_macos:
|
||||
extends:
|
||||
- .test_build_system_template
|
||||
- .before_script:build:macos
|
||||
- .after_script:build:macos:upload-failed-job-logs:ccache-show-stats
|
||||
- .rules:build:macos
|
||||
tags:
|
||||
- macos_shell
|
||||
parallel: 3
|
||||
variables:
|
||||
PYENV_VERSION: "3.8"
|
||||
# CCACHE_DIR: "/cache/idf_ccache". On macOS, you cannot write to this folder due to insufficient permissions.
|
||||
CCACHE_DIR: "" # ccache will use "$HOME/Library/Caches/ccache".
|
||||
|
||||
.test_build_system_minimal_cmake_template:
|
||||
extends: .test_build_system_template
|
||||
variables:
|
||||
INSTALL_EXTRA_TOOLS: cmake@3.16.3
|
||||
script:
|
||||
- MINIMAL_SUPPORTED_CMAKE_VERSION=$(echo "${INSTALL_EXTRA_TOOLS}" | sed -n 's/.*cmake@\([0-9.]*\).*/\1/p')
|
||||
- export PATH=$(echo "$PATH" | sed -E "s|/tools/cmake/[0-9.]+|/tools/cmake/${MINIMAL_SUPPORTED_CMAKE_VERSION}|")
|
||||
- ACTUAL_CMAKE_VERSION=$(cmake --version | head -n1 | awk '{print $3}')
|
||||
- |
|
||||
if [ "${ACTUAL_CMAKE_VERSION}" != "${MINIMAL_SUPPORTED_CMAKE_VERSION}" ]; then
|
||||
echo "ERROR: Wrong minimal CMake version! Detected: ${ACTUAL_CMAKE_VERSION}, but should be: ${MINIMAL_SUPPORTED_CMAKE_VERSION}"
|
||||
exit 1
|
||||
fi
|
||||
- ${IDF_PATH}/tools/ci/test_configure_ci_environment.sh
|
||||
- cd ${IDF_PATH}/tools/test_build_system
|
||||
- python ${IDF_PATH}/tools/ci/get_known_failure_cases_file.py
|
||||
- pytest
|
||||
-k cmake
|
||||
--cleanup-idf-copy
|
||||
--work-dir ${CI_PROJECT_DIR}/test_build_system
|
||||
--junitxml ${CI_PROJECT_DIR}/XUNIT_RESULT.xml
|
||||
--ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
|
||||
pytest_build_system_minimal_cmake:
|
||||
extends: .test_build_system_minimal_cmake_template
|
||||
|
||||
pytest_build_system_macos_minimal_cmake:
|
||||
extends:
|
||||
- .test_build_system_minimal_cmake_template
|
||||
- .before_script:build:macos
|
||||
- .after_script:build:macos:upload-failed-job-logs:ccache-show-stats
|
||||
- .rules:build:macos
|
||||
tags:
|
||||
- macos_shell
|
||||
variables:
|
||||
PYENV_VERSION: "3.8"
|
||||
# CCACHE_DIR: "/cache/idf_ccache". On macOS, you cannot write to this folder due to insufficient permissions.
|
||||
CCACHE_DIR: "" # ccache will use "$HOME/Library/Caches/ccache".
|
||||
INSTALL_EXTRA_TOOLS: ninja cmake@3.16.3
|
||||
|
||||
build_docker:
|
||||
extends:
|
||||
- .before_script:minimal
|
||||
- .rules:build:docker
|
||||
stage: host_test
|
||||
needs:
|
||||
- job: manual_gate
|
||||
optional: true
|
||||
image: espressif/docker-builder:1
|
||||
tags: [shiny, dind]
|
||||
variables:
|
||||
DOCKER_TMP_IMAGE_NAME: "idf_tmp_image"
|
||||
script:
|
||||
# use different image tag for different concurrent job
|
||||
- export DOCKER_TMP_IMAGE="${DOCKER_TMP_IMAGE_NAME}:${CI_CONCURRENT_ID:-latest}"
|
||||
- export DOCKER_BUILD_ARGS="--build-arg IDF_CLONE_URL=${CI_REPOSITORY_URL} --build-arg IDF_CLONE_BRANCH_OR_TAG=${CI_COMMIT_REF_NAME} --build-arg IDF_CHECKOUT_REF=${CI_COMMIT_TAG:-$CI_COMMIT_SHA} --build-arg IDF_CLONE_SHALLOW=1 ${EXTRA_DOCKER_BUILD_ARGS:-} "
|
||||
- time docker build --tag ${DOCKER_TMP_IMAGE} ${DOCKER_BUILD_ARGS} tools/docker/
|
||||
# We can't mount $PWD/examples/get-started/blink into the container, see https://gitlab.com/gitlab-org/gitlab-ce/issues/41227.
|
||||
# The workaround mentioned there works, but leaves around directories which need to be cleaned up manually.
|
||||
# Therefore, build a copy of the example located inside the container.
|
||||
- docker run --rm --workdir /opt/esp/idf/examples/get-started/blink ${DOCKER_TMP_IMAGE} idf.py build
|
||||
|
||||
|
||||
# This job builds template app with permutations of targets and optimization levels
|
||||
build_template_app:
|
||||
@@ -307,57 +49,67 @@ build_template_app:
|
||||
- .build_template_app_template
|
||||
- .rules:build
|
||||
stage: host_test
|
||||
dependencies: # set dependencies to null to avoid missing artifacts issue
|
||||
needs:
|
||||
- job: fast_template_app
|
||||
artifacts: false
|
||||
|
||||
########################################
|
||||
# Clang Build Apps Without Tests Cases #
|
||||
########################################
|
||||
.build_cmake_clang_template:
|
||||
extends:
|
||||
- .build_template
|
||||
- .rules:build
|
||||
needs:
|
||||
- job: fast_template_app
|
||||
artifacts: false
|
||||
- pipeline_variables
|
||||
artifacts:
|
||||
paths:
|
||||
# The other artifacts patterns are defined under .idf_ci.toml
|
||||
# Now we're uploading/downloading the binary files from our internal storage server
|
||||
#
|
||||
# keep the log file to help debug
|
||||
- "**/build*/build_log.txt"
|
||||
- "**/build*/size*.json"
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
variables:
|
||||
IDF_TOOLCHAIN: clang
|
||||
TEST_BUILD_OPTS_EXTRA: ""
|
||||
script:
|
||||
# CI specific options start from "--parallel-count xxx". could ignore when running locally
|
||||
- run_cmd idf-build-apps build
|
||||
-p tools/test_apps/system/clang_build_test
|
||||
-t $IDF_TARGET
|
||||
--parallel-count ${CI_NODE_TOTAL:-1}
|
||||
--parallel-index ${CI_NODE_INDEX:-1}
|
||||
--modified-components ${MR_MODIFIED_COMPONENTS}
|
||||
--modified-files ${MR_MODIFIED_FILES}
|
||||
$TEST_BUILD_OPTS_EXTRA
|
||||
|
||||
build_clang_test_apps_xtensa:
|
||||
extends: .build_cmake_clang_template
|
||||
parallel:
|
||||
matrix:
|
||||
- IDF_TARGET: [esp32, esp32s2, esp32s3]
|
||||
|
||||
build_clang_test_apps_riscv:
|
||||
extends: .build_cmake_clang_template
|
||||
variables:
|
||||
# https://reviews.llvm.org/D90108.
|
||||
# GNU 'as' lets .weak override .globl since binutils-gdb
|
||||
# https://github.com/bminor/binutils-gdb/commit/5ca547dc2399a0a5d9f20626d4bf5547c3ccfddd (1996)
|
||||
# while MC lets the last directive win (PR38921).
|
||||
# For RISCV chips we use integrated assembler by default, so suppress this warning to pass CI pipeline.
|
||||
TEST_BUILD_OPTS_EXTRA: "--ignore-warning-str 'changed binding to STB_WEAK'"
|
||||
parallel:
|
||||
matrix:
|
||||
- IDF_TARGET: [esp32c3, esp32c2, esp32c6, esp32c5, esp32h2, esp32p4]
|
||||
|
||||
####################
|
||||
# Dynamic Pipeline #
|
||||
####################
|
||||
generate_build_child_pipeline:
|
||||
extends:
|
||||
- .build_template
|
||||
tags: [fast_run, shiny]
|
||||
dependencies: # set dependencies to null to avoid missing artifacts issue
|
||||
needs:
|
||||
- pipeline_variables
|
||||
- job: baseline_manifest_sha
|
||||
optional: true
|
||||
artifacts:
|
||||
paths:
|
||||
- build_child_pipeline.yml
|
||||
- test_related_apps.txt
|
||||
- non_test_related_apps.txt
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
variables:
|
||||
PYTEST_IGNORE_COLLECT_IMPORT_ERROR: "1"
|
||||
script:
|
||||
# requires basic pytest dependencies
|
||||
- run_cmd bash install.sh --enable-pytest
|
||||
- run_cmd python tools/ci/dynamic_pipelines/scripts/generate_build_child_pipeline.py
|
||||
|
||||
build_child_pipeline:
|
||||
stage: build
|
||||
needs:
|
||||
- job: fast_template_app
|
||||
optional: true
|
||||
artifacts: false
|
||||
- pipeline_variables
|
||||
- generate_build_child_pipeline
|
||||
variables:
|
||||
IS_MR_PIPELINE: $IS_MR_PIPELINE
|
||||
MR_MODIFIED_COMPONENTS: $MR_MODIFIED_COMPONENTS
|
||||
MR_MODIFIED_FILES: $MR_MODIFIED_FILES
|
||||
PARENT_PIPELINE_ID: $CI_PIPELINE_ID
|
||||
BUILD_AND_TEST_ALL_APPS: $BUILD_AND_TEST_ALL_APPS
|
||||
REPORT_EXIT_CODE: $REPORT_EXIT_CODE
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/214340
|
||||
inherit:
|
||||
variables: false
|
||||
trigger:
|
||||
include:
|
||||
- artifact: build_child_pipeline.yml
|
||||
job: generate_build_child_pipeline
|
||||
strategy: depend
|
||||
# !!!
|
||||
# configurations moved to ci/actions/common/idf/
|
||||
# !!!
|
||||
|
||||
@@ -11,9 +11,18 @@ stages:
|
||||
- target_test
|
||||
- host_test
|
||||
- test_deploy
|
||||
- pre_deploy
|
||||
- deploy
|
||||
- post_deploy
|
||||
- retry_failed_jobs
|
||||
|
||||
include:
|
||||
- project: "ci/actions/common"
|
||||
ref: "v2"
|
||||
file: "templates/idf/common-scripts.yml"
|
||||
inputs:
|
||||
ci_python_constraint_file: "espidf.constraints.v5.4.txt"
|
||||
macos_image: "registry.hub.docker.com/espressif/macos-idf:sonoma-v5.4"
|
||||
windows_image: "windows_11_idf_v5_4"
|
||||
|
||||
variables:
|
||||
# System environment
|
||||
@@ -35,16 +44,12 @@ variables:
|
||||
# - set GIT_STRATEGY: "clone" to shiny runners
|
||||
# - set GIT_STRATEGY: "fetch" to brew runners
|
||||
GIT_STRATEGY: clone
|
||||
# we will download archive for each submodule instead of clone.
|
||||
# we don't do "recursive" when fetch submodule as they're not used in CI now.
|
||||
GIT_SUBMODULE_STRATEGY: none
|
||||
# since we're using merged-result pipelines, the last commit should work for most cases
|
||||
GIT_DEPTH: 1
|
||||
# --no-recurse-submodules: we use cache for submodules
|
||||
GIT_SUBMODULE_STRATEGY: none # here we use cache for submodules, so we don't need to fetch them every time
|
||||
# since we're using merged-result pipelines, the last commit should work for most cases
|
||||
# --prune --prune-tags: in case remote branch or tag is force pushed
|
||||
GIT_FETCH_EXTRA_FLAGS: "--no-recurse-submodules --prune --prune-tags"
|
||||
# we're using .cache folder for caches
|
||||
GIT_CLEAN_FLAGS: -ffdx -e .cache/
|
||||
|
||||
LATEST_GIT_TAG: v5.4.4
|
||||
|
||||
SUBMODULE_FETCH_TOOL: "tools/ci/ci_fetch_submodule.py"
|
||||
@@ -63,40 +68,36 @@ variables:
|
||||
ESP_ENV_IMAGE: "${CI_REGISTRY}/ci/images/idf-v5.4:1"
|
||||
ESP_IDF_DOC_ENV_IMAGE: "${CI_REGISTRY}/ci/images/idf-v5.4-docs:1"
|
||||
TARGET_TEST_ENV_IMAGE: "${CI_REGISTRY}/ci/images/idf-v5.4-target-test:1"
|
||||
SONARQUBE_SCANNER_IMAGE: "${CI_DOCKER_REGISTRY}/sonarqube-scanner:5"
|
||||
PRE_COMMIT_IMAGE: "${CI_DOCKER_REGISTRY}/esp-idf-pre-commit:1"
|
||||
|
||||
# cache python dependencies
|
||||
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
|
||||
|
||||
# Set this variable to the branch of idf-constraints repo in order to test a custom Python constraint file. The
|
||||
# branch name must be without the remote part ("origin/"). Keep the variable empty in order to use the constraint
|
||||
# file from https://dl.espressif.com/dl/esp-idf.
|
||||
CI_PYTHON_CONSTRAINT_BRANCH: ""
|
||||
# CI_PYTHON_CONSTRAINT_BRANCH could be defined as component input `ci_python_constraint_branch` in common-scripts above
|
||||
|
||||
# Update the filename for a specific ESP-IDF release. It is used only with CI_PYTHON_CONSTRAINT_BRANCH.
|
||||
CI_PYTHON_CONSTRAINT_FILE: "espidf.constraints.v5.4.txt"
|
||||
# CI_PYTHON_CONSTRAINT_FILE is defined as component input `ci_python_constraint_file` in common-scripts above
|
||||
|
||||
# Set this variable to repository name of a Python tool you wish to install and test in the context of ESP-IDF CI.
|
||||
# Keep the variable empty when not used.
|
||||
CI_PYTHON_TOOL_REPO: ""
|
||||
# CI_PYTHON_TOOL_REPO could be defined as component input `ci_python_tool_repo` in common-scripts above
|
||||
|
||||
# Set this variable to the branch of a Python tool repo specified in CI_PYTHON_TOOL_REPO. The
|
||||
# branch name must be without the remote part ("origin/"). Keep the variable empty when not used.
|
||||
# This is used only if CI_PYTHON_TOOL_REPO is not empty.
|
||||
CI_PYTHON_TOOL_BRANCH: ""
|
||||
# CI_PYTHON_TOOL_BRANCH could be defined as component input `ci_python_tool_branch` in common-scripts above
|
||||
|
||||
# Set this variable to Clang toolchain distro URL to be used.
|
||||
# NOTE: We have separate toolchains for Xtensa and RISCV, therefore jobs for one arch will fail.
|
||||
# This is OK as far as we use CI_CLANG_DISTRO_URL for pre-release tests purposes only.
|
||||
# Keep the variable empty when not used.
|
||||
CI_CLANG_DISTRO_URL: ""
|
||||
# CI_CLANG_DISTRO_URL could be defined as component input `ci_clang_distro_url` in common-scripts above
|
||||
|
||||
# Set this variable to specify the file name for the known failure cases.
|
||||
KNOWN_FAILURE_CASES_FILE_NAME: "5.4.txt"
|
||||
|
||||
IDF_CI_BUILD: 1
|
||||
|
||||
PYTHONWARNINGS: "ignore"
|
||||
# ccache settings
|
||||
# some settings need to set in .gitlab-ci.yml as it takes effect while start-up the job
|
||||
# https://ccache.dev/manual/latest.html#_configuring_ccache
|
||||
@@ -106,295 +107,23 @@ variables:
|
||||
CCACHE_MAXSIZE: "50G"
|
||||
|
||||
FF_USE_NEW_BASH_EVAL_STRATEGY: "true"
|
||||
FORCE_COLOR: "1" # rich print with color
|
||||
|
||||
################################################
|
||||
# `before_script` and `after_script` Templates #
|
||||
################################################
|
||||
.common_before_scripts: &common-before_scripts |
|
||||
source tools/ci/utils.sh
|
||||
|
||||
is_based_on_commits $REQUIRED_ANCESTOR_COMMITS
|
||||
|
||||
if [[ -n "$IDF_DONT_USE_MIRRORS" ]]; then
|
||||
export IDF_MIRROR_PREFIX_MAP=
|
||||
fi
|
||||
|
||||
if echo "$CI_MERGE_REQUEST_LABELS" | egrep "(^|,)include_nightly_run(,|$)"; then
|
||||
export INCLUDE_NIGHTLY_RUN="1"
|
||||
export NIGHTLY_RUN="1"
|
||||
fi
|
||||
|
||||
# configure cmake related flags
|
||||
source tools/ci/configure_ci_environment.sh
|
||||
|
||||
if [[ "$CI_CCACHE_STATS" == 1 ]] && command -v ccache >/dev/null 2>&1 && [[ -n "$CCACHE_STATSLOG" ]]; then
|
||||
mkdir -p "$(dirname "$CCACHE_STATSLOG")"
|
||||
rm -f "$CCACHE_STATSLOG"
|
||||
fi
|
||||
|
||||
# add extra python packages
|
||||
export PYTHONPATH="$IDF_PATH/tools:$IDF_PATH/tools/esp_app_trace:$IDF_PATH/components/partition_table:$IDF_PATH/tools/ci/python_packages:$PYTHONPATH"
|
||||
|
||||
.setup_tools_and_idf_python_venv: &setup_tools_and_idf_python_venv |
|
||||
# must use after setup_tools_except_target_test
|
||||
# otherwise the export.sh won't work properly
|
||||
|
||||
# download constraint file for dev
|
||||
if [[ -n "$CI_PYTHON_CONSTRAINT_BRANCH" ]]; then
|
||||
wget -O /tmp/constraint.txt --header="Authorization:Bearer ${ESPCI_TOKEN}" ${GITLAB_HTTP_SERVER}/api/v4/projects/2581/repository/files/${CI_PYTHON_CONSTRAINT_FILE}/raw?ref=${CI_PYTHON_CONSTRAINT_BRANCH}
|
||||
mkdir -p "${IDF_TOOLS_PATH:-$HOME/.espressif}"
|
||||
mv /tmp/constraint.txt "${IDF_TOOLS_PATH:-$HOME/.espressif}/${CI_PYTHON_CONSTRAINT_FILE}"
|
||||
fi
|
||||
|
||||
# Mirror
|
||||
if [[ -n "$IDF_DONT_USE_MIRRORS" ]]; then
|
||||
export IDF_MIRROR_PREFIX_MAP=
|
||||
fi
|
||||
|
||||
if [[ "${CI_JOB_STAGE}" != "target_test" ]]; then
|
||||
section_start "running_install_sh" "Running install.sh"
|
||||
if [[ "${CI_JOB_STAGE}" == "build_doc" || "${CI_JOB_GROUP_NAME}" == *docs* ]]; then
|
||||
run_cmd bash install.sh --enable-ci --enable-docs
|
||||
elif [[ "${CI_JOB_STAGE}" == "build" ]]; then
|
||||
run_cmd bash install.sh --enable-ci
|
||||
else
|
||||
if ! echo "${CI_JOB_NAME}" | egrep ".*pytest.*"; then
|
||||
run_cmd bash install.sh --enable-ci
|
||||
else
|
||||
run_cmd bash install.sh --enable-ci --enable-pytest --enable-test-specific
|
||||
fi
|
||||
fi
|
||||
section_end "running_install_sh"
|
||||
else
|
||||
section_start "install_python_env" "Install Python environment"
|
||||
run_cmd python tools/idf_tools.py install-python-env --features ci,pytest,test-specific
|
||||
section_end "install_python_env"
|
||||
fi
|
||||
|
||||
if [[ ! -z "$INSTALL_EXTRA_TOOLS" ]]; then
|
||||
section_start "installing_optional_tools" "Install optional tools ${INSTALL_EXTRA_TOOLS}"
|
||||
$IDF_PATH/tools/idf_tools.py --non-interactive install $INSTALL_EXTRA_TOOLS
|
||||
section_end "installing_optional_tools"
|
||||
fi
|
||||
|
||||
# Install esp-clang if necessary (esp-clang is separately installed)
|
||||
if [[ "$IDF_TOOLCHAIN" == "clang" && -z "$CI_CLANG_DISTRO_URL" ]]; then
|
||||
$IDF_PATH/tools/idf_tools.py --non-interactive install esp-clang
|
||||
fi
|
||||
|
||||
if [[ "${CI_JOB_STAGE}" == "target_test" ]]; then
|
||||
section_start "IDF_SKIP_TOOLS_CHECK" "Skip required tools check"
|
||||
export IDF_SKIP_TOOLS_CHECK=1
|
||||
section_end "IDF_SKIP_TOOLS_CHECK"
|
||||
fi
|
||||
section_start "source_export" "Source export.sh"
|
||||
source ./export.sh
|
||||
section_end "source_export"
|
||||
|
||||
# we need the latest DEV release for esptool to be installed in every job without bumping the minimal requirement in
|
||||
# the constraint file.
|
||||
section_start "esptool_upgrade" "Upgrade esptool to the latest development version"
|
||||
pip install --upgrade --pre esptool -c "${IDF_TOOLS_PATH:-$HOME/.espressif}/${CI_PYTHON_CONSTRAINT_FILE}"
|
||||
section_end "esptool_upgrade"
|
||||
|
||||
# Custom clang toolchain
|
||||
if [[ "$IDF_TOOLCHAIN" == "clang" && ! -z "$CI_CLANG_DISTRO_URL" ]]; then
|
||||
echo "Using custom clang from ${CI_CLANG_DISTRO_URL}"
|
||||
wget $CI_CLANG_DISTRO_URL
|
||||
ARCH_NAME=$(basename $CI_CLANG_DISTRO_URL)
|
||||
tar -x -f $ARCH_NAME
|
||||
export PATH=$PWD/esp-clang/bin:$PATH
|
||||
fi
|
||||
|
||||
# Custom OpenOCD
|
||||
if [[ "$CI_JOB_STAGE" == "target_test" ]]; then
|
||||
machine="$(uname -m)"
|
||||
if [[ "$machine" == "armv7l" ]] ; then
|
||||
OOCD_DISTRO_URL="$OOCD_DISTRO_URL_ARMHF"
|
||||
elif [[ "$machine" == "aarch64" ]] ; then
|
||||
OOCD_DISTRO_URL="$OOCD_DISTRO_URL_ARM64"
|
||||
fi
|
||||
if [[ ! -z "$OOCD_DISTRO_URL" ]]; then
|
||||
echo "Using custom OpenOCD from ${OOCD_DISTRO_URL}"
|
||||
wget $OOCD_DISTRO_URL
|
||||
ARCH_NAME=$(basename $OOCD_DISTRO_URL)
|
||||
tar -x -f $ARCH_NAME
|
||||
export OPENOCD_SCRIPTS=$PWD/openocd-esp32/share/openocd/scripts
|
||||
export PATH=$PWD/openocd-esp32/bin:$PATH
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "$CI_PYTHON_TOOL_REPO" ]]; then
|
||||
git clone --quiet --depth=1 -b ${CI_PYTHON_TOOL_BRANCH} https://gitlab-ci-token:${ESPCI_TOKEN}@${GITLAB_HTTPS_HOST}/espressif/${CI_PYTHON_TOOL_REPO}.git
|
||||
pip install ./${CI_PYTHON_TOOL_REPO}
|
||||
rm -rf ${CI_PYTHON_TOOL_REPO}
|
||||
fi
|
||||
|
||||
info "setup tools and python venv done"
|
||||
|
||||
.show_ccache_statistics: &show_ccache_statistics |
|
||||
# Show ccache statistics if enabled globally
|
||||
section_start "ccache_show_stats" "Show ccache statistics"
|
||||
if [[ "$CI_CCACHE_STATS" == 1 ]] && command -v ccache >/dev/null 2>&1; then
|
||||
if ccache --help 2>/dev/null | grep -q -- '--show-log-stats'; then
|
||||
if [[ -n "$CCACHE_STATSLOG" && -f "$CCACHE_STATSLOG" ]]; then
|
||||
ccache --show-log-stats -vv
|
||||
else
|
||||
echo "INFO: No per-job ccache statistics were recorded"
|
||||
fi
|
||||
else
|
||||
ccache --show-stats -vv
|
||||
fi
|
||||
fi || true
|
||||
section_end "ccache_show_stats"
|
||||
|
||||
.upload_failed_job_log_artifacts: &upload_failed_job_log_artifacts |
|
||||
if [ $CI_JOB_STATUS = "failed" ]; then
|
||||
python tools/ci/artifacts_handler.py upload --type logs
|
||||
fi
|
||||
|
||||
.before_script:minimal:
|
||||
before_script:
|
||||
- *common-before_scripts
|
||||
- !reference [.common_before_scripts, minimal]
|
||||
|
||||
.before_script:build:macos:
|
||||
before_script:
|
||||
# macos is running shell executor, which means it would use
|
||||
# the system installed /usr/local/bin/python3 by default.
|
||||
# Ensure pyenv and PYENV_VERSION installed
|
||||
- eval "$(pyenv init -)"
|
||||
- *common-before_scripts
|
||||
# On macOS, these tools need to be installed
|
||||
- export IDF_TOOLS_PATH="${HOME}/.espressif_runner_${CI_RUNNER_ID}_${CI_CONCURRENT_ID}"
|
||||
# remove idf-env.json, since it may contains enabled "features"
|
||||
- rm -f $IDF_TOOLS_PATH/idf-env.json
|
||||
# This adds tools (compilers) and the version-specific Python environment to PATH
|
||||
- *setup_tools_and_idf_python_venv
|
||||
- fetch_submodules
|
||||
variables:
|
||||
INSTALL_EXTRA_TOOLS: cmake ninja
|
||||
|
||||
.after_script:build:macos:upload-failed-job-logs:ccache-show-stats:
|
||||
.after_script:build:
|
||||
after_script:
|
||||
# macos is running shell executor, which means it would use
|
||||
# the system installed /usr/local/bin/python3 by default.
|
||||
# Ensure pyenv and PYENV_VERSION installed
|
||||
- eval "$(pyenv init -)"
|
||||
- *upload_failed_job_log_artifacts
|
||||
- *show_ccache_statistics
|
||||
|
||||
.before_script:build:
|
||||
before_script:
|
||||
- *common-before_scripts
|
||||
- *setup_tools_and_idf_python_venv
|
||||
- add_gitlab_ssh_keys
|
||||
- fetch_submodules
|
||||
- export EXTRA_CFLAGS=${PEDANTIC_CFLAGS}
|
||||
- export EXTRA_CXXFLAGS=${PEDANTIC_CXXFLAGS}
|
||||
|
||||
.after_script:build:ccache-show-stats:
|
||||
after_script:
|
||||
- *show_ccache_statistics
|
||||
|
||||
.after_script:build:ccache-show-stats:upload-failed-job-logs:
|
||||
after_script:
|
||||
- *show_ccache_statistics
|
||||
- *upload_failed_job_log_artifacts
|
||||
|
||||
##############################
|
||||
# Git Strategy Job Templates #
|
||||
##############################
|
||||
.git_init: &git_init |
|
||||
mkdir -p "${CI_PROJECT_DIR}"
|
||||
cd "${CI_PROJECT_DIR}"
|
||||
git init
|
||||
|
||||
.git_fetch_from_mirror_url_if_exists: &git_fetch_from_mirror_url_if_exists |
|
||||
# check if set mirror
|
||||
if [ -n "${LOCAL_GITLAB_HTTPS_HOST:-}" ] && [ -n "${ESPCI_TOKEN:-}" ]; then
|
||||
MIRROR_REPO_URL="https://bot:${ESPCI_TOKEN}@${LOCAL_GITLAB_HTTPS_HOST}/${CI_PROJECT_PATH}"
|
||||
elif [ -n "${LOCAL_GIT_MIRROR:-}" ]; then
|
||||
MIRROR_REPO_URL="${LOCAL_GIT_MIRROR}/${CI_PROJECT_PATH}"
|
||||
fi
|
||||
|
||||
# fetch from mirror first if set
|
||||
if [ -n "${MIRROR_REPO_URL:-}" ]; then
|
||||
if git remote -v | grep origin; then
|
||||
git remote set-url origin "${MIRROR_REPO_URL}"
|
||||
else
|
||||
git remote add origin "${MIRROR_REPO_URL}"
|
||||
fi
|
||||
# mirror url may fail with authentication issue
|
||||
git fetch origin --no-recurse-submodules || true
|
||||
fi
|
||||
|
||||
# set remote url to CI_REPOSITORY_URL
|
||||
if git remote -v | grep origin; then
|
||||
git remote set-url origin "${CI_REPOSITORY_URL}"
|
||||
else
|
||||
git remote add origin "${CI_REPOSITORY_URL}"
|
||||
fi
|
||||
|
||||
.git_checkout_ci_commit_sha: &git_checkout_ci_commit_sha |
|
||||
git checkout $CI_COMMIT_SHA
|
||||
git clean ${GIT_CLEAN_FLAGS}
|
||||
|
||||
# git diff requires two commits, with different CI env var
|
||||
#
|
||||
# By default, we use git strategy "clone" with depth 1 to speed up the clone process.
|
||||
# But for jobs requires running `git diff`, we need to fetch more commits to get the correct diffs.
|
||||
#
|
||||
# Since there's no way to get the correct git_depth before the job starts,
|
||||
# we can't set `GIT_DEPTH` in the job definition.
|
||||
#
|
||||
# Set git strategy to "none" and fetch manually instead.
|
||||
.before_script:fetch:git_diff:
|
||||
variables:
|
||||
GIT_STRATEGY: none
|
||||
before_script:
|
||||
- *git_init
|
||||
- *git_fetch_from_mirror_url_if_exists
|
||||
- !reference [.common_after_scripts, ccache]
|
||||
- run_cmd idf-ci gitlab upload-artifacts --type junit
|
||||
- |
|
||||
# merged results pipelines, by default
|
||||
if [[ -n $CI_MERGE_REQUEST_SOURCE_BRANCH_SHA ]]; then
|
||||
git fetch origin $CI_MERGE_REQUEST_DIFF_BASE_SHA --depth=1 ${GIT_FETCH_EXTRA_FLAGS}
|
||||
git fetch origin $CI_MERGE_REQUEST_SOURCE_BRANCH_SHA --depth=1 ${GIT_FETCH_EXTRA_FLAGS}
|
||||
export GIT_DIFF_OUTPUT=$(git diff --name-only $CI_MERGE_REQUEST_DIFF_BASE_SHA $CI_MERGE_REQUEST_SOURCE_BRANCH_SHA)
|
||||
git fetch origin $CI_COMMIT_SHA --depth=1 ${GIT_FETCH_EXTRA_FLAGS}
|
||||
# merge request pipelines, when the mr got conflicts
|
||||
elif [[ -n $CI_MERGE_REQUEST_DIFF_BASE_SHA ]]; then
|
||||
git fetch origin $CI_MERGE_REQUEST_DIFF_BASE_SHA --depth=1 ${GIT_FETCH_EXTRA_FLAGS}
|
||||
git fetch origin $CI_COMMIT_SHA --depth=1 ${GIT_FETCH_EXTRA_FLAGS}
|
||||
export GIT_DIFF_OUTPUT=$(git diff --name-only $CI_MERGE_REQUEST_DIFF_BASE_SHA $CI_COMMIT_SHA)
|
||||
# other pipelines, like the protected branches pipelines
|
||||
elif [[ "$CI_COMMIT_BEFORE_SHA" != "0000000000000000000000000000000000000000" ]]; then
|
||||
git fetch origin $CI_COMMIT_BEFORE_SHA --depth=1 ${GIT_FETCH_EXTRA_FLAGS}
|
||||
git fetch origin $CI_COMMIT_SHA --depth=1 ${GIT_FETCH_EXTRA_FLAGS}
|
||||
export GIT_DIFF_OUTPUT=$(git diff --name-only $CI_COMMIT_BEFORE_SHA $CI_COMMIT_SHA)
|
||||
else
|
||||
# pipeline source could be web, scheduler, etc.
|
||||
git fetch origin $CI_COMMIT_SHA --depth=2 ${GIT_FETCH_EXTRA_FLAGS}
|
||||
export GIT_DIFF_OUTPUT=$(git diff --name-only $CI_COMMIT_SHA~1 $CI_COMMIT_SHA)
|
||||
if [ $CI_JOB_STATUS = "failed" ]; then
|
||||
run_cmd idf-ci gitlab upload-artifacts --type log
|
||||
fi
|
||||
- *git_checkout_ci_commit_sha
|
||||
- *common-before_scripts
|
||||
- *setup_tools_and_idf_python_venv
|
||||
- add_gitlab_ssh_keys
|
||||
|
||||
# target test runners may locate in different places
|
||||
# for runners set git mirror, we fetch from the mirror first, then fetch the HEAD commit
|
||||
.before_script:fetch:target_test:
|
||||
variables:
|
||||
GIT_STRATEGY: none
|
||||
before_script:
|
||||
- *git_init
|
||||
- *git_fetch_from_mirror_url_if_exists
|
||||
- git fetch origin "${CI_COMMIT_SHA}" --depth=1 ${GIT_FETCH_EXTRA_FLAGS}
|
||||
- *git_checkout_ci_commit_sha
|
||||
- *common-before_scripts
|
||||
- *setup_tools_and_idf_python_venv
|
||||
- add_gitlab_ssh_keys
|
||||
# no submodules
|
||||
|
||||
#############
|
||||
# `default` #
|
||||
@@ -403,12 +132,6 @@ default:
|
||||
cache:
|
||||
# pull only for most of the use cases since it's cache dir.
|
||||
# Only set "push" policy for "upload_cache" stage jobs
|
||||
- key: pip-cache-${LATEST_GIT_TAG}
|
||||
fallback_keys:
|
||||
- pip-cache
|
||||
paths:
|
||||
- .cache/pip
|
||||
policy: pull
|
||||
- key: submodule-cache-${LATEST_GIT_TAG}
|
||||
fallback_keys:
|
||||
- submodule-cache
|
||||
@@ -416,20 +139,8 @@ default:
|
||||
- .cache/submodule_archives
|
||||
policy: pull
|
||||
before_script:
|
||||
- *common-before_scripts
|
||||
- *setup_tools_and_idf_python_venv
|
||||
- !reference [.common_before_scripts, with_tools]
|
||||
- add_gitlab_ssh_keys
|
||||
- fetch_submodules
|
||||
# gitlab bug, setting them here doesn't work
|
||||
# - expire_in: https://gitlab.com/gitlab-org/gitlab/-/issues/404563
|
||||
# - when: https://gitlab.com/gitlab-org/gitlab/-/issues/440672
|
||||
# artifacts:
|
||||
# expire_in: 1 week
|
||||
# when: always
|
||||
retry:
|
||||
max: 2
|
||||
when:
|
||||
# In case of a runner failure we could hop to another one, or a network error could go away.
|
||||
- runner_system_failure
|
||||
# Job execution timeout may be caused by a network issue.
|
||||
- job_execution_timeout
|
||||
- export EXTRA_CFLAGS=${PEDANTIC_CFLAGS}
|
||||
- export EXTRA_CXXFLAGS=${PEDANTIC_CXXFLAGS}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# this file support two keywords:
|
||||
# - extra_default_build_targets:
|
||||
# besides of the SUPPORTED_TARGETS in IDF,
|
||||
# enable build for the specified targets by default as well.
|
||||
# - bypass_check_test_targets:
|
||||
# suppress the check_build_test_rules check-test-script warnings for the specified targets
|
||||
#
|
||||
# This file should ONLY be used during bringup. Should be reset to empty after the bringup process
|
||||
extra_default_build_targets:
|
||||
|
||||
|
||||
bypass_check_test_targets:
|
||||
- esp32c5
|
||||
- esp32c61
|
||||
|
||||
#
|
||||
# These lines would
|
||||
# - enable the README.md check for esp32c6. Don't forget to add the build jobs in .gitlab/ci/build.yml
|
||||
# - disable the test script check with the manifest file.
|
||||
#
|
||||
@@ -1,19 +1,3 @@
|
||||
.all_targets: &all_targets
|
||||
- esp32
|
||||
- esp32s2
|
||||
- esp32s3
|
||||
- esp32c3
|
||||
- esp32c2
|
||||
- esp32c6
|
||||
- esp32c5
|
||||
- esp32h2
|
||||
- esp32p4
|
||||
|
||||
.target_test: &target_test
|
||||
- example_test
|
||||
- custom_test
|
||||
- component_ut
|
||||
|
||||
##############
|
||||
# Build Jobs #
|
||||
##############
|
||||
@@ -25,7 +9,6 @@
|
||||
- build_system
|
||||
- downloadable-tools
|
||||
included_in:
|
||||
- build:target_test
|
||||
- build:check
|
||||
|
||||
# -------------------
|
||||
@@ -57,8 +40,6 @@
|
||||
"patterns:template-app":
|
||||
patterns:
|
||||
- build_template-app
|
||||
included_in:
|
||||
- build:target_test
|
||||
|
||||
"patterns:build-check":
|
||||
patterns:
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
.deploy_job_template:
|
||||
stage: deploy
|
||||
image: $ESP_ENV_IMAGE
|
||||
tags: [ deploy ]
|
||||
|
||||
# Check this before push_to_github
|
||||
check_submodule_sync:
|
||||
extends:
|
||||
- .deploy_job_template
|
||||
- .rules:test:submodule
|
||||
stage: test_deploy
|
||||
tags: [ brew, github_sync ]
|
||||
retry: 2
|
||||
variables:
|
||||
# for brew runners, we always set GIT_STRATEGY to fetch
|
||||
GIT_STRATEGY: fetch
|
||||
SUBMODULES_TO_FETCH: "none"
|
||||
PUBLIC_IDF_URL: "https://github.com/espressif/esp-idf.git"
|
||||
dependencies: []
|
||||
script:
|
||||
- git submodule deinit --force .
|
||||
- rm -rf .git/modules # remove all the cached metadata
|
||||
# setting the default remote URL to the public one, to resolve relative location URLs
|
||||
- git config remote.origin.url ${PUBLIC_IDF_URL}
|
||||
# check if all submodules are correctly synced to public repository
|
||||
- git submodule init
|
||||
- git config --get-regexp '^submodule\..*\.url$' || true
|
||||
- git submodule update --recursive
|
||||
- echo "IDF was cloned from ${PUBLIC_IDF_URL} completely"
|
||||
|
||||
push_to_github:
|
||||
extends:
|
||||
- .deploy_job_template
|
||||
- .before_script:minimal
|
||||
- .rules:push_to_github
|
||||
needs:
|
||||
- check_submodule_sync
|
||||
tags: [ brew, github_sync ]
|
||||
variables:
|
||||
# for brew runners, we always set GIT_STRATEGY to fetch
|
||||
GIT_STRATEGY: fetch
|
||||
# github also need full record of commits
|
||||
GIT_DEPTH: 0
|
||||
script:
|
||||
- add_github_ssh_keys
|
||||
- git remote remove github &>/dev/null || true
|
||||
- git remote add github git@github.com:espressif/esp-idf.git
|
||||
- tools/ci/push_to_github.sh
|
||||
@@ -2,18 +2,17 @@
|
||||
extends: .rules:test:host_test
|
||||
stage: host_test
|
||||
image: $ESP_ENV_IMAGE
|
||||
tags:
|
||||
- host_test
|
||||
tags: [build, shiny]
|
||||
dependencies: # set dependencies to null to avoid missing artifacts issue
|
||||
# run host_test jobs immediately, only after upload cache
|
||||
needs:
|
||||
- job: upload-pip-cache
|
||||
optional: true
|
||||
artifacts: false
|
||||
- pipeline_variables
|
||||
- job: upload-submodules-cache
|
||||
optional: true
|
||||
artifacts: false
|
||||
- pipeline_variables
|
||||
- job: fast_template_app
|
||||
optional: true
|
||||
artifacts: false
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
@@ -23,16 +22,28 @@ check_public_headers:
|
||||
- .host_test_template
|
||||
- .rules:build:check
|
||||
script:
|
||||
- IDF_TARGET=esp32 python tools/ci/check_public_headers.py --jobs 4 --prefix xtensa-esp32-elf-
|
||||
- IDF_TARGET=esp32s2 python tools/ci/check_public_headers.py --jobs 4 --prefix xtensa-esp32s2-elf-
|
||||
- IDF_TARGET=esp32s3 python tools/ci/check_public_headers.py --jobs 4 --prefix xtensa-esp32s3-elf-
|
||||
- IDF_TARGET=esp32c3 python tools/ci/check_public_headers.py --jobs 4 --prefix riscv32-esp-elf-
|
||||
- IDF_TARGET=esp32c2 python tools/ci/check_public_headers.py --jobs 4 --prefix riscv32-esp-elf-
|
||||
- IDF_TARGET=esp32c6 python tools/ci/check_public_headers.py --jobs 4 --prefix riscv32-esp-elf-
|
||||
- IDF_TARGET=esp32c5 python tools/ci/check_public_headers.py --jobs 4 --prefix riscv32-esp-elf-
|
||||
- IDF_TARGET=esp32h2 python tools/ci/check_public_headers.py --jobs 4 --prefix riscv32-esp-elf-
|
||||
- IDF_TARGET=esp32p4 python tools/ci/check_public_headers.py --jobs 4 --prefix riscv32-esp-elf-
|
||||
- IDF_TARGET=esp32c61 python tools/ci/check_public_headers.py --jobs 4 --prefix riscv32-esp-elf-
|
||||
- |
|
||||
targets="esp32 \
|
||||
esp32s2 \
|
||||
esp32s3 \
|
||||
esp32c3 \
|
||||
esp32c2 \
|
||||
esp32c6 \
|
||||
esp32c5 \
|
||||
esp32h2 \
|
||||
esp32p4 \
|
||||
esp32c61"
|
||||
for target in ${targets}; do
|
||||
case "${target}" in
|
||||
esp32|esp32s2|esp32s3)
|
||||
PREFIX="xtensa-${target}-elf-"
|
||||
;;
|
||||
*)
|
||||
PREFIX="riscv32-esp-elf-"
|
||||
;;
|
||||
esac
|
||||
IDF_TARGET=${target} python tools/ci/check_public_headers.py --jobs 4 --prefix ${PREFIX}
|
||||
done
|
||||
|
||||
test_nvs_coverage:
|
||||
extends:
|
||||
@@ -51,14 +62,15 @@ test_partition_table_on_host:
|
||||
extends: .host_test_template
|
||||
script:
|
||||
- cd components/partition_table/test_gen_esp32part_host
|
||||
- ./gen_esp32part_tests.py
|
||||
- pytest_for_ut ./gen_esp32part_tests.py
|
||||
|
||||
test_ldgen_on_host:
|
||||
extends: .host_test_template
|
||||
extends:
|
||||
- .host_test_template
|
||||
script:
|
||||
- cd tools/ldgen/test
|
||||
- export PYTHONPATH=$PYTHONPATH:..
|
||||
- python -m unittest
|
||||
- pytest_for_ut .
|
||||
variables:
|
||||
LC_ALL: C.UTF-8
|
||||
|
||||
@@ -66,15 +78,13 @@ test_spiffs_on_host:
|
||||
extends: .host_test_template
|
||||
script:
|
||||
- cd components/spiffs/test_spiffsgen/
|
||||
- ./test_spiffsgen.py
|
||||
- pytest_for_ut ./test_spiffsgen.py
|
||||
|
||||
test_fatfsgen_on_host:
|
||||
extends: .host_test_template
|
||||
script:
|
||||
- cd components/fatfs/test_fatfsgen/
|
||||
- ./test_fatfsgen.py
|
||||
- ./test_wl_fatfsgen.py
|
||||
- ./test_fatfsparse.py
|
||||
- pytest_for_ut ./test_fatfsgen.py ./test_wl_fatfsgen.py ./test_fatfsparse.py
|
||||
|
||||
test_multi_heap_on_host:
|
||||
extends: .host_test_template
|
||||
@@ -86,7 +96,7 @@ test_certificate_bundle_on_host:
|
||||
extends: .host_test_template
|
||||
script:
|
||||
- cd components/mbedtls/esp_crt_bundle/test_gen_crt_bundle/
|
||||
- ./test_gen_crt_bundle.py
|
||||
- pytest_for_ut ./test_gen_crt_bundle.py
|
||||
|
||||
test_gdbstub_on_host:
|
||||
extends: .host_test_template
|
||||
@@ -115,15 +125,17 @@ test_cli_installer:
|
||||
- python3 ${IDF_PATH}/tools/idf_tools.py download required qemu-riscv32 qemu-xtensa cmake cmake@3.16.3
|
||||
- cd ${IDF_PATH}/tools/test_idf_tools
|
||||
- python3 -m pip install jsonschema --break-system-packages
|
||||
# Testing with system python3, so don't use any third-party packages
|
||||
- python3 ./test_idf_tools.py -v
|
||||
- python3 ./test_idf_tools_python_env.py
|
||||
# It runs at the end because it modifies dependencies
|
||||
- IDF_TEST_MAY_BREAK_DEPENDENCIES=1 python3 ./test_idf_tools.py -v TestSystemDependencies.test_commands_when_nodeps
|
||||
|
||||
.test_efuse_table_on_host_template:
|
||||
test_efuse_table_on_host:
|
||||
extends: .host_test_template
|
||||
variables:
|
||||
IDF_TARGET: "esp32"
|
||||
parallel:
|
||||
matrix:
|
||||
- IDF_TARGET: [esp32, esp32s2, esp32c3, esp32s3, esp32c6, esp32h2]
|
||||
artifacts:
|
||||
when: on_failure
|
||||
paths:
|
||||
@@ -133,35 +145,7 @@ test_cli_installer:
|
||||
- ./efuse_table_gen.py -t "${IDF_TARGET}" ${IDF_PATH}/components/efuse/${IDF_TARGET}/esp_efuse_table.csv
|
||||
- git diff --exit-code -- ${IDF_TARGET}/esp_efuse_table.c || { echo 'Differences found for ${IDF_TARGET} target. Please run idf.py efuse-common-table and commit the changes.'; exit 1; }
|
||||
- cd ${IDF_PATH}/components/efuse/test_efuse_host
|
||||
- ./efuse_tests.py
|
||||
|
||||
test_efuse_table_on_host_esp32:
|
||||
extends: .test_efuse_table_on_host_template
|
||||
|
||||
test_efuse_table_on_host_esp32s2:
|
||||
extends: .test_efuse_table_on_host_template
|
||||
variables:
|
||||
IDF_TARGET: esp32s2
|
||||
|
||||
test_efuse_table_on_host_esp32s3:
|
||||
extends: .test_efuse_table_on_host_template
|
||||
variables:
|
||||
IDF_TARGET: esp32s3
|
||||
|
||||
test_efuse_table_on_host_esp32c3:
|
||||
extends: .test_efuse_table_on_host_template
|
||||
variables:
|
||||
IDF_TARGET: esp32c3
|
||||
|
||||
test_efuse_table_on_host_esp32h2:
|
||||
extends: .test_efuse_table_on_host_template
|
||||
variables:
|
||||
IDF_TARGET: esp32h2
|
||||
|
||||
test_efuse_table_on_host_esp32c6:
|
||||
extends: .test_efuse_table_on_host_template
|
||||
variables:
|
||||
IDF_TARGET: esp32c6
|
||||
- pytest_for_ut ./efuse_tests.py
|
||||
|
||||
test_logtrace_proc:
|
||||
extends: .host_test_template
|
||||
@@ -199,16 +183,17 @@ test_tools:
|
||||
INSTALL_EXTRA_TOOLS: "qemu-xtensa qemu-riscv32" # for test_idf_qemu.py
|
||||
script:
|
||||
- stat=0
|
||||
- run_cmd idf-ci gitlab download-known-failure-cases-file ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
- cd ${IDF_PATH}/tools/ci/test_autocomplete
|
||||
- pytest --noconftest test_autocomplete.py --junitxml=${IDF_PATH}/XUNIT_AUTOCOMP.xml || stat=1
|
||||
- run_cmd pytest --noconftest test_autocomplete.py --junitxml=${IDF_PATH}/XUNIT_AUTOCOMP.xml --ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME} || stat=1
|
||||
- cd ${IDF_PATH}/tools/test_idf_py
|
||||
- pytest --noconftest test_idf_py.py --junitxml=${IDF_PATH}/XUNIT_IDF_PY.xml || stat=1
|
||||
- pytest --noconftest test_hints.py --junitxml=${IDF_PATH}/XUNIT_HINTS.xml || stat=1
|
||||
- pytest --noconftest test_idf_qemu.py --junitxml=${IDF_PATH}/XUNIT_IDF_PY_QEMU.xml || stat=1
|
||||
- run_cmd pytest --noconftest test_idf_py.py --junitxml=${IDF_PATH}/XUNIT_IDF_PY.xml --ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME} || stat=1
|
||||
- run_cmd pytest --noconftest test_hints.py --junitxml=${IDF_PATH}/XUNIT_HINTS.xml --ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME} || stat=1
|
||||
- run_cmd pytest --noconftest test_idf_qemu.py --junitxml=${IDF_PATH}/XUNIT_IDF_PY_QEMU.xml --ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME} || stat=1
|
||||
- cd ${IDF_PATH}/tools/test_mkdfu
|
||||
- pytest --noconftest test_mkdfu.py --junitxml=${IDF_PATH}/XUNIT_MKDFU.xml || stat=1
|
||||
- run_cmd pytest --noconftest test_mkdfu.py --junitxml=${IDF_PATH}/XUNIT_MKDFU.xml --ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME} || stat=1
|
||||
- cd ${IDF_PATH}/tools/test_idf_size
|
||||
- pytest --noconftest test_idf_size.py --junitxml=${IDF_PATH}/XUNIT_IDF_SIZE.xml || stat=1
|
||||
- run_cmd pytest --noconftest test_idf_size.py --junitxml=${IDF_PATH}/XUNIT_IDF_SIZE.xml --ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME} || stat=1
|
||||
- cd ${IDF_PATH}
|
||||
- shellcheck -s sh tools/detect_python.sh || stat=1
|
||||
- shellcheck -s bash tools/detect_python.sh || stat=1
|
||||
@@ -223,12 +208,15 @@ test_split_path_by_spaces:
|
||||
extends: .host_test_template
|
||||
script:
|
||||
- cd ${IDF_PATH}/tools
|
||||
- python -m unittest split_paths_by_spaces.py
|
||||
- pytest_for_ut ./split_paths_by_spaces.py
|
||||
|
||||
test_mqtt_on_host:
|
||||
extends: .host_test_template
|
||||
script:
|
||||
- cd ${IDF_PATH}/components/mqtt/esp-mqtt/host_test
|
||||
# no -Werror
|
||||
- export EXTRA_CFLAGS=
|
||||
- export EXTRA_CXXFLAGS=
|
||||
- idf.py build
|
||||
- LSAN_OPTIONS=verbosity=1:log_threads=1 build/host_mqtt_client_test.elf
|
||||
|
||||
@@ -269,122 +257,13 @@ test_gen_soc_caps_kconfig:
|
||||
extends: .host_test_template
|
||||
script:
|
||||
- cd ${IDF_PATH}/tools/gen_soc_caps_kconfig/
|
||||
- ./test/test_gen_soc_caps_kconfig.py
|
||||
|
||||
test_pytest_qemu:
|
||||
extends:
|
||||
- .host_test_template
|
||||
- .before_script:build
|
||||
artifacts:
|
||||
paths:
|
||||
- XUNIT_RESULT.xml
|
||||
- pytest-embedded/
|
||||
- "**/build*/*.bin"
|
||||
reports:
|
||||
junit: XUNIT_RESULT.xml
|
||||
parallel:
|
||||
matrix:
|
||||
- IDF_TARGET: "esp32"
|
||||
INSTALL_EXTRA_TOOLS: "qemu-xtensa"
|
||||
- IDF_TARGET: "esp32c3"
|
||||
INSTALL_EXTRA_TOOLS: "qemu-riscv32"
|
||||
script:
|
||||
- run_cmd python tools/ci/ci_build_apps.py . -v
|
||||
--target $IDF_TARGET
|
||||
--pytest-apps
|
||||
-m qemu
|
||||
--collect-app-info "list_job_${CI_JOB_NAME_SLUG}.txt"
|
||||
--modified-components ${MR_MODIFIED_COMPONENTS}
|
||||
--modified-files ${MR_MODIFIED_FILES}
|
||||
- python tools/ci/get_known_failure_cases_file.py
|
||||
- run_cmd pytest
|
||||
--target $IDF_TARGET
|
||||
--log-cli-level DEBUG
|
||||
-m qemu
|
||||
--embedded-services idf,qemu
|
||||
--junitxml=XUNIT_RESULT.xml
|
||||
--ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
--app-info-filepattern \"list_job_*.txt\"
|
||||
--qemu-extra-args \"-global driver=timer.$IDF_TARGET.timg,property=wdt_disable,value=true\"
|
||||
|
||||
test_pytest_linux:
|
||||
extends:
|
||||
- .host_test_template
|
||||
- .before_script:build
|
||||
artifacts:
|
||||
paths:
|
||||
- XUNIT_RESULT.xml
|
||||
- pytest-embedded/
|
||||
- "**/build*/build_log.txt"
|
||||
reports:
|
||||
junit: XUNIT_RESULT.xml
|
||||
script:
|
||||
- run_cmd python tools/ci/ci_build_apps.py components examples tools/test_apps -v
|
||||
--target linux
|
||||
--pytest-apps
|
||||
-m host_test
|
||||
--collect-app-info "list_job_${CI_JOB_NAME_SLUG}.txt"
|
||||
--modified-components ${MR_MODIFIED_COMPONENTS}
|
||||
--modified-files ${MR_MODIFIED_FILES}
|
||||
- python tools/ci/get_known_failure_cases_file.py
|
||||
- run_cmd pytest
|
||||
--target linux
|
||||
-m host_test
|
||||
--embedded-services idf
|
||||
--junitxml=XUNIT_RESULT.xml
|
||||
--ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
--app-info-filepattern \"list_job_*.txt\"
|
||||
|
||||
test_pytest_macos:
|
||||
extends:
|
||||
- .host_test_template
|
||||
- .before_script:build:macos
|
||||
tags:
|
||||
- macos_shell
|
||||
artifacts:
|
||||
paths:
|
||||
- XUNIT_RESULT.xml
|
||||
- pytest-embedded/
|
||||
- "**/build*/build_log.txt"
|
||||
reports:
|
||||
junit: XUNIT_RESULT.xml
|
||||
variables:
|
||||
PYTEST_IGNORE_COLLECT_IMPORT_ERROR: "1"
|
||||
script:
|
||||
- run_cmd python tools/ci/ci_build_apps.py components examples tools/test_apps -v
|
||||
--target linux
|
||||
--pytest-apps
|
||||
-m \"host_test and macos_shell\"
|
||||
--collect-app-info "list_job_${CI_JOB_NAME_SLUG}.txt"
|
||||
--modified-components ${MR_MODIFIED_COMPONENTS}
|
||||
--modified-files ${MR_MODIFIED_FILES}
|
||||
- python tools/ci/get_known_failure_cases_file.py
|
||||
- run_cmd pytest
|
||||
--target linux
|
||||
-m \"host_test and macos_shell\"
|
||||
--junitxml=XUNIT_RESULT.xml
|
||||
--ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
--app-info-filepattern \"list_job_*.txt\"
|
||||
|
||||
test_idf_pytest_plugin:
|
||||
extends:
|
||||
- .host_test_template
|
||||
- .rules:patterns:idf-pytest-plugin
|
||||
variables:
|
||||
SUBMODULES_TO_FETCH: "none"
|
||||
artifacts:
|
||||
reports:
|
||||
junit: XUNIT_RESULT.xml
|
||||
script:
|
||||
- cd ${IDF_PATH}/tools/ci/dynamic_pipelines/tests/test_report_generator
|
||||
- python -m unittest test_report_generator.py
|
||||
- cd ${IDF_PATH}/tools/ci/idf_pytest
|
||||
- pytest --junitxml=${CI_PROJECT_DIR}/XUNIT_RESULT.xml
|
||||
- pytest_for_ut ./test/test_gen_soc_caps_kconfig.py
|
||||
|
||||
test_idf_build_apps_load_soc_caps:
|
||||
extends: .host_test_template
|
||||
script:
|
||||
- python tools/ci/check_soc_headers_load_in_idf_build_apps.py
|
||||
- cd tools/ci
|
||||
- pytest_for_ut ./check_soc_headers_load_in_idf_build_apps.py
|
||||
|
||||
test_nvs_gen_check:
|
||||
extends: .host_test_template
|
||||
@@ -398,7 +277,7 @@ test_nvs_gen_check:
|
||||
LC_ALL: C.UTF-8
|
||||
script:
|
||||
- cd ${IDF_PATH}/components/nvs_flash/nvs_partition_tool
|
||||
- pytest --noconftest test_nvs_gen_check.py --junitxml=XUNIT_RESULT.xml
|
||||
- pytest_for_ut test_nvs_gen_check.py
|
||||
|
||||
test_esp_rom:
|
||||
extends: .host_test_template
|
||||
@@ -409,7 +288,7 @@ test_esp_rom:
|
||||
junit: XUNIT_RESULT.xml
|
||||
script:
|
||||
- cd ${IDF_PATH}/components/esp_rom/
|
||||
- pytest --noconftest test_esp_rom.py --junitxml=XUNIT_RESULT.xml
|
||||
- pytest_for_ut test_esp_rom.py
|
||||
|
||||
make_sure_soc_caps_compatible_in_idf_build_apps:
|
||||
extends:
|
||||
@@ -428,3 +307,191 @@ make_sure_soc_caps_compatible_in_idf_build_apps:
|
||||
- git checkout $CI_COMMIT_SHA -- tools/ci/idf_build_apps_dump_soc_caps.py
|
||||
- python tools/ci/idf_build_apps_dump_soc_caps.py base.json
|
||||
- diff new.json base.json
|
||||
|
||||
build_docker:
|
||||
extends:
|
||||
- .before_script:minimal
|
||||
- .rules:build:docker
|
||||
stage: host_test
|
||||
needs:
|
||||
- job: manual_gate
|
||||
optional: true
|
||||
image: espressif/docker-builder:1
|
||||
tags: [shiny, dind]
|
||||
variables:
|
||||
DOCKER_TMP_IMAGE_NAME: "idf_tmp_image"
|
||||
script:
|
||||
# use different image tag for different concurrent job
|
||||
- export DOCKER_TMP_IMAGE="${DOCKER_TMP_IMAGE_NAME}:${CI_CONCURRENT_ID:-latest}"
|
||||
- export DOCKER_BUILD_ARGS="--build-arg IDF_CLONE_URL=${CI_REPOSITORY_URL} --build-arg IDF_CLONE_BRANCH_OR_TAG=${CI_COMMIT_REF_NAME} --build-arg IDF_CHECKOUT_REF=${CI_COMMIT_TAG:-$CI_COMMIT_SHA} --build-arg IDF_CLONE_SHALLOW=1 ${EXTRA_DOCKER_BUILD_ARGS:-} "
|
||||
- time docker build --tag ${DOCKER_TMP_IMAGE} ${DOCKER_BUILD_ARGS} tools/docker/
|
||||
# We can't mount $PWD/examples/get-started/blink into the container, see https://gitlab.com/gitlab-org/gitlab-ce/issues/41227.
|
||||
# The workaround mentioned there works, but leaves around directories which need to be cleaned up manually.
|
||||
# Therefore, build a copy of the example located inside the container.
|
||||
- docker run --rm --workdir /opt/esp/idf/examples/get-started/blink ${DOCKER_TMP_IMAGE} idf.py build
|
||||
|
||||
############################
|
||||
# Host test with test apps #
|
||||
############################
|
||||
test_pytest_qemu:
|
||||
extends:
|
||||
- .host_test_template
|
||||
artifacts:
|
||||
paths:
|
||||
- XUNIT_RESULT.xml
|
||||
- pytest-embedded/
|
||||
- "**/build*/*.bin"
|
||||
reports:
|
||||
junit: XUNIT_RESULT.xml
|
||||
parallel:
|
||||
matrix:
|
||||
- IDF_TARGET: "esp32"
|
||||
INSTALL_EXTRA_TOOLS: "qemu-xtensa"
|
||||
# Skip Clang + Xtensa tests due to bootloader size issue
|
||||
IDF_TOOLCHAIN: [gcc]
|
||||
- IDF_TARGET: "esp32c3"
|
||||
INSTALL_EXTRA_TOOLS: "qemu-riscv32"
|
||||
IDF_TOOLCHAIN: [gcc, clang]
|
||||
script:
|
||||
- run_cmd idf-ci build run
|
||||
--build-system cmake
|
||||
--target $IDF_TARGET
|
||||
-m qemu
|
||||
--modified-files ${MR_MODIFIED_FILES}
|
||||
- run_cmd idf-ci gitlab download-known-failure-cases-file ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
- run_cmd pytest
|
||||
--target $IDF_TARGET
|
||||
-m 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\"
|
||||
|
||||
test_pytest_linux:
|
||||
extends:
|
||||
- .host_test_template
|
||||
artifacts:
|
||||
paths:
|
||||
- XUNIT_RESULT.xml
|
||||
- pytest-embedded/
|
||||
- "**/build*/build_log.txt"
|
||||
reports:
|
||||
junit: XUNIT_RESULT.xml
|
||||
script:
|
||||
- run_cmd idf-ci build run
|
||||
--build-system cmake
|
||||
-p components -p examples -p tools/test_apps
|
||||
--target linux
|
||||
--only-test-related
|
||||
--modified-files ${MR_MODIFIED_FILES}
|
||||
- run_cmd idf-ci gitlab download-known-failure-cases-file ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
- run_cmd pytest
|
||||
--target linux
|
||||
-m \"not macos\"
|
||||
--junitxml=XUNIT_RESULT.xml
|
||||
--ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
|
||||
test_pytest_macos:
|
||||
extends:
|
||||
- .host_test_template
|
||||
- .macos-settings
|
||||
artifacts:
|
||||
paths:
|
||||
- XUNIT_RESULT.xml
|
||||
- pytest-embedded/
|
||||
- "**/build*/build_log.txt"
|
||||
reports:
|
||||
junit: XUNIT_RESULT.xml
|
||||
script:
|
||||
- run_cmd idf-ci build run
|
||||
-p components -p examples -p tools/test_apps
|
||||
--build-system cmake
|
||||
--target linux
|
||||
--only-test-related
|
||||
-m macos
|
||||
--modified-files ${MR_MODIFIED_FILES}
|
||||
- run_cmd idf-ci gitlab download-known-failure-cases-file ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
- run_cmd pytest
|
||||
--target linux
|
||||
-m macos
|
||||
--junitxml=XUNIT_RESULT.xml
|
||||
--ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
|
||||
######################
|
||||
# Build System Tests #
|
||||
######################
|
||||
.test_build_system_template:
|
||||
stage: host_test
|
||||
extends:
|
||||
- .build_template
|
||||
- .rules:build:check
|
||||
dependencies: # set dependencies to null to avoid missing artifacts issue
|
||||
needs:
|
||||
- job: fast_template_app
|
||||
artifacts: false
|
||||
optional: true
|
||||
artifacts:
|
||||
reports:
|
||||
junit: XUNIT_RESULT.xml
|
||||
paths:
|
||||
- XUNIT_RESULT.xml
|
||||
- test_build_system
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
script:
|
||||
- ${IDF_PATH}/tools/ci/test_configure_ci_environment.sh
|
||||
- cd ${IDF_PATH}/tools/test_build_system
|
||||
- run_cmd idf-ci gitlab download-known-failure-cases-file ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
- pytest
|
||||
--cleanup-idf-copy
|
||||
--parallel-count ${CI_NODE_TOTAL:-1}
|
||||
--parallel-index ${CI_NODE_INDEX:-1}
|
||||
--work-dir ${CI_PROJECT_DIR}/test_build_system
|
||||
--junitxml ${CI_PROJECT_DIR}/XUNIT_RESULT.xml
|
||||
--ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
|
||||
pytest_build_system:
|
||||
extends: .test_build_system_template
|
||||
parallel: 3
|
||||
|
||||
pytest_build_system_macos:
|
||||
extends:
|
||||
- .test_build_system_template
|
||||
- .macos-settings
|
||||
- .rules:build:macos
|
||||
parallel: 3
|
||||
|
||||
.test_build_system_minimal_cmake_template:
|
||||
extends: .test_build_system_template
|
||||
variables:
|
||||
INSTALL_EXTRA_TOOLS: cmake@3.16.3
|
||||
script:
|
||||
- MINIMAL_SUPPORTED_CMAKE_VERSION=$(echo "${INSTALL_EXTRA_TOOLS}" | sed -n 's/.*cmake@\([0-9.]*\).*/\1/p')
|
||||
- export PATH=$(echo "$PATH" | sed -E "s|/tools/cmake/[0-9.]+|/tools/cmake/${MINIMAL_SUPPORTED_CMAKE_VERSION}|")
|
||||
- ACTUAL_CMAKE_VERSION=$(cmake --version | head -n1 | awk '{print $3}')
|
||||
- |
|
||||
if [ "${ACTUAL_CMAKE_VERSION}" != "${MINIMAL_SUPPORTED_CMAKE_VERSION}" ]; then
|
||||
echo "ERROR: Wrong minimal CMake version! Detected: ${ACTUAL_CMAKE_VERSION}, but should be: ${MINIMAL_SUPPORTED_CMAKE_VERSION}"
|
||||
exit 1
|
||||
fi
|
||||
- ${IDF_PATH}/tools/ci/test_configure_ci_environment.sh
|
||||
- cd ${IDF_PATH}/tools/test_build_system
|
||||
- run_cmd idf-ci gitlab download-known-failure-cases-file ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
- pytest
|
||||
-k cmake
|
||||
--cleanup-idf-copy
|
||||
--parallel-count ${CI_NODE_TOTAL:-1}
|
||||
--parallel-index ${CI_NODE_INDEX:-1}
|
||||
--work-dir ${CI_PROJECT_DIR}/test_build_system
|
||||
--junitxml ${CI_PROJECT_DIR}/XUNIT_RESULT.xml
|
||||
--ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
|
||||
pytest_build_system_minimal_cmake:
|
||||
extends: .test_build_system_minimal_cmake_template
|
||||
|
||||
pytest_build_system_macos_minimal_cmake:
|
||||
extends:
|
||||
- .test_build_system_minimal_cmake_template
|
||||
- .macos-settings
|
||||
- .rules:build:macos
|
||||
variables:
|
||||
INSTALL_EXTRA_TOOLS: ninja cmake@3.16.3
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
generate_failed_jobs_report:
|
||||
stage: post_deploy
|
||||
tags: [build, shiny]
|
||||
image: $ESP_ENV_IMAGE
|
||||
when: always
|
||||
dependencies: [] # Do not download artifacts from the previous stages
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
paths:
|
||||
- job_report.html
|
||||
script:
|
||||
- python tools/ci/dynamic_pipelines/scripts/generate_report.py --report-type job
|
||||
@@ -1,16 +1,9 @@
|
||||
.pre_check_template:
|
||||
stage: pre_check
|
||||
image: $ESP_ENV_IMAGE
|
||||
tags:
|
||||
- host_test
|
||||
dependencies: # set dependencies to null to avoid missing artifacts issue
|
||||
|
||||
check_version:
|
||||
# Don't run this for feature/bugfix branches, so that it is possible to modify
|
||||
# esp_idf_version.h in a branch before tagging the next version.
|
||||
extends:
|
||||
- .pre_check_template
|
||||
- .rules:protected
|
||||
- .rules:protected:check
|
||||
tags: [ brew, github_sync ]
|
||||
variables:
|
||||
# need a full clone to get the latest tag
|
||||
@@ -116,108 +109,23 @@ check_version_tag:
|
||||
check_test_scripts_build_test_rules:
|
||||
extends:
|
||||
- .pre_check_template
|
||||
- .before_script:build
|
||||
variables:
|
||||
PYTEST_IGNORE_COLLECT_IMPORT_ERROR: "1"
|
||||
script:
|
||||
# requires basic pytest dependencies
|
||||
- run_cmd bash install.sh --enable-pytest
|
||||
- python tools/ci/check_build_test_rules.py check-test-scripts examples/ tools/test_apps components
|
||||
|
||||
check_configure_ci_environment_parsing:
|
||||
check_gitlab_yaml:
|
||||
extends:
|
||||
- .pre_check_template
|
||||
- .before_script:build
|
||||
- .rules:build
|
||||
script:
|
||||
- cd tools/ci
|
||||
- python -m unittest ci_build_apps.py
|
||||
- python tools/ci/gitlab_yaml_linter.py
|
||||
|
||||
pipeline_variables:
|
||||
extends:
|
||||
- .pre_check_template
|
||||
- .before_script:fetch:git_diff
|
||||
tags: [fast_run, shiny]
|
||||
script:
|
||||
# MODIFIED_FILES is a list of files that changed, could be used everywhere
|
||||
- MODIFIED_FILES=$(echo "$GIT_DIFF_OUTPUT" | xargs)
|
||||
- echo "MODIFIED_FILES=$MODIFIED_FILES" >> pipeline.env
|
||||
- echo "REPORT_EXIT_CODE=0" >> pipeline.env
|
||||
# MR_MODIFIED_FILES and MR_MODIFIED_COMPONENTS are semicolon separated lists that is used in MR only
|
||||
# for non MR pipeline, these are empty lists
|
||||
- |
|
||||
if [ $IS_MR_PIPELINE == "0" ]; then
|
||||
echo "MR_MODIFIED_FILES=\"\"" >> pipeline.env
|
||||
echo "MR_MODIFIED_COMPONENTS=\"\"" >> pipeline.env
|
||||
else
|
||||
MR_MODIFIED_FILES=$(echo "$GIT_DIFF_OUTPUT" | tr '\n' ';')
|
||||
echo "MR_MODIFIED_FILES=\"$MR_MODIFIED_FILES\"" >> pipeline.env
|
||||
|
||||
MR_MODIFIED_COMPONENTS=$(run_cmd python tools/ci/ci_get_mr_info.py components --modified-files $MODIFIED_FILES | tr '\n' ';')
|
||||
echo "MR_MODIFIED_COMPONENTS=\"$MR_MODIFIED_COMPONENTS\"" >> pipeline.env
|
||||
fi
|
||||
- |
|
||||
if echo "$CI_MERGE_REQUEST_LABELS" | egrep "(^|,)BUILD_AND_TEST_ALL_APPS(,|$)"; then
|
||||
echo "BUILD_AND_TEST_ALL_APPS=1" >> pipeline.env
|
||||
fi
|
||||
# run full pipeline if testing constraint branch
|
||||
- |
|
||||
if [ -n "$CI_PYTHON_CONSTRAINT_BRANCH" ]; then
|
||||
echo "BUILD_AND_TEST_ALL_APPS=1" >> pipeline.env
|
||||
fi
|
||||
- echo "OOCD_DISTRO_URL_ARMHF=$OOCD_DISTRO_URL_ARMHF" >> pipeline.env
|
||||
- echo "OOCD_DISTRO_URL_ARM64=$OOCD_DISTRO_URL_ARM64" >> pipeline.env
|
||||
- python tools/ci/ci_process_description.py
|
||||
- cat pipeline.env
|
||||
- python tools/ci/artifacts_handler.py upload --type modified_files_and_components_report
|
||||
artifacts:
|
||||
reports:
|
||||
dotenv: pipeline.env
|
||||
paths:
|
||||
- pipeline.env
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
|
||||
baseline_manifest_sha:
|
||||
retry_failed_jobs:
|
||||
extends:
|
||||
- .pre_check_template
|
||||
- .rules:dev-push
|
||||
tags: [fast_run, shiny]
|
||||
script:
|
||||
- |
|
||||
# merged results pipelines, by default
|
||||
# diff between target-branch-head and merged-result-head
|
||||
if [ -n "$CI_MERGE_REQUEST_TARGET_BRANCH_SHA" ]; then
|
||||
git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_SHA --depth=1
|
||||
git checkout FETCH_HEAD
|
||||
idf-build-apps dump-manifest-sha \
|
||||
--manifest-files $(find . -name ".build-test-rules.yml" | xargs) \
|
||||
--output .manifest_sha
|
||||
# merge request pipelines, when the mr got conflicts
|
||||
# diff between diff-base-sha and merge-request-head
|
||||
elif [ -n "$CI_MERGE_REQUEST_DIFF_BASE_SHA" ]; then
|
||||
git fetch origin $CI_MERGE_REQUEST_DIFF_BASE_SHA --depth=1
|
||||
git checkout FETCH_HEAD
|
||||
idf-build-apps dump-manifest-sha \
|
||||
--manifest-files $(find . -name ".build-test-rules.yml" | xargs) \
|
||||
--output .manifest_sha
|
||||
# other pipelines, like the protected branches pipelines
|
||||
# not triggered in this job
|
||||
fi
|
||||
artifacts:
|
||||
paths:
|
||||
- .manifest_sha
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
|
||||
redundant_pass_job:
|
||||
stage: pre_check
|
||||
tags: [shiny, fast_run]
|
||||
image: $ESP_ENV_IMAGE
|
||||
dependencies: null
|
||||
before_script: []
|
||||
cache: []
|
||||
extends: []
|
||||
allow_failure: true
|
||||
script:
|
||||
- echo "This job is redundant to ensure the 'retry_failed_jobs' job can exist and not be skipped"
|
||||
when: always
|
||||
- echo "Retrieving and retrying all failed jobs for the pipeline..."
|
||||
- python tools/ci/python_packages/gitlab_api.py retry_failed_jobs $CI_MERGE_REQUEST_PROJECT_ID --pipeline_id $CI_PIPELINE_ID
|
||||
when: manual
|
||||
|
||||
@@ -2,33 +2,20 @@
|
||||
extends:
|
||||
- .before_script:minimal
|
||||
stage: pre_check
|
||||
image: $PRE_COMMIT_IMAGE
|
||||
tags: [cache, shiny]
|
||||
image: "${CI_REGISTRY}/ci/images/pre-commit-idf-v5.4:1"
|
||||
tags: [pre-commit]
|
||||
variables:
|
||||
# Both shiny and brew runners can pick this job
|
||||
GIT_STRATEGY: fetch
|
||||
GIT_DEPTH: 1
|
||||
SUBMODULES_TO_FETCH: "all"
|
||||
needs:
|
||||
- pipeline_variables
|
||||
variables:
|
||||
# cache pre_commit
|
||||
PRE_COMMIT_HOME: "$CI_PROJECT_DIR/.cache/pre-commit"
|
||||
script:
|
||||
- fetch_submodules
|
||||
- pre-commit run --files $MODIFIED_FILES
|
||||
- pre-commit run --hook-stage post-commit validate-sbom-manifest
|
||||
|
||||
check_pre_commit_upload_cache:
|
||||
extends:
|
||||
- .check_pre_commit_template
|
||||
rules:
|
||||
- if: '($CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_BRANCH =~ /^release\/v/) && $CI_PIPELINE_SOURCE == "push"'
|
||||
cache:
|
||||
- key: pre_commit-cache-${LATEST_GIT_TAG}
|
||||
paths:
|
||||
- .cache/pre-commit
|
||||
policy: pull-push
|
||||
- key: submodule-cache-${LATEST_GIT_TAG}
|
||||
paths:
|
||||
- .cache/submodule_archives
|
||||
policy: pull
|
||||
|
||||
check_pre_commit:
|
||||
extends:
|
||||
- .check_pre_commit_template
|
||||
@@ -37,10 +24,6 @@ check_pre_commit:
|
||||
when: never
|
||||
- when: on_success
|
||||
cache:
|
||||
- key: pre_commit-cache-${LATEST_GIT_TAG}
|
||||
paths:
|
||||
- .cache/pre-commit
|
||||
policy: pull
|
||||
- key: submodule-cache-${LATEST_GIT_TAG}
|
||||
paths:
|
||||
- .cache/submodule_archives
|
||||
@@ -56,25 +39,20 @@ check_powershell:
|
||||
tags:
|
||||
- dind
|
||||
- amd64
|
||||
- brew # faster "apk add"
|
||||
needs:
|
||||
- pipeline_variables
|
||||
variables:
|
||||
# cache pre_commit
|
||||
PRE_COMMIT_HOME: "$CI_PROJECT_DIR/.cache/pre-commit"
|
||||
# brew runners always use fetch
|
||||
GIT_STRATEGY: fetch
|
||||
GIT_DEPTH: 1
|
||||
SUBMODULES_TO_FETCH: "none"
|
||||
rules:
|
||||
- changes:
|
||||
- "*.ps1"
|
||||
- ".gitlab/ci/pre_commit.yml"
|
||||
script:
|
||||
- apk add python3
|
||||
- apk add py3-pip
|
||||
- pip install pre-commit --break-system-packages
|
||||
- pre-commit run --hook-stage manual check-powershell-scripts --files $MODIFIED_FILES
|
||||
cache:
|
||||
- key: pre_commit-cache-${LATEST_GIT_TAG}
|
||||
paths:
|
||||
- .cache/pre-commit
|
||||
policy: pull
|
||||
- key: submodule-cache-${LATEST_GIT_TAG}
|
||||
paths:
|
||||
- .cache/submodule_archives
|
||||
policy: pull
|
||||
|
||||
24
.gitlab/ci/pre_deploy.yml
Normal file
24
.gitlab/ci/pre_deploy.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
.pre_deploy_template:
|
||||
stage: pre_deploy
|
||||
image: $ESP_ENV_IMAGE
|
||||
|
||||
check_submodule_sync:
|
||||
extends:
|
||||
- .pre_deploy_template
|
||||
- .rules:test:submodule
|
||||
tags: [ brew, github_sync ]
|
||||
retry: 2
|
||||
variables:
|
||||
GIT_STRATEGY: fetch # use brew local mirror first
|
||||
SUBMODULES_TO_FETCH: "none"
|
||||
PUBLIC_IDF_URL: "https://github.com/espressif/esp-idf.git"
|
||||
script:
|
||||
- git submodule deinit --force .
|
||||
- rm -rf .git/modules # remove all the cached metadata
|
||||
# setting the default remote URL to the public one, to resolve relative location URLs
|
||||
- git config remote.origin.url ${PUBLIC_IDF_URL}
|
||||
# check if all submodules are correctly synced to public repository
|
||||
- git submodule init
|
||||
- git config --get-regexp '^submodule\..*\.url$' || true
|
||||
- git submodule update --recursive
|
||||
- echo "IDF was cloned from ${PUBLIC_IDF_URL} completely"
|
||||
@@ -1,15 +0,0 @@
|
||||
retry_failed_jobs:
|
||||
stage: retry_failed_jobs
|
||||
tags: [shiny, fast_run]
|
||||
allow_failure: true
|
||||
image: $ESP_ENV_IMAGE
|
||||
dependencies: null
|
||||
before_script: []
|
||||
cache: []
|
||||
extends: []
|
||||
script:
|
||||
- echo "Retrieving and retrying all failed jobs for the pipeline..."
|
||||
- python tools/ci/python_packages/gitlab_api.py retry_failed_jobs $CI_MERGE_REQUEST_PROJECT_ID --pipeline_id $CI_PIPELINE_ID
|
||||
when: manual
|
||||
needs:
|
||||
- redundant_pass_job
|
||||
@@ -9,19 +9,10 @@
|
||||
|
||||
- "**/*.{c,C,cpp}"
|
||||
- "**/*.{h,H,hpp}"
|
||||
- "Kconfig"
|
||||
- "components/**/Kconfig"
|
||||
- "components/**/CMakeList.txt"
|
||||
- "components/**/CMakeLists.txt"
|
||||
|
||||
.patterns-python-cache: &patterns-python-cache
|
||||
- "tools/requirements.json"
|
||||
- "tools/requirements/requirements.*.txt"
|
||||
|
||||
.patterns-python-files: &patterns-python-files
|
||||
- ".gitlab/ci/static-code-analysis.yml"
|
||||
- "**/*.py"
|
||||
|
||||
.patterns-sonarqube-files: &patterns-sonarqube-files
|
||||
- "tools/ci/sonar_exclude_list.txt"
|
||||
|
||||
.patterns-build_components: &patterns-build_components
|
||||
# components files except "test*/" "host*/" folders
|
||||
@@ -35,6 +26,7 @@
|
||||
# Add folders excluded by "???[!t]" and "??[!s]?"
|
||||
# pre-commit: tools/ci/check_rules_components_patterns.py
|
||||
- "components/bt/host/**/*"
|
||||
- "components/esp_psram/system_layer/*"
|
||||
|
||||
.patterns-downloadable-tools: &patterns-downloadable-tools
|
||||
- "tools/idf_tools.py"
|
||||
@@ -66,8 +58,10 @@
|
||||
- "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/**/*"
|
||||
|
||||
.patterns-host_test: &patterns-host_test
|
||||
- ".gitlab/ci/host-test.yml"
|
||||
@@ -97,6 +91,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"
|
||||
@@ -160,34 +155,22 @@
|
||||
##############
|
||||
# if anchors #
|
||||
##############
|
||||
.if-ref-master: &if-ref-master
|
||||
if: '$CI_COMMIT_REF_NAME == "master"'
|
||||
.if-master-push: &if-master-push
|
||||
if: '$CI_COMMIT_REF_NAME == "master" && $CI_PIPELINE_SOURCE == "push"'
|
||||
|
||||
.if-tag-release: &if-tag-release
|
||||
.if-release-tag: &if-release-tag
|
||||
if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+(\.\d+)?($|-)/'
|
||||
|
||||
.if-protected: &if-protected
|
||||
.if-protected-check: &if-protected-check
|
||||
if: '($CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_BRANCH =~ /^release\/v/ || $CI_COMMIT_TAG =~ /^v\d+\.\d+(\.\d+)?($|-)/) || $CI_COMMIT_TAG =~ /^qa-test/'
|
||||
|
||||
.if-protected-deploy: &if-protected-deploy
|
||||
if: '($CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_BRANCH =~ /^release\/v/ || $CI_COMMIT_TAG =~ /^v\d+\.\d+(\.\d+)?($|-)/) && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "api")'
|
||||
|
||||
.if-protected-no_label: &if-protected-no_label
|
||||
if: '($CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_BRANCH =~ /^release\/v/ || $CI_COMMIT_TAG =~ /^v\d+\.\d+(\.\d+)?($|-)/) && $BOT_TRIGGER_WITH_LABEL == null'
|
||||
|
||||
.if-protected-ref-push: &if-protected-ref-push
|
||||
.if-protected-branch-push: &if-protected-branch-push
|
||||
# rules:changes always evaluates to true for new branch pipelines or when there is no Git push event
|
||||
if: '($CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_BRANCH =~ /^release\/v/) && $CI_PIPELINE_SOURCE == "push"'
|
||||
|
||||
.if-qa-test-tag: &if-qa-test-tag
|
||||
if: '$CI_COMMIT_TAG =~ /^qa-test/'
|
||||
|
||||
.if-dev-push: &if-dev-push
|
||||
if: '$CI_COMMIT_REF_NAME != "master" && $CI_COMMIT_BRANCH !~ /^release\/v/ && $CI_COMMIT_TAG !~ /^v\d+\.\d+(\.\d+)?($|-)/ && $CI_COMMIT_TAG !~ /^qa-test/ && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event")'
|
||||
|
||||
.if-schedule: &if-schedule
|
||||
if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||
|
||||
.if-schedule-nightly: &if-schedule-nightly
|
||||
if: '$CI_PIPELINE_SOURCE == "schedule" && $INCLUDE_NIGHTLY_RUN == "1"'
|
||||
|
||||
@@ -207,39 +190,26 @@
|
||||
# Rules #
|
||||
#########
|
||||
### Branches ###
|
||||
.rules:protected:
|
||||
.rules:protected:check:
|
||||
rules:
|
||||
- <<: *if-protected
|
||||
- <<: *if-protected-check
|
||||
|
||||
.rules:push_to_github:
|
||||
.rules:master:push:
|
||||
rules:
|
||||
- <<: *if-qa-test-tag
|
||||
when: never
|
||||
- <<: *if-protected-no_label
|
||||
- <<: *if-master-push
|
||||
|
||||
.rules:tag:release:
|
||||
rules:
|
||||
- <<: *if-tag-release
|
||||
- <<: *if-release-tag
|
||||
|
||||
.rules:dev-push:
|
||||
rules:
|
||||
- <<: *if-dev-push
|
||||
|
||||
# Do not upload caches on dev branches by default
|
||||
.rules:upload-python-cache:
|
||||
rules:
|
||||
- <<: *if-tag-release
|
||||
- <<: *if-schedule-nightly
|
||||
- <<: *if-protected-ref-push
|
||||
changes: *patterns-python-cache
|
||||
- <<: *if-label-upload_cache
|
||||
when: manual
|
||||
|
||||
.rules:upload-submodule-cache:
|
||||
rules:
|
||||
# Needn't upload submodule cache in schedule pipeline
|
||||
- <<: *if-tag-release
|
||||
- <<: *if-protected-ref-push
|
||||
- <<: *if-release-tag
|
||||
- <<: *if-protected-branch-push
|
||||
changes: *patterns-submodule
|
||||
- <<: *if-label-upload_cache
|
||||
when: manual
|
||||
@@ -247,16 +217,10 @@
|
||||
### Patterns ###
|
||||
.rules:patterns:clang_tidy:
|
||||
rules:
|
||||
- <<: *if-protected
|
||||
- <<: *if-protected-check
|
||||
- <<: *if-dev-push
|
||||
changes: *patterns-c-files
|
||||
|
||||
.rules:patterns:python-files:
|
||||
rules:
|
||||
- <<: *if-protected
|
||||
- <<: *if-dev-push
|
||||
changes: *patterns-python-files
|
||||
|
||||
#.rules:patterns:static-code-analysis-preview:
|
||||
# rules:
|
||||
# - <<: *if-dev-push
|
||||
@@ -266,12 +230,6 @@
|
||||
# - <<: *if-dev-push
|
||||
# changes: *patterns-sonarqube-files
|
||||
|
||||
.rules:patterns:idf-pytest-plugin:
|
||||
rules:
|
||||
- <<: *if-protected
|
||||
- <<: *if-dev-push
|
||||
changes: *patterns-idf-pytest-plugin
|
||||
|
||||
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
# DO NOT place comments or maintain any code from this line
|
||||
#
|
||||
@@ -312,7 +270,7 @@
|
||||
rules:
|
||||
- <<: *if-revert-branch
|
||||
when: never
|
||||
- <<: *if-protected
|
||||
- <<: *if-protected-check
|
||||
- <<: *if-label-build
|
||||
- <<: *if-dev-push
|
||||
changes: *patterns-build_components
|
||||
@@ -325,7 +283,7 @@
|
||||
rules:
|
||||
- <<: *if-revert-branch
|
||||
when: never
|
||||
- <<: *if-protected
|
||||
- <<: *if-protected-check
|
||||
- <<: *if-label-build
|
||||
- <<: *if-dev-push
|
||||
changes: *patterns-build_check
|
||||
@@ -340,7 +298,7 @@
|
||||
rules:
|
||||
- <<: *if-revert-branch
|
||||
when: never
|
||||
- <<: *if-protected
|
||||
- <<: *if-protected-check
|
||||
- <<: *if-label-build
|
||||
- <<: *if-label-docker
|
||||
- <<: *if-dev-push
|
||||
@@ -356,7 +314,7 @@
|
||||
rules:
|
||||
- <<: *if-revert-branch
|
||||
when: never
|
||||
- <<: *if-protected
|
||||
- <<: *if-protected-check
|
||||
- <<: *if-label-build
|
||||
- <<: *if-label-macos
|
||||
- <<: *if-label-macos_test
|
||||
@@ -367,21 +325,6 @@
|
||||
- <<: *if-dev-push
|
||||
changes: *patterns-downloadable-tools
|
||||
|
||||
.rules:build:target_test:
|
||||
rules:
|
||||
- <<: *if-revert-branch
|
||||
when: never
|
||||
- <<: *if-protected
|
||||
- <<: *if-label-build
|
||||
- <<: *if-dev-push
|
||||
changes: *patterns-build_components
|
||||
- <<: *if-dev-push
|
||||
changes: *patterns-build_system
|
||||
- <<: *if-dev-push
|
||||
changes: *patterns-build_template-app
|
||||
- <<: *if-dev-push
|
||||
changes: *patterns-downloadable-tools
|
||||
|
||||
.rules:labels:nvs_coverage:
|
||||
rules:
|
||||
- <<: *if-revert-branch
|
||||
@@ -399,7 +342,7 @@
|
||||
rules:
|
||||
- <<: *if-revert-branch
|
||||
when: never
|
||||
- <<: *if-protected
|
||||
- <<: *if-protected-check
|
||||
- <<: *if-label-build-only
|
||||
when: never
|
||||
- <<: *if-label-host_test
|
||||
@@ -410,7 +353,7 @@
|
||||
rules:
|
||||
- <<: *if-revert-branch
|
||||
when: never
|
||||
- <<: *if-protected
|
||||
- <<: *if-protected-check
|
||||
- <<: *if-label-build-only
|
||||
when: never
|
||||
- <<: *if-label-submodule
|
||||
|
||||
@@ -16,106 +16,17 @@ clang_tidy_check:
|
||||
--limit-file tools/ci/static-analysis-rules.yml
|
||||
--xtensa-include-dir
|
||||
|
||||
check_pylint:
|
||||
gcc_static_analyzer:
|
||||
extends:
|
||||
- .pre_check_template
|
||||
- .rules:patterns:python-files
|
||||
needs:
|
||||
- pipeline_variables
|
||||
artifacts:
|
||||
reports:
|
||||
codequality: pylint.json
|
||||
paths:
|
||||
- pylint.json
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
- .rules:patterns:clang_tidy
|
||||
variables:
|
||||
CI_CCACHE_DISABLE: 1
|
||||
ANALYZING_APP: "examples/get-started/hello_world"
|
||||
script:
|
||||
- |
|
||||
if [ -n "$CI_MERGE_REQUEST_IID" ]; then
|
||||
export files=$(echo "$GIT_DIFF_OUTPUT" | grep ".py$" | xargs);
|
||||
else
|
||||
export files=$(git ls-files "*.py" | xargs);
|
||||
fi
|
||||
- if [ -z "$files" ]; then echo "No python files found"; exit 0; fi
|
||||
- run_cmd pylint --exit-zero --load-plugins=pylint_gitlab --output-format=gitlab-codeclimate:pylint.json $files
|
||||
#
|
||||
## build stage
|
||||
## Sonarqube related jobs put here for this reason:
|
||||
## Here we have two jobs. code_quality_check and code_quality_report.
|
||||
##
|
||||
## code_quality_check will analyze the code changes between your MR and
|
||||
## code repo stored in sonarqube server. The analysis result is only shown in
|
||||
## the comments under this MR and won't be transferred to the server.
|
||||
##
|
||||
## code_quality_report will analyze and transfer both of the newly added code
|
||||
## and the analysis result to the server.
|
||||
##
|
||||
## Put in the front to ensure that the newly merged code can be stored in
|
||||
## sonarqube server ASAP, in order to avoid reporting unrelated code issues
|
||||
#.sonar_scan_template:
|
||||
# stage: build
|
||||
# extends: .pre_check_template
|
||||
# # full clone since this image does not support fetch --shallow-since-cutoff
|
||||
# # shiny runners are used for full clone
|
||||
# tags: [build, shiny]
|
||||
# image: $SONARQUBE_SCANNER_IMAGE
|
||||
# before_script:
|
||||
# - source tools/ci/utils.sh
|
||||
# - export PYTHONPATH="$CI_PROJECT_DIR/tools:$CI_PROJECT_DIR/tools/ci/python_packages:$PYTHONPATH"
|
||||
# - fetch_submodules
|
||||
# # Exclude the submodules, all paths ends with /**
|
||||
# - submodules=$(get_all_submodules)
|
||||
# # get all exclude paths specified in tools/ci/sonar_exclude_list.txt | ignore lines start with # | xargs | replace all <space> to <comma>
|
||||
# - custom_excludes=$(cat $CI_PROJECT_DIR/tools/ci/sonar_exclude_list.txt | grep -v '^#' | xargs | sed -e 's/ /,/g')
|
||||
# # Exclude the report dir as well
|
||||
# - export EXCLUSIONS="$custom_excludes,$submodules"
|
||||
# - export SONAR_SCANNER_OPTS="-Xmx2048m"
|
||||
# variables:
|
||||
# GIT_DEPTH: 0
|
||||
# REPORT_PATTERN: clang_tidy_reports/**/*.txt
|
||||
# artifacts:
|
||||
# paths:
|
||||
# - $REPORT_PATTERN
|
||||
# expire_in: 1 week
|
||||
# when: always
|
||||
# dependencies: # Here is not a hard dependency relationship, could be skipped when only python files changed. so we do not use "needs" here.
|
||||
# - clang_tidy_check
|
||||
#
|
||||
#code_quality_check:
|
||||
# extends:
|
||||
# - .sonar_scan_template
|
||||
# - .rules:patterns:static-code-analysis-preview
|
||||
# allow_failure: true # it's using exit code to indicate the code analysis result,
|
||||
# # we don't want to block ci when critical issues founded
|
||||
# script:
|
||||
# - export CI_MERGE_REQUEST_COMMITS=$(python ${CI_PROJECT_DIR}/tools/ci/ci_get_mr_info.py commits --src-branch ${CI_COMMIT_REF_NAME} | tr '\n' ',')
|
||||
# # test if this branch have merge request, if not, exit 0
|
||||
# - test -n "$CI_MERGE_REQUEST_IID" || exit 0
|
||||
# - test -n "$CI_MERGE_REQUEST_COMMITS" || exit 0
|
||||
# - sonar-scanner
|
||||
# -Dsonar.analysis.mode=preview
|
||||
# -Dsonar.branch.name=$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME
|
||||
# -Dsonar.cxx.clangtidy.reportPath=$REPORT_PATTERN
|
||||
# -Dsonar.exclusions=$EXCLUSIONS
|
||||
# -Dsonar.gitlab.ci_merge_request_iid=$CI_MERGE_REQUEST_IID
|
||||
# -Dsonar.gitlab.commit_sha=$CI_MERGE_REQUEST_COMMITS
|
||||
# -Dsonar.gitlab.merge_request_discussion=true
|
||||
# -Dsonar.gitlab.ref_name=$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME
|
||||
# -Dsonar.host.url=$SONAR_HOST_URL
|
||||
# -Dsonar.login=$SONAR_LOGIN
|
||||
#
|
||||
#code_quality_report:
|
||||
# extends:
|
||||
# - .sonar_scan_template
|
||||
# - .rules:protected
|
||||
# allow_failure: true # it's using exit code to indicate the code analysis result,
|
||||
# # we don't want to block ci when critical issues founded
|
||||
# script:
|
||||
# - sonar-scanner
|
||||
# -Dsonar.branch.name=$CI_COMMIT_REF_NAME
|
||||
# -Dsonar.cxx.clangtidy.reportPath=$REPORT_PATTERN
|
||||
# -Dsonar.exclusions=$EXCLUSIONS
|
||||
# -Dsonar.gitlab.commit_sha=$PIPELINE_COMMIT_SHA
|
||||
# -Dsonar.gitlab.ref_name=$CI_COMMIT_REF_NAME
|
||||
# -Dsonar.host.url=$SONAR_HOST_URL
|
||||
# -Dsonar.login=$SONAR_LOGIN
|
||||
- echo "CONFIG_COMPILER_STATIC_ANALYZER=y" >> ${ANALYZING_APP}/sdkconfig.defaults
|
||||
- sed -i 's/.*MINIMAL_BUILD.*//g' ${ANALYZING_APP}/CMakeLists.txt
|
||||
# no -Werror
|
||||
- export EXTRA_CFLAGS=
|
||||
- export EXTRA_CXXFLAGS=
|
||||
- idf-build-apps build -p ${ANALYZING_APP} --no-check-warnings
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
# Host tests
|
||||
.host_test_win_template:
|
||||
extends: .rules:test:host_test
|
||||
extends:
|
||||
- .windows-settings
|
||||
- .rules:test:host_test
|
||||
stage: host_test
|
||||
image: $ESP_ENV_IMAGE
|
||||
tags:
|
||||
- windows-build
|
||||
dependencies: # set dependencies to null to avoid missing artifacts issue
|
||||
# run host_test jobs immediately, only after upload cache
|
||||
needs:
|
||||
- job: upload-pip-cache
|
||||
optional: true
|
||||
artifacts: false
|
||||
- job: upload-submodules-cache
|
||||
optional: true
|
||||
artifacts: false
|
||||
before_script: []
|
||||
after_script: []
|
||||
- job: manual_gate
|
||||
optional: true
|
||||
|
||||
test_cli_installer_win:
|
||||
rules:
|
||||
@@ -30,14 +25,11 @@ 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"
|
||||
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.16.3
|
||||
- cd ${IDF_PATH}\tools\test_idf_tools
|
||||
- python -m pip install jsonschema
|
||||
- cd tools\test_idf_tools
|
||||
- python .\test_idf_tools.py
|
||||
- python .\test_idf_tools_python_env.py
|
||||
|
||||
@@ -48,84 +40,48 @@ 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 --enable-pytest
|
||||
- .\export.ps1
|
||||
- python "${SUBMODULE_FETCH_TOOL}" -s "all"
|
||||
- cd ${IDF_PATH}/tools/test_idf_py
|
||||
- pytest --noconftest test_idf_py.py --junitxml=${IDF_PATH}/XUNIT_IDF_PY.xml
|
||||
- pytest --noconftest test_hints.py --junitxml=${IDF_PATH}/XUNIT_HINTS.xml
|
||||
- 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 ${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 --enable-pytest
|
||||
- . .\export.ps1
|
||||
- python "${SUBMODULE_FETCH_TOOL}" -s "all"
|
||||
- cd ${IDF_PATH}\tools\test_build_system
|
||||
- python ${IDF_PATH}\tools\ci\get_known_failure_cases_file.py
|
||||
- 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}
|
||||
- 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_build_system_win:
|
||||
extends:
|
||||
- .test_build_system_template_win
|
||||
- .rules:labels:windows_pytest_build_system
|
||||
parallel: 2
|
||||
tags:
|
||||
- windows-build
|
||||
needs:
|
||||
- job: manual_gate
|
||||
optional: true
|
||||
artifacts:
|
||||
paths:
|
||||
- XUNIT_RESULT.xml
|
||||
- test_build_system
|
||||
expire_in: 2 days
|
||||
reports:
|
||||
junit: XUNIT_RESULT.xml
|
||||
when: always
|
||||
parallel: 6
|
||||
|
||||
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
|
||||
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 --enable-pytest
|
||||
- . .\export.ps1
|
||||
- python ${IDF_PATH}\tools\idf_tools.py install cmake@${MINIMAL_CMAKE_VERSION}
|
||||
- python tools\idf_tools.py install cmake@${MINIMAL_CMAKE_VERSION}
|
||||
- $Env:PATH = "$Env:USERPROFILE\.espressif\tools\cmake\${MINIMAL_CMAKE_VERSION}\bin;$Env:PATH"
|
||||
- |
|
||||
$actualVersion = (& cmake --version).Split()[2]
|
||||
@@ -133,7 +89,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
|
||||
- python ${IDF_PATH}\tools\ci\get_known_failure_cases_file.py
|
||||
- pytest -k cmake --junitxml=${CI_PROJECT_DIR}\XUNIT_RESULT.xml --ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
|
||||
- cd 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}
|
||||
|
||||
@@ -6,26 +6,6 @@
|
||||
stage: upload_cache
|
||||
image: $ESP_ENV_IMAGE
|
||||
|
||||
upload-pip-cache:
|
||||
extends:
|
||||
- .upload_cache_template
|
||||
- .before_script:minimal
|
||||
- .rules:upload-python-cache
|
||||
tags:
|
||||
- $GEO
|
||||
- cache
|
||||
cache:
|
||||
key: pip-cache-${LATEST_GIT_TAG}
|
||||
paths:
|
||||
- .cache/pip
|
||||
policy: push
|
||||
script:
|
||||
- rm -rf .cache/pip # clear old packages
|
||||
- bash install.sh --enable-ci --enable-pytest
|
||||
parallel:
|
||||
matrix:
|
||||
- GEO: [ 'shiny', 'brew' ]
|
||||
|
||||
upload-submodules-cache:
|
||||
extends:
|
||||
- .upload_cache_template
|
||||
|
||||
63
.idf_build_apps.toml
Normal file
63
.idf_build_apps.toml
Normal file
@@ -0,0 +1,63 @@
|
||||
config_rules = [
|
||||
'sdkconfig.ci=default',
|
||||
'sdkconfig.ci.*=',
|
||||
'=default',
|
||||
]
|
||||
|
||||
extra_pythonpaths = [
|
||||
'$IDF_PATH/tools/ci/python_packages',
|
||||
'$IDF_PATH/tools/ci',
|
||||
'$IDF_PATH/tools',
|
||||
]
|
||||
build_system = "idf_ci_local.app:IdfCMakeApp"
|
||||
|
||||
recursive = true
|
||||
check_warnings = true
|
||||
keep_going = true
|
||||
copy_sdkconfig = true
|
||||
ignore_warning_files = [
|
||||
'$IDF_PATH/tools/ci/ignore_build_warnings.txt',
|
||||
]
|
||||
|
||||
build_dir = "build_@t_@w"
|
||||
build_log_filename = "build_log.txt"
|
||||
size_json_filename = "size_${CI_JOB_ID}.json"
|
||||
|
||||
verbose = 1 # INFO
|
||||
|
||||
# collect
|
||||
collect_app_info_filename = "app_info_${CI_JOB_ID}.txt"
|
||||
junitxml = "build_summary_${CI_JOB_ID}.xml"
|
||||
|
||||
# manifest
|
||||
check_manifest_rules = true
|
||||
manifest_rootpath = "$IDF_PATH"
|
||||
manifest_filepatterns = [
|
||||
'**/.build-test-rules.yml',
|
||||
]
|
||||
|
||||
# dependency-driven build
|
||||
deactivate_dependency_driven_build_by_components = [
|
||||
'cxx',
|
||||
'esp_common',
|
||||
'esp_hw_support',
|
||||
'esp_rom',
|
||||
'esp_system',
|
||||
'esp_timer',
|
||||
'freertos',
|
||||
'hal',
|
||||
'heap',
|
||||
'log',
|
||||
'newlib',
|
||||
'riscv',
|
||||
'soc',
|
||||
'xtensa',
|
||||
]
|
||||
|
||||
deactivate_dependency_driven_build_by_filepatterns = [
|
||||
# tools
|
||||
'tools/cmake/**/*',
|
||||
'tools/tools.json',
|
||||
# ci
|
||||
'tools/ci/ignore_build_warnings.txt',
|
||||
]
|
||||
116
.idf_ci.toml
Normal file
116
.idf_ci.toml
Normal file
@@ -0,0 +1,116 @@
|
||||
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',
|
||||
'tools/test_idf_py',
|
||||
'tools/ci/test_autocomplete',
|
||||
'tools/test_build_system',
|
||||
'tools/bt/ble_log_console',
|
||||
]
|
||||
|
||||
[local_runtime_envs]
|
||||
EXTRA_CFLAGS = "-Werror -Werror=deprecated-declarations -Werror=unused-variable -Werror=unused-but-set-variable -Werror=unused-function -Wstrict-prototypes"
|
||||
EXTRA_CXXFLAGS = "-Werror -Werror=deprecated-declarations -Werror=unused-variable -Werror=unused-but-set-variable -Werror=unused-function"
|
||||
LDGEN_CHECK_MAPPING = "1"
|
||||
IDF_CI_BUILD = "1"
|
||||
|
||||
[gitlab]
|
||||
|
||||
[gitlab.build_pipeline]
|
||||
workflow_name = "build_child_pipeline"
|
||||
runs_per_job = 30
|
||||
|
||||
job_tags = ['build', 'shiny']
|
||||
job_template_name = '.dynamic_build_template'
|
||||
job_template_jinja = '' # write in tools/ci/dynamic_pipelines/templates/.dynamic_jobs.yml
|
||||
pre_yaml_jinja = """
|
||||
include:
|
||||
- .gitlab/ci/common.yml
|
||||
- tools/ci/dynamic_pipelines/templates/.dynamic_jobs.yml
|
||||
- tools/ci/dynamic_pipelines/templates/test_child_pipeline.yml
|
||||
"""
|
||||
yaml_jinja = """
|
||||
{{ settings.gitlab.build_pipeline.pre_yaml_jinja }}
|
||||
|
||||
workflow:
|
||||
name: {{ settings.gitlab.build_pipeline.workflow_name }}
|
||||
rules:
|
||||
- when: always
|
||||
|
||||
{{ jobs }}
|
||||
""" # simplified since we included the tools/ci/dynamic_pipelines/templates/test_child_pipeline.yml
|
||||
|
||||
[gitlab.test_pipeline]
|
||||
job_template_name = '.dynamic_target_test_template'
|
||||
job_template_jinja = '' # write in tools/ci/dynamic_pipelines/templates/.dynamic_jobs.yml
|
||||
pre_yaml_jinja = """
|
||||
include:
|
||||
- .gitlab/ci/common.yml
|
||||
- tools/ci/dynamic_pipelines/templates/.dynamic_jobs.yml
|
||||
"""
|
||||
|
||||
[gitlab.artifacts.s3]
|
||||
enable = true
|
||||
|
||||
[gitlab.artifacts.s3.configs.debug]
|
||||
bucket = "idf-artifacts"
|
||||
zip_first = true
|
||||
build_dir_pattern = "**/build*/"
|
||||
patterns = [
|
||||
'bootloader/*.map',
|
||||
'bootloader/*.elf',
|
||||
'*.map',
|
||||
'*.elf',
|
||||
# customized
|
||||
'esp_tee/*.map',
|
||||
'esp_tee/*.elf',
|
||||
'gdbinit/*',
|
||||
]
|
||||
if_clause = 'CI_JOB_GROUP_NAME != "build_non_test_related_apps"'
|
||||
|
||||
[gitlab.artifacts.s3.configs.flash]
|
||||
bucket = "idf-artifacts"
|
||||
zip_first = true
|
||||
build_dir_pattern = "**/build*/"
|
||||
patterns = [
|
||||
'bootloader/*.bin',
|
||||
'*.bin',
|
||||
'partition_table/*.bin',
|
||||
'flasher_args.json',
|
||||
'flash_project_args',
|
||||
'config/sdkconfig.json',
|
||||
'sdkconfig',
|
||||
'project_description.json',
|
||||
# customized
|
||||
'esp_tee/*.bin',
|
||||
]
|
||||
if_clause = 'CI_JOB_GROUP_NAME != "build_non_test_related_apps"'
|
||||
|
||||
[gitlab.artifacts.s3.configs.log]
|
||||
bucket = "idf-artifacts"
|
||||
patterns = [
|
||||
'**/build*/build_log.txt',
|
||||
'**/build*/size*.json',
|
||||
]
|
||||
|
||||
[gitlab.artifacts.s3.configs.junit]
|
||||
bucket = "idf-artifacts"
|
||||
patterns = [
|
||||
'**/XUNIT_RESULT_*.xml',
|
||||
'**/build_summary_*.xml',
|
||||
]
|
||||
|
||||
[gitlab.artifacts.s3.configs.env]
|
||||
bucket = "idf-artifacts"
|
||||
patterns = [
|
||||
'**/pipeline.env',
|
||||
]
|
||||
|
||||
[gitlab.artifacts.s3.configs.longterm]
|
||||
bucket = "longterm"
|
||||
if_clause = 'CI_COMMIT_REF_NAME == "master"'
|
||||
patterns = [
|
||||
'**/build*/size*.json',
|
||||
]
|
||||
@@ -1,9 +1,15 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
|
||||
default_stages: [commit]
|
||||
default_stages: [pre-commit]
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: "v0.9.7"
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
args: [ "--fix" ]
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
@@ -14,6 +20,7 @@ repos:
|
||||
# 2 - any file matching *test*/*expected* (for host tests, if possible use this naming pattern always)
|
||||
# 3 - any directory named 'testdata'
|
||||
# 4 - protobuf auto-generated files
|
||||
# 5 - COPYING files
|
||||
exclude: &whitespace_excludes |
|
||||
(?x)^(
|
||||
.+\.(md|rst|map|bin)|
|
||||
@@ -41,21 +48,6 @@ repos:
|
||||
- id: no-commit-to-branch
|
||||
name: Do not use uppercase letters in the branch name
|
||||
args: ['--pattern', '^[^A-Z]*[A-Z]']
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 6.1.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
args: ['--config=.flake8', '--tee', '--benchmark']
|
||||
- repo: https://github.com/asottile/reorder-python-imports
|
||||
rev: v3.12.0
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
name: Reorder Python imports
|
||||
args: [--py38-plus]
|
||||
exclude: >
|
||||
(?x)^(
|
||||
.*_pb2.py
|
||||
)$
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.3.0
|
||||
hooks:
|
||||
@@ -126,7 +118,7 @@ repos:
|
||||
name: Check tools dir files patterns
|
||||
entry: tools/ci/check_tools_files_patterns.py
|
||||
language: python
|
||||
files: '^tools/.+'
|
||||
files: '^(tools/.+|.gitlab/ci/rules\.yml)'
|
||||
additional_dependencies:
|
||||
- PyYAML == 5.3.1
|
||||
pass_filenames: false
|
||||
@@ -150,11 +142,11 @@ repos:
|
||||
name: Check if all apps readme files match given .build-test-rules.yml files. Modify the supported target tables
|
||||
entry: tools/ci/check_build_test_rules.py check-readmes
|
||||
language: python
|
||||
files: 'tools/test_apps/.+|examples/.+|components/.+|tools/idf_py_actions/constants.py|tools/ci/check_build_test_rules.py'
|
||||
files: 'tools/.+|examples/.+|components/.+'
|
||||
require_serial: true
|
||||
additional_dependencies:
|
||||
- PyYAML == 5.3.1
|
||||
- idf-build-apps~=2.5
|
||||
- idf-build-apps~=2.16
|
||||
- id: sort-yaml-files
|
||||
name: sort yaml files
|
||||
entry: tools/ci/sort_yaml.py
|
||||
@@ -169,29 +161,12 @@ repos:
|
||||
files: 'tools/ci/sort_yaml\.py$'
|
||||
additional_dependencies:
|
||||
- ruamel.yaml
|
||||
- id: check-build-test-rules-path-exists
|
||||
name: check path in .build-test-rules.yml exists
|
||||
entry: tools/ci/check_build_test_rules.py check-exist
|
||||
language: python
|
||||
additional_dependencies:
|
||||
- PyYAML == 5.3.1
|
||||
always_run: true
|
||||
pass_filenames: false
|
||||
require_serial: true
|
||||
- id: cleanup-ignore-lists
|
||||
name: Remove non-existing patterns from ignore lists
|
||||
entry: tools/ci/cleanup_ignore_lists.py
|
||||
language: python
|
||||
always_run: true
|
||||
require_serial: true
|
||||
- id: gitlab-yaml-linter
|
||||
name: Check gitlab yaml files
|
||||
entry: tools/ci/gitlab_yaml_linter.py
|
||||
language: python
|
||||
files: '\.gitlab-ci\.yml|\.gitlab/ci/.+\.yml|\.gitmodules'
|
||||
pass_filenames: false
|
||||
additional_dependencies:
|
||||
- PyYAML == 5.3.1
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.0.1
|
||||
hooks:
|
||||
@@ -245,3 +220,8 @@ repos:
|
||||
hooks:
|
||||
- id: check-kconfig-files
|
||||
- id: check-deprecated-kconfig-options
|
||||
- repo: https://github.com/espressif/idf-ci
|
||||
rev: v1.2.1
|
||||
hooks:
|
||||
- id: check-tests-missing-config
|
||||
files: 'pytest_.*\.py$|sdkconfig(\..*)?$'
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -14,7 +14,10 @@ def do_test_quit(dut: Dut) -> None:
|
||||
|
||||
def do_test_help_generic(dut: Dut, registration_order: str) -> None:
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.confirm_write('"esp console help command - {} registration"'.format(registration_order), expect_str='esp>')
|
||||
dut.confirm_write(
|
||||
f'"esp console help command - {registration_order} registration"',
|
||||
expect_str='esp>',
|
||||
)
|
||||
|
||||
dut.confirm_write('help', expect_str='aaa')
|
||||
|
||||
@@ -45,63 +48,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>')
|
||||
|
||||
@@ -126,45 +125,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>')
|
||||
@@ -175,18 +170,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>')
|
||||
@@ -196,18 +190,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>')
|
||||
@@ -222,18 +215,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>')
|
||||
|
||||
@@ -241,22 +233,24 @@ def test_console_help_deregister(dut: Dut, test_on: str) -> None:
|
||||
|
||||
# in the test sequence, command a is registered before registering command z, then
|
||||
# command a is deregistered; therefore, console shall not print command a's description
|
||||
cmd_a_description, cmd_z_description = 'should appear first in help', 'should appear last in help'
|
||||
cmd_a_description, cmd_z_description = (
|
||||
'should appear first in help',
|
||||
'should appear last in help',
|
||||
)
|
||||
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>')
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
|
||||
@pytest.mark.temp_skip_ci(
|
||||
targets=['esp32s2', 'esp32s3'], reason='eFuse for S2 and S3 is similar to the C3 chip, so testing on C3 is enough'
|
||||
targets=['esp32s2', 'esp32s3'],
|
||||
reason='eFuse for S2 and S3 is similar to the C3 chip, so testing on C3 is enough',
|
||||
)
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize('target', ['supported_targets'], indirect=['target'])
|
||||
@@ -15,13 +16,11 @@ 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()
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@idf_parametrize('target', ['linux'], indirect=['target'])
|
||||
def test_efuse_linux(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases(timeout=60)
|
||||
|
||||
@@ -11,7 +11,6 @@ def test_esp_app_format(dut: Dut) -> None:
|
||||
dut.expect_unity_test_output()
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@idf_parametrize('target', ['linux'], indirect=['target'])
|
||||
def test_esp_app_format_linux(dut: Dut) -> None:
|
||||
dut.expect_unity_test_output()
|
||||
|
||||
@@ -6,9 +6,7 @@ from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize(
|
||||
'target', ['esp32h2', 'esp32c3', 'esp32s2', 'esp32s3', 'esp32c6'], indirect=['target']
|
||||
)
|
||||
@idf_parametrize('target', ['esp32h2', 'esp32c3', 'esp32s2', 'esp32s3', 'esp32c6'], indirect=['target'])
|
||||
def test_external_coex_unit_test(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
|
||||
@@ -27,30 +25,30 @@ def test_external_coex_unit_test_esp32c2_xtal26m(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
|
||||
|
||||
@pytest.mark.esp32c2eco4
|
||||
@pytest.mark.esp32c2_rev2
|
||||
@pytest.mark.xtal_26mhz
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize(
|
||||
'config, baud',
|
||||
[
|
||||
('esp32c2eco4_xtal26m', '74880'),
|
||||
('esp32c2_rev2_xtal26m', '74880'),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@idf_parametrize('target', ['esp32c2'], indirect=['target'])
|
||||
def test_external_coex_unit_test_esp32c2eco4_xtal26m(dut: Dut) -> None:
|
||||
def test_external_coex_unit_test_esp32c2_rev2_xtal26m(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
|
||||
|
||||
@pytest.mark.esp32c3eco7
|
||||
@pytest.mark.esp32c3_rev1
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'esp32c3eco7',
|
||||
'esp32c3_rev1',
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@idf_parametrize('target', ['esp32c3'], indirect=['target'])
|
||||
def test_external_coex_unit_test_esp32c3eco7(dut: Dut) -> None:
|
||||
def test_external_coex_unit_test_esp32c3_rev1(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
# SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.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_esp_event_linux(dut: Dut) -> None:
|
||||
dut.expect_exact('All tests passed', timeout=5)
|
||||
|
||||
@@ -19,9 +19,11 @@ 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.xfail(
|
||||
'config.getvalue("target") == "esp32c3"',
|
||||
reason='Unstable on QEMU, needs investigation',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
@@ -36,7 +38,6 @@ def test_esp_event_qemu(dut: Dut) -> None:
|
||||
dut._run_normal_case(case)
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@idf_parametrize('target', ['linux'], indirect=['target'])
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
# SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.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_esp_hw_support_linux(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases(group='!ignore', timeout=120)
|
||||
|
||||
@@ -1,34 +1,10 @@
|
||||
# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
import functools
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
|
||||
def target_list(targets: List[str]) -> Callable:
|
||||
def decorator(func: Callable) -> Callable:
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args: List, **kwargs: Dict) -> Callable:
|
||||
return func(*args, **kwargs) # type: ignore
|
||||
|
||||
for target in targets:
|
||||
wrapper = pytest.mark.__getattr__(target)(wrapper)
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
# SOC_PAU_SUPPORTED == 1
|
||||
retention_targets = ['esp32c6', 'esp32h2', 'esp32p4', 'esp32c5', 'esp32c61']
|
||||
|
||||
|
||||
@target_list(retention_targets)
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize('target', ['esp32c61', 'esp32c5', 'esp32p4', 'esp32h2', 'esp32c6'], indirect=['target'])
|
||||
def test_sleep_retention(dut: Dut) -> None:
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
# normal mmu tests
|
||||
|
||||
|
||||
@@ -20,14 +21,6 @@ def test_mmap(dut: Dut) -> None:
|
||||
|
||||
|
||||
# mmu tests with psram enabled
|
||||
PSRAM_RELEASE_CONFIGS = [
|
||||
pytest.param('psram_release_esp32', marks=[pytest.mark.esp32]),
|
||||
pytest.param('psram_release_esp32s2', marks=[pytest.mark.esp32s2]),
|
||||
pytest.param('psram_release_esp32s3', marks=[pytest.mark.esp32s3]),
|
||||
pytest.param('psram_release_esp32p4', marks=[pytest.mark.esp32p4]),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize(
|
||||
'config,target',
|
||||
@@ -44,12 +37,6 @@ def test_mmap_psram(dut: Dut) -> None:
|
||||
|
||||
|
||||
# mmu tests with xip_psram
|
||||
XIP_CONFIGS = [
|
||||
pytest.param('xip_psram_esp32s2', marks=[pytest.mark.esp32s2]),
|
||||
pytest.param('xip_psram_esp32s3', marks=[pytest.mark.esp32s3]),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize(
|
||||
'config,target', [('xip_psram_esp32s2', 'esp32s2'), ('xip_psram_esp32s3', 'esp32s3')], indirect=['config', 'target']
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
# SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.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_esp_partition_linux(dut: Dut) -> None:
|
||||
dut.expect_unity_test_output(timeout=5)
|
||||
|
||||
@@ -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',
|
||||
@@ -46,7 +45,6 @@ def test_esp_ringbuf_qemu(dut: Dut) -> None:
|
||||
dut._run_normal_case(case)
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@idf_parametrize('target', ['linux'], indirect=['target'])
|
||||
def test_esp_ringbuf_posix_simulator(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases(group='linux')
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
# SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.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_esp_rom_linux(dut: Dut) -> None:
|
||||
dut.expect_exact('All tests passed', timeout=5)
|
||||
|
||||
@@ -8,14 +8,22 @@ from pytest_embedded_idf.utils import idf_parametrize
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize(
|
||||
'target',
|
||||
['esp32c3', 'esp32s2', 'esp32s3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4'],
|
||||
[
|
||||
'esp32c3',
|
||||
'esp32s2',
|
||||
'esp32s3',
|
||||
'esp32c5',
|
||||
'esp32c6',
|
||||
'esp32c61',
|
||||
'esp32h2',
|
||||
'esp32p4',
|
||||
],
|
||||
indirect=['target'],
|
||||
)
|
||||
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:
|
||||
|
||||
@@ -15,15 +15,6 @@ def not_expect(dut: Dut, output_regex: str) -> None:
|
||||
raise RuntimeError(f'Found not_expect output {output_regex}')
|
||||
|
||||
|
||||
JTAG_SERIAL_MARKS = [
|
||||
pytest.mark.esp32s3,
|
||||
pytest.mark.esp32c2,
|
||||
pytest.mark.esp32c3,
|
||||
pytest.mark.esp32c6,
|
||||
pytest.mark.esp32h2,
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize('config', ['console_none'], indirect=['config'])
|
||||
@idf_parametrize('target', ['supported_targets'], indirect=['target'])
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
# SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.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_esp_system_linux(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases(timeout=60)
|
||||
|
||||
@@ -4,19 +4,6 @@ import pytest
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
CONFIGS = [
|
||||
pytest.param('general', marks=[pytest.mark.supported_targets]),
|
||||
pytest.param('release', marks=[pytest.mark.supported_targets]),
|
||||
pytest.param('single_core', marks=[pytest.mark.esp32]),
|
||||
pytest.param('freertos_compliance', marks=[pytest.mark.esp32]),
|
||||
pytest.param('isr_dispatch_esp32', marks=[pytest.mark.esp32]),
|
||||
pytest.param('isr_dispatch_esp32c3', marks=[pytest.mark.esp32c3]),
|
||||
pytest.param('cpu1_esp32', marks=[pytest.mark.esp32]),
|
||||
pytest.param('any_cpu_esp32', marks=[pytest.mark.esp32]),
|
||||
pytest.param('cpu1_esp32s3', marks=[pytest.mark.esp32s3]),
|
||||
pytest.param('any_cpu_esp32s3', marks=[pytest.mark.esp32s3]),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize(
|
||||
|
||||
@@ -36,28 +36,28 @@ def test_wifi_connect_cases_esp32c2_xtal26m(case_tester: CaseTester) -> None:
|
||||
case_tester.run_all_cases()
|
||||
|
||||
|
||||
@pytest.mark.esp32c2eco4
|
||||
@pytest.mark.esp32c2_rev2
|
||||
@pytest.mark.two_duts
|
||||
@pytest.mark.xtal_26mhz
|
||||
@pytest.mark.parametrize(
|
||||
'count, config, target, baud',
|
||||
[
|
||||
(2, 'esp32c2eco4_xtal26m', 'esp32c2', '74880'),
|
||||
(2, 'esp32c2_rev2_xtal26m', 'esp32c2', '74880'),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_wifi_connect_cases_esp32c2eco4_xtal26m(case_tester: CaseTester) -> None:
|
||||
def test_wifi_connect_cases_esp32c2_rev2_xtal26m(case_tester: CaseTester) -> None:
|
||||
case_tester.run_all_cases()
|
||||
|
||||
|
||||
@pytest.mark.two_duts
|
||||
@pytest.mark.esp32c3eco7
|
||||
@pytest.mark.esp32c3_rev1
|
||||
@pytest.mark.parametrize(
|
||||
'count, config, target',
|
||||
[
|
||||
(2, 'esp32c3eco7', 'esp32c3'),
|
||||
(2, 'esp32c3_rev1', 'esp32c3'),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_wifi_connect_cases_esp32c3eco7(case_tester: CaseTester) -> None:
|
||||
def test_wifi_connect_cases_esp32c3_rev1(case_tester: CaseTester) -> None:
|
||||
case_tester.run_all_cases()
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
# @pytest.mark.esp32c2 # esp32c2 are using xtal_26mhz
|
||||
|
||||
|
||||
@@ -28,26 +29,26 @@ def test_wifi_unit_test_esp32c2_xtal26m(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
|
||||
|
||||
@pytest.mark.esp32c2eco4
|
||||
@pytest.mark.esp32c2_rev2
|
||||
@pytest.mark.xtal_26mhz
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize(
|
||||
'config, target, baud',
|
||||
[
|
||||
('esp32c2eco4_xtal26m', 'esp32c2', '74880'),
|
||||
('esp32c2_rev2_xtal26m', 'esp32c2', '74880'),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_wifi_unit_test_esp32c2eco4_xtal26m(dut: Dut) -> None:
|
||||
def test_wifi_unit_test_esp32c2_rev2_xtal26m(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
|
||||
|
||||
@pytest.mark.esp32c3eco7
|
||||
@pytest.mark.esp32c3_rev1
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize(
|
||||
'config, target',
|
||||
[('esp32c3eco7', 'esp32c3')],
|
||||
[('esp32c3_rev1', 'esp32c3')],
|
||||
indirect=True,
|
||||
)
|
||||
def test_wifi_unit_test_esp32c3eco7(dut: Dut) -> None:
|
||||
def test_wifi_unit_test_esp32c3_rev1(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
# SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.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_fatfs_linux(dut: Dut) -> None:
|
||||
dut.expect_exact('All tests passed', timeout=120)
|
||||
|
||||
@@ -4,28 +4,6 @@ import pytest
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
CONFIGS = [
|
||||
pytest.param(
|
||||
'default',
|
||||
marks=[
|
||||
pytest.mark.supported_targets,
|
||||
],
|
||||
),
|
||||
pytest.param('freertos_options', marks=[pytest.mark.supported_targets]),
|
||||
pytest.param('psram', marks=[pytest.mark.esp32, pytest.mark.esp32s3, pytest.mark.esp32p4, pytest.mark.esp32c5]),
|
||||
pytest.param('single_core', marks=[pytest.mark.esp32, pytest.mark.esp32p4]),
|
||||
# TODO: [ESP32C5] IDF-10335
|
||||
# TODO: [ESP32C61] IDF-11146
|
||||
pytest.param(
|
||||
'smp',
|
||||
marks=[
|
||||
pytest.mark.supported_targets,
|
||||
pytest.mark.temp_skip_ci(targets=['esp32p4', 'esp32c5', 'esp32c61'], reason='test failed/TBD IDF-8113'),
|
||||
],
|
||||
),
|
||||
pytest.param('tickless_idle', marks=[pytest.mark.supported_targets]),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize(
|
||||
@@ -34,9 +12,6 @@ CONFIGS = [
|
||||
('default', 'supported_targets'),
|
||||
('freertos_options', 'supported_targets'),
|
||||
('psram', 'esp32'),
|
||||
('psram', 'esp32c5'),
|
||||
('psram', 'esp32p4'),
|
||||
('psram', 'esp32s3'),
|
||||
('single_core', 'esp32'),
|
||||
('single_core', 'esp32p4'),
|
||||
(
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
# SPDX-FileCopyrightText: 2023-2026 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_hal_utils(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
|
||||
@@ -20,11 +20,14 @@ 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',
|
||||
[('no_poisoning', 'idf,qemu'), ('light_poisoning', 'idf,qemu'), ('comprehensive_poisoning', 'idf,qemu')],
|
||||
[
|
||||
('no_poisoning', 'idf,qemu'),
|
||||
('light_poisoning', 'idf,qemu'),
|
||||
('comprehensive_poisoning', 'idf,qemu'),
|
||||
],
|
||||
)
|
||||
@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target'])
|
||||
def test_heap_poisoning_qemu(dut: Dut) -> None:
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
# SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.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_heap_linux(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases(timeout=60)
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
import pytest
|
||||
from pytest_embedded_idf.dut import IdfDut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@idf_parametrize('target', ['linux'], indirect=['target'])
|
||||
def test_linux_component(dut: IdfDut) -> None:
|
||||
dut.expect_exact('Press ENTER to see the list of tests.')
|
||||
|
||||
@@ -5,12 +5,12 @@ from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'default',
|
||||
'system_timestamptag_level_linked_list',
|
||||
'system_timestamp',
|
||||
'tag_level_linked_list',
|
||||
'tag_level_linked_list_and_array_cache',
|
||||
'tag_level_none',
|
||||
],
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.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_nvs_host_linux(dut: Dut) -> None:
|
||||
dut.expect_exact('All tests passed', timeout=60)
|
||||
@@ -1,11 +1,9 @@
|
||||
# SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.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_nvs_page_linux(dut: Dut) -> None:
|
||||
dut.expect_unity_test_output(timeout=120)
|
||||
|
||||
@@ -4,11 +4,6 @@ import pytest
|
||||
from pytest_embedded_idf.dut import IdfDut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
CONFIGS_NVS_ENCR_FLASH_ENC = [
|
||||
pytest.param('nvs_encr_flash_enc_esp32', marks=[pytest.mark.esp32]),
|
||||
pytest.param('nvs_encr_flash_enc_esp32c3', marks=[pytest.mark.esp32c3]),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize('config', ['default'], indirect=True)
|
||||
|
||||
@@ -20,7 +20,9 @@ def test_pthread(dut: Dut) -> None:
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize(
|
||||
'config,target', [('single_core_esp32', 'esp32'), ('single_core_esp32s3', 'esp32s3')], indirect=['config', 'target']
|
||||
'config,target',
|
||||
[('single_core_esp32', 'esp32'), ('single_core_esp32s3', 'esp32s3')],
|
||||
indirect=['config', 'target'],
|
||||
)
|
||||
def test_pthread_single_core(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases(group='!thread-specific', timeout=300)
|
||||
@@ -46,7 +48,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:
|
||||
@@ -55,7 +56,6 @@ def test_pthread_qemu(dut: Dut) -> None:
|
||||
dut._run_normal_case(case, timeout=75)
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@idf_parametrize('target', ['linux'], indirect=['target'])
|
||||
def test_pthread_linux(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases(timeout=120)
|
||||
|
||||
@@ -5,7 +5,6 @@ from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.parametrize('config', ['erase_check', 'no_erase_check'])
|
||||
@idf_parametrize('target', ['linux'], indirect=['target'])
|
||||
def test_spiffs_linux(dut: Dut) -> None:
|
||||
|
||||
@@ -4,11 +4,6 @@ import pytest
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
CONFIGS = [
|
||||
pytest.param('default', marks=[pytest.mark.esp32, pytest.mark.esp32c3]),
|
||||
pytest.param('psram_esp32', marks=[pytest.mark.esp32]),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize(
|
||||
|
||||
@@ -4,11 +4,6 @@ import pytest
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
CONFIGS = [
|
||||
pytest.param('default', marks=[pytest.mark.esp32s2, pytest.mark.esp32s3, pytest.mark.esp32p4]),
|
||||
pytest.param('esp32p4_psram', marks=[pytest.mark.esp32p4]),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.usb_host_flash_disk
|
||||
@pytest.mark.temp_skip_ci(targets=['esp32s2'], reason='lack of runners with usb_host_flash_disk tag')
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
# SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.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_wear_levelling_linux(dut: Dut) -> None:
|
||||
dut.expect_exact('All tests passed', timeout=120)
|
||||
|
||||
@@ -34,40 +34,40 @@ def test_wpa_supplicant_ut_offchan(case_tester: CaseTester) -> None:
|
||||
|
||||
|
||||
@pytest.mark.two_duts
|
||||
@pytest.mark.esp32c2eco4
|
||||
@pytest.mark.esp32c2_rev2
|
||||
@pytest.mark.xtal_26mhz
|
||||
@pytest.mark.parametrize(
|
||||
'count, config, target, baud',
|
||||
[
|
||||
(
|
||||
2,
|
||||
'esp32c2eco4_xtal26m',
|
||||
'esp32c2_rev2_xtal26m',
|
||||
'esp32c2',
|
||||
'74880',
|
||||
),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_wpa_supplicant_esp32c2eco4_xtal26mhz(case_tester: CaseTester) -> None:
|
||||
def test_wpa_supplicant_esp32c2_rev2_xtal26mhz(case_tester: CaseTester) -> None:
|
||||
for case in case_tester.test_menu:
|
||||
if case.attributes.get('test_env') == 'two_duts':
|
||||
case_tester.run_multi_dev_case(case=case, reset=True)
|
||||
|
||||
|
||||
@pytest.mark.two_duts
|
||||
@pytest.mark.esp32c3eco7
|
||||
@pytest.mark.esp32c3_rev1
|
||||
@pytest.mark.parametrize(
|
||||
'count, config, target',
|
||||
[
|
||||
(
|
||||
2,
|
||||
'esp32c3eco7',
|
||||
'esp32c3_rev1',
|
||||
'esp32c3',
|
||||
),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_wpa_supplicant_esp32c3eco7(case_tester: CaseTester) -> None:
|
||||
def test_wpa_supplicant_esp32c3_rev1(case_tester: CaseTester) -> None:
|
||||
for case in case_tester.test_menu:
|
||||
if case.attributes.get('test_env') == 'two_duts':
|
||||
case_tester.run_multi_dev_case(case=case, reset=True)
|
||||
|
||||
285
conftest.py
285
conftest.py
@@ -1,4 +1,4 @@
|
||||
# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# pylint: disable=W0621 # redefined-outer-name
|
||||
#
|
||||
@@ -9,6 +9,7 @@
|
||||
# please report to https://github.com/espressif/pytest-embedded/issues
|
||||
# or discuss at https://github.com/espressif/pytest-embedded/discussions
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
if os.path.join(os.path.dirname(__file__), 'tools', 'ci') not in sys.path:
|
||||
@@ -17,8 +18,6 @@ if os.path.join(os.path.dirname(__file__), 'tools', 'ci') not in sys.path:
|
||||
if os.path.join(os.path.dirname(__file__), 'tools', 'ci', 'python_packages') not in sys.path:
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), 'tools', 'ci', 'python_packages'))
|
||||
|
||||
import glob
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
@@ -26,7 +25,6 @@ import re
|
||||
import signal
|
||||
import time
|
||||
import typing as t
|
||||
import zipfile
|
||||
from copy import deepcopy
|
||||
from urllib.parse import quote
|
||||
|
||||
@@ -34,20 +32,19 @@ import common_test_methods # noqa: F401
|
||||
import gitlab_api
|
||||
import pexpect
|
||||
import pytest
|
||||
import requests
|
||||
import yaml
|
||||
from _pytest.config import Config
|
||||
from _pytest.fixtures import FixtureRequest
|
||||
from artifacts_handler import ArtifactType
|
||||
from dynamic_pipelines.constants import TEST_RELATED_APPS_DOWNLOAD_URLS_FILENAME
|
||||
from idf_ci_local.app import import_apps_from_txt
|
||||
from idf_ci_local.uploader import AppDownloader, AppUploader
|
||||
from idf_ci_utils import IDF_PATH, idf_relpath
|
||||
from idf_pytest.constants import DEFAULT_SDKCONFIG, ENV_MARKERS, SPECIAL_MARKERS, TARGET_MARKERS, PytestCase, \
|
||||
DEFAULT_LOGDIR
|
||||
from idf_pytest.plugin import IDF_PYTEST_EMBEDDED_KEY, ITEM_PYTEST_CASE_KEY, IdfPytestEmbedded
|
||||
from idf_ci import PytestCase
|
||||
from idf_ci.idf_pytest import IDF_CI_PYTEST_CASE_KEY
|
||||
from idf_ci_utils import IDF_PATH
|
||||
from idf_ci_utils import idf_relpath
|
||||
from idf_pytest.constants import DEFAULT_LOGDIR
|
||||
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 multi_dut_argument, multi_dut_fixture
|
||||
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
|
||||
from pytest_embedded_idf.dut import IdfDut
|
||||
@@ -74,23 +71,6 @@ def case_tester(unity_tester: CaseTester) -> CaseTester:
|
||||
return unity_tester
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@multi_dut_argument
|
||||
def config(request: FixtureRequest) -> str:
|
||||
return getattr(request, 'param', None) or DEFAULT_SDKCONFIG # type: ignore
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@multi_dut_fixture
|
||||
def target(request: FixtureRequest, dut_total: int, dut_index: int) -> str:
|
||||
plugin = request.config.stash[IDF_PYTEST_EMBEDDED_KEY]
|
||||
|
||||
if dut_total == 1:
|
||||
return plugin.target[0] # type: ignore
|
||||
|
||||
return plugin.target[dut_index] # type: ignore
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_func_name(request: FixtureRequest) -> str:
|
||||
return request.node.function.__name__ # type: ignore
|
||||
@@ -117,33 +97,69 @@ def pipeline_id(request: FixtureRequest) -> t.Optional[str]:
|
||||
return request.config.getoption('pipeline_id', None) or os.getenv('PARENT_PIPELINE_ID', None) # type: ignore
|
||||
|
||||
|
||||
class BuildReportDownloader(AppDownloader):
|
||||
def __init__(self, presigned_url_yaml: str) -> None:
|
||||
self.app_presigned_urls_dict: t.Dict[str, t.Dict[str, str]] = yaml.safe_load(presigned_url_yaml)
|
||||
def get_pipeline_commit_sha_by_pipeline_id(pipeline_id: str) -> t.Optional[str]:
|
||||
gl = gitlab_api.Gitlab(os.getenv('CI_PROJECT_ID', 'espressif/esp-idf'))
|
||||
pipeline = gl.project.pipelines.get(pipeline_id)
|
||||
if not pipeline:
|
||||
return None
|
||||
|
||||
def _download_app(self, app_build_path: str, artifact_type: ArtifactType) -> None:
|
||||
url = self.app_presigned_urls_dict[app_build_path][artifact_type.value]
|
||||
commit = gl.project.commits.get(pipeline.sha)
|
||||
if not commit or not commit.parent_ids:
|
||||
return None
|
||||
|
||||
logging.info('Downloading app from %s', url)
|
||||
with io.BytesIO() as f:
|
||||
for chunk in requests.get(url).iter_content(chunk_size=1024 * 1024):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
if len(commit.parent_ids) == 1:
|
||||
return commit.parent_ids[0] # type: ignore
|
||||
|
||||
f.seek(0)
|
||||
for parent_id in commit.parent_ids:
|
||||
parent_commit = gl.project.commits.get(parent_id)
|
||||
if parent_commit.parent_ids and len(parent_commit.parent_ids) == 1:
|
||||
return parent_id # type: ignore
|
||||
|
||||
with zipfile.ZipFile(f) as zip_ref:
|
||||
zip_ref.extractall(IDF_PATH)
|
||||
return None
|
||||
|
||||
def download_app(self, app_build_path: str, artifact_type: t.Optional[ArtifactType] = None) -> None:
|
||||
if app_build_path not in self.app_presigned_urls_dict:
|
||||
raise ValueError(
|
||||
f'No presigned url found for {app_build_path}. '
|
||||
f'Usually this should not happen, please re-trigger a pipeline.'
|
||||
f'If this happens again, please report this bug to the CI channel.'
|
||||
)
|
||||
|
||||
super().download_app(app_build_path, artifact_type)
|
||||
class AppDownloader:
|
||||
def __init__(
|
||||
self,
|
||||
commit_sha: str,
|
||||
pipeline_id: t.Optional[str] = None,
|
||||
) -> None:
|
||||
self.commit_sha = commit_sha
|
||||
self.pipeline_id = pipeline_id
|
||||
|
||||
def download_app(self, app_dir: str, build_dir: str, artifact_type: t.Optional[str] = None) -> None:
|
||||
args = [
|
||||
'idf-ci',
|
||||
'gitlab',
|
||||
'download-artifacts',
|
||||
'--commit-sha',
|
||||
self.commit_sha,
|
||||
]
|
||||
if artifact_type:
|
||||
args.extend(['--type', artifact_type])
|
||||
|
||||
if self.pipeline_id:
|
||||
args.extend(['--pipeline-id', self.pipeline_id])
|
||||
|
||||
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)
|
||||
|
||||
|
||||
PRESIGNED_JSON = 'presigned.json'
|
||||
|
||||
|
||||
class OpenOCD:
|
||||
@@ -166,7 +182,7 @@ class OpenOCD:
|
||||
desc_path = os.path.join(self.dut.app.binary_path, 'project_description.json')
|
||||
|
||||
try:
|
||||
with open(desc_path, 'r') as f:
|
||||
with open(desc_path) as f:
|
||||
project_desc = json.load(f)
|
||||
except FileNotFoundError:
|
||||
logging.error('Project description file not found at %s', desc_path)
|
||||
@@ -184,7 +200,7 @@ class OpenOCD:
|
||||
ocd_env = os.environ.copy()
|
||||
ocd_env['LIBUSB_DEBUG'] = '1'
|
||||
|
||||
for _ in range(1, self.MAX_RETRIES + 1):
|
||||
for attempt in range(1, self.MAX_RETRIES + 1):
|
||||
try:
|
||||
self.proc = pexpect.spawn(
|
||||
command='openocd',
|
||||
@@ -197,10 +213,12 @@ class OpenOCD:
|
||||
if self.proc and self.proc.isalive():
|
||||
self.proc.expect_exact('Info : Listening on port 3333 for gdb connections', timeout=5)
|
||||
self.connect_telnet()
|
||||
self.write('log_output {}'.format(self.log_file))
|
||||
self.write(f'log_output {self.log_file}')
|
||||
return self
|
||||
except (pexpect.exceptions.EOF, pexpect.exceptions.TIMEOUT, ConnectionRefusedError) as e:
|
||||
logging.error('Error running OpenOCD: %s', str(e))
|
||||
logging.error(
|
||||
'OpenOCD connection attempt %d/%d failed. Error: %s', attempt, self.MAX_RETRIES, type(e).__name__
|
||||
)
|
||||
self.kill()
|
||||
time.sleep(self.RETRY_DELAY)
|
||||
|
||||
@@ -255,38 +273,22 @@ def openocd_dut(dut: IdfDut) -> OpenOCD:
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def app_downloader(pipeline_id: t.Optional[str]) -> t.Optional[AppDownloader]:
|
||||
if commit_sha := os.getenv('PIPELINE_COMMIT_SHA'):
|
||||
logging.debug('pipeline commit sha from CI env is %s', commit_sha)
|
||||
return AppDownloader(commit_sha, None)
|
||||
|
||||
if not pipeline_id:
|
||||
return None
|
||||
|
||||
if (
|
||||
'IDF_S3_BUCKET' in os.environ
|
||||
and 'IDF_S3_ACCESS_KEY' in os.environ
|
||||
and 'IDF_S3_SECRET_KEY' in os.environ
|
||||
and 'IDF_S3_SERVER' in os.environ
|
||||
and 'IDF_S3_BUCKET' in os.environ
|
||||
):
|
||||
return AppUploader(pipeline_id)
|
||||
commit_sha = get_pipeline_commit_sha_by_pipeline_id(pipeline_id)
|
||||
if not commit_sha:
|
||||
raise ValueError(
|
||||
'commit sha cannot be found for pipeline id %s. Please check the pipeline id. '
|
||||
'If you think this is a bug, please report it to CI team',
|
||||
)
|
||||
logging.debug('pipeline commit sha of pipeline %s is %s', pipeline_id, commit_sha)
|
||||
|
||||
logging.info('Downloading build report from the build pipeline %s', pipeline_id)
|
||||
test_app_presigned_urls_file = None
|
||||
|
||||
gl = gitlab_api.Gitlab(os.getenv('CI_PROJECT_ID', 'espressif/esp-idf'))
|
||||
|
||||
for child_pipeline in gl.project.pipelines.get(pipeline_id, lazy=True).bridges.list(iterator=True):
|
||||
if child_pipeline.name == 'build_child_pipeline':
|
||||
for job in gl.project.pipelines.get(child_pipeline.downstream_pipeline['id'], lazy=True).jobs.list(
|
||||
iterator=True
|
||||
):
|
||||
if job.name == 'generate_pytest_build_report':
|
||||
test_app_presigned_urls_file = gl.download_artifact(
|
||||
job.id, [TEST_RELATED_APPS_DOWNLOAD_URLS_FILENAME]
|
||||
)[0]
|
||||
break
|
||||
|
||||
if test_app_presigned_urls_file:
|
||||
return BuildReportDownloader(test_app_presigned_urls_file)
|
||||
|
||||
return None
|
||||
return AppDownloader(commit_sha, pipeline_id)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -310,14 +312,21 @@ def build_dir(
|
||||
valid build directory
|
||||
"""
|
||||
# download from minio on CI
|
||||
case: PytestCase = request._pyfuncitem.stash[ITEM_PYTEST_CASE_KEY]
|
||||
if app_downloader:
|
||||
case: PytestCase = request.node.stash[IDF_CI_PYTEST_CASE_KEY]
|
||||
if 'skip_app_downloader' in case.all_markers:
|
||||
logging.debug('skip_app_downloader marker found, skip downloading app')
|
||||
downloader = None
|
||||
else:
|
||||
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 case.requires_elf_or_map:
|
||||
app_downloader.download_app(app_build_path)
|
||||
if requires_elf_or_map(case):
|
||||
downloader.download_app(app_dir, build_dir)
|
||||
else:
|
||||
app_downloader.download_app(app_build_path, ArtifactType.BUILD_DIR_WITHOUT_MAP_AND_ELF_FILES)
|
||||
downloader.download_app(app_dir, build_dir, 'flash')
|
||||
check_dirs = [f'build_{target}_{config}']
|
||||
else:
|
||||
check_dirs = []
|
||||
@@ -438,7 +447,7 @@ def check_performance(idf_path: str) -> t.Callable[[str, float, str], None]:
|
||||
def _find_perf_item(operator: str, path: str) -> float:
|
||||
with open(path, encoding='utf-8') as f:
|
||||
data = f.read()
|
||||
match = re.search(fr'#define\s+IDF_PERFORMANCE_{operator}_{item.upper()}\s+([\d.]+)', data)
|
||||
match = re.search(rf'#define\s+IDF_PERFORMANCE_{operator}_{item.upper()}\s+([\d.]+)', data)
|
||||
return float(match.group(1)) # type: ignore
|
||||
|
||||
def _check_perf(operator: str, standard_value: float) -> None:
|
||||
@@ -477,16 +486,18 @@ def check_performance(idf_path: str) -> t.Callable[[str, float, str], None]:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def log_minimum_free_heap_size(dut: IdfDut, config: str) -> t.Callable[..., None]:
|
||||
def log_minimum_free_heap_size(dut: IdfDut, config: str, idf_path: str) -> t.Callable[..., None]:
|
||||
def real_func() -> None:
|
||||
res = dut.expect(r'Minimum free heap size: (\d+) bytes')
|
||||
logging.info(
|
||||
'\n------ heap size info ------\n'
|
||||
'[app_path] {}\n'
|
||||
'[app_name] {}\n'
|
||||
'[config_name] {}\n'
|
||||
'[target] {}\n'
|
||||
'[minimum_free_heap_size] {} Bytes\n'
|
||||
'------ heap size end ------'.format(
|
||||
dut.app.app_path.replace(idf_path, '').lstrip('/\\'),
|
||||
os.path.basename(dut.app.app_path),
|
||||
config,
|
||||
dut.target,
|
||||
@@ -499,12 +510,12 @@ def log_minimum_free_heap_size(dut: IdfDut, config: str) -> t.Callable[..., None
|
||||
|
||||
@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
|
||||
|
||||
|
||||
##################
|
||||
@@ -512,10 +523,6 @@ def dev_user(request: FixtureRequest) -> str:
|
||||
##################
|
||||
def pytest_addoption(parser: pytest.Parser) -> None:
|
||||
idf_group = parser.getgroup('idf')
|
||||
idf_group.addoption(
|
||||
'--sdkconfig',
|
||||
help='sdkconfig postfix, like sdkconfig.ci.<config>. (Default: None, which would build all found apps)',
|
||||
)
|
||||
idf_group.addoption(
|
||||
'--dev-user',
|
||||
help='user name associated with some specific device/service used during the test execution',
|
||||
@@ -524,80 +531,34 @@ def pytest_addoption(parser: pytest.Parser) -> None:
|
||||
'--dev-passwd',
|
||||
help='password associated with some specific device/service used during the test execution',
|
||||
)
|
||||
idf_group.addoption(
|
||||
'--app-info-filepattern',
|
||||
help='glob pattern to specify the files that include built app info generated by '
|
||||
'`idf-build-apps --collect-app-info ...`. will not raise ValueError when binary '
|
||||
'paths not exist in local file system if not listed recorded in the app info.',
|
||||
)
|
||||
idf_group.addoption(
|
||||
'--pipeline-id',
|
||||
help='main pipeline id, not the child pipeline id. Specify this option to download the artifacts '
|
||||
'from the minio server for debugging purpose.',
|
||||
help='For users without s3 access. main pipeline id, not the child pipeline id. '
|
||||
'Specify this option to download the artifacts from the minio server for debugging purpose.',
|
||||
)
|
||||
|
||||
|
||||
def pytest_configure(config: Config) -> None:
|
||||
# cli option "--target"
|
||||
target = [_t.strip().lower() for _t in (config.getoption('target', '') or '').split(',') if _t.strip()]
|
||||
from idf_pytest.constants import PREVIEW_TARGETS
|
||||
from idf_pytest.constants import SUPPORTED_TARGETS
|
||||
from pytest_embedded_idf.utils import preview_targets
|
||||
from pytest_embedded_idf.utils import supported_targets
|
||||
|
||||
# add markers based on idf_pytest/constants.py
|
||||
for name, description in {
|
||||
**TARGET_MARKERS,
|
||||
**ENV_MARKERS,
|
||||
**SPECIAL_MARKERS,
|
||||
}.items():
|
||||
config.addinivalue_line('markers', f'{name}: {description}')
|
||||
supported_targets.set(SUPPORTED_TARGETS)
|
||||
preview_targets.set(PREVIEW_TARGETS)
|
||||
|
||||
help_commands = ['--help', '--fixtures', '--markers', '--version']
|
||||
for cmd in help_commands:
|
||||
if cmd in config.invocation_params.args:
|
||||
target = ['unneeded']
|
||||
break
|
||||
|
||||
markexpr = config.getoption('markexpr') or ''
|
||||
# check marker expr set via "pytest -m"
|
||||
if not target and markexpr:
|
||||
# we use `-m "esp32 and generic"` in our CI to filter the test cases
|
||||
# this doesn't cover all use cases, but fit what we do in CI.
|
||||
for marker in markexpr.split('and'):
|
||||
marker = marker.strip()
|
||||
if marker in TARGET_MARKERS:
|
||||
target.append(marker)
|
||||
|
||||
# "--target" must be set
|
||||
if not target:
|
||||
raise SystemExit(
|
||||
"""Pass `--target TARGET[,TARGET...]` to specify all targets the test cases are using.
|
||||
- for single DUT, we run with `pytest --target esp32`
|
||||
- for multi DUT, we run with `pytest --target esp32,esp32,esp32s2` to indicate all DUTs
|
||||
"""
|
||||
)
|
||||
|
||||
apps = None
|
||||
app_info_filepattern = config.getoption('app_info_filepattern')
|
||||
if app_info_filepattern:
|
||||
apps = []
|
||||
for f in glob.glob(os.path.join(IDF_PATH, app_info_filepattern)):
|
||||
apps.extend(import_apps_from_txt(f))
|
||||
|
||||
if '--collect-only' not in config.invocation_params.args:
|
||||
config.stash[IDF_PYTEST_EMBEDDED_KEY] = IdfPytestEmbedded(
|
||||
config_name=config.getoption('sdkconfig'),
|
||||
target=target,
|
||||
apps=apps,
|
||||
)
|
||||
config.pluginmanager.register(config.stash[IDF_PYTEST_EMBEDDED_KEY])
|
||||
config.stash[IDF_LOCAL_PLUGIN_KEY] = IdfLocalPlugin()
|
||||
config.pluginmanager.register(config.stash[IDF_LOCAL_PLUGIN_KEY])
|
||||
|
||||
|
||||
def pytest_unconfigure(config: Config) -> None:
|
||||
_pytest_embedded = config.stash.get(IDF_PYTEST_EMBEDDED_KEY, None)
|
||||
if _pytest_embedded:
|
||||
del config.stash[IDF_PYTEST_EMBEDDED_KEY]
|
||||
config.pluginmanager.unregister(_pytest_embedded)
|
||||
idf_local_plugin = config.stash.get(IDF_LOCAL_PLUGIN_KEY, None)
|
||||
if idf_local_plugin:
|
||||
del config.stash[IDF_LOCAL_PLUGIN_KEY]
|
||||
config.pluginmanager.unregister(idf_local_plugin)
|
||||
|
||||
|
||||
dut_artifacts_url = []
|
||||
dut_artifacts_url: t.List[str] = []
|
||||
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
@@ -620,10 +581,10 @@ def pytest_runtest_makereport(item, call): # type: ignore
|
||||
|
||||
if isinstance(_dut, list):
|
||||
logs_files.extend([template.format(get_path(d.logfile)) for d in _dut])
|
||||
dut_artifacts_url.append('{}:'.format(_dut[0].test_case_name))
|
||||
dut_artifacts_url.append(f'{_dut[0].test_case_name}:')
|
||||
else:
|
||||
logs_files.append(template.format(get_path(_dut.logfile)))
|
||||
dut_artifacts_url.append('{}:'.format(_dut.test_case_name))
|
||||
dut_artifacts_url.append(f'{_dut.test_case_name}:')
|
||||
|
||||
for file in logs_files:
|
||||
dut_artifacts_url.append(' - {}'.format(quote(file, safe=':/')))
|
||||
|
||||
@@ -33,13 +33,13 @@ On the host side, ESP-IDF employs the pytest framework (alongside certain pytest
|
||||
Installation
|
||||
============
|
||||
|
||||
All basic dependencies could be installed by running the ESP-IDF install script with the ``--enable-pytest`` argument:
|
||||
All basic dependencies could be installed by running the ESP-IDF install script with the ``--enable-ci`` argument:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ install.sh --enable-pytest
|
||||
$ install.sh --enable-ci
|
||||
|
||||
Additional test script specific dependencies could be installed separately by running the ESP-IDF install script with the ``--enable-pytest-specific`` argument:
|
||||
Additional test script specific dependencies could be installed separately by running the ESP-IDF install script with the ``--enable-test-specific`` argument:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -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)
|
||||
---------------------------------
|
||||
|
||||
@@ -492,7 +581,7 @@ First you need to install ESP-IDF with additional Python requirements:
|
||||
.. code-block:: shell
|
||||
|
||||
$ cd $IDF_PATH
|
||||
$ bash install.sh --enable-ci --enable-pytest
|
||||
$ bash install.sh --enable-ci
|
||||
$ . ./export.sh
|
||||
|
||||
Build Directories
|
||||
|
||||
@@ -33,13 +33,13 @@ ESP-IDF 在主机端使用 pytest 框架(以及一些 pytest 插件)来自
|
||||
安装
|
||||
============
|
||||
|
||||
基础依赖项可以通过执行 ESP-IDF 安装脚本 ``--enable-pytest`` 进行安装:
|
||||
基础依赖项可以通过执行 ESP-IDF 安装脚本 ``--enable-ci`` 进行安装:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ install.sh --enable-pytest
|
||||
$ install.sh --enable-ci
|
||||
|
||||
额外的测试脚本依赖项可以通过执行 ESP-IDF 安装脚本 ``--enable-pytest-specific`` 进行安装:
|
||||
额外的测试脚本依赖项可以通过执行 ESP-IDF 安装脚本 ``--enable-test-specific`` 进行安装:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -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 测试用例。
|
||||
|
||||
当测试逻辑相同,但执行环境不同的时候,可使用此模式。
|
||||
|
||||
测试串行输出
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -492,7 +581,7 @@ CI 的工作流程如下所示:
|
||||
.. code-block:: shell
|
||||
|
||||
$ cd $IDF_PATH
|
||||
$ bash install.sh --enable-ci --enable-pytest
|
||||
$ bash install.sh --enable-ci
|
||||
$ . ./export.sh
|
||||
|
||||
编译目录
|
||||
|
||||
@@ -11,7 +11,7 @@ https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/esp-ble-m
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example implements Bluetooth Mesh node that supports On/Off and Level models.
|
||||
It has suport for both Advertising Bearer and GATT Bearer.
|
||||
It has support for both Advertising Bearer and GATT Bearer.
|
||||
|
||||
For more information on NimBLE MESH, please visit [NimBLE_MESH](https://mynewt.apache.org/latest/network/mesh/index.html#bluetooth-mesh).
|
||||
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
import os.path
|
||||
from pathlib import Path
|
||||
from typing import Tuple
|
||||
|
||||
import pexpect
|
||||
import pytest
|
||||
from pytest_embedded_idf.dut import IdfDut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
CUR_DIR = Path(__file__).parent.resolve()
|
||||
|
||||
|
||||
# Case 1: BLE power save test
|
||||
|
||||
|
||||
@pytest.mark.two_duts
|
||||
@pytest.mark.parametrize(
|
||||
'count, app_path',
|
||||
[
|
||||
(
|
||||
2,
|
||||
f'{os.path.join(os.path.dirname(__file__), "power_save")}|{os.path.join(os.path.dirname(__file__), "blecent")}',
|
||||
f'{str(CUR_DIR / "power_save")}|{str(CUR_DIR / "blecent")}',
|
||||
),
|
||||
],
|
||||
indirect=True,
|
||||
@@ -47,7 +49,7 @@ def test_power_save_conn(app_path: str, dut: Tuple[IdfDut, IdfDut]) -> None:
|
||||
(
|
||||
'esp32c2_xtal26m',
|
||||
2,
|
||||
f'{os.path.join(os.path.dirname(__file__), "power_save")}|{os.path.join(os.path.dirname(__file__), "blecent")}',
|
||||
f'{str(CUR_DIR / "power_save")}|{str(CUR_DIR / "blecent")}',
|
||||
'74880',
|
||||
),
|
||||
],
|
||||
@@ -71,21 +73,21 @@ def test_power_save_conn_esp32c2_26mhz(dut: Tuple[IdfDut, IdfDut]) -> None:
|
||||
# Case 2: BLE power save test for ESP32C2ECO4
|
||||
@pytest.mark.two_duts
|
||||
@pytest.mark.xtal_26mhz
|
||||
@pytest.mark.esp32c2eco4
|
||||
@pytest.mark.esp32c2_rev2
|
||||
@pytest.mark.parametrize(
|
||||
'config, count, target, app_path, baud',
|
||||
[
|
||||
(
|
||||
'esp32c2eco4_xtal26m',
|
||||
'esp32c2_rev2_xtal26m',
|
||||
2,
|
||||
'esp32c2',
|
||||
f'{os.path.join(os.path.dirname(__file__), "power_save")}|{os.path.join(os.path.dirname(__file__), "blecent")}',
|
||||
f'{str(CUR_DIR / "power_save")}|{str(CUR_DIR / "blecent")}',
|
||||
'74880',
|
||||
),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_power_save_conn_esp32c2eco4(dut: Tuple[IdfDut, IdfDut]) -> None:
|
||||
def test_power_save_conn_esp32c2_rev2(dut: Tuple[IdfDut, IdfDut]) -> None:
|
||||
peripheral = dut[0]
|
||||
central = dut[1]
|
||||
|
||||
@@ -101,20 +103,20 @@ def test_power_save_conn_esp32c2eco4(dut: Tuple[IdfDut, IdfDut]) -> None:
|
||||
|
||||
# Case 2: BLE power save test for ESP32C3ECO7
|
||||
@pytest.mark.two_duts
|
||||
@pytest.mark.esp32c3eco7
|
||||
@pytest.mark.esp32c3_rev1
|
||||
@pytest.mark.parametrize(
|
||||
'config, count, target, app_path',
|
||||
[
|
||||
(
|
||||
'esp32c3eco7',
|
||||
'esp32c3_rev1',
|
||||
2,
|
||||
'esp32c3',
|
||||
f'{os.path.join(os.path.dirname(__file__), "power_save")}|{os.path.join(os.path.dirname(__file__), "blecent")}',
|
||||
f'{str(CUR_DIR / "power_save")}|{str(CUR_DIR / "blecent")}',
|
||||
),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_power_save_conn_esp32c3eco7(dut: Tuple[IdfDut, IdfDut]) -> None:
|
||||
def test_power_save_conn_esp32c3_rev1(dut: Tuple[IdfDut, IdfDut]) -> None:
|
||||
peripheral = dut[0]
|
||||
central = dut[1]
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import logging
|
||||
from typing import Callable
|
||||
from collections.abc import Callable
|
||||
|
||||
import pytest
|
||||
from pytest_embedded_idf.dut import IdfDut
|
||||
@@ -18,14 +20,12 @@ def test_hello_world(dut: IdfDut, log_minimum_free_heap_size: Callable[..., None
|
||||
log_minimum_free_heap_size()
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@idf_parametrize('target', ['linux'], indirect=['target'])
|
||||
def test_hello_world_linux(dut: IdfDut) -> None:
|
||||
dut.expect('Hello world!')
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.macos_shell
|
||||
@pytest.mark.macos
|
||||
@idf_parametrize('target', ['linux'], indirect=['target'])
|
||||
def test_hello_world_macos(dut: IdfDut) -> None:
|
||||
dut.expect('Hello world!')
|
||||
@@ -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'], indirect=['target'])
|
||||
def test_hello_world_host(app: QemuApp, dut: QemuDut) -> None:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import io
|
||||
import ipaddress
|
||||
@@ -9,14 +11,10 @@ import re
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
from collections.abc import Generator
|
||||
from concurrent.futures import Future
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import Generator
|
||||
from typing import List
|
||||
from typing import Match
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
from re import Match
|
||||
|
||||
import netifaces
|
||||
import paramiko # type: ignore
|
||||
@@ -25,9 +23,12 @@ from common_test_methods import get_host_ip_by_interface
|
||||
from netmiko import ConnectHandler
|
||||
from pytest_embedded import Dut
|
||||
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
|
||||
@@ -36,7 +37,7 @@ MIN_TCP_THROUGHPUT = 4
|
||||
|
||||
|
||||
class EndnodeSsh:
|
||||
def __init__(self, host_ip: str, usr: str, passwd: Optional[str] = None):
|
||||
def __init__(self, host_ip: str, usr: str, passwd: str | None = None):
|
||||
key_string = os.getenv('CI_ETHVM_KEY')
|
||||
key = None
|
||||
if key_string:
|
||||
@@ -93,7 +94,7 @@ class SwitchSsh:
|
||||
}
|
||||
self.ssh_client = ConnectHandler(**edgeSwitch)
|
||||
|
||||
def exec_cmd(self, cmd: Union[str, List[str]]) -> str:
|
||||
def exec_cmd(self, cmd: str | list[str]) -> str:
|
||||
if self.type == self.EDGE_SWITCH_5XP:
|
||||
_, stdout, stderr = self.ssh_client.exec_command(cmd)
|
||||
|
||||
@@ -192,7 +193,17 @@ def run_iperf(
|
||||
if ipaddress.ip_address(server_ip).is_multicast:
|
||||
# Configure Multicast Server
|
||||
server_proc = subprocess.Popen(
|
||||
['iperf', '-u', '-s', '-i', '1', '-t', str(interval), '-B', f'{server_ip}%{server_if}'],
|
||||
[
|
||||
'iperf',
|
||||
'-u',
|
||||
'-s',
|
||||
'-i',
|
||||
'1',
|
||||
'-t',
|
||||
str(interval),
|
||||
'-B',
|
||||
f'{server_ip}%{server_if}',
|
||||
],
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
@@ -284,14 +295,14 @@ def send_brcast_msg_endnode_to_host(endnode: EndnodeSsh, host_brcast_ip: str, po
|
||||
return nc_host_out
|
||||
|
||||
|
||||
def get_legacy_host_name_match() -> Optional[Match[str]]:
|
||||
def get_legacy_host_name_match() -> Match[str] | None:
|
||||
host_name = socket.gethostname()
|
||||
regex = r'ethVM-(\d+)-(\d+)'
|
||||
host_name_match = re.search(regex, host_name, re.DOTALL)
|
||||
return host_name_match
|
||||
|
||||
|
||||
def get_host_info() -> Tuple[int, int]:
|
||||
def get_host_info() -> tuple[int, int]:
|
||||
# Get switch configuration info from the hostname (legacy runners)
|
||||
sw_info = get_legacy_host_name_match()
|
||||
if sw_info is not None:
|
||||
@@ -635,5 +646,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)
|
||||
|
||||
@@ -8,16 +8,19 @@ import random
|
||||
import re
|
||||
import secrets
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from typing import Generator
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
import ot_ci_function as ocf
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
import ot_ci_function as ocf # noqa: E402
|
||||
import pexpect
|
||||
import pytest
|
||||
from pytest_embedded_idf.dut import IdfDut
|
||||
|
||||
# This file contains the test scripts for Thread:
|
||||
# Case 1: Thread network formation and attaching
|
||||
# A Thread Border Router forms a Thread network, Thread devices attach to it, then test ping
|
||||
|
||||
@@ -4,11 +4,6 @@ import pytest
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
CONFIGS = [
|
||||
pytest.param('default', marks=[pytest.mark.esp32s2, pytest.mark.esp32s3, pytest.mark.esp32p4]),
|
||||
pytest.param('esp32p4_psram', marks=[pytest.mark.esp32p4]),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.usb_host_flash_disk
|
||||
@pytest.mark.temp_skip_ci(targets=['esp32s2'], reason='lack of runners with usb_host_flash_disk tag')
|
||||
|
||||
@@ -18,7 +18,7 @@ def test_examples_protocol_esp_http_client(dut: Dut) -> None:
|
||||
"""
|
||||
binary_file = os.path.join(dut.app.binary_path, 'esp_http_client_example.bin')
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
logging.info('esp_http_client_bin_size : {}KB'.format(bin_size // 1024))
|
||||
logging.info(f'esp_http_client_bin_size : {bin_size // 1024}KB')
|
||||
# start test
|
||||
dut.expect('Connected to AP, begin http example', timeout=30)
|
||||
dut.expect(r'HTTP GET Status = 200, content_length = (\d)')
|
||||
@@ -70,7 +70,7 @@ def test_examples_protocol_esp_http_client_dynamic_buffer(dut: Dut) -> None:
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut.app.binary_path, 'esp_http_client_example.bin')
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
logging.info('esp_http_client_bin_size : {}KB'.format(bin_size // 1024))
|
||||
logging.info(f'esp_http_client_bin_size : {bin_size // 1024}KB')
|
||||
|
||||
dut.expect('Connected to AP, begin http example', timeout=30)
|
||||
dut.expect(r'HTTP GET Status = 200, content_length = (\d)')
|
||||
@@ -106,7 +106,6 @@ def test_examples_protocol_esp_http_client_dynamic_buffer(dut: Dut) -> None:
|
||||
dut.expect('Finish http example')
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
# Currently we are just testing the build for esp_http_client on Linux target. So skipping the test run.
|
||||
# Later we will enable the test run for Linux target as well.
|
||||
@pytest.mark.skipif('config.getvalue("target") == "linux"', reason='Do not run on Linux')
|
||||
|
||||
@@ -20,13 +20,13 @@ def test_examples_protocol_https_x509_bundle(dut: Dut) -> None:
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut.app.binary_path, 'https_x509_bundle.bin')
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
logging.info('https_x509_bundle_bin_size : {}KB'.format(bin_size // 1024))
|
||||
logging.info(f'https_x509_bundle_bin_size : {bin_size // 1024}KB')
|
||||
dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=30)
|
||||
# start test
|
||||
num_URLS = int(dut.expect(r'Connecting to (\d+) URLs', timeout=30)[1].decode())
|
||||
for _ in range(num_URLS):
|
||||
dut.expect(r'Connection established to ([\s\S]*)', timeout=30)
|
||||
dut.expect('Completed {} connections'.format(num_URLS), timeout=60)
|
||||
dut.expect(f'Completed {num_URLS} connections', timeout=60)
|
||||
|
||||
|
||||
@pytest.mark.ethernet
|
||||
@@ -43,16 +43,15 @@ def test_examples_protocol_https_x509_bundle_dynamic_buffer(dut: Dut) -> None:
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut.app.binary_path, 'https_x509_bundle.bin')
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
logging.info('https_x509_bundle_bin_size : {}KB'.format(bin_size // 1024))
|
||||
logging.info(f'https_x509_bundle_bin_size : {bin_size // 1024}KB')
|
||||
dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=30)
|
||||
# start test
|
||||
num_URLS = int(dut.expect(r'Connecting to (\d+) URLs', timeout=30)[1].decode())
|
||||
dut.expect(r'Connection established to ([\s\S]*)', timeout=30)
|
||||
dut.expect('Completed {} connections'.format(num_URLS), timeout=60)
|
||||
dut.expect(f'Completed {num_URLS} connections', timeout=60)
|
||||
|
||||
|
||||
@pytest.mark.qemu
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
@@ -61,14 +60,16 @@ def test_examples_protocol_https_x509_bundle_dynamic_buffer(dut: Dut) -> None:
|
||||
indirect=True,
|
||||
)
|
||||
@idf_parametrize('target', ['esp32'], indirect=['target'])
|
||||
def test_examples_protocol_https_x509_bundle_default_crt_bundle_stress_test(dut: Dut) -> None:
|
||||
def test_examples_protocol_https_x509_bundle_default_crt_bundle_stress_test(
|
||||
dut: Dut,
|
||||
) -> None:
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut.app.binary_path, 'https_x509_bundle.bin')
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
logging.info('https_x509_bundle_bin_size : {}KB'.format(bin_size // 1024))
|
||||
logging.info(f'https_x509_bundle_bin_size : {bin_size // 1024}KB')
|
||||
dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=30)
|
||||
# start test
|
||||
num_URLS = int(dut.expect(r'Connecting to (\d+) URLs', timeout=30)[1].decode())
|
||||
for _ in range(num_URLS):
|
||||
dut.expect(r'Connection established to ([\s\S]*)', timeout=60)
|
||||
dut.expect('Completed {} connections'.format(num_URLS), timeout=180)
|
||||
dut.expect(f'Completed {num_URLS} connections', timeout=180)
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
# BSD Socket API Examples
|
||||
|
||||
This directory contains simple examples demonstrating BSD Socket API.
|
||||
Each example, contains README.md file with mode detailed informations about that particular example.
|
||||
For more general informations about all examples, see the README.md file in the upper level 'examples' directory.
|
||||
Each example, contains README.md file with mode detailed information about that particular example.
|
||||
For more general information about all examples, see the README.md file in the upper level 'examples' directory.
|
||||
Examples:
|
||||
|
||||
* UDP Client - The application creates UDP socket and sends message to the predefined port and IP address. After the server's reply, the application prints received reply as ASCII text, waits for 2 seconds and sends another message.
|
||||
@@ -56,7 +56,7 @@ They can also be run locally. Ref: [ESP-IDF Tests with Pytest Guide](https://doc
|
||||
Example:
|
||||
```bash
|
||||
$ cd $IDF_PATH
|
||||
$ bash install.sh --enable-pytest
|
||||
$ bash install.sh --enable-ci
|
||||
$ . ./export.sh
|
||||
$ cd examples/protocols/sockets/tcp_client
|
||||
$ python $IDF_PATH/tools/ci/ci_build_apps.py . --target esp32 -vv --pytest-apps
|
||||
@@ -112,7 +112,7 @@ Please make sure that when using the Local Link address, an interface id is incl
|
||||
* On the host
|
||||
|
||||
- Interface name suffix is present when passing the address as a string, for example `fe80::260a:XXX:XXX:XXX%en0`
|
||||
- The interface id is present when passing the endpoint as tupple, for example `socket.connect(('fd00::260a:XXXX:XXXX:XXXX', 3333, 0, 3))`
|
||||
- The interface id is present when passing the endpoint as tuple, for example `socket.connect(('fd00::260a:XXXX:XXXX:XXXX', 3333, 0, 3))`
|
||||
|
||||
## Hardware Required
|
||||
|
||||
|
||||
@@ -10,26 +10,6 @@ from pytest_embedded_idf.utils import idf_parametrize
|
||||
touch_wake_up_support = ['esp32', 'esp32s2']
|
||||
|
||||
|
||||
CONFIGS = [
|
||||
pytest.param('esp32_singlecore', marks=[pytest.mark.esp32]),
|
||||
pytest.param(
|
||||
'basic',
|
||||
marks=[
|
||||
pytest.mark.esp32,
|
||||
pytest.mark.esp32s2,
|
||||
pytest.mark.esp32s3,
|
||||
pytest.mark.esp32c3,
|
||||
pytest.mark.esp32c5,
|
||||
pytest.mark.esp32c6,
|
||||
pytest.mark.esp32c61,
|
||||
pytest.mark.esp32h2,
|
||||
pytest.mark.esp32p4,
|
||||
pytest.mark.esp32c2,
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize(
|
||||
'config,target', [('esp32_singlecore', 'esp32'), ('basic', 'supported_targets')], indirect=['config', 'target']
|
||||
@@ -42,11 +22,11 @@ def test_deep_sleep(dut: Dut) -> None:
|
||||
'esp32s2': [9],
|
||||
}[dut.target]
|
||||
|
||||
logging.info('Expecting to see wakeup configured on pad(s): {}'.format(wake_pads))
|
||||
logging.info(f'Expecting to see wakeup configured on pad(s): {wake_pads}')
|
||||
|
||||
expect_items = ['Enabling timer wakeup, 20s']
|
||||
for pad in wake_pads:
|
||||
expect_items += [r'Touch pad #{} average: \d+, wakeup threshold set to \d+.'.format(pad)]
|
||||
expect_items += [rf'Touch pad #{pad} average: \d+, wakeup threshold set to \d+.']
|
||||
expect_items += ['Enabling touch pad wakeup']
|
||||
|
||||
for exp in expect_items:
|
||||
@@ -69,7 +49,7 @@ def test_deep_sleep(dut: Dut) -> None:
|
||||
dut.expect_exact('boot: ESP-IDF') # first output that's the same on all chips
|
||||
|
||||
sleep_time = time.time() - start_sleep
|
||||
logging.info('Host measured sleep time at {:.2f}s'.format(sleep_time))
|
||||
logging.info(f'Host measured sleep time at {sleep_time:.2f}s')
|
||||
assert 18 < sleep_time < 22 # note: high tolerance as measuring time on the host may have some timing skew
|
||||
|
||||
dut.expect_exact('boot: Fast booting app from partition', timeout=2)
|
||||
|
||||
@@ -10,7 +10,10 @@ from pytest_embedded_qemu.dut import QemuDut
|
||||
|
||||
|
||||
def basic_efuse_example(dut: Dut) -> None:
|
||||
dut.expect(r'example: Coding Scheme (3/4)|(NONE)|(REPEAT)|(RS \(Reed-Solomon coding\))', timeout=20)
|
||||
dut.expect(
|
||||
r'example: Coding Scheme (3/4)|(NONE)|(REPEAT)|(RS \(Reed-Solomon coding\))',
|
||||
timeout=20,
|
||||
)
|
||||
dut.expect(
|
||||
[
|
||||
'example: read efuse fields',
|
||||
@@ -43,7 +46,6 @@ def test_examples_efuse(dut: Dut) -> None:
|
||||
basic_efuse_example(dut)
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@idf_parametrize('target', ['linux'], indirect=['target'])
|
||||
def test_examples_efuse_linux(dut: Dut) -> None:
|
||||
basic_efuse_example(dut)
|
||||
@@ -60,14 +62,24 @@ def test_examples_efuse_linux(dut: Dut) -> None:
|
||||
@pytest.mark.parametrize('skip_autoflash', ['y'], indirect=True)
|
||||
@idf_parametrize(
|
||||
'target',
|
||||
['esp32', 'esp32c2', 'esp32c3', 'esp32c5', 'esp32c61', 'esp32c6', 'esp32h2', 'esp32s2', 'esp32s3'],
|
||||
[
|
||||
'esp32',
|
||||
'esp32c2',
|
||||
'esp32c3',
|
||||
'esp32c5',
|
||||
'esp32c61',
|
||||
'esp32c6',
|
||||
'esp32h2',
|
||||
'esp32s2',
|
||||
'esp32s3',
|
||||
],
|
||||
indirect=['target'],
|
||||
)
|
||||
def test_examples_efuse_with_virt_flash_enc(dut: Dut) -> None:
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut.app.binary_path, 'bootloader', 'bootloader.bin')
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
logging.info('{}_bootloader_virt_flash_enc_bin_size: {}KB'.format(dut.app.target, bin_size // 1024))
|
||||
logging.info(f'{dut.app.target}_bootloader_virt_flash_enc_bin_size: {bin_size // 1024}KB')
|
||||
|
||||
print(' - Erase flash')
|
||||
dut.serial.erase_flash()
|
||||
@@ -126,7 +138,7 @@ def test_examples_efuse_with_virt_flash_enc_aes_256(dut: Dut) -> None:
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut.app.binary_path, 'bootloader', 'bootloader.bin')
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
logging.info('{}_bootloader_virt_flash_enc_aes_256_bin_size: {}KB'.format(dut.app.target, bin_size // 1024))
|
||||
logging.info(f'{dut.app.target}_bootloader_virt_flash_enc_aes_256_bin_size: {bin_size // 1024}KB')
|
||||
|
||||
print(' - Erase flash')
|
||||
dut.serial.erase_flash()
|
||||
@@ -169,7 +181,17 @@ def test_examples_efuse_with_virt_flash_enc_aes_256(dut: Dut) -> None:
|
||||
@pytest.mark.parametrize('skip_autoflash', ['y'], indirect=True)
|
||||
@idf_parametrize(
|
||||
'target',
|
||||
['esp32', 'esp32c2', 'esp32c3', 'esp32c5', 'esp32c61', 'esp32c6', 'esp32h2', 'esp32s2', 'esp32s3'],
|
||||
[
|
||||
'esp32',
|
||||
'esp32c2',
|
||||
'esp32c3',
|
||||
'esp32c5',
|
||||
'esp32c61',
|
||||
'esp32c6',
|
||||
'esp32h2',
|
||||
'esp32s2',
|
||||
'esp32s3',
|
||||
],
|
||||
indirect=['target'],
|
||||
)
|
||||
def test_examples_efuse_with_virt_flash_enc_pre_loaded(dut: Dut) -> None:
|
||||
@@ -239,14 +261,24 @@ def test_examples_efuse_with_virt_flash_enc_pre_loaded(dut: Dut) -> None:
|
||||
@pytest.mark.parametrize('skip_autoflash', ['y'], indirect=True)
|
||||
@idf_parametrize(
|
||||
'target',
|
||||
['esp32', 'esp32c2', 'esp32c3', 'esp32c5', 'esp32c61', 'esp32c6', 'esp32h2', 'esp32s2', 'esp32s3'],
|
||||
[
|
||||
'esp32',
|
||||
'esp32c2',
|
||||
'esp32c3',
|
||||
'esp32c5',
|
||||
'esp32c61',
|
||||
'esp32c6',
|
||||
'esp32h2',
|
||||
'esp32s2',
|
||||
'esp32s3',
|
||||
],
|
||||
indirect=['target'],
|
||||
)
|
||||
def test_examples_efuse_with_virt_flash_enc_release(dut: Dut) -> None:
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut.app.binary_path, 'bootloader', 'bootloader.bin')
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
logging.info('{}_bootloader_virt_flash_enc_release_bin_size: {}KB'.format(dut.app.target, bin_size // 1024))
|
||||
logging.info(f'{dut.app.target}_bootloader_virt_flash_enc_release_bin_size: {bin_size // 1024}KB')
|
||||
|
||||
dut.serial.erase_flash()
|
||||
|
||||
@@ -302,7 +334,7 @@ def test_examples_efuse_with_virt_secure_boot_v1(dut: Dut) -> None:
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut.app.binary_path, 'bootloader', 'bootloader.bin')
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
logging.info('{}_bootloader_virt_secure_boot_v1_bin_size: {}KB'.format(dut.app.target, bin_size // 1024))
|
||||
logging.info(f'{dut.app.target}_bootloader_virt_secure_boot_v1_bin_size: {bin_size // 1024}KB')
|
||||
|
||||
print(' - Erase flash')
|
||||
dut.serial.erase_flash()
|
||||
@@ -400,7 +432,7 @@ def test_examples_efuse_with_virt_secure_boot_v1_pre_loaded(dut: Dut) -> None:
|
||||
dut.expect('example: Done')
|
||||
|
||||
|
||||
@pytest.mark.esp32eco3
|
||||
@pytest.mark.esp32_rev3
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
@@ -414,7 +446,7 @@ def test_examples_efuse_with_virt_secure_boot_v2(dut: Dut) -> None:
|
||||
# only for ESP32 ECO3
|
||||
binary_file = os.path.join(dut.app.binary_path, 'bootloader', 'bootloader.bin')
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
logging.info('{}_bootloader_secure_boot_v2_bin_size: {}KB'.format(dut.app.target, bin_size // 1024))
|
||||
logging.info(f'{dut.app.target}_bootloader_secure_boot_v2_bin_size: {bin_size // 1024}KB')
|
||||
|
||||
print(' - Erase flash')
|
||||
dut.serial.erase_flash()
|
||||
@@ -520,7 +552,7 @@ def test_examples_efuse_with_virt_secure_boot_v2(dut: Dut) -> None:
|
||||
dut.expect('example: Done')
|
||||
|
||||
|
||||
@pytest.mark.esp32eco3
|
||||
@pytest.mark.esp32_rev3
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
@@ -593,14 +625,24 @@ def test_examples_efuse_with_virt_secure_boot_v2_pre_loaded(dut: Dut) -> None:
|
||||
@pytest.mark.parametrize('skip_autoflash', ['y'], indirect=True)
|
||||
@idf_parametrize(
|
||||
'target',
|
||||
['esp32c3', 'esp32c2', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4', 'esp32s2', 'esp32s3'],
|
||||
[
|
||||
'esp32c3',
|
||||
'esp32c2',
|
||||
'esp32c5',
|
||||
'esp32c6',
|
||||
'esp32c61',
|
||||
'esp32h2',
|
||||
'esp32p4',
|
||||
'esp32s2',
|
||||
'esp32s3',
|
||||
],
|
||||
indirect=['target'],
|
||||
)
|
||||
def test_examples_efuse_with_virt_secure_boot_v2_esp32xx(dut: Dut) -> None:
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut.app.binary_path, 'bootloader', 'bootloader.bin')
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
logging.info('{}_bootloader_virt_secure_boot_v2_bin_size: {}KB'.format(dut.app.target, bin_size // 1024))
|
||||
logging.info(f'{dut.app.target}_bootloader_virt_secure_boot_v2_bin_size: {bin_size // 1024}KB')
|
||||
|
||||
print(' - Erase flash')
|
||||
dut.serial.erase_flash()
|
||||
@@ -616,13 +658,13 @@ def test_examples_efuse_with_virt_secure_boot_v2_esp32xx(dut: Dut) -> None:
|
||||
signed_scheme = 'ECDSA'
|
||||
else:
|
||||
signed_scheme = 'RSA-PSS'
|
||||
dut.expect('secure_boot_v2: Verifying with %s...' % signed_scheme)
|
||||
dut.expect(f'secure_boot_v2: Verifying with {signed_scheme}...')
|
||||
dut.expect('secure_boot_v2: Signature verified successfully!')
|
||||
|
||||
dut.expect('secure_boot_v2: enabling secure boot v2...')
|
||||
dut.expect('Verifying image signature...')
|
||||
dut.expect('secure_boot_v2: Secure boot V2 is not enabled yet and eFuse digest keys are not set')
|
||||
dut.expect('secure_boot_v2: Verifying with %s...' % signed_scheme)
|
||||
dut.expect(f'secure_boot_v2: Verifying with {signed_scheme}...')
|
||||
dut.expect('secure_boot_v2: Signature verified successfully!')
|
||||
dut.expect('secure_boot_v2: Secure boot digests absent, generating..')
|
||||
dut.expect('secure_boot_v2: Digests successfully calculated, 1 valid signatures')
|
||||
@@ -651,7 +693,7 @@ def test_examples_efuse_with_virt_secure_boot_v2_esp32xx(dut: Dut) -> None:
|
||||
dut.serial.hard_reset()
|
||||
dut.expect('Loading virtual efuse blocks from flash')
|
||||
dut.expect('Verifying image signature...')
|
||||
dut.expect('secure_boot_v2: Verifying with %s...' % signed_scheme)
|
||||
dut.expect(f'secure_boot_v2: Verifying with {signed_scheme}...')
|
||||
dut.expect('secure_boot_v2: Signature verified successfully!')
|
||||
dut.expect('secure_boot_v2: enabling secure boot v2...')
|
||||
dut.expect('secure_boot_v2: secure boot v2 is already enabled, continuing..')
|
||||
@@ -664,7 +706,17 @@ def test_examples_efuse_with_virt_secure_boot_v2_esp32xx(dut: Dut) -> None:
|
||||
@pytest.mark.parametrize('skip_autoflash', ['y'], indirect=True)
|
||||
@idf_parametrize(
|
||||
'target',
|
||||
['esp32c3', 'esp32c2', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4', 'esp32s2', 'esp32s3'],
|
||||
[
|
||||
'esp32c3',
|
||||
'esp32c2',
|
||||
'esp32c5',
|
||||
'esp32c6',
|
||||
'esp32c61',
|
||||
'esp32h2',
|
||||
'esp32p4',
|
||||
'esp32s2',
|
||||
'esp32s3',
|
||||
],
|
||||
indirect=['target'],
|
||||
)
|
||||
def test_example_efuse_with_virt_secure_boot_v2_esp32xx_pre_loaded(dut: Dut) -> None:
|
||||
@@ -685,12 +737,14 @@ def test_example_efuse_with_virt_secure_boot_v2_esp32xx_pre_loaded(dut: Dut) ->
|
||||
# Resets eFuse, which enables Secure boot feature
|
||||
# Resets eFuses, which control digest slots
|
||||
if dut.app.sdkconfig.get('SOC_EFUSE_REVOKE_BOOT_KEY_DIGESTS'):
|
||||
dut.serial.erase_field_on_emul_efuse_by_name([
|
||||
'SECURE_BOOT_EN',
|
||||
'SECURE_BOOT_KEY_REVOKE0',
|
||||
'SECURE_BOOT_KEY_REVOKE1',
|
||||
'SECURE_BOOT_KEY_REVOKE2',
|
||||
])
|
||||
dut.serial.erase_field_on_emul_efuse_by_name(
|
||||
[
|
||||
'SECURE_BOOT_EN',
|
||||
'SECURE_BOOT_KEY_REVOKE0',
|
||||
'SECURE_BOOT_KEY_REVOKE1',
|
||||
'SECURE_BOOT_KEY_REVOKE2',
|
||||
]
|
||||
)
|
||||
else:
|
||||
dut.serial.erase_field_on_emul_efuse_by_name(['SECURE_BOOT_EN'])
|
||||
|
||||
@@ -704,7 +758,7 @@ def test_example_efuse_with_virt_secure_boot_v2_esp32xx_pre_loaded(dut: Dut) ->
|
||||
else:
|
||||
signed_scheme = 'RSA-PSS'
|
||||
|
||||
dut.expect('secure_boot_v2: Verifying with %s...' % signed_scheme)
|
||||
dut.expect(f'secure_boot_v2: Verifying with {signed_scheme}...')
|
||||
dut.expect('secure_boot_v2: Signature verified successfully!')
|
||||
dut.expect('secure_boot_v2: Secure boot digests already present')
|
||||
dut.expect('secure_boot_v2: Using pre-loaded public key digest in eFuse')
|
||||
@@ -727,7 +781,7 @@ def test_example_efuse_with_virt_secure_boot_v2_esp32xx_pre_loaded(dut: Dut) ->
|
||||
dut.serial.hard_reset()
|
||||
dut.expect('Loading virtual efuse blocks from flash')
|
||||
dut.expect('Verifying image signature...')
|
||||
dut.expect('secure_boot_v2: Verifying with %s...' % signed_scheme)
|
||||
dut.expect(f'secure_boot_v2: Verifying with {signed_scheme}...')
|
||||
dut.expect('secure_boot_v2: Signature verified successfully!')
|
||||
dut.expect('secure_boot_v2: enabling secure boot v2...')
|
||||
dut.expect('secure_boot_v2: secure boot v2 is already enabled, continuing..')
|
||||
@@ -749,7 +803,7 @@ def test_examples_efuse_with_virt_sb_v1_and_fe(dut: Dut) -> None:
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut.app.binary_path, 'bootloader', 'bootloader.bin')
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
logging.info('{}_bootloader_virt_sb_v1_and_fe_bin_size: {}KB'.format(dut.app.target, bin_size // 1024))
|
||||
logging.info(f'{dut.app.target}_bootloader_virt_sb_v1_and_fe_bin_size: {bin_size // 1024}KB')
|
||||
|
||||
print(' - Erase flash')
|
||||
dut.serial.erase_flash()
|
||||
@@ -806,7 +860,7 @@ def test_examples_efuse_with_virt_sb_v1_and_fe(dut: Dut) -> None:
|
||||
dut.expect('example: Done')
|
||||
|
||||
|
||||
@pytest.mark.esp32eco3
|
||||
@pytest.mark.esp32_rev3
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
@@ -820,7 +874,7 @@ def test_examples_efuse_with_virt_sb_v2_and_fe(dut: Dut) -> None:
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut.app.binary_path, 'bootloader', 'bootloader.bin')
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
logging.info('{}_bootloader_virt_sb_v2_and_fe_bin_size: {}KB'.format(dut.app.target, bin_size // 1024))
|
||||
logging.info(f'{dut.app.target}_bootloader_virt_sb_v2_and_fe_bin_size: {bin_size // 1024}KB')
|
||||
|
||||
print(' - Erase flash')
|
||||
dut.serial.erase_flash()
|
||||
@@ -894,7 +948,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',
|
||||
@@ -906,7 +959,8 @@ def test_examples_efuse_with_virt_sb_v2_and_fe(dut: Dut) -> None:
|
||||
@pytest.mark.parametrize(
|
||||
'qemu_extra_args',
|
||||
[
|
||||
f'-drive file={os.path.join(os.path.dirname(__file__), "test", "esp32eco3_efuses.bin")},if=none,format=raw,id=efuse '
|
||||
f'-drive file={os.path.join(os.path.dirname(__file__), "test", "esp32_rev3_efuses.bin")},'
|
||||
'if=none,format=raw,id=efuse '
|
||||
'-global driver=nvram.esp32.efuse,property=drive,value=efuse '
|
||||
'-global driver=timer.esp32.timg,property=wdt_disable,value=true',
|
||||
],
|
||||
@@ -976,11 +1030,14 @@ def test_examples_efuse_with_virt_sb_v2_and_fe_qemu(dut: QemuDut) -> None:
|
||||
|
||||
finally:
|
||||
# the above example test burns the efuses, and hence the efuses file which the
|
||||
# qemu uses to emulate the efuses, "test/esp32eco3_efuses.bin", gets modified.
|
||||
# qemu uses to emulate the efuses, "test/esp32_rev3_efuses.bin", gets modified.
|
||||
# Thus, restore the efuses file values back to the default ESP32-ECO3 efuses values.
|
||||
with open(os.path.join(os.path.dirname(__file__), 'test', 'esp32eco3_efuses.bin'), 'wb') as efuse_file:
|
||||
esp32eco3_efuses = '0' * 26 + '8' + '0' * 17 + '1' + '0' * 203
|
||||
efuse_file.write(bytearray.fromhex(esp32eco3_efuses))
|
||||
with open(
|
||||
os.path.join(os.path.dirname(__file__), 'test', 'esp32_rev3_efuses.bin'),
|
||||
'wb',
|
||||
) as efuse_file:
|
||||
esp32_rev3_efuses = '0' * 26 + '8' + '0' * 17 + '1' + '0' * 203
|
||||
efuse_file.write(bytearray.fromhex(esp32_rev3_efuses))
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@@ -988,14 +1045,23 @@ def test_examples_efuse_with_virt_sb_v2_and_fe_qemu(dut: QemuDut) -> None:
|
||||
@pytest.mark.parametrize('config', ['virt_sb_v2_and_fe'], indirect=True)
|
||||
@idf_parametrize(
|
||||
'target',
|
||||
['esp32c3', 'esp32c2', 'esp32c5', 'esp32c61', 'esp32c6', 'esp32h2', 'esp32s2', 'esp32s3'],
|
||||
[
|
||||
'esp32c3',
|
||||
'esp32c2',
|
||||
'esp32c5',
|
||||
'esp32c61',
|
||||
'esp32c6',
|
||||
'esp32h2',
|
||||
'esp32s2',
|
||||
'esp32s3',
|
||||
],
|
||||
indirect=['target'],
|
||||
)
|
||||
def test_examples_efuse_with_virt_sb_v2_and_fe_esp32xx(dut: Dut) -> None:
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut.app.binary_path, 'bootloader', 'bootloader.bin')
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
logging.info('{}_bootloader_virt_sb_v2_and_fe_bin_size: {}KB'.format(dut.app.target, bin_size // 1024))
|
||||
logging.info(f'{dut.app.target}_bootloader_virt_sb_v2_and_fe_bin_size: {bin_size // 1024}KB')
|
||||
|
||||
dut.serial.erase_flash()
|
||||
|
||||
@@ -1012,13 +1078,13 @@ def test_examples_efuse_with_virt_sb_v2_and_fe_esp32xx(dut: Dut) -> None:
|
||||
signed_scheme = 'ECDSA'
|
||||
else:
|
||||
signed_scheme = 'RSA-PSS'
|
||||
dut.expect('secure_boot_v2: Verifying with %s...' % signed_scheme)
|
||||
dut.expect(f'secure_boot_v2: Verifying with {signed_scheme}...')
|
||||
dut.expect('secure_boot_v2: Signature verified successfully!')
|
||||
|
||||
dut.expect('secure_boot_v2: enabling secure boot v2...')
|
||||
dut.expect('Verifying image signature...')
|
||||
dut.expect('secure_boot_v2: Secure boot V2 is not enabled yet and eFuse digest keys are not set')
|
||||
dut.expect('secure_boot_v2: Verifying with %s...' % signed_scheme)
|
||||
dut.expect(f'secure_boot_v2: Verifying with {signed_scheme}...')
|
||||
dut.expect('secure_boot_v2: Signature verified successfully!')
|
||||
dut.expect('secure_boot_v2: Secure boot digests absent, generating..')
|
||||
dut.expect('secure_boot_v2: Digests successfully calculated, 1 valid signatures')
|
||||
@@ -1057,20 +1123,20 @@ def test_examples_efuse_with_virt_sb_v2_and_fe_esp32xx(dut: Dut) -> None:
|
||||
dut.expect('boot: Secure boot permanently enabled')
|
||||
|
||||
dut.expect('Verifying image signature...')
|
||||
dut.expect('secure_boot_v2: Verifying with %s...' % signed_scheme)
|
||||
dut.expect(f'secure_boot_v2: Verifying with {signed_scheme}...')
|
||||
dut.expect('secure_boot_v2: Signature verified successfully!')
|
||||
dut.expect('flash_encrypt: bootloader encrypted successfully')
|
||||
dut.expect('flash_encrypt: partition table encrypted and loaded successfully')
|
||||
|
||||
dut.expect('Verifying image signature...')
|
||||
dut.expect('secure_boot_v2: Verifying with %s...' % signed_scheme)
|
||||
dut.expect(f'secure_boot_v2: Verifying with {signed_scheme}...')
|
||||
dut.expect('secure_boot_v2: Signature verified successfully!')
|
||||
dut.expect('flash_encrypt: Flash encryption completed', timeout=90)
|
||||
dut.expect('Resetting with flash encryption enabled...')
|
||||
|
||||
dut.expect('Loading virtual efuse blocks from flash')
|
||||
dut.expect('Verifying image signature...')
|
||||
dut.expect('secure_boot_v2: Verifying with %s...' % signed_scheme)
|
||||
dut.expect(f'secure_boot_v2: Verifying with {signed_scheme}...')
|
||||
dut.expect('secure_boot_v2: Signature verified successfully!')
|
||||
dut.expect('secure_boot_v2: enabling secure boot v2...')
|
||||
dut.expect('secure_boot_v2: secure boot v2 is already enabled, continuing..')
|
||||
|
||||
@@ -6,6 +6,7 @@ from typing import Tuple
|
||||
import pytest
|
||||
from pytest_embedded_idf.dut import IdfDut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
# @pytest.mark.supported_targets
|
||||
# This test should support all targets, even between different target types
|
||||
# For now our CI only support multi dut with esp32
|
||||
@@ -82,13 +83,13 @@ def test_wifi_getting_started_esp32c2_xtal_26mhz(dut: Tuple[IdfDut, IdfDut]) ->
|
||||
|
||||
@pytest.mark.two_duts
|
||||
@pytest.mark.xtal_26mhz
|
||||
@pytest.mark.esp32c2eco4
|
||||
@pytest.mark.esp32c2_rev2
|
||||
@pytest.mark.parametrize(
|
||||
'count, config, target, baud, app_path',
|
||||
[
|
||||
(
|
||||
2,
|
||||
'esp32c2eco4_xtal26m',
|
||||
'esp32c2_rev2_xtal26m',
|
||||
'esp32c2',
|
||||
'74880',
|
||||
f'{os.path.join(os.path.dirname(__file__), "softAP")}|{os.path.join(os.path.dirname(__file__), "station")}',
|
||||
@@ -96,7 +97,7 @@ def test_wifi_getting_started_esp32c2_xtal_26mhz(dut: Tuple[IdfDut, IdfDut]) ->
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_wifi_getting_started_esp32c2eco4_xtal_26mhz(dut: Tuple[IdfDut, IdfDut]) -> None:
|
||||
def test_wifi_getting_started_esp32c2_rev2_xtal_26mhz(dut: Tuple[IdfDut, IdfDut]) -> None:
|
||||
softap = dut[0]
|
||||
station = dut[1]
|
||||
|
||||
@@ -113,20 +114,20 @@ def test_wifi_getting_started_esp32c2eco4_xtal_26mhz(dut: Tuple[IdfDut, IdfDut])
|
||||
|
||||
|
||||
@pytest.mark.two_duts
|
||||
@pytest.mark.esp32c3eco7
|
||||
@pytest.mark.esp32c3_rev1
|
||||
@pytest.mark.parametrize(
|
||||
'count, config, target, app_path',
|
||||
[
|
||||
(
|
||||
2,
|
||||
'esp32c3eco7',
|
||||
'esp32c3_rev1',
|
||||
'esp32c3',
|
||||
f'{os.path.join(os.path.dirname(__file__), "softAP")}|{os.path.join(os.path.dirname(__file__), "station")}',
|
||||
),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_wifi_getting_started_esp32c3eco7(dut: Tuple[IdfDut, IdfDut]) -> None:
|
||||
def test_wifi_getting_started_esp32c3_rev1(dut: Tuple[IdfDut, IdfDut]) -> None:
|
||||
softap = dut[0]
|
||||
station = dut[1]
|
||||
|
||||
|
||||
@@ -93,24 +93,24 @@ def test_wifi_power_save_esp32c2_26mhz(dut: Dut) -> None:
|
||||
|
||||
@pytest.mark.wifi_ap
|
||||
@pytest.mark.xtal_26mhz
|
||||
@pytest.mark.esp32c2eco4
|
||||
@pytest.mark.esp32c2_rev2
|
||||
@pytest.mark.parametrize(
|
||||
'config, baud, target',
|
||||
[
|
||||
('c2eco4_xtal26m', '74880', 'esp32c2'),
|
||||
('esp32c2_rev2_xtal26m', '74880', 'esp32c2'),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_wifi_power_save_esp32c2eco4_26mhz(dut: Dut) -> None:
|
||||
def test_wifi_power_save_esp32c2_rev2_26mhz(dut: Dut) -> None:
|
||||
_run_test(dut)
|
||||
|
||||
|
||||
@pytest.mark.wifi_ap
|
||||
@pytest.mark.esp32c3eco7
|
||||
@pytest.mark.esp32c3_rev1
|
||||
@pytest.mark.parametrize(
|
||||
'config, target',
|
||||
[('c3eco7', 'esp32c3')],
|
||||
[('esp32c3_rev1', 'esp32c3')],
|
||||
indirect=True,
|
||||
)
|
||||
def test_wifi_power_save_esp32c3eco7(dut: Dut) -> None:
|
||||
def test_wifi_power_save_esp32c3_rev1(dut: Dut) -> None:
|
||||
_run_test(dut)
|
||||
|
||||
97
pytest.ini
97
pytest.ini
@@ -7,7 +7,6 @@ python_files = pytest_*.py
|
||||
addopts =
|
||||
-s -vv
|
||||
--embedded-services esp,idf
|
||||
-p no:idf-ci
|
||||
--tb short
|
||||
--strict-markers
|
||||
--skip-check-coredump y
|
||||
@@ -15,12 +14,14 @@ addopts =
|
||||
--check-duplicates y
|
||||
--ignore-glob */managed_components/*
|
||||
--ignore pytest-embedded
|
||||
--unity-test-report-mode merge
|
||||
--ignore-no-tests-collected-error
|
||||
|
||||
# ignore DeprecationWarning
|
||||
filterwarnings =
|
||||
ignore::DeprecationWarning:matplotlib.*:
|
||||
ignore::DeprecationWarning:google.protobuf.*:
|
||||
ignore::_pytest.warning_types.PytestExperimentalApiWarning
|
||||
ignore::FutureWarning
|
||||
|
||||
# log related
|
||||
log_cli = True
|
||||
@@ -34,3 +35,95 @@ junit_family = xunit1
|
||||
## log all to `system-out` when case fail
|
||||
junit_logging = stdout
|
||||
junit_log_passing_tests = False
|
||||
|
||||
markers =
|
||||
temp_skip_ci: mark test to be skipped in CI
|
||||
temp_skip: mark test to be skipped in CI and locally
|
||||
skip_app_downloader: mark test required apps built locally, not downloaded from CI
|
||||
require_elf: mark test to be skipped if no elf file is found
|
||||
|
||||
env_markers =
|
||||
qemu: build and test using qemu, not real target
|
||||
macos: tests should be run on macos hosts
|
||||
generic: tests should be run on generic runners
|
||||
flash_suspend: support flash suspend feature
|
||||
eth_ip101: connected via IP101 ethernet transceiver
|
||||
eth_lan8720: connected via LAN8720 ethernet transceiver
|
||||
eth_rtl8201: connected via RTL8201 ethernet transceiver
|
||||
eth_ksz8041: connected via KSZ8041 ethernet transceiver
|
||||
eth_dp83848: connected via DP83848 ethernet transceiver
|
||||
eth_w5500: SPI Ethernet module with two W5500
|
||||
eth_ksz8851snl: SPI Ethernet module with two KSZ8851SNL
|
||||
eth_dm9051: SPI Ethernet module with two DM9051
|
||||
quad_psram: runners with quad psram
|
||||
octal_psram: runners with octal psram
|
||||
usb_host_flash_disk: usb host runners with USB flash disk attached
|
||||
usb_device: usb device runners
|
||||
ethernet_ota: ethernet OTA runners
|
||||
flash_encryption: Flash Encryption runners
|
||||
flash_encryption_f4r8: Flash Encryption runners with 4-line flash and 8-line psram
|
||||
flash_encryption_f8r8: Flash Encryption runners with 8-line flash and 8-line psram
|
||||
flash_encryption_ota: Flash Encryption runners with ethernet OTA support with 4mb flash size
|
||||
flash_multi: Multiple flash chips tests
|
||||
psram: Chip has 4-line psram
|
||||
ir_transceiver: runners with a pair of IR transmitter and receiver
|
||||
flash_encryption_wifi_high_traffic: Flash Encryption runners with wifi high traffic support
|
||||
ethernet: ethernet runner
|
||||
ethernet_flash_8m: ethernet runner with 8mb flash
|
||||
ethernet_router: both the runner and dut connect to the same router through ethernet NIC
|
||||
ethernet_vlan: ethernet runner GARM-32-SH-1-R16S5N3
|
||||
wifi_ap: a wifi AP in the environment
|
||||
wifi_router: both the runner and dut connect to the same wifi router
|
||||
wifi_high_traffic: wifi high traffic runners
|
||||
wifi_wlan: wifi runner with a wireless NIC
|
||||
wifi_iperf: the AP and ESP dut were placed in a shielded box - for iperf test
|
||||
Example_ShieldBox: multiple shielded APs connected to shielded ESP DUT via RF cable with programmable attenuator
|
||||
xtal_26mhz: runner with 26MHz xtal on board
|
||||
xtal_40mhz: runner with 40MHz xtal on board
|
||||
external_flash: external flash memory connected via VSPI (FSPI)
|
||||
sdcard_sdmode: sdcard running in SD mode, to be removed after test migration
|
||||
sdcard_spimode: sdcard running in SPI mode
|
||||
emmc: eMMC card
|
||||
sdcard: sdcard runner
|
||||
MSPI_F8R8: runner with Octal Flash and Octal PSRAM
|
||||
MSPI_F4R8: runner with Quad Flash and Octal PSRAM
|
||||
MSPI_F4R4: runner with Quad Flash and Quad PSRAM
|
||||
jtag: runner where the chip is accessible through JTAG as well
|
||||
usb_serial_jtag: runner where the chip is accessible through builtin JTAG as well
|
||||
xtal32k: Runner with external 32k crystal connected
|
||||
no32kXtal: Runner with no external 32k crystal connected
|
||||
psramv0: Runner with PSRAM version 0
|
||||
ecdsa_efuse: Runner with test ECDSA private keys programmed in efuse
|
||||
ccs811: Runner with CCS811 connected
|
||||
nvs_encr_hmac: Runner with test HMAC key programmed in efuse
|
||||
i2c_oled: Runner with ssd1306 I2C oled connected
|
||||
httpbin: runner for tests that need to access the httpbin service
|
||||
flash_4mb: C2 runners with 4 MB flash
|
||||
jtag_re_enable: Runner to re-enable jtag which is softly disabled by burning bit SOFT_DIS_JTAG on eFuse
|
||||
es8311: Development board that carries es8311 codec
|
||||
multi_dut_modbus_rs485: a pair of runners connected by RS485 bus
|
||||
ieee802154: ieee802154 related tests should run on ieee802154 runners.
|
||||
openthread_br: openthread BR basic and multicast cases.
|
||||
openthread_br_connect: openthread BR attach and connectivity cases.
|
||||
openthread_br_service: openthread BR service discovery cases.
|
||||
openthread_br_nat64: openthread BR NAT64 cases without internet.
|
||||
openthread_bbr: openthread BR cases with internet access.
|
||||
openthread_sleep: tests should be used for openthread sleepy device.
|
||||
zigbee_multi_dut: zigbee runner which have multiple duts.
|
||||
two_duts: the runner has two duts with antenna available.
|
||||
generic_multi_device: generic multiple devices whose corresponding gpio pins are connected to each other.
|
||||
twai_network: multiple runners form a TWAI network.
|
||||
sdio_master_slave: Test sdio multi board, esp32+esp32
|
||||
sdio_multidev_32_c6: Test sdio multi board, esp32+esp32c6
|
||||
usj_device: Test usb_serial_jtag and usb_serial_jtag is used as serial only (not console)
|
||||
twai_adapter: runner with multiple twai_std
|
||||
lp_i2s: lp_i2s runner tested with hp_i2s
|
||||
ram_app: ram_app runners
|
||||
|
||||
# rev markers
|
||||
rev_default: Runner with default revision connected
|
||||
esp32_rev3: Runner with ESP32 rev 3.x connected
|
||||
esp32c2_rev2: Runner with ESP32-C2 rev 2.x connected
|
||||
esp32c3_rev1: Runner with ESP32-C3 rev 1.x connected
|
||||
esp32c5_rev1: Runner with ESP32-C5 rev 1.x connected
|
||||
esp32p4_rev1: Runner with ESP32-P4 rev 1.x connected
|
||||
|
||||
48
ruff.toml
Normal file
48
ruff.toml
Normal file
@@ -0,0 +1,48 @@
|
||||
line-length = 120
|
||||
target-version = "py38"
|
||||
|
||||
[format]
|
||||
quote-style = "single"
|
||||
docstring-code-format = true
|
||||
|
||||
[lint]
|
||||
select = [
|
||||
"A", # flake8-builtins
|
||||
"E", # pycodestyle
|
||||
"F", # pyflakes
|
||||
"W", # pycodestyle
|
||||
"I", # reorder-imports
|
||||
"UP", # pyupgrade
|
||||
]
|
||||
ignore = [
|
||||
"E203", # whitespace before ':'
|
||||
"E221", # multiple spaces before operator
|
||||
"E225", # missing whitespace around operator
|
||||
"E231", # missing whitespace after ',', ';', or ':'
|
||||
"E241", # multiple spaces after ','
|
||||
]
|
||||
|
||||
# Exclude a variety of commonly ignored directories.
|
||||
exclude = [
|
||||
".git",
|
||||
"__pycache__",
|
||||
# submodules
|
||||
"components/bootloader/subproject/components/micro-ecc/micro-ecc",
|
||||
"components/bt/host/nimble/nimble",
|
||||
"components/cmock/CMock",
|
||||
"components/JSON/cJSON",
|
||||
"components/mbedtls/mbedtls",
|
||||
"components/openthread/openthread",
|
||||
"components/unity/unity",
|
||||
"components/spiffs/spiffs",
|
||||
# autogenerated scripts
|
||||
"components/protocomm/python/constants_pb2.py",
|
||||
"components/protocomm/python/sec0_pb2.py",
|
||||
"components/protocomm/python/sec1_pb2.py",
|
||||
"components/protocomm/python/sec2_pb2.py",
|
||||
"components/protocomm/python/session_pb2.py",
|
||||
"components/esp_local_ctrl/python/esp_local_ctrl_pb2.py",
|
||||
]
|
||||
|
||||
[lint.isort]
|
||||
force-single-line = true
|
||||
@@ -1,24 +0,0 @@
|
||||
sonar.projectKey=esp-idf
|
||||
sonar.projectName=Espressif IoT Development Framework
|
||||
sonar.projectVersion=4.4
|
||||
|
||||
sonar.sources=.
|
||||
sonar.sourceEncoding=UTF-8
|
||||
|
||||
sonar.inclusions=\
|
||||
**/*.c,**/*.cpp,**/*.h,**/*.hpp,\
|
||||
**/*.py
|
||||
|
||||
####################
|
||||
# sonar-cxx-plugin #
|
||||
####################
|
||||
|
||||
sonar.cxx.includeDirectories=/usr/include
|
||||
sonar.cxx.file.suffixes=.cxx,.cpp,.cc,.c,.hxx,.hpp,.hh,.h
|
||||
|
||||
#######################
|
||||
# sonar-gitlab-plugin #
|
||||
#######################
|
||||
|
||||
sonar.gitlab.failure_notification_mode=exit-code
|
||||
sonar.gitlab.project_id=103
|
||||
@@ -11,6 +11,13 @@ set -euo pipefail
|
||||
|
||||
TEMPLATE_APP_PATH="esp-idf-template"
|
||||
|
||||
# Set default paths for template app builds
|
||||
BUILD_PATH="${IDF_PATH}/build_template_app"
|
||||
BUILD_DIR="${BUILD_PATH}/@t/@w"
|
||||
|
||||
LOG_PATH="${IDF_PATH}/log_template_app"
|
||||
BUILD_LOG_CMAKE="${LOG_PATH}/cmake_@t_@w.txt"
|
||||
|
||||
gen_configs() {
|
||||
# CONFIG_COMPILER_OPTIMIZATION_NONE with flag -O0
|
||||
echo "CONFIG_COMPILER_OPTIMIZATION_NONE=y" > ${TEMPLATE_APP_PATH}/sdkconfig.ci.O0
|
||||
@@ -54,30 +61,23 @@ build_stage2() {
|
||||
# Override EXTRA_CFLAGS and EXTRA_CXXFLAGS in the environment
|
||||
export EXTRA_CFLAGS=${PEDANTIC_CFLAGS/-Werror=unused-variable -Werror=unused-but-set-variable -Werror=unused-function/}
|
||||
export EXTRA_CXXFLAGS=${PEDANTIC_CXXFLAGS/-Werror=unused-variable -Werror=unused-but-set-variable -Werror=unused-function/}
|
||||
python -m idf_build_apps build -v \
|
||||
idf-build-apps build \
|
||||
-p ${TEMPLATE_APP_PATH} \
|
||||
-t all \
|
||||
${CONFIG_STR} \
|
||||
--work-dir ${BUILD_PATH}/cmake \
|
||||
--build-dir ${BUILD_DIR} \
|
||||
--build-log ${BUILD_LOG_CMAKE} \
|
||||
--size-file size.json \
|
||||
--keep-going \
|
||||
--collect-size-info size_info.txt \
|
||||
--default-build-targets esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c5 esp32c6 esp32h2 esp32p4
|
||||
}
|
||||
|
||||
build_stage1() {
|
||||
CONFIG_STR=$(get_config_str sdkconfig.ci2.*=)
|
||||
python -m idf_build_apps build -v \
|
||||
idf-build-apps build \
|
||||
-p ${TEMPLATE_APP_PATH} \
|
||||
-t all \
|
||||
${CONFIG_STR} \
|
||||
--work-dir ${BUILD_PATH}/cmake \
|
||||
--build-dir ${BUILD_DIR} \
|
||||
--build-log ${BUILD_LOG_CMAKE} \
|
||||
--size-file size.json \
|
||||
--collect-size-info size_info.txt \
|
||||
--default-build-targets esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c5 esp32c6 esp32h2 esp32p4
|
||||
}
|
||||
|
||||
|
||||
@@ -1,33 +1,27 @@
|
||||
#!/usr/bin/env python
|
||||
# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import inspect
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
import yaml
|
||||
from idf_ci_utils import get_all_manifest_files
|
||||
from idf_build_apps import App
|
||||
from idf_build_apps import find_apps
|
||||
from idf_build_apps.constants import SUPPORTED_TARGETS
|
||||
from idf_ci_utils import IDF_PATH
|
||||
|
||||
# Copy of the constant from idf_pytest.constants, to avoid installing
|
||||
# additional dependencies (pytest, pytest-embedded) in pre-commit hooks
|
||||
DEFAULT_CONFIG_RULES_STR = ['sdkconfig.ci=default', 'sdkconfig.ci.*=', '=default']
|
||||
|
||||
YES = u'\u2713'
|
||||
NO = u'\u2717'
|
||||
from idf_ci_utils import idf_relpath
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
# | Supported Target | ... |
|
||||
# | ---------------- | --- |
|
||||
SUPPORTED_TARGETS_TABLE_REGEX = re.compile(
|
||||
r'^\|\s*Supported Targets.+$\n^\|(?:\s*|-).+$\n?', re.MULTILINE
|
||||
)
|
||||
SUPPORTED_TARGETS_TABLE_REGEX = re.compile(r'^\|\s*Supported Targets.+$\n^\|(?:\s*|-).+$\n?', re.MULTILINE)
|
||||
|
||||
USUAL_TO_FORMAL = {
|
||||
'esp32': 'ESP32',
|
||||
@@ -43,350 +37,265 @@ USUAL_TO_FORMAL = {
|
||||
'linux': 'Linux',
|
||||
}
|
||||
|
||||
FORMAL_TO_USUAL = {
|
||||
'ESP32': 'esp32',
|
||||
'ESP32-S2': 'esp32s2',
|
||||
'ESP32-S3': 'esp32s3',
|
||||
'ESP32-C3': 'esp32c3',
|
||||
'ESP32-C2': 'esp32c2',
|
||||
'ESP32-C6': 'esp32c6',
|
||||
'ESP32-C5': 'esp32c5',
|
||||
'ESP32-H2': 'esp32h2',
|
||||
'ESP32-P4': 'esp32p4',
|
||||
'ESP32-C61': 'esp32c61',
|
||||
'Linux': 'linux',
|
||||
}
|
||||
FORMAL_TO_USUAL = {v: k for k, v in USUAL_TO_FORMAL.items()}
|
||||
|
||||
# Targets under bringup: suppress check-test-scripts warnings for these.
|
||||
# Reset to [] once bringup is complete.
|
||||
BYPASS_CHECK_TEST_TARGETS: list[str] = [
|
||||
'esp32c5',
|
||||
'esp32c61',
|
||||
]
|
||||
|
||||
|
||||
def doublequote(s: str) -> str:
|
||||
if s.startswith('"') and s.endswith('"'):
|
||||
return s
|
||||
def print_diff_table(
|
||||
list1: list[str],
|
||||
list2: list[str],
|
||||
title1: str,
|
||||
title2: str,
|
||||
excluded: list[str] | None = None,
|
||||
) -> None:
|
||||
left_set = set(list1)
|
||||
right_set = set(list2)
|
||||
|
||||
return f'"{s}"'
|
||||
if left_set == right_set:
|
||||
return
|
||||
|
||||
all_items = sorted(left_set.union(right_set))
|
||||
left_column = []
|
||||
right_column = []
|
||||
|
||||
for item in all_items:
|
||||
if item in left_set and item not in right_set:
|
||||
if excluded and item in excluded:
|
||||
left_column.append(f'[red]{item}[/red] (excluded)')
|
||||
else:
|
||||
left_column.append(f'[red]{item}[/red]')
|
||||
right_column.append('')
|
||||
elif item in right_set and item not in left_set:
|
||||
if excluded and item in excluded:
|
||||
right_column.append(f'[green]{item}[/green] (excluded)')
|
||||
else:
|
||||
right_column.append(f'[green]{item}[/green]')
|
||||
left_column.append('')
|
||||
else:
|
||||
left_column.append(item)
|
||||
right_column.append(item)
|
||||
|
||||
table = Table(show_header=True, header_style='bold magenta')
|
||||
table.add_column(title1.strip(), justify='left')
|
||||
table.add_column(title2.strip(), justify='left')
|
||||
|
||||
for l_col, r_col in zip(left_column, right_column):
|
||||
table.add_row(l_col, r_col)
|
||||
|
||||
console = Console()
|
||||
console.print(table)
|
||||
|
||||
|
||||
def get_readme_path(app_dir: str) -> str | None:
|
||||
readme_path = os.path.join(app_dir, 'README.md')
|
||||
|
||||
if not os.path.isfile(readme_path):
|
||||
readme_path = os.path.join(app_dir, '..', 'README.md')
|
||||
|
||||
if not os.path.isfile(readme_path):
|
||||
readme_path = None # type: ignore
|
||||
|
||||
return readme_path
|
||||
|
||||
|
||||
def generate_new_support_table(targets: list[str]) -> str:
|
||||
# extra space here
|
||||
table_headers = [f'{USUAL_TO_FORMAL[target]}' for target in targets]
|
||||
table_headers = ['Supported Targets'] + table_headers
|
||||
|
||||
res = '| ' + ' | '.join(table_headers) + ' |\n'
|
||||
res += '| ' + ' | '.join(['-' * len(item) for item in table_headers]) + ' |'
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def parse_existing_table(app_dir: str) -> tuple[str | None, list[str]]:
|
||||
readme_path = get_readme_path(app_dir)
|
||||
if not readme_path:
|
||||
return None, SUPPORTED_TARGETS
|
||||
|
||||
with open(readme_path, encoding='utf8') as _fr:
|
||||
readme_str = _fr.read()
|
||||
|
||||
support_string = SUPPORTED_TARGETS_TABLE_REGEX.findall(readme_str)
|
||||
if not support_string:
|
||||
return None, SUPPORTED_TARGETS
|
||||
|
||||
# old style
|
||||
parts = [part.strip() for part in support_string[0].split('\n', 1)[0].split('|') if part.strip()]
|
||||
return support_string[0].strip(), [FORMAL_TO_USUAL[part] for part in parts[1:] if part in FORMAL_TO_USUAL]
|
||||
|
||||
|
||||
def get_grouped_apps(
|
||||
paths: list[str],
|
||||
exclude_dirs: list[str] | None = None,
|
||||
) -> dict[str, list[App]]:
|
||||
apps = sorted(
|
||||
find_apps(
|
||||
paths,
|
||||
'all',
|
||||
exclude_list=exclude_dirs or [],
|
||||
)
|
||||
)
|
||||
|
||||
grouped_apps: dict[str, list[App]] = defaultdict(list)
|
||||
for app in apps:
|
||||
grouped_apps[app.app_dir].append(app)
|
||||
|
||||
return grouped_apps
|
||||
|
||||
|
||||
def check_readme(
|
||||
paths: List[str],
|
||||
exclude_dirs: Optional[List[str]] = None,
|
||||
extra_default_build_targets: Optional[List[str]] = None,
|
||||
paths: list[str],
|
||||
exclude_dirs: list[str] | None = None,
|
||||
) -> None:
|
||||
from idf_build_apps import App, find_apps
|
||||
from idf_build_apps.constants import SUPPORTED_TARGETS
|
||||
grouped_apps = get_grouped_apps(paths, exclude_dirs)
|
||||
exit_code = 0
|
||||
|
||||
def app_build_targets(_app: App) -> List[str]:
|
||||
return sorted(
|
||||
for app_dir, apps in grouped_apps.items():
|
||||
old_table, old_targets = parse_existing_table(app_dir)
|
||||
manifest_targets = sorted(
|
||||
{
|
||||
target
|
||||
for app in apps
|
||||
for target in (
|
||||
App.MANIFEST.enable_build_targets(_app.app_dir)
|
||||
+ App.MANIFEST.enable_build_targets(_app.app_dir, config_name=_app.config_name)
|
||||
App.MANIFEST.enable_build_targets(app_dir)
|
||||
+ App.MANIFEST.enable_build_targets(app_dir, config_name=app.config_name)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
def get_readme_path(_app: App) -> Optional[str]:
|
||||
_readme_path = os.path.join(_app.app_dir, 'README.md')
|
||||
|
||||
if not os.path.isfile(_readme_path):
|
||||
_readme_path = os.path.join(_app.app_dir, '..', 'README.md')
|
||||
|
||||
if not os.path.isfile(_readme_path):
|
||||
_readme_path = None # type: ignore
|
||||
|
||||
return _readme_path
|
||||
|
||||
def _generate_new_support_table_str(_app: App) -> str:
|
||||
manifest_targets = app_build_targets(_app)
|
||||
# extra space here
|
||||
table_headers = [
|
||||
f'{USUAL_TO_FORMAL[target]}' for target in manifest_targets
|
||||
]
|
||||
table_headers = ['Supported Targets'] + table_headers
|
||||
|
||||
res = '| ' + ' | '.join(table_headers) + ' |\n'
|
||||
res += '| ' + ' | '.join(['-' * len(item) for item in table_headers]) + ' |'
|
||||
|
||||
return res
|
||||
|
||||
def _parse_existing_support_table_str(_app: App) -> Tuple[Optional[str], List[str]]:
|
||||
_readme_path = get_readme_path(_app)
|
||||
if not _readme_path:
|
||||
return None, SUPPORTED_TARGETS
|
||||
|
||||
with open(_readme_path, encoding='utf8') as _fr:
|
||||
_readme_str = _fr.read()
|
||||
|
||||
support_string = SUPPORTED_TARGETS_TABLE_REGEX.findall(_readme_str)
|
||||
if not support_string:
|
||||
return None, SUPPORTED_TARGETS
|
||||
|
||||
# old style
|
||||
parts = [
|
||||
part.strip()
|
||||
for part in support_string[0].split('\n', 1)[0].split('|')
|
||||
if part.strip()
|
||||
]
|
||||
return support_string[0].strip(), [FORMAL_TO_USUAL[part] for part in parts[1:] if part in FORMAL_TO_USUAL]
|
||||
|
||||
def check_enable_build(_app: App, _old_supported_targets: List[str]) -> bool:
|
||||
manifest_targets = app_build_targets(_app)
|
||||
|
||||
if manifest_targets == sorted(_old_supported_targets):
|
||||
return True
|
||||
|
||||
_readme_path = get_readme_path(_app)
|
||||
if_clause = f'IDF_TARGET in [{", ".join([doublequote(target) for target in sorted(_old_supported_targets)])}]'
|
||||
|
||||
print(
|
||||
inspect.cleandoc(
|
||||
f'''
|
||||
{_app.app_dir}:
|
||||
- enable build targets according to the manifest file: {manifest_targets}
|
||||
- enable build targets according to the old Supported Targets table under readme "{_readme_path}": {_old_supported_targets}
|
||||
|
||||
If you want to disable some targets, please use the following snippet:
|
||||
|
||||
# Please combine this with the original one
|
||||
#
|
||||
# Notes:
|
||||
# - please keep in mind to avoid duplicated folders as yaml keys
|
||||
# - please use parentheses to group conditions, the "or" and "and" operators could only accept two operands
|
||||
{_app.app_dir}:
|
||||
enable:
|
||||
- if: {if_clause}
|
||||
temporary: true
|
||||
reason: <why only enable build jobs for these targets>
|
||||
|
||||
'''
|
||||
)
|
||||
)
|
||||
|
||||
return False
|
||||
|
||||
apps = sorted(
|
||||
find_apps(
|
||||
paths,
|
||||
'all',
|
||||
recursive=True,
|
||||
exclude_list=exclude_dirs or [],
|
||||
manifest_files=get_all_manifest_files(),
|
||||
default_build_targets=SUPPORTED_TARGETS + extra_default_build_targets,
|
||||
config_rules_str=DEFAULT_CONFIG_RULES_STR,
|
||||
)
|
||||
)
|
||||
exit_code = 0
|
||||
|
||||
checked_app_dirs = set()
|
||||
for app in apps:
|
||||
if app.app_dir not in checked_app_dirs:
|
||||
checked_app_dirs.add(app.app_dir)
|
||||
else:
|
||||
continue
|
||||
|
||||
replace_str, old_supported_targets = _parse_existing_support_table_str(app)
|
||||
success = check_enable_build(app, old_supported_targets)
|
||||
if not success:
|
||||
print(f'check_enable_build failed for app: {app}')
|
||||
print('-' * 80)
|
||||
if sorted(manifest_targets) != sorted(old_targets):
|
||||
print(f'Build target MISMATCH!!!: {app_dir}')
|
||||
print_diff_table(manifest_targets, old_targets, 'Manifest', 'README')
|
||||
print(f'Corresponding manifest file: {App.MANIFEST.most_suitable_rule(app_dir).by_manifest_file}')
|
||||
exit_code = 1
|
||||
|
||||
readme_path = get_readme_path(app)
|
||||
readme_path = get_readme_path(app_dir)
|
||||
new_table = generate_new_support_table(manifest_targets)
|
||||
# no readme, create a new file
|
||||
if not readme_path:
|
||||
with open(os.path.join(app.app_dir, 'README.md'), 'w') as fw:
|
||||
fw.write(_generate_new_support_table_str(app) + '\n')
|
||||
print(f'Added new README file: {os.path.join(app.app_dir, "README.md")}')
|
||||
print('-' * 80)
|
||||
with open(os.path.join(app_dir, 'README.md'), 'w') as fw:
|
||||
fw.write(new_table + '\n')
|
||||
print(f'Auto-fixing: Added new README: {os.path.join(app_dir, "README.md")}')
|
||||
exit_code = 1
|
||||
# has old table, but different string
|
||||
elif replace_str and replace_str != _generate_new_support_table_str(app):
|
||||
elif old_table and old_table != new_table:
|
||||
with open(readme_path) as fr:
|
||||
readme_str = fr.read()
|
||||
|
||||
with open(readme_path, 'w') as fw:
|
||||
fw.write(
|
||||
readme_str.replace(
|
||||
replace_str, _generate_new_support_table_str(app)
|
||||
)
|
||||
)
|
||||
print(f'Modified README file: {readme_path}')
|
||||
print('-' * 80)
|
||||
fw.write(readme_str.replace(old_table, new_table))
|
||||
print(f'Auto-fixing: Updated README: {readme_path}')
|
||||
exit_code = 1
|
||||
# does not have old table
|
||||
elif not replace_str:
|
||||
elif not old_table:
|
||||
with open(readme_path) as fr:
|
||||
readme_str = fr.read()
|
||||
|
||||
with open(readme_path, 'w') as fw:
|
||||
fw.write(
|
||||
_generate_new_support_table_str(app) + '\n\n' + readme_str
|
||||
) # extra new line
|
||||
|
||||
print(f'Modified README file: {readme_path}')
|
||||
print('-' * 80)
|
||||
fw.write(new_table + '\n\n' + readme_str) # extra new line
|
||||
print(f'Auto-fixing: Modified README: {readme_path}')
|
||||
exit_code = 1
|
||||
|
||||
if exit_code != 0:
|
||||
print('') # extra new line for readability
|
||||
print('Related documentation:')
|
||||
print(
|
||||
'\t- https://docs.espressif.com/projects/idf-build-apps/en/latest/references/manifest.html#enable-disable-rules'
|
||||
)
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
def get_grouped_cases(paths: list[str]) -> dict[str, dict[str, set[str]]]:
|
||||
"""
|
||||
return something like this:
|
||||
{
|
||||
app_dir: {
|
||||
'script_paths': {'path/to/script1', 'path/to/script2'},
|
||||
'targets': {'esp32', 'esp32s2', 'esp32s3', 'esp32c3', 'esp32c2', 'linux'},
|
||||
}
|
||||
}
|
||||
"""
|
||||
from idf_ci import get_pytest_cases
|
||||
|
||||
pytest_cases = get_pytest_cases(
|
||||
paths=paths,
|
||||
marker_expr=None, # don't filter host_test
|
||||
)
|
||||
|
||||
grouped_cases = {}
|
||||
for case in pytest_cases:
|
||||
for pytest_app in case.apps:
|
||||
app_dir = idf_relpath(pytest_app.path)
|
||||
if app_dir not in grouped_cases:
|
||||
grouped_cases[app_dir] = {
|
||||
'script_paths': {case.path},
|
||||
'targets': {pytest_app.target},
|
||||
}
|
||||
else:
|
||||
grouped_cases[app_dir]['script_paths'].add(case.path)
|
||||
grouped_cases[app_dir]['targets'].add(pytest_app.target)
|
||||
|
||||
return grouped_cases
|
||||
|
||||
|
||||
def check_test_scripts(
|
||||
paths: List[str],
|
||||
exclude_dirs: Optional[List[str]] = None,
|
||||
bypass_check_test_targets: Optional[List[str]] = None,
|
||||
extra_default_build_targets: Optional[List[str]] = None,
|
||||
paths: list[str],
|
||||
exclude_dirs: list[str] | None = None,
|
||||
*,
|
||||
bypass_targets: list[str] | None = None,
|
||||
) -> None:
|
||||
from idf_build_apps import App, find_apps
|
||||
from idf_build_apps.constants import SUPPORTED_TARGETS
|
||||
from idf_pytest.script import get_pytest_cases
|
||||
|
||||
# takes long time, run only in CI
|
||||
# dict:
|
||||
# {
|
||||
# app_dir: {
|
||||
# 'script_path': 'path/to/script',
|
||||
# 'targets': ['esp32', 'esp32s2', 'esp32s3', 'esp32c3', 'esp32c2', 'linux'],
|
||||
# }
|
||||
# }
|
||||
def check_enable_test(
|
||||
_app: App,
|
||||
_pytest_app_dir_targets_dict: Dict[str, Dict[str, str]],
|
||||
) -> bool:
|
||||
if _app.app_dir in _pytest_app_dir_targets_dict:
|
||||
test_script_path = _pytest_app_dir_targets_dict[_app.app_dir]['script_path']
|
||||
actual_verified_targets = sorted(
|
||||
set(_pytest_app_dir_targets_dict[_app.app_dir]['targets'])
|
||||
)
|
||||
else:
|
||||
return True # no test case
|
||||
grouped_apps = get_grouped_apps(paths, exclude_dirs)
|
||||
grouped_cases = get_grouped_cases(paths)
|
||||
exit_code = 0
|
||||
|
||||
manifest_targets = sorted({
|
||||
target for target in (
|
||||
App.MANIFEST.enable_test_targets(_app.app_dir)
|
||||
+ App.MANIFEST.enable_test_targets(_app.app_dir, config_name=_app.config_name)
|
||||
)
|
||||
})
|
||||
for app_dir, apps in grouped_apps.items():
|
||||
if app_dir not in grouped_cases:
|
||||
continue
|
||||
|
||||
actual_extra_tested_targets = set(actual_verified_targets) - set(manifest_targets)
|
||||
if actual_extra_tested_targets - set(bypass_check_test_targets or []):
|
||||
print(
|
||||
inspect.cleandoc(
|
||||
f'''
|
||||
{_app.app_dir}:
|
||||
- enable test targets according to the manifest file: {manifest_targets}
|
||||
- enable test targets according to the test scripts: {actual_verified_targets}
|
||||
|
||||
test scripts enabled targets should be a subset of the manifest file declared ones.
|
||||
Please check the test script: {test_script_path}.
|
||||
|
||||
'''
|
||||
# manifest defined ones
|
||||
manifest_targets = sorted(
|
||||
{
|
||||
target
|
||||
for app in apps
|
||||
for target in (
|
||||
App.MANIFEST.enable_test_targets(app_dir)
|
||||
+ App.MANIFEST.enable_test_targets(app_dir, config_name=app.config_name)
|
||||
)
|
||||
)
|
||||
return False
|
||||
}
|
||||
)
|
||||
actual_targets = sorted(grouped_cases[app_dir]['targets'])
|
||||
|
||||
if manifest_targets == actual_verified_targets:
|
||||
return True
|
||||
if not ((set(manifest_targets) ^ set(actual_verified_targets)) - set(bypass_check_test_targets or [])):
|
||||
print(f'WARNING: bypass test script check on {_app.app_dir} for targets {bypass_check_test_targets} ')
|
||||
return True
|
||||
if manifest_targets == actual_targets:
|
||||
continue
|
||||
if not ((set(manifest_targets) ^ set(actual_targets)) - set(bypass_targets or [])):
|
||||
continue
|
||||
|
||||
if_clause = f'IDF_TARGET in [{", ".join([doublequote(target) for target in sorted(set(manifest_targets) - set(actual_verified_targets))])}]'
|
||||
print(f'Test target MISMATCH!!!: {app_dir}')
|
||||
print_diff_table(manifest_targets, actual_targets, 'Manifest', 'Test Scripts', bypass_targets)
|
||||
print(f'Corresponding manifest file: {App.MANIFEST.most_suitable_rule(app_dir).by_manifest_file}')
|
||||
print('Corresponding test scripts:')
|
||||
for script_path in grouped_cases[app_dir]['script_paths']:
|
||||
print(' - ' + script_path)
|
||||
exit_code = 1
|
||||
|
||||
if exit_code != 0:
|
||||
print('') # extra new line for readability
|
||||
print('Related documentation:')
|
||||
print(
|
||||
inspect.cleandoc(
|
||||
f'''
|
||||
{_app.app_dir}:
|
||||
- enable test targets according to the manifest file: {manifest_targets}
|
||||
- enable test targets according to the test scripts: {actual_verified_targets}
|
||||
|
||||
the test scripts enabled test targets should be the same with the manifest file enabled ones. Please check
|
||||
the test script manually: {test_script_path}.
|
||||
|
||||
If you want to enable test targets in the pytest test scripts, please add `@pytest.mark.MISSING_TARGET`
|
||||
marker above the test case function.
|
||||
|
||||
If you want to disable the test targets in the manifest file, please modify your manifest file with
|
||||
the following code snippet:
|
||||
|
||||
# Please combine this with the original one
|
||||
#
|
||||
# Notes:
|
||||
# - please keep in mind to avoid duplicated folders as yaml keys
|
||||
# - please use parentheses to group conditions, the "or" and "and" operators could only accept two operands
|
||||
{_app.app_dir}:
|
||||
disable_test:
|
||||
- if: {if_clause}
|
||||
temporary: true
|
||||
reason: <why you disable this test>
|
||||
|
||||
'''
|
||||
)
|
||||
'\t- https://docs.espressif.com/projects/idf-build-apps/en/latest/references/manifest.html#enable-disable-rules'
|
||||
)
|
||||
return False
|
||||
|
||||
apps = sorted(
|
||||
find_apps(
|
||||
paths,
|
||||
'all',
|
||||
recursive=True,
|
||||
exclude_list=exclude_dirs or [],
|
||||
manifest_files=get_all_manifest_files(),
|
||||
default_build_targets=SUPPORTED_TARGETS + extra_default_build_targets,
|
||||
config_rules_str=DEFAULT_CONFIG_RULES_STR,
|
||||
print(
|
||||
'\t- https://docs.espressif.com/projects/esp-idf/en/latest/esp32/contribute/esp-idf-tests-with-pytest.html'
|
||||
)
|
||||
)
|
||||
exit_code = 0
|
||||
|
||||
pytest_cases = get_pytest_cases(paths)
|
||||
|
||||
pytest_app_dir_targets_dict = {}
|
||||
for case in pytest_cases:
|
||||
for pytest_app in case.apps:
|
||||
app_dir = os.path.relpath(pytest_app.path, IDF_PATH)
|
||||
if app_dir not in pytest_app_dir_targets_dict:
|
||||
pytest_app_dir_targets_dict[app_dir] = {
|
||||
'script_path': case.path,
|
||||
'targets': [pytest_app.target],
|
||||
}
|
||||
else:
|
||||
pytest_app_dir_targets_dict[app_dir]['targets'].append(
|
||||
pytest_app.target
|
||||
)
|
||||
|
||||
checked_app_dirs = set()
|
||||
for app in apps:
|
||||
if app.app_dir not in checked_app_dirs:
|
||||
checked_app_dirs.add(app.app_dir)
|
||||
else:
|
||||
continue
|
||||
|
||||
success = check_enable_test(
|
||||
app, pytest_app_dir_targets_dict
|
||||
)
|
||||
if not success:
|
||||
print(f'check_enable_test failed for app: {app}')
|
||||
print('-' * 80)
|
||||
exit_code = 1
|
||||
continue
|
||||
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
def check_exist() -> None:
|
||||
exit_code = 0
|
||||
|
||||
config_files = get_all_manifest_files()
|
||||
for file in config_files:
|
||||
if 'managed_components' in Path(file).parts:
|
||||
continue
|
||||
|
||||
with open(file) as fr:
|
||||
configs = yaml.safe_load(fr)
|
||||
for path in configs.keys():
|
||||
if path.startswith('.'):
|
||||
continue
|
||||
if not os.path.isdir(path):
|
||||
print(f'Path \'{path}\' referred in \'{file}\' does not exist!')
|
||||
exit_code = 1
|
||||
|
||||
sys.exit(exit_code)
|
||||
|
||||
@@ -398,92 +307,57 @@ if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='ESP-IDF apps build/test checker')
|
||||
action = parser.add_subparsers(dest='action')
|
||||
|
||||
_check_readme = action.add_parser('check-readmes')
|
||||
_check_readme.add_argument('paths', nargs='+', help='check under paths')
|
||||
_check_readme.add_argument(
|
||||
'-c',
|
||||
'--config',
|
||||
default=os.path.join(IDF_PATH, '.gitlab', 'ci', 'default-build-test-rules.yml'),
|
||||
help='default build test rules config file',
|
||||
)
|
||||
|
||||
_check_test_scripts = action.add_parser('check-test-scripts')
|
||||
_check_test_scripts.add_argument('paths', nargs='+', help='check under paths')
|
||||
_check_test_scripts.add_argument(
|
||||
'-c',
|
||||
'--config',
|
||||
default=os.path.join(IDF_PATH, '.gitlab', 'ci', 'default-build-test-rules.yml'),
|
||||
help='default build test rules config file',
|
||||
)
|
||||
|
||||
_check_exist = action.add_parser('check-exist')
|
||||
readme_parser = action.add_parser('check-readmes')
|
||||
readme_parser.add_argument('paths', nargs='+', help='check under paths')
|
||||
|
||||
test_parser = action.add_parser('check-test-scripts')
|
||||
test_parser.add_argument('paths', nargs='+', help='check under paths')
|
||||
arg = parser.parse_args()
|
||||
|
||||
# Since this script is executed from the pre-commit hook environment, make sure IDF_PATH is set
|
||||
os.environ['IDF_PATH'] = os.path.realpath(
|
||||
os.path.join(os.path.dirname(__file__), '..', '..')
|
||||
check_dirs = set()
|
||||
|
||||
# check if *_caps.h files changed
|
||||
check_all = False
|
||||
soc_caps_header_files = list((Path(IDF_PATH) / 'components' / 'soc').glob('**/*_caps.h'))
|
||||
for p in arg.paths:
|
||||
if Path(p).resolve() in soc_caps_header_files:
|
||||
check_all = True
|
||||
break
|
||||
|
||||
if os.path.isfile(p):
|
||||
check_dirs.add(os.path.dirname(p))
|
||||
else:
|
||||
check_dirs.add(p)
|
||||
|
||||
if 'tools/idf_py_actions/constants.py' in arg.paths or 'tools/ci/check_build_test_rules.py' in arg.paths:
|
||||
check_all = True
|
||||
|
||||
if check_all:
|
||||
check_dirs = {IDF_PATH}
|
||||
|
||||
_exclude_dirs = [
|
||||
os.path.join(IDF_PATH, 'tools', 'unit-test-app'),
|
||||
os.path.join(IDF_PATH, 'tools', 'test_build_system'),
|
||||
os.path.join(IDF_PATH, 'tools', 'templates', 'sample_project'),
|
||||
os.path.join(IDF_PATH, 'tools', 'cmakev2', 'test'),
|
||||
]
|
||||
|
||||
os.environ.update(
|
||||
{
|
||||
'IDF_PATH': IDF_PATH,
|
||||
'INCLUDE_NIGHTLY_RUN': '1',
|
||||
'NIGHTLY_RUN': '1',
|
||||
}
|
||||
)
|
||||
|
||||
if arg.action == 'check-exist':
|
||||
check_exist()
|
||||
else:
|
||||
check_dirs = set()
|
||||
|
||||
# check if *_caps.h files changed
|
||||
check_all = False
|
||||
soc_caps_header_files = list(
|
||||
(Path(IDF_PATH) / 'components' / 'soc').glob('**/*_caps.h')
|
||||
if arg.action == 'check-readmes':
|
||||
check_readme(
|
||||
list(check_dirs),
|
||||
_exclude_dirs,
|
||||
)
|
||||
elif arg.action == 'check-test-scripts':
|
||||
check_test_scripts(
|
||||
list(check_dirs),
|
||||
_exclude_dirs,
|
||||
bypass_targets=BYPASS_CHECK_TEST_TARGETS,
|
||||
)
|
||||
for p in arg.paths:
|
||||
if Path(p).resolve() in soc_caps_header_files:
|
||||
check_all = True
|
||||
break
|
||||
|
||||
if os.path.isfile(p):
|
||||
check_dirs.add(os.path.dirname(p))
|
||||
else:
|
||||
check_dirs.add(p)
|
||||
|
||||
if 'tools/idf_py_actions/constants.py' in arg.paths or 'tools/ci/check_build_test_rules.py' in arg.paths:
|
||||
check_all = True
|
||||
|
||||
if check_all:
|
||||
check_dirs = {IDF_PATH}
|
||||
_exclude_dirs = [os.path.join(IDF_PATH, 'tools', 'unit-test-app'),
|
||||
os.path.join(IDF_PATH, 'tools', 'test_build_system', 'build_test_app'),
|
||||
os.path.join(IDF_PATH, 'tools', 'templates', 'sample_project')]
|
||||
else:
|
||||
_exclude_dirs = [os.path.join(IDF_PATH, 'tools', 'templates', 'sample_project')]
|
||||
|
||||
extra_default_build_targets_list: List[str] = []
|
||||
bypass_check_test_targets_list: List[str] = []
|
||||
if arg.config:
|
||||
with open(arg.config) as fr:
|
||||
configs = yaml.safe_load(fr)
|
||||
|
||||
if configs:
|
||||
extra_default_build_targets_list = (
|
||||
configs.get('extra_default_build_targets') or []
|
||||
)
|
||||
bypass_check_test_targets_list = (
|
||||
configs.get('bypass_check_test_targets') or []
|
||||
)
|
||||
|
||||
if arg.action == 'check-readmes':
|
||||
os.environ['INCLUDE_NIGHTLY_RUN'] = '1'
|
||||
os.environ['NIGHTLY_RUN'] = '1'
|
||||
check_readme(
|
||||
list(check_dirs),
|
||||
exclude_dirs=_exclude_dirs,
|
||||
extra_default_build_targets=extra_default_build_targets_list,
|
||||
)
|
||||
elif arg.action == 'check-test-scripts':
|
||||
os.environ['INCLUDE_NIGHTLY_RUN'] = '1'
|
||||
os.environ['NIGHTLY_RUN'] = '1'
|
||||
check_test_scripts(
|
||||
list(check_dirs),
|
||||
exclude_dirs=_exclude_dirs,
|
||||
bypass_check_test_targets=bypass_check_test_targets_list,
|
||||
extra_default_build_targets=extra_default_build_targets_list,
|
||||
)
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# internal use only for CI
|
||||
# get latest MR information by source branch
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import typing as t
|
||||
from pathlib import Path
|
||||
|
||||
from gitlab_api import Gitlab
|
||||
from idf_ci_utils import IDF_PATH
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from gitlab.v4.objects import ProjectCommit, ProjectMergeRequest
|
||||
|
||||
|
||||
def _get_mr_obj(source_branch: str) -> t.Optional['ProjectMergeRequest']:
|
||||
gl = Gitlab(os.getenv('CI_PROJECT_ID', 'espressif/esp-idf'))
|
||||
if not gl.project:
|
||||
return None
|
||||
|
||||
mrs = gl.project.mergerequests.list(state='opened', source_branch=source_branch)
|
||||
if mrs:
|
||||
return mrs[0] # one source branch can only have one opened MR at one moment
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_mr_iid(source_branch: str) -> str:
|
||||
mr = _get_mr_obj(source_branch)
|
||||
if not mr:
|
||||
return ''
|
||||
else:
|
||||
return str(mr.iid)
|
||||
|
||||
|
||||
def get_mr_changed_files(source_branch: str) -> t.List[str]:
|
||||
mr = _get_mr_obj(source_branch)
|
||||
if not mr:
|
||||
return []
|
||||
|
||||
git_output = subprocess.check_output(
|
||||
['git', 'diff', '--name-only', '--diff-filter=d', f'origin/{mr.target_branch}...origin/{source_branch}']
|
||||
).decode('utf8')
|
||||
|
||||
return [line.strip() for line in git_output.splitlines() if line.strip()]
|
||||
|
||||
|
||||
def get_mr_commits(source_branch: str) -> t.List['ProjectCommit']:
|
||||
mr = _get_mr_obj(source_branch)
|
||||
if not mr:
|
||||
return []
|
||||
|
||||
return list(mr.commits())
|
||||
|
||||
|
||||
_COMPONENT_NAME_DIR_RECORDS = {}
|
||||
|
||||
|
||||
def get_modified_component(filepath: str) -> t.Optional[str]:
|
||||
"""Return the component name if the file is in a component directory, otherwise None."""
|
||||
try:
|
||||
f_path = Path(filepath).resolve().relative_to(IDF_PATH)
|
||||
except ValueError: # not in IDF_PATH
|
||||
return None
|
||||
|
||||
# skip md files, etc.
|
||||
if f_path.suffix in ['.md', '.yml']:
|
||||
return None
|
||||
|
||||
# skip test_apps files
|
||||
if 'test_apps' in f_path.parts:
|
||||
return None
|
||||
|
||||
component_parent_dirs = [f_path.parts[0]]
|
||||
for part in f_path.parts[1:]:
|
||||
if component_parent_dirs[-1] == 'components' or component_parent_dirs[-1].endswith('common_components'):
|
||||
if part not in _COMPONENT_NAME_DIR_RECORDS:
|
||||
logging.debug('Found component "%s" in path "%s"' % (part, component_parent_dirs))
|
||||
_COMPONENT_NAME_DIR_RECORDS[part] = component_parent_dirs
|
||||
elif _COMPONENT_NAME_DIR_RECORDS.get(part) != component_parent_dirs:
|
||||
logging.debug(
|
||||
'WARNING!!! Found component "%s" in path "%s" and "%s"'
|
||||
% (part, component_parent_dirs, _COMPONENT_NAME_DIR_RECORDS.get(part))
|
||||
)
|
||||
|
||||
return part
|
||||
|
||||
component_parent_dirs.append(part)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_mr_components(
|
||||
source_branch: t.Optional[str] = None, modified_files: t.Optional[t.List[str]] = None
|
||||
) -> t.List[str]:
|
||||
components: t.Set[str] = set()
|
||||
if modified_files is None:
|
||||
if not source_branch:
|
||||
raise RuntimeError('--src-branch is required if --modified-files is not provided')
|
||||
|
||||
modified_files = get_mr_changed_files(source_branch)
|
||||
|
||||
for f in modified_files:
|
||||
modified_component = get_modified_component(f)
|
||||
if modified_component:
|
||||
components.add(modified_component)
|
||||
|
||||
return list(components)
|
||||
|
||||
|
||||
def get_target_in_tags(tags: str) -> str:
|
||||
from idf_pytest.constants import TARGET_MARKERS
|
||||
|
||||
for x in tags.split(','):
|
||||
if x in TARGET_MARKERS:
|
||||
return x
|
||||
|
||||
raise RuntimeError(f'No target marker found in {tags}')
|
||||
|
||||
|
||||
def _print_list(_list: t.List[str], separator: str = '\n') -> None:
|
||||
print(separator.join(_list))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='Get the latest merge request info by pipeline')
|
||||
actions = parser.add_subparsers(dest='action', help='info type', required=True)
|
||||
|
||||
common_args = argparse.ArgumentParser(add_help=False)
|
||||
common_args.add_argument('--src-branch', help='source branch')
|
||||
common_args.add_argument(
|
||||
'--modified-files',
|
||||
nargs='+',
|
||||
help='space-separated list specifies the modified files. will be detected by --src-branch if not provided',
|
||||
)
|
||||
|
||||
actions.add_parser('id', parents=[common_args])
|
||||
actions.add_parser('commits', parents=[common_args])
|
||||
actions.add_parser('components', parents=[common_args])
|
||||
target = actions.add_parser('target_in_tags')
|
||||
target.add_argument('tags', help='comma separated tags, e.g., esp32,generic')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.action == 'id':
|
||||
if not args.src_branch:
|
||||
raise RuntimeError('--src-branch is required')
|
||||
print(get_mr_iid(args.src_branch))
|
||||
elif args.action == 'commits':
|
||||
if not args.src_branch:
|
||||
raise RuntimeError('--src-branch is required')
|
||||
_print_list([commit.id for commit in get_mr_commits(args.src_branch)])
|
||||
elif args.action == 'components':
|
||||
_print_list(get_mr_components(args.src_branch, args.modified_files))
|
||||
elif args.action == 'target_in_tags':
|
||||
print(get_target_in_tags(args.tags))
|
||||
else:
|
||||
raise NotImplementedError('not possible to get here')
|
||||
@@ -1,217 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# CI script to deploy docs to a webserver. Not useful outside of CI environment
|
||||
#
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
import glob
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
from typing import Any, List, Tuple
|
||||
|
||||
import packaging.version
|
||||
|
||||
|
||||
def env(variable: str, default: str=None) -> str:
|
||||
""" Shortcut to return the expanded version of an environment variable """
|
||||
return os.path.expandvars(os.environ.get(variable, default) if default else os.environ[variable])
|
||||
|
||||
|
||||
# import sanitize_version from the docs directory, shared with here
|
||||
sys.path.append(os.path.join(env('IDF_PATH'), 'docs'))
|
||||
from sanitize_version import sanitize_version # noqa
|
||||
|
||||
|
||||
def main() -> None:
|
||||
# if you get KeyErrors on the following lines, it's probably because you're not running in Gitlab CI
|
||||
git_ver = env('GIT_VER') # output of git describe --always
|
||||
ci_ver = env('CI_COMMIT_REF_NAME', git_ver) # branch or tag we're building for (used for 'release' & URL)
|
||||
|
||||
version = sanitize_version(ci_ver)
|
||||
print('Git version: {}'.format(git_ver))
|
||||
print('CI Version: {}'.format(ci_ver))
|
||||
print('Deployment version: {}'.format(version))
|
||||
|
||||
if not version:
|
||||
raise RuntimeError('A version is needed to deploy')
|
||||
|
||||
build_dir = env('DOCS_BUILD_DIR') # top-level local build dir, where docs have already been built
|
||||
|
||||
if not build_dir:
|
||||
raise RuntimeError('Valid DOCS_BUILD_DIR is needed to deploy')
|
||||
|
||||
url_base = env('DOCS_DEPLOY_URL_BASE') # base for HTTP URLs, used to print the URL to the log after deploying
|
||||
|
||||
docs_server = env('DOCS_DEPLOY_SERVER') # ssh server to deploy to
|
||||
docs_user = env('DOCS_DEPLOY_SERVER_USER')
|
||||
docs_path = env('DOCS_DEPLOY_PATH') # filesystem path on DOCS_SERVER
|
||||
|
||||
if not docs_server:
|
||||
raise RuntimeError('Valid DOCS_DEPLOY_SERVER is needed to deploy')
|
||||
|
||||
if not docs_user:
|
||||
raise RuntimeError('Valid DOCS_DEPLOY_SERVER_USER is needed to deploy')
|
||||
|
||||
docs_server = '{}@{}'.format(docs_user, docs_server)
|
||||
|
||||
if not docs_path:
|
||||
raise RuntimeError('Valid DOCS_DEPLOY_PATH is needed to deploy')
|
||||
|
||||
print('DOCS_DEPLOY_SERVER {} DOCS_DEPLOY_PATH {}'.format(docs_server, docs_path))
|
||||
|
||||
tarball_path, version_urls = build_doc_tarball(version, git_ver, build_dir)
|
||||
|
||||
deploy(version, tarball_path, docs_path, docs_server)
|
||||
|
||||
print('Docs URLs:')
|
||||
doc_deploy_type = os.getenv('TYPE')
|
||||
for vurl in version_urls:
|
||||
language, _, target = vurl.split('/')
|
||||
tag = '{}_{}'.format(language, target)
|
||||
url = '{}/{}/index.html'.format(url_base, vurl) # (index.html needed for the preview server)
|
||||
url = re.sub(r'([^:])//', r'\1/', url) # get rid of any // that isn't in the https:// part
|
||||
print('[document {}][{}] {}'.format(doc_deploy_type, tag, url))
|
||||
|
||||
# note: it would be neater to use symlinks for stable, but because of the directory order
|
||||
# (language first) it's kind of a pain to do on a remote server, so we just repeat the
|
||||
# process but call the version 'stable' this time
|
||||
if is_stable_version(version):
|
||||
print('Deploying again as stable version...')
|
||||
tarball_path, version_urls = build_doc_tarball('stable', git_ver, build_dir)
|
||||
deploy('stable', tarball_path, docs_path, docs_server)
|
||||
|
||||
|
||||
def deploy(version: str, tarball_path: str, docs_path: str, docs_server: str) -> None:
|
||||
def run_ssh(commands: List) -> None:
|
||||
""" Log into docs_server and run a sequence of commands using ssh """
|
||||
print('Running ssh: {}'.format(commands))
|
||||
subprocess.run(['ssh', '-o', 'BatchMode=yes', docs_server, '-x', ' && '.join(commands)], check=True)
|
||||
|
||||
# copy the version tarball to the server
|
||||
run_ssh(['mkdir -p {}'.format(docs_path)])
|
||||
print('Running scp {} to {}'.format(tarball_path, '{}:{}'.format(docs_server, docs_path)))
|
||||
subprocess.run(['scp', '-B', tarball_path, '{}:{}'.format(docs_server, docs_path)], check=True)
|
||||
|
||||
tarball_name = os.path.basename(tarball_path)
|
||||
|
||||
run_ssh(['cd {}'.format(docs_path),
|
||||
'rm -rf ./*/{}'.format(version), # remove any pre-existing docs matching this version
|
||||
'tar -zxvf {}'.format(tarball_name), # untar the archive with the new docs
|
||||
'rm {}'.format(tarball_name)])
|
||||
|
||||
# Note: deleting and then extracting the archive is a bit awkward for updating stable/latest/etc
|
||||
# as the version will be invalid for a window of time. Better to do it atomically, but this is
|
||||
# another thing made much more complex by the directory structure putting language before version...
|
||||
|
||||
|
||||
def build_doc_tarball(version: str, git_ver: str, build_dir: str) -> Tuple[str, List]:
|
||||
""" Make a tar.gz archive of the docs, in the directory structure used to deploy as
|
||||
the given version """
|
||||
version_paths = []
|
||||
tarball_path = '{}/{}.tar.gz'.format(build_dir, version)
|
||||
|
||||
# find all the 'html/' directories under build_dir
|
||||
html_dirs = glob.glob('{}/**/html/'.format(build_dir), recursive=True)
|
||||
print('Found %d html directories' % len(html_dirs))
|
||||
|
||||
pdfs = glob.glob('{}/**/latex/build/*.pdf'.format(build_dir), recursive=True)
|
||||
print('Found %d PDFs in latex directories' % len(pdfs))
|
||||
|
||||
# add symlink for stable and latest and adds them to PDF blob
|
||||
symlinks = create_and_add_symlinks(version, git_ver, pdfs)
|
||||
|
||||
def not_sources_dir(ti: Any) -> Any:
|
||||
print(type(ti))
|
||||
""" Filter the _sources directories out of the tarballs """
|
||||
if ti.name.endswith('/_sources'):
|
||||
return None
|
||||
|
||||
ti.mode |= stat.S_IWGRP # make everything group-writeable
|
||||
return ti
|
||||
|
||||
try:
|
||||
os.remove(tarball_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
with tarfile.open(tarball_path, 'w:gz') as tarball:
|
||||
for html_dir in html_dirs:
|
||||
# html_dir has the form '<ignored>/<language>/<target>/html/'
|
||||
target_dirname = os.path.dirname(os.path.dirname(html_dir))
|
||||
target = os.path.basename(target_dirname)
|
||||
language = os.path.basename(os.path.dirname(target_dirname))
|
||||
|
||||
# when deploying, we want the top-level directory layout 'language/version/target'
|
||||
archive_path = '{}/{}/{}'.format(language, version, target)
|
||||
print("Archiving '{}' as '{}'...".format(html_dir, archive_path))
|
||||
tarball.add(html_dir, archive_path, filter=not_sources_dir)
|
||||
version_paths.append(archive_path)
|
||||
|
||||
for pdf_path in pdfs:
|
||||
# pdf_path has the form '<ignored>/<language>/<target>/latex/build'
|
||||
latex_dirname = os.path.dirname(pdf_path)
|
||||
pdf_filename = os.path.basename(pdf_path)
|
||||
target_dirname = os.path.dirname(os.path.dirname(latex_dirname))
|
||||
target = os.path.basename(target_dirname)
|
||||
language = os.path.basename(os.path.dirname(target_dirname))
|
||||
|
||||
# when deploying, we want the layout 'language/version/target/pdf'
|
||||
archive_path = '{}/{}/{}/{}'.format(language, version, target, pdf_filename)
|
||||
print("Archiving '{}' as '{}'...".format(pdf_path, archive_path))
|
||||
tarball.add(pdf_path, archive_path)
|
||||
|
||||
for symlink in symlinks:
|
||||
os.unlink(symlink)
|
||||
|
||||
return (os.path.abspath(tarball_path), version_paths)
|
||||
|
||||
|
||||
def create_and_add_symlinks(version: str, git_ver: str, pdfs: List) -> List:
|
||||
""" Create symbolic links for PDFs for 'latest' and 'stable' releases """
|
||||
|
||||
symlinks = []
|
||||
if 'stable' in version or 'latest' in version:
|
||||
for pdf_path in pdfs:
|
||||
symlink_path = pdf_path.replace(git_ver, version)
|
||||
os.symlink(pdf_path, symlink_path)
|
||||
symlinks.append(symlink_path)
|
||||
|
||||
pdfs.extend(symlinks)
|
||||
print('Found %d PDFs in latex directories after adding symlink' % len(pdfs))
|
||||
|
||||
return symlinks
|
||||
|
||||
|
||||
def is_stable_version(version: str) -> bool:
|
||||
""" Heuristic for whether this is the latest stable release """
|
||||
if not version.startswith('v'):
|
||||
return False # branch name
|
||||
if '-' in version:
|
||||
return False # prerelease tag
|
||||
|
||||
git_out = subprocess.check_output(['git', 'tag', '-l']).decode('utf-8')
|
||||
|
||||
versions = [v.strip() for v in git_out.split('\n')]
|
||||
versions = [v for v in versions if re.match(r'^v[\d\.]+$', v.strip())] # include vX.Y.Z only
|
||||
|
||||
versions_pack = [packaging.version.parse(v) for v in versions]
|
||||
|
||||
max_version = max(versions_pack)
|
||||
|
||||
if max_version.public != version[1:]:
|
||||
print('Stable version is v{}. This version is {}.'.format(max_version.public, version))
|
||||
return False
|
||||
else:
|
||||
print('This version {} is the stable version'.format(version))
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,49 +1,9 @@
|
||||
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import os
|
||||
|
||||
from idf_ci_utils import IDF_PATH
|
||||
|
||||
# use relative path to avoid absolute path in pipeline
|
||||
DEFAULT_TEST_PATHS = [
|
||||
'examples',
|
||||
os.path.join('tools', 'test_apps'),
|
||||
'components',
|
||||
]
|
||||
|
||||
DEFAULT_APPS_BUILD_PER_JOB = 60
|
||||
DEFAULT_CASES_TEST_PER_JOB = 30
|
||||
|
||||
DEFAULT_BUILD_CHILD_PIPELINE_FILEPATH = os.path.join(IDF_PATH, 'build_child_pipeline.yml')
|
||||
DEFAULT_TARGET_TEST_CHILD_PIPELINE_FILEPATH = os.path.join(IDF_PATH, 'target_test_child_pipeline.yml')
|
||||
|
||||
DEFAULT_BUILD_CHILD_PIPELINE_NAME = 'Build Child Pipeline'
|
||||
DEFAULT_TARGET_TEST_CHILD_PIPELINE_NAME = 'Target Test Child Pipeline'
|
||||
|
||||
TEST_RELATED_BUILD_JOB_NAME = 'build_test_related_apps'
|
||||
NON_TEST_RELATED_BUILD_JOB_NAME = 'build_non_test_related_apps'
|
||||
|
||||
COMMENT_START_MARKER = '### Dynamic Pipeline Report'
|
||||
TEST_RELATED_APPS_FILENAME = 'test_related_apps.txt'
|
||||
NON_TEST_RELATED_APPS_FILENAME = 'non_test_related_apps.txt'
|
||||
|
||||
TEST_RELATED_APPS_DOWNLOAD_URLS_FILENAME = 'test_related_apps_download_urls.yml'
|
||||
REPORT_TEMPLATE_FILEPATH = os.path.join(
|
||||
IDF_PATH, 'tools', 'ci', 'dynamic_pipelines', 'templates', 'report.template.html'
|
||||
)
|
||||
|
||||
RETRY_JOB_PICTURE_PATH = 'tools/ci/dynamic_pipelines/templates/retry-jobs.png'
|
||||
RETRY_JOB_TITLE = '\n\nRetry failed jobs with with help of "retry_failed_jobs" stage of the pipeline:'
|
||||
RETRY_JOB_PICTURE_LINK = ''
|
||||
|
||||
BUILD_ONLY_LABEL = 'For Maintainers: Only Build Tests'
|
||||
|
||||
KNOWN_GENERATE_TEST_CHILD_PIPELINE_WARNINGS_FILEPATH = os.path.join(
|
||||
IDF_PATH, 'tools', 'ci', 'dynamic_pipelines', 'templates', 'known_generate_test_child_pipeline_warnings.yml'
|
||||
)
|
||||
|
||||
CI_JOB_TOKEN = os.getenv('CI_JOB_TOKEN', '')
|
||||
CI_DASHBOARD_API = os.getenv('CI_DASHBOARD_API', '')
|
||||
CI_PAGES_URL = os.getenv('CI_PAGES_URL', '')
|
||||
CI_PROJECT_URL = os.getenv('CI_PROJECT_URL', '')
|
||||
CI_MERGE_REQUEST_SOURCE_BRANCH_SHA = os.getenv('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', '')
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user