heap: migrate unit tests to pytest

This commit is contained in:
Guillaume Souchere
2022-09-19 11:46:55 +02:00
parent f39869fdf5
commit 405c26da6d
44 changed files with 175 additions and 57 deletions

View File

@@ -0,0 +1,6 @@
# 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.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(test_heap)

View File

@@ -0,0 +1,2 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- |

View File

@@ -0,0 +1,15 @@
set(src_test "test_app_main.c"
"test_aligned_alloc_caps.c"
"test_allocator_timings.c"
"test_corruption_check.c"
"test_diram.c"
"test_heap_trace.c"
"test_leak.c"
"test_malloc_caps.c"
"test_malloc.c"
"test_realloc.c"
"test_runtime_heap_reg.c")
idf_component_register(SRCS ${src_test}
INCLUDE_DIRS "."
WHOLE_ARCHIVE)

View File

@@ -0,0 +1,152 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/*
Tests for the capabilities-based memory allocator.
*/
#include <esp_types.h>
#include <stdio.h>
#include "unity.h"
#include "esp_attr.h"
#include "esp_heap_caps.h"
#include "spi_flash_mmap.h"
#include <stdlib.h>
#include <sys/param.h>
#include <string.h>
#include <malloc.h>
#include <inttypes.h>
TEST_CASE("Capabilities aligned allocator test", "[heap]")
{
uint32_t alignments = 0;
printf("[ALIGNED_ALLOC] Allocating from default CAP: \n");
for(;alignments <= 1024; alignments++) {
uint8_t *buf = (uint8_t *)memalign(alignments, (alignments + 137));
if(((alignments & (alignments - 1)) != 0) || (!alignments)) {
TEST_ASSERT( buf == NULL );
//printf("[ALIGNED_ALLOC] alignment: %"PRIu32" is not a power of two, don't allow allocation \n", aligments);
} else {
TEST_ASSERT( buf != NULL );
printf("[ALIGNED_ALLOC] alignment required: %"PRIu32" \n", alignments);
printf("[ALIGNED_ALLOC] address of allocated memory: %p \n\n", (void *)buf);
//Address of obtained block must be aligned with selected value
TEST_ASSERT(((intptr_t)buf & (alignments - 1)) == 0);
//Write some data, if it corrupts memory probably the heap
//canary verification will fail:
memset(buf, 0xA5, (alignments + 137));
free(buf);
}
}
//Alloc from a non permitted area:
uint32_t *not_permitted_buf = (uint32_t *)heap_caps_aligned_alloc(alignments, (alignments + 137), MALLOC_CAP_EXEC | MALLOC_CAP_32BIT);
TEST_ASSERT( not_permitted_buf == NULL );
#if CONFIG_SPIRAM
alignments = 0;
printf("[ALIGNED_ALLOC] Allocating from external memory: \n");
for(;alignments <= 1024 * 512; alignments++) {
//Now try to take aligned memory from IRAM:
uint8_t *buf = (uint8_t *)heap_caps_aligned_alloc(alignments, 10*1024, MALLOC_CAP_SPIRAM);
if(((alignments & (alignments - 1)) != 0) || (!alignments)) {
TEST_ASSERT( buf == NULL );
//printf("[ALIGNED_ALLOC] alignment: %"PRIu32" is not a power of two, don't allow allocation \n", aligments);
} else {
TEST_ASSERT( buf != NULL );
printf("[ALIGNED_ALLOC] alignment required: %"PRIu32" \n", alignments);
printf("[ALIGNED_ALLOC] address of allocated memory: %p \n\n", (void *)buf);
//Address of obtained block must be aligned with selected value
TEST_ASSERT(((intptr_t)buf & (alignments - 1)) == 0);
//Write some data, if it corrupts memory probably the heap
//canary verification will fail:
memset(buf, 0xA5, (10*1024));
heap_caps_free(buf);
}
}
#endif
}
TEST_CASE("Capabilities aligned calloc test", "[heap]")
{
uint32_t alignments = 0;
printf("[ALIGNED_ALLOC] Allocating from default CAP: \n");
for(;alignments <= 1024; alignments++) {
uint8_t *buf = (uint8_t *)heap_caps_aligned_calloc(alignments, 1, (alignments + 137), MALLOC_CAP_DEFAULT);
if(((alignments & (alignments - 1)) != 0) || (!alignments)) {
TEST_ASSERT( buf == NULL );
//printf("[ALIGNED_ALLOC] alignment: %"PRIu32" is not a power of two, don't allow allocation \n", aligments);
} else {
TEST_ASSERT( buf != NULL );
printf("[ALIGNED_ALLOC] alignment required: %"PRIu32" \n", alignments);
printf("[ALIGNED_ALLOC] address of allocated memory: %p \n\n", (void *)buf);
//Address of obtained block must be aligned with selected value
TEST_ASSERT(((intptr_t)buf & (alignments - 1)) == 0);
//Write some data, if it corrupts memory probably the heap
//canary verification will fail:
memset(buf, 0xA5, (alignments + 137));
heap_caps_free(buf);
}
}
//Check if memory is initialized with zero:
uint8_t byte_array[1024];
memset(&byte_array, 0, sizeof(byte_array));
uint8_t *buf = (uint8_t *)heap_caps_aligned_calloc(1024, 1, 1024, MALLOC_CAP_DEFAULT);
TEST_ASSERT(memcmp(byte_array, buf, sizeof(byte_array)) == 0);
heap_caps_free(buf);
//Same size, but different chunk:
buf = (uint8_t *)heap_caps_aligned_calloc(1024, 1024, 1, MALLOC_CAP_DEFAULT);
TEST_ASSERT(memcmp(byte_array, buf, sizeof(byte_array)) == 0);
heap_caps_free(buf);
//Alloc from a non permitted area:
uint32_t *not_permitted_buf = (uint32_t *)heap_caps_aligned_calloc(alignments, 1, (alignments + 137), MALLOC_CAP_32BIT);
TEST_ASSERT( not_permitted_buf == NULL );
#if CONFIG_SPIRAM
alignments = 0;
printf("[ALIGNED_ALLOC] Allocating from external memory: \n");
for(;alignments <= 1024 * 512; alignments++) {
//Now try to take aligned memory from IRAM:
uint8_t *buf = (uint8_t *)(uint8_t *)heap_caps_aligned_calloc(alignments, 1, 10*1024, MALLOC_CAP_SPIRAM);
if(((alignments & (alignments - 1)) != 0) || (!alignments)) {
TEST_ASSERT( buf == NULL );
//printf("[ALIGNED_ALLOC] alignment: %"PRIu32" is not a power of two, don't allow allocation \n", aligments);
} else {
TEST_ASSERT( buf != NULL );
printf("[ALIGNED_ALLOC] alignment required: %"PRIu32" \n", alignments);
printf("[ALIGNED_ALLOC] address of allocated memory: %p \n\n", (void *)buf);
//Address of obtained block must be aligned with selected value
TEST_ASSERT(((intptr_t)buf & (alignments - 1)) == 0);
//Write some data, if it corrupts memory probably the heap
//canary verification will fail:
memset(buf, 0xA5, (10*1024));
heap_caps_free(buf);
}
}
#endif
}
TEST_CASE("aligned_alloc(0) should return a NULL pointer", "[heap]")
{
void *p;
p = heap_caps_aligned_alloc(32, 0, MALLOC_CAP_DEFAULT);
TEST_ASSERT(p == NULL);
}

