選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

1177 行
34 KiB

// *** TODO ***
// Consider "Arrange, Act, Assert" structure.
// - So far this looks pretty nice. Encourages a structure I like.
// - I will probably define some macros to generate the boilerplate code that
// will be repeated over and over.
//
// Handle segfaults as ordinary test failures.
// - See https://stackoverflow.com/questions/10202941/segmentation-fault-handling
// - Would be a great way to allow more tests to run after a test has crashed.
#include <stddef.h>
#include <assert.h>
#define RDIC_IMPLEMENTATION
#include "../rdic.h"
typedef struct GUI_Node {
// NOTE(Zelaven): This codebase assumes rn is the first member for subtyping.
RDIC_Node rn;
char *debug_name;
} GUI_Node;
// --- ARRANGE HELPERS ---
#define ARRAYLENGTH(X) (sizeof(X)/sizeof((X)[0]))
void init_node_freelist(GUI_Node *nodes, int node_count)
{
// NOTE(Zelaven): We special-case the last node.
for(int i = 0; i < node_count -1; i++)
{
nodes[i].rn = (RDIC_Node){
.sibling = &((nodes+i+1)->rn),
};
nodes[i].debug_name = NULL;
}
nodes[node_count-1].rn = (RDIC_Node){0};
}
#define ARRANGE_DEFAULT_CONFIGURATION(FREELIST_LENGTH)\
RDIC_Context context = {0};\
GUI_Node freelist_nodes[FREELIST_LENGTH] = {0};\
init_node_freelist(freelist_nodes, ARRAYLENGTH(freelist_nodes));\
RDIC_Freelist freelist = {&freelist_nodes[0].rn};\
context.freelist = &freelist;\
RDIC_Node_Reference root = {0};
RDIC_Node_Reference test_get_node(
RDIC_Context *context,
RDIC_Node_Reference reference,
char *name)
{
RDIC_Node_Reference new_ref = rdic_get_node(context, reference, NULL);
if(new_ref.node != NULL)
{
((GUI_Node*)new_ref.node)->debug_name = name;
}
return new_ref;
}
#define GET_NODE(context, ref) test_get_node(context, ref, #ref);
#define SET_DEBUG_NAME(REF, NAME) ((GUI_Node*)(REF).node)->debug_name = (NAME)
// --- ASSERT HELPERS ---
// NOTE(Zelaven): Helpers can be buggy too and could in principle have tests.
int freelist_contains(RDIC_Context *context, RDIC_Node *node)
{
RDIC_Node *current = context->freelist->head;
int result = 0;
while(!result && current != NULL)
{
if(current == node) {result = 1;}
current = current->sibling;
}
return result;
}
#define ASSERT_REFERENCE_VALID(REF)\
if((REF).node == NULL)\
{TEST_ERROR("`" #REF "` is invalid: node is NULL.");}\
else if((REF).generation != (REF).node->generation)\
{TEST_ERROR("`" #REF "` is invalid: generation differs.");}
// TODO(Zelaven): create TEST_ERROR_FMT and use it here.
//{TEST_ERROR("`" #REF "` is invalid: generation differs (" #REF ": %d, node: %d).");}
#define ASSERT_REFERENCE_INVALID(REF, TEXT...)\
if(((REF).node != NULL) && ((REF).generation == (REF).node->generation))\
{TEST_ERROR("`" #REF "` is valid when it should be invalid. "TEXT);}
#define ASSERT_PARENT_OF(PARENT, CHILD)\
if((CHILD).node->parent != (PARENT).node)\
{TEST_ERROR("parent of `" #CHILD "` is not `" #PARENT "`.");}
#define ASSERT_SIBLING_OF(SIBLING, NODE)\
if((NODE).node->sibling != (SIBLING).node)\
{TEST_ERROR("sibling of `" #NODE "` is not `" #SIBLING "`.");}
#define ASSERT_NO_SIBLING(NODE)\
if((NODE).node->sibling != NULL)\
{TEST_ERROR("`" #NODE "` has a sibling when it should not.");}
#define ASSERT_FRISTCHILD_OF(FIRSTCHILD, PARENT)\
if((PARENT).node->first_child != (FIRSTCHILD).node)\
{TEST_ERROR("first_child of `" #PARENT "` is not `" #FIRSTCHILD "`.");}
#define ASSERT_IN_FREELIST(CONTEXT, NODE)\
if(!freelist_contains(CONTEXT, NODE.node))\
{TEST_ERROR("`"#NODE"` not in freelist.");}
// --- TEST INFRASTRUCTURE ---
// TODO(Zelaven): Should probably make a separate arena for strings generated by running the tests.
#include "memory_arena.c"
#include "memory_arena_linux.c"
typedef struct Report_Test_Result
{
enum {
TESTRESULT_UNDEFINED = 0,
TESTRESULT_SUCCESS,
TESTRESULT_ERROR,
TESTRESULT_NUM_LEVELS,
} level;
const char *test_name;
char *string;
} Report_Test_Result;
typedef struct Report_Group Report_Group;
struct Report_Group {
char *name;
Report_Group *next;
unsigned int num_test_results;
Report_Test_Result results[];
};
Memory_Arena g_test_report_arena;
unsigned int _g_test_report_num_test_results = 0;
Report_Group *g_first_report_group = NULL;
Report_Group *g_current_report_group = NULL;
int g_error_occurred = 0;
#define arena_allocate_TYPE(arena, TYPE) ((TYPE*)memory_arena_allocate(arena, sizeof(TYPE), _Alignof(TYPE)))
#define arena_allocate_report_group(arena) arena_allocate_TYPE(arena, Report_Group)
#define arena_allocate_report_test_result(arena) arena_allocate_TYPE(arena, Report_Test_Result)
static inline void log_new_report_group(char *name)
{
Report_Group *new_group = arena_allocate_report_group(&g_test_report_arena);
if(g_current_report_group == NULL)
{
g_first_report_group = new_group;
}
else
{
g_current_report_group->next = new_group;
}
g_current_report_group = new_group;
*new_group = (Report_Group){
.name = name,
};
}
static inline void log_result_to_report(
Report_Test_Result result)
{
// This is allocated in extension of the results[] member of a Report_Group.
*arena_allocate_report_test_result(&g_test_report_arena) = result;
//g_test_report_num_test_results += 1;
g_current_report_group->num_test_results += 1;
}
char *g_test_name;
#define TEST_ERROR(STRING) \
g_error_occurred = 1;\
log_result_to_report((Report_Test_Result){\
.level = TESTRESULT_ERROR,\
.test_name = g_test_name,\
.string = STRING,\
})
// --- TESTS ---
void test__start_frame__empty_freelist(void)
{
// Arrange.
RDIC_Context context = {0};
RDIC_Node_Reference root = {0};
// Act.
root = rdic_context_start_frame(
&context,
root);
// Assert.
ASSERT_REFERENCE_INVALID(root, "(Empty freelist)");
}
void test__start_frame__succeed(void)
{
// Arrange.
RDIC_Context context = {0};
GUI_Node freelist_nodes[1] = {0};
init_node_freelist(freelist_nodes, ARRAYLENGTH(freelist_nodes));
RDIC_Freelist freelist = {&freelist_nodes[0].rn};
context.freelist = &freelist;
RDIC_Node_Reference root = {0};
// Act.
root = rdic_context_start_frame(
&context,
root);
// Assert.
ASSERT_REFERENCE_VALID(root);
}
void test__finish_frame__succeed(void)
{
// Arrange.
RDIC_Context context = {0};
GUI_Node freelist_nodes[1] = {0};
init_node_freelist(freelist_nodes, ARRAYLENGTH(freelist_nodes));
RDIC_Freelist freelist = {&freelist_nodes[0].rn};
context.freelist = &freelist;
RDIC_Node_Reference root = {0};
root = rdic_context_start_frame(
&context,
root);
// Act.
rdic_context_finish_frame(&context);
// Assert.
// No asserts. If it doesn't crash then we're good for now.
// In the future we will want to assert on the structure of the node tree.
}
// ---
void test__get_node_with_empty_freelist__return_null_reference(void)
{
// Arrange.
RDIC_Context context = {0};
GUI_Node freelist_nodes[1] = {0};
init_node_freelist(freelist_nodes, ARRAYLENGTH(freelist_nodes));
RDIC_Freelist freelist = {&freelist_nodes[0].rn};
context.freelist = &freelist;
RDIC_Node_Reference root = {0};
root = rdic_context_start_frame(
&context,
root);
// Act.
RDIC_Node_Reference node = {0}; // Invalid reference -> new node.
node = GET_NODE(&context, node);
// Assert.
// NOTE(Zelaven): We specifically assert that the reference is all 0.
if(context.freelist->head != NULL)
{TEST_ERROR("Freelist not empty.");}
if(node.node != NULL)
{TEST_ERROR("Non-NULL node despite empty freelist.");}
if(node.generation != 0)
{TEST_ERROR("Non-0 generation despite empty freelist.");}
}
void test__get_node_with_empty_freelist_with_nonzero_generation__return_null_reference(void)
{
// Arrange.
RDIC_Context context = {0};
GUI_Node freelist_nodes[1] = {0};
init_node_freelist(freelist_nodes, ARRAYLENGTH(freelist_nodes));
RDIC_Freelist freelist = {&freelist_nodes[0].rn};
context.freelist = &freelist;
RDIC_Node_Reference root = {0};
root = rdic_context_start_frame(
&context,
root);
// Act.
RDIC_Node_Reference node = {0}; // Invalid reference -> new node.
node.generation = 42; // Should become 0.
node = GET_NODE(&context, node);
// Assert.
// NOTE(Zelaven): We specifically assert that the reference is all 0.
if(node.node != NULL)
{TEST_ERROR("Non-NULL node despite empty freelist.");}
if(node.generation != 0)
{TEST_ERROR("Non-0 generation despite empty freelist.");}
}
void test__get_node__succeed(void)
{
// Arrange.
RDIC_Context context = {0};
GUI_Node freelist_nodes[2] = {0};
init_node_freelist(freelist_nodes, ARRAYLENGTH(freelist_nodes));
RDIC_Freelist freelist = {&freelist_nodes[0].rn};
context.freelist = &freelist;
RDIC_Node_Reference root = {0};
root = rdic_context_start_frame(
&context,
root);
// Act.
RDIC_Node_Reference node = {0}; // Invalid reference -> new node.
node = GET_NODE(&context, node);
// Assert.
ASSERT_REFERENCE_VALID(node);
// NOTE(Zelaven): We want to assert this to make sure we are counting from 0.
// If not then we may be incrementing on the allocation of the node, which is
// incorrect. We increment (invalidate) the generation on de-allocation.
if(node.generation != 0)
{TEST_ERROR("Non-0 generation for first allocation of node from freelist.");}
}
void test__get_node_finish_frame__succeed(void)
{
// Arrange.
RDIC_Context context = {0};
GUI_Node freelist_nodes[2] = {0};
init_node_freelist(freelist_nodes, ARRAYLENGTH(freelist_nodes));
RDIC_Freelist freelist = {&freelist_nodes[0].rn};
context.freelist = &freelist;
RDIC_Node_Reference root = {0};
root = rdic_context_start_frame(
&context,
root);
// Act.
RDIC_Node_Reference node = {0}; // Invalid reference -> new node.
node = GET_NODE(&context, node);
rdic_context_finish_frame(&context);
// Assert.
}
void test__single_node__returned_to_freelist(void)
{
// Arrange.
ARRANGE_DEFAULT_CONFIGURATION(2);
RDIC_Node_Reference node = {0};
// Act.
// First frame.
root = rdic_context_start_frame(
&context,
root);
// Invalid reference -> new node.
node = GET_NODE(&context, node);
rdic_context_finish_frame(&context);
// Second frame.
// node should return to freelist.
root = rdic_context_start_frame(
&context,
root);
// No call to get_node for node.
rdic_context_finish_frame(&context);
// Assert.
// Firstly, we know that the release of the node should increment its
// generation.
// We also know that the pointer points to a node that has just been submitted
// to the freelist.
// In fact, we know that it has been released precisely 1 time, so its
// generation should be 1 and the reference should still have a generation of
// 0 as we seek to invalidate it, so they should mismatch.
if(node.generation != 0)
{TEST_ERROR("Node _reference_ generation changed?");}
if(node.node->generation == 0)
{TEST_ERROR("Node submitted to freelist stayed generation 0.");}
if(node.node->generation != 1)
{TEST_ERROR("Node submitted to freelist increased generation more than once?");}
ASSERT_REFERENCE_INVALID(node, "(Skipped in second frame)");
if(context.freelist->head != node.node)
{TEST_ERROR("Node not prepended to freelist.");}
if(root.node->first_child != NULL)
{TEST_ERROR("Last frame is empty, yet root has a first_child.");}
}
#define ASSERT_REFERENCES_EQUAL(REF1, REF2)\
if(!rdic_node_references_equal((REF1), (REF2)))\
{TEST_ERROR("`"#REF1"` != `"#REF2"`.\n");}
void test__single_node__nodes_are_stable(void)
{
// Arrange.
RDIC_Context context = {0};
GUI_Node freelist_nodes[2] = {0};
init_node_freelist(freelist_nodes, ARRAYLENGTH(freelist_nodes));
RDIC_Freelist freelist = {&freelist_nodes[0].rn};
context.freelist = &freelist;
RDIC_Node_Reference root = {0};
RDIC_Node_Reference node = {0};
RDIC_Node_Reference new_root = {0};
RDIC_Node_Reference new_node = {0};
// Act.
// First frame.
root = rdic_context_start_frame(&context, root);
node = GET_NODE(&context, node);
rdic_context_finish_frame(&context);
// Second frame.
new_root = rdic_context_start_frame(&context, root);
new_node = GET_NODE(&context, node);
rdic_context_finish_frame(&context);
// Assert.
ASSERT_REFERENCES_EQUAL(root, new_root);
ASSERT_REFERENCES_EQUAL(node, new_node);
}
void test__single_node_freelist_reuse__succeed(void)
{
// Arrange.
ARRANGE_DEFAULT_CONFIGURATION(2);
RDIC_Node_Reference node = {0};
// Act.
// --- FRAME ---
root = rdic_context_start_frame(
&context,
root);
// Invalid reference -> new node.
node = GET_NODE(&context, node);
rdic_context_finish_frame(&context);
// --- FRAME ---
// node should return to freelist.
root = rdic_context_start_frame(
&context,
root);
// No call to get_node for node.
rdic_context_finish_frame(&context);
// --- FRAME ---
root = rdic_context_start_frame(
&context,
root);
// Invalid reference -> new node.
node = GET_NODE(&context, node);
rdic_context_finish_frame(&context);
// Assert.
// Firstly, we know that the release of the node should increment its
// generation. Therefore, when we get the node again, its generation should
// be exactly 1.
if(node.generation != 1)
{TEST_ERROR("`node` generation is not 1.");}
ASSERT_REFERENCE_VALID(node);
if(context.freelist->head != NULL)
{TEST_ERROR("Freelist should be empty.");}
}
// ---
void test__parent_child_frame__succeed(void)
{
// Arrange.
RDIC_Context context = {0};
GUI_Node freelist_nodes[3] = {0};
init_node_freelist(freelist_nodes, ARRAYLENGTH(freelist_nodes));
RDIC_Freelist freelist = {&freelist_nodes[0].rn};
context.freelist = &freelist;
RDIC_Node_Reference root = {0};
RDIC_Node_Reference parent_node = {0};
RDIC_Node_Reference child_node = {0};
// Act.
// First frame.
root = rdic_context_start_frame(
&context,
root);
// Invalid reference -> new node.
parent_node = GET_NODE(&context, parent_node);
rdic_push_parent(&context, parent_node);
child_node = GET_NODE(&context, child_node);
rdic_pop_parent(&context);
rdic_context_finish_frame(&context);
// Assert.
ASSERT_PARENT_OF(root, parent_node);
ASSERT_PARENT_OF(parent_node, child_node);
ASSERT_FRISTCHILD_OF(child_node, parent_node);
}
void test__parent_child_free_child__child_to_freelist(void)
{
// Arrange.
RDIC_Context context = {0};
GUI_Node freelist_nodes[3] = {0};
init_node_freelist(freelist_nodes, ARRAYLENGTH(freelist_nodes));
RDIC_Freelist freelist = {&freelist_nodes[0].rn};
context.freelist = &freelist;
RDIC_Node_Reference root = {0};
RDIC_Node_Reference parent_node = {0};
RDIC_Node_Reference child_node = {0};
// Act.
// First frame.
root = rdic_context_start_frame(
&context,
root);
// Invalid reference -> new node.
parent_node = GET_NODE(&context, parent_node);
rdic_push_parent(&context, parent_node);
child_node = GET_NODE(&context, child_node);
rdic_pop_parent(&context);
rdic_context_finish_frame(&context);
// Second frame.
root = rdic_context_start_frame(
&context,
root);
parent_node = GET_NODE(&context, parent_node);
rdic_push_parent(&context, parent_node);
rdic_pop_parent(&context);
rdic_context_finish_frame(&context);
// Assert.
if(child_node.generation != 0)
{TEST_ERROR("Node _reference_ generation changed?");}
if(child_node.node->generation != 1)
{TEST_ERROR("Node submitted to freelist retained same generation?");}
ASSERT_REFERENCE_INVALID(child_node, "(Skipped in second frame)");
if(context.freelist->head != child_node.node)
{TEST_ERROR("Node not prepended to freelist.");}
}
void test__parent_child_free_child__parent_to_freelist(void)
{
// Arrange.
RDIC_Context context = {0};
GUI_Node freelist_nodes[3] = {0};
init_node_freelist(freelist_nodes, ARRAYLENGTH(freelist_nodes));
RDIC_Freelist freelist = {&freelist_nodes[0].rn};
context.freelist = &freelist;
RDIC_Node_Reference root = {0};
RDIC_Node_Reference parent = {0};
RDIC_Node_Reference child = {0};
// Act.
// First frame.
root = rdic_context_start_frame(
&context,
root);
// Invalid reference -> new node.
parent = GET_NODE(&context, parent);
rdic_push_parent(&context, parent);
child = GET_NODE(&context, child);
rdic_pop_parent(&context);
rdic_context_finish_frame(&context);
// Second frame.
root = rdic_context_start_frame(
&context,
root);
rdic_context_finish_frame(&context);
// Assert.
if(child.generation != 0)
{TEST_ERROR("`child` _reference_ generation changed?");}
if(child.node->generation != 1)
{TEST_ERROR("`child` submitted to freelist retained same generation?");}
ASSERT_REFERENCE_INVALID(child, "(Skipped in second frame)");
ASSERT_IN_FREELIST(&context, child);
if(parent.generation != 0)
{TEST_ERROR("`parent` _reference_ generation changed?");}
if(parent.node->generation != 1)
{TEST_ERROR("`parent` submitted to freelist retained same generation?");}
ASSERT_REFERENCE_INVALID(parent, "(Skipped in second frame)");
ASSERT_IN_FREELIST(&context, parent);
}
void test__parent_child_both_reused__succeed(void)
{
// Arrange.
ARRANGE_DEFAULT_CONFIGURATION(3);
RDIC_Node_Reference parent = {0};
RDIC_Node_Reference child = {0};
// NOTE(Zelaven): Multiple re-uses because it uncovers new bugs.
// Act.
// --- FRAME ---
root = rdic_context_start_frame(&context, root);
SET_DEBUG_NAME(root, "root");
parent = GET_NODE(&context, parent);
rdic_push_parent(&context, parent);
child = GET_NODE(&context, child);
rdic_pop_parent(&context);
rdic_context_finish_frame(&context);
// --- FRAME ---
root = rdic_context_start_frame(&context, root);
rdic_context_finish_frame(&context);
// --- FRAME ---
root = rdic_context_start_frame(&context, root);
SET_DEBUG_NAME(root, "root");
parent = GET_NODE(&context, parent);
rdic_push_parent(&context, parent);
child = GET_NODE(&context, child);
rdic_pop_parent(&context);
rdic_context_finish_frame(&context);
// --- FRAME ---
root = rdic_context_start_frame(&context, root);
rdic_context_finish_frame(&context);
// --- FRAME ---
root = rdic_context_start_frame(&context, root);
SET_DEBUG_NAME(root, "root");
parent = GET_NODE(&context, parent);
rdic_push_parent(&context, parent);
child = GET_NODE(&context, child);
rdic_pop_parent(&context);
rdic_context_finish_frame(&context);
// --- FRAME ---
root = rdic_context_start_frame(&context, root);
rdic_context_finish_frame(&context);
// --- FRAME ---
root = rdic_context_start_frame(&context, root);
SET_DEBUG_NAME(root, "root");
parent = GET_NODE(&context, parent);
rdic_push_parent(&context, parent);
child = GET_NODE(&context, child);
rdic_pop_parent(&context);
rdic_context_finish_frame(&context);
// Assert.
ASSERT_REFERENCE_VALID(parent);
ASSERT_REFERENCE_VALID(child);
}
// ---
#define PREAMBLE__parent_3children\
RDIC_Context context = {0};\
GUI_Node freelist_nodes[5] = {0};\
init_node_freelist(freelist_nodes, ARRAYLENGTH(freelist_nodes));\
RDIC_Freelist freelist = {&freelist_nodes[0].rn};\
context.freelist = &freelist;\
RDIC_Node_Reference root = {0};\
RDIC_Node_Reference parent = {0};\
RDIC_Node_Reference child1 = {0};\
RDIC_Node_Reference child2 = {0};\
RDIC_Node_Reference child3 = {0};
#define ACT__parent_3children_full_frame\
root = rdic_context_start_frame(&context, root);\
SET_DEBUG_NAME(root, "root");\
parent = GET_NODE(&context, parent);\
rdic_push_parent(&context, parent);\
child1 = GET_NODE(&context, child1);\
child2 = GET_NODE(&context, child2);\
child3 = GET_NODE(&context, child3);\
rdic_pop_parent(&context);\
rdic_context_finish_frame(&context);
#define ASSERT__parent_3children__123_frame_structure\
ASSERT_PARENT_OF(root, parent);\
ASSERT_FRISTCHILD_OF(child1, parent);\
ASSERT_PARENT_OF(parent, child1);\
ASSERT_PARENT_OF(parent, child2);\
ASSERT_PARENT_OF(parent, child3);\
ASSERT_SIBLING_OF(child2, child1);\
ASSERT_SIBLING_OF(child3, child2);\
ASSERT_NO_SIBLING(child3);
void test__parent_3children_123frame__succeed(void) {
// Arrange.
PREAMBLE__parent_3children
// Act.
// --- FRAME ---
ACT__parent_3children_full_frame
// Assert.
ASSERT__parent_3children__123_frame_structure
}
void test__parent_3children_partial_frame_then_complete__succeed(
int enable_child1, int enable_child2, int enable_child3)
{
PREAMBLE__parent_3children;
// --- FRAME ---
root = rdic_context_start_frame(&context, root);
SET_DEBUG_NAME(root, "root");
parent = GET_NODE(&context, parent);
rdic_push_parent(&context, parent);
if(enable_child1) child1 = GET_NODE(&context, child1);
if(enable_child2) child2 = GET_NODE(&context, child2);
if(enable_child3) child3 = GET_NODE(&context, child3);
rdic_pop_parent(&context);
rdic_context_finish_frame(&context);
//ASSERT_REFERENCE_VALID(child1);
//ASSERT_REFERENCE_VALID(child2);
//ASSERT_REFERENCE_VALID(child3);
// --- FRAME ---
ACT__parent_3children_full_frame
// Assert.
ASSERT__parent_3children__123_frame_structure
}
void test__parent_3children_culling__succeed(
int enable_child1, int enable_child2, int enable_child3)
{
PREAMBLE__parent_3children;
// --- FRAME ---
ACT__parent_3children_full_frame
// --- FRAME ---
root = rdic_context_start_frame(&context, root);
SET_DEBUG_NAME(root, "root");
parent = GET_NODE(&context, parent);
rdic_push_parent(&context, parent);
if(enable_child1) {
child1 = GET_NODE(&context, child1);}
if(enable_child2) {
child2 = GET_NODE(&context, child2);}
if(enable_child3) {
child3 = GET_NODE(&context, child3);}
rdic_pop_parent(&context);
rdic_context_finish_frame(&context);
// Assert.
// NOTE(Zelaven): Yes, the assertions look messy, but it's just as easy to
// make 9 cases of copy-paste messy.
// Deciphering the exact conditions may take a few seconds, but this was the
// simplest solution I could come up with that met my quality standards for
// this test code.
if(enable_child1) {ASSERT_REFERENCE_VALID(child1);}
else {ASSERT_REFERENCE_INVALID(child1);}
if(enable_child2) {ASSERT_REFERENCE_VALID(child2);}
else {ASSERT_REFERENCE_INVALID(child2);}
if(enable_child3) {ASSERT_REFERENCE_VALID(child3);}
else {ASSERT_REFERENCE_INVALID(child3);}
ASSERT_PARENT_OF(root, parent);
if(enable_child1) {ASSERT_FRISTCHILD_OF(child1, parent);}
else if(enable_child2) {ASSERT_FRISTCHILD_OF(child2, parent);}
else if (enable_child3) {ASSERT_FRISTCHILD_OF(child3, parent);}
if(enable_child1) {ASSERT_PARENT_OF(parent, child1);}
if(enable_child2) {ASSERT_PARENT_OF(parent, child2);}
if(enable_child3) {ASSERT_PARENT_OF(parent, child3);}
if(enable_child1 && enable_child2) {ASSERT_SIBLING_OF(child2, child1);}
else if(enable_child1 && enable_child3) {ASSERT_SIBLING_OF(child3, child1);}
if(enable_child2 && enable_child3) {ASSERT_SIBLING_OF(child3, child2);}
if(enable_child3) {ASSERT_NO_SIBLING(child3);}
else if(enable_child2) {ASSERT_NO_SIBLING(child2);}
else if(enable_child1) {ASSERT_NO_SIBLING(child2);}
if(!enable_child1) {ASSERT_IN_FREELIST(&context, child1);}
if(!enable_child2) {ASSERT_IN_FREELIST(&context, child2);}
if(!enable_child3) {ASSERT_IN_FREELIST(&context, child3);}
}
void test__parent_3children_full_frame_reuse__succeed(void) {
// Arrange.
PREAMBLE__parent_3children
// Act.
// --- FRAME ---
ACT__parent_3children_full_frame
// --- FRAME ---
root = rdic_context_start_frame(&context, root);
rdic_context_finish_frame(&context);
// --- FRAME ---
ACT__parent_3children_full_frame
// --- FRAME ---
root = rdic_context_start_frame(&context, root);
rdic_context_finish_frame(&context);
// --- FRAME ---
ACT__parent_3children_full_frame
// --- FRAME ---
root = rdic_context_start_frame(&context, root);
rdic_context_finish_frame(&context);
// --- FRAME ---
ACT__parent_3children_full_frame
// --- FRAME ---
root = rdic_context_start_frame(&context, root);
rdic_context_finish_frame(&context);
// --- FRAME ---
ACT__parent_3children_full_frame
// --- FRAME ---
root = rdic_context_start_frame(&context, root);
rdic_context_finish_frame(&context);
// --- FRAME ---
ACT__parent_3children_full_frame
// --- FRAME ---
root = rdic_context_start_frame(&context, root);
rdic_context_finish_frame(&context);
// --- FRAME ---
ACT__parent_3children_full_frame
// Assert.
ASSERT__parent_3children__123_frame_structure
}
// ---
void test__exhaust_freelist_larger_tree__no_crash(void)
{
// Arrange.
RDIC_Context context = {0};
GUI_Node freelist_nodes[3] = {0};
init_node_freelist(freelist_nodes, ARRAYLENGTH(freelist_nodes));
RDIC_Freelist freelist = {&freelist_nodes[0].rn};
context.freelist = &freelist;
RDIC_Node_Reference root = {0};
RDIC_Node_Reference n1 = {0};
RDIC_Node_Reference n1_1 = {0};
RDIC_Node_Reference n1_1_1 = {0};
RDIC_Node_Reference n1_1_2 = {0};
RDIC_Node_Reference n1_2 = {0};
RDIC_Node_Reference n1_2_1 = {0};
RDIC_Node_Reference n1_2_2 = {0};
RDIC_Node_Reference n2 = {0};
RDIC_Node_Reference n2_1 = {0};
RDIC_Node_Reference n2_2 = {0};
RDIC_Node_Reference n3 = {0};
RDIC_Node_Reference n3_1 = {0};
RDIC_Node_Reference n3_2 = {0};
// Act.
/*
* r
* n n n
* n n nn nn
* nn nn
*/
// First frame.
root = rdic_context_start_frame(&context, root);
SET_DEBUG_NAME(root, "root");
n1 = GET_NODE(&context, n1);
rdic_push_parent(&context, n1);
n1_1 = GET_NODE(&context, n1_1);
rdic_push_parent(&context, n1_1);
n1_1_1 = GET_NODE(&context, n1_1_1);
n1_1_2 = GET_NODE(&context, n1_1_2);
rdic_pop_parent(&context);
n1_2 = GET_NODE(&context, n1_2);
rdic_push_parent(&context, n1_2);
n1_2_1 = GET_NODE(&context, n1_2_1);
n1_2_2 = GET_NODE(&context, n1_2_2);
rdic_pop_parent(&context);
rdic_pop_parent(&context);
n2 = GET_NODE(&context, n2);
rdic_push_parent(&context, n2);
n2_1 = GET_NODE(&context, n2_1);
n2_2 = GET_NODE(&context, n2_2);
rdic_pop_parent(&context);
n3 = GET_NODE(&context, n3);
rdic_push_parent(&context, n3);
n3_1 = GET_NODE(&context, n3_1);
n3_2 = GET_NODE(&context, n3_2);
rdic_pop_parent(&context);
rdic_context_finish_frame(&context);
root = rdic_context_start_frame(&context, root);
rdic_context_finish_frame(&context);
// Assert.
}
void test__large_tree_constructed__succeed(void)
{
// Arrange.
RDIC_Context context = {0};
GUI_Node freelist_nodes[14] = {0};
init_node_freelist(freelist_nodes, ARRAYLENGTH(freelist_nodes));
RDIC_Freelist freelist = {&freelist_nodes[0].rn};
context.freelist = &freelist;
RDIC_Node_Reference root = {0};
RDIC_Node_Reference n1 = {0};
RDIC_Node_Reference n1_1 = {0};
RDIC_Node_Reference n1_1_1 = {0};
RDIC_Node_Reference n1_1_2 = {0};
RDIC_Node_Reference n1_2 = {0};
RDIC_Node_Reference n1_2_1 = {0};
RDIC_Node_Reference n1_2_2 = {0};
RDIC_Node_Reference n2 = {0};
RDIC_Node_Reference n2_1 = {0};
RDIC_Node_Reference n2_2 = {0};
RDIC_Node_Reference n3 = {0};
RDIC_Node_Reference n3_1 = {0};
RDIC_Node_Reference n3_2 = {0};
// Act.
/*
* r
* n n n
* n n nn nn
* nn nn
*/
// First frame.
root = rdic_context_start_frame(&context, root);
SET_DEBUG_NAME(root, "root");
n1 = GET_NODE(&context, n1);
rdic_push_parent(&context, n1);
n1_1 = GET_NODE(&context, n1_1);
rdic_push_parent(&context, n1_1);
n1_1_1 = GET_NODE(&context, n1_1_1);
n1_1_2 = GET_NODE(&context, n1_1_2);
rdic_pop_parent(&context);
n1_2 = GET_NODE(&context, n1_2);
rdic_push_parent(&context, n1_2);
n1_2_1 = GET_NODE(&context, n1_2_1);
n1_2_2 = GET_NODE(&context, n1_2_2);
rdic_pop_parent(&context);
rdic_pop_parent(&context);
n2 = GET_NODE(&context, n2);
rdic_push_parent(&context, n2);
n2_1 = GET_NODE(&context, n2_1);
n2_2 = GET_NODE(&context, n2_2);
rdic_pop_parent(&context);
n3 = GET_NODE(&context, n3);
rdic_push_parent(&context, n3);
n3_1 = GET_NODE(&context, n3_1);
n3_2 = GET_NODE(&context, n3_2);
rdic_pop_parent(&context);
rdic_context_finish_frame(&context);
// Assert.
ASSERT_REFERENCE_VALID(root);
ASSERT_REFERENCE_VALID(n1);
ASSERT_REFERENCE_VALID(n1_1);
ASSERT_REFERENCE_VALID(n1_1_1);
ASSERT_REFERENCE_VALID(n1_1_2);
ASSERT_REFERENCE_VALID(n1_2);
ASSERT_REFERENCE_VALID(n1_2_1);
ASSERT_REFERENCE_VALID(n1_2_2);
ASSERT_REFERENCE_VALID(n2);
ASSERT_REFERENCE_VALID(n2_1);
ASSERT_REFERENCE_VALID(n2_2);
ASSERT_REFERENCE_VALID(n3);
ASSERT_REFERENCE_VALID(n3_1);
ASSERT_REFERENCE_VALID(n3_2);
// NOTE(Zelaven): More asserts could be added to assert correct structure.
}
void test__culling_that_needs_to_use_stash__succeed(void)
{
// Arrange.
RDIC_Context context = {0};
GUI_Node freelist_nodes[14] = {0};
init_node_freelist(freelist_nodes, ARRAYLENGTH(freelist_nodes));
RDIC_Freelist freelist = {&freelist_nodes[0].rn};
context.freelist = &freelist;
RDIC_Node_Reference root = {0};
RDIC_Node_Reference n1 = {0};
RDIC_Node_Reference n1_1 = {0};
RDIC_Node_Reference n1_1_1 = {0};
RDIC_Node_Reference n1_1_2 = {0};
RDIC_Node_Reference n1_2 = {0};
RDIC_Node_Reference n1_2_1 = {0};
RDIC_Node_Reference n1_2_2 = {0};
RDIC_Node_Reference n2 = {0};
RDIC_Node_Reference n2_1 = {0};
RDIC_Node_Reference n2_2 = {0};
RDIC_Node_Reference n3 = {0};
RDIC_Node_Reference n3_1 = {0};
RDIC_Node_Reference n3_2 = {0};
// Act.
/*
* r
* n n n
* n n nn nn
* nn nn
*/
// First frame.
root = rdic_context_start_frame(&context, root);
SET_DEBUG_NAME(root, "root");
n1 = GET_NODE(&context, n1);
rdic_push_parent(&context, n1);
n1_1 = GET_NODE(&context, n1_1);
rdic_push_parent(&context, n1_1);
n1_1_1 = GET_NODE(&context, n1_1_1);
n1_1_2 = GET_NODE(&context, n1_1_2);
rdic_pop_parent(&context);
n1_2 = GET_NODE(&context, n1_2);
rdic_push_parent(&context, n1_2);
n1_2_1 = GET_NODE(&context, n1_2_1);
n1_2_2 = GET_NODE(&context, n1_2_2);
rdic_pop_parent(&context);
rdic_pop_parent(&context);
n2 = GET_NODE(&context, n2);
rdic_push_parent(&context, n2);
n2_1 = GET_NODE(&context, n2_1);
n2_2 = GET_NODE(&context, n2_2);
rdic_pop_parent(&context);
n3 = GET_NODE(&context, n3);
rdic_push_parent(&context, n3);
n3_1 = GET_NODE(&context, n3_1);
n3_2 = GET_NODE(&context, n3_2);
rdic_pop_parent(&context);
rdic_context_finish_frame(&context);
root = rdic_context_start_frame(&context, root);
rdic_context_finish_frame(&context);
// Assert.
// NOTE(Zelaven): The root node is never collected (always re-used).
//ASSERT_REFERENCE_INVALID(root);
//ASSERT_IN_FREELIST(&context, root);
ASSERT_REFERENCE_INVALID(n1);
ASSERT_IN_FREELIST(&context, n1);
ASSERT_REFERENCE_INVALID(n1_1);
ASSERT_IN_FREELIST(&context, n1_1);
ASSERT_REFERENCE_INVALID(n1_1_1);
ASSERT_IN_FREELIST(&context, n1_1_1);
ASSERT_REFERENCE_INVALID(n1_1_2);
ASSERT_IN_FREELIST(&context, n1_1_2);
ASSERT_REFERENCE_INVALID(n1_2);
ASSERT_IN_FREELIST(&context, n1_2);
ASSERT_REFERENCE_INVALID(n1_2_1);
ASSERT_IN_FREELIST(&context, n1_2_1);
ASSERT_REFERENCE_INVALID(n1_2_2);
ASSERT_IN_FREELIST(&context, n1_2_2);
ASSERT_REFERENCE_INVALID(n2);
ASSERT_IN_FREELIST(&context, n2);
ASSERT_REFERENCE_INVALID(n2_1);
ASSERT_IN_FREELIST(&context, n2_1);
ASSERT_REFERENCE_INVALID(n2_2);
ASSERT_IN_FREELIST(&context, n2_2);
ASSERT_REFERENCE_INVALID(n3);
ASSERT_IN_FREELIST(&context, n3);
ASSERT_REFERENCE_INVALID(n3_1);
ASSERT_IN_FREELIST(&context, n3_1);
ASSERT_REFERENCE_INVALID(n3_2);
ASSERT_IN_FREELIST(&context, n3_2);
}
#include <stdio.h>
#if 0
// You can remove the %d in this format specifier by using stringification.
// I just lazied and didn't bother.
#define MY_ASSERT(X) if(!(X)) printf(__FILE__":%d: %s: `"#X"` failed.\n", __LINE__, __func__);
MY_ASSERT(node.generation != 0);
assert(node.generation != 0);
#endif
// --- TEST RUNNER ---
#include <stdio.h>
void print_test_result(Report_Test_Result result)
{
char *color_codes[TESTRESULT_NUM_LEVELS] = {
"\033[30m",
"\033[32m",
"\033[31m",
};
printf("%s: %s%s\033[0m\n", result.test_name, color_codes[result.level], result.string);
}
//#define TEST(func_to_test, args...) print_test_result(#func_to_test, func_to_test(args));
#define TEST(func_to_test, args...) \
g_error_occurred = 0;\
g_test_name = #func_to_test"("#args")";\
func_to_test(args);\
if(!g_error_occurred) log_result_to_report((Report_Test_Result){\
.level = TESTRESULT_SUCCESS,\
.test_name = g_test_name,\
.string = "Test Successful.",\
});
int main()
{
assert(linux_allocate_arena_memory(
&g_test_report_arena,
1024*4) == 0);
log_new_report_group("Starting and Finishing Frames.");
// Starting and finishing frames.
TEST(test__start_frame__empty_freelist);
TEST(test__start_frame__succeed);
TEST(test__finish_frame__succeed);
log_new_report_group("Next node - getting a single node.");
// Next node.
TEST(test__get_node_with_empty_freelist__return_null_reference);
TEST(test__get_node_with_empty_freelist_with_nonzero_generation__return_null_reference);
TEST(test__get_node__succeed);
TEST(test__get_node_finish_frame__succeed);
TEST(test__single_node__returned_to_freelist);
TEST(test__single_node__nodes_are_stable);
TEST(test__single_node_freelist_reuse__succeed);
log_new_report_group("Root - parent - child.");
TEST(test__parent_child_frame__succeed);
TEST(test__parent_child_free_child__child_to_freelist);
TEST(test__parent_child_free_child__parent_to_freelist);
TEST(test__parent_child_both_reused__succeed);
log_new_report_group("Root - parent - 3 children.");
TEST(test__parent_3children_123frame__succeed);
TEST(test__parent_3children_partial_frame_then_complete__succeed, 1,1,1);
TEST(test__parent_3children_partial_frame_then_complete__succeed, 1,1,0);
TEST(test__parent_3children_partial_frame_then_complete__succeed, 1,0,1);
TEST(test__parent_3children_partial_frame_then_complete__succeed, 0,1,1);
TEST(test__parent_3children_partial_frame_then_complete__succeed, 1,0,0);
TEST(test__parent_3children_partial_frame_then_complete__succeed, 0,1,0);
TEST(test__parent_3children_partial_frame_then_complete__succeed, 0,0,1);
TEST(test__parent_3children_partial_frame_then_complete__succeed, 0,0,0);
TEST(test__parent_3children_culling__succeed, 1,1,0);
TEST(test__parent_3children_culling__succeed, 1,0,1);
TEST(test__parent_3children_culling__succeed, 0,1,1);
TEST(test__parent_3children_culling__succeed, 1,0,0);
TEST(test__parent_3children_culling__succeed, 0,1,0);
TEST(test__parent_3children_culling__succeed, 0,0,1);
TEST(test__parent_3children_culling__succeed, 0,0,0);
TEST(test__parent_3children_full_frame_reuse__succeed);
log_new_report_group("Larger tree.");
TEST(test__exhaust_freelist_larger_tree__no_crash);
TEST(test__large_tree_constructed__succeed);
TEST(test__culling_that_needs_to_use_stash__succeed);
// TODO(Zelaven): A test that causes the deallocation of a subtree that
// requires the collector to use the stashing mechanism.
//printf("\n\n\n");
//Test_Result *results = g_test_report_arena.memory;
Report_Group *current_group = g_first_report_group;
while(current_group != NULL)
{
printf("\n*** %s ***\n", current_group->name);
Report_Test_Result *results = current_group->results;
for(unsigned int i = 0; i < current_group->num_test_results; i++)
{
print_test_result(results[i]);
}
current_group = current_group->next;
}
return 0;
}