Merge branch 'ci/known_generate_target_test_issues' into 'master'

ci: add known warnings while generating the target test jobs

Closes IDFCI-1941

See merge request espressif/esp-idf!28551
This commit is contained in:
Fu Hanxi 2024-01-30 00:25:24 +08:00
commit 7913c42996
13 changed files with 297 additions and 87 deletions

View File

@ -287,6 +287,7 @@ generate_build_child_pipeline:
dependencies: # set dependencies to null to avoid missing artifacts issue dependencies: # set dependencies to null to avoid missing artifacts issue
needs: needs:
- pipeline_variables - pipeline_variables
- check_test_cases_env_markers_and_required_runners
artifacts: artifacts:
paths: paths:
- build_child_pipeline.yml - build_child_pipeline.yml

View File

@ -170,3 +170,9 @@ pipeline_variables:
artifacts: artifacts:
reports: reports:
dotenv: pipeline.env dotenv: pipeline.env
check_test_cases_env_markers_and_required_runners:
extends:
- .pre_check_template
script:
- python tools/ci/dynamic_pipelines/scripts/generate_target_test_child_pipeline.py --check

View File

@ -157,13 +157,19 @@ repos:
additional_dependencies: additional_dependencies:
- PyYAML == 5.3.1 - PyYAML == 5.3.1
- idf-build-apps~=2.0 - idf-build-apps~=2.0
- id: sort-build-test-rules-ymls - id: sort-yaml-files
name: sort .build-test-rules.yml files name: sort yaml files
entry: tools/ci/check_build_test_rules.py sort-yaml entry: tools/ci/sort_yaml.py
language: python language: python
files: '\.build-test-rules\.yml' files: '\.build-test-rules\.yml$|known_generate_test_child_pipeline_warnings\.yml$'
additional_dependencies:
- ruamel.yaml
- id: sort-yaml-test
name: sort yaml test
entry: python -m unittest tools/ci/sort_yaml.py
language: python
files: 'tools/ci/sort_yaml\.py$'
additional_dependencies: additional_dependencies:
- PyYAML == 5.3.1
- ruamel.yaml - ruamel.yaml
- id: check-build-test-rules-path-exists - id: check-build-test-rules-path-exists
name: check path in .build-test-rules.yml exists name: check path in .build-test-rules.yml exists

View File

@ -11,8 +11,6 @@
import os import os
import sys import sys
import gitlab
if os.path.join(os.path.dirname(__file__), 'tools', 'ci') not in sys.path: if os.path.join(os.path.dirname(__file__), 'tools', 'ci') not in sys.path:
sys.path.append(os.path.join(os.path.dirname(__file__), 'tools', 'ci')) sys.path.append(os.path.join(os.path.dirname(__file__), 'tools', 'ci'))
@ -161,22 +159,8 @@ def app_downloader(pipeline_id: t.Optional[str]) -> t.Optional[AppDownloader]:
logging.info('Downloading build report from the build pipeline %s', pipeline_id) logging.info('Downloading build report from the build pipeline %s', pipeline_id)
test_app_presigned_urls_file = None test_app_presigned_urls_file = None
try:
gl = gitlab_api.Gitlab(os.getenv('CI_PROJECT_ID', 'espressif/esp-idf')) gl = gitlab_api.Gitlab(os.getenv('CI_PROJECT_ID', 'espressif/esp-idf'))
except gitlab.exceptions.GitlabAuthenticationError:
msg = """To download artifacts from gitlab, please create ~/.python-gitlab.cfg with the following content:
[global]
default = internal
ssl_verify = true
timeout = 5
[internal]
url = <OUR INTERNAL HTTPS SERVER URL>
private_token = <YOUR PERSONAL ACCESS TOKEN>
api_version = 4
"""
raise SystemExit(msg)
for child_pipeline in gl.project.pipelines.get(pipeline_id, lazy=True).bridges.list(iterator=True): for child_pipeline in gl.project.pipelines.get(pipeline_id, lazy=True).bridges.list(iterator=True):
if child_pipeline.name == 'build_child_pipeline': if child_pipeline.name == 'build_child_pipeline':

View File

