AzerothCore 3.3.5a
OpenSource WoW Emulator
Loading...
Searching...
No Matches
SpellProcFullCoverageTest.cpp File Reference

Data-driven tests for ALL 869 spell_proc entries. More...

#include "ProcChanceTestHelper.h"
#include "ProcEventInfoHelper.h"
#include "SpellProcTestData.h"
#include "AuraStub.h"
#include "gtest/gtest.h"

Go to the source code of this file.

Classes

class  SpellProcFullCoverageTest
 
class  SpellProcCoverageStatsTest
 

Functions

 TEST_P (SpellProcFullCoverageTest, CooldownBlocking_WhenCooldownSet)
 
 TEST_P (SpellProcFullCoverageTest, Level60Reduction_WhenAttributeSet)
 
 TEST_P (SpellProcFullCoverageTest, AttributeMask_ValidFlags)
 
 TEST_P (SpellProcFullCoverageTest, UseStacksForCharges_Behavior)
 
 TEST_P (SpellProcFullCoverageTest, TriggeredCanProc_FlagSet)
 
 TEST_P (SpellProcFullCoverageTest, ReqManaCost_FlagSet)
 
 TEST_P (SpellProcFullCoverageTest, ChanceValue_InValidRange)
 
 TEST_P (SpellProcFullCoverageTest, ChanceCalculation_WithEntry)
 
 TEST_P (SpellProcFullCoverageTest, ProcFlags_NotEmpty)
 
 TEST_P (SpellProcFullCoverageTest, CooldownValue_Reasonable)
 
 TEST_P (SpellProcFullCoverageTest, SpellId_NonZero)
 
 INSTANTIATE_TEST_SUITE_P (AllSpellProcEntries, SpellProcFullCoverageTest, ::testing::ValuesIn(GetAllSpellProcTestEntries()), [](const ::testing::TestParamInfo< SpellProcTestEntry > &info) { int32_t id=info.param.SpellId;if(id< 0) return "NegId_"+std::to_string(-id);return "SpellId_"+std::to_string(id);})
 
 TEST_F (SpellProcCoverageStatsTest, CountEntriesWithCooldown)
 
 TEST_F (SpellProcCoverageStatsTest, CountEntriesWithChance)
 
 TEST_F (SpellProcCoverageStatsTest, CountEntriesWithLevel60Reduction)
 
 TEST_F (SpellProcCoverageStatsTest, CountEntriesWithUseStacks)
 
 TEST_F (SpellProcCoverageStatsTest, CountEntriesWithTriggeredCanProc)
 
 TEST_F (SpellProcCoverageStatsTest, CountEntriesWithReqManaCost)
 
 TEST_F (SpellProcCoverageStatsTest, TotalEntryCount)
 

Detailed Description

Data-driven tests for ALL 869 spell_proc entries.

Tests proc calculations for every spell_proc entry:

  • Cooldown blocking behavior
  • Chance calculation with level reduction
  • Attribute flag validation

This complements SpellProcDataDrivenTest.cpp which tests CanSpellTriggerProcOnEvent().


DESIGN NOTE: Why Tests Skip Certain Entries

This test file uses parameterized tests that run against ALL 869 spell_proc entries. Each test validates a specific feature (cooldowns, level reduction, attribute flags, etc.). Tests use GTEST_SKIP() for entries that don't have the feature being tested.

For example (current counts from test output):

  • CooldownBlocking_WhenCooldownSet: Tests 246 entries with Cooldown > 0 (skips 623)
  • Level60Reduction_WhenAttributeSet: Tests entries with PROC_ATTR_REDUCE_PROC_60 (0 currently)
  • UseStacksForCharges_Behavior: Tests entries with PROC_ATTR_USE_STACKS_FOR_CHARGES (0 currently)
  • TriggeredCanProc_FlagSet: Tests 73 entries with PROC_ATTR_TRIGGERED_CAN_PROC (skips 796)
  • ReqManaCost_FlagSet: Tests 5 entries with PROC_ATTR_REQ_MANA_COST (skips 864)

This is INTENTIONAL. Running parameterized tests against all entries ensures:

  1. Every entry is validated for applicable features
  2. Statistics show exact coverage (X entries with feature Y)
  3. New entries added to spell_proc are automatically tested
  4. Regression detection if an entry unexpectedly gains/loses a feature