View File

@@ -0,0 +1,112 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "freertos/FreeRTOS.h"
#include <esp_types.h>
#include <stdio.h>
#include "unity.h"
#include "esp_attr.h"
#include "esp_heap_caps.h"
#include <stdlib.h>
#include <sys/param.h>
#include <string.h>
//This test only makes sense with poisoning disabled (light or comprehensive)
#if !defined(CONFIG_HEAP_POISONING_COMPREHENSIVE) && !defined(CONFIG_HEAP_POISONING_LIGHT)
#define NUM_POINTERS 128
#define ITERATIONS 10000
TEST_CASE("Heap many random allocations timings", "[heap]")
{
void *p[NUM_POINTERS] = { 0 };
size_t s[NUM_POINTERS] = { 0 };
uint32_t cycles_before;
uint64_t alloc_time_average = 0;
uint64_t free_time_average = 0;
uint64_t realloc_time_average = 0;
for (int i = 0; i < ITERATIONS; i++) {
uint8_t n = (uint32_t)rand() % NUM_POINTERS;
if (ITERATIONS % 4 == 0) {
/* 1 in 4 iterations, try to realloc the buffer instead
of using malloc/free
*/
size_t new_size = (uint32_t)rand() % 1024;
cycles_before = portGET_RUN_TIME_COUNTER_VALUE();
void *new_p = heap_caps_realloc(p[n], new_size, MALLOC_CAP_DEFAULT);
realloc_time_average = portGET_RUN_TIME_COUNTER_VALUE() - cycles_before;
printf("realloc %p -> %p (%zu -> %zu) time spent cycles: %lld \n", p[n], new_p, s[n], new_size, realloc_time_average);
heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true);
if (new_size == 0 || new_p != NULL) {
p[n] = new_p;
s[n] = new_size;
if (new_size > 0) {
memset(p[n], n, new_size);
}
}
continue;
}
if (p[n] != NULL) {
if (s[n] > 0) {
/* Verify pre-existing contents of p[n] */
uint8_t compare[s[n]];
memset(compare, n, s[n]);
TEST_ASSERT(( memcmp(compare, p[n], s[n]) == 0 ));
}
TEST_ASSERT(heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true));
cycles_before = portGET_RUN_TIME_COUNTER_VALUE();
heap_caps_free(p[n]);
free_time_average = portGET_RUN_TIME_COUNTER_VALUE() - cycles_before;
printf("freed %p (%zu) time spent cycles: %lld\n", p[n], s[n], free_time_average);
if (!heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true)) {
printf("FAILED iteration %d after freeing %p\n", i, p[n]);
heap_caps_dump(MALLOC_CAP_DEFAULT);
TEST_ASSERT(0);
}
}
s[n] = rand() % 1024;
heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true);
cycles_before = portGET_RUN_TIME_COUNTER_VALUE();
p[n] = heap_caps_malloc(s[n], MALLOC_CAP_DEFAULT);
alloc_time_average = portGET_RUN_TIME_COUNTER_VALUE() - cycles_before;
printf("malloc %p (%zu) time spent cycles: %lld \n", p[n], s[n], alloc_time_average);
if (!heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true)) {
printf("FAILED iteration %d after mallocing %p (%zu bytes)\n", i, p[n], s[n]);
heap_caps_dump(MALLOC_CAP_DEFAULT);
TEST_ASSERT(0);
}
if (p[n] != NULL) {
memset(p[n], n, s[n]);
}
}
for (int i = 0; i < NUM_POINTERS; i++) {
cycles_before = portGET_RUN_TIME_COUNTER_VALUE();
heap_caps_free( p[i]);
free_time_average = portGET_RUN_TIME_COUNTER_VALUE() - cycles_before;
if (!heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true)) {
printf("FAILED during cleanup after freeing %p\n", p[i]);
heap_caps_dump(MALLOC_CAP_DEFAULT);
TEST_ASSERT(0);
}
}
TEST_ASSERT(heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true));
}
#endif

View File

@@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "unity.h"
#include "unity_test_runner.h"
#include "esp_heap_caps.h"
#define TEST_MEMORY_LEAK_THRESHOLD (-1024)
static size_t before_free_8bit;
static size_t before_free_32bit;
static void check_leak(size_t before_free, size_t after_free, const char *type)
{
ssize_t delta = after_free - before_free;
printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta);
TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
}
void setUp(void)
{
before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
}
void tearDown(void)
{
size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
check_leak(before_free_8bit, after_free_8bit, "8BIT");
check_leak(before_free_32bit, after_free_32bit, "32BIT");
}
void app_main(void)
{
printf("Running heap component tests\n");
unity_run_menu();
}

View File

