AzerothCore 3.3.5a
OpenSource WoW Emulator
Loading...
Searching...
No Matches
FlatMultimapAuraPatternTest.cpp File Reference
#include "gtest/gtest.h"
#include <boost/container/flat_map.hpp>
#include <cstdint>
#include <set>
#include <vector>

Go to the source code of this file.

Classes

struct  FakeAura
 

Typedefs

using AuraMap = boost::container::flat_multimap< uint32_t, FakeAura * >
 

Functions

 TEST (FlatMultimapAuraPattern, SnapshotUpdate)
 
 TEST (FlatMultimapAuraPattern, EraseResetToBegin)
 
 TEST (FlatMultimapAuraPattern, LowerUpperBoundErase)
 
 TEST (FlatMultimapAuraPattern, SelectiveLowerBoundErase)
 
 TEST (FlatMultimapAuraPattern, EraseDuringSnapshot)
 
 TEST (FlatMultimapAuraPattern, InsertDuringSnapshot)
 
 TEST (FlatMultimapAuraPattern, PredicateRemovalResetBegin)
 
 TEST (FlatMultimapAuraPattern, PredicateRemovalInKeyRange)
 
 TEST (FlatMultimapAuraPattern, AuraStateMapErasePattern)
 
 TEST (FlatMultimapAuraPattern, EmptyMapPatterns)
 
 TEST (FlatMultimapAuraPattern, SingleElementPatterns)
 
 TEST (FlatMultimapAuraPattern, EraseAllViaResetBegin)
 
 TEST (FlatMultimapAuraPattern, DuplicateKeysInterleavedErase)
 
 TEST (FlatMultimapAuraPattern, DISABLED_StaleIteratorAfterCascade_AsanOnly)
 
 TEST (FlatMultimapAuraPattern, IteratorResetAfterCascade)
 

Typedef Documentation

◆ AuraMap

using AuraMap = boost::container::flat_multimap<uint32_t, FakeAura*>

Function Documentation

◆ TEST() [1/15]

TEST ( FlatMultimapAuraPattern  ,
AuraStateMapErasePattern   
)
395{
396 // Uses an enum-like key (AuraStateType is an enum in the real code)
397 using AuraStateMap = boost::container::flat_multimap<uint32_t, FakeAura*>;
398
399 FakeAura a1(10), a2(20), a3(30), a4(40);
400 AuraStateMap map;
401 // State 1 has multiple entries
402 map.emplace(1, &a1);
403 map.emplace(1, &a2);
404 map.emplace(1, &a3);
405 // State 2 has one entry
406 map.emplace(2, &a4);
407
408 // Remove a2 from state 1 — mirrors _UnapplyAura pattern
409 uint32_t auraState = 1;
410 FakeAura* target = &a2;
411 for (auto itr = map.lower_bound(auraState);
412 itr != map.upper_bound(auraState);)
413 {
414 if (itr->second == target)
415 {
416 map.erase(itr);
417 itr = map.lower_bound(auraState);
418 continue;
419 }
420 ++itr;
421 }
422
423 EXPECT_EQ(map.count(1), 2u);
424 EXPECT_EQ(map.count(2), 1u);
425
426 // Verify a2 is gone, a1 and a3 remain
427 std::set<FakeAura*> stateOnes;
428 for (auto itr = map.lower_bound(1); itr != map.upper_bound(1); ++itr)
429 stateOnes.insert(itr->second);
430 EXPECT_TRUE(stateOnes.count(&a1));
431 EXPECT_FALSE(stateOnes.count(&a2));
432 EXPECT_TRUE(stateOnes.count(&a3));
433}
Definition FlatMultimapAuraPatternTest.cpp:26

◆ TEST() [2/15]

