Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/zephyr-unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,12 @@ jobs:
run: |
cd workspace
export ZEPHYR_TOOLCHAIN_VARIANT=host/llvm
west twister --testsuite-root sof/test/ztest/unit/ --platform native_sim --verbose \
--inline-logs
# DEBUGINFOD_URLS must stay empty: otherwise llvm-symbolizer queries
# debuginfod while LSan prints a leak report, which takes longer than
# twister's post-verdict grace period. Twister then kills the binary,
# ignores its exit code and reports the leaking suite as passed.
DEBUGINFOD_URLS= west twister --testsuite-root sof/test/ztest/unit/ \
--platform native_sim --enable-asan --enable-lsan --verbose --inline-logs
# This part is commented out because it is not needed at the moment.
# - name: Install Zephyr SDK
# run: |
Expand Down
212 changes: 212 additions & 0 deletions test/ztest/unit/common/mod_alloc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// SPDX-License-Identifier: BSD-3-Clause
//
// Copyright(c) 2026 Intel Corporation.
//
// These contents may have been developed with support from one or more
// Intel-operated generative artificial intelligence solutions.
//
// Module resource allocator for ztest unit suites, see mod_alloc.h.

#include <zephyr/ztest.h>

#include <rtos/alloc.h>
#include <sof/audio/module_adapter/module/generic.h>

#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#include "mod_alloc.h"

/*
* The definitions below are named after the mod_alloc() API, not after its
* z_impl_*() back ends. generic.h only maps the two onto each other without
* CONFIG_USERSPACE; with userspace the API becomes a set of __syscall
* declarations and these definitions would no longer replace it.
*/
BUILD_ASSERT(!IS_ENABLED(CONFIG_USERSPACE),
"the ztest module allocator does not implement the mod_alloc() syscalls");

/** @brief Book-keeping entry for one live module allocation. */
struct mod_alloc_test_block {
const void *ptr;
size_t size;
};

static struct mod_alloc_test_block mod_alloc_blocks[MOD_ALLOC_TEST_MAX_ALLOCATIONS];
static size_t mod_alloc_count;
static size_t mod_alloc_bytes;

/**
* @brief Record an allocation.
* @param ptr Allocated pointer.
* @param size Requested size in bytes.
* @return true when the allocation was recorded, false when the table is full.
*/
static bool mod_alloc_block_add(const void *ptr, size_t size)
{
if (mod_alloc_count == ARRAY_SIZE(mod_alloc_blocks)) {
return false;
}

mod_alloc_blocks[mod_alloc_count].ptr = ptr;
mod_alloc_blocks[mod_alloc_count].size = size;
mod_alloc_count++;
mod_alloc_bytes += size;

return true;
}

/**
* @brief Drop the record of an allocation.
* @param ptr Pointer to look up.
* @return true when @p ptr was a live allocation, false otherwise.
*/
static bool mod_alloc_block_del(const void *ptr)
{
size_t i;

for (i = 0; i < mod_alloc_count; i++) {
if (mod_alloc_blocks[i].ptr != ptr) {
continue;
}

mod_alloc_bytes -= mod_alloc_blocks[i].size;
mod_alloc_count--;
mod_alloc_blocks[i] = mod_alloc_blocks[mod_alloc_count];

return true;
}

return false;
}

/**
* @brief Allocate and record memory, the back end of the mod_alloc() API.
* @param flags SOF_MEM_FLAG_* allocation flags.
* @param size Size in bytes, zero is rejected like in the production allocator.
* @param alignment Alignment in bytes, 0 for the default.
* @return Pointer to the allocated memory or NULL on failure.
*/
static void *mod_alloc_test_alloc(uint32_t flags, size_t size, size_t alignment)
{
void *ptr;

if (!size) {
return NULL;
}

ptr = sof_ctx_alloc(NULL, flags, size, alignment);
if (!ptr) {
return NULL;
}

if (!mod_alloc_block_add(ptr, size)) {
sof_ctx_free(NULL, ptr);
return NULL;
}

return ptr;
}

/**
* @brief Allocate an aligned buffer for a module.
*
* Uses the same flags as z_impl_mod_balloc_align().
*
* @param mod Module the memory is allocated for, only used for book-keeping.
* @param size Size in bytes.
* @param alignment Alignment in bytes, 0 for the default.
* @return Pointer to the allocated memory or NULL on failure.
*/
void *mod_balloc_align(struct processing_module *mod, size_t size, size_t alignment)
{
(void)mod;

return mod_alloc_test_alloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_LARGE_BUFFER, size, alignment);
}

/**
* @brief Allocate memory for a module with explicit allocator flags.
* @param mod Module the memory is allocated for, only used for book-keeping.
* @param flags SOF_MEM_FLAG_* allocation flags.
* @param size Size in bytes.
* @param alignment Alignment in bytes, 0 for the default.
* @return Pointer to the allocated memory or NULL on failure.
*/
void *mod_alloc_ext(struct processing_module *mod, uint32_t flags, size_t size, size_t alignment)
{
(void)mod;

return mod_alloc_test_alloc(flags, size, alignment);
}