The statistics tests at the bottom output the exact counts: "[ INFO ] Entries with cooldown: 85 / 869" "[ INFO ] Entries with REDUCE_PROC_60: 15 / 869" etc.

SKIPPED tests are expected and correct. Each skip message includes:

  • The SpellId being skipped

- The reason (e.g., "has no cooldown", "doesn't have REDUCE_PROC_60")

Definition in file SpellProcFullCoverageTest.cpp.

Function Documentation

◆ INSTANTIATE_TEST_SUITE_P()

INSTANTIATE_TEST_SUITE_P ( AllSpellProcEntries  ,
SpellProcFullCoverageTest  ,
::testing::ValuesIn(GetAllSpellProcTestEntries())  ,
[] (const ::testing::TestParamInfo< SpellProcTestEntry > &info) { int32_t id=info.param.SpellId;if(id< 0) return "NegId_"+std::to_string(-id);return "SpellId_"+std::to_string(id);}   
)

◆ TEST_F() [1/7]

TEST_F ( SpellProcCoverageStatsTest  ,
CountEntriesWithChance   
)
394{
395 size_t withChance = 0;
396 for (auto const& entry : _allEntries)
397 {
398 if (entry.Chance > 0.0f)
399 ++withChance;
400 }
401 std::cout << "[ INFO ] Entries with chance > 0: " << withChance
402 << " / " << _allEntries.size() << std::endl;
403}

◆ TEST_F() [2/7]

TEST_F ( SpellProcCoverageStatsTest  ,
CountEntriesWithCooldown   
)
381{
382 size_t withCooldown = 0;
383 for (auto const& entry : _allEntries)
384 {
385 if (entry.Cooldown > 0)
386 ++withCooldown;
387 }
388 std::cout << "[ INFO ] Entries with cooldown: " << withCooldown
389 << " / " << _allEntries.size() << std::endl;
390 EXPECT_GT(withCooldown, 0u);
391}

◆ TEST_F() [3/7]

TEST_F ( SpellProcCoverageStatsTest  ,
CountEntriesWithLevel60Reduction   
)
406{
407 size_t withReduction = 0;
408 for (auto const& entry : _allEntries)
409 {
410 if (entry.AttributesMask & PROC_ATTR_REDUCE_PROC_60)
411 ++withReduction;
412 }
413 std::cout << "[ INFO ] Entries with REDUCE_PROC_60: " << withReduction
414 << " / " << _allEntries.size() << std::endl;
415}
@ PROC_ATTR_REDUCE_PROC_60
Definition SpellMgr.h:281

References PROC_ATTR_REDUCE_PROC_60.

◆ TEST_F() [4/7]

TEST_F ( SpellProcCoverageStatsTest  ,
CountEntriesWithReqManaCost   
)
442{
443 size_t withReqManaCost = 0;
444 for (auto const& entry : _allEntries)
445 {
446 if (entry.AttributesMask & PROC_ATTR_REQ_MANA_COST)
447 ++withReqManaCost;
448 }
449 std::cout << "[ INFO ] Entries with REQ_MANA_COST: " << withReqManaCost
450 << " / " << _allEntries.size() << std::endl;
451}
@ PROC_ATTR_REQ_MANA_COST
Definition SpellMgr.h:278

References PROC_ATTR_REQ_MANA_COST.

◆ TEST_F() [5/7]

TEST_F ( SpellProcCoverageStatsTest  ,
CountEntriesWithTriggeredCanProc   
)
430{
431 size_t withTriggered = 0;
432 for (auto const& entry : _allEntries)
433 {
434 if (entry.AttributesMask & PROC_ATTR_TRIGGERED_CAN_PROC)
435 ++withTriggered;
436 }
437 std::cout << "[ INFO ] Entries with TRIGGERED_CAN_PROC: " << withTriggered
438 << " / " << _allEntries.size() << std::endl;
439}
@ PROC_ATTR_TRIGGERED_CAN_PROC
Definition SpellMgr.h:277

References PROC_ATTR_TRIGGERED_CAN_PROC.

◆ TEST_F() [6/7]

