|  | // 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"); |