TEST ( FlatMultimapAuraPattern  ,
DISABLED_StaleIteratorAfterCascade_AsanOnly   
)
561{
562 FakeAura a1(100), a2(200), a3(300), cascade1(999), cascade2(998);
563
564 AuraMap map;
565 map.emplace(100, &a1);
566 map.emplace(200, &a2);
567 map.emplace(300, &a3);
568 map.shrink_to_fit();
569
570 // caller loop holds an iterator
571 AuraMap::iterator iter = map.begin();
572
573 // RemoveAura: erase + reset, then cascades grow past capacity -> realloc
574 map.erase(iter);
575 iter = map.begin();
576 map.emplace(999, &cascade1);
577 map.emplace(998, &cascade2);
578
579 // resume with the pre-cascade iterator: heap-use-after-free under ASAN
580 EXPECT_NE(iter->second->spellId, 0u);
581}
boost::container::flat_multimap< uint32_t, FakeAura * > AuraMap
Definition FlatMultimapAuraPatternTest.cpp:37

◆ TEST() [3/15]

TEST ( FlatMultimapAuraPattern  ,
DuplicateKeysInterleavedErase   
)
524{
525 FakeAura a1(100), a2(100), a3(100), a4(200), a5(200);
526 a2.expired = true;
527 a5.expired = true;
528
529 AuraMap map;
530 map.emplace(100, &a1);
531 map.emplace(100, &a2);
532 map.emplace(100, &a3);
533 map.emplace(200, &a4);
534 map.emplace(200, &a5);
535
536 // Global erase-reset-to-begin for expired entries
537 for (auto i = map.begin(); i != map.end();)
538 {
539 if (i->second->IsExpired())
540 {
541 map.erase(i);
542 i = map.begin();
543 }
544 else
545 ++i;
546 }
547
548 EXPECT_EQ(map.size(), 3u);
549 EXPECT_EQ(map.count(100), 2u);
550 EXPECT_EQ(map.count(200), 1u);
551}

References FakeAura::expired.

◆ TEST() [4/15]

TEST ( FlatMultimapAuraPattern  ,
EmptyMapPatterns   
)
439{
440 AuraMap map;
441
442 // Snapshot of empty map
443 std::vector<FakeAura*> snapshot;
444 for (auto& [id, aura] : map)
445 snapshot.push_back(aura);
446 EXPECT_TRUE(snapshot.empty());
447
448 // Erase-reset-to-begin on empty map
449 for (auto i = map.begin(); i != map.end();)
450 {
451 map.erase(i);
452 i = map.begin();
453 }
454 EXPECT_TRUE(map.empty());
455
456 // lower_bound/upper_bound on empty map
457 for (auto itr = map.lower_bound(100); itr != map.upper_bound(100);)
458 {
459 map.erase(itr);
460 itr = map.lower_bound(100);
461 }
462 EXPECT_TRUE(map.empty());
463}

◆ TEST() [5/15]

TEST ( FlatMultimapAuraPattern  ,
EraseAllViaResetBegin   
)
496{
497 constexpr int N = 50;
498 std::vector<FakeAura> auras;
499 auras.reserve(N);
500
501 AuraMap map;
502 for (int i = 0; i < N; ++i)
503 {
504 auras.emplace_back(static_cast<uint32_t>(i * 10));
505 map.emplace(auras.back().spellId, &auras.back());
506 }
507
508 ASSERT_EQ(map.size(), static_cast<size_t>(N));
509
510 // Remove everything — every entry triggers erase + reset
511 for (auto i = map.begin(); i != map.end();)
512 {
513 map.erase(i);
514 i = map.begin();
515 }
516
517 EXPECT_TRUE(map.empty());
518}

◆ TEST() [6/15]