@@ -0,0 +1,73 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "unity.h"
#include "stdio.h"
#include "esp_heap_caps.h"
//This test only makes sense with poisoning enabled (light or comprehensive)
#if defined(CONFIG_HEAP_POISONING_COMPREHENSIVE) || defined(CONFIG_HEAP_POISONING_LIGHT)
/* executing multi_heap_internal_check_block_poisoning()
* takes longer on external RAM and therefore the timeout
* in the test of 30 seconds is exceeded. Execute the test
* on a smaller memory chunk
*/
#ifdef CONFIG_SPIRAM
const size_t MALLOC_SIZE = 16;
#else
const size_t MALLOC_SIZE = 64;
#endif
const uint8_t CORRUPTED_VALUE = 0xaa;
/* This test will corrupt the memory of a free block in the heap and check
* that in the case of comprehensive poisoning the heap corruption is detected
* by heap_caps_check_integrity(). For light poisoning and no poisoning, the test will
* check that heap_caps_check_integrity() does not report the corruption.
*/
TEST_CASE("multi_heap poisoning detection", "[heap]")
{
/* malloc some memory to get a pointer */
uint8_t *ptr = heap_caps_malloc(MALLOC_SIZE, MALLOC_CAP_8BIT);
/* free the memory to free the block but keep the pointer in mind */
heap_caps_free(ptr);
/* variable used in the test */
uint8_t original_value = 0x00;
for (size_t i = 0; i < MALLOC_SIZE; i++)
{
/* keep the good value in store in order to check that when we set the byte back
* to its original value, heap_caps_check_integrity() no longer returns the
* heap corruption. */
original_value = ptr[i];
/* set corrupted value in the free memory*/
ptr[i] = CORRUPTED_VALUE;
bool is_heap_ok = heap_caps_check_integrity(MALLOC_CAP_8BIT, true);
/* fix the corruption by restoring the original value at ptr + i.
* We need to do this before the ASSERT because they may print a message.
* Using print allocates memory on the heap, so the heap has to be fixed. */
ptr[i] = original_value;
#if CONFIG_HEAP_POISONING_COMPREHENSIVE
/* check that heap_caps_check_integrity() detects the corruption */
TEST_ASSERT_FALSE(is_heap_ok);
#else
/* the comprehensive corruption is not checked in the heap_caps_check_integrity() */
TEST_ASSERT_TRUE(is_heap_ok);
#endif
/* check that heap_caps_check_integrity() stops reporting the corruption */
is_heap_ok = heap_caps_check_integrity(MALLOC_CAP_8BIT, true);
TEST_ASSERT_TRUE(is_heap_ok);
}
}
#endif

View File

