522 lines
14 KiB
C
522 lines
14 KiB
C
/**
|
|
* Copyright (c) 2026 Dominic Masters
|
|
*
|
|
* This software is released under the MIT License.
|
|
* https://opensource.org/licenses/MIT
|
|
*/
|
|
|
|
#include "dusktest.h"
|
|
#include "util/memory.h"
|
|
|
|
static void test_memoryAllocate(void **state) {
|
|
|
|
|
|
// Memory should allocate
|
|
void *ptr = memoryAllocate(128);
|
|
assert_non_null(ptr);
|
|
|
|
// Allocating should be tracked
|
|
assert_int_equal(memoryGetAllocatedCount(), 1);
|
|
|
|
void *ptr2 = memoryAllocate(256);
|
|
assert_non_null(ptr2);
|
|
assert_int_equal(memoryGetAllocatedCount(), 2);
|
|
|
|
// Free memory
|
|
memoryFree(ptr);
|
|
assert_int_equal(memoryGetAllocatedCount(), 1);
|
|
memoryFree(ptr2);
|
|
assert_int_equal(memoryGetAllocatedCount(), 0);
|
|
|
|
// Should not be able to allocate 0 bytes
|
|
expect_assert_failure(memoryAllocate(0));
|
|
|
|
// Should not be able to allocate more memory than possible
|
|
expect_assert_failure(memoryAllocate(SIZE_MAX));
|
|
|
|
// Expect no leaks
|
|
assert_int_equal(memoryGetAllocatedCount(), 0);
|
|
}
|
|
|
|
static void test_memoryFree(void **state) {
|
|
|
|
|
|
// Create some memory
|
|
void *ptr = memoryAllocate(64);
|
|
assert_non_null(ptr);
|
|
assert_int_equal(memoryGetAllocatedCount(), 1);
|
|
|
|
// Free
|
|
memoryFree(ptr);
|
|
|
|
// Allocated count should decrease
|
|
assert_int_equal(memoryGetAllocatedCount(), 0);
|
|
|
|
ptr = memoryAllocate(32);
|
|
assert_non_null(ptr);
|
|
void *ptr2 = memoryAllocate(32);
|
|
assert_non_null(ptr2);
|
|
assert_int_equal(memoryGetAllocatedCount(), 2);
|
|
|
|
// Free both
|
|
memoryFree(ptr);
|
|
assert_int_equal(memoryGetAllocatedCount(), 1);
|
|
memoryFree(ptr2);
|
|
assert_int_equal(memoryGetAllocatedCount(), 0);
|
|
|
|
// Expect unable to free NULL
|
|
expect_assert_failure(memoryFree(NULL));
|
|
|
|
// Expect no leaks
|
|
assert_int_equal(memoryGetAllocatedCount(), 0);
|
|
}
|
|
|
|
static void test_memoryCopy(void **state) {
|
|
|
|
|
|
// Create some memory
|
|
size_t size = 32;
|
|
void *src = memoryAllocate(size);
|
|
void *dest = memoryAllocate(size);
|
|
assert_non_null(src);
|
|
assert_non_null(dest);
|
|
|
|
// Fill source with known pattern and copy
|
|
memorySet(src, 0xAB, size);
|
|
memoryCopy(dest, src, size);
|
|
assert_memory_equal(src, dest, size);
|
|
|
|
// Should not be able to copy to NULL
|
|
expect_assert_failure(memoryCopy(NULL, src, size));
|
|
|
|
// Should not be able to copy from NULL
|
|
expect_assert_failure(memoryCopy(dest, NULL, size));
|
|
|
|
// Cannot copy 0 bytes
|
|
expect_assert_failure(memoryCopy(dest, src, 0));
|
|
|
|
// Cannot copy to itself
|
|
expect_assert_failure(memoryCopy(src, src, size));
|
|
|
|
// Overlapping regions (should assert)
|
|
void *overlap = memoryAllocate(size * 2);
|
|
expect_assert_failure(memoryCopy((uint8_t*)overlap, (uint8_t*)overlap + 8, size));
|
|
expect_assert_failure(memoryCopy((uint8_t*)overlap + 8, (uint8_t*)overlap, size));
|
|
memoryFree(overlap);
|
|
|
|
memoryFree(src);
|
|
memoryFree(dest);
|
|
|
|
// Expect no leaks
|
|
assert_int_equal(memoryGetAllocatedCount(), 0);
|
|
}
|
|
|
|
static void test_memorySet(void **state) {
|
|
|
|
|
|
// Allocate memory
|
|
size_t size = 16;
|
|
void *ptr = memoryAllocate(size);
|
|
assert_non_null(ptr);
|
|
|
|
// Fill with pattern
|
|
memorySet(ptr, 0xCD, size);
|
|
uint8_t expected[16];
|
|
for(size_t i = 0; i < size; i++) {
|
|
expected[i] = 0xCD;
|
|
}
|
|
assert_memory_equal(ptr, expected, size);
|
|
|
|
// Cannot set to NULL
|
|
expect_assert_failure(memorySet(NULL, 0x00, size));
|
|
|
|
// Canot set 0 bytes
|
|
expect_assert_failure(memorySet(ptr, 0x00, 0));
|
|
|
|
// Set with value outside 0-255 (should be cast to uint8_t)
|
|
memorySet(ptr, 0x1FF, size);
|
|
for(size_t i = 0; i < size; i++) {
|
|
assert_int_equal(((uint8_t*)ptr)[i], 0xFF);
|
|
}
|
|
|
|
memoryFree(ptr);
|
|
|
|
// Expect no leaks
|
|
assert_int_equal(memoryGetAllocatedCount(), 0);
|
|
}
|
|
|
|
static void test_memoryZero(void **state) {
|
|
|
|
|
|
// Create some memory
|
|
size_t size = 20;
|
|
void *ptr = memoryAllocate(size);
|
|
assert_non_null(ptr);
|
|
|
|
// Fill and then zero
|
|
memorySet(ptr, 0xEF, size);
|
|
memoryZero(ptr, size);
|
|
|
|
// All memory should be zeroed
|
|
uint8_t expected[20] = {0};
|
|
assert_memory_equal(ptr, expected, size);
|
|
|
|
// Cannot zero to NULL pointer
|
|
expect_assert_failure(memoryZero(NULL, size));
|
|
|
|
// Cannot zero 0 bytes
|
|
expect_assert_failure(memoryZero(ptr, 0));
|
|
|
|
memoryFree(ptr);
|
|
|
|
// Expect no leaks
|
|
assert_int_equal(memoryGetAllocatedCount(), 0);
|
|
}
|
|
|
|
static void test_memoryCopyRangeSafe(void **state) {
|
|
|
|
|
|
// Create some memory
|
|
size_t size = 10;
|
|
void *src = memoryAllocate(size);
|
|
void *dest = memoryAllocate(size);
|
|
assert_non_null(src);
|
|
assert_non_null(dest);
|
|
|
|
// Initialize source with known pattern
|
|
for(size_t i = 0; i < size; i++) {
|
|
((uint8_t*)src)[i] = (uint8_t)(i + 1); // 1, 2, ..., 10
|
|
}
|
|
|
|
// Copy a range from src[2] to src[7] (5 bytes) into dest
|
|
memoryCopyRangeSafe(dest, (uint8_t*)src + 2, (uint8_t*)src + 7, size);
|
|
uint8_t expected[10] = {3, 4, 5, 6, 7}; // Expected data in dest
|
|
assert_memory_equal(dest, expected, 5);
|
|
|
|
// Copy entire buffer
|
|
memoryCopyRangeSafe(dest, src, (uint8_t*)src + size, size);
|
|
assert_memory_equal(dest, src, size);
|
|
|
|
// Cannot copy to NULL destination
|
|
expect_assert_failure(memoryCopyRangeSafe(NULL, src, (uint8_t*)src + 5, size));
|
|
|
|
// Cannot copy from NULL start
|
|
expect_assert_failure(memoryCopyRangeSafe(dest, NULL, (uint8_t*)src + 5, size));
|
|
|
|
// Cannot copy from NULL end
|
|
expect_assert_failure(memoryCopyRangeSafe(dest, (uint8_t*)src, NULL, size));
|
|
|
|
// Start and end are the same
|
|
expect_assert_failure(memoryCopyRangeSafe(dest, (uint8_t*)src, (uint8_t*)src, size));
|
|
|
|
// End is before start
|
|
expect_assert_failure(memoryCopyRangeSafe(dest, (uint8_t*)src + 5, (uint8_t*)src + 2, size));
|
|
|
|
// Size to copy exceeds maximum
|
|
expect_assert_failure(memoryCopyRangeSafe(dest, (uint8_t*)src, (uint8_t*)src + 10, 5));
|
|
|
|
memoryFree(src);
|
|
memoryFree(dest);
|
|
|
|
// Expect no leaks
|
|
assert_int_equal(memoryGetAllocatedCount(), 0);
|
|
}
|
|
|
|
static void test_memoryMove(void **state) {
|
|
|
|
|
|
// Create some memory
|
|
size_t size = 15;
|
|
void *ptr = memoryAllocate(size + 5); // Extra space for overlap
|
|
assert_non_null(ptr);
|
|
|
|
// Initialize memory with known pattern
|
|
for(size_t i = 0; i < size + 5; i++) {
|
|
((uint8_t*)ptr)[i] = (uint8_t)(i + 1); // 1, 2, ..., size+5
|
|
}
|
|
|
|
// Move overlapping region: from ptr[0..14] to ptr[5..19]
|
|
memoryMove((uint8_t*)ptr + 5, ptr, size);
|
|
|
|
// Expected pattern after move
|
|
uint8_t expected[20];
|
|
for(size_t i = 0; i < 5; i++) {
|
|
expected[i] = (uint8_t)(i + 1); // Original first 5 bytes
|
|
}
|
|
for(size_t i = 5; i < 20; i++) {
|
|
expected[i] = (uint8_t)(i - 4); // Moved bytes
|
|
}
|
|
assert_memory_equal(ptr, expected, size + 5);
|
|
|
|
// Overlap backward: move ptr[5..19] to ptr[0..14]
|
|
for(size_t i = 0; i < size + 5; i++) {
|
|
((uint8_t*)ptr)[i] = (uint8_t)(i + 1);
|
|
}
|
|
memoryMove(ptr, (uint8_t*)ptr + 5, size);
|
|
for(size_t i = 0; i < size; i++) {
|
|
assert_int_equal(((uint8_t*)ptr)[i], (uint8_t)(i + 6));
|
|
}
|
|
|
|
// Cannot move to NULL
|
|
expect_assert_failure(memoryMove(NULL, ptr, size));
|
|
|
|
// Cannot move from NULL
|
|
expect_assert_failure(memoryMove(ptr, NULL, size));
|
|
|
|
// Cannot move 0 bytes
|
|
expect_assert_failure(memoryMove(ptr, ptr, 0));
|
|
|
|
// Cannot move to itself
|
|
expect_assert_failure(memoryMove(ptr, ptr, size));
|
|
|
|
memoryFree(ptr);
|
|
|
|
// Expect no leaks
|
|
assert_int_equal(memoryGetAllocatedCount(), 0);
|
|
}
|
|
|
|
static void test_memoryCompare(void **state) {
|
|
|
|
|
|
// Create two memory blocks
|
|
size_t size = 8;
|
|
void *a = memoryAllocate(size);
|
|
void *b = memoryAllocate(size);
|
|
assert_non_null(a);
|
|
assert_non_null(b);
|
|
|
|
// Initialize both with same data
|
|
for(size_t i = 0; i < size; i++) {
|
|
((uint8_t*)a)[i] = (uint8_t)(i + 1);
|
|
((uint8_t*)b)[i] = (uint8_t)(i + 1);
|
|
}
|
|
|
|
// They should be equal
|
|
assert_int_equal(memoryCompare(a, b, size), 0);
|
|
|
|
// Change b and compare again
|
|
((uint8_t*)b)[size - 1] = 0xFF;
|
|
assert_true(memoryCompare(a, b, size) < 0);
|
|
|
|
// Change a and compare again
|
|
((uint8_t*)a)[size - 1] = 0xFF;
|
|
((uint8_t*)a)[size - 2] = 0xFE;
|
|
assert_true(memoryCompare(a, b, size) > 0);
|
|
|
|
// CAnnot compare with NULL a
|
|
expect_assert_failure(memoryCompare(NULL, b, size));
|
|
|
|
// Cannot compare with NULL b
|
|
expect_assert_failure(memoryCompare(a, NULL, size));
|
|
|
|
// Comparing with self always equal
|
|
assert_int_equal(memoryCompare(a, a, size), 0);
|
|
assert_int_equal(memoryCompare(b, b, size), 0);
|
|
|
|
// Comparison of 0 bytes is always true
|
|
assert_int_equal(memoryCompare(a, b, 0), 0);
|
|
|
|
// Compare different sizes (should assert, so we only test the API contract)
|
|
// Not possible with current API, as size is explicit, but document intent.
|
|
|
|
memoryFree(a);
|
|
memoryFree(b);
|
|
|
|
// Expect no leaks
|
|
assert_int_equal(memoryGetAllocatedCount(), 0);
|
|
}
|
|
|
|
static void test_memoryReallocate(void **state) {
|
|
|
|
|
|
size_t initialSize = 16;
|
|
void *ptr = memoryAllocate(initialSize);
|
|
assert_non_null(ptr);
|
|
|
|
// Reallocate to a larger size
|
|
size_t newSize = 32;
|
|
memoryReallocate(&ptr, newSize);
|
|
assert_non_null(ptr);
|
|
|
|
// Reallocate to a smaller size
|
|
size_t smallerSize = 8;
|
|
memoryReallocate(&ptr, smallerSize);
|
|
assert_non_null(ptr);
|
|
|
|
// Cannot realloc to size 0
|
|
expect_assert_failure(memoryReallocate(&ptr, 0));
|
|
|
|
// Cannot realloc NULL pointer
|
|
expect_assert_failure(memoryReallocate(NULL, 16));
|
|
|
|
// Cannot reallocate more memory than possible
|
|
expect_assert_failure(memoryReallocate(&ptr, SIZE_MAX));
|
|
|
|
// All we really care about is that the pointer is valid after reallocations
|
|
memoryFree(ptr);
|
|
|
|
// Expect no leaks
|
|
assert_int_equal(memoryGetAllocatedCount(), 0);
|
|
}
|
|
|
|
static void test_memoryResize(void **state) {
|
|
|
|
|
|
size_t initialSize = 32;
|
|
void *ptr = memoryAllocate(initialSize);
|
|
assert_non_null(ptr);
|
|
assert_int_equal(memoryGetAllocatedCount(), 1);
|
|
|
|
// Initialize memory
|
|
for(size_t i = 0; i < initialSize; i++) {
|
|
((uint8_t*)ptr)[i] = (uint8_t)(i + 1);
|
|
}
|
|
|
|
// Equal size shouldn't touch the pointer
|
|
void *oldPtr = ptr;
|
|
memoryResize(&ptr, initialSize, initialSize);
|
|
assert_non_null(ptr);
|
|
assert_ptr_equal(ptr, oldPtr);
|
|
assert_int_equal(memoryGetAllocatedCount(), 1);
|
|
|
|
// Reallocate to a larger size, it should not leak.
|
|
size_t newSize = 64;
|
|
memoryResize(&ptr, initialSize, newSize);
|
|
assert_non_null(ptr);
|
|
assert_int_equal(memoryGetAllocatedCount(), 1);
|
|
|
|
// Check that old data is preserved
|
|
for(size_t i = 0; i < initialSize; i++) {
|
|
assert_int_equal(((uint8_t*)ptr)[i], (uint8_t)(i + 1));
|
|
}
|
|
|
|
// Reallocate to a smaller size
|
|
size_t smallerSize = 16;
|
|
memoryResize(&ptr, newSize, smallerSize);
|
|
assert_non_null(ptr);
|
|
assert_int_equal(memoryGetAllocatedCount(), 1);
|
|
|
|
// Check that data is still correct up to the smaller size
|
|
for(size_t i = 0; i < smallerSize; i++) {
|
|
assert_int_equal(((uint8_t*)ptr)[i], (uint8_t)(i + 1));
|
|
}
|
|
|
|
// Cannot realloc to size 0
|
|
expect_assert_failure(memoryResize(&ptr, smallerSize, 0));
|
|
assert_int_equal(memoryGetAllocatedCount(), 1);
|
|
|
|
// Cannot take NULL
|
|
expect_assert_failure(memoryResize(NULL, smallerSize, 16));
|
|
assert_int_equal(memoryGetAllocatedCount(), 1);
|
|
|
|
// memoryResize with oldsize of 0 not possible
|
|
expect_assert_failure(memoryResize(&ptr, 0, 16));
|
|
assert_int_equal(memoryGetAllocatedCount(), 1);
|
|
|
|
// Cannot resize more memory than possible
|
|
expect_assert_failure(memoryResize(&ptr, smallerSize, SIZE_MAX));
|
|
assert_int_equal(memoryGetAllocatedCount(), 1);
|
|
|
|
memoryFree(ptr);
|
|
|
|
// Expect no leaks
|
|
assert_int_equal(memoryGetAllocatedCount(), 0);
|
|
}
|
|
|
|
static void test_memoryCopyInterleaved(void **state) {
|
|
|
|
|
|
// Basic: copy 4 uint32_t values from contiguous source into every-other dest
|
|
// slot (stride = 2*sizeof(uint32_t)).
|
|
uint32_t src[4] = { 0x11111111, 0x22222222, 0x33333333, 0x44444444 };
|
|
uint32_t dest[8];
|
|
memoryZero(dest, sizeof(dest));
|
|
|
|
memoryCopyInterleaved(
|
|
dest, sizeof(uint32_t) * 2,
|
|
src, sizeof(uint32_t),
|
|
sizeof(uint32_t), 4
|
|
);
|
|
|
|
assert_int_equal(dest[0], 0x11111111);
|
|
assert_int_equal(dest[2], 0x22222222);
|
|
assert_int_equal(dest[4], 0x33333333);
|
|
assert_int_equal(dest[6], 0x44444444);
|
|
// Skipped slots should be untouched.
|
|
assert_int_equal(dest[1], 0);
|
|
assert_int_equal(dest[3], 0);
|
|
assert_int_equal(dest[5], 0);
|
|
assert_int_equal(dest[7], 0);
|
|
|
|
// Strided source: pick every second element into a contiguous destination.
|
|
uint32_t src2[8] = { 0xA, 0xFF, 0xB, 0xFF, 0xC, 0xFF, 0xD, 0xFF };
|
|
uint32_t dest2[4];
|
|
memoryZero(dest2, sizeof(dest2));
|
|
|
|
memoryCopyInterleaved(
|
|
dest2, sizeof(uint32_t),
|
|
src2, sizeof(uint32_t) * 2,
|
|
sizeof(uint32_t), 4
|
|
);
|
|
|
|
assert_int_equal(dest2[0], 0xA);
|
|
assert_int_equal(dest2[1], 0xB);
|
|
assert_int_equal(dest2[2], 0xC);
|
|
assert_int_equal(dest2[3], 0xD);
|
|
|
|
// Multi-byte element: copy structs field-by-field pattern.
|
|
typedef struct { uint32_t x; uint32_t y; } pair_t;
|
|
uint32_t yVals[3] = { 10, 20, 30 };
|
|
pair_t pairs[3];
|
|
memoryZero(pairs, sizeof(pairs));
|
|
|
|
memoryCopyInterleaved(
|
|
&pairs[0].y, sizeof(pair_t),
|
|
yVals, sizeof(uint32_t),
|
|
sizeof(uint32_t), 3
|
|
);
|
|
|
|
assert_int_equal(pairs[0].y, 10);
|
|
assert_int_equal(pairs[1].y, 20);
|
|
assert_int_equal(pairs[2].y, 30);
|
|
assert_int_equal(pairs[0].x, 0);
|
|
assert_int_equal(pairs[1].x, 0);
|
|
assert_int_equal(pairs[2].x, 0);
|
|
|
|
// Count of 0 is a no-op.
|
|
uint32_t untouched[2] = { 0xDEAD, 0xBEEF };
|
|
memoryCopyInterleaved(untouched, 4, src, 4, 4, 0);
|
|
assert_int_equal(untouched[0], 0xDEAD);
|
|
|
|
// NULL dest
|
|
expect_assert_failure(memoryCopyInterleaved(NULL, 8, src, 4, 4, 2));
|
|
|
|
// NULL src
|
|
expect_assert_failure(memoryCopyInterleaved(dest, 8, NULL, 4, 4, 2));
|
|
|
|
// elementSize 0
|
|
expect_assert_failure(memoryCopyInterleaved(dest, 8, src, 4, 0, 2));
|
|
|
|
// destStride smaller than elementSize
|
|
expect_assert_failure(memoryCopyInterleaved(dest, 2, src, 4, 4, 2));
|
|
|
|
// srcStride smaller than elementSize
|
|
expect_assert_failure(memoryCopyInterleaved(dest, 8, src, 2, 4, 2));
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
const struct CMUnitTest tests[] = {
|
|
cmocka_unit_test(test_memoryAllocate),
|
|
cmocka_unit_test(test_memoryFree),
|
|
cmocka_unit_test(test_memoryCopy),
|
|
cmocka_unit_test(test_memorySet),
|
|
cmocka_unit_test(test_memoryZero),
|
|
cmocka_unit_test(test_memoryCopyRangeSafe),
|
|
cmocka_unit_test(test_memoryMove),
|
|
cmocka_unit_test(test_memoryCompare),
|
|
cmocka_unit_test(test_memoryReallocate),
|
|
cmocka_unit_test(test_memoryResize),
|
|
cmocka_unit_test(test_memoryCopyInterleaved),
|
|
};
|
|
return cmocka_run_group_tests(tests, NULL, NULL);
|
|
} |