TEST ( FlatMultimapAuraPattern  ,
EraseDuringSnapshot   
)
210{
211 FakeAura a1(100), a2(200), a3(300), a4(400);
212 AuraMap map;
213 map.emplace(100, &a1);
214 map.emplace(200, &a2);
215 map.emplace(300, &a3);
216 map.emplace(400, &a4);
217
218 // Snapshot pointers
219 std::vector<FakeAura*> snapshot;
220 snapshot.reserve(map.size());
221 for (auto& [id, aura] : map)
222 snapshot.push_back(aura);
223
224 // Process snapshot; during processing, erase entries from the map
225 std::vector<uint32_t> processed;
226 for (FakeAura* aura : snapshot)
227 {
228 if (aura->IsRemoved())
229 continue;
230
231 processed.push_back(aura->spellId);
232
233 // Simulate cascading removal: processing a1 causes a3 to be
234 // removed from the map and marked removed
235 if (aura == &a1)
236 {
237 a3.removed = true;
238 // Erase a3 from map by finding it
239 for (auto it = map.begin(); it != map.end(); ++it)
240 {
241 if (it->second == &a3)
242 {
243 map.erase(it);
244 break;
245 }
246 }
247 }
248 }
249
250 // a1, a2, a4 processed; a3 skipped due to IsRemoved()
251 ASSERT_EQ(processed.size(), 3u);
252 EXPECT_EQ(processed[0], 100u);
253 EXPECT_EQ(processed[1], 200u);
254 EXPECT_EQ(processed[2], 400u);
255
256 // Map should have 3 entries (a3 was erased)
257 EXPECT_EQ(map.size(), 3u);
258}

◆ TEST() [7/15]

TEST ( FlatMultimapAuraPattern  ,
EraseResetToBegin   
)
86{
87 FakeAura a1(100), a2(200), a3(300), a4(400), a5(500);
88 a2.expired = true;
89 a4.expired = true;
90
91 AuraMap map;
92 map.emplace(100, &a1);
93 map.emplace(200, &a2);
94 map.emplace(300, &a3);
95 map.emplace(400, &a4);
96 map.emplace(500, &a5);
97
98 std::vector<FakeAura*> removed;
99
100 // Mirrors the expire loop in _UpdateSpells / RemoveOwnedAuras
101 for (AuraMap::iterator i = map.begin(); i != map.end();)
102 {
103 if (i->second->IsExpired())
104 {
105 FakeAura* aura = i->second;
106 map.erase(i);
107 i = map.begin(); // reset — flat_multimap invalidates all
108 removed.push_back(aura);
109 }
110 else
111 ++i;
112 }
113
114 // a2 and a4 should have been removed
115 ASSERT_EQ(removed.size(), 2u);
116 EXPECT_EQ(removed[0]->spellId, 200u);
117 EXPECT_EQ(removed[1]->spellId, 400u);
118
119 // Remaining entries
120 ASSERT_EQ(map.size(), 3u);
121 std::set<uint32_t> remaining;
122 for (auto& [id, aura] : map)
123 remaining.insert(id);
124 EXPECT_TRUE(remaining.count(100));
125 EXPECT_TRUE(remaining.count(300));
126 EXPECT_TRUE(remaining.count(500));
127}

◆ TEST() [8/15]

TEST ( FlatMultimapAuraPattern  ,
InsertDuringSnapshot   
)
265{
266 FakeAura a1(100), a2(200);
267 FakeAura a3(300); // will be inserted during snapshot iteration
268
269 AuraMap map;
270 map.emplace(100, &a1);
271 map.emplace(200, &a2);
272
273 // Snapshot before iteration
274 std::vector<FakeAura*> snapshot;
275 snapshot.reserve(map.size());
276 for (auto& [id, aura] : map)
277 snapshot.push_back(aura);
278
279 ASSERT_EQ(snapshot.size(), 2u);
280
281 // During iteration, insert a new entry
282 std::vector<uint32_t> processed;
283 for (FakeAura* aura : snapshot)
284 {
285 if (!aura->IsRemoved())
286 {
287 processed.push_back(aura->spellId);
288 aura->updated = true;
289 }
290
291 // Insert a3 while iterating the snapshot
292 if (aura == &a1)
293 map.emplace(300, &a3);
294 }
295
296 // Only the original 2 were processed
297 ASSERT_EQ(processed.size(), 2u);
298 EXPECT_EQ(processed[0], 100u);
299 EXPECT_EQ(processed[1], 200u);
300
301 // a3 was NOT processed (not in snapshot)
302 EXPECT_FALSE(a3.updated);
303
304 // But it IS in the map for future iterations
305 EXPECT_EQ(map.size(), 3u);
306 EXPECT_EQ(map.count(300), 1u);
307}