@@ -0,0 +1,84 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/*
Tests for D/IRAM support in heap capability allocator
*/
#include <esp_types.h>
#include <stdio.h>
#include "unity.h"
#include "esp_heap_caps.h"
#include "soc/soc_memory_layout.h"
#define ALLOC_SZ 1024
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32C2)
//IDF-5167
#ifndef CONFIG_ESP_SYSTEM_MEMPROT_FEATURE
static void *malloc_block_diram(uint32_t caps)
{
void *attempts[256] = { 0 }; // Allocate up to 256 ALLOC_SZ blocks to exhaust all non-D/IRAM memory temporarily
int count = 0;
void *result;
while(count < sizeof(attempts)/sizeof(void *)) {
result = heap_caps_malloc(ALLOC_SZ, caps);
TEST_ASSERT_NOT_NULL_MESSAGE(result, "not enough free heap to perform test");
if (esp_ptr_in_diram_dram(result) || esp_ptr_in_diram_iram(result)) {
break;
}
attempts[count] = result;
result = NULL;
count++;
}
for (int i = 0; i < count; i++) {
free(attempts[i]);
}
TEST_ASSERT_NOT_NULL_MESSAGE(result, "not enough D/IRAM memory is free");
return result;
}
TEST_CASE("Allocate D/IRAM as DRAM", "[heap]")
{
uint32_t *dram = malloc_block_diram(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
for (int i = 0; i < ALLOC_SZ / sizeof(uint32_t); i++) {
uint32_t v = i + 0xAAAA;
dram[i] = v;
volatile uint32_t *iram = esp_ptr_diram_dram_to_iram(dram + i);
TEST_ASSERT_EQUAL(v, dram[i]);
TEST_ASSERT_EQUAL(v, *iram);
*iram = UINT32_MAX;
TEST_ASSERT_EQUAL(UINT32_MAX, *iram);
TEST_ASSERT_EQUAL(UINT32_MAX, dram[i]);
}
free(dram);
}
TEST_CASE("Allocate D/IRAM as IRAM", "[heap]")
{
uint32_t *iram = malloc_block_diram(MALLOC_CAP_EXEC);
for (int i = 0; i < ALLOC_SZ / sizeof(uint32_t); i++) {
uint32_t v = i + 0xEEE;
iram[i] = v;
volatile uint32_t *dram = esp_ptr_diram_iram_to_dram(iram + i);
TEST_ASSERT_EQUAL_HEX32(v, iram[i]);
TEST_ASSERT_EQUAL_HEX32(v, *dram);
*dram = UINT32_MAX;
TEST_ASSERT_EQUAL_HEX32(UINT32_MAX, *dram);
TEST_ASSERT_EQUAL_HEX32(UINT32_MAX, iram[i]);
}
free(iram);
}
#endif // CONFIG_ESP_SYSTEM_MEMPROT_FEATURE
#endif //!TEMPORARY_DISABLED_FOR_TARGETS(ESP32C2)

View File

@@ -0,0 +1,169 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/*
Generic test for heap tracing support
Only compiled in if CONFIG_HEAP_TRACING is set
*/
#include <esp_types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sdkconfig.h"
#include "unity.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#ifdef CONFIG_HEAP_TRACING
// only compile in heap tracing tests if tracing is enabled
#include "esp_heap_trace.h"
TEST_CASE("heap trace leak check", "[heap]")
{
heap_trace_record_t recs[8];
heap_trace_init_standalone(recs, 8);
printf("Leak check test\n"); // Print something before trace starts, or stdout allocations skew total counts
fflush(stdout);
heap_trace_start(HEAP_TRACE_LEAKS);
void *a = malloc(64);
memset(a, '3', 64);
void *b = malloc(96);
memset(b, '4', 11);
printf("a.address %p vs %p b.address %p vs %p\n", a, recs[0].address, b, recs[1].address);
heap_trace_dump();
TEST_ASSERT_EQUAL(2, heap_trace_get_count());
heap_trace_record_t trace_a, trace_b;
heap_trace_get(0, &trace_a);
heap_trace_get(1, &trace_b);
printf("trace_a.address %p trace_bb.address %p\n", trace_a.address, trace_b.address);
TEST_ASSERT_EQUAL_PTR(a, trace_a.address);
TEST_ASSERT_EQUAL_PTR(b, trace_b.address);
TEST_ASSERT_EQUAL_PTR(recs[0].address, trace_a.address);
TEST_ASSERT_EQUAL_PTR(recs[1].address, trace_b.address);
free(a);
TEST_ASSERT_EQUAL(1, heap_trace_get_count());
heap_trace_get(0, &trace_b);
TEST_ASSERT_EQUAL_PTR(b, trace_b.address);
/* buffer deletes trace_a when freed,
so trace_b at head of buffer */
TEST_ASSERT_EQUAL_PTR(recs[0].address, trace_b.address);
heap_trace_stop();
}
TEST_CASE("heap trace wrapped buffer check", "[heap]")
{
const size_t N = 8;
heap_trace_record_t recs[N];
heap_trace_init_standalone(recs, N);
heap_trace_start(HEAP_TRACE_LEAKS);
void *ptrs[N+1];
for (int i = 0; i < N+1; i++) {
ptrs[i] = malloc(i*3);
}
// becuase other mallocs happen as part of this control flow,
// we can't guarantee N entries of ptrs[] are in the heap check buffer.
// but we should guarantee at least the last one is
bool saw_last_ptr = false;
for (int i = 0; i < N; i++) {
heap_trace_record_t rec;
heap_trace_get(i, &rec);
if (rec.address == ptrs[N-1]) {
saw_last_ptr = true;
}
}
TEST_ASSERT(saw_last_ptr);
void *other = malloc(6);
heap_trace_dump();
for (int i = 0; i < N+1; i++) {
free(ptrs[i]);
}
heap_trace_dump();
bool saw_other = false;
for (int i = 0; i < heap_trace_get_count(); i++) {
heap_trace_record_t rec;
heap_trace_get(i, &rec);
// none of ptr[]s should be in the heap trace any more
for (int j = 0; j < N+1; j++) {
TEST_ASSERT_NOT_EQUAL(ptrs[j], rec.address);
}
if (rec.address == other) {
saw_other = true;
}
}
// 'other' pointer should be somewhere in the leak dump
TEST_ASSERT(saw_other);
heap_trace_stop();
}
static void print_floats_task(void *ignore)
{
heap_trace_start(HEAP_TRACE_ALL);
char buf[16] = { };
volatile float f = 12.3456;
sprintf(buf, "%.4f", f);
TEST_ASSERT_EQUAL_STRING("12.3456", buf);
heap_trace_stop();
vTaskDelete(NULL);
}
TEST_CASE("can trace allocations made by newlib", "[heap]")
{
const size_t N = 8;
heap_trace_record_t recs[N];
heap_trace_init_standalone(recs, N);
/* Verifying that newlib code performs an allocation is very fiddly:
- Printing a float allocates data associated with the task, but only the
first time a task prints a float of this length. So we do it in a one-shot task
to avoid possibility it already happened.
- If newlib is updated this test may start failing if the printf() implementation
changes. (This version passes for both nano & regular formatting in newlib 2.2.0)
- We also do the tracing in the task so we only capture things directly related to it.
*/
xTaskCreate(print_floats_task, "print_float", 4096, NULL, 5, NULL);
vTaskDelay(10);
/* has to be at least a few as newlib allocates via multiple different function calls */
TEST_ASSERT(heap_trace_get_count() > 3);
}
#endif

View File

@@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/*
Tests for a leak tag
*/
#include <stdio.h>
#include "unity.h"
#include "esp_heap_caps_init.h"
#include "esp_system.h"
#include <stdlib.h>
static char* check_calloc(int size)
{
char *arr = calloc(size, sizeof(char));
TEST_ASSERT_NOT_NULL(arr);
return arr;
}
TEST_CASE("Check for leaks (no leak)", "[heap]")
{
char *arr = check_calloc(1000);
free(arr);
}
TEST_CASE("Check for leaks (leak)", "[heap][ignore]")
{
check_calloc(1000);
}
TEST_CASE("Not check for leaks", "[heap][leaks]")
{
check_calloc(1000);
}
TEST_CASE("Set a leak level = 7016", "[heap][leaks=7016]")
{
check_calloc(7000);
}
static void test_fn(void)
{
check_calloc(1000);
}
TEST_CASE_MULTIPLE_STAGES("Not check for leaks in MULTIPLE_STAGES mode", "[heap][leaks]", test_fn, test_fn, test_fn);
TEST_CASE_MULTIPLE_STAGES("Check for leaks in MULTIPLE_STAGES mode (leak)", "[heap][ignore]", test_fn, test_fn, test_fn);
static void test_fn2(void)
{
check_calloc(1000);
esp_restart();
}
static void test_fn3(void)
{
check_calloc(1000);
}
TEST_CASE_MULTIPLE_STAGES("Check for leaks in MULTIPLE_STAGES mode (manual reset)", "[heap][leaks][reset=SW_CPU_RESET, SW_CPU_RESET]", test_fn2, test_fn2, test_fn3);

View File

@@ -0,0 +1,161 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/*
Generic test for malloc/free
*/
#include <esp_types.h>
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "unity.h"
#include "esp_heap_caps.h"
#include "sdkconfig.h"
static int **allocatedMem;
static int noAllocated;
static int tryAllocMem(void) {
int i, j;
const int allocateMaxK=1024*5; //try to allocate a max of 5MiB
allocatedMem=malloc(sizeof(int *)*allocateMaxK);
if (!allocatedMem) return 0;
for (i=0; i<allocateMaxK; i++) {
allocatedMem[i]=malloc(1024);
if (allocatedMem[i]==NULL) break;
for (j=0; j<1024/4; j++) allocatedMem[i][j]=(0xdeadbeef);
}
noAllocated=i;
return i;
}
static void tryAllocMemFree(void) {
int i, j;
for (i=0; i<noAllocated; i++) {
for (j=0; j<1024/4; j++) {
TEST_ASSERT(allocatedMem[i][j]==(0xdeadbeef));
}
free(allocatedMem[i]);
}
free(allocatedMem);
}
TEST_CASE("Malloc/overwrite, then free all available DRAM", "[heap]")
{
int m1=0, m2=0;
m1=tryAllocMem();
tryAllocMemFree();
m2=tryAllocMem();
tryAllocMemFree();
printf("Could allocate %dK on first try, %dK on 2nd try.\n", m1, m2);
TEST_ASSERT(m1==m2);
}
#if CONFIG_SPIRAM_USE_MALLOC
#if (CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL > 1024)
TEST_CASE("Check if reserved DMA pool still can allocate even when malloc()'ed memory is exhausted", "[heap]")
{
char** dmaMem=malloc(sizeof(char*)*512);
assert(dmaMem);
int m=tryAllocMem();
int i=0;
for (i=0; i<512; i++) {
dmaMem[i]=heap_caps_malloc(1024, MALLOC_CAP_DMA);
if (dmaMem[i]==NULL) break;
}
for (int j=0; j<i; j++) free(dmaMem[j]);
free(dmaMem);
tryAllocMemFree();
printf("Could allocate %dK of DMA memory after allocating all of %dK of normal memory.\n", i, m);
TEST_ASSERT(i);
}
#endif
#endif
/* As you see, we are desperately trying to outsmart the compiler, so that it
* doesn't warn about oversized allocations in the next two unit tests.
* To be removed when we switch to GCC 8.2 and add
* -Wno-alloc-size-larger-than=PTRDIFF_MAX to CFLAGS for this file.
*/
void* (*g_test_malloc_ptr)(size_t) = &malloc;
void* (*g_test_calloc_ptr)(size_t, size_t) = &calloc;
void* test_malloc_wrapper(size_t size)
{
return (*g_test_malloc_ptr)(size);
}
void* test_calloc_wrapper(size_t count, size_t size)
{
return (*g_test_calloc_ptr)(count, size);
}
TEST_CASE("alloc overflows should all fail", "[heap]")
{
/* allocates 8 bytes if size_t overflows */
TEST_ASSERT_NULL(test_calloc_wrapper(SIZE_MAX / 2 + 4, 2));
/* will overflow if any poisoning is enabled
(should fail for sensible OOM reasons, otherwise) */
TEST_ASSERT_NULL(test_malloc_wrapper(SIZE_MAX - 1));
TEST_ASSERT_NULL(test_calloc_wrapper(SIZE_MAX - 1, 1));
/* will overflow when the size is rounded up to word align it */
TEST_ASSERT_NULL(heap_caps_malloc(SIZE_MAX-1, MALLOC_CAP_32BIT));
TEST_ASSERT_NULL(heap_caps_malloc(SIZE_MAX-1, MALLOC_CAP_EXEC));
}
TEST_CASE("unreasonable allocs should all fail", "[heap]")
{
TEST_ASSERT_NULL(test_calloc_wrapper(16, 1024*1024));
TEST_ASSERT_NULL(test_malloc_wrapper(16*1024*1024));
TEST_ASSERT_NULL(test_malloc_wrapper(SIZE_MAX / 2));
TEST_ASSERT_NULL(test_malloc_wrapper(SIZE_MAX - 256));
TEST_ASSERT_NULL(test_malloc_wrapper(xPortGetFreeHeapSize() - 1));
}
TEST_CASE("malloc(0) should return a NULL pointer", "[heap]")
{
void *p;
p = malloc(0);
TEST_ASSERT(p == NULL);
}
static bool failure_occured = false;
static void test_alloc_failure_callback(size_t size, uint32_t caps, const char * function_name)
{
failure_occured = true;
}
TEST_CASE("malloc/calloc(0) should not call failure callback", "[heap]")
{
void* ptr = NULL;
esp_err_t ret = heap_caps_register_failed_alloc_callback(test_alloc_failure_callback);
TEST_ASSERT(ret == ESP_OK);
ptr = malloc(0);
TEST_ASSERT_NULL(ptr);
/* Check that our callback was NOT called */
TEST_ASSERT_FALSE(failure_occured);
/* Do the same thing for calloc */
ptr = calloc(0, 0);
TEST_ASSERT_NULL(ptr);
TEST_ASSERT_FALSE(failure_occured);
}

View File

@@ -0,0 +1,287 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/*
Tests for the capabilities-based memory allocator.
*/
#include <esp_types.h>
#include <stdio.h>
#include "unity.h"
#include "esp_attr.h"
#include "esp_heap_caps.h"
#include "spi_flash_mmap.h"
#include "esp_memory_utils.h"
#include "esp_private/spi_flash_os.h"
#include <stdlib.h>
#include <sys/param.h>
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32C2)
//IDF-5167
#ifndef CONFIG_ESP_SYSTEM_MEMPROT_FEATURE
TEST_CASE("Capabilities allocator test", "[heap]")
{
char *m1, *m2[10];
int x;
size_t free8start, free32start, free8, free32;
/* It's important we printf() something before we take the empty heap sizes,
as the first printf() in a task allocates heap resources... */
printf("Testing capabilities allocator...\n");
free8start = heap_caps_get_free_size(MALLOC_CAP_8BIT);
free32start = heap_caps_get_free_size(MALLOC_CAP_32BIT);
printf("Free 8bit-capable memory (start): %dK, 32-bit capable memory %dK\n", free8start, free32start);
TEST_ASSERT(free32start >= free8start);
printf("Allocating 10K of 8-bit capable RAM\n");
m1= heap_caps_malloc(10*1024, MALLOC_CAP_8BIT);
printf("--> %p\n", m1);
free8 = heap_caps_get_free_size(MALLOC_CAP_8BIT);
free32 = heap_caps_get_free_size(MALLOC_CAP_32BIT);
printf("Free 8bit-capable memory (both reduced): %dK, 32-bit capable memory %dK\n", free8, free32);
//Both should have gone down by 10K; 8bit capable ram is also 32-bit capable
TEST_ASSERT(free8<=(free8start-10*1024));
TEST_ASSERT(free32<=(free32start-10*1024));
//Assume we got DRAM back
TEST_ASSERT((((int)m1)&0xFF000000)==0x3F000000);
free(m1);
//The goal here is to allocate from IRAM. Since there is no external IRAM (yet)
//the following gives size of IRAM-only (not D/IRAM) memory.
size_t free_iram = heap_caps_get_free_size(MALLOC_CAP_INTERNAL) -
heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
size_t alloc32 = MIN(free_iram / 2, 10*1024) & (~3);
if(free_iram) {
printf("Freeing; allocating %u bytes of 32K-capable RAM\n", alloc32);
m1 = heap_caps_malloc(alloc32, MALLOC_CAP_32BIT);
printf("--> %p\n", m1);
//Check that we got IRAM back
TEST_ASSERT((((int)m1)&0xFF000000)==0x40000000);
free8 = heap_caps_get_free_size(MALLOC_CAP_8BIT);
free32 = heap_caps_get_free_size(MALLOC_CAP_32BIT);
printf("Free 8bit-capable memory (after 32-bit): %dK, 32-bit capable memory %dK\n", free8, free32);
//Only 32-bit should have gone down by alloc32: 32-bit isn't necessarily 8bit capable
TEST_ASSERT(free32<=(free32start-alloc32));
TEST_ASSERT(free8==free8start);
free(m1);
} else {
printf("This platform has no 32-bit only capable RAM, jumping to next test \n");
}
printf("Allocating impossible caps\n");
m1= heap_caps_malloc(10*1024, MALLOC_CAP_8BIT|MALLOC_CAP_EXEC);
printf("--> %p\n", m1);
TEST_ASSERT(m1==NULL);
if(free_iram) {
printf("Testing changeover iram -> dram");
// priorities will exhaust IRAM first, then start allocating from DRAM
for (x=0; x<10; x++) {
m2[x]= heap_caps_malloc(alloc32, MALLOC_CAP_32BIT);
printf("--> %p\n", m2[x]);
}
TEST_ASSERT((((int)m2[0])&0xFF000000)==0x40000000);
TEST_ASSERT((((int)m2[9])&0xFF000000)==0x3F000000);
} else {
printf("This platform has no IRAM-only so changeover will never occur, jumping to next test\n");
}
printf("Test if allocating executable code still gives IRAM, even with dedicated IRAM region depleted\n");
if(free_iram) {
// (the allocation should come from D/IRAM)
free_iram = heap_caps_get_free_size(MALLOC_CAP_EXEC);
m1= heap_caps_malloc(MIN(free_iram / 2, 10*1024), MALLOC_CAP_EXEC);
printf("--> %p\n", m1);
TEST_ASSERT((((int)m1)&0xFF000000)==0x40000000);
for (x=0; x<10; x++) free(m2[x]);
} else {
// (the allocation should come from D/IRAM)
free_iram = heap_caps_get_free_size(MALLOC_CAP_EXEC);
m1= heap_caps_malloc(MIN(free_iram / 2, 10*1024), MALLOC_CAP_EXEC);
printf("--> %p\n", m1);
TEST_ASSERT((((int)m1)&0xFF000000)==0x40000000);
}
free(m1);
printf("Done.\n");
}
#endif // CONFIG_ESP_SYSTEM_MEMPROT_FEATURE
#endif //!TEMPORARY_DISABLED_FOR_TARGETS(ESP32C2)
#ifdef CONFIG_ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY
TEST_CASE("IRAM_8BIT capability test", "[heap]")
{
uint8_t *ptr;
size_t free_size, free_size32, largest_free_size;
/* need to print something as first printf allocates some heap */
printf("IRAM_8BIT capability test\n");
free_size = heap_caps_get_free_size(MALLOC_CAP_IRAM_8BIT);
free_size32 = heap_caps_get_free_size(MALLOC_CAP_32BIT);
largest_free_size = heap_caps_get_largest_free_block(MALLOC_CAP_IRAM_8BIT);
ptr = heap_caps_malloc(largest_free_size, MALLOC_CAP_IRAM_8BIT);
TEST_ASSERT((((int)ptr)&0xFF000000)==0x40000000);
/* As the heap allocator may present an overhead for allocated blocks,
* we need to check that the free heap size is now smaller or equal to the former free size. */
TEST_ASSERT(heap_caps_get_free_size(MALLOC_CAP_IRAM_8BIT) <= (free_size - heap_caps_get_allocated_size(ptr)));
TEST_ASSERT(heap_caps_get_free_size(MALLOC_CAP_32BIT) <= (free_size32 - heap_caps_get_allocated_size(ptr)));
free(ptr);
}
#endif
TEST_CASE("heap_caps metadata test", "[heap]")
{
/* need to print something as first printf allocates some heap */
printf("heap_caps metadata test\n");
heap_caps_print_heap_info(MALLOC_CAP_8BIT);
multi_heap_info_t original;
heap_caps_get_info(&original, MALLOC_CAP_8BIT);
void *b = heap_caps_malloc(original.largest_free_block, MALLOC_CAP_8BIT);
TEST_ASSERT_NOT_NULL(b);
printf("After allocating %d bytes:\n", original.largest_free_block);
heap_caps_print_heap_info(MALLOC_CAP_8BIT);
multi_heap_info_t after;
heap_caps_get_info(&after, MALLOC_CAP_8BIT);
TEST_ASSERT(after.largest_free_block <= original.largest_free_block);
TEST_ASSERT(after.total_free_bytes <= original.total_free_bytes);
free(b);
heap_caps_get_info(&after, MALLOC_CAP_8BIT);
printf("\n\n After test, heap status:\n");
heap_caps_print_heap_info(MALLOC_CAP_8BIT);
/* Allow some leeway here, because LWIP sometimes allocates up to 144 bytes in the background
as part of timer management.
*/
TEST_ASSERT_INT32_WITHIN(200, after.total_free_bytes, original.total_free_bytes);
TEST_ASSERT_INT32_WITHIN(200, after.largest_free_block, original.largest_free_block);
TEST_ASSERT(after.minimum_free_bytes < original.total_free_bytes);
}
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32C2)
//IDF-5167
/* Small function runs from IRAM to check that malloc/free/realloc
all work OK when cache is disabled...
*/
#ifndef CONFIG_ESP_SYSTEM_MEMPROT_FEATURE
static IRAM_ATTR __attribute__((noinline)) bool iram_malloc_test(void)
{
spi_flash_guard_get()->start(); // Disables flash cache
bool result = true;
void *x = heap_caps_malloc(64, MALLOC_CAP_EXEC);
result = result && (x != NULL);
void *y = heap_caps_realloc(x, 32, MALLOC_CAP_EXEC);
result = result && (y != NULL);
heap_caps_free(y);
spi_flash_guard_get()->end(); // Re-enables flash cache
return result;
}
TEST_CASE("heap_caps_xxx functions work with flash cache disabled", "[heap]")
{
TEST_ASSERT( iram_malloc_test() );
}
#endif // CONFIG_ESP_SYSTEM_MEMPROT_FEATURE
#endif //!TEMPORARY_DISABLED_FOR_TARGETS(ESP32C2)
#ifdef CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS
TEST_CASE("When enabled, allocation operation failure generates an abort", "[heap][reset=abort,SW_CPU_RESET]")
{
const size_t stupid_allocation_size = (128 * 1024 * 1024);
void *ptr = heap_caps_malloc(stupid_allocation_size, MALLOC_CAP_DEFAULT);
(void)ptr;
TEST_FAIL_MESSAGE("should not be reached");
}
#endif
static bool called_user_failed_hook = false;
void heap_caps_alloc_failed_hook(size_t requested_size, uint32_t caps, const char *function_name)
{
printf("%s was called but failed to allocate %d bytes with 0x%lX capabilities. \n",function_name, requested_size, caps);
called_user_failed_hook = true;
}
TEST_CASE("user provided alloc failed hook must be called when allocation fails", "[heap]")
{
TEST_ASSERT(heap_caps_register_failed_alloc_callback(heap_caps_alloc_failed_hook) == ESP_OK);
const size_t stupid_allocation_size = (128 * 1024 * 1024);
void *ptr = heap_caps_malloc(stupid_allocation_size, MALLOC_CAP_DEFAULT);
TEST_ASSERT(called_user_failed_hook != false);
called_user_failed_hook = false;
ptr = heap_caps_realloc(ptr, stupid_allocation_size, MALLOC_CAP_DEFAULT);
TEST_ASSERT(called_user_failed_hook != false);
called_user_failed_hook = false;
ptr = heap_caps_aligned_alloc(0x200, stupid_allocation_size, MALLOC_CAP_DEFAULT);
TEST_ASSERT(called_user_failed_hook != false);
(void)ptr;
}
TEST_CASE("allocation with invalid capability should also trigger the alloc failed hook", "[heap]")
{
const size_t allocation_size = 64;
const uint32_t invalid_cap = MALLOC_CAP_INVALID;
TEST_ASSERT(heap_caps_register_failed_alloc_callback(heap_caps_alloc_failed_hook) == ESP_OK);
called_user_failed_hook = false;
void *ptr = heap_caps_malloc(allocation_size, invalid_cap);
TEST_ASSERT(called_user_failed_hook != false);
called_user_failed_hook = false;
ptr = heap_caps_realloc(ptr, allocation_size, invalid_cap);
TEST_ASSERT(called_user_failed_hook != false);
called_user_failed_hook = false;
ptr = heap_caps_aligned_alloc(0x200, allocation_size, invalid_cap);
TEST_ASSERT(called_user_failed_hook != false);
(void)ptr;
}
#ifdef CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP
/**
* In MR 16031, the priority of RTC memory has been adjusted to the lowest.
* RTC memory will not be consumed a lot during the startup process.
*/
TEST_CASE("RTC memory shoule be lowest priority and its free size should be big enough", "[heap]")
{
const size_t allocation_size = 1024 * 4;
void *ptr = NULL;
size_t free_size = 0;
ptr = heap_caps_malloc(allocation_size, MALLOC_CAP_DEFAULT);
TEST_ASSERT_NOT_NULL(ptr);
TEST_ASSERT(!esp_ptr_in_rtc_dram_fast(ptr));
free_size = heap_caps_get_free_size(MALLOC_CAP_RTCRAM);
TEST_ASSERT_GREATER_OR_EQUAL(1024 * 4, free_size);
free(ptr);
}
#endif

