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
This commit is contained in:
Guillaume Souchere
2026-03-02 08:44:50 +01:00
parent 0549755e37
commit bf2d8faebb
10 changed files with 185 additions and 7 deletions
+6 -7
View File
@@ -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 <mach-o/getsect.h>
#include <mach-o/ldsyms.h>
#include <mach-o/dyld.h>
#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; \
@@ -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
@@ -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)
@@ -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;
@@ -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()
@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_attr.h"
#include <stdint.h>
/*
* 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;
@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_attr.h"
#include <stdint.h>
/*
* 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;
@@ -0,0 +1,81 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#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);
}
@@ -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.')
@@ -0,0 +1 @@
CONFIG_IDF_TARGET="linux"