TEST_F ( SpellProcCoverageStatsTest  ,
CountEntriesWithUseStacks   
)
418{
419 size_t withUseStacks = 0;
420 for (auto const& entry : _allEntries)
421 {
422 if (entry.AttributesMask & PROC_ATTR_USE_STACKS_FOR_CHARGES)
423 ++withUseStacks;
424 }
425 std::cout << "[ INFO ] Entries with USE_STACKS_FOR_CHARGES: " << withUseStacks
426 << " / " << _allEntries.size() << std::endl;
427}
@ PROC_ATTR_USE_STACKS_FOR_CHARGES
Definition SpellMgr.h:280

References PROC_ATTR_USE_STACKS_FOR_CHARGES.

◆ TEST_F() [7/7]

TEST_F ( SpellProcCoverageStatsTest  ,
TotalEntryCount   
)
454{
455 std::cout << "[ INFO ] Total spell_proc entries tested: " << _allEntries.size() << std::endl;
456 EXPECT_EQ(_allEntries.size(), 869u)
457 << "Expected 869 entries but got " << _allEntries.size();
458}

◆ TEST_P() [1/11]

TEST_P ( SpellProcFullCoverageTest  ,
AttributeMask_ValidFlags   
)
175{
176 // Valid attribute flags
177 constexpr uint32 VALID_ATTRIBUTE_MASK =
185
186 // Check for invalid bits (skip 0x20 and 0x40 which are unused/reserved)
187 uint32 invalidBits = _entry.AttributesMask & ~VALID_ATTRIBUTE_MASK & ~0x60;
188 EXPECT_EQ(invalidBits, 0u)
189 << "SpellId " << _entry.SpellId << " has invalid attribute bits: 0x"
190 << std::hex << invalidBits;
191}
std::uint32_t uint32
Definition Define.h:107
@ PROC_ATTR_CANT_PROC_FROM_ITEM_CAST
Definition SpellMgr.h:282
@ PROC_ATTR_REQ_EXP_OR_HONOR
Definition SpellMgr.h:276
@ PROC_ATTR_REQ_SPELLMOD
Definition SpellMgr.h:279

References PROC_ATTR_CANT_PROC_FROM_ITEM_CAST, PROC_ATTR_REDUCE_PROC_60, PROC_ATTR_REQ_EXP_OR_HONOR, PROC_ATTR_REQ_MANA_COST, PROC_ATTR_REQ_SPELLMOD, PROC_ATTR_TRIGGERED_CAN_PROC, and PROC_ATTR_USE_STACKS_FOR_CHARGES.

◆ TEST_P() [2/11]

TEST_P ( SpellProcFullCoverageTest  ,
ChanceCalculation_WithEntry   
)
267{
268 // SKIP REASON: This test validates proc chance calculation with level reduction.
269 // Entries with Chance = 0 rely on DBC defaults or use PPM (procs per minute) instead.
270 // We can only test explicit chance calculation for entries that define a Chance value.
271 // PPM-based procs are tested separately in SpellProcPPMTest.cpp.
272 if (_entry.Chance <= 0.0f)
273 GTEST_SKIP() << "SpellId " << _entry.SpellId << " has no base chance";
274
275 // Calculate chance at level 80 (typical max level)
276 float calculatedChance = ProcChanceTestHelper::SimulateCalcProcChance(
277 _procEntry, 80, 2500, 0.0f, 0.0f, false);
278
279 if (_entry.AttributesMask & PROC_ATTR_REDUCE_PROC_60)
280 {
281 // With level 60+ reduction at level 80
282 float expectedReduced = _entry.Chance * (1.0f - 20.0f/30.0f);
283 EXPECT_NEAR(calculatedChance, expectedReduced, 0.5f)
284 << "SpellId " << _entry.SpellId << " reduced chance mismatch";
285 }
286 else
287 {
288 // Without reduction
289 EXPECT_NEAR(calculatedChance, _entry.Chance, 0.01f)
290 << "SpellId " << _entry.SpellId << " base chance mismatch";
291 }
292}
static float SimulateCalcProcChance(SpellProcEntry const &procEntry, uint32 actorLevel=80, uint32 weaponSpeed=2500, float chanceModifier=0.0f, float ppmModifier=0.0f, bool hasDamageInfo=true, bool hasHealInfo=false)
Simulate CalcProcChance() from SpellAuras.cpp.
Definition ProcChanceTestHelper.h:85

