From bf2d8faebbab4ca86d406a93519ecad2d7d60bd0 Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Mon, 2 Mar 2026 08:44:50 +0100 Subject: [PATCH] test(esp_common): Add Linux host test for portable section macros Add a test app that verifies PLACE_IN_SECTION, _SECTION_ATTR_SYMBOL_DECL_GENERIC, _SECTION_START and _SECTION_END macros work correctly on Linux. The test places 5 uint32_t values into a custom .test_data_table section from two separate translation units, then iterates the section at runtime to verify the correct count and content of all entries. Includes: - Custom linker script (ld/test_section.ld) - Build-test-rules entry (linux only) - pytest host_test marker --- components/esp_common/include/esp_attr.h | 13 ++- .../test_apps/.build-test-rules.yml | 6 ++ .../test_apps/section_macros/CMakeLists.txt | 10 +++ .../section_macros/ld/test_section.ld | 19 +++++ .../section_macros/main/CMakeLists.txt | 11 +++ .../section_macros/main/test_entries_a.c | 16 ++++ .../section_macros/main/test_entries_b.c | 16 ++++ .../section_macros/main/test_section_macros.c | 81 +++++++++++++++++++ .../section_macros/pytest_section_macros.py | 19 +++++ .../section_macros/sdkconfig.defaults | 1 + 10 files changed, 185 insertions(+), 7 deletions(-) create mode 100644 components/esp_common/test_apps/section_macros/CMakeLists.txt create mode 100644 components/esp_common/test_apps/section_macros/ld/test_section.ld create mode 100644 components/esp_common/test_apps/section_macros/main/CMakeLists.txt create mode 100644 components/esp_common/test_apps/section_macros/main/test_entries_a.c create mode 100644 components/esp_common/test_apps/section_macros/main/test_entries_b.c create mode 100644 components/esp_common/test_apps/section_macros/main/test_section_macros.c create mode 100644 components/esp_common/test_apps/section_macros/pytest_section_macros.py create mode 100644 components/esp_common/test_apps/section_macros/sdkconfig.defaults diff --git a/components/esp_common/include/esp_attr.h b/components/esp_common/include/esp_attr.h index 19a58fa6f0..101746a93a 100644 --- a/components/esp_common/include/esp_attr.h +++ b/components/esp_common/include/esp_attr.h @@ -248,13 +248,13 @@ FORCE_INLINE_ATTR TYPE& operator<<=(TYPE& a, int b) { a = a << b; return a; } #if defined(__APPLE__) && defined(__MACH__) /* ---------- macOS (Mach-O) ---------- */ #include +#include #include -#define PLACE_IN_SECTION(SECTION) \ +#define _SECTION_ATTR_IMPL_GENERIC(SECTION, COUNTER) \ __attribute__((used, aligned(4), section("__DATA," SECTION))) -#define _SECTION_ATTR_IMPL_GENERIC(SECTION, COUNTER) \ - __attribute__((aligned(4), section("__DATA," SECTION))) +#define PLACE_IN_SECTION(SECTION) _SECTION_ATTR_IMPL_GENERIC(SECTION, __COUNTER__) #define _SECTION_ATTR_SYMBOL_DECL_GENERIC(TYPE, SECTION_NAME) \ static const TYPE *_##SECTION_NAME##_start_ptr; \ @@ -278,11 +278,10 @@ FORCE_INLINE_ATTR TYPE& operator<<=(TYPE& a, int b) { a = a << b; return a; } #else /* ELF targets (Linux and embedded) */ -#define PLACE_IN_SECTION(SECTION) \ - __attribute__((used, aligned(4), section("." SECTION))) - #define _SECTION_ATTR_IMPL_GENERIC(SECTION, COUNTER) \ - __attribute__((aligned(4), section("." SECTION "." _COUNTER_STRINGIFY(COUNTER)))) + __attribute__((used, aligned(4), section("." SECTION "." _COUNTER_STRINGIFY(COUNTER)))) + +#define PLACE_IN_SECTION(SECTION) _SECTION_ATTR_IMPL_GENERIC(SECTION, __COUNTER__) #define _SECTION_ATTR_SYMBOL_DECL_GENERIC(TYPE, SECTION_NAME) \ extern TYPE _##SECTION_NAME##_start; \ diff --git a/components/esp_common/test_apps/.build-test-rules.yml b/components/esp_common/test_apps/.build-test-rules.yml index 11ecc24852..8e8e2c7e6c 100644 --- a/components/esp_common/test_apps/.build-test-rules.yml +++ b/components/esp_common/test_apps/.build-test-rules.yml @@ -8,3 +8,9 @@ components/esp_common/test_apps/esp_common: depends_components: - esp_common - esp_system # Defines the section placement for attributes + +components/esp_common/test_apps/section_macros: + enable: + - if: IDF_TARGET == "linux" + depends_components: + - esp_common diff --git a/components/esp_common/test_apps/section_macros/CMakeLists.txt b/components/esp_common/test_apps/section_macros/CMakeLists.txt new file mode 100644 index 0000000000..1b379bddac --- /dev/null +++ b/components/esp_common/test_apps/section_macros/CMakeLists.txt @@ -0,0 +1,10 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +set(COMPONENTS main) + +project(test_section_macros) diff --git a/components/esp_common/test_apps/section_macros/ld/test_section.ld b/components/esp_common/test_apps/section_macros/ld/test_section.ld new file mode 100644 index 0000000000..926d9557c5 --- /dev/null +++ b/components/esp_common/test_apps/section_macros/ld/test_section.ld @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * Linker script for the section macros test on Linux host builds. + * Collects all uint32_t entries placed in the .test_data_table section. + */ + +SECTIONS +{ + .test_data_table : + { + PROVIDE(_test_data_table_start = .); + KEEP(*(SORT(.test_data_table*))) + PROVIDE(_test_data_table_end = .); + } +} +INSERT AFTER .data; diff --git a/components/esp_common/test_apps/section_macros/main/CMakeLists.txt b/components/esp_common/test_apps/section_macros/main/CMakeLists.txt new file mode 100644 index 0000000000..a3c2f54ed6 --- /dev/null +++ b/components/esp_common/test_apps/section_macros/main/CMakeLists.txt @@ -0,0 +1,11 @@ +idf_component_register(SRCS "test_section_macros.c" "test_entries_a.c" "test_entries_b.c" + INCLUDE_DIRS "." + WHOLE_ARCHIVE) + +if(CONFIG_IDF_TARGET_LINUX AND NOT CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") + # Add linker script that collects the .test_data_table section + # On macOS, custom sections are handled via Mach-O __DATA segments + # and do not require linker scripts (which use GNU ld -T syntax). + target_link_options(${COMPONENT_LIB} INTERFACE + "-Wl,-T,${CMAKE_CURRENT_LIST_DIR}/../ld/test_section.ld") +endif() diff --git a/components/esp_common/test_apps/section_macros/main/test_entries_a.c b/components/esp_common/test_apps/section_macros/main/test_entries_a.c new file mode 100644 index 0000000000..4a3a16f07a --- /dev/null +++ b/components/esp_common/test_apps/section_macros/main/test_entries_a.c @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_attr.h" +#include + +/* + * Three entries placed in the test_data_table section from translation unit A. + * The test verifies these values are discoverable at link time. + */ +static const uint32_t entry_a1 PLACE_IN_SECTION("test_data_table") = 0xDEADBEEF; +static const uint32_t entry_a2 PLACE_IN_SECTION("test_data_table") = 0xCAFEBABE; +static const uint32_t entry_a3 PLACE_IN_SECTION("test_data_table") = 0x12345678; diff --git a/components/esp_common/test_apps/section_macros/main/test_entries_b.c b/components/esp_common/test_apps/section_macros/main/test_entries_b.c new file mode 100644 index 0000000000..bad3e70abc --- /dev/null +++ b/components/esp_common/test_apps/section_macros/main/test_entries_b.c @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_attr.h" +#include + +/* + * Two entries placed in the test_data_table section from a DIFFERENT + * translation unit. This verifies the linker collects entries across + * multiple object files. + */ +static const uint32_t entry_b1 PLACE_IN_SECTION("test_data_table") = 0xAAAAAAAA; +static const uint32_t entry_b2 PLACE_IN_SECTION("test_data_table") = 0x55555555; diff --git a/components/esp_common/test_apps/section_macros/main/test_section_macros.c b/components/esp_common/test_apps/section_macros/main/test_section_macros.c new file mode 100644 index 0000000000..c55f810e91 --- /dev/null +++ b/components/esp_common/test_apps/section_macros/main/test_section_macros.c @@ -0,0 +1,81 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "esp_attr.h" + +/* + * Test for the portable link-time section macros defined in esp_attr.h. + * + * Two separate translation units (test_entries_a.c and test_entries_b.c) + * place uint32_t values into the "test_data_table" section using + * PLACE_IN_SECTION(). This main function uses _SECTION_ATTR_SYMBOL_DECL_GENERIC + * and _SECTION_START/_SECTION_END to iterate the collected entries and verify: + * 1. The total count matches the expected number (5 entries). + * 2. Every expected value is present in the section. + */ + +_SECTION_ATTR_SYMBOL_DECL_GENERIC(uint32_t, test_data_table) + +/* Expected values — order is linker-determined, so we check membership */ +static const uint32_t expected_values[] = { + 0xDEADBEEF, + 0xCAFEBABE, + 0x12345678, + 0xAAAAAAAA, + 0x55555555, +}; + +#define EXPECTED_COUNT (sizeof(expected_values) / sizeof(expected_values[0])) + +void app_main(void) +{ + const uint32_t *start = _SECTION_START(test_data_table); + const uint32_t *end = _SECTION_END(test_data_table); + + /* --- Check 1: entry count -------------------------------------------- */ + size_t count = (size_t)(end - start); + printf("Section entry count: %zu (expected %zu)\n", count, (size_t)EXPECTED_COUNT); + + if (count != EXPECTED_COUNT) { + printf("FAIL: entry count mismatch\n"); + exit(1); + } + + /* --- Check 2: every expected value is present ------------------------ */ + for (size_t i = 0; i < EXPECTED_COUNT; i++) { + int found = 0; + for (size_t j = 0; j < count; j++) { + if (start[j] == expected_values[i]) { + found = 1; + break; + } + } + if (!found) { + printf("FAIL: expected value 0x%08X not found in section\n", expected_values[i]); + exit(1); + } + } + + /* --- Check 3: no unexpected values ----------------------------------- */ + for (size_t j = 0; j < count; j++) { + int known = 0; + for (size_t i = 0; i < EXPECTED_COUNT; i++) { + if (start[j] == expected_values[i]) { + known = 1; + break; + } + } + if (!known) { + printf("FAIL: unexpected value 0x%08X found in section\n", start[j]); + exit(1); + } + } + + printf("SUCCESS: All %zu section entries verified.\n", count); +} diff --git a/components/esp_common/test_apps/section_macros/pytest_section_macros.py b/components/esp_common/test_apps/section_macros/pytest_section_macros.py new file mode 100644 index 0000000000..25d26dca70 --- /dev/null +++ b/components/esp_common/test_apps/section_macros/pytest_section_macros.py @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.host_test +@idf_parametrize('target', ['linux'], indirect=['target']) +def test_section_macros(dut: Dut) -> None: + dut.expect('SUCCESS: All 5 section entries verified.') + + +@pytest.mark.host_test +@pytest.mark.macos +@idf_parametrize('target', ['linux'], indirect=['target']) +def test_section_macros_macos(dut: Dut) -> None: + dut.expect('SUCCESS: All 5 section entries verified.') diff --git a/components/esp_common/test_apps/section_macros/sdkconfig.defaults b/components/esp_common/test_apps/section_macros/sdkconfig.defaults new file mode 100644 index 0000000000..9b39f10b99 --- /dev/null +++ b/components/esp_common/test_apps/section_macros/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_IDF_TARGET="linux"