tests/lib: Add sys_heap test
Use the white box validation and test rig added as part of the sys_heap work. Add a layer that puts hashed cookies into the blocks to detect corruption, check the validity state after every operation, and enumerate a few different usage patterns: + Small heap, "real world" allocation where the heap is about half full and most allocations succeed. + Small heap, "fragmentation runaway" scenario where most allocations start failing, but the heap must remain consistent. + Big heap. We can't test this with the same exhaustive coverage (many re/allocations for every byte of storage) for performance reasons, but we do what we can. Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
This commit is contained in:
parent
aa4227754c
commit
15c52ed12a
4 changed files with 220 additions and 0 deletions
8
tests/lib/heap/CMakeLists.txt
Normal file
8
tests/lib/heap/CMakeLists.txt
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.13.1)
|
||||||
|
include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)
|
||||||
|
project(heap)
|
||||||
|
|
||||||
|
FILE(GLOB app_sources src/*.c)
|
||||||
|
target_sources(app PRIVATE ${app_sources})
|
2
tests/lib/heap/prj.conf
Normal file
2
tests/lib/heap/prj.conf
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
CONFIG_ZTEST=y
|
||||||
|
CONFIG_SYS_HEAP_VALIDATE=y
|
207
tests/lib/heap/src/main.c
Normal file
207
tests/lib/heap/src/main.c
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 Intel Corporation
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
#include <zephyr.h>
|
||||||
|
#include <ztest.h>
|
||||||
|
#include <sys/sys_heap.h>
|
||||||
|
|
||||||
|
/* Guess at a value for heap size based on available memory on the
|
||||||
|
* platform.
|
||||||
|
*
|
||||||
|
* Note that mps2_an521 blows up if allowed to link into large area,
|
||||||
|
* even though the link is successful and it claims the memory is
|
||||||
|
* there. We get hard faults on boot before entry to cstart() once
|
||||||
|
* MEMSZ is allowed to get near 256kb.
|
||||||
|
*
|
||||||
|
* And native_posix doesn't support CONFIG_SRAM_SIZE at all (because
|
||||||
|
* it can link anything big enough to fit on the host), so just use a
|
||||||
|
* reasonable value.
|
||||||
|
*/
|
||||||
|
#if defined(CONFIG_BOARD_MPS2_AN521)
|
||||||
|
# define MEMSZ (192 * 1024)
|
||||||
|
#elif defined(CONFIG_ARCH_POSIX)
|
||||||
|
# define MEMSZ (2 * 1024 * 1024)
|
||||||
|
#else
|
||||||
|
# define MEMSZ (1024 * CONFIG_SRAM_SIZE)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define BIG_HEAP_SZ MIN(256 * 1024, MEMSZ / 3)
|
||||||
|
#define SMALL_HEAP_SZ MIN(BIG_HEAP_SZ, 2048)
|
||||||
|
#define SCRATCH_SZ (sizeof(heapmem) / 2)
|
||||||
|
|
||||||
|
/* The test memory. Make them pointer arrays for robust alignment
|
||||||
|
* behavior
|
||||||
|
*/
|
||||||
|
void *heapmem[BIG_HEAP_SZ / sizeof(void *)];
|
||||||
|
void *scratchmem[SCRATCH_SZ / sizeof(void *)];
|
||||||
|
|
||||||
|
/* How many alloc/free operations are tested on each heap. Two per
|
||||||
|
* byte of heap sounds about right to get exhaustive coverage without
|
||||||
|
* blowing too many cycles
|
||||||
|
*/
|
||||||
|
#define ITERATION_COUNT (2 * SMALL_HEAP_SZ)
|
||||||
|
|
||||||
|
/* Simple dumb hash function of the size and address */
|
||||||
|
static size_t fill_token(void *p, size_t sz)
|
||||||
|
{
|
||||||
|
size_t pi = (size_t) p;
|
||||||
|
|
||||||
|
return (pi * sz) ^ ((sz ^ 0xea6d) * ((pi << 11) | (pi >> 21)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Puts markers at the start and end of a block to ensure that nothing
|
||||||
|
* scribbled on it while it was allocated. The first word is the
|
||||||
|
* block size. The second and last (if they fits) are a hashed "fill
|
||||||
|
* token"
|
||||||
|
*/
|
||||||
|
static void fill_block(void *p, size_t sz)
|
||||||
|
{
|
||||||
|
if (p == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t tok = fill_token(p, sz);
|
||||||
|
|
||||||
|
((size_t *)p)[0] = sz;
|
||||||
|
|
||||||
|
if (sz >= 2 * sizeof(size_t)) {
|
||||||
|
((size_t *)p)[1] = tok;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sz > 3*sizeof(size_t)) {
|
||||||
|
((size_t *)p)[sz / sizeof(size_t) - 1] = tok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checks markers just before freeing a block */
|
||||||
|
static void check_fill(void *p)
|
||||||
|
{
|
||||||
|
size_t sz = ((size_t *)p)[0];
|
||||||
|
size_t tok = fill_token(p, sz);
|
||||||
|
|
||||||
|
zassert_true(sz > 0, "");
|
||||||
|
|
||||||
|
if (sz >= 2 * sizeof(size_t)) {
|
||||||
|
zassert_true(((size_t *)p)[1] == tok, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sz > 3 * sizeof(size_t)) {
|
||||||
|
zassert_true(((size_t *)p)[sz / sizeof(size_t) - 1] == tok, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void *testalloc(void *arg, size_t bytes)
|
||||||
|
{
|
||||||
|
void *ret = sys_heap_alloc(arg, bytes);
|
||||||
|
|
||||||
|
fill_block(ret, bytes);
|
||||||
|
sys_heap_validate(arg);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void testfree(void *arg, void *p)
|
||||||
|
{
|
||||||
|
check_fill(p);
|
||||||
|
sys_heap_free(arg, p);
|
||||||
|
sys_heap_validate(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void log_result(u32_t sz, struct z_heap_stress_result *r)
|
||||||
|
{
|
||||||
|
u32_t tot = r->total_allocs + r->total_frees;
|
||||||
|
u32_t avg = (u32_t)((r->accumulated_in_use_bytes + tot/2) / tot);
|
||||||
|
u32_t avg_pct = (u32_t)(100ULL * avg + sz / 2) / sz;
|
||||||
|
u32_t succ_pct = ((100ULL * r->successful_allocs + r->total_allocs / 2)
|
||||||
|
/ r->total_allocs);
|
||||||
|
|
||||||
|
TC_PRINT("successful allocs: %d/%d (%d%%), frees: %d,"
|
||||||
|
" avg usage: %d/%d (%d%%)\n",
|
||||||
|
r->successful_allocs, r->total_allocs, succ_pct,
|
||||||
|
r->total_frees, avg, sz, avg_pct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Do a heavy test over a small heap, with many iterations that need
|
||||||
|
* to reuse memory repeatedly. Target 50% fill, as that setting tends
|
||||||
|
* to prevent runaway fragmentation and most allocations continue to
|
||||||
|
* succeed in steady state.
|
||||||
|
*/
|
||||||
|
static void test_small_heap(void)
|
||||||
|
{
|
||||||
|
struct sys_heap heap;
|
||||||
|
struct z_heap_stress_result result;
|
||||||
|
|
||||||
|
TC_PRINT("Testing small (%d byte) heap\n", SMALL_HEAP_SZ);
|
||||||
|
|
||||||
|
sys_heap_init(&heap, heapmem, SMALL_HEAP_SZ);
|
||||||
|
zassert_true(sys_heap_validate(&heap), "");
|
||||||
|
sys_heap_stress(testalloc, testfree, &heap,
|
||||||
|
SMALL_HEAP_SZ, ITERATION_COUNT,
|
||||||
|
scratchmem, sizeof(scratchmem),
|
||||||
|
50, &result);
|
||||||
|
|
||||||
|
log_result(SMALL_HEAP_SZ, &result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Very similar, but tests a fragmentation runaway scenario where we
|
||||||
|
* target 100% fill and end up breaking memory up into maximally
|
||||||
|
* fragmented blocks (i.e. small allocations always grab and split the
|
||||||
|
* bigger chunks). Obviously success rates in alloc will be very low,
|
||||||
|
* but consistency should still be maintained. Paradoxically, fill
|
||||||
|
* level is not much better than the 50% target due to all the
|
||||||
|
* fragmentation overhead (also the way we do accounting: we are
|
||||||
|
* counting bytes requested, so if you ask for a 3 byte block and
|
||||||
|
* receive a 8 byte minimal chunk, we still count that as 5 bytes of
|
||||||
|
* waste).
|
||||||
|
*/
|
||||||
|
static void test_fragmentation(void)
|
||||||
|
{
|
||||||
|
struct sys_heap heap;
|
||||||
|
struct z_heap_stress_result result;
|
||||||
|
|
||||||
|
TC_PRINT("Testing maximally fragmented (%d byte) heap\n",
|
||||||
|
SMALL_HEAP_SZ);
|
||||||
|
|
||||||
|
sys_heap_init(&heap, heapmem, SMALL_HEAP_SZ);
|
||||||
|
zassert_true(sys_heap_validate(&heap), "");
|
||||||
|
sys_heap_stress(testalloc, testfree, &heap,
|
||||||
|
SMALL_HEAP_SZ, ITERATION_COUNT,
|
||||||
|
scratchmem, sizeof(scratchmem),
|
||||||
|
100, &result);
|
||||||
|
|
||||||
|
log_result(SMALL_HEAP_SZ, &result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The heap block format changes for heaps with more than 2^15 chunks,
|
||||||
|
* so test that case too. This can be too large to iterate over
|
||||||
|
* exhaustively with good performance, so the relative operation count
|
||||||
|
* and fragmentation is going to be lower.
|
||||||
|
*/
|
||||||
|
static void test_big_heap(void)
|
||||||
|
{
|
||||||
|
struct sys_heap heap;
|
||||||
|
struct z_heap_stress_result result;
|
||||||
|
|
||||||
|
TC_PRINT("Testing big (%d byte) heap\n", BIG_HEAP_SZ);
|
||||||
|
|
||||||
|
sys_heap_init(&heap, heapmem, BIG_HEAP_SZ);
|
||||||
|
zassert_true(sys_heap_validate(&heap), "");
|
||||||
|
sys_heap_stress(testalloc, testfree, &heap,
|
||||||
|
BIG_HEAP_SZ, ITERATION_COUNT,
|
||||||
|
scratchmem, sizeof(scratchmem),
|
||||||
|
100, &result);
|
||||||
|
|
||||||
|
log_result(BIG_HEAP_SZ, &result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_main(void)
|
||||||
|
{
|
||||||
|
ztest_test_suite(lib_heap_test,
|
||||||
|
ztest_unit_test(test_small_heap),
|
||||||
|
ztest_unit_test(test_fragmentation),
|
||||||
|
ztest_unit_test(test_big_heap)
|
||||||
|
);
|
||||||
|
|
||||||
|
ztest_run_test_suite(lib_heap_test);
|
||||||
|
}
|
3
tests/lib/heap/testcase.yaml
Normal file
3
tests/lib/heap/testcase.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
tests:
|
||||||
|
lib.heap:
|
||||||
|
tags: heap
|
Loading…
Add table
Add a link
Reference in a new issue