@ -6,7 +6,6 @@ import inspect
import os import os
import re import re
import sys import sys
from io import StringIO
from pathlib import Path from pathlib import Path
from typing import Dict from typing import Dict
from typing import List from typing import List
@ -345,38 +344,6 @@ def check_test_scripts(
sys.exit(exit_code) sys.exit(exit_code)
def sort_yaml(files: List[str]) -> None:
from ruamel.yaml import YAML, CommentedMap
yaml = YAML()
yaml.indent(mapping=2, sequence=4, offset=2)
yaml.width = 4096 # avoid wrap lines
exit_code = 0
for f in files:
with open(f) as fr:
file_s = fr.read()
fr.seek(0)
file_d: CommentedMap = yaml.load(fr)
sorted_yaml = CommentedMap(dict(sorted(file_d.items())))
file_d.copy_attributes(sorted_yaml)
with StringIO() as s:
yaml.dump(sorted_yaml, s)
string = s.getvalue()
if string != file_s:
with open(f, 'w') as fw:
fw.write(string)
print(
f'Sorted yaml file {f}. Please take a look. sometimes the format is a bit messy'
)
exit_code = 1
sys.exit(exit_code)
def check_exist() -> None: def check_exist() -> None:
exit_code = 0 exit_code = 0
@ -422,9 +389,6 @@ if __name__ == '__main__':
help='default build test rules config file', help='default build test rules config file',
) )
_sort_yaml = action.add_parser('sort-yaml')
_sort_yaml.add_argument('files', nargs='+', help='all specified yaml files')
_check_exist = action.add_parser('check-exist') _check_exist = action.add_parser('check-exist')
arg = parser.parse_args() arg = parser.parse_args()
@ -434,9 +398,7 @@ if __name__ == '__main__':
os.path.join(os.path.dirname(__file__), '..', '..') os.path.join(os.path.dirname(__file__), '..', '..')
) )
if arg.action == 'sort-yaml': if arg.action == 'check-exist':
sort_yaml(arg.files)
elif arg.action == 'check-exist':
check_exist() check_exist()
else: else:
check_dirs = set() check_dirs = set()

View File

@ -1,6 +1,5 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
import os import os
from idf_ci_utils import IDF_PATH from idf_ci_utils import IDF_PATH
@ -31,3 +30,7 @@ REPORT_TEMPLATE_FILEPATH = os.path.join(
) )
BUILD_ONLY_LABEL = 'For Maintainers: Only Build Tests' 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'
)

View File

