# 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'