// SPDX-License-Identifier: GPL-2.0 /* * KUnit test for the Kernel Hashtable structures. * * Copyright (C) 2022, Google LLC. * Author: Rae Moar */ #include #include 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_LICENSE("GPL");