1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-03-03 14:52:21 +02:00

Add C memory contexts.

This commit is contained in:
David Steele 2017-10-16 11:25:49 -04:00
parent 6f5186f9e6
commit 583a76f605
5 changed files with 846 additions and 0 deletions

View File

@ -40,6 +40,10 @@
<release-item>
<p>Page checksum module uses new C error handler.</p>
</release-item>
<release-item>
<p>Add C memory contexts.</p>
</release-item>
</release-refactor-list>
</release-core-list>

395
src/common/memContext.c Normal file
View File

@ -0,0 +1,395 @@
/***********************************************************************************************************************************
Memory Context Manager
***********************************************************************************************************************************/
#include <stdlib.h>
#include <string.h>
#include "common/errorType.h"
#include "common/memContext.h"
/***********************************************************************************************************************************
Memory context states
***********************************************************************************************************************************/
typedef enum {memContextStateFree = 0, memContextStateFreeing, memContextStateActive} MemContextState;
/***********************************************************************************************************************************
Contains information about the memory context
***********************************************************************************************************************************/
struct MemContext
{
MemContextState state; // Current state of the context
const char name[MEM_CONTEXT_NAME_SIZE + 1]; // Indicates what the context is being used for
MemContext *contextParent; // All contexts have a parent except top
MemContext **contextChildList; // List of contexts created in this context
int contextChildListSize; // Size of child context list (not the actual count of contexts)
void **allocList; // List of memory allocations created in this context
int allocListSize; // Size of alloc list (not the actual count of allocations)
MemContextCallback callbackFunction; // Function to call before the context is freed
void *callbackArgument; // Argument to pass to callback function
};
/***********************************************************************************************************************************
Top context
The top context always exists and can never be freed. All other contexts are children of the top context. The top context is
generally used to allocate memory that exists for the life of the program.
***********************************************************************************************************************************/
MemContext contextTop = {.state = memContextStateActive, .name = "TOP"};
/***********************************************************************************************************************************
Current context
All memory allocations will be done from the current context. Initialized to top context at execution start.
***********************************************************************************************************************************/
MemContext *contextCurrent = &contextTop;
/***********************************************************************************************************************************
Wrapper around malloc()
***********************************************************************************************************************************/
static void *memAllocInternal(size_t size, bool zero)
{
// Allocate memory
void *buffer = malloc(size);
// Error when malloc fails
if (!buffer)
ERROR_THROW(MemoryError, "unable to allocate %lu bytes", size);
// Zero the memory when requested
if (zero)
memset(buffer, 0, size);
// Return the buffer
return buffer;
}
/***********************************************************************************************************************************
Wrapper around realloc()
***********************************************************************************************************************************/
static void *memReAllocInternal(void *bufferOld, size_t sizeOld, size_t sizeNew, bool zeroNew)
{
// Allocate memory
void *bufferNew = realloc(bufferOld, sizeNew);
// Error when realloc fails
if(!bufferNew)
ERROR_THROW(MemoryError, "unable to reallocate %lu bytes", sizeNew);
// Zero the new memory when requested - old memory is left untouched else why bother with a realloc?
if (zeroNew)
memset(bufferNew + sizeOld, 0, sizeNew - sizeOld);
// Return the buffer
return bufferNew;
}
/***********************************************************************************************************************************
Wrapper around free()
***********************************************************************************************************************************/
static void memFreeInternal(void *buffer)
{
// Error if pointer is null
if(!buffer)
ERROR_THROW(MemoryError, "unable to free null pointer");
// Free the buffer
free(buffer);
}
/***********************************************************************************************************************************
Create a new memory context
***********************************************************************************************************************************/
MemContext *
memContextNew(const char *name)
{
// Check context name length
if (strlen(name) == 0 || strlen(name) > MEM_CONTEXT_NAME_SIZE)
ERROR_THROW(AssertError, "context name length must be > 0 and <= %d", MEM_CONTEXT_NAME_SIZE);
// Try to find space for the new context
int contextIdx;
for (contextIdx = 0; contextIdx < memContextCurrent()->contextChildListSize; contextIdx++)
if (!memContextCurrent()->contextChildList[contextIdx] ||
memContextCurrent()->contextChildList[contextIdx]->state == memContextStateFree)
{
break;
}
// If no space was found then allocate more
if (contextIdx == memContextCurrent()->contextChildListSize)
{
// If no space has been allocated to the list
if (memContextCurrent()->contextChildListSize == 0)
{
// Allocate memory before modifying anything else in case there is an error
memContextCurrent()->contextChildList = memAllocInternal(sizeof(MemContext *) * MEM_CONTEXT_INITIAL_SIZE, true);
// Set new list size
memContextCurrent()->contextChildListSize = MEM_CONTEXT_INITIAL_SIZE;
}
// Else grow the list
else
{
// Calculate new list size
int contextChildListSizeNew = memContextCurrent()->contextChildListSize * 2;
// ReAllocate memory before modifying anything else in case there is an error
memContextCurrent()->contextChildList = memReAllocInternal(
memContextCurrent()->contextChildList, sizeof(MemContext *) * memContextCurrent()->contextChildListSize,
sizeof(MemContext *) * contextChildListSizeNew, true);
// Set new list size
memContextCurrent()->contextChildListSize = contextChildListSizeNew;
}
}
// If the context has not been allocated yet
if (!memContextCurrent()->contextChildList[contextIdx])
memContextCurrent()->contextChildList[contextIdx] = memAllocInternal(sizeof(MemContext), true);
// Get the context
MemContext *this = memContextCurrent()->contextChildList[contextIdx];
// Create initial space for allocations
this->allocList = memAllocInternal(sizeof(void *) * MEM_CONTEXT_ALLOC_INITIAL_SIZE, true);
this->allocListSize = MEM_CONTEXT_ALLOC_INITIAL_SIZE;
// Set the context name
strcpy((char *)this->name, name);
// Set new context active
this->state = memContextStateActive;
// Set current context as the parent
this->contextParent = memContextCurrent();
// Return context
return this;
}
/***********************************************************************************************************************************
Register a callback to be called just before the context is freed
***********************************************************************************************************************************/
void
memContextCallback(MemContext *this, void (*callbackFunction)(void *), void *callbackArgument)
{
// Error if context is not active
if (this->state != memContextStateActive)
ERROR_THROW(AssertError, "cannot assign callback to inactive context");
// Top context cannot have a callback
if (this == memContextTop())
ERROR_THROW(AssertError, "top context may not have a callback");
// Error if callback has already been set - there may be valid use cases for this but error until one is found
if (this->callbackFunction)
ERROR_THROW(AssertError, "callback is already set for context '%s'", this->name);
// Set callback function and argument
this->callbackFunction = callbackFunction;
this->callbackArgument = callbackArgument;
}
/***********************************************************************************************************************************
Allocate memory in the memory context and optionally zero it.
***********************************************************************************************************************************/
static void *
memContextAlloc(size_t size, bool zero)
{
// Find space for the new allocation
int allocIdx;
for (allocIdx = 0; allocIdx < memContextCurrent()->allocListSize; allocIdx++)
if (!memContextCurrent()->allocList[allocIdx])
break;
// If no space was found then allocate more
if (allocIdx == memContextCurrent()->allocListSize)
{
// Only the top context will not have initial space for allocations
if (memContextCurrent()->allocListSize == 0)
{
// Allocate memory before modifying anything else in case there is an error
memContextCurrent()->allocList = memAllocInternal(sizeof(void *) * MEM_CONTEXT_ALLOC_INITIAL_SIZE, true);
// Set new size
memContextCurrent()->allocListSize = MEM_CONTEXT_ALLOC_INITIAL_SIZE;
}
// Else grow the list
else
{
// Calculate new list size
int allocListSizeNew = memContextCurrent()->allocListSize * 2;
// ReAllocate memory before modifying anything else in case there is an error
memContextCurrent()->allocList = memReAllocInternal(
memContextCurrent()->allocList, sizeof(void *) * memContextCurrent()->contextChildListSize,
sizeof(MemContext *) * allocListSizeNew, true);
// Set new size
memContextCurrent()->allocListSize = allocListSizeNew;
}
}
// Allocate the memory
memContextCurrent()->allocList[allocIdx] = memAllocInternal(size, zero);
// Return buffer
return memContextCurrent()->allocList[allocIdx];
}
/***********************************************************************************************************************************
Allocate zeroed memory in the memory context
***********************************************************************************************************************************/
void *
memNew(size_t size)
{
return memContextAlloc(size, true);
}
/***********************************************************************************************************************************
Allocate memory in the memory context without initializing it
***********************************************************************************************************************************/
void *
memNewRaw(size_t size)
{
return memContextAlloc(size, false);
}
/***********************************************************************************************************************************
Free a memory allocation in the context
***********************************************************************************************************************************/
void
memFree(void *buffer)
{
// Error if buffer is null
if (!buffer)
ERROR_THROW(AssertError, "unable to free null allocation");
// Find memory to free
int allocIdx;
for (allocIdx = 0; allocIdx < memContextCurrent()->allocListSize; allocIdx++)
if (memContextCurrent()->allocList[allocIdx] == buffer)
break;
// Error if the buffer was not found
if (allocIdx == memContextCurrent()->allocListSize)
ERROR_THROW(AssertError, "unable to find allocation");
// Free the buffer
memFreeInternal(memContextCurrent()->allocList[allocIdx]);
memContextCurrent()->allocList[allocIdx] = NULL;
}
/***********************************************************************************************************************************
Switch to the specified context and return the old context
***********************************************************************************************************************************/
MemContext *
memContextSwitch(MemContext *this)
{
// Error if context is not active
if (this->state != memContextStateActive)
ERROR_THROW(AssertError, "cannot switch to inactive context");
MemContext *memContextOld = memContextCurrent();
contextCurrent = this;
return memContextOld;
}
/***********************************************************************************************************************************
Return the top context
***********************************************************************************************************************************/
MemContext *
memContextTop()
{
return &contextTop;
}
/***********************************************************************************************************************************
Return the current context
***********************************************************************************************************************************/
MemContext *
memContextCurrent()
{
return contextCurrent;
}
/***********************************************************************************************************************************
Return the context name
***********************************************************************************************************************************/
const char *
memContextName(MemContext *this)
{
// Error if context is not active
if (this->state != memContextStateActive)
ERROR_THROW(AssertError, "cannot get name for inactive context");
return this->name;
}
/***********************************************************************************************************************************
memContextFree - free all memory used by the context and all child contexts
***********************************************************************************************************************************/
void
memContextFree(MemContext *this)
{
// If context is already freeing then return if memContextFree() is called again - this can happen in callbacks
if (this->state == memContextStateFreeing)
return;
// Top context cannot be freed
if (this == memContextTop())
ERROR_THROW(AssertError, "cannot free top context");
// Current context cannot be freed
if (this == memContextCurrent())
ERROR_THROW(AssertError, "cannot free current context '%s'", this->name);
// Error if context is not active
if (this->state != memContextStateActive)
ERROR_THROW(AssertError, "cannot free inactive context");
// Free child contexts
if (this->contextChildListSize > 0)
for (int contextIdx = 0; contextIdx < this->contextChildListSize; contextIdx++)
if (this->contextChildList[contextIdx] && this->contextChildList[contextIdx]->state == memContextStateActive)
memContextFree(this->contextChildList[contextIdx]);
// Set state to freeing now that there are no child contexts. Child contexts might need to interact with their parent while
// freeing so the parent needs to remain active until they are all gone.
this->state = memContextStateFreeing;
// Execute callback if defined
if (this->callbackFunction)
this->callbackFunction(this->callbackArgument);
// Free child context allocations
if (this->contextChildListSize > 0)
{
for (int contextIdx = 0; contextIdx < this->contextChildListSize; contextIdx++)
if (this->contextChildList[contextIdx])
memFreeInternal(this->contextChildList[contextIdx]);
memFreeInternal(this->contextChildList);
}
// Free memory allocations
if (this->allocListSize > 0)
{
for (int allocIdx = 0; allocIdx < this->allocListSize; allocIdx++)
if (this->allocList[allocIdx])
memFreeInternal(this->allocList[allocIdx]);
memFreeInternal(this->allocList);
}
// Reset the memory context so it can be used again
memset(this, 0, sizeof(MemContext));
}