References PROC_ATTR_REDUCE_PROC_60, and ProcChanceTestHelper::SimulateCalcProcChance().

◆ TEST_P() [3/11]

TEST_P ( SpellProcFullCoverageTest  ,
ChanceValue_InValidRange   
)
255{
256 // Chance should be in valid range (0-100 normally, but some can exceed)
257 // Just verify it's not negative
258 EXPECT_GE(_entry.Chance, 0.0f)
259 << "SpellId " << _entry.SpellId << " has negative chance";
260
261 // And not absurdly high (>500% would be suspicious)
262 EXPECT_LE(_entry.Chance, 500.0f)
263 << "SpellId " << _entry.SpellId << " has suspiciously high chance";
264}

◆ TEST_P() [4/11]

TEST_P ( SpellProcFullCoverageTest  ,
CooldownBlocking_WhenCooldownSet   
)
94{
95 // SKIP REASON: This test validates cooldown blocking behavior.
96 // Only entries with Cooldown > 0 can be tested for ICD (Internal Cooldown).
97 // Entries without cooldowns proc on every valid trigger, so there's nothing
98 // to test here. The skip count shows how many entries lack cooldowns.
99 if (_entry.Cooldown == 0)
100 GTEST_SKIP() << "SpellId " << _entry.SpellId << " has no cooldown";
101
102 ProcTestScenario scenario;
103 scenario.WithAura(std::abs(_entry.SpellId));
104
105 // Set 100% chance to isolate cooldown testing
106 SpellProcEntry testEntry = _procEntry;
107 testEntry.Chance = 100.0f;
108 testEntry.Cooldown = Milliseconds(_entry.Cooldown);
109
110 // First proc should succeed
111 EXPECT_TRUE(scenario.SimulateProc(testEntry))
112 << "SpellId " << _entry.SpellId << " first proc should succeed";
113
114 // Second proc immediately after should fail (on cooldown)
115 EXPECT_FALSE(scenario.SimulateProc(testEntry))
116 << "SpellId " << _entry.SpellId << " should be blocked during "
117 << _entry.Cooldown << "ms cooldown";
118
119 // Wait for cooldown to expire
120 scenario.AdvanceTime(std::chrono::milliseconds(_entry.Cooldown + 1));
121
122 // Third proc after cooldown should succeed
123 EXPECT_TRUE(scenario.SimulateProc(testEntry))
124 << "SpellId " << _entry.SpellId << " should proc after cooldown expires";
125}
std::chrono::milliseconds Milliseconds
Milliseconds shorthand typedef.
Definition Duration.h:27
Test context for proc simulation scenarios.
Definition ProcChanceTestHelper.h:613
void AdvanceTime(std::chrono::milliseconds duration)
Definition ProcChanceTestHelper.h:618
ProcTestScenario & WithAura(uint32_t spellId, uint8_t charges=0, uint8_t stacks=1)
Definition ProcChanceTestHelper.h:644
bool SimulateProc(SpellProcEntry const &procEntry, float rollResult=0.0f)
Definition ProcChanceTestHelper.h:654
Definition SpellMgr.h:286
Milliseconds Cooldown
Definition SpellMgr.h:298
float Chance
Definition SpellMgr.h:297

References ProcTestScenario::AdvanceTime(), SpellProcEntry::Chance, SpellProcEntry::Cooldown, ProcTestScenario::SimulateProc(), and ProcTestScenario::WithAura().

◆ TEST_P() [5/11]

TEST_P ( SpellProcFullCoverageTest  ,
CooldownValue_Reasonable   
)
318{
319 // SKIP REASON: This test validates cooldown values are within reasonable bounds.
320 // Entries without cooldowns (Cooldown = 0) can proc on every trigger with no
321 // internal cooldown. 623 entries have no ICD and this is intentional - they
322 // rely on proc chance alone to limit frequency.
323 // Only 246 entries with explicit cooldowns need range validation.
324 if (_entry.Cooldown == 0)
325 GTEST_SKIP() << "SpellId " << _entry.SpellId << " has no cooldown";
326
327 // Cooldowns should be reasonable (not too short, not too long)
328 // Shortest reasonable cooldown is ~1ms
329 // Longest reasonable cooldown is ~15 minutes (900000ms) - some trinkets have 10+ min ICDs
330 EXPECT_GE(_entry.Cooldown, 1u)
331 << "SpellId " << _entry.SpellId << " has suspiciously short cooldown";
332 EXPECT_LE(_entry.Cooldown, 900000u)
333 << "SpellId " << _entry.SpellId << " has suspiciously long cooldown ("
334 << _entry.Cooldown << "ms = " << _entry.Cooldown/60000 << " minutes)";
335}

