UnorderedSetTest.cpp 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2024 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #include "UnitTestFramework.h"
  5. #include <Jolt/Core/UnorderedSet.h>
  6. TEST_SUITE("UnorderedSetTest")
  7. {
  8. TEST_CASE("TestUnorderedSet")
  9. {
  10. UnorderedSet<int> set;
  11. CHECK(set.bucket_count() == 0);
  12. set.reserve(10);
  13. CHECK(set.bucket_count() == 16);
  14. // Check system limits
  15. CHECK(set.max_bucket_count() == 0x80000000);
  16. CHECK(set.max_size() == uint64(0x80000000) * 7 / 8);
  17. // Insert some entries
  18. CHECK(*set.insert(1).first == 1);
  19. CHECK(set.insert(3).second);
  20. CHECK(!set.insert(3).second);
  21. CHECK(set.size() == 2);
  22. CHECK(*set.find(1) == 1);
  23. CHECK(*set.find(3) == 3);
  24. CHECK(set.find(5) == set.cend());
  25. // Validate all elements are visited by a visitor
  26. int count = 0;
  27. bool visited[10] = { false };
  28. for (UnorderedSet<int>::const_iterator i = set.begin(); i != set.end(); ++i)
  29. {
  30. visited[*i] = true;
  31. ++count;
  32. }
  33. CHECK(count == 2);
  34. CHECK(visited[1]);
  35. CHECK(visited[3]);
  36. for (UnorderedSet<int>::iterator i = set.begin(); i != set.end(); ++i)
  37. {
  38. visited[*i] = false;
  39. --count;
  40. }
  41. CHECK(count == 0);
  42. CHECK(!visited[1]);
  43. CHECK(!visited[3]);
  44. // Copy the set
  45. UnorderedSet<int> set2;
  46. set2 = set;
  47. CHECK(*set2.find(1) == 1);
  48. CHECK(*set2.find(3) == 3);
  49. CHECK(set2.find(5) == set2.cend());
  50. // Swap
  51. UnorderedSet<int> set3;
  52. set3.swap(set);
  53. CHECK(*set3.find(1) == 1);
  54. CHECK(*set3.find(3) == 3);
  55. CHECK(set3.find(5) == set3.end());
  56. CHECK(set.empty());
  57. // Move construct
  58. UnorderedSet<int> set4(std::move(set3));
  59. CHECK(*set4.find(1) == 1);
  60. CHECK(*set4.find(3) == 3);
  61. CHECK(set4.find(5) == set4.end());
  62. CHECK(set3.empty());
  63. // Move assign
  64. UnorderedSet<int> set5;
  65. set5.insert(999);
  66. CHECK(*set5.find(999) == 999);
  67. set5 = std::move(set4);
  68. CHECK(set5.find(999) == set5.end());
  69. CHECK(*set5.find(1) == 1);
  70. CHECK(*set5.find(3) == 3);
  71. CHECK(set4.empty());
  72. }
  73. TEST_CASE("TestUnorderedSetGrow")
  74. {
  75. UnorderedSet<int> set;
  76. for (int i = 0; i < 10000; ++i)
  77. CHECK(set.insert(i).second);
  78. CHECK(set.size() == 10000);
  79. for (int i = 0; i < 10000; ++i)
  80. CHECK(*set.find(i) == i);
  81. CHECK(set.find(10001) == set.cend());
  82. for (int i = 0; i < 5000; ++i)
  83. CHECK(set.erase(i) == 1);
  84. CHECK(set.size() == 5000);
  85. for (int i = 0; i < 5000; ++i)
  86. CHECK(set.find(i) == set.end());
  87. for (int i = 5000; i < 10000; ++i)
  88. CHECK(*set.find(i) == i);
  89. CHECK(set.find(10001) == set.cend());
  90. for (int i = 0; i < 5000; ++i)
  91. CHECK(set.insert(i).second);
  92. CHECK(!set.insert(0).second);
  93. CHECK(set.size() == 10000);
  94. for (int i = 0; i < 10000; ++i)
  95. CHECK(*set.find(i) == i);
  96. CHECK(set.find(10001) == set.cend());
  97. }
  98. TEST_CASE("TestUnorderedSetHashCollision")
  99. {
  100. // A hash function that's guaranteed to collide
  101. class MyBadHash
  102. {
  103. public:
  104. size_t operator () (int inValue) const
  105. {
  106. return 0;
  107. }
  108. };
  109. UnorderedSet<int, MyBadHash> set;
  110. for (int i = 0; i < 10; ++i)
  111. CHECK(set.insert(i).second);
  112. CHECK(set.size() == 10);
  113. for (int i = 0; i < 10; ++i)
  114. CHECK(*set.find(i) == i);
  115. CHECK(set.find(11) == set.cend());
  116. for (int i = 0; i < 5; ++i)
  117. CHECK(set.erase(i) == 1);
  118. CHECK(set.size() == 5);
  119. for (int i = 0; i < 5; ++i)
  120. CHECK(set.find(i) == set.end());
  121. for (int i = 5; i < 10; ++i)
  122. CHECK(*set.find(i) == i);
  123. CHECK(set.find(11) == set.cend());
  124. for (int i = 0; i < 5; ++i)
  125. CHECK(set.insert(i).second);
  126. CHECK(!set.insert(0).second);
  127. CHECK(set.size() == 10);
  128. for (int i = 0; i < 10; ++i)
  129. CHECK(*set.find(i) == i);
  130. CHECK(set.find(11) == set.cend());
  131. }
  132. TEST_CASE("TestUnorderedSetAddRemoveCyles")
  133. {
  134. UnorderedSet<int> set;
  135. constexpr int cBucketCount = 64;
  136. set.reserve(int(set.max_load_factor() * cBucketCount));
  137. CHECK(set.bucket_count() == cBucketCount);
  138. // Repeatedly add and remove elements to see if the set cleans up tombstones
  139. constexpr int cNumElements = 64 * 6 / 8; // We make sure that the map is max 6/8 full to ensure that we never grow the map but rehash it instead
  140. int add_counter = 0;
  141. int remove_counter = 0;
  142. for (int i = 0; i < 100; ++i)
  143. {
  144. for (int j = 0; j < cNumElements; ++j)
  145. {
  146. CHECK(set.find(add_counter) == set.end());
  147. CHECK(set.insert(add_counter).second);
  148. CHECK(set.find(add_counter) != set.end());
  149. ++add_counter;
  150. }
  151. CHECK(set.size() == cNumElements);
  152. for (int j = 0; j < cNumElements; ++j)
  153. {
  154. CHECK(set.find(remove_counter) != set.end());
  155. CHECK(set.erase(remove_counter) == 1);
  156. CHECK(set.erase(remove_counter) == 0);
  157. CHECK(set.find(remove_counter) == set.end());
  158. ++remove_counter;
  159. }
  160. CHECK(set.size() == 0);
  161. CHECK(set.empty());
  162. }
  163. // Test that adding and removing didn't resize the set
  164. CHECK(set.bucket_count() == cBucketCount);
  165. }
  166. TEST_CASE("TestUnorderedSetManyTombStones")
  167. {
  168. // A hash function that makes sure that consecutive ints end up in consecutive buckets starting at bucket 63
  169. class MyBadHash
  170. {
  171. public:
  172. size_t operator () (int inValue) const
  173. {
  174. return (inValue + 63) << 7;
  175. }
  176. };
  177. UnorderedSet<int, MyBadHash> set;
  178. constexpr int cBucketCount = 64;
  179. set.reserve(int(set.max_load_factor() * cBucketCount));
  180. CHECK(set.bucket_count() == cBucketCount);
  181. // Fill 32 buckets
  182. int add_counter = 0;
  183. for (int i = 0; i < 32; ++i)
  184. CHECK(set.insert(add_counter++).second);
  185. // Since we control the hash, we know in which order we'll visit the elements
  186. // The first element was inserted in bucket 63, so we start at 1
  187. int expected = 1;
  188. for (int i : set)
  189. {
  190. CHECK(i == expected);
  191. expected = (expected + 1) & 31;
  192. }
  193. expected = 1;
  194. for (int i : set)
  195. {
  196. CHECK(i == expected);
  197. expected = (expected + 1) & 31;
  198. }
  199. // Remove a bucket in the middle with so that the number of occupied slots
  200. // surrounding the bucket exceed 16 to force creating a tombstone,
  201. // then add one at the end
  202. int remove_counter = 16;
  203. for (int i = 0; i < 100; ++i)
  204. {
  205. CHECK(set.find(remove_counter) != set.end());
  206. CHECK(set.erase(remove_counter) == 1);
  207. CHECK(set.find(remove_counter) == set.end());
  208. CHECK(set.find(add_counter) == set.end());
  209. CHECK(set.insert(add_counter).second);
  210. CHECK(set.find(add_counter) != set.end());
  211. ++add_counter;
  212. ++remove_counter;
  213. }
  214. // Check that the elements we inserted are still there
  215. CHECK(set.size() == 32);
  216. for (int i = 0; i < 16; ++i)
  217. CHECK(*set.find(i) == i);
  218. for (int i = 0; i < 16; ++i)
  219. CHECK(*set.find(add_counter - 1 - i) == add_counter - 1 - i);
  220. // Test that adding and removing didn't resize the set
  221. CHECK(set.bucket_count() == cBucketCount);
  222. }
  223. static bool sReversedHash = false;
  224. TEST_CASE("TestUnorderedSetRehash")
  225. {
  226. // A hash function for which we can switch the hashing algorithm
  227. class MyBadHash
  228. {
  229. public:
  230. size_t operator () (int inValue) const
  231. {
  232. return (sReversedHash? 127 - inValue : inValue) << 7;
  233. }
  234. };
  235. using Set = UnorderedSet<int, MyBadHash>;
  236. Set set;
  237. constexpr int cBucketCount = 128;
  238. set.reserve(int(set.max_load_factor() * cBucketCount));
  239. CHECK(set.bucket_count() == cBucketCount);
  240. // Fill buckets
  241. sReversedHash = false;
  242. constexpr int cNumElements = 96;
  243. for (int i = 0; i < cNumElements; ++i)
  244. CHECK(set.insert(i).second);
  245. // Check that we get the elements in the expected order
  246. int expected = 0;
  247. for (int i : set)
  248. CHECK(i == expected++);
  249. // Change the hashing algorithm so that a rehash is forced to move elements.
  250. // The test is designed in such a way that it will both need to move elements to empty slots
  251. // and to move elements to slots that currently already have another element.
  252. sReversedHash = true;
  253. set.rehash(0);
  254. // Check that all elements are still there
  255. for (int i = 0; i < cNumElements; ++i)
  256. CHECK(*set.find(i) == i);
  257. // The hash went from filling buckets 0 .. 95 with values 0 .. 95 to bucket 127 .. 31 with values 0 .. 95
  258. // However, we don't move elements if they still fall within the same batch, this means that the first 8
  259. // elements didn't move
  260. Set::const_iterator it = set.begin();
  261. for (int i = 0; i < 8; ++i, ++it)
  262. CHECK(*it == i);
  263. // The rest will have been reversed
  264. for (int i = 95; i > 7; --i, ++it)
  265. CHECK(*it == i);
  266. // Test that adding and removing didn't resize the set
  267. CHECK(set.bucket_count() == cBucketCount);
  268. }
  269. }