Files
Frantisek Hrbata 2abd858e61 feat(test_build_system): allow buildv2 specific tests
Currently, cmakev2 is being tested only in backward-compatible mode by
using the existing cmakev1 tests with the cmakev2 test application. We
also need to add tests specific to cmakev2, and it is convenient to
reuse the existing build system testing framework. Let's add a `buildv2`
subdirectory to the existing `tools/test_build_system` directory and use
the `pytest_collection_modifyitems` hook to ignore tests in this
directory unless the `--buildv2` option is used.

Without the `--buildv2` option, only the existing cmakev1 tests are
executed and tests in `buildv2` directory are skipped. With the
`--buildv2` option, the existing cmakev1 tests run with the cmakev2
testing application for backward compatibility testing, and all cmakev2
tests within the `buildv2` subdirectory are also executed.

Note: we cannot use the `pytest_ignore_collect` hook, because the
`--buildv2` option is not known to the pytest, so the
`config.getoption('--buildv2', False)` returns always False. We would
likely need to add the `--buildv2` option in the conftest.py in the
esp-idf root directory.

Signed-off-by: Frantisek Hrbata <frantisek.hrbata@espressif.com>

fix: lsadjf las jflasjfl aslfsald asl fsadlf sladsal jfsadfas

Signed-off-by: Frantisek Hrbata <frantisek.hrbata@espressif.com>
2026-03-20 08:13:26 +01:00

264 lines
9.4 KiB
Python

# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import datetime
import logging
import os
import shutil
import subprocess
import typing
from pathlib import Path
from tempfile import mkdtemp
import pytest
from _pytest.config import Config
from _pytest.fixtures import FixtureRequest
from _pytest.main import Session
from _pytest.nodes import Item
from test_build_system_helpers import EXT_IDF_PATH
from test_build_system_helpers import EnvDict
from test_build_system_helpers import IdfPyFunc
from test_build_system_helpers import get_idf_build_env
from test_build_system_helpers import run_idf_py
# Pytest hook used to check if the test has passed or failed, from a fixture.
# Based on https://docs.pytest.org/en/latest/example/simple.html#making-test-result-information-available-in-fixtures
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item: typing.Any, call: typing.Any) -> typing.Generator[None, pytest.TestReport, None]: # pylint: disable=unused-argument
outcome = yield # Execute all other hooks to obtain the report object
report = outcome.get_result()
if report.when == 'call' and report.passed:
# set an attribute which can be checked using 'should_clean_test_dir' function below
setattr(item, 'passed', True)
def should_clean_test_dir(request: FixtureRequest) -> bool:
# Only remove the test directory if the test has passed
return getattr(request.node, 'passed', False) or request.config.getoption('cleanup_idf_copy', False)
def pytest_addoption(parser: pytest.Parser) -> None:
parser.addoption(
'--work-dir',
action='store',
default=None,
help='Directory for temporary files. If not specified, an OS-specific temporary directory will be used.',
)
parser.addoption(
'--cleanup-idf-copy',
action='store_true',
help='Always clean up the IDF copy after the test. By default, the copy is cleaned up only if the test passes.',
)
parser.addoption(
'--buildv2',
action='store_true',
help='Use the IDF build system v2 project for testing.',
)
@pytest.fixture(scope='session')
def _session_work_dir(request: FixtureRequest) -> typing.Generator[tuple[Path, bool], None, None]:
work_dir = request.config.getoption('--work-dir')
if work_dir:
work_dir = os.path.join(work_dir, datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d_%H-%M-%S'))
logging.debug(f'using work directory: {work_dir}')
os.makedirs(work_dir, exist_ok=True)
clean_dir = None
is_temp_dir = False
else:
work_dir = mkdtemp()
logging.debug(f'created temporary work directory: {work_dir}')
clean_dir = work_dir
is_temp_dir = True
# resolve allows using relative paths with --work-dir option
yield Path(work_dir).resolve(), is_temp_dir
if clean_dir:
logging.debug(f'cleaning up {clean_dir}')
shutil.rmtree(clean_dir, ignore_errors=True)
@pytest.fixture(name='func_work_dir', autouse=True)
def work_dir(request: FixtureRequest, _session_work_dir: tuple[Path, bool]) -> typing.Generator[Path, None, None]:
session_work_dir, is_temp_dir = _session_work_dir
if request._pyfuncitem.keywords.get('force_temp_work_dir') and not is_temp_dir:
work_dir = Path(mkdtemp()).resolve()
logging.debug('Force using temporary work directory')
clean_dir = work_dir
else:
work_dir = session_work_dir
clean_dir = None
# resolve allows using relative paths with --work-dir option
yield work_dir
if clean_dir:
logging.debug(f'cleaning up {clean_dir}')
shutil.rmtree(clean_dir, ignore_errors=True)
@pytest.fixture
def test_app_copy(func_work_dir: Path, request: FixtureRequest) -> typing.Generator[Path, None, None]:
# by default, use hello_world app and copy it to a temporary directory with
# the name resembling that of the test
if request.config.getoption('buildv2', False):
copy_from = 'tools/test_build_system/buildv2_test_app'
else:
copy_from = 'tools/test_build_system/build_test_app'
# sanitize test name in case pytest.mark.parametrize was used
test_name_sanitized = request.node.name.replace('[', '_').replace(']', '')
copy_to = test_name_sanitized + '_app'
# allow overriding source and destination via pytest.mark.test_app_copy()
mark = request.node.get_closest_marker('test_app_copy')
if mark:
copy_from = mark.args[0]
if len(mark.args) > 1:
copy_to = mark.args[1]
path_from = Path(os.environ['IDF_PATH']) / copy_from
path_to = func_work_dir / copy_to
# if the new directory inside the original directory,
# make sure not to go into recursion.
ignore = shutil.ignore_patterns(
path_to.name,
# also ignore files which may be present in the work directory
'build',
'sdkconfig',
)
logging.debug(f'copying {path_from} to {path_to}')
shutil.copytree(path_from, path_to, ignore=ignore, symlinks=True)
old_cwd = Path.cwd()
os.chdir(path_to)
yield Path(path_to)
os.chdir(old_cwd)
if should_clean_test_dir(request):
logging.debug(f'cleaning up work directory after a successful test: {path_to}')
shutil.rmtree(path_to, ignore_errors=True)
@pytest.fixture
def test_git_template_app(func_work_dir: Path, request: FixtureRequest) -> typing.Generator[Path, None, None]:
# sanitize test name in case pytest.mark.parametrize was used
test_name_sanitized = request.node.name.replace('[', '_').replace(']', '')
copy_to = test_name_sanitized + '_app'
path_to = func_work_dir / copy_to
logging.debug(f'cloning git-template app to {path_to}')
path_to.mkdir()
# No need to clone full repository, just a single master branch
subprocess.run(
[
'git',
'clone',
'--single-branch',
'-b',
'master',
'--depth',
'1',
'https://github.com/espressif/esp-idf-template.git',
'.',
],
cwd=path_to,
capture_output=True,
)
old_cwd = Path.cwd()
os.chdir(path_to)
yield Path(path_to)
os.chdir(old_cwd)
if should_clean_test_dir(request):
logging.debug(f'cleaning up work directory after a successful test: {path_to}')
shutil.rmtree(path_to, ignore_errors=True)
@pytest.fixture
def idf_copy(func_work_dir: Path, request: FixtureRequest) -> typing.Generator[Path, None, None]:
# sanitize test name in case pytest.mark.parametrize was used
test_name_sanitized = request.node.name.replace('[', '_').replace(']', '')
copy_to = test_name_sanitized + '_idf'
# allow overriding the destination via pytest.mark.idf_copy_with_space so the destination contain space
mark_with_space = request.node.get_closest_marker('idf_copy_with_space')
if mark_with_space:
copy_to = test_name_sanitized + ' idf'
# allow overriding the destination via pytest.mark.idf_copy()
mark = request.node.get_closest_marker('idf_copy')
if mark:
copy_to = mark.args[0]
path_from = EXT_IDF_PATH
path_to = func_work_dir / copy_to
# if the new directory inside the original directory,
# make sure not to go into recursion.
ignore = shutil.ignore_patterns(
path_to.name,
# also ignore the build directories which may be quite large
# plus ignore .git since it is causing trouble when removing on Windows
'**/build',
'.git',
)
logging.debug(f'copying {path_from} to {path_to}')
shutil.copytree(path_from, path_to, ignore=ignore, symlinks=True)
orig_idf_path = os.environ['IDF_PATH']
os.environ['IDF_PATH'] = str(path_to)
yield Path(path_to)
os.environ['IDF_PATH'] = orig_idf_path
if should_clean_test_dir(request):
logging.debug(f'cleaning up work directory after a successful test: {path_to}')
shutil.rmtree(path_to, ignore_errors=True)
@pytest.fixture(name='default_idf_env')
def fixture_default_idf_env() -> EnvDict:
return get_idf_build_env(os.environ['IDF_PATH']) # type: ignore
@pytest.fixture
def idf_py(default_idf_env: EnvDict) -> IdfPyFunc:
def result(*args: str, check: bool = True, input_str: str | None = None) -> subprocess.CompletedProcess:
return run_idf_py(*args, env=default_idf_env, workdir=os.getcwd(), check=check, input_str=input_str) # type: ignore
return result
def pytest_collection_modifyitems(session: Session, config: Config, items: list[Item]) -> None:
buildv2_dir = Path(__file__).parent / 'buildv2'
is_buildv2 = config.getoption('--buildv2', False)
for item in items:
if is_buildv2:
marker = item.get_closest_marker('buildv2_skip')
if marker:
reason = marker.args[0] if marker.args else 'Skipped as this test is specific to build system v1.'
item.add_marker(pytest.mark.skip(reason=reason))
else:
if buildv2_dir in item.path.parents or item.path == buildv2_dir:
item.add_marker(pytest.mark.skip(reason='Skipped as build system v2 tests are disabled.'))
def pytest_report_header(config: Config) -> str:
"""Add a clear header to the terminal output whether buildv1 or buildv2 testing is in progress."""
if config.getoption('--buildv2'):
return 'Testing ESP-IDF CMake-based build system v2'
else:
return 'Testing ESP-IDF CMake-based build system v1'