◆ TEST_P() [6/11]

TEST_P ( SpellProcFullCoverageTest  ,
Level60Reduction_WhenAttributeSet   
)
134{
135 // SKIP REASON: This test validates the level 60+ proc chance reduction formula.
136 // Only entries with PROC_ATTR_REDUCE_PROC_60 attribute have their proc chance
137 // reduced at higher levels. Spells like old weapon procs (Fiery, Crusader)
138 // use this to prevent them from being overpowered at level 80.
139 // Entries without this attribute maintain constant proc chance at all levels.
140 if (!(_entry.AttributesMask & PROC_ATTR_REDUCE_PROC_60))
141 GTEST_SKIP() << "SpellId " << _entry.SpellId << " doesn't have REDUCE_PROC_60";
142
143 // Use a meaningful base chance for testing
144 float baseChance = _entry.Chance > 0 ? _entry.Chance : 30.0f;
145
146 // Level 60: No reduction
147 float chanceAt60 = ProcChanceTestHelper::ApplyLevel60Reduction(baseChance, 60);
148 EXPECT_NEAR(chanceAt60, baseChance, 0.01f)
149 << "SpellId " << _entry.SpellId << " should have no reduction at level 60";
150
151 // Level 70: 33.33% reduction
152 float chanceAt70 = ProcChanceTestHelper::ApplyLevel60Reduction(baseChance, 70);
153 float expectedAt70 = baseChance * (1.0f - 10.0f/30.0f);
154 EXPECT_NEAR(chanceAt70, expectedAt70, 0.5f)
155 << "SpellId " << _entry.SpellId << " should have 33% reduction at level 70";
156
157 // Level 80: 66.67% reduction
158 float chanceAt80 = ProcChanceTestHelper::ApplyLevel60Reduction(baseChance, 80);
159 float expectedAt80 = baseChance * (1.0f - 20.0f/30.0f);
160 EXPECT_NEAR(chanceAt80, expectedAt80, 0.5f)
161 << "SpellId " << _entry.SpellId << " should have 66% reduction at level 80";
162
163 // Verify reduction is correct
164 EXPECT_LT(chanceAt80, chanceAt70)
165 << "SpellId " << _entry.SpellId << " chance at 80 should be less than at 70";
166 EXPECT_LT(chanceAt70, chanceAt60)
167 << "SpellId " << _entry.SpellId << " chance at 70 should be less than at 60";
168}
static float ApplyLevel60Reduction(float baseChance, uint32 actorLevel)
Calculate level 60+ reduction Implements PROC_ATTR_REDUCE_PROC_60: 3.333% reduction per level above 6...
Definition ProcChanceTestHelper.h:63

References ProcChanceTestHelper::ApplyLevel60Reduction(), and PROC_ATTR_REDUCE_PROC_60.

◆ TEST_P() [7/11]

TEST_P ( SpellProcFullCoverageTest  ,
ProcFlags_NotEmpty   
)
299{
300 // Most entries should have proc flags OR spell family filters
301 // Skip validation if both are zero (some entries use only SchoolMask)
302 if (_entry.ProcFlags == 0 && _entry.SpellFamilyName == 0 && _entry.SchoolMask == 0)
303 {
304 // This is a potential configuration issue, but not necessarily an error
305 // Some entries are passive effects that don't proc from events
306 }
307
308 // Just verify ProcFlags is valid (no invalid bits)
309 // All valid proc flags are defined in SpellMgr.h
310 // This is a basic sanity check
311}

◆ TEST_P() [8/11]

