/* * Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include "rbtree.h" struct nvgpu_rbtree_node *elements[INITIAL_ELEMENTS]; /* * Helper function to ensure a given tree satisfies all the properties to be * considered a red-black binary tree. That is: * 1. Every node is either red or black: implied since color is a bool with only * two possible values. * 2. The root is black: checked by the function below. * 3. Every leaf is black: implied since all leaves are NULL. * 4. If a node is red, then both its children have to be black: checked by the * function below. * 5. All simple paths from a node to its descendant leaves must contain the * same number of black nodes: checked by the function below. * * So only properties 2, 4 and 5 need to be checked. * * Returns either: * - a negative value in case of error * - the number of black nodes to leaves (which is the black height of the tree * when ran from the root). */ static int check_rbtree(struct unit_module *m, struct nvgpu_rbtree_node *node) { int left_black_count, right_black_count; int black_count = 0; if (node == NULL) { /* This is a leaf, so black count is 1 */ return 1; } /* Check property 2 (root is black) */ if ((node->parent == NULL) && (node->is_red)) { unit_err(m, "check_rbtree: root is red\n"); return -1; } /* Check property 4 (if red node, children must be black) */ if (node->is_red) { /* * If left or right is NULL then it is a leaf which is * implicitly black */ if ((node->left != NULL) && (node->left->is_red)) { unit_err(m, "check_rbtree: l_child of red parent is also red\n"); return -1; } if ((node->right != NULL) && (node->right->is_red)) { unit_err(m, "check_rbtree: r_child of red parent is also red\n"); return -1; } } /* Count black nodes */ if (!node->is_red) { black_count = 1; } /* * Check property 5 (descendant leaves must have the same number of * black nodes) * Start by recursively checking the height of the left and right * sub-trees. */ left_black_count = check_rbtree(m, node->left); right_black_count = check_rbtree(m, node->right); if ((left_black_count == -1) || (right_black_count == -1)) { /* There was an error in one of the subtrees, propagate it */ return -1; } if (left_black_count != right_black_count) { unit_err(m, "check_rbtree: mismatch between left and right\n"); return -1; } return left_black_count + black_count; } /* * Helper function to insert elements into a tree using the initial_key_start * values. */ static int fill_test_tree(struct unit_module *m, struct nvgpu_rbtree_node **root) { int i; for (i = 0; i < INITIAL_ELEMENTS; i++) { elements[i] = (struct nvgpu_rbtree_node *) malloc(sizeof(struct nvgpu_rbtree_node)); elements[i]->key_start = initial_key_start[i]; elements[i]->key_end = initial_key_start[i]+RANGE_SIZE; nvgpu_rbtree_insert(elements[i], root); } return UNIT_SUCCESS; } /* * Helper function to free the test nodes of the tree. */ static void free_test_tree(struct unit_module *m, struct nvgpu_rbtree_node *root) { int i; for (i = 0; i < INITIAL_ELEMENTS; i++) { free(elements[i]); } /* No need to explicitly free the root as it was one of the elements */ } int test_insert(struct unit_module *m, struct gk20a *g, void *args) { struct nvgpu_rbtree_node *root = NULL; struct nvgpu_rbtree_node *node1, *node2 = NULL; int status = UNIT_FAIL; fill_test_tree(m, &root); if (check_rbtree(m, root) < 0) { goto free_tree; } node1 = (struct nvgpu_rbtree_node *) malloc(sizeof(struct nvgpu_rbtree_node)); node1->key_start = RED_BLACK_VIOLATION_1; node1->key_end = RED_BLACK_VIOLATION_1+RANGE_SIZE; nvgpu_rbtree_insert(node1, &root); node2 = (struct nvgpu_rbtree_node *) malloc(sizeof(struct nvgpu_rbtree_node)); node2->key_start = RED_BLACK_VIOLATION_2; node2->key_end = RED_BLACK_VIOLATION_2+RANGE_SIZE; nvgpu_rbtree_insert(node2, &root); if (check_rbtree(m, root) < 0) { goto free_nodes; } status = UNIT_SUCCESS; free_nodes: free(node1); free(node2); free_tree: free_test_tree(m, root); return status; } int test_unlink(struct unit_module *m, struct gk20a *g, void *args) { struct nvgpu_rbtree_node *root = NULL; struct nvgpu_rbtree_node *result = NULL; int status = UNIT_FAIL; bool duplicate_handled = false; u64 key_start_search; int i; fill_test_tree(m, &root); for (i = 0; i < INITIAL_ELEMENTS; i++) { /* * Search for a node from values in the initial_key_start table. */ key_start_search = initial_key_start[i]; if ((key_start_search == DUPLICATE_VALUE) && (!duplicate_handled)) { duplicate_handled = true; continue; } nvgpu_rbtree_search(key_start_search, &result, root); if (result == NULL) { unit_err(m, "Search failed for key_start=%lld\n", key_start_search); goto cleanup; } else { if (verbose_lvl(m) > 0) { unit_info(m, "Found node with key_start=%lld\n", result->key_start); } } /* * Unlink will simply remove the node from the tree. It will not * free the resources. It will be done at the end of this * function. */ nvgpu_rbtree_unlink(result, &root); /* Make sure the node was actually removed */ nvgpu_rbtree_search(key_start_search, &result, root); if (result != NULL) { unit_err(m, "Unlink failed, node still exists\n"); goto cleanup; } else { if (verbose_lvl(m) > 0) { unit_info(m, "Node was removed as expected\n"); } } } status = UNIT_SUCCESS; cleanup: free_test_tree(m, root); return status; } int test_search(struct unit_module *m, struct gk20a *g, void *args) { struct nvgpu_rbtree_node *root = NULL; struct nvgpu_rbtree_node *result1, *result2 = NULL; int status = UNIT_FAIL; u64 key_start_search = SEARCH_KEY; fill_test_tree(m, &root); /* Search with a NULL root should not crash and keep result as NULL */ nvgpu_rbtree_search(key_start_search, &result1, NULL); if (result1 != NULL) { unit_err(m, "Search did not fail as expected\n"); goto cleanup; } /* Same thing with the range_search operation */ nvgpu_rbtree_range_search(key_start_search, &result2, NULL); if (result1 != NULL) { unit_err(m, "Range search did not fail as expected\n"); goto cleanup; } /* Now search for a real value */ if (verbose_lvl(m) > 0) { unit_info(m, "Searching for key_start=%lld\n", key_start_search); } nvgpu_rbtree_search(key_start_search, &result1, root); if (result1 == NULL) { unit_err(m, "Search failed\n"); goto cleanup; } else if (verbose_lvl(m) > 0) { unit_info(m, "Found node with key_start=%lld key_end=%lld\n", result1->key_start, result1->key_end); } /* * Now do a range search by just incrementing key_start_search by 1 * which should yield the exact same result as the previous search * (since it will fall in the same range) */ key_start_search++; if (verbose_lvl(m) > 0) { unit_info(m, "Range searching for key=%lld\n", key_start_search); } nvgpu_rbtree_range_search(key_start_search, &result2, root); if (result2 == NULL) { unit_err(m, "Range search failed\n"); goto cleanup; } else if (result1 != result2) { unit_err(m, "Range search did not find the expected result\n"); goto cleanup; } else if (verbose_lvl(m) > 0) { unit_info(m, "Found node with key_start=%lld key_end=%lld\n", result1->key_start, result1->key_end); } status = UNIT_SUCCESS; cleanup: free_test_tree(m, root); return status; } int test_enum(struct unit_module *m, struct gk20a *g, void *args) { struct nvgpu_rbtree_node *root = NULL; struct nvgpu_rbtree_node *node = NULL; int status = UNIT_FAIL; u64 key_start; int i; fill_test_tree(m, &root); /* Enum with a NULL root should not crash and keep result as NULL */ nvgpu_rbtree_enum_start(0, &node, NULL); if (node != NULL) { unit_err(m, "Enum did not fail as expected (NULL root)\n"); goto cleanup; } /* Enum all the nodes we know are in the tree */ for (i = 0; i < INITIAL_ELEMENTS; i++) { key_start = initial_key_start[i]; nvgpu_rbtree_enum_start(key_start, &node, root); if (node->key_start != key_start) { unit_err(m, "Enum mismatch\n"); goto cleanup; } } /* If the key_start does not exist, enum should return a NULL node */ nvgpu_rbtree_enum_start(INVALID_KEY_START, &node, root); if (node != NULL) { unit_err(m, "Enum did not fail as expected: wrong key_start\n"); goto cleanup; } status = UNIT_SUCCESS; cleanup: free_test_tree(m, root); return status; } int test_enum_next(struct unit_module *m, struct gk20a *g, void *args) { struct nvgpu_rbtree_node *root = NULL; struct nvgpu_rbtree_node *node = NULL; int status = UNIT_FAIL; u64 prev_key_start; fill_test_tree(m, &root); /* Enum with a NULL root should not crash and keep result as NULL */ nvgpu_rbtree_enum_next(&node, NULL); if (node != NULL) { unit_err(m, "Enum_next did not fail as expected (NULL root)\n"); goto cleanup; } /* * The tree is balanced and we know there are INITIAL_ELEMENTS inside. * Enumerate the next key_start value from root. */ node = root; prev_key_start = node->key_start; while (node != NULL) { nvgpu_rbtree_enum_next(&node, root); if (node != NULL) { if (verbose_lvl(m) > 0) { unit_info(m, "Node has key_start=%lld\n", node->key_start); } if (node->key_start < prev_key_start) { unit_err(m, "Enum_next returned a low value\n"); goto cleanup; } prev_key_start = node->key_start; } } /* For branch coverage, test some error handling. */ node = NULL; nvgpu_rbtree_enum_next(&node, root); nvgpu_rbtree_enum_next(&node, NULL); status = UNIT_SUCCESS; cleanup: free_test_tree(m, root); return status; } int test_search_less(struct unit_module *m, struct gk20a *g, void *args) { struct nvgpu_rbtree_node *root = NULL; struct nvgpu_rbtree_node *result; int status = UNIT_FAIL; u64 key_start_search; fill_test_tree(m, &root); /* * The tree is balanced, so the range in the root should be in the * middle of the values, so searching for that value will guarantee * a result. */ key_start_search = root->key_start; nvgpu_rbtree_less_than_search(key_start_search, &result, root); if (result == NULL) { unit_err(m, "less_than_search unexpectedly failed\n"); goto cleanup; } if (result->key_start >= key_start_search) { unit_err(m, "less_than_search returned a wrong result\n"); goto cleanup; } status = UNIT_SUCCESS; cleanup: free_test_tree(m, root); return status; } int test_unlink_corner_cases(struct unit_module *m, struct gk20a *g, void *args) { struct nvgpu_rbtree_node *root = NULL; u64 more_key_start[] = {0x1000, 0x61000, 0x79000, 0x7d000, 0x7f000, 0x80000, 0x91000, 0x81000, 0x71000, 0x99000, 0x9d000, 0xa0000, 0x500, 0x600, 0x700, 0x800, 0x900, 0xa000, 0xb000, 0xc000, 0xd000, 0xe000, 0xf000}; u64 num_elems = sizeof(more_key_start) / sizeof(u64); struct nvgpu_rbtree_node **more_elements = malloc(sizeof(struct nvgpu_rbtree_node *)*num_elems); u64 i; fill_test_tree(m, &root); /* * Add extra nodes to create a much more complicated tree that will * allow targeting specific conditions when unlinking those nodes. * Even though the unlinking of some of those nodes have no direct * impact on line or branch coverage, their presence is needed to create * the corner cases we need. */ for (i = 0; i < num_elems; i++) { more_elements[i] = (struct nvgpu_rbtree_node *) malloc(sizeof(struct nvgpu_rbtree_node)); more_elements[i]->key_start = more_key_start[i]; more_elements[i]->key_end = more_key_start[i]+RANGE_SIZE; nvgpu_rbtree_insert(more_elements[i], &root); } /* No impact on coverage */ nvgpu_rbtree_unlink(more_elements[0], &root); /* * Targets some conditions when removing a node on the left and the * right needs to become red. */ nvgpu_rbtree_unlink(more_elements[1], &root); /* No impact on coverage */ nvgpu_rbtree_unlink(more_elements[2], &root); /* * Targets some conditions when removing a node on the left and the * right is NULL. */ nvgpu_rbtree_unlink(more_elements[3], &root); /* No impact on coverage */ nvgpu_rbtree_unlink(more_elements[4], &root); /* * Targets some conditions when removing a node on the left and the * right node is red. This requires rotating the tree to the left. */ nvgpu_rbtree_unlink(more_elements[5], &root); /* No impact on coverage */ nvgpu_rbtree_unlink(more_elements[6], &root); nvgpu_rbtree_unlink(more_elements[7], &root); /* * Targets statements in the link rebuilding of the rotate_left * function. Also targets some conditions when removing a node on the * right and the left needs to become black. */ nvgpu_rbtree_unlink(more_elements[8], &root); /* * Targets statements in the link rebuilding of nvgpu_rbtree_unlink */ nvgpu_rbtree_unlink(more_elements[9], &root); /* No impact on coverage */ nvgpu_rbtree_unlink(more_elements[10], &root); nvgpu_rbtree_unlink(more_elements[11], &root); nvgpu_rbtree_unlink(more_elements[12], &root); nvgpu_rbtree_unlink(more_elements[13], &root); nvgpu_rbtree_unlink(more_elements[14], &root); nvgpu_rbtree_unlink(more_elements[15], &root); nvgpu_rbtree_unlink(more_elements[16], &root); nvgpu_rbtree_unlink(more_elements[17], &root); /* * Targets statements in the link rebuilding of delete_fixup (right * sibling of deleted node is black or has a right sentinel) */ nvgpu_rbtree_unlink(more_elements[18], &root); /* * Targets statement in the rotate_left function (the rotated node * becomes the root of the tree) */ nvgpu_rbtree_unlink(more_elements[19], &root); /* No impact on coverage */ nvgpu_rbtree_unlink(more_elements[20], &root); nvgpu_rbtree_unlink(more_elements[21], &root); nvgpu_rbtree_unlink(more_elements[22], &root); free_test_tree(m, root); for (i = 0; i < num_elems; i++) { free(more_elements[i]); } free(more_elements); return UNIT_SUCCESS; } struct unit_module_test interface_rbtree_tests[] = { UNIT_TEST(insert, test_insert, NULL, 0), UNIT_TEST(search, test_search, NULL, 0), UNIT_TEST(unlink, test_unlink, NULL, 0), UNIT_TEST(enum, test_enum, NULL, 0), UNIT_TEST(enum_next, test_enum_next, NULL, 0), UNIT_TEST(search_less_than, test_search_less, NULL, 0), UNIT_TEST(unlink_corner_cases, test_unlink_corner_cases, NULL, 0), }; UNIT_MODULE(interface_rbtree, interface_rbtree_tests, UNIT_PRIO_NVGPU_TEST);