152
src/common/memContext.h Normal file
View File

@ -0,0 +1,152 @@
/***********************************************************************************************************************************
Memory Context Manager
***********************************************************************************************************************************/
#ifndef MEM_CONTEXT_H
#define MEM_CONTEXT_H
#include "common/error.h"
#include "common/type.h"
/***********************************************************************************************************************************
Memory context names cannot be larger than this size (excluding terminator) or an error will be thrown
***********************************************************************************************************************************/
#define MEM_CONTEXT_NAME_SIZE 64
/***********************************************************************************************************************************
Define initial number of memory contexts
No space is reserved for child contexts when a new context is created because most contexts will be leaves. When a child context is
requested then space will be reserved for this many child contexts initially. When more space is needed the size will be doubled.
***********************************************************************************************************************************/
#define MEM_CONTEXT_INITIAL_SIZE 4
/***********************************************************************************************************************************
Define initial number of memory allocations
Space is reserved for this many allocations when a context is created. When more space is needed the size will be doubled.
***********************************************************************************************************************************/
#define MEM_CONTEXT_ALLOC_INITIAL_SIZE 4
/***********************************************************************************************************************************
Memory context object
***********************************************************************************************************************************/
typedef struct MemContext MemContext;
/***********************************************************************************************************************************
Memory context callback function type, useful for casts in memContextCallback()
***********************************************************************************************************************************/
typedef void (*MemContextCallback)(void *callbackArgument);
/***********************************************************************************************************************************
Memory context management functions
MemContext *context = memContextNew();
MemContext *contextOld = memContextSwitch(context);
ERROR_TRY()
{
<Do something with the memory context>
}
ERROR_CATCH_ANY()
{
<only needed if the error renders the memory context useless - for instance in a constructor>
memContextFree(context);
RETHROW();
}
FINALLY
{
memContextSwitch(context);
}
Use the MEM_CONTEXT*() macros when possible rather than implement error-handling for every memory context block.
***********************************************************************************************************************************/
MemContext *memContextNew(const char *name);
void memContextCallback(MemContext *this, void (*callbackFunction)(void *), void *callbackArgument);
MemContext *memContextSwitch(MemContext *this);
void memContextFree(MemContext *this);
/***********************************************************************************************************************************
Memory context accessors
***********************************************************************************************************************************/
MemContext *memContextCurrent();
MemContext *memContextTop();
const char *memContextName(MemContext *this);
/***********************************************************************************************************************************
Memory management
These functions always new/free within the current memory context.
***********************************************************************************************************************************/
void *memNew(size_t size);
void *memNewRaw(size_t size);
void memFree(void *buffer);
/***********************************************************************************************************************************
Ensure that the old memory context is restored after the block executes (even on error)
MEM_CONTEXT_BEGIN(memContext)
{
<The mem context specified is now the current context>
<Old context can be accessed with the MEM_CONTEXT_OLD() macro>
}
MEM_CONTEXT_END();
<Old memory context is restored>
***********************************************************************************************************************************/
#define MEM_CONTEXT_BEGIN(memContext) \
{ \
/* Switch to the new memory context */ \
MemContext *MEM_CONTEXT_memContextOld = memContextSwitch(memContext); \
\
/* Try the statement block */ \
ERROR_TRY()
#define MEM_CONTEXT_OLD() \
MEM_CONTEXT_memContextOld
#define MEM_CONTEXT_END() \
/* Free the context on error */ \
ERROR_FINALLY() \
{ \
memContextSwitch(MEM_CONTEXT_OLD()); \
} \
}
/***********************************************************************************************************************************
Create a new context and make sure it is freed on error and old context is restored in all cases
MEM_CONTEXT_NEW_BEGIN(memContextName)
{
<The mem context created is now the current context and can be accessed with the MEM_CONTEXT_NEW() macro>
ObjectType *object = memNew(sizeof(ObjectType));
object->memContext = MEM_CONTEXT_NEW();
<Old context can be accessed with the MEM_CONTEXT_OLD() macro>
<On error the newly created context will be freed and the error rethrown>
}
MEM_CONTEXT_NEW_END();
<Old memory context is restored>
***********************************************************************************************************************************/
#define MEM_CONTEXT_NEW_BEGIN(memContextName) \
{ \
MemContext *MEM_CONTEXT_NEW_BEGIN_memContext = memContextNew(memContextName); \
\
MEM_CONTEXT_BEGIN(MEM_CONTEXT_NEW_BEGIN_memContext) \
#define MEM_CONTEXT_NEW() \
MEM_CONTEXT_NEW_BEGIN_memContext
#define MEM_CONTEXT_NEW_END() \
ERROR_CATCH_ANY() \
{ \
memContextSwitch(MEM_CONTEXT_OLD()); \
memContextFree(MEM_CONTEXT_NEW()); \
ERROR_RETHROW(); \
} \
MEM_CONTEXT_END(); \
}
#endif

