// *** 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 #include #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 #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 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; }