OILS / mycpp / gc_dict_test.cc View on Github | oilshell.org

745 lines, 501 significant
1#include "mycpp/gc_dict.h"
2
3#include <unordered_map>
4
5#include "mycpp/gc_mylib.h"
6#include "vendor/greatest.h"
7
8// Convenience function
9template <typename K, typename V>
10Dict<K, V>* NewDict() {
11 return Alloc<Dict<K, V>>();
12}
13
14GLOBAL_STR(kStrFoo, "foo");
15GLOBAL_STR(kStrBar, "bar");
16
17TEST test_dict_init() {
18 BigStr* s = StrFromC("foo");
19 BigStr* s2 = StrFromC("bar");
20
21 Dict<int, BigStr*>* d = Alloc<Dict<int, BigStr*>>(
22 std::initializer_list<int>{42}, std::initializer_list<BigStr*>{s});
23 ASSERT_EQ(s, d->at(42));
24
25 Dict<BigStr*, int>* d2 =
26 Alloc<Dict<BigStr*, int>>(std::initializer_list<BigStr*>{s, s2},
27 std::initializer_list<int>{43, 99});
28 ASSERT_EQ(43, d2->at(s));
29 ASSERT_EQ(99, d2->at(s2));
30
31 PASS();
32}
33
34TEST test_dict() {
35 Dict<int, BigStr*>* d = NewDict<int, BigStr*>();
36
37 // Regression: clear empty dict
38 d->clear();
39
40 d->set(1, StrFromC("foo"));
41 log("d[1] = %s", d->at(1)->data_);
42
43 auto d2 = NewDict<BigStr*, int>();
44 BigStr* key = StrFromC("key");
45 d2->set(key, 42);
46
47 log("d2['key'] = %d", d2->at(key));
48 d2->set(StrFromC("key2"), 2);
49 d2->set(StrFromC("key3"), 3);
50
51 ASSERT_EQ_FMT(3, len(d2), "%d");
52 ASSERT_EQ_FMT(3, len(d2->keys()), "%d");
53 ASSERT_EQ_FMT(3, len(d2->values()), "%d");
54
55 d2->clear();
56 ASSERT_EQ(0, len(d2));
57
58 log(" iterating over Dict");
59 for (DictIter<BigStr*, int> it(d2); !it.Done(); it.Next()) {
60 log("k = %s, v = %d", it.Key()->data_, it.Value());
61 }
62
63 ASSERT(dict_contains(d, 1));
64 ASSERT(!dict_contains(d, 423));
65
66 BigStr* v1 = d->get(1);
67 log("v1 = %s", v1->data_);
68 ASSERT(str_equals0("foo", v1));
69
70 BigStr* v2 = d->get(423); // nonexistent
71 ASSERT_EQ(nullptr, v2);
72 log("v2 = %p", v2);
73
74 auto d3 = NewDict<BigStr*, int>();
75 ASSERT_EQ(0, len(d3));
76
77 auto a = StrFromC("a");
78
79 d3->set(StrFromC("b"), 11);
80 ASSERT_EQ(1, len(d3));
81
82 d3->set(StrFromC("c"), 12);
83 ASSERT_EQ(2, len(d3));
84
85 d3->set(StrFromC("a"), 10);
86 ASSERT_EQ(3, len(d3));
87
88 ASSERT_EQ(10, d3->at(StrFromC("a")));
89 ASSERT_EQ(11, d3->at(StrFromC("b")));
90 ASSERT_EQ(12, d3->at(StrFromC("c")));
91 ASSERT_EQ(3, len(d3));
92
93 auto keys = sorted(d3);
94 ASSERT(str_equals0("a", keys->at(0)));
95 ASSERT(str_equals0("b", keys->at(1)));
96 ASSERT(str_equals0("c", keys->at(2)));
97 ASSERT_EQ(3, len(keys));
98
99 auto keys3 = d3->keys();
100 ASSERT(list_contains(keys3, a));
101 ASSERT(!list_contains(keys3, StrFromC("zzz")));
102
103 ASSERT(dict_contains(d3, a));
104 mylib::dict_erase(d3, a);
105 ASSERT(!dict_contains(d3, a));
106 ASSERT_EQ(2, len(d3));
107
108 // Test removed item
109 for (DictIter<BigStr*, int> it(d3); !it.Done(); it.Next()) {
110 auto key = it.Key();
111 printf("d3 key = ");
112 print(key);
113 }
114
115 // Test a different type of dict, to make sure partial template
116 // specialization works
117 auto ss = NewDict<BigStr*, BigStr*>();
118 ss->set(a, a);
119 ASSERT_EQ(1, len(ss));
120
121 ASSERT_EQ(1, len(ss->keys()));
122 ASSERT_EQ(1, len(ss->values()));
123
124 mylib::dict_erase(ss, a);
125 ASSERT_EQ(0, len(ss));
126
127 // Test removed item
128 for (DictIter<BigStr*, BigStr*> it(ss); !it.Done(); it.Next()) {
129 auto key = it.Key();
130 printf("ss key = ");
131 print(key);
132 }
133
134 // Testing NewDict() stub for ordered dicts ... hm.
135 //
136 // Dict<int, int>* frame = nullptr;
137 // frame = NewDict<int, int>();
138
139 PASS();
140}
141
142// TODO:
143// - Test set() can resize the dict
144// - I guess you have to do rehashing?
145
146TEST test_dict_internals() {
147 auto dict1 = NewDict<int, int>();
148 StackRoots _roots1({&dict1});
149 auto dict2 = NewDict<BigStr*, BigStr*>();
150 StackRoots _roots2({&dict2});
151
152 ASSERT_EQ(0, len(dict1));
153 ASSERT_EQ(0, len(dict2));
154
155 ASSERT_EQ_FMT(HeapTag::FixedSize, ObjHeader::FromObject(dict1)->heap_tag,
156 "%d");
157 ASSERT_EQ_FMT(HeapTag::FixedSize, ObjHeader::FromObject(dict1)->heap_tag,
158 "%d");
159
160 ASSERT_EQ_FMT(0, dict1->capacity_, "%d");
161 ASSERT_EQ_FMT(0, dict2->capacity_, "%d");
162
163 ASSERT_EQ(nullptr, dict1->index_);
164 ASSERT_EQ(nullptr, dict1->keys_);
165 ASSERT_EQ(nullptr, dict1->values_);
166
167 // Make sure they're on the heap
168#ifndef MARK_SWEEP
169 int diff1 = reinterpret_cast<char*>(dict1) - gHeap.from_space_.begin_;
170 int diff2 = reinterpret_cast<char*>(dict2) - gHeap.from_space_.begin_;
171 ASSERT(diff1 < 1024);
172 ASSERT(diff2 < 1024);
173#endif
174
175 dict1->set(42, 5);
176 ASSERT_EQ(5, dict1->at(42));
177 ASSERT_EQ(1, len(dict1));
178#if 0
179 ASSERT_EQ_FMT(6, dict1->capacity_, "%d");
180#endif
181
182#if 0
183 ASSERT_EQ_FMT(32, ObjHeader::FromObject(dict1->index_)->obj_len, "%d");
184 ASSERT_EQ_FMT(32, ObjHeader::FromObject(dict1->keys_)->obj_len, "%d");
185 ASSERT_EQ_FMT(32, ObjHeader::FromObject(dict1->values_)->obj_len, "%d");
186#endif
187
188 dict1->set(42, 99);
189 ASSERT_EQ(99, dict1->at(42));
190 ASSERT_EQ(1, len(dict1));
191#if 0
192 ASSERT_EQ_FMT(6, dict1->capacity_, "%d");
193#endif
194
195 dict1->set(43, 10);
196 ASSERT_EQ(10, dict1->at(43));
197 ASSERT_EQ(2, len(dict1));
198#if 0
199 ASSERT_EQ_FMT(6, dict1->capacity_, "%d");
200#endif
201
202 // Dict<int, int>
203 // capacity: 6 -> 14 -> 30 -> ...
204 // index len: 9 -> 21 -> 45 -> ...
205
206 // 6 * 4 bytes = 24, plus 8 byte header = 32, which fits in the second pool
207 // 9 * 4 bytes = 36, plus 8 byte header = 44, which fits in the second pool
208 for (int i = 0; i < 14; ++i) {
209 dict1->set(i, i + 999);
210 log("len_ = %d, capacity = %d, index len %d", dict1->len_, dict1->capacity_,
211 dict1->index_len_);
212
213 // make sure we didn't lose old entry after resize
214 ASSERT_EQ(10, dict1->at(43));
215 }
216
217 BigStr* foo = nullptr;
218 BigStr* bar = nullptr;
219 StackRoots _roots3({&foo, &bar});
220 foo = StrFromC("foo");
221 bar = StrFromC("bar");
222
223 dict2->set(foo, bar);
224
225 ASSERT_EQ(1, len(dict2));
226 ASSERT(str_equals(bar, dict2->at(foo)));
227
228#if 0
229 ASSERT_EQ_FMT(32, ObjHeader::FromObject(dict2->index_)->obj_len, "%d");
230 ASSERT_EQ_FMT(64, ObjHeader::FromObject(dict2->keys_)->obj_len, "%d");
231 ASSERT_EQ_FMT(64, ObjHeader::FromObject(dict2->values_)->obj_len, "%d");
232#endif
233
234 auto dict_si = NewDict<BigStr*, int>();
235 StackRoots _roots4({&dict_si});
236 dict_si->set(foo, 42);
237 ASSERT_EQ(1, len(dict_si));
238
239#if 0
240 ASSERT_EQ_FMT(32, ObjHeader::FromObject(dict_si->index_)->obj_len, "%d");
241 ASSERT_EQ_FMT(64, ObjHeader::FromObject(dict_si->keys_)->obj_len, "%d");
242 ASSERT_EQ_FMT(32, ObjHeader::FromObject(dict_si->values_)->obj_len, "%d");
243#endif
244
245 auto dict_is = NewDict<int, BigStr*>();
246 StackRoots _roots5({&dict_is});
247 dict_is->set(42, foo);
248 PASS();
249
250 ASSERT_EQ(1, len(dict_is));
251
252#if 0
253 ASSERT_EQ_FMT(32, ObjHeader::FromObject(dict_is->index_)->obj_len, "%d");
254 ASSERT_EQ_FMT(32, ObjHeader::FromObject(dict_is->keys_)->obj_len, "%d");
255 ASSERT_EQ_FMT(64, ObjHeader::FromObject(dict_is->values_)->obj_len, "%d");
256#endif
257
258 auto two = StrFromC("two");
259 StackRoots _roots6({&two});
260
261 auto dict3 = Alloc<Dict<int, BigStr*>>(
262 std::initializer_list<int>{1, 2},
263 std::initializer_list<BigStr*>{kEmptyString, two});
264 StackRoots _roots7({&dict3});
265
266 ASSERT_EQ_FMT(2, len(dict3), "%d");
267 ASSERT(str_equals(kEmptyString, dict3->get(1)));
268 ASSERT(str_equals(two, dict3->get(2)));
269
270 PASS();
271}
272
273TEST test_empty_dict() {
274 auto d = Alloc<Dict<BigStr*, BigStr*>>();
275
276 // Look up in empty dict
277 BigStr* val = d->get(StrFromC("nonexistent"));
278 log("val %p", val);
279 ASSERT_EQ(nullptr, val);
280
281 BigStr* val2 = d->get(StrFromC("nonexistent"), kEmptyString);
282 ASSERT_EQ(kEmptyString, val2);
283
284 PASS();
285}
286
287TEST dict_methods_test() {
288 Dict<int, BigStr*>* d = nullptr;
289 Dict<BigStr*, int>* d2 = nullptr;
290 BigStr* key = nullptr;
291 StackRoots _roots({&d, &d2, &key});
292
293 d = Alloc<Dict<int, BigStr*>>();
294 d->set(1, kStrFoo);
295 ASSERT(str_equals0("foo", d->at(1)));
296
297 d2 = Alloc<Dict<BigStr*, int>>();
298 key = StrFromC("key");
299 d2->set(key, 42);
300 ASSERT_EQ(42, d2->at(key));
301
302 PASS();
303
304 d2->set(StrFromC("key2"), 2);
305 d2->set(StrFromC("key3"), 3);
306
307 ASSERT_EQ_FMT(3, len(d2), "%d");
308
309 auto keys = d2->keys();
310 ASSERT_EQ_FMT(3, len(keys), "%d");
311
312 // Retain insertion order
313 ASSERT(str_equals0("key", keys->at(0)));
314 ASSERT(str_equals0("key2", keys->at(1)));
315 ASSERT(str_equals0("key3", keys->at(2)));
316
317 mylib::dict_erase(d2, StrFromC("key"));
318 ASSERT_EQ_FMT(2, len(d2), "%d");
319
320 auto keys2 = d2->keys();
321 ASSERT_EQ_FMT(2, len(keys2), "%d");
322 ASSERT(str_equals0("key2", keys2->at(0)));
323 ASSERT(str_equals0("key3", keys2->at(1)));
324
325 auto values = d2->values();
326 ASSERT_EQ_FMT(2, len(values), "%d");
327 ASSERT_EQ(2, values->at(0));
328 ASSERT_EQ(3, values->at(1));
329
330 int j = 0;
331 for (DictIter<BigStr*, int> it(d2); !it.Done(); it.Next()) {
332 auto key = it.Key();
333 auto value = it.Value();
334 log("d2 key = %s, value = %d", key->data_, value);
335 ++j;
336 }
337 ASSERT_EQ_FMT(len(d2), j, "%d");
338
339 d2->clear();
340 ASSERT_EQ(0, len(d2));
341 // Ensure it was zero'd
342 ASSERT_EQ(nullptr, d2->keys_->items_[0]);
343 ASSERT_EQ(0, d2->values_->items_[0]);
344
345 // get()
346 ASSERT(str_equals0("foo", d->get(1)));
347
348 // dict_contains()
349 ASSERT(dict_contains(d, 1));
350 ASSERT(!dict_contains(d, 2));
351
352 ASSERT_EQ(nullptr, d->get(423)); // nonexistent
353
354 // get(k, default)
355 ASSERT_EQ(kEmptyString, d->get(423, kEmptyString));
356 ASSERT_EQ(-99, d2->get(kEmptyString, -99));
357
358 // sorted()
359 auto d3 = Alloc<Dict<BigStr*, int>>();
360 auto a = StrFromC("a");
361
362 d3->set(StrFromC("b"), 11);
363 d3->set(StrFromC("c"), 12);
364 d3->set(StrFromC("a"), 10);
365 ASSERT_EQ(10, d3->at(StrFromC("a")));
366 ASSERT_EQ(11, d3->at(StrFromC("b")));
367 ASSERT_EQ(12, d3->at(StrFromC("c")));
368 ASSERT_EQ(3, len(d3));
369
370 auto keys3 = sorted(d3);
371 ASSERT_EQ(3, len(keys3));
372 ASSERT(str_equals0("a", keys3->at(0)));
373 ASSERT(str_equals0("b", keys3->at(1)));
374 ASSERT(str_equals0("c", keys3->at(2)));
375
376 auto keys4 = d3->keys();
377 ASSERT(list_contains(keys4, a));
378 ASSERT(!list_contains(keys4, StrFromC("zzz")));
379
380 ASSERT(dict_contains(d3, a));
381 mylib::dict_erase(d3, a);
382 ASSERT(!dict_contains(d3, a));
383 ASSERT_EQ(2, len(d3));
384
385 // Test a different type of dict, to make sure partial template
386 // specialization works
387 auto ss = Alloc<Dict<BigStr*, BigStr*>>();
388 ss->set(a, a);
389 ASSERT_EQ(1, len(ss));
390 ASSERT_EQ(1, len(ss->keys()));
391 ASSERT_EQ(1, len(ss->values()));
392
393 int k = 0;
394 for (DictIter<BigStr*, BigStr*> it(ss); !it.Done(); it.Next()) {
395 auto key = it.Key();
396 log("ss key = %s", key->data_);
397 ++k;
398 }
399 ASSERT_EQ_FMT(len(ss), k, "%d");
400
401 mylib::dict_erase(ss, a);
402 ASSERT_EQ(0, len(ss));
403
404 int m = 0;
405 for (DictIter<BigStr*, BigStr*> it(ss); !it.Done(); it.Next()) {
406 auto key = it.Key();
407 log("ss key = %s", key->data_);
408 ++m;
409 }
410 ASSERT_EQ_FMT(0, m, "%d");
411 ASSERT_EQ_FMT(len(ss), m, "%d");
412
413 PASS();
414}
415
416TEST dict_iters_test() {
417 Dict<BigStr*, int>* d2 = nullptr;
418 List<BigStr*>* keys = nullptr;
419 StackRoots _roots({&d2, &keys});
420
421 d2 = Alloc<Dict<BigStr*, int>>();
422 d2->set(kStrFoo, 2);
423 d2->set(kStrBar, 3);
424
425 keys = d2->keys();
426 for (int i = 0; i < len(keys); ++i) {
427 printf("k %s\n", keys->at(i)->data_);
428 }
429
430 log(" iterating over Dict");
431 for (DictIter<BigStr*, int> it(d2); !it.Done(); it.Next()) {
432 log("k = %s, v = %d", it.Key()->data_, it.Value());
433 }
434
435 PASS();
436}
437
438TEST test_tuple_construct() {
439 auto kvs = Alloc<List<Tuple2<int, int>*>>();
440 auto t1 = Alloc<Tuple2<int, int>>(0xdead, 0xbeef);
441 auto t2 = Alloc<Tuple2<int, int>>(0xbeee, 0xeeef);
442 kvs->append(t1);
443 kvs->append(t2);
444
445 auto d = dict(kvs);
446 ASSERT_EQ(d->at(0xdead), 0xbeef);
447 ASSERT_EQ(d->at(0xbeee), 0xeeef);
448
449 PASS();
450}
451
452TEST test_update_dict() {
453 auto d = Alloc<Dict<int, int>>();
454 d->set(1, 0xdead);
455 d->set(2, 0xbeef);
456 ASSERT_EQ(d->at(1), 0xdead);
457 ASSERT_EQ(d->at(2), 0xbeef);
458
459 auto kvs = Alloc<List<Tuple2<int, int>*>>();
460 auto t1 = Alloc<Tuple2<int, int>>(2, 0xfeeb);
461 auto t2 = Alloc<Tuple2<int, int>>(3, 0x3333);
462 kvs->append(t1);
463 kvs->append(t2);
464 d->update(kvs);
465 ASSERT_EQ(d->at(1), 0xdead);
466 ASSERT_EQ(d->at(2), 0xfeeb);
467 ASSERT_EQ(d->at(3), 0x3333);
468
469 PASS();
470}
471
472TEST test_tuple_key() {
473 auto d1 = Alloc<Dict<Tuple2<int, int>*, int>>();
474 auto t1 = Alloc<Tuple2<int, int>>(0xdead, 0xbeef);
475 auto t2 = Alloc<Tuple2<int, int>>(0xbeee, 0xeeef);
476 d1->set(t1, -42);
477 d1->set(t2, 17);
478 ASSERT_EQ(d1->at(t1), -42);
479 ASSERT_EQ(d1->at(t2), 17);
480
481 auto d2 = Alloc<Dict<Tuple2<BigStr*, int>*, int>>();
482 auto t3 = Alloc<Tuple2<BigStr*, int>>(StrFromC("foo"), 0xbeef);
483 auto t4 = Alloc<Tuple2<BigStr*, int>>(StrFromC("bar"), 0xeeef);
484 d2->set(t3, 12345);
485 d2->set(t4, 67890);
486 ASSERT_EQ(d2->at(t3), 12345);
487 ASSERT_EQ(d2->at(t4), 67890);
488
489 PASS();
490}
491
492TEST test_dict_erase() {
493 auto d = Alloc<Dict<int, int>>();
494 d->set(25315, 0xdead);
495 d->set(25316, 0xbeef);
496 d->set(25317, 0xc0ffee);
497
498 ASSERT_EQ(0xdead, d->at(25315));
499 ASSERT_EQ(0xbeef, d->at(25316));
500 ASSERT_EQ(0xc0ffee, d->at(25317));
501
502 mylib::dict_erase(d, 25315);
503 ASSERT_FALSE(dict_contains(d, 25315));
504 ASSERT_EQ(0xbeef, d->at(25316));
505 ASSERT_EQ(0xc0ffee, d->at(25317));
506
507 mylib::dict_erase(d, 25316);
508 ASSERT_FALSE(dict_contains(d, 25316));
509 ASSERT_EQ(0xc0ffee, d->at(25317));
510
511 // This is a trace of processes coming and going in a real shell. It tickles a
512 // (now fixed) bug in dict_erase() that would prematurely open a slot in the
513 // index before compacting the last inserted entry. With the right sequence of
514 // collisions (hence this trace) this behavior can lead to an index slot that
515 // points to an invalid entry, causing future calls to `find_key_in_index()`
516 // to crash (e.g. by dereferencing a bad pointer).
517 d = Alloc<Dict<int, int>>();
518 d->set(326224, 0);
519 d->set(326225, 1);
520 d->set(326226, 2);
521 d->set(326227, 3);
522 d->set(326228, 4);
523 mylib::dict_erase(d, 326227);
524 d->set(326229, 4);
525 d->set(326230, 5);
526 mylib::dict_erase(d, 326229);
527 d->set(326231, 5);
528 d->set(326232, 6);
529 mylib::dict_erase(d, 326231);
530 d->set(326233, 6);
531 d->set(326234, 7);
532 mylib::dict_erase(d, 326233);
533 d->set(326235, 7);
534 d->set(326236, 8);
535 mylib::dict_erase(d, 326235);
536 d->set(326237, 8);
537 d->set(326238, 9);
538 mylib::dict_erase(d, 326237);
539 d->set(326239, 9);
540 d->set(326240, 10);
541 mylib::dict_erase(d, 326239);
542 d->set(326241, 10);
543
544 PASS();
545}
546
547TEST test_dict_erase2() {
548 auto d = NewDict<int, BigStr*>();
549
550 for (int i = 0; i < 6; ++i) {
551 d->set(i, kEmptyString);
552 }
553 log("len(d) = %d", len(d));
554 ASSERT_EQ(6, len(d));
555
556 mylib::dict_erase(d, 99);
557 ASSERT_EQ(6, len(d));
558
559 PASS();
560}
561
562// Ints hash to themselves, so we can control when collisions happen. This test
563// sets up a few contrived workloads and checks that Dict still operates as
564// expected.
565TEST test_dict_probe() {
566 auto d = Alloc<Dict<int, int>>();
567
568 // This trace is a regression test for a weird bug where the index is full but
569 // the table has two free slots, causing a write to needlessly fail.
570 d->set(584818, -1);
571 d->set(584828, -1);
572 mylib::dict_erase(d, 584828);
573 d->set(584833, -1);
574 mylib::dict_erase(d, 584833);
575 d->set(584888, -1);
576
577 d->reserve(32);
578 d->clear();
579
580 // First, fill the table to the brim and check that we can recall
581 // everything.
582 int n = d->capacity_;
583 for (int i = 0; i < n; i++) {
584 d->set(i, i);
585 }
586 ASSERT_EQ(n, d->capacity_);
587 for (int i = 0; i < n; i++) {
588 ASSERT_EQ(i, d->at(i));
589 }
590 // Triger a rehash, and check that everything is OK.
591 d->set(n, n);
592 ASSERT(d->capacity_ > n);
593 for (int i = 0; i <= n; i++) {
594 ASSERT_EQ(i, d->at(i));
595 }
596 for (int i = 0; i <= n; i++) {
597 d->set(i, n * i);
598 }
599 for (int i = 0; i <= n; i++) {
600 ASSERT_EQ(n * i, d->at(i));
601 }
602
603 // Reset and fill the table with keys that all has onto the same index slot
604 n = d->capacity_;
605 int target = n / 2; // pick a slot in the middle to test wrap around
606 d->clear();
607 for (int i = 0; i < n; i++) {
608 d->set(target * i, i);
609 }
610 // Remove each entry one-by-one, stopping after each removal to check that
611 // the other keys can be set and retrieved without issue. This implicitly
612 // checks that special index entries like tombstones are working correctly.
613 for (int i = 0; i < n; i++) {
614 mylib::dict_erase(d, target * i);
615 for (int j = i + 1; j < n; j++) {
616 d->set(target * j, j + 1);
617 ASSERT_EQ(j + 1, d->at(target * j));
618 }
619 }
620
621 PASS();
622}
623
624GLOBAL_DICT(gDict, int, int, 2, {42 COMMA 43}, {1 COMMA 2});
625
626GLOBAL_DICT(gStrDict, BigStr*, BigStr*, 2, {kStrFoo COMMA kStrBar},
627 {kStrBar COMMA kStrFoo});
628
629TEST test_global_dict() {
630 log("gDict len = %d", len(gDict));
631 ASSERT_EQ(2, len(gDict));
632 ASSERT_EQ(1, gDict->at(42));
633 ASSERT_EQ(2, gDict->at(43));
634
635 log("gStrDict len = %d", len(gStrDict));
636 ASSERT_EQ(kStrFoo, gStrDict->at(kStrBar));
637 ASSERT_EQ(kStrBar, gStrDict->at(kStrFoo));
638
639 ASSERT(dict_contains(gStrDict, kStrFoo));
640 ASSERT(dict_contains(gStrDict, kStrBar));
641 ASSERT(!dict_contains(gStrDict, kEmptyString));
642
643 PASS();
644}
645
646TEST test_dict_ordering() {
647 auto d = Alloc<Dict<int, int>>();
648
649 auto in = NewList<int>(std::initializer_list<int>{95, 9, 67, 70, 93, 30, 25,
650 98, 80, 39, 56, 48, 99});
651 for (ListIter<int> it(in); !it.Done(); it.Next()) {
652 d->set(it.Value(), -1);
653 }
654
655 auto keys = d->keys();
656 ASSERT_EQ(len(in), len(keys));
657 for (int i = 0; i < len(in); i++) {
658 ASSERT_EQ(in->at(i), keys->at(i));
659 }
660
661 // check that order survives rehashing
662 d->reserve(2 * len(d));
663 keys = d->keys();
664 ASSERT_EQ(len(in), len(keys));
665 for (int i = 0; i < len(in); i++) {
666 ASSERT_EQ(in->at(i), keys->at(i));
667 }
668
669 PASS();
670}
671
672TEST test_hash() {
673 int i = 0;
674 int j = 0;
675 log("&i = %p", &i);
676 log("&j = %p", &j);
677
678 unsigned h1 = hash_key(&i);
679 log("h1 = %d", h1);
680 unsigned h2 = hash_key(&i);
681 log("h2 = %d", h2);
682 unsigned h3 = hash_key(&j);
683 log("h3 = %d", h3);
684
685 ASSERT_EQ_FMT(h1, h2, "%d");
686 ASSERT(h1 != h3);
687
688 PASS();
689}
690
691TEST hash_pileup_bug() {
692 auto* d = Alloc<Dict<mops::BigInt, BigStr*>>();
693
694 std::unordered_map<unsigned, bool> hist;
695
696 for (int i = 0; i < 24; ++i) {
697 mops::BigInt index{1 << i};
698 // log("index %ld", index);
699
700 for (mops::BigInt j = index; j < index + 2000; ++j) {
701 d->set(j, kEmptyString);
702 unsigned h = hash_key(j);
703 hist[h] = true;
704 // log("%ld %d", j, h);
705 }
706 // log("len %d", len(d));
707 }
708
709 log("len %d", len(d));
710 log("unique hashes %d", hist.size());
711
712 PASS();
713}
714
715GREATEST_MAIN_DEFS();
716
717int main(int argc, char** argv) {
718 gHeap.Init();
719
720 GREATEST_MAIN_BEGIN();
721
722 RUN_TEST(test_dict_init);
723 RUN_TEST(test_dict);
724 RUN_TEST(test_dict_internals);
725 RUN_TEST(test_empty_dict);
726 RUN_TEST(test_tuple_construct);
727 RUN_TEST(test_update_dict);
728 RUN_TEST(test_tuple_key);
729 RUN_TEST(test_dict_erase);
730 RUN_TEST(test_dict_erase2);
731 RUN_TEST(test_global_dict);
732 RUN_TEST(test_dict_ordering);
733 RUN_TEST(test_dict_probe);
734
735 RUN_TEST(dict_methods_test);
736 RUN_TEST(dict_iters_test);
737
738 RUN_TEST(test_hash);
739 RUN_TEST(hash_pileup_bug);
740
741 gHeap.CleanProcessExit();
742
743 GREATEST_MAIN_END();
744 return 0;
745}