Files
linux-nvgpu/userspace/units/interface/rbtree/rbtree.c
Nicolas Benech 9ad3de64b1 gpu: nvgpu: unit: SWUTS for interface.rbtree
Add SWUTS documentation for the interface.rbtree unit.

JIRA NVGPU-3943

Change-Id: I9555090bc986f5cea76add6a46309a13b37f371c
Signed-off-by: Nicolas Benech <nbenech@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/2207312
Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com>
Tested-by: mobile promotions <svcmobile_promotions@nvidia.com>
2020-12-15 14:05:52 -06:00

549 lines
15 KiB
C

/*
* 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 <unit/io.h>
#include <unit/unit.h>
#include <unit/core.h>
#include <nvgpu/rbtree.h>
#include <stdlib.h>
#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);