@ -1,28 +1,36 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
"""This file is used for generating the child pipeline for target test jobs. """This file is used for generating the child pipeline for target test jobs.
1. Check the build jobs' artifacts to get the built apps' information. 1. Check the build jobs' artifacts to get the built apps' information.
2. Post the Build Report if it's running in an MR pipeline. 2. Post the Build Report if it's running in an MR pipeline.
3. Generate the child pipeline for target test jobs. 3. Generate the child pipeline for target test jobs.
""" """
import argparse import argparse
import glob import glob
import logging
import os import os
import typing as t import typing as t
from collections import Counter, defaultdict from collections import Counter
from collections import defaultdict
import __init__ # noqa: F401 # inject the system path import __init__ # noqa: F401 # inject the system path
from dynamic_pipelines.constants import (BUILD_ONLY_LABEL, DEFAULT_CASES_TEST_PER_JOB, import yaml
DEFAULT_TARGET_TEST_CHILD_PIPELINE_FILEPATH, DEFAULT_TEST_PATHS) from dynamic_pipelines.constants import BUILD_ONLY_LABEL
from dynamic_pipelines.models import EmptyJob, Job, TargetTestJob from dynamic_pipelines.constants import DEFAULT_CASES_TEST_PER_JOB
from dynamic_pipelines.constants import DEFAULT_TARGET_TEST_CHILD_PIPELINE_FILEPATH
from dynamic_pipelines.constants import DEFAULT_TEST_PATHS
from dynamic_pipelines.constants import KNOWN_GENERATE_TEST_CHILD_PIPELINE_WARNINGS_FILEPATH
from dynamic_pipelines.models import EmptyJob
from dynamic_pipelines.models import Job
from dynamic_pipelines.models import TargetTestJob
from dynamic_pipelines.utils import dump_jobs_to_yaml from dynamic_pipelines.utils import dump_jobs_to_yaml
from gitlab.v4.objects import Project from gitlab.v4.objects import Project
from gitlab_api import Gitlab from gitlab_api import Gitlab
from idf_build_apps import App from idf_build_apps import App
from idf_build_apps.constants import BuildStatus
from idf_ci.app import import_apps_from_txt from idf_ci.app import import_apps_from_txt
from idf_pytest.script import get_all_apps
from idf_pytest.script import get_pytest_cases from idf_pytest.script import get_pytest_cases
@ -41,16 +49,23 @@ def get_tags_with_amount(s: str) -> t.List[str]:
return sorted(res) return sorted(res)
def get_target_test_jobs(project: Project, paths: str, apps: t.List[App]) -> t.Tuple[t.List[Job], t.List[str]]: def get_target_test_jobs(
project: Project, paths: str, apps: t.List[App]
) -> t.Tuple[t.List[Job], t.List[str], t.Dict[str, t.List[str]]]:
""" """
Return the target test jobs and the extra yaml files to include Return the target test jobs and the extra yaml files to include
""" """
issues: t.Dict[str, t.List[str]] = {
'no_env_marker_test_cases': [],
'no_runner_tags': [],
}
if mr_labels := os.getenv('CI_MERGE_REQUEST_LABELS'): if mr_labels := os.getenv('CI_MERGE_REQUEST_LABELS'):
print(f'MR labels: {mr_labels}') print(f'MR labels: {mr_labels}')
if BUILD_ONLY_LABEL in mr_labels.split(','): if BUILD_ONLY_LABEL in mr_labels.split(','):
print('MR has build only label, skip generating target test child pipeline') print('MR has build only label, skip generating target test child pipeline')
return [EmptyJob()], [] return [EmptyJob()], [], issues
pytest_cases = get_pytest_cases( pytest_cases = get_pytest_cases(
paths, paths,
@ -61,7 +76,7 @@ def get_target_test_jobs(project: Project, paths: str, apps: t.List[App]) -> t.T
res = defaultdict(list) res = defaultdict(list)
for case in pytest_cases: for case in pytest_cases:
if not case.env_markers: if not case.env_markers:
print(f'No env markers found for {case.item.originalname} in {case.path}. Ignoring...') issues['no_env_marker_test_cases'].append(case.item.nodeid)
continue continue
res[(case.target_selector, tuple(sorted(case.env_markers)))].append(case) res[(case.target_selector, tuple(sorted(case.env_markers)))].append(case)
@ -72,9 +87,10 @@ def get_target_test_jobs(project: Project, paths: str, apps: t.List[App]) -> t.T
# we don't need to get all runner, as long as we get one runner, it's fine # we don't need to get all runner, as long as we get one runner, it's fine
runner_list = project.runners.list(status='online', tag_list=','.join(runner_tags), get_all=False) runner_list = project.runners.list(status='online', tag_list=','.join(runner_tags), get_all=False)
if not runner_list: if not runner_list:
print(f'WARNING: No runner found with tag {",".join(runner_tags)}, ignoring the following test cases:') issues['no_runner_tags'].append(','.join(runner_tags))
logging.warning(f'No runner found for {",".join(runner_tags)}, required by cases:')
for case in cases: for case in cases:
print(f' - {case.name}') logging.warning(f' - {case.item.nodeid}')
continue continue
target_test_job = TargetTestJob( target_test_job = TargetTestJob(
@ -95,11 +111,48 @@ def get_target_test_jobs(project: Project, paths: str, apps: t.List[App]) -> t.T
else: else:
extra_include_yml = ['tools/ci/dynamic_pipelines/templates/generate_target_test_report.yml'] extra_include_yml = ['tools/ci/dynamic_pipelines/templates/generate_target_test_report.yml']
return target_test_jobs, extra_include_yml issues['no_env_marker_test_cases'] = sorted(issues['no_env_marker_test_cases'])
issues['no_runner_tags'] = sorted(issues['no_runner_tags'])
return target_test_jobs, extra_include_yml, issues
def generate_target_test_child_pipeline(project: Project, paths: str, apps: t.List[App], output_filepath: str) -> None: def generate_target_test_child_pipeline(
target_test_jobs, extra_include_yml = get_target_test_jobs(project, paths, apps) project: Project,
paths: str,
apps: t.List[App],
output_filepath: str,
) -> None:
target_test_jobs, extra_include_yml, issues = get_target_test_jobs(project, paths, apps)
with open(KNOWN_GENERATE_TEST_CHILD_PIPELINE_WARNINGS_FILEPATH) as fr:
known_warnings_dict = yaml.safe_load(fr) or dict()
failed = False
known_no_env_marker_test_cases = set(known_warnings_dict.get('no_env_marker_test_cases', []))
no_env_marker_test_cases = set(issues['no_env_marker_test_cases'])
if no_env_marker_test_cases - known_no_env_marker_test_cases:
print('ERROR: NEW "no_env_marker_test_cases" detected:')
for case in no_env_marker_test_cases - known_no_env_marker_test_cases:
print(f' - {case}')
failed = True
known_no_runner_tags = set(known_warnings_dict.get('no_runner_tags', []))
no_runner_tags = set(issues['no_runner_tags'])
if no_runner_tags - known_no_runner_tags:
print('ERROR: NEW "no_runner_tags" detected:')
for tag in no_runner_tags - known_no_runner_tags:
print(f' - {tag}')
failed = True
if failed:
raise SystemExit(
f'Please fix the issue, '
f'or update the known warnings file: {KNOWN_GENERATE_TEST_CHILD_PIPELINE_WARNINGS_FILEPATH}'
)
dump_jobs_to_yaml(target_test_jobs, output_filepath, extra_include_yml) dump_jobs_to_yaml(target_test_jobs, output_filepath, extra_include_yml)
print(f'Generate child pipeline yaml file {output_filepath} with {sum(j.parallel for j in target_test_jobs)} jobs') print(f'Generate child pipeline yaml file {output_filepath} with {sum(j.parallel for j in target_test_jobs)} jobs')
@ -134,13 +187,37 @@ if __name__ == '__main__':
default=DEFAULT_TARGET_TEST_CHILD_PIPELINE_FILEPATH, default=DEFAULT_TARGET_TEST_CHILD_PIPELINE_FILEPATH,
help='Output child pipeline file path', help='Output child pipeline file path',
) )
parser.add_argument(
'--check',
action='store_true',
help='Check if the child pipeline could be generated successfully. '
'test cases without env marker or required unset runner will be printed out. '
'(Note: All apps and test cases will be checked)',
)
parser.add_argument(
'--app-info-filepattern',
default='list_job_*.txt',
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.',
)
args = parser.parse_args() args = parser.parse_args()
app_list_filepattern = 'list_job_*.txt' gl_project = Gitlab(args.project_id).project
if args.check:
apps = list(get_all_apps(args.paths)[0]) # test related apps only
for app in apps:
app.build_status = BuildStatus.SUCCESS # pretend they are built successfully
else:
apps = [] apps = []
for f in glob.glob(app_list_filepattern): for f in glob.glob(args.app_info_filepattern):
apps.extend(import_apps_from_txt(f)) apps.extend(import_apps_from_txt(f))
gl_project = Gitlab(args.project_id).project generate_target_test_child_pipeline(
generate_target_test_child_pipeline(gl_project, args.paths, apps, args.output) gl_project,
args.paths,
apps,
args.output,
)

View File

@ -0,0 +1,53 @@
no_env_marker_test_cases:
- components/fatfs/test_apps/flash_ro/pytest_fatfs_flash_ro.py::test_fatfs_flash_ro
- components/fatfs/test_apps/flash_wl/pytest_fatfs_flash_wl.py::test_fatfs_flash_wl_generic[default]
- components/fatfs/test_apps/flash_wl/pytest_fatfs_flash_wl.py::test_fatfs_flash_wl_generic[fastseek]
- components/fatfs/test_apps/flash_wl/pytest_fatfs_flash_wl.py::test_fatfs_flash_wl_generic[release]
- components/nvs_flash/test_apps/pytest_nvs_flash.py::test_nvs_flash[default]
- components/pthread/test_apps/pthread_psram_tests/pytest_pthread_psram_tests.py::test_pthread_psram
- components/vfs/test_apps/pytest_vfs.py::test_vfs_ccomp[ccomp]
- components/vfs/test_apps/pytest_vfs.py::test_vfs_default[default]
- components/vfs/test_apps/pytest_vfs.py::test_vfs_default[iram]
- examples/protocols/http_server/file_serving/pytest_http_server_file_serving.py::test_examples_protocol_http_server_file_serving[spiffs]
- examples/storage/fatfsgen/pytest_fatfsgen_example.py::test_examples_fatfsgen[test_read_only_partition_gen]
- examples/storage/fatfsgen/pytest_fatfsgen_example.py::test_examples_fatfsgen[test_read_only_partition_gen_default_dt]
- examples/storage/fatfsgen/pytest_fatfsgen_example.py::test_examples_fatfsgen[test_read_only_partition_gen_ln]
- examples/storage/fatfsgen/pytest_fatfsgen_example.py::test_examples_fatfsgen[test_read_only_partition_gen_ln_default_dt]
- examples/storage/fatfsgen/pytest_fatfsgen_example.py::test_examples_fatfsgen[test_read_write_partition_gen]
- examples/storage/fatfsgen/pytest_fatfsgen_example.py::test_examples_fatfsgen[test_read_write_partition_gen_default_dt]
- examples/storage/fatfsgen/pytest_fatfsgen_example.py::test_examples_fatfsgen[test_read_write_partition_gen_ln]
- examples/storage/fatfsgen/pytest_fatfsgen_example.py::test_examples_fatfsgen[test_read_write_partition_gen_ln_default_dt]
- examples/storage/nvs_rw_blob/pytest_nvs_rw_blob.py::test_examples_nvs_rw_blob
- examples/storage/nvs_rw_value/pytest_nvs_rw_value.py::test_examples_nvs_rw_value
- examples/storage/nvs_rw_value_cxx/pytest_nvs_rw_value_cxx.py::test_examples_nvs_rw_value_cxx
- examples/storage/nvsgen/pytest_nvsgen_example.py::test_nvsgen_example
- examples/storage/partition_api/partition_find/pytest_partition_find_example.py::test_partition_find_example
- examples/storage/partition_api/partition_mmap/pytest_partition_mmap_example.py::test_partition_mmap_example
- examples/storage/partition_api/partition_ops/pytest_partition_ops_example.py::test_partition_ops_example
- examples/storage/parttool/pytest_parttool_example.py::test_examples_parttool
- examples/storage/spiffs/pytest_spiffs_example.py::test_examples_spiffs
- examples/storage/spiffsgen/pytest_spiffsgen_example.py::test_spiffsgen_example
- examples/storage/wear_levelling/pytest_wear_levelling_example.py::test_wear_levelling_example
- tools/test_apps/system/panic/pytest_panic.py::test_gdbstub_coredump[gdbstub_coredump]
- tools/test_apps/system/panic/pytest_panic.py::test_panic_delay[panic_delay]
no_runner_tags:
- esp32,ip101
- esp32,psram,quad_psram
- esp32,quad_psram
- esp32c2,ethernet,xtal_40mhz
- esp32c2,ethernet_ota,xtal_40mhz
- esp32c2,jtag,xtal_40mhz
- esp32c3,Example_ShieldBox_Basic
- esp32c3,ethernet_flash_8m
- esp32c3,ethernet_ota
- esp32c3,sdcard_sdmode
- esp32c6,jtag
- esp32h2,jtag
- esp32s2,Example_ShieldBox_Basic
- esp32s2,ethernet_flash_8m
- esp32s2,ethernet_ota
- esp32s2,usb_host_flash_disk
- esp32s2,wifi_high_traffic
- esp32s3,Example_ShieldBox_Basic
- esp32s3,ethernet_flash_8m
- esp32s3,ethernet_ota

View File

@ -50,3 +50,4 @@ tools/ci/python_packages/gitlab_api.py
tools/ci/python_packages/idf_http_server_test/**/* tools/ci/python_packages/idf_http_server_test/**/*
tools/ci/python_packages/idf_iperf_test_util/**/* tools/ci/python_packages/idf_iperf_test_util/**/*
tools/esp_prov/**/* tools/esp_prov/**/*
tools/ci/sort_yaml.py

View File

@ -77,6 +77,7 @@ tools/ci/gitlab_yaml_linter.py
tools/ci/mirror-submodule-update.sh tools/ci/mirror-submodule-update.sh
tools/ci/multirun_with_pyenv.sh tools/ci/multirun_with_pyenv.sh
tools/ci/push_to_github.sh tools/ci/push_to_github.sh
tools/ci/sort_yaml.py
tools/ci/test_autocomplete/test_autocomplete.py tools/ci/test_autocomplete/test_autocomplete.py
tools/ci/test_configure_ci_environment.sh tools/ci/test_configure_ci_environment.sh
tools/ci/test_reproducible_build.sh tools/ci/test_reproducible_build.sh

View File

@ -1,6 +1,5 @@
# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
import argparse import argparse
import logging import logging
import os import os
@ -11,7 +10,12 @@ import tempfile
import time import time
import zipfile import zipfile
from functools import wraps from functools import wraps
from typing import Any, Callable, Dict, List, Optional, Union from typing import Any
from typing import Callable
from typing import Dict
from typing import List
from typing import Optional
from typing import Union
import gitlab import gitlab
@ -80,7 +84,24 @@ class Gitlab(object):
def _init_gitlab_inst(self, project_id: Optional[int], config_files: Optional[List[str]]) -> None: def _init_gitlab_inst(self, project_id: Optional[int], config_files: Optional[List[str]]) -> None:
gitlab_id = os.getenv('LOCAL_GITLAB_HTTPS_HOST') # if None, will use the default gitlab server gitlab_id = os.getenv('LOCAL_GITLAB_HTTPS_HOST') # if None, will use the default gitlab server
self.gitlab_inst = gitlab.Gitlab.from_config(gitlab_id=gitlab_id, config_files=config_files) self.gitlab_inst = gitlab.Gitlab.from_config(gitlab_id=gitlab_id, config_files=config_files)
try:
self.gitlab_inst.auth() self.gitlab_inst.auth()
except gitlab.exceptions.GitlabAuthenticationError:
msg = """To call gitlab apis locally, please create ~/.python-gitlab.cfg with the following content:
[global]
default = internal
ssl_verify = true
timeout = 5
[internal]
url = <OUR INTERNAL HTTPS SERVER URL>
private_token = <YOUR PERSONAL ACCESS TOKEN>
api_version = 4
"""
raise SystemExit(msg)
if project_id: if project_id:
self.project = self.gitlab_inst.projects.get(project_id, lazy=True) self.project = self.gitlab_inst.projects.get(project_id, lazy=True)
else: else:

94
tools/ci/sort_yaml.py Executable file
View File

@ -0,0 +1,94 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
"""
Sort yaml file
Exit non-zero if any file is modified
"""
import io
import os
import sys
import tempfile
import unittest
from ruamel.yaml import CommentedMap
from ruamel.yaml import YAML
def sort_yaml(f: str) -> int:
yaml = YAML()
yaml.indent(mapping=2, sequence=4, offset=2)
yaml.width = 4096 # avoid wrap lines``
exit_code = 0
with open(f) as fr:
file_s = fr.read()
fr.seek(0)
try:
file_d: CommentedMap = yaml.load(fr)
except Exception as e:
print(f'Failed to load yaml file {f}: {e}')
return 1
# sort dict keys
sorted_yaml = CommentedMap(dict(sorted(file_d.items())))
file_d.copy_attributes(sorted_yaml)
# sort item
for k, v in sorted_yaml.items():
if isinstance(v, list):
sorted_yaml[k].sort()
with io.StringIO() as s:
yaml.dump(sorted_yaml, s)
string = s.getvalue()
if string != file_s:
with open(f, 'w') as fw:
fw.write(string)
print(f'Sorted yaml file {f}. Please take a look. sometimes the format is a bit messy')
exit_code = 1
return exit_code
class TestSortYaml(unittest.TestCase):
def test_sort_yaml(self) -> None:
_, test_yaml = tempfile.mkstemp()
with open(test_yaml, 'w') as fw:
fw.write(
'''no_runner: []
no_env_marker:
- 1
- 3 # foo
- 2 # bar'''
)
sort_yaml(fw.name)
try:
with open(test_yaml) as fr:
self.assertEqual(
fr.read(),
'''no_env_marker:
- 1
- 2 # bard
- 3 # foo
no_runner: []''',
)
except AssertionError:
print(f'Please check the sorted yaml file {test_yaml}')
else:
os.remove(test_yaml)
if __name__ == '__main__':
ret = 0
for _f in sys.argv[1:]:
exit_code = sort_yaml(_f)
if exit_code != 0 and ret == 0:
ret = exit_code
sys.exit(ret)

View File

@ -1,6 +1,5 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0 # SPDX-License-Identifier: CC0-1.0
import os import os
import pytest import pytest
@ -11,6 +10,7 @@ PROMPT = 'test_intr_dump>'
@pytest.mark.esp32 @pytest.mark.esp32
@pytest.mark.qemu @pytest.mark.qemu
@pytest.mark.host_test
def test_esp_intr_dump_nonshared(dut: Dut) -> None: def test_esp_intr_dump_nonshared(dut: Dut) -> None:
dut.expect_exact(PROMPT, timeout=10) dut.expect_exact(PROMPT, timeout=10)
@ -24,6 +24,7 @@ def test_esp_intr_dump_nonshared(dut: Dut) -> None:
@pytest.mark.esp32 @pytest.mark.esp32
@pytest.mark.qemu @pytest.mark.qemu
@pytest.mark.host_test
def test_esp_intr_dump_shared(dut: Dut) -> None: def test_esp_intr_dump_shared(dut: Dut) -> None:
dut.expect_exact(PROMPT, timeout=10) dut.expect_exact(PROMPT, timeout=10)