2016-04-01 10:46:28 +03:00
|
|
|
// Copyright 2016-present Facebook. All Rights Reserved.
|
|
|
|
//
|
|
|
|
// node_test.c: unit tests for the node.c
|
2016-04-01 21:19:53 +03:00
|
|
|
//
|
|
|
|
// no-check-code
|
2016-04-01 10:46:28 +03:00
|
|
|
|
|
|
|
#include "node.h"
|
|
|
|
#include "tests.h"
|
|
|
|
|
|
|
|
#define ALLOC_NODE_STR(name, max_children) \
|
|
|
|
alloc_node(name, strlen(name), max_children)
|
|
|
|
#define GET_CHILD_BY_NAME_STR(node, name) \
|
|
|
|
get_child_by_name(node, name, strlen(name))
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a child and ensure that it can be found.
|
|
|
|
*/
|
|
|
|
void test_simple_parent_child() {
|
|
|
|
node_t* parent = ALLOC_NODE_STR("parent", 1);
|
|
|
|
node_t* child = ALLOC_NODE_STR("child", 0);
|
|
|
|
parent->in_use = true;
|
|
|
|
parent->type = TYPE_IMPLICIT;
|
|
|
|
child->in_use = true;
|
|
|
|
child->type = TYPE_LEAF;
|
|
|
|
|
|
|
|
node_add_child_result_t result = add_child(parent, child);
|
|
|
|
ASSERT(result == ADD_CHILD_OK);
|
|
|
|
|
|
|
|
node_t* lookup_child = GET_CHILD_BY_NAME_STR(parent, "child");
|
|
|
|
ASSERT(lookup_child == child);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Ensure that our size calculations are reasonable accurate by allocating a
|
|
|
|
* bunch of differently sized parents and adding a child.
|
|
|
|
*/
|
|
|
|
void test_space() {
|
|
|
|
for (uint16_t name_sz = 1; name_sz <= 8; name_sz ++) {
|
|
|
|
node_t* parent = alloc_node("abcdefgh", name_sz, 1);
|
|
|
|
node_t* child = ALLOC_NODE_STR("child", 0);
|
|
|
|
parent->in_use = true;
|
|
|
|
parent->type = TYPE_IMPLICIT;
|
|
|
|
child->in_use = true;
|
|
|
|
child->type = TYPE_LEAF;
|
|
|
|
|
|
|
|
node_add_child_result_t result = add_child(parent, child);
|
|
|
|
ASSERT(result == ADD_CHILD_OK);
|
|
|
|
|
|
|
|
node_t* lookup_child = GET_CHILD_BY_NAME_STR(parent, "child");
|
|
|
|
ASSERT(lookup_child == child);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Try to add a child to a node that does not have enough space.
|
|
|
|
*/
|
|
|
|
void test_insufficient_space() {
|
|
|
|
node_t* parent = ALLOC_NODE_STR("parent", 1);
|
|
|
|
node_t* child1 = ALLOC_NODE_STR("child1", 0);
|
|
|
|
node_t* child2 = ALLOC_NODE_STR("child2", 0);
|
|
|
|
parent->in_use = true;
|
|
|
|
parent->type = TYPE_IMPLICIT;
|
|
|
|
child1->in_use = true;
|
|
|
|
child1->type = TYPE_LEAF;
|
|
|
|
child2->in_use = true;
|
|
|
|
child2->type = TYPE_LEAF;
|
|
|
|
|
|
|
|
node_add_child_result_t result = add_child(parent, child1);
|
|
|
|
ASSERT(result == ADD_CHILD_OK);
|
|
|
|
result = add_child(parent, child2);
|
|
|
|
ASSERT(result == NEEDS_LARGER_NODE);
|
|
|
|
|
|
|
|
node_t* lookup_child = GET_CHILD_BY_NAME_STR(parent, "child1");
|
|
|
|
ASSERT(lookup_child == child1);
|
|
|
|
lookup_child = GET_CHILD_BY_NAME_STR(parent, "child2");
|
|
|
|
ASSERT(lookup_child == NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Call `add_child` with a bunch of different arguments and verify the results
|
|
|
|
* are reasonable.
|
|
|
|
*/
|
|
|
|
typedef struct {
|
|
|
|
bool parent_in_use;
|
|
|
|
int parent_type;
|
|
|
|
bool child_in_use;
|
|
|
|
int child_type;
|
|
|
|
node_add_child_result_t expected_result;
|
|
|
|
} parent_child_test_cases_t;
|
|
|
|
void test_add_child_combinations() {
|
|
|
|
parent_child_test_cases_t cases[] =
|
|
|
|
{
|
|
|
|
// parent or child not in use.
|
|
|
|
{false, TYPE_IMPLICIT, true, TYPE_LEAF, ADD_CHILD_ILLEGAL_PARENT},
|
|
|
|
{true, TYPE_IMPLICIT, false, TYPE_LEAF, ADD_CHILD_ILLEGAL_CHILD},
|
|
|
|
|
|
|
|
// parent type invalid.
|
|
|
|
{true, TYPE_LEAF, true, TYPE_LEAF, ADD_CHILD_ILLEGAL_PARENT},
|
|
|
|
|
|
|
|
// child type invalid.
|
|
|
|
{true, TYPE_IMPLICIT, false, TYPE_UNDEFINED, ADD_CHILD_ILLEGAL_CHILD},
|
|
|
|
|
|
|
|
// some good outcomes.
|
|
|
|
{true, TYPE_IMPLICIT, true, TYPE_LEAF, ADD_CHILD_OK},
|
|
|
|
{true, TYPE_IMPLICIT, true, TYPE_IMPLICIT, ADD_CHILD_OK},
|
|
|
|
};
|
|
|
|
|
|
|
|
for (int ix = 0;
|
|
|
|
ix < sizeof(cases) / sizeof(parent_child_test_cases_t);
|
|
|
|
ix ++) {
|
|
|
|
node_t* parent;
|
|
|
|
node_t* child;
|
|
|
|
|
|
|
|
parent = ALLOC_NODE_STR("parent", 1);
|
|
|
|
child = ALLOC_NODE_STR("child", 0);
|
|
|
|
|
|
|
|
parent->in_use = cases[ix].parent_in_use;
|
|
|
|
parent->type = cases[ix].parent_type;
|
|
|
|
child->in_use = cases[ix].child_in_use;
|
|
|
|
child->type = cases[ix].child_type;
|
|
|
|
node_add_child_result_t result = add_child(parent, child);
|
|
|
|
ASSERT(result == cases[ix].expected_result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Insert children in lexicographical order. Ensure that we can find them.
|
|
|
|
*
|
|
|
|
* requirement: strlen(TEST_MANY_CHILDREN_NAME_STR) >=
|
|
|
|
* TEST_MANY_CHILDREN_CHILD_COUNT
|
|
|
|
*/
|
|
|
|
#define TEST_MANY_CHILDREN_NAME_STR "abcdefgh"
|
|
|
|
#define TEST_MANY_CHILDREN_COUNT 8
|
|
|
|
void test_many_children() {
|
|
|
|
node_t* parent = ALLOC_NODE_STR("parent", TEST_MANY_CHILDREN_COUNT);
|
|
|
|
node_t* children[TEST_MANY_CHILDREN_COUNT]; // this should be ordered as we
|
|
|
|
// expect to find them in the
|
|
|
|
// parent's list of children.
|
|
|
|
for (uint16_t name_sz = 1; name_sz <= TEST_MANY_CHILDREN_COUNT; name_sz ++) {
|
|
|
|
node_t* child = alloc_node(
|
|
|
|
TEST_MANY_CHILDREN_NAME_STR,
|
|
|
|
name_sz,
|
|
|
|
0);
|
|
|
|
parent->in_use = true;
|
|
|
|
parent->type = TYPE_IMPLICIT;
|
|
|
|
child->in_use = true;
|
|
|
|
child->type = TYPE_LEAF;
|
|
|
|
|
|
|
|
node_add_child_result_t result = add_child(parent, child);
|
|
|
|
ASSERT(result == ADD_CHILD_OK);
|
|
|
|
|
|
|
|
children[name_sz - 1] = child;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (uint16_t name_sz = 1; name_sz <= TEST_MANY_CHILDREN_COUNT; name_sz ++) {
|
|
|
|
node_t* result = get_child_by_name(
|
|
|
|
parent,
|
|
|
|
TEST_MANY_CHILDREN_NAME_STR,
|
|
|
|
name_sz);
|
|
|
|
ASSERT(result == children[name_sz - 1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Insert children in reverse lexicographical order. Ensure that we can find
|
|
|
|
* them.
|
|
|
|
*
|
|
|
|
* requirement: strlen(TEST_MANY_CHILDREN_NAME_STR) >=
|
|
|
|
* TEST_MANY_CHILDREN_CHILD_COUNT
|
|
|
|
*/
|
|
|
|
void test_many_children_reverse() {
|
|
|
|
node_t* parent = ALLOC_NODE_STR("parent", TEST_MANY_CHILDREN_COUNT);
|
|
|
|
node_t* children[TEST_MANY_CHILDREN_COUNT]; // this should be ordered as we
|
|
|
|
// expect to find them in the
|
|
|
|
// parent's list of children.
|
|
|
|
for (uint16_t name_sz = TEST_MANY_CHILDREN_COUNT; name_sz > 0; name_sz --) {
|
|
|
|
node_t* child = alloc_node(
|
|
|
|
TEST_MANY_CHILDREN_NAME_STR,
|
|
|
|
name_sz,
|
|
|
|
0);
|
|
|
|
parent->in_use = true;
|
|
|
|
parent->type = TYPE_IMPLICIT;
|
|
|
|
child->in_use = true;
|
|
|
|
child->type = TYPE_LEAF;
|
|
|
|
|
|
|
|
node_add_child_result_t result = add_child(parent, child);
|
|
|
|
ASSERT(result == ADD_CHILD_OK);
|
|
|
|
|
|
|
|
children[name_sz - 1] = child;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (uint16_t name_sz = 1; name_sz <= TEST_MANY_CHILDREN_COUNT; name_sz ++) {
|
|
|
|
node_t* result = get_child_by_name(
|
|
|
|
parent,
|
|
|
|
TEST_MANY_CHILDREN_NAME_STR,
|
|
|
|
name_sz);
|
|
|
|
ASSERT(result == children[name_sz - 1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a node with many children. Clone the node. Ensure we can locate all
|
|
|
|
* of the children.
|
|
|
|
*
|
|
|
|
* requirement: strlen(TEST_CLONE_NAME_STR) >=
|
|
|
|
* TEST_CLONE_COUNT
|
|
|
|
*/
|
|
|
|
#define TEST_CLONE_NAME_STR "abcdefgh"
|
|
|
|
#define TEST_CLONE_COUNT 8
|
|
|
|
void test_clone() {
|
|
|
|
node_t* parent = ALLOC_NODE_STR("parent", TEST_CLONE_COUNT);
|
|
|
|
node_t* children[TEST_CLONE_COUNT]; // this should be ordered as we
|
|
|
|
// expect to find them in the
|
|
|
|
// parent's list of children.
|
|
|
|
for (uint16_t name_sz = 1; name_sz <= TEST_CLONE_COUNT; name_sz ++) {
|
|
|
|
node_t* child = alloc_node(
|
|
|
|
TEST_CLONE_NAME_STR,
|
|
|
|
name_sz,
|
|
|
|
0);
|
|
|
|
parent->in_use = true;
|
|
|
|
parent->type = TYPE_IMPLICIT;
|
|
|
|
child->in_use = true;
|
|
|
|
child->type = TYPE_LEAF;
|
|
|
|
|
|
|
|
node_add_child_result_t result = add_child(parent, child);
|
|
|
|
ASSERT(result == ADD_CHILD_OK);
|
|
|
|
|
|
|
|
children[name_sz - 1] = child;
|
|
|
|
}
|
|
|
|
|
|
|
|
node_t* clone = clone_node(parent);
|
|
|
|
|
|
|
|
for (uint16_t name_sz = 1; name_sz <= TEST_CLONE_COUNT; name_sz ++) {
|
|
|
|
node_t* result = get_child_by_name(
|
|
|
|
clone,
|
|
|
|
TEST_CLONE_NAME_STR,
|
|
|
|
name_sz);
|
|
|
|
ASSERT(result == children[name_sz - 1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT(max_children(clone) > max_children(parent));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a node with many children. Remove them in a pseudorandom fashion.
|
|
|
|
* Ensure that the remaining children can be correctly found.
|
|
|
|
*
|
|
|
|
* requirement: strlen(TEST_REMOVE_CHILD_NAME_STR) >=
|
|
|
|
* TEST_REMOVE_CHILD_COUNT
|
|
|
|
*/
|
|
|
|
#define TEST_REMOVE_CHILD_NAME_STR "1234ffgg"
|
|
|
|
#define TEST_REMOVE_CHILD_COUNT 8
|
|
|
|
void test_remove_child() {
|
|
|
|
node_t* parent = ALLOC_NODE_STR("parent", TEST_REMOVE_CHILD_COUNT);
|
|
|
|
node_t* children[TEST_REMOVE_CHILD_COUNT]; // this should be ordered as we
|
|
|
|
// expect to find them in the
|
|
|
|
// parent's list of children.
|
|
|
|
bool valid[TEST_REMOVE_CHILD_COUNT];
|
|
|
|
for (uint16_t name_sz = 1; name_sz <= TEST_REMOVE_CHILD_COUNT; name_sz ++) {
|
|
|
|
node_t* child = alloc_node(
|
|
|
|
TEST_REMOVE_CHILD_NAME_STR,
|
|
|
|
name_sz,
|
|
|
|
0);
|
|
|
|
parent->in_use = true;
|
|
|
|
parent->type = TYPE_IMPLICIT;
|
|
|
|
child->in_use = true;
|
|
|
|
child->type = TYPE_LEAF;
|
|
|
|
|
|
|
|
node_add_child_result_t result = add_child(parent, child);
|
|
|
|
ASSERT(result == ADD_CHILD_OK);
|
|
|
|
|
|
|
|
children[name_sz - 1] = child;
|
|
|
|
valid[name_sz - 1] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (uint16_t ix = 0; ix < TEST_REMOVE_CHILD_COUNT; ix ++) {
|
|
|
|
uint16_t victim_index = 0;
|
|
|
|
for (uint16_t jx = 0; jx < TEST_REMOVE_CHILD_COUNT + 1; jx ++) {
|
|
|
|
do {
|
|
|
|
victim_index = (victim_index + 1) % TEST_REMOVE_CHILD_COUNT;
|
|
|
|
} while (valid[victim_index] == false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// ok, we found our victim. remove it.
|
|
|
|
node_search_children_result_t search_result = search_children(
|
|
|
|
parent,
|
|
|
|
TEST_REMOVE_CHILD_NAME_STR,
|
|
|
|
victim_index + 1);
|
|
|
|
|
|
|
|
ASSERT(search_result.child == children[victim_index]);
|
|
|
|
valid[victim_index] = false;
|
|
|
|
|
|
|
|
ASSERT(remove_child(parent, search_result.child_num) ==
|
|
|
|
REMOVE_CHILD_OK);
|
|
|
|
|
|
|
|
// go through the items that should still be children, and make sure they're
|
|
|
|
// still reachable.
|
|
|
|
for (uint16_t name_sz = 1; name_sz <= TEST_REMOVE_CHILD_COUNT; name_sz ++) {
|
|
|
|
node_t* child = get_child_by_name(
|
|
|
|
parent,
|
|
|
|
TEST_REMOVE_CHILD_NAME_STR,
|
|
|
|
name_sz);
|
|
|
|
if (valid[name_sz - 1]) {
|
|
|
|
ASSERT(child != NULL);
|
|
|
|
} else {
|
|
|
|
ASSERT(child == NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a node and add many children. Enlarge one of the children.
|
|
|
|
*
|
|
|
|
* requirement: strlen(TEST_ENLARGE_CHILD_CAPACITY_NAME_STR) >=
|
|
|
|
* TEST_ENLARGE_CHILD_CAPACITY_COUNT
|
|
|
|
*/
|
|
|
|
#define TEST_ENLARGE_CHILD_CAPACITY_NAME_STR "abcdefgh"
|
|
|
|
#define TEST_ENLARGE_CHILD_CAPACITY_COUNT 8
|
|
|
|
void test_enlarge_child_capacity() {
|
|
|
|
node_t* parent = ALLOC_NODE_STR(
|
|
|
|
"parent",
|
|
|
|
TEST_MANY_CHILDREN_COUNT);
|
|
|
|
node_t* children[TEST_MANY_CHILDREN_COUNT]; // this should be ordered as we
|
|
|
|
// expect to find them in the
|
|
|
|
// parent's list of children.
|
|
|
|
for (uint16_t name_sz = 1; name_sz <= TEST_MANY_CHILDREN_COUNT; name_sz ++) {
|
|
|
|
node_t* child = alloc_node(
|
|
|
|
TEST_ENLARGE_CHILD_CAPACITY_NAME_STR,
|
|
|
|
name_sz,
|
|
|
|
0);
|
|
|
|
parent->in_use = true;
|
|
|
|
parent->type = TYPE_IMPLICIT;
|
|
|
|
child->in_use = true;
|
|
|
|
child->type = TYPE_LEAF;
|
|
|
|
|
|
|
|
node_add_child_result_t result = add_child(parent, child);
|
|
|
|
ASSERT(result == ADD_CHILD_OK);
|
|
|
|
|
|
|
|
children[name_sz - 1] = child;
|
|
|
|
}
|
|
|
|
|
|
|
|
node_enlarge_child_capacity_result_t enlarge_child_capacity_result =
|
|
|
|
enlarge_child_capacity(parent, 0);
|
|
|
|
ASSERT(enlarge_child_capacity_result.code == ENLARGE_OK);
|
|
|
|
ASSERT(enlarge_child_capacity_result.old_child ==
|
|
|
|
children[0]);
|
|
|
|
|
|
|
|
node_t* enlarged = get_child_by_index(parent, 0);
|
|
|
|
ASSERT(max_children(enlarged) > 0);
|
|
|
|
ASSERT(name_compare(enlarged->name, enlarged->name_sz,
|
|
|
|
enlarge_child_capacity_result.old_child) == 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char* argv[]) {
|
|
|
|
test_simple_parent_child();
|
|
|
|
test_space();
|
|
|
|
test_insufficient_space();
|
|
|
|
test_add_child_combinations();
|
|
|
|
test_many_children();
|
|
|
|
test_many_children_reverse();
|
|
|
|
test_clone();
|
|
|
|
test_remove_child();
|
|
|
|
test_enlarge_child_capacity();
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|