From a548952beae278df806ea5f56ee396d1fdff085f Mon Sep 17 00:00:00 2001 From: Fu Hanxi Date: Wed, 15 Jan 2025 12:25:01 +0100 Subject: [PATCH 1/2] feat: support build with modified files and components --- idf_ci/scripts.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/idf_ci/scripts.py b/idf_ci/scripts.py index 268f758..d4c56da 100644 --- a/idf_ci/scripts.py +++ b/idf_ci/scripts.py @@ -7,6 +7,7 @@ from ._compat import UNDEF, PathLike, Undefined from .profiles import TomlProfileManager +from .settings import CiSettings def build( @@ -16,6 +17,7 @@ def build( profiles: t.List[PathLike] = UNDEF, # type: ignore parallel_count: int = 1, parallel_index: int = 1, + modified_files: t.Optional[t.List[str]] = None, ): if isinstance(profiles, Undefined): profiles = ['default'] @@ -41,6 +43,17 @@ def build( str(parallel_index), ] + if modified_files is not None: + modified_components = CiSettings().get_modified_components(modified_files) + args.extend( + [ + '--modified-files', + *modified_files, + '--modified-components', + *modified_components, + ] + ) + subprocess.run( args, check=True, From 3ec6894ba152d7c7b343c9dff03b590216369928 Mon Sep 17 00:00:00 2001 From: Fu Hanxi Date: Wed, 15 Jan 2025 12:56:52 +0100 Subject: [PATCH 2/2] feat: support `idf-ci test run` --- .pre-commit-config.yaml | 2 +- idf_ci/cli/_options.py | 20 +++++++++++++++++++- idf_ci/cli/build.py | 13 ++++++------- idf_ci/cli/test.py | 34 ++++++++++++++++++++++++++++++++++ idf_ci/profiles.py | 4 ++-- idf_ci/scripts.py | 34 +++++++++++++++++++++++++++++++++- pyproject.toml | 6 +++++- 7 files changed, 100 insertions(+), 13 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8e6c3e6..622c0b8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - license_header.txt # defaults to: LICENSE.txt - --use-current-year - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: 'v0.9.1' + rev: 'v0.9.2' hooks: - id: ruff args: ['--fix'] diff --git a/idf_ci/cli/_options.py b/idf_ci/cli/_options.py index 8495c81..51fb9b0 100644 --- a/idf_ci/cli/_options.py +++ b/idf_ci/cli/_options.py @@ -1,5 +1,7 @@ # SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 +import functools +import os import click @@ -36,17 +38,23 @@ def option_paths(func): return click.option( '--paths', '-p', + default=[os.getcwd()], multiple=True, type=click.Path(dir_okay=True, file_okay=False, exists=True), help=_OPTION_PATHS_HELP, )(func) +def option_target(func): + return click.option( + '--target', '-t', default='all', help='Target to be processed. Or "all" to process all targets.' + )(func) + + _OPTION_PROFILES_HELP = """ \b List of profiles to apply. Could be "default" or file path to a custom profile. Support passing multiple times. The later profiles will override the previous ones. -[default: default] \b Example: @@ -61,3 +69,13 @@ def option_profiles(func): help=_OPTION_PROFILES_HELP, callback=_semicolon_separated_list, )(func) + + +def option_parallel(func): + @click.option('--parallel-count', default=1, help='Number of parallel builds') + @click.option('--parallel-index', default=1, help='Index of the parallel build') + @functools.wraps(func) + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + + return wrapper diff --git a/idf_ci/cli/build.py b/idf_ci/cli/build.py index f7b0c63..6c87b3e 100644 --- a/idf_ci/cli/build.py +++ b/idf_ci/cli/build.py @@ -6,10 +6,10 @@ import click -from idf_ci import CiSettings -from idf_ci import build as build_cmd +from idf_ci.scripts import build as build_cmd +from idf_ci.settings import CiSettings -from ._options import option_paths, option_profiles +from ._options import option_parallel, option_paths, option_profiles, option_target @click.group() @@ -22,11 +22,10 @@ def build(): @build.command() @option_paths -@click.option('--target', '-t', default='all', help='Target to be built. Or "all" to build all targets.') -@click.option('--parallel-count', default=1, help='Number of parallel builds') -@click.option('--parallel-index', default=1, help='Index of the parallel build') +@option_target @option_profiles -def run(paths, target, profiles, parallel_count, parallel_index): +@option_parallel +def run(*, paths, target, profiles, parallel_count, parallel_index): """ Run build according to the given profiles """ diff --git a/idf_ci/cli/test.py b/idf_ci/cli/test.py index 30b61f4..32671ac 100644 --- a/idf_ci/cli/test.py +++ b/idf_ci/cli/test.py @@ -6,6 +6,10 @@ import click +from idf_ci.cli._options import option_parallel, option_paths, option_profiles, option_target +from idf_ci.scripts import test as test_cmd +from idf_ci.settings import CiSettings + @click.group() def test(): @@ -15,6 +19,36 @@ def test(): pass +@test.command() +@option_paths +@option_target +@option_profiles +@option_parallel +@click.option( + '--collected-app-info-filepath', + type=click.Path(dir_okay=False, file_okay=True, exists=True), + help='File path to the recorded app info list, generated by idf-ci build', +) +def run(*, paths, target, profiles, parallel_count, parallel_index, collected_app_info_filepath): + """ + Run tests according to the given profiles + """ + if profiles is not None: + pass + else: + profiles = CiSettings().build_profiles + + click.echo(f'Building {target} with profiles {profiles} at {paths}') + test_cmd( + paths, + target, + profiles=profiles, + parallel_count=parallel_count, + parallel_index=parallel_index, + collected_app_info_filepath=collected_app_info_filepath, + ) + + @test.command() @click.option('--path', default=os.getcwd(), help='Path to create the CI profile') def init_profile(path: str): diff --git a/idf_ci/profiles.py b/idf_ci/profiles.py index 1d66493..fb7d753 100644 --- a/idf_ci/profiles.py +++ b/idf_ci/profiles.py @@ -63,7 +63,7 @@ class IniProfileManager(ProfileManager): suffix: t.ClassVar[t.Literal['.ini', '.toml']] = '.ini' def read(self, profile: PathLike) -> t.Dict: - config = configparser.ConfigParser() + config = configparser.ConfigParser(interpolation=None) config.read(profile) @@ -80,7 +80,7 @@ def merge(self) -> None: merged_dict[section].update(options) with self._merged_profile_writer() as fw: - config = configparser.ConfigParser() + config = configparser.ConfigParser(interpolation=None) for section, options in merged_dict.items(): config[section] = options config.write(fw) diff --git a/idf_ci/scripts.py b/idf_ci/scripts.py index d4c56da..ff0d3ea 100644 --- a/idf_ci/scripts.py +++ b/idf_ci/scripts.py @@ -5,8 +5,10 @@ import subprocess import typing as t +import pytest + from ._compat import UNDEF, PathLike, Undefined -from .profiles import TomlProfileManager +from .profiles import IniProfileManager, TomlProfileManager from .settings import CiSettings @@ -58,3 +60,33 @@ def build( args, check=True, ) + + +def test( + paths: t.List[str], + target: str, + *, + profiles: t.List[PathLike] = UNDEF, # type: ignore + parallel_count: int = 1, + parallel_index: int = 1, + collected_app_info_filepath: t.Optional[PathLike] = None, # noqa # FIXME +): + profile_o = IniProfileManager( + profiles=profiles, + default_profile_path=os.path.join(os.path.dirname(__file__), 'templates', 'default_test_profile.ini'), + ) + + pytest.main( + [ + *paths, + '-c', + profile_o.merged_profile_path, + '--target', + target, + '--parallel-count', + str(parallel_count), + '--parallel-index', + str(parallel_index), + ], + plugins=['idf_ci.idf_pytest.plugin'], + ) diff --git a/pyproject.toml b/pyproject.toml index 04a9862..2eef18d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,11 @@ dependencies = [ # build related "idf-build-apps~=2.6", # test related - "pytest-embedded-idf[serial]~=1.12", + "pytest-embedded-idf[serial]~=1.13", + "pytest-embedded-jtag~=1.13", + "pytest-embedded-qemu~=1.13", + "pytest-ignore-test-results", + "pytest-timeout", ] [project.urls]