References FakeAura::updated.

◆ TEST() [9/15]

TEST ( FlatMultimapAuraPattern  ,
IteratorResetAfterCascade   
)
585{
586 FakeAura a1(100), a2(200), a3(300), cascade1(999), cascade2(998);
587
588 AuraMap map;
589 map.emplace(100, &a1);
590 map.emplace(200, &a2);
591 map.emplace(300, &a3);
592 map.shrink_to_fit();
593
594 AuraMap::iterator iter = map.begin();
595
596 map.erase(iter);
597 map.emplace(999, &cascade1);
598 map.emplace(998, &cascade2);
599 iter = map.begin(); // reset AFTER the cascades
600
601 std::set<uint32_t> seen;
602 for (; iter != map.end(); ++iter)
603 seen.insert(iter->second->spellId);
604
605 EXPECT_EQ(seen.size(), 4u);
606 EXPECT_FALSE(seen.count(100)); // removed
607 EXPECT_TRUE(seen.count(200));
608 EXPECT_TRUE(seen.count(300));
609 EXPECT_TRUE(seen.count(999)); // cascade-applied
610 EXPECT_TRUE(seen.count(998));
611}

◆ TEST() [10/15]

TEST ( FlatMultimapAuraPattern  ,
LowerUpperBoundErase   
)
137{
138 FakeAura a1(100), a2(100), a3(100), a4(200), a5(200);
139 AuraMap map;
140 map.emplace(100, &a1);
141 map.emplace(100, &a2);
142 map.emplace(100, &a3);
143 map.emplace(200, &a4);
144 map.emplace(200, &a5);
145
146 // Remove all entries with key 100 — mirrors RemoveOwnedAura(spellId)
147 uint32_t targetKey = 100;
148 for (auto itr = map.lower_bound(targetKey);
149 itr != map.upper_bound(targetKey);)
150 {
151 map.erase(itr);
152 itr = map.lower_bound(targetKey); // reset after erase
153 }
154
155 // All key-100 entries gone
156 EXPECT_EQ(map.count(100), 0u);
157
158 // Key-200 entries untouched
159 EXPECT_EQ(map.count(200), 2u);
160 ASSERT_EQ(map.size(), 2u);
161}

◆ TEST() [11/15]

TEST ( FlatMultimapAuraPattern  ,
PredicateRemovalInKeyRange   
)
352{
353 FakeAura a1(100), a2(100), a3(100), a4(100);
354 a1.expired = false;
355 a2.expired = true;
356 a3.expired = false;
357 a4.expired = true;
358
359 AuraMap map;
360 map.emplace(100, &a1);
361 map.emplace(100, &a2);
362 map.emplace(100, &a3);
363 map.emplace(100, &a4);
364 map.emplace(200, new FakeAura(200)); // different key, untouched
365
366 uint32_t spellId = 100;
367 for (auto iter = map.lower_bound(spellId);
368 iter != map.upper_bound(spellId);)
369 {
370 if (iter->second->IsExpired())
371 {
372 map.erase(iter);
373 iter = map.lower_bound(spellId);
374 continue;
375 }
376 ++iter;
377 }
378
379 // 2 expired removed, 2 non-expired remain under key 100
380 EXPECT_EQ(map.count(100), 2u);
381 // Key 200 untouched
382 EXPECT_EQ(map.count(200), 1u);
383
384 // Clean up heap-allocated entry
385 for (auto& [id, aura] : map)
386 if (id == 200)
387 delete aura;
388}

References FakeAura::expired.

◆ TEST() [12/15]