View File

@@ -0,0 +1,73 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/*
Generic test for realloc
*/
#include <stdlib.h>
#include <string.h>
#include "unity.h"
#include "sdkconfig.h"
#include "esp_heap_caps.h"
#include "soc/soc_memory_layout.h"
#ifndef CONFIG_HEAP_POISONING_COMPREHENSIVE
/* (can't realloc in place if comprehensive is enabled) */
TEST_CASE("realloc shrink buffer in place", "[heap]")
{
void *x = malloc(64);
TEST_ASSERT(x);
void *y = realloc(x, 48);
TEST_ASSERT_EQUAL_PTR(x, y);
}
#endif
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32C2)
//IDF-5167
#ifndef CONFIG_ESP_SYSTEM_MEMPROT_FEATURE
TEST_CASE("realloc shrink buffer with EXEC CAPS", "[heap]")
{
const size_t buffer_size = 64;
void *x = heap_caps_malloc(buffer_size, MALLOC_CAP_EXEC);
TEST_ASSERT(x);
void *y = heap_caps_realloc(x, buffer_size - 16, MALLOC_CAP_EXEC);
TEST_ASSERT(y);
//y needs to fall in a compatible memory area of IRAM:
TEST_ASSERT(esp_ptr_executable(y)|| esp_ptr_in_iram(y) || esp_ptr_in_diram_iram(y));
free(y);
}
TEST_CASE("realloc move data to a new heap type", "[heap]")
{
const char *test = "I am some test content to put in the heap";
char buf[64];
memset(buf, 0xEE, 64);
strlcpy(buf, test, 64);
char *a = malloc(64);
memcpy(a, buf, 64);
// move data from 'a' to IRAM
char *b = heap_caps_realloc(a, 64, MALLOC_CAP_EXEC);
TEST_ASSERT_NOT_NULL(b);
TEST_ASSERT(heap_caps_check_integrity(MALLOC_CAP_INVALID, true));
TEST_ASSERT_EQUAL_HEX32_ARRAY(buf, b, 64 / sizeof(uint32_t));
// Move data back to DRAM
char *c = heap_caps_realloc(b, 48, MALLOC_CAP_8BIT);
TEST_ASSERT_NOT_NULL(c);
TEST_ASSERT(heap_caps_check_integrity(MALLOC_CAP_INVALID, true));
TEST_ASSERT_EQUAL_HEX8_ARRAY(buf, c, 48);
free(c);
}
#endif // CONFIG_ESP_SYSTEM_MEMPROT_FEATURE
#endif //!TEMPORARY_DISABLED_FOR_TARGETS(ESP32C2)

