162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * KUnit test for the Kernel Hashtable structures. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2022, Google LLC. 662306a36Sopenharmony_ci * Author: Rae Moar <rmoar@google.com> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci#include <kunit/test.h> 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/hashtable.h> 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_cistruct hashtable_test_entry { 1362306a36Sopenharmony_ci int key; 1462306a36Sopenharmony_ci int data; 1562306a36Sopenharmony_ci struct hlist_node node; 1662306a36Sopenharmony_ci int visited; 1762306a36Sopenharmony_ci}; 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_cistatic void hashtable_test_hash_init(struct kunit *test) 2062306a36Sopenharmony_ci{ 2162306a36Sopenharmony_ci /* Test the different ways of initialising a hashtable. */ 2262306a36Sopenharmony_ci DEFINE_HASHTABLE(hash1, 2); 2362306a36Sopenharmony_ci DECLARE_HASHTABLE(hash2, 3); 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci /* When using DECLARE_HASHTABLE, must use hash_init to 2662306a36Sopenharmony_ci * initialize the hashtable. 2762306a36Sopenharmony_ci */ 2862306a36Sopenharmony_ci hash_init(hash2); 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci KUNIT_EXPECT_TRUE(test, hash_empty(hash1)); 3162306a36Sopenharmony_ci KUNIT_EXPECT_TRUE(test, hash_empty(hash2)); 3262306a36Sopenharmony_ci} 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cistatic void hashtable_test_hash_empty(struct kunit *test) 3562306a36Sopenharmony_ci{ 3662306a36Sopenharmony_ci struct hashtable_test_entry a; 3762306a36Sopenharmony_ci DEFINE_HASHTABLE(hash, 1); 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci KUNIT_EXPECT_TRUE(test, hash_empty(hash)); 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci a.key = 1; 4262306a36Sopenharmony_ci a.data = 13; 4362306a36Sopenharmony_ci hash_add(hash, &a.node, a.key); 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci /* Hashtable should no longer be empty. */ 4662306a36Sopenharmony_ci KUNIT_EXPECT_FALSE(test, hash_empty(hash)); 4762306a36Sopenharmony_ci} 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_cistatic void hashtable_test_hash_hashed(struct kunit *test) 5062306a36Sopenharmony_ci{ 5162306a36Sopenharmony_ci struct hashtable_test_entry a, b; 5262306a36Sopenharmony_ci DEFINE_HASHTABLE(hash, 4); 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci a.key = 1; 5562306a36Sopenharmony_ci a.data = 13; 5662306a36Sopenharmony_ci hash_add(hash, &a.node, a.key); 5762306a36Sopenharmony_ci b.key = 1; 5862306a36Sopenharmony_ci b.data = 2; 5962306a36Sopenharmony_ci hash_add(hash, &b.node, b.key); 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci KUNIT_EXPECT_TRUE(test, hash_hashed(&a.node)); 6262306a36Sopenharmony_ci KUNIT_EXPECT_TRUE(test, hash_hashed(&b.node)); 6362306a36Sopenharmony_ci} 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistatic void hashtable_test_hash_add(struct kunit *test) 6662306a36Sopenharmony_ci{ 6762306a36Sopenharmony_ci struct hashtable_test_entry a, b, *x; 6862306a36Sopenharmony_ci int bkt; 6962306a36Sopenharmony_ci DEFINE_HASHTABLE(hash, 3); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci a.key = 1; 7262306a36Sopenharmony_ci a.data = 13; 7362306a36Sopenharmony_ci a.visited = 0; 7462306a36Sopenharmony_ci hash_add(hash, &a.node, a.key); 7562306a36Sopenharmony_ci b.key = 2; 7662306a36Sopenharmony_ci b.data = 10; 7762306a36Sopenharmony_ci b.visited = 0; 7862306a36Sopenharmony_ci hash_add(hash, &b.node, b.key); 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci hash_for_each(hash, bkt, x, node) { 8162306a36Sopenharmony_ci x->visited++; 8262306a36Sopenharmony_ci if (x->key == a.key) 8362306a36Sopenharmony_ci KUNIT_EXPECT_EQ(test, x->data, 13); 8462306a36Sopenharmony_ci else if (x->key == b.key) 8562306a36Sopenharmony_ci KUNIT_EXPECT_EQ(test, x->data, 10); 8662306a36Sopenharmony_ci else 8762306a36Sopenharmony_ci KUNIT_FAIL(test, "Unexpected key in hashtable."); 8862306a36Sopenharmony_ci } 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci /* Both entries should have been visited exactly once. */ 9162306a36Sopenharmony_ci KUNIT_EXPECT_EQ(test, a.visited, 1); 9262306a36Sopenharmony_ci KUNIT_EXPECT_EQ(test, b.visited, 1); 9362306a36Sopenharmony_ci} 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_cistatic void hashtable_test_hash_del(struct kunit *test) 9662306a36Sopenharmony_ci{ 9762306a36Sopenharmony_ci struct hashtable_test_entry a, b, *x; 9862306a36Sopenharmony_ci DEFINE_HASHTABLE(hash, 6); 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci a.key = 1; 10162306a36Sopenharmony_ci a.data = 13; 10262306a36Sopenharmony_ci hash_add(hash, &a.node, a.key); 10362306a36Sopenharmony_ci b.key = 2; 10462306a36Sopenharmony_ci b.data = 10; 10562306a36Sopenharmony_ci b.visited = 0; 10662306a36Sopenharmony_ci hash_add(hash, &b.node, b.key); 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci hash_del(&b.node); 10962306a36Sopenharmony_ci hash_for_each_possible(hash, x, node, b.key) { 11062306a36Sopenharmony_ci x->visited++; 11162306a36Sopenharmony_ci KUNIT_EXPECT_NE(test, x->key, b.key); 11262306a36Sopenharmony_ci } 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci /* The deleted entry should not have been visited. */ 11562306a36Sopenharmony_ci KUNIT_EXPECT_EQ(test, b.visited, 0); 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci hash_del(&a.node); 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci /* The hashtable should be empty. */ 12062306a36Sopenharmony_ci KUNIT_EXPECT_TRUE(test, hash_empty(hash)); 12162306a36Sopenharmony_ci} 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_cistatic void hashtable_test_hash_for_each(struct kunit *test) 12462306a36Sopenharmony_ci{ 12562306a36Sopenharmony_ci struct hashtable_test_entry entries[3]; 12662306a36Sopenharmony_ci struct hashtable_test_entry *x; 12762306a36Sopenharmony_ci int bkt, i, j, count; 12862306a36Sopenharmony_ci DEFINE_HASHTABLE(hash, 3); 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci /* Add three entries to the hashtable. */ 13162306a36Sopenharmony_ci for (i = 0; i < 3; i++) { 13262306a36Sopenharmony_ci entries[i].key = i; 13362306a36Sopenharmony_ci entries[i].data = i + 10; 13462306a36Sopenharmony_ci entries[i].visited = 0; 13562306a36Sopenharmony_ci hash_add(hash, &entries[i].node, entries[i].key); 13662306a36Sopenharmony_ci } 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci count = 0; 13962306a36Sopenharmony_ci hash_for_each(hash, bkt, x, node) { 14062306a36Sopenharmony_ci x->visited += 1; 14162306a36Sopenharmony_ci KUNIT_ASSERT_GE_MSG(test, x->key, 0, "Unexpected key in hashtable."); 14262306a36Sopenharmony_ci KUNIT_ASSERT_LT_MSG(test, x->key, 3, "Unexpected key in hashtable."); 14362306a36Sopenharmony_ci count++; 14462306a36Sopenharmony_ci } 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci /* Should have visited each entry exactly once. */ 14762306a36Sopenharmony_ci KUNIT_EXPECT_EQ(test, count, 3); 14862306a36Sopenharmony_ci for (j = 0; j < 3; j++) 14962306a36Sopenharmony_ci KUNIT_EXPECT_EQ(test, entries[j].visited, 1); 15062306a36Sopenharmony_ci} 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_cistatic void hashtable_test_hash_for_each_safe(struct kunit *test) 15362306a36Sopenharmony_ci{ 15462306a36Sopenharmony_ci struct hashtable_test_entry entries[3]; 15562306a36Sopenharmony_ci struct hashtable_test_entry *x; 15662306a36Sopenharmony_ci struct hlist_node *tmp; 15762306a36Sopenharmony_ci int bkt, i, j, count; 15862306a36Sopenharmony_ci DEFINE_HASHTABLE(hash, 3); 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci /* Add three entries to the hashtable. */ 16162306a36Sopenharmony_ci for (i = 0; i < 3; i++) { 16262306a36Sopenharmony_ci entries[i].key = i; 16362306a36Sopenharmony_ci entries[i].data = i + 10; 16462306a36Sopenharmony_ci entries[i].visited = 0; 16562306a36Sopenharmony_ci hash_add(hash, &entries[i].node, entries[i].key); 16662306a36Sopenharmony_ci } 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci count = 0; 16962306a36Sopenharmony_ci hash_for_each_safe(hash, bkt, tmp, x, node) { 17062306a36Sopenharmony_ci x->visited += 1; 17162306a36Sopenharmony_ci KUNIT_ASSERT_GE_MSG(test, x->key, 0, "Unexpected key in hashtable."); 17262306a36Sopenharmony_ci KUNIT_ASSERT_LT_MSG(test, x->key, 3, "Unexpected key in hashtable."); 17362306a36Sopenharmony_ci count++; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci /* Delete entry during loop. */ 17662306a36Sopenharmony_ci hash_del(&x->node); 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci /* Should have visited each entry exactly once. */ 18062306a36Sopenharmony_ci KUNIT_EXPECT_EQ(test, count, 3); 18162306a36Sopenharmony_ci for (j = 0; j < 3; j++) 18262306a36Sopenharmony_ci KUNIT_EXPECT_EQ(test, entries[j].visited, 1); 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_cistatic void hashtable_test_hash_for_each_possible(struct kunit *test) 18662306a36Sopenharmony_ci{ 18762306a36Sopenharmony_ci struct hashtable_test_entry entries[4]; 18862306a36Sopenharmony_ci struct hashtable_test_entry *x, *y; 18962306a36Sopenharmony_ci int buckets[2]; 19062306a36Sopenharmony_ci int bkt, i, j, count; 19162306a36Sopenharmony_ci DEFINE_HASHTABLE(hash, 5); 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci /* Add three entries with key = 0 to the hashtable. */ 19462306a36Sopenharmony_ci for (i = 0; i < 3; i++) { 19562306a36Sopenharmony_ci entries[i].key = 0; 19662306a36Sopenharmony_ci entries[i].data = i; 19762306a36Sopenharmony_ci entries[i].visited = 0; 19862306a36Sopenharmony_ci hash_add(hash, &entries[i].node, entries[i].key); 19962306a36Sopenharmony_ci } 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci /* Add an entry with key = 1. */ 20262306a36Sopenharmony_ci entries[3].key = 1; 20362306a36Sopenharmony_ci entries[3].data = 3; 20462306a36Sopenharmony_ci entries[3].visited = 0; 20562306a36Sopenharmony_ci hash_add(hash, &entries[3].node, entries[3].key); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci count = 0; 20862306a36Sopenharmony_ci hash_for_each_possible(hash, x, node, 0) { 20962306a36Sopenharmony_ci x->visited += 1; 21062306a36Sopenharmony_ci KUNIT_ASSERT_GE_MSG(test, x->data, 0, "Unexpected data in hashtable."); 21162306a36Sopenharmony_ci KUNIT_ASSERT_LT_MSG(test, x->data, 4, "Unexpected data in hashtable."); 21262306a36Sopenharmony_ci count++; 21362306a36Sopenharmony_ci } 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci /* Should have visited each entry with key = 0 exactly once. */ 21662306a36Sopenharmony_ci for (j = 0; j < 3; j++) 21762306a36Sopenharmony_ci KUNIT_EXPECT_EQ(test, entries[j].visited, 1); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci /* Save the buckets for the different keys. */ 22062306a36Sopenharmony_ci hash_for_each(hash, bkt, y, node) { 22162306a36Sopenharmony_ci KUNIT_ASSERT_GE_MSG(test, y->key, 0, "Unexpected key in hashtable."); 22262306a36Sopenharmony_ci KUNIT_ASSERT_LE_MSG(test, y->key, 1, "Unexpected key in hashtable."); 22362306a36Sopenharmony_ci buckets[y->key] = bkt; 22462306a36Sopenharmony_ci } 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci /* If entry with key = 1 is in the same bucket as the entries with 22762306a36Sopenharmony_ci * key = 0, check it was visited. Otherwise ensure that only three 22862306a36Sopenharmony_ci * entries were visited. 22962306a36Sopenharmony_ci */ 23062306a36Sopenharmony_ci if (buckets[0] == buckets[1]) { 23162306a36Sopenharmony_ci KUNIT_EXPECT_EQ(test, count, 4); 23262306a36Sopenharmony_ci KUNIT_EXPECT_EQ(test, entries[3].visited, 1); 23362306a36Sopenharmony_ci } else { 23462306a36Sopenharmony_ci KUNIT_EXPECT_EQ(test, count, 3); 23562306a36Sopenharmony_ci KUNIT_EXPECT_EQ(test, entries[3].visited, 0); 23662306a36Sopenharmony_ci } 23762306a36Sopenharmony_ci} 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_cistatic void hashtable_test_hash_for_each_possible_safe(struct kunit *test) 24062306a36Sopenharmony_ci{ 24162306a36Sopenharmony_ci struct hashtable_test_entry entries[4]; 24262306a36Sopenharmony_ci struct hashtable_test_entry *x, *y; 24362306a36Sopenharmony_ci struct hlist_node *tmp; 24462306a36Sopenharmony_ci int buckets[2]; 24562306a36Sopenharmony_ci int bkt, i, j, count; 24662306a36Sopenharmony_ci DEFINE_HASHTABLE(hash, 5); 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci /* Add three entries with key = 0 to the hashtable. */ 24962306a36Sopenharmony_ci for (i = 0; i < 3; i++) { 25062306a36Sopenharmony_ci entries[i].key = 0; 25162306a36Sopenharmony_ci entries[i].data = i; 25262306a36Sopenharmony_ci entries[i].visited = 0; 25362306a36Sopenharmony_ci hash_add(hash, &entries[i].node, entries[i].key); 25462306a36Sopenharmony_ci } 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci /* Add an entry with key = 1. */ 25762306a36Sopenharmony_ci entries[3].key = 1; 25862306a36Sopenharmony_ci entries[3].data = 3; 25962306a36Sopenharmony_ci entries[3].visited = 0; 26062306a36Sopenharmony_ci hash_add(hash, &entries[3].node, entries[3].key); 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci count = 0; 26362306a36Sopenharmony_ci hash_for_each_possible_safe(hash, x, tmp, node, 0) { 26462306a36Sopenharmony_ci x->visited += 1; 26562306a36Sopenharmony_ci KUNIT_ASSERT_GE_MSG(test, x->data, 0, "Unexpected data in hashtable."); 26662306a36Sopenharmony_ci KUNIT_ASSERT_LT_MSG(test, x->data, 4, "Unexpected data in hashtable."); 26762306a36Sopenharmony_ci count++; 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci /* Delete entry during loop. */ 27062306a36Sopenharmony_ci hash_del(&x->node); 27162306a36Sopenharmony_ci } 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci /* Should have visited each entry with key = 0 exactly once. */ 27462306a36Sopenharmony_ci for (j = 0; j < 3; j++) 27562306a36Sopenharmony_ci KUNIT_EXPECT_EQ(test, entries[j].visited, 1); 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci /* Save the buckets for the different keys. */ 27862306a36Sopenharmony_ci hash_for_each(hash, bkt, y, node) { 27962306a36Sopenharmony_ci KUNIT_ASSERT_GE_MSG(test, y->key, 0, "Unexpected key in hashtable."); 28062306a36Sopenharmony_ci KUNIT_ASSERT_LE_MSG(test, y->key, 1, "Unexpected key in hashtable."); 28162306a36Sopenharmony_ci buckets[y->key] = bkt; 28262306a36Sopenharmony_ci } 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci /* If entry with key = 1 is in the same bucket as the entries with 28562306a36Sopenharmony_ci * key = 0, check it was visited. Otherwise ensure that only three 28662306a36Sopenharmony_ci * entries were visited. 28762306a36Sopenharmony_ci */ 28862306a36Sopenharmony_ci if (buckets[0] == buckets[1]) { 28962306a36Sopenharmony_ci KUNIT_EXPECT_EQ(test, count, 4); 29062306a36Sopenharmony_ci KUNIT_EXPECT_EQ(test, entries[3].visited, 1); 29162306a36Sopenharmony_ci } else { 29262306a36Sopenharmony_ci KUNIT_EXPECT_EQ(test, count, 3); 29362306a36Sopenharmony_ci KUNIT_EXPECT_EQ(test, entries[3].visited, 0); 29462306a36Sopenharmony_ci } 29562306a36Sopenharmony_ci} 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_cistatic struct kunit_case hashtable_test_cases[] = { 29862306a36Sopenharmony_ci KUNIT_CASE(hashtable_test_hash_init), 29962306a36Sopenharmony_ci KUNIT_CASE(hashtable_test_hash_empty), 30062306a36Sopenharmony_ci KUNIT_CASE(hashtable_test_hash_hashed), 30162306a36Sopenharmony_ci KUNIT_CASE(hashtable_test_hash_add), 30262306a36Sopenharmony_ci KUNIT_CASE(hashtable_test_hash_del), 30362306a36Sopenharmony_ci KUNIT_CASE(hashtable_test_hash_for_each), 30462306a36Sopenharmony_ci KUNIT_CASE(hashtable_test_hash_for_each_safe), 30562306a36Sopenharmony_ci KUNIT_CASE(hashtable_test_hash_for_each_possible), 30662306a36Sopenharmony_ci KUNIT_CASE(hashtable_test_hash_for_each_possible_safe), 30762306a36Sopenharmony_ci {}, 30862306a36Sopenharmony_ci}; 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_cistatic struct kunit_suite hashtable_test_module = { 31162306a36Sopenharmony_ci .name = "hashtable", 31262306a36Sopenharmony_ci .test_cases = hashtable_test_cases, 31362306a36Sopenharmony_ci}; 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_cikunit_test_suites(&hashtable_test_module); 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 318