TEST ( FlatMultimapAuraPattern  ,
PredicateRemovalResetBegin   
)
314{
315 FakeAura a1(100), a2(200), a3(300), a4(400), a5(500);
316 // Remove even-numbered spell IDs
317 AuraMap map;
318 map.emplace(100, &a1);
319 map.emplace(200, &a2);
320 map.emplace(300, &a3);
321 map.emplace(400, &a4);
322 map.emplace(500, &a5);
323
324 auto shouldRemove = [](FakeAura const* a) {
325 return (a->spellId % 200) == 0;
326 };
327
328 for (AuraMap::iterator iter = map.begin(); iter != map.end();)
329 {
330 if (shouldRemove(iter->second))
331 {
332 map.erase(iter);
333 iter = map.begin(); // reset — mirrors RemoveOwnedAuras
334 continue;
335 }
336 ++iter;
337 }
338
339 ASSERT_EQ(map.size(), 3u);
340 EXPECT_EQ(map.count(100), 1u);
341 EXPECT_EQ(map.count(200), 0u);
342 EXPECT_EQ(map.count(300), 1u);
343 EXPECT_EQ(map.count(400), 0u);
344 EXPECT_EQ(map.count(500), 1u);
345}

◆ TEST() [13/15]

TEST ( FlatMultimapAuraPattern  ,
SelectiveLowerBoundErase   
)
168{
169 FakeAura a1(100), a2(100), a3(100);
170 // Only remove entries whose spellId matches AND which are expired
171 a1.expired = false;
172 a2.expired = true;
173 a3.expired = false;
174
175 AuraMap map;
176 map.emplace(100, &a1);
177 map.emplace(100, &a2);
178 map.emplace(100, &a3);
179
180 uint32_t targetKey = 100;
181 for (auto itr = map.lower_bound(targetKey);
182 itr != map.upper_bound(targetKey);)
183 {
184 if (itr->second->IsExpired())
185 {
186 map.erase(itr);
187 itr = map.lower_bound(targetKey);
188 }
189 else
190 ++itr;
191 }
192
193 // Only a2 removed
194 EXPECT_EQ(map.count(100), 2u);
195
196 // Verify the right ones survived
197 std::set<FakeAura*> survivors;
198 for (auto itr = map.lower_bound(100); itr != map.upper_bound(100); ++itr)
199 survivors.insert(itr->second);
200 EXPECT_TRUE(survivors.count(&a1));
201 EXPECT_FALSE(survivors.count(&a2));
202 EXPECT_TRUE(survivors.count(&a3));
203}

References FakeAura::expired.

◆ TEST() [14/15]

TEST ( FlatMultimapAuraPattern  ,
SingleElementPatterns   
)
469{
470 FakeAura a1(100);
471 a1.expired = true;
472
473 AuraMap map;
474 map.emplace(100, &a1);
475
476 // Erase-reset-to-begin with single element
477 for (auto i = map.begin(); i != map.end();)
478 {
479 if (i->second->IsExpired())
480 {
481 map.erase(i);
482 i = map.begin();
483 }
484 else
485 ++i;
486 }
487
488 EXPECT_TRUE(map.empty());
489}

References FakeAura::expired.

◆ TEST() [15/15]

TEST ( FlatMultimapAuraPattern  ,
SnapshotUpdate   
)
46{
47 FakeAura a1(100), a2(100), a3(200), a4(300);
48 AuraMap map;
49 map.emplace(100, &a1);
50 map.emplace(100, &a2);
51 map.emplace(200, &a3);
52 map.emplace(300, &a4);
53
54 // Snapshot pointers (mirrors Unit::_UpdateSpells)
55 std::vector<FakeAura*> snapshot;
56 snapshot.reserve(map.size());
57 for (auto& [id, aura] : map)
58 snapshot.push_back(aura);
59
60 ASSERT_EQ(snapshot.size(), 4u);
61
62 // Mark a2 as removed mid-iteration (simulates cascading removal)
63 a2.removed = true;
64
65 // Process snapshot — skip removed entries
66 for (FakeAura* aura : snapshot)
67 {
68 if (!aura->IsRemoved())
69 aura->updated = true;
70 }
71
72 EXPECT_TRUE(a1.updated);
73 EXPECT_FALSE(a2.updated); // skipped — was removed
74 EXPECT_TRUE(a3.updated);
75 EXPECT_TRUE(a4.updated);
76}

References FakeAura::updated.