/**
* @brief Free memory allocated with mod_alloc_ext() or mod_balloc_align().
* @param mod Module the memory was allocated for, only used for book-keeping.
* @param ptr Pointer to free, NULL is ignored.
* @return 0 on success, -EINVAL if @p ptr is not a live module allocation.
*/
int mod_free(struct processing_module *mod, const void *ptr)
{
(void)mod;

if (!ptr) {
return 0;
}

if (!mod_alloc_block_del(ptr)) {
return -EINVAL;
}

sof_ctx_free(NULL, (void *)ptr);

return 0;
}

/**
* @brief Free every allocation still held by a module.
* @param mod Module to release, this allocator does not track ownership so all
* live allocations are released.
*/
void mod_free_all(struct processing_module *mod)
{
(void)mod;

mod_alloc_test_free_all();
}

size_t mod_alloc_test_allocations(void)
{
return mod_alloc_count;
}

size_t mod_alloc_test_bytes(void)
{
return mod_alloc_bytes;
}

void mod_alloc_test_free_all(void)
{
while (mod_alloc_count) {
mod_alloc_count--;
sof_ctx_free(NULL, (void *)mod_alloc_blocks[mod_alloc_count].ptr);
}

mod_alloc_bytes = 0;
}

void mod_alloc_test_after(void *fixture)
{
size_t leaked = mod_alloc_count;
size_t leaked_bytes = mod_alloc_bytes;

(void)fixture;

/* Release the leak so that the next test case starts from a clean heap */
mod_alloc_test_free_all();

zassert_equal(leaked, 0, "Test case leaked %zu module allocation(s), %zu bytes", leaked,
leaked_bytes);
}
73 changes: 73 additions & 0 deletions test/ztest/unit/common/mod_alloc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright(c) 2026 Intel Corporation.
*
* These contents may have been developed with support from one or more
* Intel-operated generative artificial intelligence solutions.
*/

#ifndef __TEST_ZTEST_UNIT_COMMON_MOD_ALLOC_H__
#define __TEST_ZTEST_UNIT_COMMON_MOD_ALLOC_H__

#include <stddef.h>

/**
* @file
*
* Module resource allocator for ztest unit suites that exercise code calling
* the mod_alloc()/mod_free() API but do not link the production allocator in
* src/audio/module_adapter/module/generic.c (which pulls in the whole module
* adapter, IPC and objpool infrastructure).
*
* The implementation in mod_alloc.c forwards every allocation to the SOF heap
* through sof_ctx_alloc()/sof_ctx_free(), so a suite exercises whichever SOF
* allocator it links, for example the real zephyr/lib/alloc.c in either its
* Zephyr sys_heap or its native_sim host heap
* (CONFIG_SOF_NATIVE_SIM_HOST_HEAP) flavour.
*
* Like the production allocator it tracks the live allocations, so
* mod_free_all() works and a suite can detect leaked, double-freed or foreign
* pointers on its own, without relying on ASan/LSan and the host heap. The
* tracking is not thread safe; unit suites are single threaded.
*/

/** @brief Number of live module allocations a suite may have at any time. */
#ifndef MOD_ALLOC_TEST_MAX_ALLOCATIONS
#define MOD_ALLOC_TEST_MAX_ALLOCATIONS 128
#endif

/**
* @brief Return the number of module allocations that are currently live.
* @return Number of pointers handed out and not yet freed.
*/
size_t mod_alloc_test_allocations(void);

/**
* @brief Return the number of bytes currently allocated by modules.
* @return Sum of the requested sizes of all live allocations.
*/
size_t mod_alloc_test_bytes(void);

/**
* @brief Free every live module allocation, whichever module owns it.
*
* Used by mod_alloc_test_after() to keep test cases independent and available
* to suites that install their own ztest teardown.
*/
void mod_alloc_test_free_all(void);

/**
* @brief ztest after-hook asserting that a test case leaked no module memory.
*
* Matches the ztest after_fn signature, so a suite can install it directly:
*
* ZTEST_SUITE(my_suite, NULL, NULL, NULL, mod_alloc_test_after, NULL);
*
* Any leaked allocation is freed before the assertion, so one failing test
* case does not cascade into the following ones.
*
* @param fixture ztest suite fixture, unused.
*/
void mod_alloc_test_after(void *fixture);

#endif /* __TEST_ZTEST_UNIT_COMMON_MOD_ALLOC_H__ */
34 changes: 34 additions & 0 deletions test/ztest/unit/common/sof_context.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: BSD-3-Clause
//
// Copyright(c) 2026 Intel Corporation.
//
// These contents may have been developed with support from one or more
// Intel-operated generative artificial intelligence solutions.
//
// Minimal SOF context for standalone ztest applications that link SOF code
// calling sof_get(), for example src/audio/component.c, but do not link
// src/init/init.c. The context is created on first use and only its component
// driver list is initialised, which is what buffer_new() and the component
// infrastructure need.

#include <rtos/sof.h>
#include <sof/audio/component_ext.h>

#include <stdbool.h>

static struct sof sof_context;
static bool sof_context_initialized;

/**
* @brief Return the minimal SOF context used by ztest unit suites.
* @return Pointer to the singleton test SOF context.
*/
struct sof *sof_get(void)
{
if (!sof_context_initialized) {
sys_comp_init(&sof_context);
sof_context_initialized = true;
}

return &sof_context;
}
Loading
Loading