View File

@@ -0,0 +1,96 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/*
Tests for registering new heap memory at runtime
*/
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include "unity.h"
#include "esp_heap_caps_init.h"
#include "esp_system.h"
#include "heap_memory_layout.h"
/* NOTE: This is not a well-formed unit test, it leaks memory */
TEST_CASE("Allocate new heap at runtime", "[heap][ignore]")
{
const size_t BUF_SZ = 1000;
const size_t HEAP_OVERHEAD_MAX = 200;
void *buffer = malloc(BUF_SZ);
TEST_ASSERT_NOT_NULL(buffer);
uint32_t before_free = esp_get_free_heap_size();
TEST_ESP_OK( heap_caps_add_region((intptr_t)buffer, (intptr_t)buffer + BUF_SZ) );
uint32_t after_free = esp_get_free_heap_size();
printf("Before %"PRIu32" after %"PRIu32"\n", before_free, after_free);
/* allow for some 'heap overhead' from accounting structures */
TEST_ASSERT(after_free >= before_free + BUF_SZ - HEAP_OVERHEAD_MAX);
}
/* NOTE: This is not a well-formed unit test, it leaks memory and
may fail if run twice in a row without a reset.
*/
TEST_CASE("Allocate new heap with new capability", "[heap][ignore]")
{
const size_t BUF_SZ = 100;
#ifdef CONFIG_ESP_SYSTEM_MEMPROT_FEATURE
const size_t ALLOC_SZ = 32;
#else
const size_t ALLOC_SZ = 64; // More than half of BUF_SZ
#endif
const uint32_t MALLOC_CAP_INVENTED = (1 << 30); /* this must be unused in esp_heap_caps.h */
/* no memory exists to provide this capability */
TEST_ASSERT_NULL( heap_caps_malloc(ALLOC_SZ, MALLOC_CAP_INVENTED) );
void *buffer = malloc(BUF_SZ);
TEST_ASSERT_NOT_NULL(buffer);
uint32_t caps[SOC_MEMORY_TYPE_NO_PRIOS] = { MALLOC_CAP_INVENTED };
TEST_ESP_OK( heap_caps_add_region_with_caps(caps, (intptr_t)buffer, (intptr_t)buffer + BUF_SZ) );
/* ta-da, it's now possible! */
TEST_ASSERT_NOT_NULL( heap_caps_malloc(ALLOC_SZ, MALLOC_CAP_INVENTED) );
}
/* NOTE: This is not a well-formed unit test.
* If run twice without a reset, it will failed.
*/
TEST_CASE("Add .bss memory to heap region runtime", "[heap][ignore]")
{
#define BUF_SZ 1000
#define HEAP_OVERHEAD_MAX 200
static uint8_t s_buffer[BUF_SZ];
printf("s_buffer start %08x end %08x\n", (intptr_t)s_buffer, (intptr_t)s_buffer + BUF_SZ);
uint32_t before_free = esp_get_free_heap_size();
TEST_ESP_OK( heap_caps_add_region((intptr_t)s_buffer, (intptr_t)s_buffer + BUF_SZ) );
uint32_t after_free = esp_get_free_heap_size();
printf("Before %"PRIu32" after %"PRIu32"\n", before_free, after_free);
/* allow for some 'heap overhead' from accounting structures */
TEST_ASSERT(after_free >= before_free + BUF_SZ - HEAP_OVERHEAD_MAX);
/* Twice add must be failed */
TEST_ASSERT( (heap_caps_add_region((intptr_t)s_buffer, (intptr_t)s_buffer + BUF_SZ) != ESP_OK) );
}
extern esp_err_t heap_caps_check_add_region_allowed(intptr_t heap_start, intptr_t heap_end, intptr_t start, intptr_t end);
TEST_CASE("Add heap region address range checks", "[heap]")
{
const intptr_t heap_start = 0x1000;
const intptr_t heap_end = 0x3000;
TEST_ASSERT_TRUE(heap_caps_check_add_region_allowed(heap_start, heap_end, 0x0, 0x1000));
TEST_ASSERT_TRUE(heap_caps_check_add_region_allowed(heap_start, heap_end, 0x1000, 0x2000));
TEST_ASSERT_TRUE(heap_caps_check_add_region_allowed(heap_start, heap_end, 0x1000, 0x3000));
TEST_ASSERT_TRUE(heap_caps_check_add_region_allowed(heap_start, heap_end, 0x3000, 0x4000));
TEST_ASSERT_FALSE(heap_caps_check_add_region_allowed(heap_start, heap_end, 0x0, 0x2000));
TEST_ASSERT_FALSE(heap_caps_check_add_region_allowed(heap_start, heap_end, 0x0, 0x4000));
TEST_ASSERT_FALSE(heap_caps_check_add_region_allowed(heap_start, heap_end, 0x1000, 0x4000));
TEST_ASSERT_FALSE(heap_caps_check_add_region_allowed(heap_start, heap_end, 0x2000, 0x4000));
}

View File

@@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.generic
@pytest.mark.supported_targets
def test_heap(dut: Dut) -> None:
dut.expect_exact('Press ENTER to see the list of tests')
dut.write('*')
dut.expect_unity_test_output(timeout=300)

View File

@@ -0,0 +1,2 @@
CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n
CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n # memory protection needs to be disabled for certain tests