View File

@ -118,6 +118,16 @@ my $oTestDef =
'common/errorType' => TESTDEF_COVERAGE_FULL,
},
},
{
&TESTDEF_NAME => 'mem-context',
&TESTDEF_TOTAL => 6,
&TESTDEF_C => true,
&TESTDEF_COVERAGE =>
{
'common/memContext' => TESTDEF_COVERAGE_FULL,
},
},
{
&TESTDEF_NAME => 'http-client',
&TESTDEF_TOTAL => 2,

View File

@ -0,0 +1,285 @@
/***********************************************************************************************************************************
Test Memory Contexts
***********************************************************************************************************************************/
/***********************************************************************************************************************************
testFree - test callback function
***********************************************************************************************************************************/
MemContext *memContextCallbackArgument = NULL;
void testFree(MemContext *this)
{
TEST_RESULT_INT(this->state, memContextStateFreeing, "state should be freeing before memContextFree() in callback");
memContextFree(this);
TEST_RESULT_INT(this->state, memContextStateFreeing, "state should still be freeing after memContextFree() in callback");
TEST_ERROR(
memContextCallback(this, (MemContextCallback)testFree, this),
AssertError, "cannot assign callback to inactive context");
TEST_ERROR(memContextSwitch(this), AssertError, "cannot switch to inactive context");
TEST_ERROR(memContextName(this), AssertError, "cannot get name for inactive context");
memContextCallbackArgument = this;
}
/***********************************************************************************************************************************
Test Run
***********************************************************************************************************************************/
void testRun()
{
// -----------------------------------------------------------------------------------------------------------------------------
if (testBegin("memAllocInternal(), memReAllocInternal(), and memFreeInternal()"))
{
TEST_ERROR(
memAllocInternal(-1, false), MemoryError,
sizeof(size_t) == 8 ? "unable to allocate 18446744073709551615 bytes" : "unable to allocate 4294967295 bytes");
TEST_ERROR(memFreeInternal(NULL), MemoryError, "unable to free null pointer");
// Check that bad realloc is caught
void *buffer = memAllocInternal(sizeof(size_t), false);
TEST_ERROR(
memReAllocInternal(buffer, sizeof(size_t), -1, false), MemoryError,
sizeof(size_t) == 8 ? "unable to reallocate 18446744073709551615 bytes" : "unable to reallocate 4294967295 bytes");
memFreeInternal(buffer);
// Normal memory allocation
buffer = memAllocInternal(sizeof(size_t), false);
buffer = memReAllocInternal(buffer, sizeof(size_t), sizeof(size_t) * 2, false);
memFreeInternal(buffer);
// Zeroed memory allocation
unsigned char *buffer2 = memAllocInternal(sizeof(size_t), true);
int expectedTotal = 0;
for (int charIdx = 0; charIdx < sizeof(size_t); charIdx++)
if (buffer2[charIdx] == 0)
expectedTotal++;
TEST_RESULT_INT(expectedTotal, sizeof(size_t), "all bytes are 0");
// Zeroed memory reallocation
memset(buffer2, 0xC7, sizeof(size_t));
buffer2 = memReAllocInternal(buffer2, sizeof(size_t), sizeof(size_t) * 2, true);
expectedTotal = 0;
for (int charIdx = 0; charIdx < sizeof(size_t); charIdx++)
if (buffer2[charIdx] == 0xC7)
expectedTotal++;
TEST_RESULT_INT(expectedTotal, sizeof(size_t), "all old bytes are filled");
expectedTotal = 0;
for (int charIdx = 0; charIdx < sizeof(size_t); charIdx++)
if ((buffer2 + sizeof(size_t))[charIdx] == 0)
expectedTotal++;
TEST_RESULT_INT(expectedTotal, sizeof(size_t), "all new bytes are 0");
}
// -----------------------------------------------------------------------------------------------------------------------------
if (testBegin("memContextNew() and memContextFree()"))
{
// Make sure top context was created
TEST_RESULT_STR(memContextName(memContextTop()), "TOP", "top context should exist");
TEST_RESULT_INT(memContextTop()->contextChildListSize, 0, "top context should init with zero children");
TEST_RESULT_PTR(memContextTop()->contextChildList, NULL, "top context child list empty");
TEST_ERROR(memContextFree(memContextTop()), AssertError, "cannot free top context");
// Current context should equal top context
TEST_RESULT_PTR(memContextCurrent(), memContextTop(), "top context == current context");
// Context name length errors
TEST_ERROR(memContextNew(""), AssertError, "context name length must be > 0 and <= 64");
TEST_ERROR(
memContextNew("12345678901234567890123456789012345678901234567890123456789012345"),
AssertError, "context name length must be > 0 and <= 64");
MemContext *memContext = memContextNew("test1");
TEST_RESULT_STR(memContextName(memContext), "test1", "test1 context name");
TEST_RESULT_PTR(memContext->contextParent, memContextTop(), "test1 context parent is top");
TEST_RESULT_INT(memContextTop()->contextChildListSize, MEM_CONTEXT_INITIAL_SIZE, "initial top context child list size");
TEST_RESULT_PTR(memContextSwitch(memContext), memContextTop(), "switch returns top as old context");
TEST_RESULT_PTR(memContext, memContextCurrent(), "current context is now test1");
// Create enough mem contexts to use up the initially allocated block
for (int contextIdx = 1; contextIdx < MEM_CONTEXT_INITIAL_SIZE; contextIdx++)
{
memContextSwitch(memContextTop());
memContextNew("test-filler");
TEST_RESULT_BOOL(
memContextTop()->contextChildList[contextIdx]->state == memContextStateActive, true, "new context is active");
TEST_RESULT_STR(memContextName(memContextTop()->contextChildList[contextIdx]), "test-filler", "new contest name");
}
// This forces the child context array to grow
memContextNew("test5");
TEST_RESULT_INT(memContextTop()->contextChildListSize, MEM_CONTEXT_INITIAL_SIZE * 2, "increased child context list size");
// Free a context
memContextFree(memContextTop()->contextChildList[1]);
TEST_RESULT_BOOL(
memContextTop()->contextChildList[1]->state == memContextStateActive, false, "child context inactive after free");
TEST_RESULT_PTR(memContextCurrent(), memContextTop(), "current context is top");
// Create a new context and it should end up in the same spot
memContextNew("test-reuse");
TEST_RESULT_BOOL(
memContextTop()->contextChildList[1]->state == memContextStateActive,
true, "new context in same index as freed context is active");
TEST_RESULT_STR(memContextName(memContextTop()->contextChildList[1]), "test-reuse", "new context name");
// Create a child context to test recursive free
memContextSwitch(memContextTop()->contextChildList[MEM_CONTEXT_INITIAL_SIZE]);
memContextNew("test-reuse");
TEST_RESULT_PTR_NE(
memContextTop()->contextChildList[MEM_CONTEXT_INITIAL_SIZE]->contextChildList, NULL, "context child list is allocated");
TEST_RESULT_INT(
memContextTop()->contextChildList[MEM_CONTEXT_INITIAL_SIZE]->contextChildListSize, MEM_CONTEXT_INITIAL_SIZE,
"context child list initial size");
TEST_ERROR(
memContextFree(memContextTop()->contextChildList[MEM_CONTEXT_INITIAL_SIZE]),
AssertError, "cannot free current context 'test5'");
memContextSwitch(memContextTop());
memContextFree(memContextTop()->contextChildList[MEM_CONTEXT_INITIAL_SIZE]);
TEST_ERROR(
memContextFree(memContextTop()->contextChildList[MEM_CONTEXT_INITIAL_SIZE]),
AssertError, "cannot free inactive context");
}
// -----------------------------------------------------------------------------------------------------------------------------
if (testBegin("memContextAlloc(), memNew*(), and memFree()"))
{
memContextSwitch(memContextTop());
memNew(sizeof(size_t));
MemContext *memContext = memContextNew("test-alloc");
memContextSwitch(memContext);
for (int allocIdx = 0; allocIdx <= MEM_CONTEXT_ALLOC_INITIAL_SIZE; allocIdx++)
{
unsigned char *buffer = memNew(sizeof(size_t));
// Check that the buffer is zeroed
int expectedTotal = 0;
for (int charIdx = 0; charIdx < sizeof(size_t); charIdx++)
if (buffer[charIdx] == 0)
expectedTotal++;
TEST_RESULT_INT(expectedTotal, sizeof(size_t), "all bytes are 0");
TEST_RESULT_INT(
memContextCurrent()->allocListSize,
allocIdx == MEM_CONTEXT_ALLOC_INITIAL_SIZE ? MEM_CONTEXT_ALLOC_INITIAL_SIZE * 2 : MEM_CONTEXT_ALLOC_INITIAL_SIZE,
"allocation list size");
}
unsigned char *buffer= memNewRaw(sizeof(size_t));
TEST_ERROR(memFree(NULL), AssertError, "unable to free null allocation");
TEST_ERROR(memFree((void *)0x01), AssertError, "unable to find allocation");
memFree(buffer);
memContextSwitch(memContextTop());
memContextFree(memContext);
}
// -----------------------------------------------------------------------------------------------------------------------------
if (testBegin("memContextCallback()"))
{
TEST_ERROR(memContextCallback(memContextTop(), NULL, NULL), AssertError, "top context may not have a callback");
MemContext *memContext = memContextNew("test-callback");
memContextCallback(memContext, (MemContextCallback)testFree, memContext);
TEST_ERROR(
memContextCallback(memContext, (MemContextCallback)testFree, memContext),
AssertError, "callback is already set for context 'test-callback'");
memContextFree(memContext);
TEST_RESULT_PTR(memContextCallbackArgument, memContext, "callback argument is context");
}
// -----------------------------------------------------------------------------------------------------------------------------
if (testBegin("MEM_CONTEXT_BEGIN() and MEM_CONTEXT_END()"))
{
memContextSwitch(memContextTop());
MemContext *memContext = memContextNew("test-block");
// Check normal block
MEM_CONTEXT_BEGIN(memContext)
{
TEST_RESULT_STR(memContextName(memContextCurrent()), "test-block", "context is now test-block");
}
MEM_CONTEXT_END();
TEST_RESULT_STR(memContextName(memContextCurrent()), "TOP", "context is now top");
// Check block that errors
TEST_ERROR(
MEM_CONTEXT_BEGIN(memContext)
{
TEST_RESULT_STR(memContextName(memContextCurrent()), "test-block", "context is now test-block");
ERROR_THROW(AssertError, "error in test block");
}
MEM_CONTEXT_END(),
AssertError, "error in test block");
TEST_RESULT_STR(memContextName(memContextCurrent()), "TOP", "context is now top");
}
// -----------------------------------------------------------------------------------------------------------------------------
if (testBegin("MEM_CONTEXT_NEW_BEGIN() and MEM_CONTEXT_NEW_END()"))
{
// ------------------------------------------------------------------------------------------------------------------------
// Successful context new block
char *memContextTestName = "test-new-block";
MemContext *memContext;
MEM_CONTEXT_NEW_BEGIN(memContextTestName)
{
memContext = MEM_CONTEXT_NEW();
TEST_RESULT_PTR(memContext, memContextCurrent(), "new mem context is current");
TEST_RESULT_STR(memContextName(memContext), memContextTestName, "context is now '%s'", memContextTestName);
}
MEM_CONTEXT_NEW_END();
TEST_RESULT_PTR(memContextCurrent(), memContextTop(), "context is now 'TOP'");
TEST_RESULT_BOOL(memContext->state == memContextStateActive, true, "new mem context is still active");
memContextFree(memContext);
// ------------------------------------------------------------------------------------------------------------------------
// Failed context new block
memContextTestName = "test-new-failed-block";
bool bCatch = false;
ERROR_TRY()
{
MEM_CONTEXT_NEW_BEGIN(memContextTestName)
{
memContext = MEM_CONTEXT_NEW();
TEST_RESULT_STR(memContextName(memContext), memContextTestName, "context is now '%s'", memContextTestName);
ERROR_THROW(AssertError, "create failed");
}
MEM_CONTEXT_NEW_END();
}
ERROR_CATCH(AssertError)
{
bCatch = true;
}
TEST_RESULT_BOOL(bCatch, true, "new context error was caught");
TEST_RESULT_PTR(memContextCurrent(), memContextTop(), "context is now 'TOP'");
TEST_RESULT_BOOL(memContext->state == memContextStateFree, true, "new mem context is not active");
}
}