TestHashTable.cpp 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. /*
  2. * Copyright (c) 2021, thislooksfun <tlf@thislooks.fun>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibTest/TestCase.h>
  7. #include <AK/HashTable.h>
  8. #include <AK/NonnullOwnPtr.h>
  9. #include <AK/String.h>
  10. TEST_CASE(construct)
  11. {
  12. using IntTable = HashTable<int>;
  13. EXPECT(IntTable().is_empty());
  14. EXPECT_EQ(IntTable().size(), 0u);
  15. }
  16. TEST_CASE(basic_move)
  17. {
  18. HashTable<int> foo;
  19. foo.set(1);
  20. EXPECT_EQ(foo.size(), 1u);
  21. auto bar = move(foo);
  22. EXPECT_EQ(bar.size(), 1u);
  23. EXPECT_EQ(foo.size(), 0u);
  24. foo = move(bar);
  25. EXPECT_EQ(bar.size(), 0u);
  26. EXPECT_EQ(foo.size(), 1u);
  27. }
  28. TEST_CASE(move_is_not_swap)
  29. {
  30. HashTable<int> foo;
  31. foo.set(1);
  32. HashTable<int> bar;
  33. bar.set(2);
  34. foo = move(bar);
  35. EXPECT(foo.contains(2));
  36. EXPECT(!bar.contains(1));
  37. EXPECT_EQ(bar.size(), 0u);
  38. }
  39. TEST_CASE(populate)
  40. {
  41. HashTable<String> strings;
  42. strings.set("One");
  43. strings.set("Two");
  44. strings.set("Three");
  45. EXPECT_EQ(strings.is_empty(), false);
  46. EXPECT_EQ(strings.size(), 3u);
  47. }
  48. TEST_CASE(range_loop)
  49. {
  50. HashTable<String> strings;
  51. EXPECT_EQ(strings.set("One"), AK::HashSetResult::InsertedNewEntry);
  52. EXPECT_EQ(strings.set("Two"), AK::HashSetResult::InsertedNewEntry);
  53. EXPECT_EQ(strings.set("Three"), AK::HashSetResult::InsertedNewEntry);
  54. int loop_counter = 0;
  55. for (auto& it : strings) {
  56. EXPECT_EQ(it.is_null(), false);
  57. ++loop_counter;
  58. }
  59. EXPECT_EQ(loop_counter, 3);
  60. }
  61. TEST_CASE(table_remove)
  62. {
  63. HashTable<String> strings;
  64. EXPECT_EQ(strings.set("One"), AK::HashSetResult::InsertedNewEntry);
  65. EXPECT_EQ(strings.set("Two"), AK::HashSetResult::InsertedNewEntry);
  66. EXPECT_EQ(strings.set("Three"), AK::HashSetResult::InsertedNewEntry);
  67. EXPECT_EQ(strings.remove("One"), true);
  68. EXPECT_EQ(strings.size(), 2u);
  69. EXPECT(strings.find("One") == strings.end());
  70. EXPECT_EQ(strings.remove("Three"), true);
  71. EXPECT_EQ(strings.size(), 1u);
  72. EXPECT(strings.find("Three") == strings.end());
  73. EXPECT(strings.find("Two") != strings.end());
  74. }
  75. TEST_CASE(remove_all_matching)
  76. {
  77. HashTable<int> ints;
  78. ints.set(1);
  79. ints.set(2);
  80. ints.set(3);
  81. ints.set(4);
  82. EXPECT_EQ(ints.size(), 4u);
  83. EXPECT_EQ(ints.remove_all_matching([&](int value) { return value > 2; }), true);
  84. EXPECT_EQ(ints.remove_all_matching([&](int) { return false; }), false);
  85. EXPECT_EQ(ints.size(), 2u);
  86. EXPECT(ints.contains(1));
  87. EXPECT(ints.contains(2));
  88. EXPECT_EQ(ints.remove_all_matching([&](int) { return true; }), true);
  89. EXPECT(ints.is_empty());
  90. EXPECT_EQ(ints.remove_all_matching([&](int) { return true; }), false);
  91. }
  92. TEST_CASE(case_insensitive)
  93. {
  94. HashTable<String, CaseInsensitiveStringTraits> casetable;
  95. EXPECT_EQ(String("nickserv").to_lowercase(), String("NickServ").to_lowercase());
  96. EXPECT_EQ(casetable.set("nickserv"), AK::HashSetResult::InsertedNewEntry);
  97. EXPECT_EQ(casetable.set("NickServ"), AK::HashSetResult::ReplacedExistingEntry);
  98. EXPECT_EQ(casetable.size(), 1u);
  99. }
  100. TEST_CASE(many_strings)
  101. {
  102. HashTable<String> strings;
  103. for (int i = 0; i < 999; ++i) {
  104. EXPECT_EQ(strings.set(String::number(i)), AK::HashSetResult::InsertedNewEntry);
  105. }
  106. EXPECT_EQ(strings.size(), 999u);
  107. for (int i = 0; i < 999; ++i) {
  108. EXPECT_EQ(strings.remove(String::number(i)), true);
  109. }
  110. EXPECT_EQ(strings.is_empty(), true);
  111. }
  112. TEST_CASE(many_collisions)
  113. {
  114. struct StringCollisionTraits : public GenericTraits<String> {
  115. static unsigned hash(String const&) { return 0; }
  116. };
  117. HashTable<String, StringCollisionTraits> strings;
  118. for (int i = 0; i < 999; ++i) {
  119. EXPECT_EQ(strings.set(String::number(i)), AK::HashSetResult::InsertedNewEntry);
  120. }
  121. EXPECT_EQ(strings.set("foo"), AK::HashSetResult::InsertedNewEntry);
  122. EXPECT_EQ(strings.size(), 1000u);
  123. for (int i = 0; i < 999; ++i) {
  124. EXPECT_EQ(strings.remove(String::number(i)), true);
  125. }
  126. // FIXME: Doing this with an "EXPECT_NOT_EQ" would be cleaner.
  127. EXPECT_EQ(strings.find("foo") == strings.end(), false);
  128. }
  129. TEST_CASE(space_reuse)
  130. {
  131. struct StringCollisionTraits : public GenericTraits<String> {
  132. static unsigned hash(String const&) { return 0; }
  133. };
  134. HashTable<String, StringCollisionTraits> strings;
  135. // Add a few items to allow it to do initial resizing.
  136. EXPECT_EQ(strings.set("0"), AK::HashSetResult::InsertedNewEntry);
  137. for (int i = 1; i < 5; ++i) {
  138. EXPECT_EQ(strings.set(String::number(i)), AK::HashSetResult::InsertedNewEntry);
  139. EXPECT_EQ(strings.remove(String::number(i - 1)), true);
  140. }
  141. auto capacity = strings.capacity();
  142. for (int i = 5; i < 999; ++i) {
  143. EXPECT_EQ(strings.set(String::number(i)), AK::HashSetResult::InsertedNewEntry);
  144. EXPECT_EQ(strings.remove(String::number(i - 1)), true);
  145. }
  146. EXPECT_EQ(strings.capacity(), capacity);
  147. }
  148. TEST_CASE(basic_remove)
  149. {
  150. HashTable<int> table;
  151. table.set(1);
  152. table.set(2);
  153. table.set(3);
  154. EXPECT_EQ(table.remove(3), true);
  155. EXPECT_EQ(table.remove(3), false);
  156. EXPECT_EQ(table.size(), 2u);
  157. EXPECT_EQ(table.remove(1), true);
  158. EXPECT_EQ(table.remove(1), false);
  159. EXPECT_EQ(table.size(), 1u);
  160. EXPECT_EQ(table.remove(2), true);
  161. EXPECT_EQ(table.remove(2), false);
  162. EXPECT_EQ(table.size(), 0u);
  163. }
  164. TEST_CASE(basic_contains)
  165. {
  166. HashTable<int> table;
  167. table.set(1);
  168. table.set(2);
  169. table.set(3);
  170. EXPECT_EQ(table.contains(1), true);
  171. EXPECT_EQ(table.contains(2), true);
  172. EXPECT_EQ(table.contains(3), true);
  173. EXPECT_EQ(table.contains(4), false);
  174. EXPECT_EQ(table.remove(3), true);
  175. EXPECT_EQ(table.contains(3), false);
  176. EXPECT_EQ(table.contains(1), true);
  177. EXPECT_EQ(table.contains(2), true);
  178. EXPECT_EQ(table.remove(2), true);
  179. EXPECT_EQ(table.contains(2), false);
  180. EXPECT_EQ(table.contains(3), false);
  181. EXPECT_EQ(table.contains(1), true);
  182. EXPECT_EQ(table.remove(1), true);
  183. EXPECT_EQ(table.contains(1), false);
  184. }
  185. TEST_CASE(capacity_leak)
  186. {
  187. HashTable<int> table;
  188. for (size_t i = 0; i < 10000; ++i) {
  189. table.set(i);
  190. table.remove(i);
  191. }
  192. EXPECT(table.capacity() < 100u);
  193. }
  194. TEST_CASE(non_trivial_type_table)
  195. {
  196. HashTable<NonnullOwnPtr<int>> table;
  197. table.set(make<int>(3));
  198. table.set(make<int>(11));
  199. for (int i = 0; i < 1'000; ++i) {
  200. table.set(make<int>(-i));
  201. }
  202. for (int i = 0; i < 10'000; ++i) {
  203. table.set(make<int>(i));
  204. table.remove(make<int>(i));
  205. }
  206. EXPECT_EQ(table.remove_all_matching([&](auto&) { return true; }), true);
  207. EXPECT(table.is_empty());
  208. EXPECT_EQ(table.remove_all_matching([&](auto&) { return true; }), false);
  209. }
  210. TEST_CASE(floats)
  211. {
  212. HashTable<float> table;
  213. table.set(0);
  214. table.set(1.0f);
  215. table.set(2.0f);
  216. EXPECT_EQ(table.size(), 3u);
  217. EXPECT(table.contains(0));
  218. EXPECT(table.contains(1.0f));
  219. EXPECT(table.contains(2.0f));
  220. }
  221. TEST_CASE(doubles)
  222. {
  223. HashTable<double> table;
  224. table.set(0);
  225. table.set(1.0);
  226. table.set(2.0);
  227. EXPECT_EQ(table.size(), 3u);
  228. EXPECT(table.contains(0));
  229. EXPECT(table.contains(1.0));
  230. EXPECT(table.contains(2.0));
  231. }
  232. // Inserting and removing a bunch of elements will "thrash" the table, leading to a lot of "deleted" markers.
  233. BENCHMARK_CASE(benchmark_thrashing)
  234. {
  235. HashTable<int> table;
  236. // Ensure that there needs to be some copying when rehashing.
  237. table.set(3);
  238. table.set(7);
  239. table.set(11);
  240. table.set(13);
  241. for (int i = 0; i < 10'000; ++i) {
  242. table.set(-i);
  243. }
  244. for (int i = 0; i < 10'000'000; ++i) {
  245. table.set(i);
  246. table.remove(i);
  247. }
  248. }
  249. TEST_CASE(reinsertion)
  250. {
  251. OrderedHashTable<String> map;
  252. map.set("ytidb::LAST_RESULT_ENTRY_KEY");
  253. map.set("__sak");
  254. map.remove("__sak");
  255. map.set("__sak");
  256. }
  257. TEST_CASE(clear_with_capacity_when_empty)
  258. {
  259. HashTable<int> map;
  260. map.clear_with_capacity();
  261. map.set(0);
  262. map.set(1);
  263. VERIFY(map.size() == 2);
  264. }