diff --git a/tools/test_build_system/buildv2/test_project.py b/tools/test_build_system/buildv2/test_project.py new file mode 100644 index 0000000000..92d40dd660 --- /dev/null +++ b/tools/test_build_system/buildv2/test_project.py @@ -0,0 +1,118 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +import json +import logging +import subprocess +from pathlib import Path + +import pytest +from test_build_system_helpers import IdfPyFunc +from test_build_system_helpers import replace_in_file + + +@pytest.mark.usefixtures('test_app_copy') +def test_project_init_explicit_steps_without_binary(idf_py: IdfPyFunc) -> None: + """idf_project_init() + idf_build_executable() produces ELF but no .bin when idf_build_binary() is omitted.""" + logging.info('Testing idf_project_init with explicit steps but without idf_build_binary') + + replace_in_file( + 'CMakeLists.txt', + 'idf_project_default()', + 'idf_project_init()\n' + 'idf_build_executable(build_test_app COMPONENTS main)\n' + 'idf_build_generate_metadata(EXECUTABLE build_test_app)\n', + ) + + idf_py('build') + assert Path('build/project_description.json').exists(), 'project_description.json not produced' + assert Path('build/build_test_app.elf').exists(), 'ELF not created despite idf_build_executable()' + assert not Path('build/build_test_app.bin').exists(), '.bin should not exist when idf_build_binary() is omitted' + + +@pytest.mark.usefixtures('test_app_copy') +def test_project_default_produces_all_artifacts(idf_py: IdfPyFunc) -> None: + """idf_project_default() should produce ELF, .bin, project_description.json, + flasher_args.json, and standard build targets (menuconfig, uf2, size).""" + logging.info('Testing idf_project_default full artifact generation') + + idf_py('build') + + assert Path('build/build_test_app.elf').exists(), 'ELF not produced' + assert Path('build/build_test_app.bin').exists(), '.bin not produced' + assert Path('build/project_description.json').exists(), 'project_description.json not produced' + assert Path('build/flasher_args.json').exists(), 'flasher_args.json not produced' + + flasher_args = json.loads(Path('build/flasher_args.json').read_text()) + assert 'flash_files' in flasher_args, 'flasher_args.json should contain flash_files' + + ninja_targets_result = subprocess.run( + ['ninja', '-t', 'targets', 'all'], + capture_output=True, + text=True, + cwd='build', + check=True, + ) + ninja_targets = ninja_targets_result.stdout + for target in ( + 'menuconfig', + 'confserver', + 'save-defconfig', + 'config-report', + 'uf2', + 'uf2-app', + 'size', + 'app-flash', + ): + assert f'{target}:' in ninja_targets, f'"{target}" target not found in ninja targets' + + +@pytest.mark.usefixtures('test_app_copy') +def test_project_init_requires_cmake_project(idf_py: IdfPyFunc) -> None: + """idf_project_init() called before cmake project() should fail.""" + logging.info('Testing idf_project_init before project() fails') + + replace_in_file('CMakeLists.txt', '# placeholder_after_include_project_cmake', 'idf_project_init()\n') + replace_in_file('CMakeLists.txt', 'idf_project_default()', '# project_default removed for ordering test\n') + + cmake_content = Path('CMakeLists.txt').read_text() + assert 'idf_project_init()' in cmake_content, 'replace_in_file did not inject idf_project_init()' + + with pytest.raises(subprocess.CalledProcessError): + idf_py('reconfigure') + + +@pytest.mark.usefixtures('test_app_copy') +def test_build_generate_flasher_args(idf_py: IdfPyFunc) -> None: + """idf_build_generate_flasher_args() generates flasher_args.json with correct content.""" + logging.info('Testing idf_build_generate_flasher_args flasher_args.json generation') + + replace_in_file( + 'CMakeLists.txt', + 'idf_project_default()', + 'idf_project_init()\n' + 'idf_build_executable(build_test_app COMPONENTS main)\n' + 'idf_build_binary(build_test_app\n' + ' OUTPUT_FILE "${CMAKE_BINARY_DIR}/build_test_app.bin"\n' + ' TARGET build_test_app_binary)\n' + 'idf_flash_binary(build_test_app_binary\n' + ' TARGET app-flash\n' + ' NAME "app"\n' + ' FLASH)\n' + 'idf_check_binary_size(build_test_app_binary)\n' + 'add_custom_target(app ALL DEPENDS build_test_app_binary)\n' + 'idf_build_generate_metadata(BINARY build_test_app_binary)\n' + 'idf_build_generate_flasher_args()\n', + ) + + idf_py('build') + + # Verify flasher_args.json is created + flasher_args_path = Path('build/flasher_args.json') + assert flasher_args_path.exists(), 'flasher_args.json not generated by idf_build_generate_flasher_args' + + # Verify flasher_args.json contains expected structure + flasher_args = json.loads(flasher_args_path.read_text()) + assert 'flash_files' in flasher_args, 'flasher_args.json should contain flash_files key' + assert 'flash_settings' in flasher_args, 'flasher_args.json should contain flash_settings key' + assert 'extra_esptool_args' in flasher_args, 'flasher_args.json should contain extra_esptool_args' + assert 'chip' in flasher_args.get('extra_esptool_args', {}), 'extra_esptool_args should contain chip information'