TEST_P ( SpellProcFullCoverageTest  ,
ReqManaCost_FlagSet   
)
236{
237 // SKIP REASON: This test validates the PROC_ATTR_REQ_MANA_COST attribute.
238 // Only 5 entries require the triggering spell to have a mana cost.
239 // This prevents free spells (instant casts with no cost) from triggering procs.
240 // Example: Illumination should only proc from actual heals, not free procs.
241 // 864 entries don't care about mana cost, so this test is skipped for them.
242 if (!(_entry.AttributesMask & PROC_ATTR_REQ_MANA_COST))
243 GTEST_SKIP() << "SpellId " << _entry.SpellId << " doesn't have REQ_MANA_COST";
244
245 // Just verify the flag is properly set in the entry
246 EXPECT_TRUE(_procEntry.AttributesMask & PROC_ATTR_REQ_MANA_COST)
247 << "SpellId " << _entry.SpellId << " REQ_MANA_COST should be set";
248}

References PROC_ATTR_REQ_MANA_COST.

◆ TEST_P() [9/11]

TEST_P ( SpellProcFullCoverageTest  ,
SpellId_NonZero   
)
342{
343 // SpellId should never be zero
344 EXPECT_NE(_entry.SpellId, 0)
345 << "Entry has zero SpellId which is invalid";
346}

◆ TEST_P() [10/11]

TEST_P ( SpellProcFullCoverageTest  ,
TriggeredCanProc_FlagSet   
)
221{
222 // SKIP REASON: This test validates the PROC_ATTR_TRIGGERED_CAN_PROC attribute.
223 // Most proc auras (796 entries) do NOT allow triggered spells to trigger them,
224 // preventing infinite proc chains. Only 73 entries explicitly allow triggered
225 // spells to proc (e.g., some talent effects that should chain-react).
226 // Entries without this flag block triggered spell procs for safety.
227 if (!(_entry.AttributesMask & PROC_ATTR_TRIGGERED_CAN_PROC))
228 GTEST_SKIP() << "SpellId " << _entry.SpellId << " doesn't have TRIGGERED_CAN_PROC";
229
230 // Just verify the flag is properly set in the entry
231 EXPECT_TRUE(_procEntry.AttributesMask & PROC_ATTR_TRIGGERED_CAN_PROC)
232 << "SpellId " << _entry.SpellId << " TRIGGERED_CAN_PROC should be set";
233}

References PROC_ATTR_TRIGGERED_CAN_PROC.

◆ TEST_P() [11/11]

TEST_P ( SpellProcFullCoverageTest  ,
UseStacksForCharges_Behavior   
)
194{
195 // SKIP REASON: This test validates stack consumption instead of charge consumption.
196 // Currently 0 entries use PROC_ATTR_USE_STACKS_FOR_CHARGES (attribute data may
197 // need population). When set, this causes procs to decrement the aura's stack
198 // count rather than its charge count.
199 // Example: Druid's Eclipse - each proc reduces stacks until buff expires.
200 // Most proc auras use charges (consumed individually) not stacks.
201 if (!(_entry.AttributesMask & PROC_ATTR_USE_STACKS_FOR_CHARGES))
202 GTEST_SKIP() << "SpellId " << _entry.SpellId << " doesn't use stacks for charges";
203
204 auto aura = AuraStubBuilder()
205 .WithId(std::abs(_entry.SpellId))
207 .Build();
208
209 SpellProcEntry testEntry = _procEntry;
210 testEntry.Chance = 100.0f;
211
212 // Consume should decrement stacks
213 bool removed = ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), testEntry);
214
215 EXPECT_EQ(aura->GetStackAmount(), 4)
216 << "SpellId " << _entry.SpellId << " should decrement stacks";
217 EXPECT_FALSE(removed);
218}
Builder for creating AuraStub instances with fluent API.
Definition AuraStub.h:290
AuraStubBuilder & WithStackAmount(uint8_t amount)
Definition AuraStub.h:320
std::unique_ptr< AuraStub > Build()
Definition AuraStub.h:353
AuraStubBuilder & WithId(uint32_t id)
Definition AuraStub.h:294
static bool SimulateConsumeProcCharges(AuraStub *aura, SpellProcEntry const &procEntry)
Simulate charge consumption from ConsumeProcCharges()
Definition ProcChanceTestHelper.h:121

References AuraStubBuilder::Build(), SpellProcEntry::Chance, PROC_ATTR_USE_STACKS_FOR_CHARGES, ProcChanceTestHelper::SimulateConsumeProcCharges(), AuraStubBuilder::WithId(), and AuraStubBuilder::WithStackAmount().