Merge branch 'ci/build_test_apps_according_to_required_components' into 'master'

CI: build test apps according to `requires_components` in `.build-test-rules.yml`s

Closes IDFCI-1651

See merge request espressif/esp-idf!22633
This commit is contained in:
Fu Hanxi 2023-05-31 09:05:38 +08:00
commit 3c8a782113
16 changed files with 331 additions and 292 deletions

2
.gitignore vendored
View File

@ -97,3 +97,5 @@ managed_components
# pytest log
pytest_embedded_log/
list_job_*.txt
size_info.txt

View File

@ -20,9 +20,11 @@ workflow:
- 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
variables:
@ -57,7 +59,6 @@ variables:
PYTHON_VER: 3.7.10
CLANG_TIDY_RUNNER_PROJ: 2107 # idf/clang-tidy-runner
IDF_BUILD_APPS_PROJ: 2818 # espressif/idf-build-apps
# Docker images
BOT_DOCKER_IMAGE_TAG: ":latest"

View File

@ -21,11 +21,7 @@
- [Shell Script Related](#shell-script-related)
- [Manifest File to Control the Build/Test apps](#manifest-file-to-control-the-buildtest-apps)
- [Grammar](#grammar)
- [Operands](#operands)
- [Operators](#operators)
- [Limitation:](#limitation)
- [How does it work?](#how-does-it-work)
- [Example](#example)
- [Special Rules](#special-rules)
## General Workflow
@ -242,3 +238,10 @@ We're using the latest version of [idf-build-apps][idf-build-apps]. Please refer
[idf-build-apps]: https://github.com/espressif/idf-build-apps
[manifest-doc]: https://docs.espressif.com/projects/idf-build-apps/en/latest/manifest.html
### Special Rules
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`
- Run in protected branches

View File

@ -14,36 +14,6 @@
script:
- run_cmd python tools/ci/python_packages/ttfw_idf/IDFAssignTest.py $TEST_TYPE $TEST_DIR -c $CI_TARGET_TEST_CONFIG_FILE -o $TEST_DIR/test_configs
assign_example_test:
extends:
- .assign_test_template
- .rules:build:example_test
needs:
- job: build_examples_cmake_esp32
artifacts: false
optional: true
- job: build_examples_cmake_esp32s2
artifacts: false
optional: true
- job: build_examples_cmake_esp32c2
artifacts: false
optional: true
- job: build_examples_cmake_esp32c3
artifacts: false
optional: true
- job: build_examples_cmake_esp32c6
artifacts: false
optional: true
- job: build_examples_cmake_esp32h2
artifacts: false
optional: true
- job: build_examples_cmake_esp32s3
artifacts: false
optional: true
variables:
TEST_TYPE: example_test
TEST_DIR: examples
assign_unit_test:
extends:
- .assign_test_template

View File

@ -21,6 +21,8 @@
needs:
- job: fast_template_app
artifacts: false
- job: mr_variables
optional: true # only MR pipelines would have this
artifacts:
paths:
- "**/build*/size.json"
@ -37,7 +39,7 @@
- "**/build*/sdkconfig"
- "**/build*/bootloader/*.bin"
- "**/build*/partition_table/*.bin"
- list_job_*.json
- list_job_*.txt
- size_info.txt
# unit test specific
- components/idf_test/unit_test/*.yml
@ -66,18 +68,18 @@
# would be clean up after 4 days
- mc share download shiny-s3/idf-artifacts/${CI_PIPELINE_ID}/${CI_JOB_ID}.zip --expire=96h
script:
# CI specific options start from "--collect-size-info xxx". could ignore when running locally
# 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
--collect-size-info size_info.txt
--collect-app-info list_job_${CI_NODE_INDEX:-1}.json
--parallel-count ${CI_NODE_TOTAL:-1}
--parallel-index ${CI_NODE_INDEX:-1}
--extra-preserve-dirs
examples/bluetooth/esp_ble_mesh/ble_mesh_console
examples/bluetooth/hci/controller_hci_uart_esp32
examples/wifi/iperf
--modified-components ${MR_MODIFIED_COMPONENTS}
--modified-files ${MR_MODIFIED_FILES}
.build_cmake_clang_template:
extends:
@ -87,14 +89,14 @@
TEST_BUILD_OPTS_EXTRA: ""
TEST_DIR: tools/test_apps/system/cxx_pthread_bluetooth
script:
# CI specific options start from "--collect-size-info xxx". could ignore when running locally
# 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
--collect-size-info size_info.txt
--collect-app-info list_job_${CI_NODE_INDEX:-1}.json
--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_pytest_template:
@ -114,30 +116,32 @@
- "**/build*/config/sdkconfig.json"
- "**/build*/bootloader/*.bin"
- "**/build*/partition_table/*.bin"
- list_job_*.json
- list_job_*.txt
- size_info.txt
when: always
expire_in: 4 days
script:
# CI specific options start from "--collect-size-info xxx". could ignore when running locally
# 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
--pytest-apps
--collect-size-info size_info.txt
--parallel-count ${CI_NODE_TOTAL:-1}
--parallel-index ${CI_NODE_INDEX:-1}
--modified-components ${MR_MODIFIED_COMPONENTS}
--modified-files ${MR_MODIFIED_FILES}
.build_pytest_no_jtag_template:
extends: .build_pytest_template
script:
# CI specific options start from "--collect-size-info xxx". could ignore when running locally
# 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
-m \"not host_test and not jtag\"
--pytest-apps
--collect-size-info size_info.txt
--parallel-count ${CI_NODE_TOTAL:-1}
--parallel-index ${CI_NODE_INDEX:-1}
--modified-components ${MR_MODIFIED_COMPONENTS}
--modified-files ${MR_MODIFIED_FILES}
.build_pytest_jtag_template:
extends:
@ -156,19 +160,20 @@
- "**/build*/config/sdkconfig.json"
- "**/build*/bootloader/*.bin"
- "**/build*/partition_table/*.bin"
- list_job_*.json
- list_job_*.txt
- size_info.txt
when: always
expire_in: 4 days
script:
# CI specific options start from "--collect-size-info xxx". could ignore when running locally
# 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
-m \"not host_test and jtag\"
--pytest-apps
--collect-size-info size_info.txt
--parallel-count ${CI_NODE_TOTAL:-1}
--parallel-index ${CI_NODE_INDEX:-1}
--modified-components ${MR_MODIFIED_COMPONENTS}
--modified-files ${MR_MODIFIED_FILES}
build_pytest_examples_esp32:
extends:
@ -310,13 +315,13 @@ build_only_components_apps:
parallel: 5
script:
- set_component_ut_vars
# CI specific options start from "--collect-size-info xxx". could ignore when running locally
# CI specific options start from "--parallel-count xxx". could ignore when running locally
- run_cmd python tools/ci/ci_build_apps.py $COMPONENT_UT_DIRS -v
-t all
--collect-size-info size_info.txt
--collect-app-info list_job_${CI_NODE_INDEX:-1}.json
--parallel-count ${CI_NODE_TOTAL:-1}
--parallel-index ${CI_NODE_INDEX:-1}
--modified-components ${MR_MODIFIED_COMPONENTS}
--modified-files ${MR_MODIFIED_FILES}
.build_pytest_test_apps_template:
extends: .build_pytest_template
@ -336,7 +341,7 @@ build_only_components_apps:
- "**/build*/bootloader/*.bin"
- "**/build*/partition_table/*.bin"
- "**/build*/project_description.json"
- list_job_*.json
- list_job_*.txt
- size_info.txt
when: always
expire_in: 4 days
@ -404,13 +409,13 @@ build_only_tools_test_apps:
- .rules:build:custom_test
parallel: 9
script:
# CI specific options start from "--collect-size-info xxx". could ignore when running locally
# CI specific options start from "--parallel-count xxx". could ignore when running locally
- run_cmd python tools/ci/ci_build_apps.py tools/test_apps -v
-t all
--collect-size-info size_info.txt
--collect-app-info list_job_${CI_NODE_INDEX:-1}.json
--parallel-count ${CI_NODE_TOTAL:-1}
--parallel-index ${CI_NODE_INDEX:-1}
--modified-components ${MR_MODIFIED_COMPONENTS}
--modified-files ${MR_MODIFIED_FILES}
.build_template_app_template:
extends:
@ -529,20 +534,18 @@ build_ssc_esp32h2:
- "**/build*/sdkconfig"
- "**/build*/bootloader/*.bin"
- "**/build*/partition_table/*.bin"
- list_job_*.json
- list_job_*.txt
- size_info.txt
- components/idf_test/unit_test/*.yml
when: always
expire_in: 4 days
script:
# CI specific options start from "--collect-size-info xxx". could ignore when running locally
# CI specific options start from "--parallel-count xxx". could ignore when running locally
- run_cmd python tools/ci/ci_build_apps.py tools/unit-test-app -v
-t $IDF_TARGET
--config "configs/*="
--copy-sdkconfig
--preserve-all
--collect-size-info size_info.txt
--collect-app-info list_job_${CI_NODE_INDEX:-1}.json
--parallel-count ${CI_NODE_TOTAL:-1}
--parallel-index ${CI_NODE_INDEX:-1}
- run_cmd python tools/unit-test-app/tools/UnitTestParser.py tools/unit-test-app ${CI_NODE_INDEX:-1}

View File

@ -39,14 +39,6 @@
- build_system
- downloadable-tools
"build:windows":
labels:
- build
- windows
patterns:
- build_system
- windows
"build:macos":
labels:
- build
@ -140,7 +132,6 @@ build:integration_test:
- i154
- flash_multi
- ecdsa
- ccs811 # pytest*ccs811*
- nvs_encr_hmac
patterns:
- "{0}-{1}-{2}"

View File

@ -186,3 +186,22 @@ check_configure_ci_environment_parsing:
script:
- cd tools/ci
- python -m unittest ci_build_apps.py
mr_variables:
extends:
- .pre_check_template
- .rules:mr
- .before_script_minimal
tags:
- build
script:
- echo "MR_MODIFIED_FILES=$(python tools/ci/ci_get_mr_info.py files ${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME} | xargs)" >> mr.env
- echo "MR_MODIFIED_COMPONENTS=$(python tools/ci/ci_get_mr_info.py components ${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME} | xargs)" >> mr.env
- >
if echo "$CI_MERGE_REQUEST_LABELS" | egrep "^([^,\n\r]+,)*BUILD_AND_TEST_ALL_APPS(,[^,\n\r]+)*$"; then
echo "BUILD_AND_TEST_ALL_APPS=1" >> mr.env
fi
artifacts:
reports:
dotenv: mr.env
expire_in: 4 days

View File

@ -166,9 +166,6 @@
- "tools/split_paths_by_spaces.py"
.patterns-windows: &patterns-windows
- "tools/windows/**/*"
.patterns-docker: &patterns-docker
- "tools/docker/**/*"
@ -261,13 +258,6 @@
- "components/driver/include/driver/sdmmc*.h"
- "components/sdmmc/**/*"
.patterns-example_test-ccs811: &patterns-example_test-ccs811
# components
- "examples/system/console/advanced/components/**/*"
- "components/driver/i2c/**/*"
# tests
- "examples/peripherals/i2c/i2c_tools/**/*"
# for jobs: UT_xx_SDSPI related
.patterns-unit_test-sdio: &patterns-unit_test-sdio
- "components/hal/sdio*.c"
@ -374,6 +364,7 @@
#########
# Rules #
#########
### Branches ###
.rules:protected:
rules:
- <<: *if-protected
@ -382,6 +373,30 @@
rules:
- <<: *if-protected-no_label
.rules:dev:
rules:
- <<: *if-trigger
- <<: *if-dev-push
.rules:mr:
rules:
- <<: *if-dev-push
.rules:tag:release:
rules:
- <<: *if-tag-release
.rules:ref:master-schedule:
rules:
- <<: *if-ref-master
- <<: *if-schedule
.rules:ref:master-always:
rules:
- <<: *if-ref-master
when: always
### Patterns ###
.rules:patterns:python-cache:
rules:
- *if-schedule
@ -404,25 +419,6 @@
- <<: *if-dev-push
changes: *patterns-danger-npm
.rules:dev:
rules:
- <<: *if-trigger
- <<: *if-dev-push
.rules:tag:release:
rules:
- <<: *if-tag-release
.rules:ref:master-schedule:
rules:
- <<: *if-ref-master
- <<: *if-schedule
.rules:ref:master-always:
rules:
- <<: *if-ref-master
when: always
.rules:patterns:clang_tidy:
rules:
- <<: *if-protected
@ -588,9 +584,6 @@
.if-label-unit_test_esp32s3: &if-label-unit_test_esp32s3
if: '$BOT_LABEL_UNIT_TEST_ESP32S3 || $CI_MERGE_REQUEST_LABELS =~ /^(?:[^,\n\r]+,)*unit_test_esp32s3(?:,[^,\n\r]+)*$/i'
.if-label-windows: &if-label-windows
if: '$BOT_LABEL_WINDOWS || $CI_MERGE_REQUEST_LABELS =~ /^(?:[^,\n\r]+,)*windows(?:,[^,\n\r]+)*$/i'
.rules:build:
rules:
- <<: *if-revert-branch
@ -1136,57 +1129,6 @@
- <<: *if-dev-push
changes: *patterns-submodule
.rules:build:example_test:
rules:
- <<: *if-revert-branch
when: never
- <<: *if-protected
- <<: *if-example_test-ota-include_nightly_run-rule
- <<: *if-label-build
- <<: *if-label-example_test
- <<: *if-label-example_test_esp32
- <<: *if-label-example_test_esp32c2
- <<: *if-label-example_test_esp32c3
- <<: *if-label-example_test_esp32c6
- <<: *if-label-example_test_esp32h2
- <<: *if-label-example_test_esp32s2
- <<: *if-label-example_test_esp32s3
- <<: *if-label-target_test
- <<: *if-dev-push
changes: *patterns-build-example_test
- <<: *if-dev-push
changes: *patterns-build_components
- <<: *if-dev-push
changes: *patterns-build_system
- <<: *if-dev-push
changes: *patterns-downloadable-tools
- <<: *if-dev-push
changes: *patterns-example_test
- <<: *if-dev-push
changes: *patterns-example_test-bt
- <<: *if-dev-push
changes: *patterns-example_test-ccs811
- <<: *if-dev-push
changes: *patterns-example_test-ethernet
- <<: *if-dev-push
changes: *patterns-example_test-i154
- <<: *if-dev-push
changes: *patterns-example_test-nvs_encr_hmac
- <<: *if-dev-push
changes: *patterns-example_test-sdio
- <<: *if-dev-push
changes: *patterns-example_test-usb
- <<: *if-dev-push
changes: *patterns-example_test-wifi
- <<: *if-dev-push
changes: *patterns-target_test-adc
- <<: *if-dev-push
changes: *patterns-target_test-ecdsa
- <<: *if-dev-push
changes: *patterns-target_test-i154
- <<: *if-dev-push
changes: *patterns-target_test-wifi
.rules:build:example_test-esp32:
rules:
- <<: *if-revert-branch
@ -1209,8 +1151,6 @@
changes: *patterns-example_test
- <<: *if-dev-push
changes: *patterns-example_test-bt
- <<: *if-dev-push
changes: *patterns-example_test-ccs811
- <<: *if-dev-push
changes: *patterns-example_test-ethernet
- <<: *if-dev-push
@ -1253,8 +1193,6 @@
changes: *patterns-example_test
- <<: *if-dev-push
changes: *patterns-example_test-bt
- <<: *if-dev-push
changes: *patterns-example_test-ccs811
- <<: *if-dev-push
changes: *patterns-example_test-ethernet
- <<: *if-dev-push
@ -1298,8 +1236,6 @@
changes: *patterns-example_test
- <<: *if-dev-push
changes: *patterns-example_test-bt
- <<: *if-dev-push
changes: *patterns-example_test-ccs811
- <<: *if-dev-push
changes: *patterns-example_test-ethernet
- <<: *if-dev-push
@ -1342,8 +1278,6 @@
changes: *patterns-example_test
- <<: *if-dev-push
changes: *patterns-example_test-bt
- <<: *if-dev-push
changes: *patterns-example_test-ccs811
- <<: *if-dev-push
changes: *patterns-example_test-ethernet
- <<: *if-dev-push
@ -1386,8 +1320,6 @@
changes: *patterns-example_test
- <<: *if-dev-push
changes: *patterns-example_test-bt
- <<: *if-dev-push
changes: *patterns-example_test-ccs811
- <<: *if-dev-push
changes: *patterns-example_test-ethernet
- <<: *if-dev-push
@ -1430,8 +1362,6 @@
changes: *patterns-example_test
- <<: *if-dev-push
changes: *patterns-example_test-bt
- <<: *if-dev-push
changes: *patterns-example_test-ccs811
- <<: *if-dev-push
changes: *patterns-example_test-ethernet
- <<: *if-dev-push
@ -1474,8 +1404,6 @@
changes: *patterns-example_test
- <<: *if-dev-push
changes: *patterns-example_test-bt
- <<: *if-dev-push
changes: *patterns-example_test-ccs811
- <<: *if-dev-push
changes: *patterns-example_test-ethernet
- <<: *if-dev-push
@ -1599,8 +1527,6 @@
changes: *patterns-example_test
- <<: *if-dev-push
changes: *patterns-example_test-bt
- <<: *if-dev-push
changes: *patterns-example_test-ccs811
- <<: *if-dev-push
changes: *patterns-example_test-ethernet
- <<: *if-dev-push
@ -2403,19 +2329,6 @@
- <<: *if-dev-push
changes: *patterns-target_test-adc
.rules:test:example_test-esp32-ccs811:
rules:
- <<: *if-revert-branch
when: never
- <<: *if-protected
- <<: *if-label-build-only
when: never
- <<: *if-label-example_test
- <<: *if-label-example_test_esp32
- <<: *if-label-target_test
- <<: *if-dev-push
changes: *patterns-example_test-ccs811
.rules:test:example_test-esp32-ethernet:
rules:
- <<: *if-revert-branch

View File

@ -39,6 +39,7 @@
--parallel-count ${CI_NODE_TOTAL:-1}
--parallel-index ${CI_NODE_INDEX:-1}
${PYTEST_EXTRA_FLAGS}
--app-info-filepattern \"list_job_*.txt\"
.pytest_examples_dir_template:
extends: .pytest_template
@ -100,7 +101,7 @@ pytest_examples_esp32_jtag:
pytest_examples_esp32_ccs811:
extends:
- .pytest_examples_dir_template
- .rules:test:example_test-esp32-ccs811
- .rules:test:example_test-esp32
needs:
- build_pytest_examples_esp32
tags: [ esp32, ccs811 ]
@ -1132,36 +1133,6 @@ pytest_test_apps_esp32s3_mspi_f4r4:
- cd tools/ci/python_packages/tiny_test_fw/bin
- run_cmd python Runner.py $TEST_CASE_PATH -c $CONFIG_FILE -e $ENV_FILE --known_failure_cases_file $CI_PROJECT_DIR/known_failure_cases/known_failure_cases.txt
.example_test_template:
extends: .target_test_job_template
needs:
- assign_example_test
variables:
TEST_CASE_PATH: "$CI_PROJECT_DIR/examples"
CONFIG_FILE_PATH: "${CI_PROJECT_DIR}/examples/test_configs"
.example_test_esp32_template:
extends:
- .example_test_template
- .rules:test:example_test-esp32
.example_test_esp32c3_template:
extends:
- .example_test_template
- .rules:test:example_test-esp32c3
example_test_001C:
extends: .example_test_esp32_template
tags:
- ESP32
- Example_GENERIC
example_test_C3_GENERIC:
extends: .example_test_esp32c3_template
tags:
- ESP32C3
- Example_GENERIC
.unit_test_template:
extends: .target_test_job_template
needs: # the assign already needs all the build jobs
@ -1208,7 +1179,7 @@ example_test_C3_GENERIC:
UT_001:
extends: .unit_test_esp32_template
parallel: 16
parallel: 2
tags:
- ESP32_IDF
- UT_T1_1
@ -1217,7 +1188,6 @@ UT_001:
UT_002:
extends: .unit_test_esp32_template
parallel: 7
tags:
- ESP32_IDF
- UT_T1_1
@ -1264,7 +1234,7 @@ UT_028:
UT_035:
extends: .unit_test_esp32s2_template
parallel: 16
parallel: 2
tags:
- ESP32S2_IDF
- UT_T1_1
@ -1279,7 +1249,6 @@ UT_S2_SDSPI:
UT_C2:
extends: .unit_test_esp32c2_template
parallel: 8
tags:
- ESP32C2_IDF
- UT_T1_1
@ -1287,7 +1256,6 @@ UT_C2:
UT_C3:
extends: .unit_test_esp32c3_template
parallel: 11
tags:
- ESP32C3_IDF
- UT_T1_1
@ -1302,21 +1270,19 @@ UT_C3_SDSPI:
UT_C6:
extends: .unit_test_esp32c6_template
parallel: 8
tags:
- ESP32C6_IDF
- UT_T1_1
UT_H2:
extends: .unit_test_esp32h2_template
parallel: 5
tags:
- ESP32H2_IDF
- UT_T1_1
UT_S3:
extends: .unit_test_esp32s3_template
parallel: 9
parallel: 2
tags:
- ESP32S3_IDF
- UT_T1_1

View File

@ -143,7 +143,7 @@ repos:
require_serial: true
additional_dependencies:
- PyYAML == 5.3.1
- idf_build_apps
- idf_build_apps~=1.0
- id: sort-build-test-rules-ymls
name: sort .build-test-rules.yml files
entry: tools/ci/check_build_test_rules.py sort-yaml

View File

@ -13,6 +13,8 @@
# This is an experimental feature, and if you found any bug or have any question, please report to
# https://github.com/espressif/pytest-embedded/issues
import glob
import json
import logging
import os
import re
@ -36,11 +38,11 @@ from pytest_embedded.utils import find_by_suffix
from pytest_embedded_idf.dut import IdfDut
try:
from idf_ci_utils import to_list
from idf_ci_utils import IDF_PATH, to_list
from idf_unity_tester import CaseTester
except ImportError:
sys.path.append(os.path.join(os.path.dirname(__file__), 'tools', 'ci'))
from idf_ci_utils import to_list
from idf_ci_utils import IDF_PATH, to_list
from idf_unity_tester import CaseTester
try:
@ -252,7 +254,7 @@ def test_case_name(request: FixtureRequest, target: str, config: str) -> str:
@pytest.fixture
@multi_dut_fixture
def build_dir(app_path: str, target: Optional[str], config: Optional[str]) -> str:
def build_dir(request: FixtureRequest, app_path: str, target: Optional[str], config: Optional[str]) -> str:
"""
Check local build dir with the following priority:
@ -261,11 +263,6 @@ def build_dir(app_path: str, target: Optional[str], config: Optional[str]) -> st
3. build_<config>
4. build
Args:
app_path: app path
target: target
config: config
Returns:
valid build directory
"""
@ -278,6 +275,25 @@ def build_dir(app_path: str, target: Optional[str], config: Optional[str]) -> st
check_dirs.append(f'build_{config}')
check_dirs.append('build')
idf_pytest_embedded = request.config.stash[_idf_pytest_embedded_key]
build_dir = None
if idf_pytest_embedded.apps_list is not None:
for check_dir in check_dirs:
binary_path = os.path.join(app_path, check_dir)
if binary_path in idf_pytest_embedded.apps_list:
build_dir = check_dir
break
if build_dir is None:
pytest.skip(
f'app path {app_path} with target {target} and config {config} is not listed in app info list files'
)
return '' # not reachable, to fool mypy
if build_dir:
check_dirs = [build_dir]
for check_dir in check_dirs:
binary_path = os.path.join(app_path, check_dir)
if os.path.isdir(binary_path):
@ -286,9 +302,8 @@ def build_dir(app_path: str, target: Optional[str], config: Optional[str]) -> st
logging.warning('checking binary path: %s... missing... try another place', binary_path)
recommend_place = check_dirs[0]
raise ValueError(
f'no build dir valid. Please build the binary via "idf.py -B {recommend_place} build" and run pytest again'
f'no build dir valid. Please build the binary via "idf.py -B {check_dirs[0]} build" and run pytest again'
)
@ -412,20 +427,32 @@ def dev_user(request: FixtureRequest) -> str:
# Hook functions #
##################
def pytest_addoption(parser: pytest.Parser) -> None:
base_group = parser.getgroup('idf')
base_group.addoption(
idf_group = parser.getgroup('idf')
idf_group.addoption(
'--sdkconfig',
help='sdkconfig postfix, like sdkconfig.ci.<config>. (Default: None, which would build all found apps)',
)
base_group.addoption('--known-failure-cases-file', help='known failure cases file path')
base_group.addoption(
idf_group.addoption('--known-failure-cases-file', help='known failure cases file path')
idf_group.addoption(
'--dev-user',
help='user name associated with some specific device/service used during the test execution',
)
base_group.addoption(
idf_group.addoption(
'--dev-passwd',
help='password associated with some specific device/service used during the test execution',
)
idf_group.addoption(
'--app-info-basedir',
default=IDF_PATH,
help='app info base directory. specify this value when you\'re building under a '
'different IDF_PATH. (Default: $IDF_PATH)',
)
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_pytest_embedded_key = pytest.StashKey['IdfPytestEmbedded']()
@ -446,10 +473,34 @@ def pytest_configure(config: Config) -> None:
if not target: # also could specify through markexpr via "-m"
target = get_target_marker_from_expr(config.getoption('markexpr') or '')
apps_list = None
app_info_basedir = config.getoption('app_info_basedir')
app_info_filepattern = config.getoption('app_info_filepattern')
if app_info_filepattern:
apps_list = []
for file in glob.glob(os.path.join(IDF_PATH, app_info_filepattern)):
with open(file) as fr:
for line in fr.readlines():
if not line.strip():
continue
# each line is a valid json
app_info = json.loads(line.strip())
if app_info_basedir and app_info['app_dir'].startswith(app_info_basedir):
relative_app_dir = os.path.relpath(app_info['app_dir'], app_info_basedir)
apps_list.append(os.path.join(IDF_PATH, os.path.join(relative_app_dir, app_info['build_dir'])))
print('Detected app: ', apps_list[-1])
else:
print(
f'WARNING: app_info base dir {app_info_basedir} not recognizable in {app_info["app_dir"]}, skipping...'
)
continue
config.stash[_idf_pytest_embedded_key] = IdfPytestEmbedded(
target=target,
sdkconfig=config.getoption('sdkconfig'),
known_failure_cases_file=config.getoption('known_failure_cases_file'),
apps_list=apps_list,
)
config.pluginmanager.register(config.stash[_idf_pytest_embedded_key])
@ -470,11 +521,13 @@ class IdfPytestEmbedded:
target: str,
sdkconfig: Optional[str] = None,
known_failure_cases_file: Optional[str] = None,
apps_list: Optional[List[str]] = None,
):
# CLI options to filter the test cases
self.target = target.lower()
self.sdkconfig = sdkconfig
self.known_failure_patterns = self._parse_known_failure_cases_file(known_failure_cases_file)
self.apps_list = apps_list
self._failed_cases: List[Tuple[str, bool, bool]] = [] # (test_case_name, is_known_failure_cases, is_xfail)
@ -599,7 +652,11 @@ class IdfPytestEmbedded:
test_case_name = item.funcargs.get('test_case_name', '')
if test_case_name:
self._failed_cases.append(
(test_case_name, self._is_known_failure(test_case_name), report.keywords.get('xfail', False))
(
test_case_name,
self._is_known_failure(test_case_name),
report.keywords.get('xfail', False),
)
)
return report

View File

@ -1,5 +1,12 @@
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
.i2c_dependencies: &i2c_dependencies
depends_filepatterns:
# components
- examples/system/console/advanced/components/**/*
- components/driver/i2c/**/*
- components/driver/Kconfig
examples/peripherals/adc/continuous_read:
disable:
- if: SOC_ADC_DMA_SUPPORTED != 1
@ -24,11 +31,13 @@ examples/peripherals/i2c/i2c_self_test:
disable:
- if: SOC_I2C_SUPPORT_SLAVE != 1
reason: the test requires both master and slave
<<: *i2c_dependencies
examples/peripherals/i2c/i2c_simple:
disable:
- if: SOC_I2C_SUPPORT_SLAVE != 1
reason: the test requires both master and slave
<<: *i2c_dependencies
examples/peripherals/i2c/i2c_tools:
disable:
@ -37,6 +46,7 @@ examples/peripherals/i2c/i2c_tools:
- if: IDF_TARGET != "esp32"
temporary: true
reason: lack of runners
<<: *i2c_dependencies
examples/peripherals/i2s/i2s_adc_dac:
disable:

View File

@ -5,7 +5,7 @@ python_files = pytest_*.py
# ignore PytestExperimentalApiWarning for record_xml_attribute
# set traceback to "short" to prevent the overwhelming tracebacks
addopts =
-s
-s -vv
--embedded-services esp,idf
--tb short
--strict-markers

View File

@ -8,10 +8,10 @@ This file is used in CI generate binary files for different kinds of apps
import argparse
import os
import sys
import typing as t
import unittest
from collections import defaultdict
from pathlib import Path
from typing import List, Optional, Set
import yaml
from idf_build_apps import LOGGER, App, build_apps, find_apps, setup_logging
@ -20,25 +20,28 @@ from idf_ci_utils import IDF_PATH, PytestApp, get_pytest_cases, get_ttfw_app_pat
CI_ENV_VARS = {
'EXTRA_CFLAGS': '-Werror -Werror=deprecated-declarations -Werror=unused-variable '
'-Werror=unused-but-set-variable -Werror=unused-function -Wstrict-prototypes',
'-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',
'-Werror=unused-but-set-variable -Werror=unused-function',
'LDGEN_CHECK_MAPPING': '1',
}
def get_pytest_apps(
paths: List[str],
paths: t.List[str],
target: str,
config_rules_str: List[str],
config_rules_str: t.List[str],
marker_expr: str,
filter_expr: str,
preserve_all: bool = False,
extra_default_build_targets: Optional[List[str]] = None,
) -> List[App]:
extra_default_build_targets: t.Optional[t.List[str]] = None,
modified_components: t.Optional[t.List[str]] = None,
modified_files: t.Optional[t.List[str]] = None,
ignore_app_dependencies_filepatterns: t.Optional[t.List[str]] = None,
) -> t.List[App]:
pytest_cases = get_pytest_cases(paths, target, marker_expr, filter_expr)
_paths: Set[str] = set()
_paths: t.Set[str] = set()
test_related_app_configs = defaultdict(set)
for case in pytest_cases:
for app in case.apps:
@ -53,6 +56,9 @@ def get_pytest_apps(
if not case.nightly_run:
test_related_app_configs[app.path].add(app.config)
if not extra_default_build_targets:
extra_default_build_targets = []
app_dirs = list(_paths)
if not app_dirs:
raise RuntimeError('No apps found')
@ -68,9 +74,12 @@ def get_pytest_apps(
build_log_path='build_log.txt',
size_json_path='size.json',
check_warnings=True,
manifest_rootpath=IDF_PATH,
manifest_files=[str(p) for p in Path(IDF_PATH).glob('**/.build-test-rules.yml')],
default_build_targets=SUPPORTED_TARGETS + extra_default_build_targets,
manifest_rootpath=IDF_PATH,
modified_components=modified_components,
modified_files=modified_files,
ignore_app_dependencies_filepatterns=ignore_app_dependencies_filepatterns,
)
for app in apps:
@ -85,12 +94,15 @@ def get_pytest_apps(
def get_cmake_apps(
paths: List[str],
paths: t.List[str],
target: str,
config_rules_str: List[str],
config_rules_str: t.List[str],
preserve_all: bool = False,
extra_default_build_targets: Optional[List[str]] = None,
) -> List[App]:
extra_default_build_targets: t.Optional[t.List[str]] = None,
modified_components: t.Optional[t.List[str]] = None,
modified_files: t.Optional[t.List[str]] = None,
ignore_app_dependencies_filepatterns: t.Optional[t.List[str]] = None,
) -> t.List[App]:
ttfw_app_dirs = get_ttfw_app_paths(paths, target)
apps = find_apps(
@ -103,9 +115,12 @@ def get_cmake_apps(
size_json_path='size.json',
check_warnings=True,
preserve=False,
manifest_rootpath=IDF_PATH,
manifest_files=[str(p) for p in Path(IDF_PATH).glob('**/.build-test-rules.yml')],
default_build_targets=SUPPORTED_TARGETS + extra_default_build_targets,
manifest_rootpath=IDF_PATH,
modified_components=modified_components,
modified_files=modified_files,
ignore_app_dependencies_filepatterns=ignore_app_dependencies_filepatterns,
)
apps_for_build = []
@ -130,7 +145,7 @@ APPS_BUILD_PER_JOB = 30
def main(args: argparse.Namespace) -> None:
extra_default_build_targets: List[str] = []
extra_default_build_targets: t.List[str] = []
if args.default_build_test_rules:
with open(args.default_build_test_rules) as fr:
configs = yaml.safe_load(fr)
@ -148,6 +163,9 @@ def main(args: argparse.Namespace) -> None:
args.filter_expr,
args.preserve_all,
extra_default_build_targets,
args.modified_components,
args.modified_files,
args.ignore_app_dependencies_filepatterns,
)
else:
LOGGER.info('build apps. will skip pytest apps with pytest scripts')
@ -157,6 +175,9 @@ def main(args: argparse.Namespace) -> None:
args.config,
args.preserve_all,
extra_default_build_targets,
args.modified_components,
args.modified_files,
args.ignore_app_dependencies_filepatterns,
)
LOGGER.info('Found %d apps after filtering', len(apps))
@ -175,22 +196,28 @@ def main(args: argparse.Namespace) -> None:
if abs_extra_preserve_dir == abs_app_dir or abs_extra_preserve_dir in abs_app_dir.parents:
app.preserve = True
sys.exit(
build_apps(
apps,
parallel_count=args.parallel_count,
parallel_index=args.parallel_index,
dry_run=False,
build_verbose=args.build_verbose,
keep_going=True,
collect_size_info=args.collect_size_info,
collect_app_info=args.collect_app_info,
ignore_warning_strs=args.ignore_warning_str,
ignore_warning_file=args.ignore_warning_file,
copy_sdkconfig=args.copy_sdkconfig,
)
res = build_apps(
apps,
parallel_count=args.parallel_count,
parallel_index=args.parallel_index,
dry_run=False,
build_verbose=args.build_verbose,
keep_going=True,
collect_size_info='size_info.txt',
collect_app_info='list_job_@p.txt',
ignore_warning_strs=args.ignore_warning_str,
ignore_warning_file=args.ignore_warning_file,
copy_sdkconfig=args.copy_sdkconfig,
modified_components=args.modified_components,
modified_files=args.modified_files,
ignore_app_dependencies_filepatterns=args.ignore_app_dependencies_filepatterns,
)
if isinstance(res, tuple):
sys.exit(res[0])
else:
sys.exit(res)
if __name__ == '__main__':
parser = argparse.ArgumentParser(
@ -249,8 +276,7 @@ if __name__ == '__main__':
parser.add_argument(
'--ignore-warning-str',
nargs='+',
help='Ignore the warning string that match the specified regex in the build output. '
'Can be specified multiple times.',
help='Ignore the warning string that match the specified regex in the build output. space-separated list',
)
parser.add_argument(
'--ignore-warning-file',
@ -298,6 +324,30 @@ if __name__ == '__main__':
help='by default this script would set the build flags exactly the same as the CI ones. '
'Set this flag to use your local build flags.',
)
parser.add_argument(
'--modified-components',
nargs='*',
default=None,
help='space-separated list which specifies the modified components. app with `depends_components` set in the '
'corresponding manifest files would only be built if depends on any of the specified components.',
)
parser.add_argument(
'--modified-files',
nargs='*',
default=None,
help='space-separated list which specifies the modified files. app with `depends_filepatterns` set in the '
'corresponding manifest files would only be built if any of the specified file pattern matches any of the '
'specified modified files.',
)
parser.add_argument(
'-if',
'--ignore-app-dependencies-filepatterns',
nargs='*',
default=None,
help='space-separated list which specifies the file patterns used for ignoring checking the app dependencies. '
'The `depends_components` and `depends_filepatterns` set in the manifest files will be ignored when any of the '
'specified file patterns matches any of the modified files. Must be used together with --modified-files',
)
arguments = parser.parse_args()
@ -309,6 +359,37 @@ if __name__ == '__main__':
os.environ[_k] = _v
LOGGER.info(f'env var {_k} set to "{_v}"')
if os.getenv('IS_MR_PIPELINE') == '0' or os.getenv('BUILD_AND_TEST_ALL_APPS') == '1':
# if it's not MR pipeline or env var BUILD_AND_TEST_ALL_APPS=1,
# remove component dependency related arguments
if 'modified_components' in arguments:
arguments.modified_components = None
if 'modified_files' in arguments:
arguments.modified_files = None
# file patterns to tigger full build
if 'modified_components' in arguments and not arguments.ignore_app_dependencies_filepatterns:
arguments.ignore_app_dependencies_filepatterns = [
# tools
'tools/cmake/**/*',
'tools/tools.json',
# components
'components/cxx/**/*',
'components/esp_common/**/*',
'components/esp_hw_support/**/*',
'components/esp_rom/**/*',
'components/esp_system/**/*',
'components/esp_timer/**/*',
'components/freertos/**/*',
'components/hal/**/*',
'components/heap/**/*',
'components/log/**/*',
'components/newlib/**/*',
'components/riscv/**/*',
'components/soc/**/*',
'components/xtensa/**/*',
]
main(arguments)

View File

@ -10,22 +10,20 @@
import argparse
import os
import subprocess
import typing as t
from pathlib import Path
from gitlab_api import Gitlab
try:
from typing import Any, Union
except ImportError:
# Only used for type annotations
pass
if t.TYPE_CHECKING:
from gitlab.v4.objects import ProjectCommit, ProjectMergeRequest
def _get_mr_obj(source_branch): # type: (str) -> Union[Gitlab, None]
if not source_branch:
return None
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
@ -33,7 +31,7 @@ def _get_mr_obj(source_branch): # type: (str) -> Union[Gitlab, None]
return None
def get_mr_iid(source_branch): # type: (str) -> str
def get_mr_iid(source_branch: str) -> str:
mr = _get_mr_obj(source_branch)
if not mr:
return ''
@ -41,40 +39,65 @@ def get_mr_iid(source_branch): # type: (str) -> str
return str(mr.iid)
def get_mr_changed_files(source_branch): # type: (str) -> Any
def get_mr_changed_files(source_branch: str) -> t.List[str]:
mr = _get_mr_obj(source_branch)
if not mr:
return ''
return []
return subprocess.check_output(['git', 'diff', '--name-only',
'origin/{}...origin/{}'.format(mr.target_branch, source_branch)]).decode('utf8')
git_output = subprocess.check_output(
['git', 'diff', '--name-only', 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): # type: (str) -> str
def get_mr_commits(source_branch: str) -> t.List['ProjectCommit']:
mr = _get_mr_obj(source_branch)
if not mr:
return ''
return '\n'.join([commit.id for commit in mr.commits()])
return []
return list(mr.commits())
def get_mr_components(source_branch: str) -> t.List[str]:
components: t.Set[str] = set()
for f in get_mr_changed_files(source_branch):
file = Path(f)
if (
file.parts[0] == 'components'
and 'test_apps' not in file.parts
and file.parts[-1] != '.build-test-rules.yml'
):
components.add(file.parts[1])
return list(components)
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')
actions = parser.add_subparsers(dest='action', help='info type', required=True)
common_args = argparse.ArgumentParser(add_help=False)
common_args.add_argument('src_branch', nargs='?', help='source branch')
common_args.add_argument('src_branch', help='source branch')
actions.add_parser('id', parents=[common_args])
actions.add_parser('files', parents=[common_args])
actions.add_parser('commits', parents=[common_args])
actions.add_parser('components', parents=[common_args])
args = parser.parse_args()
if args.action == 'id':
print(get_mr_iid(args.src_branch))
elif args.action == 'files':
print(get_mr_changed_files(args.src_branch))
_print_list(get_mr_changed_files(args.src_branch))
elif args.action == 'commits':
print(get_mr_commits(args.src_branch))
_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))
else:
raise NotImplementedError('not possible to get here')

View File

@ -54,7 +54,7 @@ class IDFAssignTest(CIAssignTest.AssignTest):
super(IDFAssignTest, self).__init__(test_case_path, ci_config_file, case_group)
def format_build_log_path(self, parallel_num):
return 'list_job_{}.json'.format(parallel_num)
return 'list_job_{}.txt'.format(parallel_num)
def create_artifact_index_file(self, project_id=None, pipeline_id=None):
if project_id is None: