diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 2cb7917..a6317fe 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -39,7 +39,7 @@ jobs: pip install -e '.[test]' - name: Run tests with pytest and coverage run: | - pytest --junitxml pytest.xml --cov-report term-missing:skip-covered --cov idf_ci tests/ | tee pytest-coverage.txt + pytest --junitxml pytest.xml --cov-report term-missing --cov idf_ci tests/ | tee pytest-coverage.txt - name: Upload coverage report to PR (Python 3.13 only) if: matrix.python-version == '3.13' && github.event_name == 'pull_request' uses: MishaKav/pytest-coverage-comment@v1 diff --git a/.gitignore b/.gitignore index 2cd699b..efd8d76 100644 --- a/.gitignore +++ b/.gitignore @@ -73,7 +73,8 @@ instance/ .scrapy # Sphinx documentation -docs/_build/ +docs/*/_build/ +docs/*/references/api/ # PyBuilder .pybuilder/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9ae31b1..8e6c3e6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,13 +1,13 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: 'v5.0.0' hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: mixed-line-ending args: [ '-f=lf' ] - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.5.5 + rev: 'v1.5.5' hooks: - id: insert-license files: \.py$ @@ -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.8.4' + rev: 'v0.9.1' hooks: - id: ruff args: ['--fix'] @@ -33,3 +33,13 @@ repos: - tomlkit - idf-build-apps~=2.6 - pytest-embedded~=1.12 + - repo: https://github.com/hfudev/rstfmt + rev: 'v0.1.4' + hooks: + - id: rstfmt + args: ['-w', '-1'] + files: "docs\/.+rst$" + additional_dependencies: + - sphinx-argparse + - sphinx-tabs + - sphinxcontrib-mermaid diff --git a/docs/_apidoc_templates/module.rst_t b/docs/_apidoc_templates/module.rst_t new file mode 100644 index 0000000..49cca84 --- /dev/null +++ b/docs/_apidoc_templates/module.rst_t @@ -0,0 +1,8 @@ +{%- if show_headings %} +{{- [basename, "module"] | join(' ') | e | heading }} + +{% endif -%} +.. automodule:: {{ qualname }} +{%- for option in automodule_options %} + :{{ option }}: +{%- endfor %} diff --git a/docs/_apidoc_templates/package.rst_t b/docs/_apidoc_templates/package.rst_t new file mode 100644 index 0000000..44cbce9 --- /dev/null +++ b/docs/_apidoc_templates/package.rst_t @@ -0,0 +1,49 @@ +{%- macro automodule(modname, options) -%} +.. automodule:: {{ modname }} +{%- for option in options %} + :{{ option }}: +{%- endfor %} +{%- endmacro %} + +{%- macro toctree(docnames) -%} +.. toctree:: + :maxdepth: {{ maxdepth }} +{% for docname in docnames %} + {{ docname }} +{%- endfor %} +{%- endmacro %} + +{%- if is_namespace %} +{{- [pkgname, "namespace"] | join(" ") | e | heading }} +{% else %} +{{- [pkgname, "package"] | join(" ") | e | heading }} +{% endif %} + +{%- if is_namespace %} +.. py:module:: {{ pkgname }} +{% endif %} + +{%- if modulefirst and not is_namespace %} +{{ automodule(pkgname, automodule_options) }} +{% endif %} + +{%- if subpackages %} +{{ toctree(subpackages) }} +{% endif %} + +{%- if submodules %} +{% if separatemodules %} +{{ toctree(submodules) }} +{% else %} +{%- for submodule in submodules %} +{% if show_headings %} +{{- [submodule, "module"] | join(" ") | e | heading(2) }} +{% endif %} +{{ automodule(submodule, automodule_options) }} +{% endfor %} +{%- endif %} +{%- endif %} + +{%- if not modulefirst and not is_namespace %} +{{ automodule(pkgname, automodule_options) }} +{% endif %} diff --git a/docs/_apidoc_templates/toc.rst_t b/docs/_apidoc_templates/toc.rst_t new file mode 100644 index 0000000..878540c --- /dev/null +++ b/docs/_apidoc_templates/toc.rst_t @@ -0,0 +1,7 @@ +{{ header | heading }} + +.. toctree:: + :maxdepth: {{ maxdepth }} +{% for docname in docnames %} + {{ docname }} +{%- endfor %} diff --git a/docs/_static/espressif-logo.svg b/docs/_static/espressif-logo.svg new file mode 100644 index 0000000..1725310 --- /dev/null +++ b/docs/_static/espressif-logo.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_static/theme_overrides.css b/docs/_static/theme_overrides.css new file mode 100644 index 0000000..a88100a --- /dev/null +++ b/docs/_static/theme_overrides.css @@ -0,0 +1,55 @@ +/* override table width restrictions */ +@media screen and (min-width: 767px) { + + .wy-table-responsive table td { + /* !important prevents the common CSS stylesheets from overriding + this as on RTD they are loaded after this stylesheet */ + white-space: normal !important; + } + + .wy-table-responsive { + overflow: visible !important; + } + } + + .wy-side-nav-search { + background-color: #e3e3e3 !important; + } + + .wy-side-nav-search input[type=text] { + border-radius: 0px !important; + border-color: #333333 !important; + } + + .icon-home { + color: #333333 !important; + } + + .icon-home:hover { + background-color: #d6d6d6 !important; + } + + .version { + color: #000000 !important; + } + + a:hover { + color: #bd2c2a !important; + } + + .logo { + width: 240px !important; + } + + .highlight .c1 { + color: #008080; + } + + .bolditalics { + font-weight: bold; + font-style: italic; + } + +pre { + white-space: pre-wrap !important; +} diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html new file mode 100644 index 0000000..631d13d --- /dev/null +++ b/docs/_templates/layout.html @@ -0,0 +1,7 @@ +{% extends "!layout.html" %} + +{% block document %} + + {{ super() }} + +{% endblock %} diff --git a/docs/conf_common.py b/docs/conf_common.py new file mode 100644 index 0000000..b0fff3c --- /dev/null +++ b/docs/conf_common.py @@ -0,0 +1,61 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import os +import subprocess + +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'idf-ci' +project_homepage = 'https://github.com/espressif/idf-ci' +copyright = '2025, Espressif Systems (Shanghai) Co., Ltd.' # noqa: A001 +author = 'Fu Hanxi' +languages = ['en'] +version = '0.x' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx_copybutton', + 'myst_parser', + 'sphinxcontrib.mermaid', + 'sphinxarg.ext', + 'sphinx_tabs.tabs', +] + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_css_files = ['theme_overrides.css'] +html_logo = '../_static/espressif-logo.svg' +html_static_path = ['../_static'] +html_theme = 'sphinx_rtd_theme' + + +def generate_api_docs(language): + subprocess.run( + [ + 'sphinx-apidoc', + os.path.join(os.path.dirname(__file__), '..', 'idf_ci'), + '--remove-old', + '-f', + '-H', + 'API Reference', + '--no-headings', + '-t', + '_apidoc_templates', + '-o', + os.path.join(os.path.dirname(__file__), language, 'references', 'api'), + ] + ) diff --git a/docs/en/Makefile b/docs/en/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/docs/en/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/en/conf.py b/docs/en/conf.py new file mode 100644 index 0000000..6bd9b88 --- /dev/null +++ b/docs/en/conf.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import os +import sys + +language = 'en' + +sys.path.insert(0, os.path.abspath('../')) + +from conf_common import * # noqa + +generate_api_docs(language) # noqa diff --git a/docs/en/explanations/profiles.rst b/docs/en/explanations/profiles.rst new file mode 100644 index 0000000..d5684ed --- /dev/null +++ b/docs/en/explanations/profiles.rst @@ -0,0 +1,47 @@ +########## + Profiles +########## + +Profiles are a way to store a set of settings. We have three types of profiles: + +- `CI Profile`_: settings for idf-ci_ +- `Build Profile`_: settings for idf-build-apps_ +- `Test Profile`_: settings for pytest_ + +Relationship between profiles + +.. mermaid:: + + graph LR + C[CI Profile] -- selects --> A[Build Profiles] -- merge --> A_O[Merged Build Profile] --> idf-build-apps + C -- selects --> B[Test Profiles] -- merge --> B_O[Merged Test Profile] --> pytest + +************ + CI Profile +************ + +By default it is located at ``.idf_ci.toml`` in the root of the project. It can be overriden by calling ``idf-ci`` with ``--ci-profile`` option. + +For more information, please refer to :doc:`../references/ci-profile`. + +*************** + Build Profile +*************** + +Support passing multiple profiles. The order of profiles is important, the later profile will override the former profile. Each profile can be a path to a file or a keyword 'default' which applies the default settings. + +For more information, please refer to :doc:`../references/build-profile`. + +************** + Test Profile +************** + +Support passing multiple profiles. The order of profiles is important, the later profile will override the former profile. Each profile can be a path to a file or a keyword 'default' which applies the default settings. + +For more information, please refer to :doc:`../references/test-profile`. + +.. _idf-build-apps: https://github.com/espressif/idf-build-apps + +.. _idf-ci: https://github.com/espressif/idf-ci + +.. _pytest: https://github.com/pytest-dev/pytest diff --git a/docs/en/index.rst b/docs/en/index.rst new file mode 100644 index 0000000..d5d2fd6 --- /dev/null +++ b/docs/en/index.rst @@ -0,0 +1,20 @@ +################################ + idf-ci |version| Documentation +################################ + +This documentation is for idf-ci. idf-ci is a python library for CI/CD of ESP-IDF projects + +.. toctree:: + :maxdepth: 1 + :caption: Explanations + + explanations/profiles + +.. toctree:: + :maxdepth: 1 + :caption: References + + references/ci-profile + references/build-profile + references/test-profile + references/api/modules.rst diff --git a/docs/en/make.bat b/docs/en/make.bat new file mode 100644 index 0000000..954237b --- /dev/null +++ b/docs/en/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/en/references/build-profile.rst b/docs/en/references/build-profile.rst new file mode 100644 index 0000000..fb57070 --- /dev/null +++ b/docs/en/references/build-profile.rst @@ -0,0 +1,10 @@ +############### + Build Profile +############### + +****************** + Default Settings +****************** + +.. literalinclude:: ../../../idf_ci/templates/default_build_profile.toml + :language: toml diff --git a/docs/en/references/ci-profile.rst b/docs/en/references/ci-profile.rst new file mode 100644 index 0000000..c15e32f --- /dev/null +++ b/docs/en/references/ci-profile.rst @@ -0,0 +1,10 @@ +############ + CI Profile +############ + +****************** + Default Settings +****************** + +.. literalinclude:: ../../../idf_ci/templates/default_ci_profile.toml + :language: toml diff --git a/docs/en/references/test-profile.rst b/docs/en/references/test-profile.rst new file mode 100644 index 0000000..af2789e --- /dev/null +++ b/docs/en/references/test-profile.rst @@ -0,0 +1,10 @@ +############## + Test Profile +############## + +****************** + Default Settings +****************** + +.. literalinclude:: ../../../idf_ci/templates/default_test_profile.ini + :language: ini diff --git a/idf_ci/cli/__init__.py b/idf_ci/cli/__init__.py index 10523ad..8139596 100644 --- a/idf_ci/cli/__init__.py +++ b/idf_ci/cli/__init__.py @@ -5,6 +5,8 @@ import click +from idf_ci.settings import CiSettings + from .build import build from .test import test @@ -15,8 +17,15 @@ @click.group(context_settings=_CLI_SETTINGS) -def cli(): - pass +@click.option( + '--ci-profile', + type=click.Path(dir_okay=False, file_okay=True, exists=True), + help='Path to the CI profile file', +) +def cli(ci_profile): + if ci_profile: + print('Using CI profile:', ci_profile) + CiSettings.CONFIG_FILE_PATH = ci_profile @cli.command() diff --git a/idf_ci/cli/_options.py b/idf_ci/cli/_options.py index a38912f..8495c81 100644 --- a/idf_ci/cli/_options.py +++ b/idf_ci/cli/_options.py @@ -3,6 +3,25 @@ import click +from idf_ci._compat import UNDEF + + +######################## +# Validation Functions # +######################## +def _semicolon_separated_list(ctx, param, value): # noqa: ARG001 + if value is None: + return UNDEF + + if not isinstance(value, str): + raise click.BadParameter('Value must be a string') + + return [v.strip() for v in value.split(';') if v.strip()] + + +########### +# Options # +########### _OPTION_PATHS_HELP = """ List of directories to process. Support passing multiple times. @@ -31,15 +50,14 @@ def option_paths(func): \b Example: - --profiles default --profiles custom.toml + --profiles default;custom.toml # To apply default and custom profiles + --profiles ';' # To unset the default profile """ def option_profiles(func): return click.option( '--profiles', - multiple=True, - default=['default'], - show_default=False, help=_OPTION_PROFILES_HELP, + callback=_semicolon_separated_list, )(func) diff --git a/idf_ci/cli/build.py b/idf_ci/cli/build.py index 694d98a..f7b0c63 100644 --- a/idf_ci/cli/build.py +++ b/idf_ci/cli/build.py @@ -6,6 +6,7 @@ import click +from idf_ci import CiSettings from idf_ci import build as build_cmd from ._options import option_paths, option_profiles @@ -22,13 +23,20 @@ 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_profiles -def run(paths, target, profiles): +def run(paths, target, profiles, parallel_count, parallel_index): """ Run build 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}') - build_cmd(paths, target, profiles=profiles) + build_cmd(paths, target, profiles=profiles, parallel_count=parallel_count, parallel_index=parallel_index) @build.command() diff --git a/idf_ci/cli/completions.py b/idf_ci/cli/completions.py deleted file mode 100644 index ad4cb6a..0000000 --- a/idf_ci/cli/completions.py +++ /dev/null @@ -1,2 +0,0 @@ -# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: Apache-2.0 diff --git a/idf_ci/scripts.py b/idf_ci/scripts.py index 082e15f..268f758 100644 --- a/idf_ci/scripts.py +++ b/idf_ci/scripts.py @@ -14,6 +14,8 @@ def build( target: str, *, profiles: t.List[PathLike] = UNDEF, # type: ignore + parallel_count: int = 1, + parallel_index: int = 1, ): if isinstance(profiles, Undefined): profiles = ['default'] @@ -23,19 +25,23 @@ def build( default_profile_path=os.path.join(os.path.dirname(__file__), 'templates', 'default_build_profile.toml'), ) - print(profile_o.merged_profile_path) + args = [ + 'idf-build-apps', + 'build', + '-p', + *paths, + '-t', + target, + '--config-file', + profile_o.merged_profile_path, + '-v', + '--parallel-count', + str(parallel_count), + '--parallel-index', + str(parallel_index), + ] subprocess.run( - [ - 'idf-build-apps', - 'build', - '-p', - *paths, - '-t', - target, - '--config-file', - profile_o.merged_profile_path, - '-vvv', - ], + args, check=True, ) diff --git a/idf_ci/settings.py b/idf_ci/settings.py index 4427288..e24d5e0 100644 --- a/idf_ci/settings.py +++ b/idf_ci/settings.py @@ -9,13 +9,14 @@ from pydantic_settings import ( BaseSettings, PydanticBaseSettingsSource, - SettingsConfigDict, TomlConfigSettingsSource, ) # noinspection PyDataclass class CiSettings(BaseSettings): + CONFIG_FILE_PATH: t.ClassVar[t.Optional[Path]] = None + component_mapping_regexes: t.List[str] = [ '/components/(.+)/', '/common_components/(.+)/', @@ -31,11 +32,8 @@ class CiSettings(BaseSettings): ] extend_component_ignored_file_extensions: t.List[str] = [] - build_profile: str = 'default' # or your custom profile path - - model_config = SettingsConfigDict( - toml_file='.idf_ci.toml', - ) + build_profiles: t.List[str] = ['default'] + test_profiles: t.List[str] = ['default'] @classmethod def settings_customise_sources( @@ -46,7 +44,13 @@ def settings_customise_sources( dotenv_settings: PydanticBaseSettingsSource, # noqa: ARG003 file_secret_settings: PydanticBaseSettingsSource, # noqa: ARG003 ) -> t.Tuple[PydanticBaseSettingsSource, ...]: - return TomlConfigSettingsSource(settings_cls), init_settings + sources: t.Tuple[PydanticBaseSettingsSource, ...] = (init_settings,) + if cls.CONFIG_FILE_PATH is None: + sources += (TomlConfigSettingsSource(settings_cls, '.idf_ci.toml'),) + else: + sources += (TomlConfigSettingsSource(settings_cls, cls.CONFIG_FILE_PATH),) + + return sources @property def all_component_mapping_regexes(self) -> t.Set[re.Pattern]: diff --git a/pyproject.toml b/pyproject.toml index 6be0393..04a9862 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,18 @@ Changelog = "https://github.com/espressif/idf-ci/blob/master/CHANGELOG.md" [project.optional-dependencies] test = ["pytest", "pytest-cov"] +doc = [ + "sphinx", + # theme + "sphinx-rtd-theme", + # extensions + "sphinx_copybutton", # copy button + "myst-parser", # markdown support + "sphinxcontrib-mermaid", # mermaid graph support + "sphinx-argparse", # auto-generate cli help message + "sphinx-tabs", # tabs +] + [project.scripts] idf-ci = "idf_ci.cli:cli" diff --git a/tests/conftest.py b/tests/conftest.py index 9096054..484c2c5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,6 +6,8 @@ import pytest from click.testing import CliRunner +from idf_ci import CiSettings + @pytest.fixture def runner(): @@ -15,3 +17,10 @@ def runner(): @pytest.fixture def temp_dir(tmp_path: Path) -> str: return str(tmp_path) + + +@pytest.fixture(autouse=True) +def reset_settings(): + CiSettings.CONFIG_FILE_PATH = None + + yield diff --git a/tests/test_settings.py b/tests/test_settings.py index b5adce7..852e854 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -2,26 +2,21 @@ # SPDX-License-Identifier: Apache-2.0 import os +import re -import pytest - +from idf_ci.cli import cli from idf_ci.settings import CiSettings -@pytest.fixture -def default_settings(): - return CiSettings() - - -def test_default_component_mapping_regexes(default_settings): +def test_default_component_mapping_regexes(): expected_regexes = [ '/components/(.+)/', '/common_components/(.+)/', ] - assert default_settings.component_mapping_regexes == expected_regexes + assert CiSettings().component_mapping_regexes == expected_regexes -def test_default_component_ignored_file_extensions(default_settings): +def test_default_component_ignored_file_extensions(): expected_extensions = [ '.md', '.rst', @@ -29,10 +24,10 @@ def test_default_component_ignored_file_extensions(default_settings): '.yml', '.py', ] - assert default_settings.component_ignored_file_extensions == expected_extensions + assert CiSettings().component_ignored_file_extensions == expected_extensions -def test_get_modified_components(default_settings): +def test_get_modified_components(): test_files = [ 'components/wifi/wifi.c', 'components/bt/bt_main.c', @@ -42,10 +37,10 @@ def test_get_modified_components(default_settings): ] expected_components = {'wifi', 'bt', 'esp_common'} - assert default_settings.get_modified_components(test_files) == expected_components + assert CiSettings().get_modified_components(test_files) == expected_components -def test_ignored_file_extensions(default_settings): +def test_ignored_file_extensions(): test_files = [ 'components/wifi/README.md', 'components/bt/docs.rst', @@ -54,7 +49,7 @@ def test_ignored_file_extensions(default_settings): 'components/utils/util.py', ] - assert default_settings.get_modified_components(test_files) == set() + assert CiSettings().get_modified_components(test_files) == set() def test_extended_component_mapping_regexes(): @@ -91,19 +86,8 @@ def test_extended_ignored_extensions(): assert settings.get_modified_components(test_files) == expected_components -def test_build_profile_default(): - settings = CiSettings() - assert settings.build_profile == 'default' - - -def test_build_profile_custom(): - custom_profile = 'custom_profile' - settings = CiSettings(build_profile=custom_profile) - assert settings.build_profile == custom_profile - - -def test_all_component_mapping_regexes(default_settings): - patterns = default_settings.all_component_mapping_regexes +def test_all_component_mapping_regexes(): + patterns = CiSettings().all_component_mapping_regexes assert len(patterns) == 2 test_path = '/components/test_component/test.c' @@ -114,7 +98,45 @@ def test_all_component_mapping_regexes(default_settings): assert match.group(1) == 'test_component' -def test_component_mapping_with_absolute_paths(default_settings): +def test_component_mapping_with_absolute_paths(): abs_path = os.path.abspath('components/wifi/wifi.c') - components = default_settings.get_modified_components([abs_path]) + components = CiSettings().get_modified_components([abs_path]) assert components == {'wifi'} + + +def test_ci_profile_option(temp_dir, runner): + # Create a custom CI profile file + custom_profile = os.path.join(temp_dir, 'custom_ci_profile.toml') + with open(custom_profile, 'w') as f: + f.write(""" +extend_component_mapping_regexes = [ + '/custom/path/(.+)/' +] + +component_ignored_file_extensions = [ + '.custom' +] +""") + + result = runner.invoke(cli, ['--ci-profile', custom_profile, 'build', 'run', '--paths', temp_dir]) + assert result.exit_code == 0 + assert f'Using CI profile: {custom_profile}' in result.output + assert CiSettings.CONFIG_FILE_PATH == custom_profile + assert len(CiSettings().all_component_mapping_regexes) == 3 # default got 2 + + CiSettings.CONFIG_FILE_PATH = None # reset + + # Test with non-existent profile + non_existent = os.path.join(temp_dir, 'non_existent.toml') + result = runner.invoke(cli, ['--ci-profile', non_existent]) + assert result.exit_code == 2 # Click returns 2 for parameter validation errors + assert re.search(r"Error: Invalid value for '--ci-profile': File .* does not exist.", result.output) + assert len(CiSettings().all_component_mapping_regexes) == 2 # default got 2 + + +def test_ci_profile_not_specified(runner): + # Test default behavior when no profile is specified + original_config_path = CiSettings.CONFIG_FILE_PATH + result = runner.invoke(cli, ['build', 'init-profile']) + assert result.exit_code == 0 + assert CiSettings.CONFIG_FILE_PATH == original_config_path