| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * KUnit test for the Kernel Hashtable structures. | |
| * | |
| * Copyright (C) 2022, Google LLC. | |
| * Author: Rae Moar <rmoar@google.com> | |
| */ | |
| #include <kunit/test.h> | |
| #include <linux/hashtable.h> | |
| struct hashtable_test_entry { | |
| int key; | |
| int data; | |
| struct hlist_node node; | |
| int visited; | |
| }; | |
| static void hashtable_test_hash_init(struct kunit *test) | |
| { | |
| /* Test the different ways of initialising a hashtable. */ | |
| DEFINE_HASHTABLE(hash1, 2); | |
| DECLARE_HASHTABLE(hash2, 3); | |
| /* When using DECLARE_HASHTABLE, must use hash_init to | |
| * initialize the hashtable. | |
| */ | |
| hash_init(hash2); | |
| KUNIT_EXPECT_TRUE(test, hash_empty(hash1)); | |
| KUNIT_EXPECT_TRUE(test, hash_empty(hash2)); | |
| } | |
| static void hashtable_test_hash_empty(struct kunit *test) | |
| { | |
| struct hashtable_test_entry a; | |
| DEFINE_HASHTABLE(hash, 1); | |
| KUNIT_EXPECT_TRUE(test, hash_empty(hash)); | |
| a.key = 1; | |
| a.data = 13; | |
| hash_add(hash, &a.node, a.key); | |
| /* Hashtable should no longer be empty. */ | |
| KUNIT_EXPECT_FALSE(test, hash_empty(hash)); | |
| } | |
| static void hashtable_test_hash_hashed(struct kunit *test) | |
| { | |
| struct hashtable_test_entry a, b; | |
| DEFINE_HASHTABLE(hash, 4); | |
| a.key = 1; | |
| a.data = 13; | |
| hash_add(hash, &a.node, a.key); | |
| b.key = 1; | |
| b.data = 2; | |
| hash_add(hash, &b.node, b.key); | |
| KUNIT_EXPECT_TRUE(test, hash_hashed(&a.node)); | |
| KUNIT_EXPECT_TRUE(test, hash_hashed(&b.node)); | |
| } | |
| static void hashtable_test_hash_add(struct kunit *test) | |
| { | |
| struct hashtable_test_entry a, b, *x; | |
| int bkt; | |
| DEFINE_HASHTABLE(hash, 3); | |
| a.key = 1; | |
| a.data = 13; | |
| a.visited = 0; | |
| hash_add(hash, &a.node, a.key); | |
| b.key = 2; | |
| b.data = 10; | |
| b.visited = 0; | |
| hash_add(hash, &b.node, b.key); | |
| hash_for_each(hash, bkt, x, node) { | |
| x->visited++; | |
| if (x->key == a.key) | |
| KUNIT_EXPECT_EQ(test, x->data, 13); | |
| else if (x->key == b.key) | |
| KUNIT_EXPECT_EQ(test, x->data, 10); | |
| else | |
| KUNIT_FAIL(test, "Unexpected key in hashtable."); | |
| } | |
| /* Both entries should have been visited exactly once. */ | |
| KUNIT_EXPECT_EQ(test, a.visited, 1); | |
| KUNIT_EXPECT_EQ(test, b.visited, 1); | |
| } | |
| static void hashtable_test_hash_del(struct kunit *test) | |
| { | |
| struct hashtable_test_entry a, b, *x; | |
| DEFINE_HASHTABLE(hash, 6); | |
| a.key = 1; | |
| a.data = 13; | |
| hash_add(hash, &a.node, a.key); | |
| b.key = 2; | |
| b.data = 10; | |
| b.visited = 0; | |
| hash_add(hash, &b.node, b.key); | |
| hash_del(&b.node); | |
| hash_for_each_possible(hash, x, node, b.key) { | |
| x->visited++; | |
| KUNIT_EXPECT_NE(test, x->key, b.key); | |
| } | |
| /* The deleted entry should not have been visited. */ | |
| KUNIT_EXPECT_EQ(test, b.visited, 0); | |
| hash_del(&a.node); | |
| /* The hashtable should be empty. */ | |
| KUNIT_EXPECT_TRUE(test, hash_empty(hash)); | |
| } | |
| static void hashtable_test_hash_for_each(struct kunit *test) | |
| { | |
| struct hashtable_test_entry entries[3]; | |
| struct hashtable_test_entry *x; | |
| int bkt, i, j, count; | |
| DEFINE_HASHTABLE(hash, 3); | |
| /* Add three entries to the hashtable. */ | |
| for (i = 0; i < 3; i++) { | |
| entries[i].key = i; | |
| entries[i].data = i + 10; | |
| entries[i].visited = 0; | |
| hash_add(hash, &entries[i].node, entries[i].key); | |
| } | |
| count = 0; | |
| hash_for_each(hash, bkt, x, node) { | |
| x->visited += 1; | |
| KUNIT_ASSERT_GE_MSG(test, x->key, 0, "Unexpected key in hashtable."); | |
| KUNIT_ASSERT_LT_MSG(test, x->key, 3, "Unexpected key in hashtable."); | |
| count++; | |
| } | |
| /* Should have visited each entry exactly once. */ | |
| KUNIT_EXPECT_EQ(test, count, 3); | |
| for (j = 0; j < 3; j++) | |
| KUNIT_EXPECT_EQ(test, entries[j].visited, 1); | |
| } | |
| static void hashtable_test_hash_for_each_safe(struct kunit *test) | |
| { | |
| struct hashtable_test_entry entries[3]; | |
| struct hashtable_test_entry *x; | |
| struct hlist_node *tmp; | |
| int bkt, i, j, count; | |
| DEFINE_HASHTABLE(hash, 3); | |
| /* Add three entries to the hashtable. */ | |
| for (i = 0; i < 3; i++) { | |
| entries[i].key = i; | |
| entries[i].data = i + 10; | |
| entries[i].visited = 0; | |
| hash_add(hash, &entries[i].node, entries[i].key); | |
| } | |
| count = 0; | |
| hash_for_each_safe(hash, bkt, tmp, x, node) { | |
| x->visited += 1; | |
| KUNIT_ASSERT_GE_MSG(test, x->key, 0, "Unexpected key in hashtable."); | |
| KUNIT_ASSERT_LT_MSG(test, x->key, 3, "Unexpected key in hashtable."); | |
| count++; | |
| /* Delete entry during loop. */ | |
| hash_del(&x->node); | |
| } | |
| /* Should have visited each entry exactly once. */ | |
| KUNIT_EXPECT_EQ(test, count, 3); | |
| for (j = 0; j < 3; j++) | |
| KUNIT_EXPECT_EQ(test, entries[j].visited, 1); | |
| } | |
| static void hashtable_test_hash_for_each_possible(struct kunit *test) | |
| { | |
| struct hashtable_test_entry entries[4]; | |
| struct hashtable_test_entry *x, *y; | |
| int buckets[2]; | |
| int bkt, i, j, count; | |
| DEFINE_HASHTABLE(hash, 5); | |
| /* Add three entries with key = 0 to the hashtable. */ | |
| for (i = 0; i < 3; i++) { | |
| entries[i].key = 0; | |
| entries[i].data = i; | |
| entries[i].visited = 0; | |
| hash_add(hash, &entries[i].node, entries[i].key); | |
| } | |
| /* Add an entry with key = 1. */ | |
| entries[3].key = 1; | |
| entries[3].data = 3; | |
| entries[3].visited = 0; | |
| hash_add(hash, &entries[3].node, entries[3].key); | |
| count = 0; | |
| hash_for_each_possible(hash, x, node, 0) { | |
| x->visited += 1; | |
| KUNIT_ASSERT_GE_MSG(test, x->data, 0, "Unexpected data in hashtable."); | |
| KUNIT_ASSERT_LT_MSG(test, x->data, 4, "Unexpected data in hashtable."); | |
| count++; | |
| } | |
| /* Should have visited each entry with key = 0 exactly once. */ | |
| for (j = 0; j < 3; j++) | |
| KUNIT_EXPECT_EQ(test, entries[j].visited, 1); | |
| /* Save the buckets for the different keys. */ | |
| hash_for_each(hash, bkt, y, node) { | |
| KUNIT_ASSERT_GE_MSG(test, y->key, 0, "Unexpected key in hashtable."); | |
| KUNIT_ASSERT_LE_MSG(test, y->key, 1, "Unexpected key in hashtable."); | |
| buckets[y->key] = bkt; | |
| } | |
| /* If entry with key = 1 is in the same bucket as the entries with | |
| * key = 0, check it was visited. Otherwise ensure that only three | |
| * entries were visited. | |
| */ | |
| if (buckets[0] == buckets[1]) { | |
| KUNIT_EXPECT_EQ(test, count, 4); | |
| KUNIT_EXPECT_EQ(test, entries[3].visited, 1); | |
| } else { | |
| KUNIT_EXPECT_EQ(test, count, 3); | |
| KUNIT_EXPECT_EQ(test, entries[3].visited, 0); | |
| } | |
| } | |
| static void hashtable_test_hash_for_each_possible_safe(struct kunit *test) | |
| { | |
| struct hashtable_test_entry entries[4]; | |
| struct hashtable_test_entry *x, *y; | |
| struct hlist_node *tmp; | |
| int buckets[2]; | |
| int bkt, i, j, count; | |
| DEFINE_HASHTABLE(hash, 5); | |
| /* Add three entries with key = 0 to the hashtable. */ | |
| for (i = 0; i < 3; i++) { | |
| entries[i].key = 0; | |
| entries[i].data = i; | |
| entries[i].visited = 0; | |
| hash_add(hash, &entries[i].node, entries[i].key); | |
| } | |
| /* Add an entry with key = 1. */ | |
| entries[3].key = 1; | |
| entries[3].data = 3; | |
| entries[3].visited = 0; | |
| hash_add(hash, &entries[3].node, entries[3].key); | |
| count = 0; | |
| hash_for_each_possible_safe(hash, x, tmp, node, 0) { | |
| x->visited += 1; | |
| KUNIT_ASSERT_GE_MSG(test, x->data, 0, "Unexpected data in hashtable."); | |
| KUNIT_ASSERT_LT_MSG(test, x->data, 4, "Unexpected data in hashtable."); | |
| count++; | |
| /* Delete entry during loop. */ | |
| hash_del(&x->node); | |
| } | |
| /* Should have visited each entry with key = 0 exactly once. */ | |
| for (j = 0; j < 3; j++) | |
| KUNIT_EXPECT_EQ(test, entries[j].visited, 1); | |
| /* Save the buckets for the different keys. */ | |
| hash_for_each(hash, bkt, y, node) { | |
| KUNIT_ASSERT_GE_MSG(test, y->key, 0, "Unexpected key in hashtable."); | |
| KUNIT_ASSERT_LE_MSG(test, y->key, 1, "Unexpected key in hashtable."); | |
| buckets[y->key] = bkt; | |
| } | |
| /* If entry with key = 1 is in the same bucket as the entries with | |
| * key = 0, check it was visited. Otherwise ensure that only three | |
| * entries were visited. | |
| */ | |
| if (buckets[0] == buckets[1]) { | |
| KUNIT_EXPECT_EQ(test, count, 4); | |
| KUNIT_EXPECT_EQ(test, entries[3].visited, 1); | |
| } else { | |
| KUNIT_EXPECT_EQ(test, count, 3); | |
| KUNIT_EXPECT_EQ(test, entries[3].visited, 0); | |
| } | |
| } | |
| static struct kunit_case hashtable_test_cases[] = { | |
| KUNIT_CASE(hashtable_test_hash_init), | |
| KUNIT_CASE(hashtable_test_hash_empty), | |
| KUNIT_CASE(hashtable_test_hash_hashed), | |
| KUNIT_CASE(hashtable_test_hash_add), | |
| KUNIT_CASE(hashtable_test_hash_del), | |
| KUNIT_CASE(hashtable_test_hash_for_each), | |
| KUNIT_CASE(hashtable_test_hash_for_each_safe), | |
| KUNIT_CASE(hashtable_test_hash_for_each_possible), | |
| KUNIT_CASE(hashtable_test_hash_for_each_possible_safe), | |
| {}, | |
| }; | |
| static struct kunit_suite hashtable_test_module = { | |
| .name = "hashtable", | |
| .test_cases = hashtable_test_cases, | |
| }; | |
| kunit_test_suites(&hashtable_test_module); | |
| MODULE_DESCRIPTION("KUnit test for the Kernel Hashtable structures"); | |
| MODULE_LICENSE("GPL"); |