full source

This commit is contained in:
luis
2026-01-20 21:37:09 -03:00
parent dbe6ddf847
commit 62ea7784c4
1596 changed files with 795804 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
# This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
GroupSources(${CMAKE_CURRENT_SOURCE_DIR})
add_executable(tests)
CollectAndAddSourceFiles(
tests
${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(tests
PRIVATE
trinity-core-interface
game
Catch2::Catch2)
target_include_directories(tests
PRIVATE
${CMAKE_CURRENT_BINARY_DIR})
catch_discover_tests(tests)
set_target_properties(tests
PROPERTIES
FOLDER
"tests")
+79
View File
@@ -0,0 +1,79 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "DummyData.h"
#include "DB2Stores.h"
#include "ItemDefines.h"
#include "ItemTemplate.h"
#include "ObjectMgr.h"
/*static*/ ItemTemplate& UnitTestDataLoader::GetItemTemplate(uint32 itemId, std::string_view name)
{
ItemTemplate& t = sObjectMgr->_itemTemplateStore[itemId];
ItemEntry* itemEntry = new ItemEntry();
memset(itemEntry, 0, sizeof(ItemEntry));
itemEntry->ID = itemId;
itemEntry->ClassID = ITEM_CLASS_MISCELLANEOUS;
t.BasicData = itemEntry;
ItemSparseEntry* itemSparse = new ItemSparseEntry();
itemSparse->ID = itemId;
for (char const*& display : itemSparse->Display.Str)
display = "";
itemSparse->Display.Str[LOCALE_enUS] = name.data();
itemSparse->OverallQualityID = ITEM_QUALITY_ARTIFACT;
t.ExtendedData = itemSparse;
return t;
}
/*static*/ void UnitTestDataLoader::SetItemLocale(uint32 id, LocaleConstant locale, std::string_view name)
{
ItemTemplate& t = sObjectMgr->_itemTemplateStore[id];
const_cast<ItemSparseEntry*>(t.ExtendedData)->Display.Str[locale] = name.data();
}
/*static*/ void UnitTestDataLoader::LoadItemTemplates()
{
if (!sObjectMgr->_itemTemplateStore.empty())
return;
ItemTemplate& t = GetItemTemplate(6948, "Hearthstone");
const_cast<ItemSparseEntry*>(t.ExtendedData)->OverallQualityID = ITEM_QUALITY_NORMAL;
SetItemLocale(6948, LOCALE_esMX, "Piedra de hogar");
}
static UnitTestDataLoader::DB2<AchievementEntry, &AchievementEntry::ID> achievements(sAchievementStore);
/*static*/ void UnitTestDataLoader::LoadAchievementTemplates()
{
auto loader = achievements.Loader();
AchievementEntry& toc5 = loader.Add();
toc5.ID = 4298;
toc5.Faction = 1;
toc5.InstanceID = 650;
toc5.Title.Str.fill("");
toc5.Title.Str[LOCALE_enUS] = "Heroic: Trial of the Champion";
toc5.Title.Str[LOCALE_esES] = "Heroico: Prueba del Campe\xc3\xb3n";
toc5.Title.Str[LOCALE_esMX] = "Heroico: Prueba del Campe\xc3\xb3n";
toc5.Category = 14921;
toc5.Points = 10;
toc5.Flags = 0;
toc5.MinimumCriteria = 0;
toc5.SharesCriteria = 0;
}
+75
View File
@@ -0,0 +1,75 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TRINITY_DUMMYDATA_H
#define TRINITY_DUMMYDATA_H
#include "Common.h"
#include "Define.h"
#include "DB2Store.h"
#include <string_view>
struct ItemTemplate;
class UnitTestDataLoader
{
public:
template <typename T, uint32 T::*ID>
class DB2
{
class LoaderGuard
{
public:
LoaderGuard(DB2& d) : _d(d) {}
~LoaderGuard() { _d.Dump(); }
T& Add() { return _d._storage.emplace_back(); }
private:
DB2& _d;
};
public:
DB2(DB2Storage<T>& store) : _store(store) {}
LoaderGuard Loader() { return {*this}; }
void Dump()
{
delete[] _store._indexTable;
for (T const& entry : _storage)
if (entry.*ID >= _store._indexTableSize)
_store._indexTableSize = entry.*ID + 1;
_store._indexTable = new char*[_store._indexTableSize];
for (size_t i = 0; i < _store._indexTableSize; ++i)
_store._indexTable[i] = nullptr;
for (T& entry : _storage)
_store._indexTable[entry.*ID] = reinterpret_cast<char*>(&entry);
}
private:
std::vector<T> _storage;
DB2Storage<T>& _store;
};
static void LoadAchievementTemplates();
static void LoadItemTemplates();
private:
static ItemTemplate& GetItemTemplate(uint32 id, std::string_view name);
static void SetItemLocale(uint32 id, LocaleConstant locale, std::string_view name);
};
#endif
@@ -0,0 +1,353 @@
# =========================================================================
# PHASE 3A - PRIEST BASELINE METRICS CAPTURE
# =========================================================================
# Purpose: Capture comprehensive baseline metrics for PriestAI before refactoring
# Date: 2025-10-17
# Author: Claude Code (Phase 3A Week 1)
# =========================================================================
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
# Configuration
$PROJECT_ROOT = "C:\TrinityBots\TrinityCore"
$BUILD_DIR = "$PROJECT_ROOT\build"
$OUTPUT_DIR = "$PROJECT_ROOT\Tests\Phase3\Baseline"
$TIMESTAMP = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
$BASELINE_FILE = "$OUTPUT_DIR\priest_baseline_$TIMESTAMP.json"
Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host "PRIEST BASELINE METRICS CAPTURE" -ForegroundColor Cyan
Write-Host "========================================`n" -ForegroundColor Cyan
# Create output directory
New-Item -ItemType Directory -Force -Path $OUTPUT_DIR | Out-Null
# =========================================================================
# 1. CODE METRICS - Static Analysis
# =========================================================================
Write-Host "[1/5] Capturing Code Metrics..." -ForegroundColor Yellow
$priestFiles = @(
"src\modules\Playerbot\AI\ClassAI\Priests\PriestAI.h",
"src\modules\Playerbot\AI\ClassAI\Priests\PriestAI.cpp",
"src\modules\Playerbot\AI\ClassAI\Priests\PriestSpecialization.h",
"src\modules\Playerbot\AI\ClassAI\Priests\PriestSpecialization.cpp",
"src\modules\Playerbot\AI\ClassAI\Priests\PriestAI_Specialization.cpp",
"src\modules\Playerbot\AI\ClassAI\Priests\DisciplineSpecialization.h",
"src\modules\Playerbot\AI\ClassAI\Priests\DisciplineSpecialization.cpp",
"src\modules\Playerbot\AI\ClassAI\Priests\HolySpecialization.h",
"src\modules\Playerbot\AI\ClassAI\Priests\HolySpecialization.cpp",
"src\modules\Playerbot\AI\ClassAI\Priests\ShadowSpecialization.h",
"src\modules\Playerbot\AI\ClassAI\Priests\ShadowSpecialization.cpp"
)
$codeMetrics = @{
timestamp = $TIMESTAMP
files = @()
totals = @{
lineCount = 0
fileCount = $priestFiles.Count
headerLines = 0
sourceLines = 0
}
}
foreach ($file in $priestFiles) {
$fullPath = Join-Path $PROJECT_ROOT $file
if (Test-Path $fullPath) {
$content = Get-Content $fullPath -Raw
$lineCount = ($content -split "`n").Count
$fileMetrics = @{
path = $file
lineCount = $lineCount
sizeBytes = (Get-Item $fullPath).Length
type = if ($file -match "\.h$") { "header" } else { "source" }
}
$codeMetrics.files += $fileMetrics
$codeMetrics.totals.lineCount += $lineCount
if ($file -match "\.h$") {
$codeMetrics.totals.headerLines += $lineCount
} else {
$codeMetrics.totals.sourceLines += $lineCount
}
}
}
Write-Host " Total Lines: $($codeMetrics.totals.lineCount)" -ForegroundColor Green
Write-Host " Total Files: $($codeMetrics.totals.fileCount)" -ForegroundColor Green
# =========================================================================
# 2. COMPILATION METRICS - Build Performance
# =========================================================================
Write-Host "`n[2/5] Capturing Compilation Metrics..." -ForegroundColor Yellow
# Clean build of PriestAI only
$compilationMetrics = @{
timestamp = $TIMESTAMP
buildConfig = "Release"
platform = "x64"
measurements = @()
}
# Measure PriestAI.cpp compilation time (3 runs for average)
$compileTimes = @()
for ($i = 1; $i -le 3; $i++) {
Write-Host " Compilation run $i/3..." -ForegroundColor Gray
# Force recompilation by touching the file
(Get-Item "$PROJECT_ROOT\src\modules\Playerbot\AI\ClassAI\Priests\PriestAI.cpp").LastWriteTime = Get-Date
$startTime = Get-Date
$buildOutput = & "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe" `
"$BUILD_DIR\src\server\modules\Playerbot\playerbot.vcxproj" `
-p:Configuration=Release `
-p:Platform=x64 `
-verbosity:quiet `
-nologo `
2>&1
$endTime = Get-Date
$duration = ($endTime - $startTime).TotalSeconds
$compileTimes += $duration
Write-Host " Duration: $([math]::Round($duration, 2))s" -ForegroundColor Gray
}
$compilationMetrics.measurements = @{
compileTimeAvg = [math]::Round(($compileTimes | Measure-Object -Average).Average, 2)
compileTimeMin = [math]::Round(($compileTimes | Measure-Object -Minimum).Minimum, 2)
compileTimeMax = [math]::Round(($compileTimes | Measure-Object -Maximum).Maximum, 2)
runs = 3
}
Write-Host " Average Compile Time: $($compilationMetrics.measurements.compileTimeAvg)s" -ForegroundColor Green
# =========================================================================
# 3. BINARY SIZE METRICS
# =========================================================================
Write-Host "`n[3/5] Capturing Binary Size Metrics..." -ForegroundColor Yellow
$binaryMetrics = @{
timestamp = $TIMESTAMP
}
# Measure playerbot.lib size
$playerbotLib = "$BUILD_DIR\src\server\modules\Playerbot\Release\playerbot.lib"
if (Test-Path $playerbotLib) {
$libSize = (Get-Item $playerbotLib).Length
$binaryMetrics.playerbotLibSize = $libSize
$binaryMetrics.playerbotLibSizeMB = [math]::Round($libSize / 1MB, 2)
Write-Host " playerbot.lib: $($binaryMetrics.playerbotLibSizeMB) MB" -ForegroundColor Green
}
# Measure worldserver.exe size
$worldserverExe = "$BUILD_DIR\src\server\worldserver\Release\worldserver.exe"
if (Test-Path $worldserverExe) {
$exeSize = (Get-Item $worldserverExe).Length
$binaryMetrics.worldserverExeSize = $exeSize
$binaryMetrics.worldserverExeSizeMB = [math]::Round($exeSize / 1MB, 2)
Write-Host " worldserver.exe: $($binaryMetrics.worldserverExeSizeMB) MB" -ForegroundColor Green
}
# =========================================================================
# 4. DEPENDENCY METRICS - Include Analysis
# =========================================================================
Write-Host "`n[4/5] Capturing Dependency Metrics..." -ForegroundColor Yellow
$dependencyMetrics = @{
timestamp = $TIMESTAMP
priestAI = @{
directIncludes = @()
includeCount = 0
}
}
$priestAICpp = "$PROJECT_ROOT\src\modules\Playerbot\AI\ClassAI\Priests\PriestAI.cpp"
if (Test-Path $priestAICpp) {
$includes = Select-String -Path $priestAICpp -Pattern '^\s*#include\s+"([^"]+)"' -AllMatches |
ForEach-Object { $_.Matches.Groups[1].Value }
$dependencyMetrics.priestAI.directIncludes = $includes
$dependencyMetrics.priestAI.includeCount = $includes.Count
Write-Host " Direct Includes: $($includes.Count)" -ForegroundColor Green
}
# =========================================================================
# 5. COMPLEXITY METRICS - Cyclomatic Complexity Estimation
# =========================================================================
Write-Host "`n[5/5] Capturing Complexity Metrics..." -ForegroundColor Yellow
$complexityMetrics = @{
timestamp = $TIMESTAMP
priestAICpp = @{
functionCount = 0
ifStatements = 0
switchStatements = 0
loopStatements = 0
estimatedComplexity = 0
}
}
if (Test-Path $priestAICpp) {
$content = Get-Content $priestAICpp -Raw
# Count control flow structures (rough complexity estimation)
$complexityMetrics.priestAICpp.functionCount = ([regex]::Matches($content, '\w+::\w+\s*\(')).Count
$complexityMetrics.priestAICpp.ifStatements = ([regex]::Matches($content, '\bif\s*\(')).Count
$complexityMetrics.priestAICpp.switchStatements = ([regex]::Matches($content, '\bswitch\s*\(')).Count
$complexityMetrics.priestAICpp.loopStatements = ([regex]::Matches($content, '\b(for|while)\s*\(')).Count
# Estimated cyclomatic complexity: functions + if + switch + loops
$complexityMetrics.priestAICpp.estimatedComplexity =
$complexityMetrics.priestAICpp.functionCount +
$complexityMetrics.priestAICpp.ifStatements +
$complexityMetrics.priestAICpp.switchStatements +
$complexityMetrics.priestAICpp.loopStatements
Write-Host " Functions: $($complexityMetrics.priestAICpp.functionCount)" -ForegroundColor Green
Write-Host " If Statements: $($complexityMetrics.priestAICpp.ifStatements)" -ForegroundColor Green
Write-Host " Switch Statements: $($complexityMetrics.priestAICpp.switchStatements)" -ForegroundColor Green
Write-Host " Loop Statements: $($complexityMetrics.priestAICpp.loopStatements)" -ForegroundColor Green
Write-Host " Estimated Complexity: $($complexityMetrics.priestAICpp.estimatedComplexity)" -ForegroundColor Green
}
# =========================================================================
# 6. COMBINE AND SAVE BASELINE
# =========================================================================
Write-Host "`n[6/6] Saving Baseline Data..." -ForegroundColor Yellow
$baseline = @{
metadata = @{
timestamp = $TIMESTAMP
projectRoot = $PROJECT_ROOT
gitCommit = (git rev-parse HEAD 2>$null)
gitBranch = (git rev-parse --abbrev-ref HEAD 2>$null)
phase = "3A"
week = 1
target = "PriestAI"
status = "BASELINE_BEFORE_REFACTORING"
}
codeMetrics = $codeMetrics
compilationMetrics = $compilationMetrics
binaryMetrics = $binaryMetrics
dependencyMetrics = $dependencyMetrics
complexityMetrics = $complexityMetrics
}
# Save as JSON
$baseline | ConvertTo-Json -Depth 10 | Out-File -FilePath $BASELINE_FILE -Encoding UTF8
Write-Host " Baseline saved to: $BASELINE_FILE" -ForegroundColor Green
# =========================================================================
# 7. GENERATE HUMAN-READABLE REPORT
# =========================================================================
$reportFile = "$OUTPUT_DIR\priest_baseline_$TIMESTAMP.md"
$report = @"
# PRIEST BASELINE METRICS REPORT
**Date**: $TIMESTAMP
**Phase**: 3A Week 1
**Target**: PriestAI God Class Refactoring
**Status**: BASELINE_BEFORE_REFACTORING
---
## Executive Summary
This baseline captures the current state of the PriestAI implementation before Phase 3A refactoring begins.
### Code Metrics
- **Total Lines**: $($codeMetrics.totals.lineCount)
- **Total Files**: $($codeMetrics.totals.fileCount)
- **Header Lines**: $($codeMetrics.totals.headerLines)
- **Source Lines**: $($codeMetrics.totals.sourceLines)
### God Class Metrics (PriestAI.cpp)
- **Line Count**: 3,154 lines
- **Functions**: $($complexityMetrics.priestAICpp.functionCount)
- **If Statements**: $($complexityMetrics.priestAICpp.ifStatements)
- **Switch Statements**: $($complexityMetrics.priestAICpp.switchStatements)
- **Loop Statements**: $($complexityMetrics.priestAICpp.loopStatements)
- **Estimated Complexity**: $($complexityMetrics.priestAICpp.estimatedComplexity)
### Compilation Metrics
- **Average Compile Time**: $($compilationMetrics.measurements.compileTimeAvg)s
- **Min Compile Time**: $($compilationMetrics.measurements.compileTimeMin)s
- **Max Compile Time**: $($compilationMetrics.measurements.compileTimeMax)s
### Binary Size
- **playerbot.lib**: $($binaryMetrics.playerbotLibSizeMB) MB
- **worldserver.exe**: $($binaryMetrics.worldserverExeSizeMB) MB
### Dependencies
- **Direct Includes**: $($dependencyMetrics.priestAI.includeCount)
---
## File Breakdown
| File | Lines | Size (KB) | Type |
|------|-------|-----------|------|
"@
foreach ($file in $codeMetrics.files) {
$sizeKB = [math]::Round($file.sizeBytes / 1KB, 1)
$report += "| $($file.path) | $($file.lineCount) | $sizeKB | $($file.type) |`n"
}
$report += @"
---
## Refactoring Targets
### Phase 3A Goals
1. **PriestAI.cpp**: Reduce from 3,154 lines to ~500 lines (-84%)
2. **Pattern**: Migrate to header-based template specialization
3. **Performance**: Reduce compile time by >30%
4. **Maintainability**: Reduce complexity by >50%
### Expected Outcomes
- Faster compilation (incremental builds)
- Better cache efficiency (smaller translation units)
- Improved testability (isolated specializations)
- Zero runtime overhead (compile-time polymorphism)
---
**Git Commit**: $($baseline.metadata.gitCommit)
**Git Branch**: $($baseline.metadata.gitBranch)
**Baseline File**: $BASELINE_FILE
---
*Generated by Phase 3A Week 1 Baseline Capture Script*
"@
$report | Out-File -FilePath $reportFile -Encoding UTF8
Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host "BASELINE CAPTURE COMPLETE" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "`nBaseline Data: $BASELINE_FILE" -ForegroundColor Green
Write-Host "Report: $reportFile" -ForegroundColor Green
Write-Host "`nNext Steps:" -ForegroundColor Yellow
Write-Host " 1. Review baseline metrics" -ForegroundColor White
Write-Host " 2. Create unit tests for Holy/Shadow specializations" -ForegroundColor White
Write-Host " 3. Begin PriestAI.cpp coordinator transformation" -ForegroundColor White
Write-Host ""
@@ -0,0 +1,149 @@
{
"complexityMetrics": {
"timestamp": "2025-10-17_23-06-34",
"priestAICpp": {
"functionCount": 163,
"ifStatements": 471,
"loopStatements": 30,
"estimatedComplexity": 674,
"switchStatements": 10
}
},
"compilationMetrics": {
"buildConfig": "Release",
"timestamp": "2025-10-17_23-06-34",
"platform": "x64",
"measurements": {
"compileTimeMin": 9.56,
"compileTimeMax": 16.7,
"runs": 3,
"compileTimeAvg": 14.18
}
},
"dependencyMetrics": {
"timestamp": "2025-10-17_23-06-34",
"priestAI": {
"directIncludes": [
"PriestAI.h",
"HolySpecialization.h",
"DisciplineSpecialization.h",
"ShadowSpecialization.h",
"Player.h",
"Unit.h",
"SpellMgr.h",
"SpellInfo.h",
"Map.h",
"Group.h",
"Item.h",
"MotionMaster.h",
"Log.h",
"ObjectAccessor.h",
"WorldSession.h",
"GridNotifiers.h",
"GridNotifiersImpl.h",
"Cell.h",
"CellImpl.h",
"../CooldownManager.h",
"Spell.h",
"../../../Movement/BotMovementUtil.h",
"SpellAuras.h",
"SpellDefines.h",
"../BaselineRotationManager.h",
"../../Combat/CombatBehaviorIntegration.h"
],
"includeCount": 26
}
},
"codeMetrics": {
"timestamp": "2025-10-17_23-06-34",
"files": [
{
"lineCount": 354,
"path": "src\\modules\\Playerbot\\AI\\ClassAI\\Priests\\PriestAI.h",
"type": "header",
"sizeBytes": 11271
},
{
"lineCount": 3155,
"path": "src\\modules\\Playerbot\\AI\\ClassAI\\Priests\\PriestAI.cpp",
"type": "source",
"sizeBytes": 88918
},
{
"lineCount": 201,
"path": "src\\modules\\Playerbot\\AI\\ClassAI\\Priests\\PriestSpecialization.h",
"type": "header",
"sizeBytes": 6072
},
{
"lineCount": 665,
"path": "src\\modules\\Playerbot\\AI\\ClassAI\\Priests\\PriestSpecialization.cpp",
"type": "source",
"sizeBytes": 18804
},
{
"lineCount": 305,
"path": "src\\modules\\Playerbot\\AI\\ClassAI\\Priests\\PriestAI_Specialization.cpp",
"type": "source",
"sizeBytes": 9065
},
{
"lineCount": 164,
"path": "src\\modules\\Playerbot\\AI\\ClassAI\\Priests\\DisciplineSpecialization.h",
"type": "header",
"sizeBytes": 5145
},
{
"lineCount": 875,
"path": "src\\modules\\Playerbot\\AI\\ClassAI\\Priests\\DisciplineSpecialization.cpp",
"type": "source",
"sizeBytes": 22709
},
{
"lineCount": 197,
"path": "src\\modules\\Playerbot\\AI\\ClassAI\\Priests\\HolySpecialization.h",
"type": "header",
"sizeBytes": 6249
},
{
"lineCount": 892,
"path": "src\\modules\\Playerbot\\AI\\ClassAI\\Priests\\HolySpecialization.cpp",
"type": "source",
"sizeBytes": 23385
},
{
"lineCount": 214,
"path": "src\\modules\\Playerbot\\AI\\ClassAI\\Priests\\ShadowSpecialization.h",
"type": "header",
"sizeBytes": 6875
},
{
"lineCount": 942,
"path": "src\\modules\\Playerbot\\AI\\ClassAI\\Priests\\ShadowSpecialization.cpp",
"type": "source",
"sizeBytes": 23777
}
],
"totals": {
"lineCount": 7964,
"sourceLines": 6834,
"fileCount": 11,
"headerLines": 1130
}
},
"metadata": {
"gitCommit": "82510c54c5b7a18454ee0da83a42b1701202dd19",
"timestamp": "2025-10-17_23-06-34",
"week": 1,
"status": "BASELINE_BEFORE_REFACTORING",
"target": "PriestAI",
"gitBranch": "playerbot-dev",
"phase": "3A",
"projectRoot": "C:\\TrinityBots\\TrinityCore"
},
"binaryMetrics": {
"playerbotLibSizeMB": 2561.18,
"timestamp": "2025-10-17_23-06-34",
"playerbotLibSize": 2685588620
}
}
@@ -0,0 +1,80 @@
# PRIEST BASELINE METRICS REPORT
**Date**: 2025-10-17_23-06-34
**Phase**: 3A Week 1
**Target**: PriestAI God Class Refactoring
**Status**: BASELINE_BEFORE_REFACTORING
---
## Executive Summary
This baseline captures the current state of the PriestAI implementation before Phase 3A refactoring begins.
### Code Metrics
- **Total Lines**: 7964
- **Total Files**: 11
- **Header Lines**: 1130
- **Source Lines**: 6834
### God Class Metrics (PriestAI.cpp)
- **Line Count**: 3,154 lines
- **Functions**: 163
- **If Statements**: 471
- **Switch Statements**: 10
- **Loop Statements**: 30
- **Estimated Complexity**: 674
### Compilation Metrics
- **Average Compile Time**: 14.18s
- **Min Compile Time**: 9.56s
- **Max Compile Time**: 16.7s
### Binary Size
- **playerbot.lib**: 2561.18 MB
- **worldserver.exe**: MB
### Dependencies
- **Direct Includes**: 26
---
## File Breakdown
| File | Lines | Size (KB) | Type |
|------|-------|-----------|------|| src\modules\Playerbot\AI\ClassAI\Priests\PriestAI.h | 354 | 11 | header |
| src\modules\Playerbot\AI\ClassAI\Priests\PriestAI.cpp | 3155 | 86.8 | source |
| src\modules\Playerbot\AI\ClassAI\Priests\PriestSpecialization.h | 201 | 5.9 | header |
| src\modules\Playerbot\AI\ClassAI\Priests\PriestSpecialization.cpp | 665 | 18.4 | source |
| src\modules\Playerbot\AI\ClassAI\Priests\PriestAI_Specialization.cpp | 305 | 8.9 | source |
| src\modules\Playerbot\AI\ClassAI\Priests\DisciplineSpecialization.h | 164 | 5 | header |
| src\modules\Playerbot\AI\ClassAI\Priests\DisciplineSpecialization.cpp | 875 | 22.2 | source |
| src\modules\Playerbot\AI\ClassAI\Priests\HolySpecialization.h | 197 | 6.1 | header |
| src\modules\Playerbot\AI\ClassAI\Priests\HolySpecialization.cpp | 892 | 22.8 | source |
| src\modules\Playerbot\AI\ClassAI\Priests\ShadowSpecialization.h | 214 | 6.7 | header |
| src\modules\Playerbot\AI\ClassAI\Priests\ShadowSpecialization.cpp | 942 | 23.2 | source |
---
## Refactoring Targets
### Phase 3A Goals
1. **PriestAI.cpp**: Reduce from 3,154 lines to ~500 lines (-84%)
2. **Pattern**: Migrate to header-based template specialization
3. **Performance**: Reduce compile time by >30%
4. **Maintainability**: Reduce complexity by >50%
### Expected Outcomes
- Faster compilation (incremental builds)
- Better cache efficiency (smaller translation units)
- Improved testability (isolated specializations)
- Zero runtime overhead (compile-time polymorphism)
---
**Git Commit**: 82510c54c5b7a18454ee0da83a42b1701202dd19
**Git Branch**: playerbot-dev
**Baseline File**: C:\TrinityBots\TrinityCore\Tests\Phase3\Baseline\priest_baseline_2025-10-17_23-06-34.json
---
*Generated by Phase 3A Week 1 Baseline Capture Script*
+233
View File
@@ -0,0 +1,233 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tc_catch2.h"
#include "modules/Playerbot/AI/Decision/ActionPriorityQueue.h"
#include "modules/Playerbot/AI/Decision/DecisionFusionSystem.h"
using namespace bot::ai;
// Test spell IDs (placeholder values)
constexpr uint32 SPELL_FIREBALL = 133;
constexpr uint32 SPELL_PYROBLAST = 11366;
constexpr uint32 SPELL_FLAMESTRIKE = 2120;
constexpr uint32 SPELL_ICE_BLOCK = 45438;
TEST_CASE("ActionPriorityQueue - Basic Registration", "[Phase5][ActionPriorityQueue]")
{
ActionPriorityQueue queue;
REQUIRE(queue.GetSpellCount() == 0);
SECTION("Register a single spell")
{
queue.RegisterSpell(SPELL_FIREBALL, SpellPriority::HIGH, SpellCategory::DAMAGE_SINGLE);
REQUIRE(queue.GetSpellCount() == 1);
}
SECTION("Register multiple spells")
{
queue.RegisterSpell(SPELL_FIREBALL, SpellPriority::HIGH, SpellCategory::DAMAGE_SINGLE);
queue.RegisterSpell(SPELL_PYROBLAST, SpellPriority::CRITICAL, SpellCategory::DAMAGE_SINGLE);
queue.RegisterSpell(SPELL_FLAMESTRIKE, SpellPriority::MEDIUM, SpellCategory::DAMAGE_AOE);
REQUIRE(queue.GetSpellCount() == 3);
}
SECTION("Register duplicate spell does not increase count")
{
queue.RegisterSpell(SPELL_FIREBALL, SpellPriority::HIGH, SpellCategory::DAMAGE_SINGLE);
queue.RegisterSpell(SPELL_FIREBALL, SpellPriority::HIGH, SpellCategory::DAMAGE_SINGLE);
REQUIRE(queue.GetSpellCount() == 1);
}
}
TEST_CASE("ActionPriorityQueue - Priority Levels", "[Phase5][ActionPriorityQueue]")
{
SECTION("Priority values are correctly ordered")
{
REQUIRE(static_cast<uint8>(SpellPriority::EMERGENCY) > static_cast<uint8>(SpellPriority::CRITICAL));
REQUIRE(static_cast<uint8>(SpellPriority::CRITICAL) > static_cast<uint8>(SpellPriority::HIGH));
REQUIRE(static_cast<uint8>(SpellPriority::HIGH) > static_cast<uint8>(SpellPriority::MEDIUM));
REQUIRE(static_cast<uint8>(SpellPriority::MEDIUM) > static_cast<uint8>(SpellPriority::LOW));
REQUIRE(static_cast<uint8>(SpellPriority::LOW) > static_cast<uint8>(SpellPriority::OPTIONAL));
}
SECTION("Emergency priority has highest value")
{
REQUIRE(static_cast<uint8>(SpellPriority::EMERGENCY) == 100);
}
}
TEST_CASE("ActionPriorityQueue - Spell Categories", "[Phase5][ActionPriorityQueue]")
{
ActionPriorityQueue queue;
SECTION("Register spells with different categories")
{
queue.RegisterSpell(1001, SpellPriority::HIGH, SpellCategory::DEFENSIVE);
queue.RegisterSpell(1002, SpellPriority::HIGH, SpellCategory::OFFENSIVE);
queue.RegisterSpell(1003, SpellPriority::HIGH, SpellCategory::HEALING);
queue.RegisterSpell(1004, SpellPriority::HIGH, SpellCategory::CROWD_CONTROL);
queue.RegisterSpell(1005, SpellPriority::HIGH, SpellCategory::UTILITY);
queue.RegisterSpell(1006, SpellPriority::HIGH, SpellCategory::DAMAGE_SINGLE);
queue.RegisterSpell(1007, SpellPriority::HIGH, SpellCategory::DAMAGE_AOE);
queue.RegisterSpell(1008, SpellPriority::HIGH, SpellCategory::RESOURCE_BUILDER);
queue.RegisterSpell(1009, SpellPriority::HIGH, SpellCategory::RESOURCE_SPENDER);
queue.RegisterSpell(1010, SpellPriority::HIGH, SpellCategory::MOVEMENT);
REQUIRE(queue.GetSpellCount() == 10);
}
}
TEST_CASE("ActionPriorityQueue - Spell Conditions", "[Phase5][ActionPriorityQueue]")
{
ActionPriorityQueue queue;
queue.RegisterSpell(SPELL_PYROBLAST, SpellPriority::CRITICAL, SpellCategory::DAMAGE_SINGLE);
SECTION("Add condition to spell")
{
bool conditionCalled = false;
queue.AddCondition(SPELL_PYROBLAST,
[&conditionCalled](Player*, Unit*) {
conditionCalled = true;
return true;
},
"Test condition");
// Condition existence is verified - actual execution requires Player/Unit
REQUIRE(queue.GetSpellCount() == 1);
}
SECTION("Add condition to non-existent spell fails gracefully")
{
queue.AddCondition(999999,
[](Player*, Unit*) { return true; },
"Invalid spell");
REQUIRE(queue.GetSpellCount() == 1); // Only PYROBLAST registered
}
}
TEST_CASE("ActionPriorityQueue - Priority Multipliers", "[Phase5][ActionPriorityQueue]")
{
ActionPriorityQueue queue;
queue.RegisterSpell(SPELL_FIREBALL, SpellPriority::HIGH, SpellCategory::DAMAGE_SINGLE);
SECTION("Set priority multiplier")
{
queue.SetPriorityMultiplier(SPELL_FIREBALL, 2.0f);
// Multiplier is set - verification requires GetPrioritizedSpells with bot/target
REQUIRE(queue.GetSpellCount() == 1);
}
SECTION("Set multiplier on non-existent spell fails gracefully")
{
queue.SetPriorityMultiplier(999999, 2.0f);
REQUIRE(queue.GetSpellCount() == 1);
}
}
TEST_CASE("ActionPriorityQueue - Clear Functionality", "[Phase5][ActionPriorityQueue]")
{
ActionPriorityQueue queue;
queue.RegisterSpell(SPELL_FIREBALL, SpellPriority::HIGH, SpellCategory::DAMAGE_SINGLE);
queue.RegisterSpell(SPELL_PYROBLAST, SpellPriority::CRITICAL, SpellCategory::DAMAGE_SINGLE);
REQUIRE(queue.GetSpellCount() == 2);
queue.Clear();
REQUIRE(queue.GetSpellCount() == 0);
}
TEST_CASE("ActionPriorityQueue - DecisionVote Generation", "[Phase5][ActionPriorityQueue]")
{
// Note: Full DecisionVote testing requires Player* and Unit* which are not available in unit tests
// These tests verify the interface and basic functionality
SECTION("DecisionVote has correct source")
{
ActionPriorityQueue queue;
queue.RegisterSpell(SPELL_FIREBALL, SpellPriority::HIGH, SpellCategory::DAMAGE_SINGLE);
// DecisionVote generation requires bot/target which aren't available in unit tests
// Verified: Interface exists and compiles
REQUIRE(queue.GetSpellCount() == 1);
}
}
TEST_CASE("ActionPriorityQueue - Context Awareness", "[Phase5][ActionPriorityQueue]")
{
SECTION("Combat contexts are defined")
{
// Verify all combat contexts exist
CombatContext contexts[] = {
CombatContext::SOLO,
CombatContext::GROUP,
CombatContext::DUNGEON_TRASH,
CombatContext::DUNGEON_BOSS,
CombatContext::RAID_NORMAL,
CombatContext::RAID_HEROIC,
CombatContext::PVP_ARENA,
CombatContext::PVP_BG
};
REQUIRE(sizeof(contexts) / sizeof(contexts[0]) == 8);
}
}
TEST_CASE("ActionPriorityQueue - Debug Logging", "[Phase5][ActionPriorityQueue]")
{
ActionPriorityQueue queue;
SECTION("Enable debug logging")
{
queue.EnableDebugLogging(true);
queue.RegisterSpell(SPELL_FIREBALL, SpellPriority::HIGH, SpellCategory::DAMAGE_SINGLE);
REQUIRE(queue.GetSpellCount() == 1);
}
SECTION("Disable debug logging")
{
queue.EnableDebugLogging(false);
queue.RegisterSpell(SPELL_PYROBLAST, SpellPriority::CRITICAL, SpellCategory::DAMAGE_SINGLE);
REQUIRE(queue.GetSpellCount() == 1);
}
}
TEST_CASE("ActionPriorityQueue - Record Cast Functionality", "[Phase5][ActionPriorityQueue]")
{
ActionPriorityQueue queue;
queue.RegisterSpell(SPELL_FIREBALL, SpellPriority::HIGH, SpellCategory::DAMAGE_SINGLE);
SECTION("Record spell cast")
{
queue.RecordCast(SPELL_FIREBALL);
// Cast time is recorded internally
REQUIRE(queue.GetSpellCount() == 1);
}
SECTION("Record cast of non-registered spell fails gracefully")
{
queue.RecordCast(999999);
REQUIRE(queue.GetSpellCount() == 1);
}
}
+450
View File
@@ -0,0 +1,450 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tc_catch2.h"
#include "modules/Playerbot/AI/Decision/BehaviorTree.h"
using namespace bot::ai;
TEST_CASE("BehaviorTree - NodeStatus Values", "[Phase5][BehaviorTree]")
{
SECTION("NodeStatus enum values exist")
{
NodeStatus success = NodeStatus::SUCCESS;
NodeStatus failure = NodeStatus::FAILURE;
NodeStatus running = NodeStatus::RUNNING;
REQUIRE(success != failure);
REQUIRE(success != running);
REQUIRE(failure != running);
}
}
TEST_CASE("BehaviorTree - NodeType Values", "[Phase5][BehaviorTree]")
{
SECTION("NodeType enum values exist")
{
NodeType composite = NodeType::COMPOSITE;
NodeType decorator = NodeType::DECORATOR;
NodeType leaf = NodeType::LEAF;
REQUIRE(composite != decorator);
REQUIRE(composite != leaf);
REQUIRE(decorator != leaf);
}
}
TEST_CASE("BehaviorTree - ConditionNode", "[Phase5][BehaviorTree]")
{
SECTION("Condition returns SUCCESS when true")
{
auto condition = std::make_shared<ConditionNode>("AlwaysTrue",
[](Player*, Unit*) { return true; });
NodeStatus status = condition->Tick(nullptr, nullptr);
REQUIRE(status == NodeStatus::SUCCESS);
}
SECTION("Condition returns FAILURE when false")
{
auto condition = std::make_shared<ConditionNode>("AlwaysFalse",
[](Player*, Unit*) { return false; });
NodeStatus status = condition->Tick(nullptr, nullptr);
REQUIRE(status == NodeStatus::FAILURE);
}
SECTION("Condition can access parameters")
{
int callCount = 0;
auto condition = std::make_shared<ConditionNode>("Counter",
[&callCount](Player*, Unit*) {
callCount++;
return true;
});
condition->Tick(nullptr, nullptr);
REQUIRE(callCount == 1);
condition->Tick(nullptr, nullptr);
REQUIRE(callCount == 2);
}
}
TEST_CASE("BehaviorTree - ActionNode", "[Phase5][BehaviorTree]")
{
SECTION("Action returns SUCCESS")
{
auto action = std::make_shared<ActionNode>("SuccessAction",
[](Player*, Unit*) { return NodeStatus::SUCCESS; });
NodeStatus status = action->Tick(nullptr, nullptr);
REQUIRE(status == NodeStatus::SUCCESS);
}
SECTION("Action returns FAILURE")
{
auto action = std::make_shared<ActionNode>("FailureAction",
[](Player*, Unit*) { return NodeStatus::FAILURE; });
NodeStatus status = action->Tick(nullptr, nullptr);
REQUIRE(status == NodeStatus::FAILURE);
}
SECTION("Action returns RUNNING")
{
auto action = std::make_shared<ActionNode>("RunningAction",
[](Player*, Unit*) { return NodeStatus::RUNNING; });
NodeStatus status = action->Tick(nullptr, nullptr);
REQUIRE(status == NodeStatus::RUNNING);
}
SECTION("Action executes custom logic")
{
int executionCount = 0;
auto action = std::make_shared<ActionNode>("CustomAction",
[&executionCount](Player*, Unit*) {
executionCount++;
return NodeStatus::SUCCESS;
});
action->Tick(nullptr, nullptr);
REQUIRE(executionCount == 1);
action->Tick(nullptr, nullptr);
REQUIRE(executionCount == 2);
}
}
TEST_CASE("BehaviorTree - SequenceNode", "[Phase5][BehaviorTree]")
{
SECTION("Empty sequence returns SUCCESS")
{
auto sequence = std::make_shared<SequenceNode>("EmptySequence");
NodeStatus status = sequence->Tick(nullptr, nullptr);
REQUIRE(status == NodeStatus::SUCCESS);
}
SECTION("Sequence with all SUCCESS children returns SUCCESS")
{
auto sequence = std::make_shared<SequenceNode>("AllSuccess");
sequence->AddChild(std::make_shared<ConditionNode>("True1", [](Player*, Unit*) { return true; }));
sequence->AddChild(std::make_shared<ConditionNode>("True2", [](Player*, Unit*) { return true; }));
sequence->AddChild(std::make_shared<ConditionNode>("True3", [](Player*, Unit*) { return true; }));
NodeStatus status = sequence->Tick(nullptr, nullptr);
REQUIRE(status == NodeStatus::SUCCESS);
}
SECTION("Sequence stops at first FAILURE")
{
int firstExecuted = 0;
int secondExecuted = 0;
int thirdExecuted = 0;
auto sequence = std::make_shared<SequenceNode>("FailSequence");
sequence->AddChild(std::make_shared<ActionNode>("First",
[&firstExecuted](Player*, Unit*) {
firstExecuted++;
return NodeStatus::SUCCESS;
}));
sequence->AddChild(std::make_shared<ActionNode>("Second",
[&secondExecuted](Player*, Unit*) {
secondExecuted++;
return NodeStatus::FAILURE;
}));
sequence->AddChild(std::make_shared<ActionNode>("Third",
[&thirdExecuted](Player*, Unit*) {
thirdExecuted++;
return NodeStatus::SUCCESS;
}));
NodeStatus status = sequence->Tick(nullptr, nullptr);
REQUIRE(status == NodeStatus::FAILURE);
REQUIRE(firstExecuted == 1);
REQUIRE(secondExecuted == 1);
REQUIRE(thirdExecuted == 0); // Should not execute after failure
}
SECTION("Sequence returns RUNNING when child is RUNNING")
{
auto sequence = std::make_shared<SequenceNode>("RunningSequence");
sequence->AddChild(std::make_shared<ConditionNode>("True", [](Player*, Unit*) { return true; }));
sequence->AddChild(std::make_shared<ActionNode>("Running",
[](Player*, Unit*) { return NodeStatus::RUNNING; }));
sequence->AddChild(std::make_shared<ConditionNode>("NeverReached", [](Player*, Unit*) { return true; }));
NodeStatus status = sequence->Tick(nullptr, nullptr);
REQUIRE(status == NodeStatus::RUNNING);
}
}
TEST_CASE("BehaviorTree - SelectorNode", "[Phase5][BehaviorTree]")
{
SECTION("Empty selector returns FAILURE")
{
auto selector = std::make_shared<SelectorNode>("EmptySelector");
NodeStatus status = selector->Tick(nullptr, nullptr);
REQUIRE(status == NodeStatus::FAILURE);
}
SECTION("Selector returns SUCCESS at first SUCCESS child")
{
int firstExecuted = 0;
int secondExecuted = 0;
int thirdExecuted = 0;
auto selector = std::make_shared<SelectorNode>("SuccessSelector");
selector->AddChild(std::make_shared<ActionNode>("First",
[&firstExecuted](Player*, Unit*) {
firstExecuted++;
return NodeStatus::FAILURE;
}));
selector->AddChild(std::make_shared<ActionNode>("Second",
[&secondExecuted](Player*, Unit*) {
secondExecuted++;
return NodeStatus::SUCCESS;
}));
selector->AddChild(std::make_shared<ActionNode>("Third",
[&thirdExecuted](Player*, Unit*) {
thirdExecuted++;
return NodeStatus::SUCCESS;
}));
NodeStatus status = selector->Tick(nullptr, nullptr);
REQUIRE(status == NodeStatus::SUCCESS);
REQUIRE(firstExecuted == 1);
REQUIRE(secondExecuted == 1);
REQUIRE(thirdExecuted == 0); // Should not execute after success
}
SECTION("Selector returns FAILURE when all children FAIL")
{
auto selector = std::make_shared<SelectorNode>("AllFail");
selector->AddChild(std::make_shared<ConditionNode>("False1", [](Player*, Unit*) { return false; }));
selector->AddChild(std::make_shared<ConditionNode>("False2", [](Player*, Unit*) { return false; }));
selector->AddChild(std::make_shared<ConditionNode>("False3", [](Player*, Unit*) { return false; }));
NodeStatus status = selector->Tick(nullptr, nullptr);
REQUIRE(status == NodeStatus::FAILURE);
}
SECTION("Selector returns RUNNING when child is RUNNING")
{
auto selector = std::make_shared<SelectorNode>("RunningSelector");
selector->AddChild(std::make_shared<ConditionNode>("False", [](Player*, Unit*) { return false; }));
selector->AddChild(std::make_shared<ActionNode>("Running",
[](Player*, Unit*) { return NodeStatus::RUNNING; }));
selector->AddChild(std::make_shared<ConditionNode>("NeverReached", [](Player*, Unit*) { return true; }));
NodeStatus status = selector->Tick(nullptr, nullptr);
REQUIRE(status == NodeStatus::RUNNING);
}
}
TEST_CASE("BehaviorTree - InverterNode", "[Phase5][BehaviorTree]")
{
SECTION("Inverter converts SUCCESS to FAILURE")
{
auto condition = std::make_shared<ConditionNode>("True", [](Player*, Unit*) { return true; });
auto inverter = std::make_shared<InverterNode>("Inverter", condition);
NodeStatus status = inverter->Tick(nullptr, nullptr);
REQUIRE(status == NodeStatus::FAILURE);
}
SECTION("Inverter converts FAILURE to SUCCESS")
{
auto condition = std::make_shared<ConditionNode>("False", [](Player*, Unit*) { return false; });
auto inverter = std::make_shared<InverterNode>("Inverter", condition);
NodeStatus status = inverter->Tick(nullptr, nullptr);
REQUIRE(status == NodeStatus::SUCCESS);
}
SECTION("Inverter does not affect RUNNING")
{
auto action = std::make_shared<ActionNode>("Running",
[](Player*, Unit*) { return NodeStatus::RUNNING; });
auto inverter = std::make_shared<InverterNode>("Inverter", action);
NodeStatus status = inverter->Tick(nullptr, nullptr);
REQUIRE(status == NodeStatus::RUNNING);
}
}
TEST_CASE("BehaviorTree - RepeaterNode", "[Phase5][BehaviorTree]")
{
SECTION("Repeater with max repeats 0 runs indefinitely")
{
int executionCount = 0;
auto action = std::make_shared<ActionNode>("Increment",
[&executionCount](Player*, Unit*) {
executionCount++;
return NodeStatus::SUCCESS;
});
auto repeater = std::make_shared<RepeaterNode>("InfiniteRepeater", action, 0);
// First tick should return RUNNING
NodeStatus status = repeater->Tick(nullptr, nullptr);
REQUIRE(status == NodeStatus::RUNNING);
REQUIRE(executionCount == 1);
}
SECTION("Repeater executes N times")
{
int executionCount = 0;
auto action = std::make_shared<ActionNode>("Increment",
[&executionCount](Player*, Unit*) {
executionCount++;
return NodeStatus::SUCCESS;
});
auto repeater = std::make_shared<RepeaterNode>("ThreeRepeater", action, 3);
// Execute 3 times
for (int i = 0; i < 3; ++i)
{
NodeStatus status = repeater->Tick(nullptr, nullptr);
if (i < 2)
REQUIRE(status == NodeStatus::RUNNING);
else
REQUIRE(status == NodeStatus::SUCCESS);
}
REQUIRE(executionCount == 3);
}
SECTION("Repeater stops on FAILURE")
{
int executionCount = 0;
auto action = std::make_shared<ActionNode>("FailAfterTwo",
[&executionCount](Player*, Unit*) {
executionCount++;
return (executionCount < 2) ? NodeStatus::SUCCESS : NodeStatus::FAILURE;
});
auto repeater = std::make_shared<RepeaterNode>("RepeatUntilFail", action, 5);
repeater->Tick(nullptr, nullptr); // Count = 1, returns RUNNING
NodeStatus status = repeater->Tick(nullptr, nullptr); // Count = 2, returns FAILURE
REQUIRE(status == NodeStatus::FAILURE);
REQUIRE(executionCount == 2);
}
}
TEST_CASE("BehaviorTree - Complex Tree Structures", "[Phase5][BehaviorTree]")
{
SECTION("Nested sequence in selector")
{
// Selector {
// Sequence { false, true } -> FAILURE
// Sequence { true, true } -> SUCCESS
// }
// Expected: SUCCESS
auto selector = std::make_shared<SelectorNode>("Root");
auto failSeq = std::make_shared<SequenceNode>("FailSeq");
failSeq->AddChild(std::make_shared<ConditionNode>("False", [](Player*, Unit*) { return false; }));
failSeq->AddChild(std::make_shared<ConditionNode>("True", [](Player*, Unit*) { return true; }));
auto successSeq = std::make_shared<SequenceNode>("SuccessSeq");
successSeq->AddChild(std::make_shared<ConditionNode>("True1", [](Player*, Unit*) { return true; }));
successSeq->AddChild(std::make_shared<ConditionNode>("True2", [](Player*, Unit*) { return true; }));
selector->AddChild(failSeq);
selector->AddChild(successSeq);
NodeStatus status = selector->Tick(nullptr, nullptr);
REQUIRE(status == NodeStatus::SUCCESS);
}
}
TEST_CASE("BehaviorTree - Tree Reset Functionality", "[Phase5][BehaviorTree]")
{
SECTION("Tree reset works correctly")
{
auto tree = std::make_shared<BehaviorTree>("TestTree");
auto action = std::make_shared<ActionNode>("Action",
[](Player*, Unit*) { return NodeStatus::SUCCESS; });
tree->SetRoot(action);
// Execute tree
NodeStatus status1 = tree->Tick(nullptr, nullptr);
REQUIRE(status1 == NodeStatus::SUCCESS);
// Reset tree
tree->Reset();
// Execute again after reset
NodeStatus status2 = tree->Tick(nullptr, nullptr);
REQUIRE(status2 == NodeStatus::SUCCESS);
}
}
TEST_CASE("BehaviorTree - Tree Name and Status", "[Phase5][BehaviorTree]")
{
auto tree = std::make_shared<BehaviorTree>("MyTree");
SECTION("Tree has correct name")
{
REQUIRE(tree->GetName() == "MyTree");
}
SECTION("Tree status tracking")
{
auto runningAction = std::make_shared<ActionNode>("Running",
[](Player*, Unit*) { return NodeStatus::RUNNING; });
tree->SetRoot(runningAction);
tree->Tick(nullptr, nullptr);
REQUIRE(tree->IsRunning() == true);
REQUIRE(tree->GetLastStatus() == NodeStatus::RUNNING);
}
}
TEST_CASE("BehaviorTree - Debug Logging", "[Phase5][BehaviorTree]")
{
auto tree = std::make_shared<BehaviorTree>("DebugTree");
SECTION("Enable debug logging")
{
tree->EnableDebugLogging(true);
auto action = std::make_shared<ActionNode>("Action",
[](Player*, Unit*) { return NodeStatus::SUCCESS; });
tree->SetRoot(action);
tree->Tick(nullptr, nullptr);
// Logging enabled - verified by compilation
REQUIRE(true);
}
SECTION("Disable debug logging")
{
tree->EnableDebugLogging(false);
auto action = std::make_shared<ActionNode>("Action",
[](Player*, Unit*) { return NodeStatus::SUCCESS; });
tree->SetRoot(action);
tree->Tick(nullptr, nullptr);
// Logging disabled - verified by compilation
REQUIRE(true);
}
}
+448
View File
@@ -0,0 +1,448 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tc_catch2.h"
#include "modules/Playerbot/AI/Decision/DecisionFusionSystem.h"
using namespace bot::ai;
TEST_CASE("DecisionFusion - DecisionVote Weighted Score", "[Phase5][DecisionFusion]")
{
SECTION("Weighted score calculation")
{
DecisionVote vote(
DecisionSource::BEHAVIOR_PRIORITY,
12345, // actionId
nullptr, // target
0.8f, // confidence
0.6f, // urgency
"Test vote"
);
float systemWeight = 1.0f;
float weightedScore = vote.CalculateWeightedScore(systemWeight);
// Score = confidence × urgency × systemWeight
// 0.8 × 0.6 × 1.0 = 0.48
REQUIRE(weightedScore == Approx(0.48f));
}
SECTION("Weighted score with different system weight")
{
DecisionVote vote(
DecisionSource::ACTION_PRIORITY,
12345,
nullptr,
1.0f, // confidence
1.0f, // urgency
"Maximum vote"
);
float systemWeight = 0.5f;
float weightedScore = vote.CalculateWeightedScore(systemWeight);
// 1.0 × 1.0 × 0.5 = 0.5
REQUIRE(weightedScore == Approx(0.5f));
}
SECTION("Zero confidence gives zero score")
{
DecisionVote vote(
DecisionSource::BEHAVIOR_TREE,
12345,
nullptr,
0.0f, // confidence
1.0f, // urgency
"Zero confidence"
);
float weightedScore = vote.CalculateWeightedScore(1.0f);
REQUIRE(weightedScore == Approx(0.0f));
}
SECTION("Zero urgency gives zero score")
{
DecisionVote vote(
DecisionSource::ADAPTIVE_BEHAVIOR,
12345,
nullptr,
1.0f, // confidence
0.0f, // urgency
"Zero urgency"
);
float weightedScore = vote.CalculateWeightedScore(1.0f);
REQUIRE(weightedScore == Approx(0.0f));
}
}
TEST_CASE("DecisionFusion - Vote Fusion Logic", "[Phase5][DecisionFusion]")
{
DecisionFusionSystem fusion;
SECTION("Single vote returns that action")
{
std::vector<DecisionVote> votes;
votes.emplace_back(
DecisionSource::BEHAVIOR_PRIORITY,
12345,
nullptr,
0.8f,
0.7f,
"Only vote"
);
DecisionResult result = fusion.FuseDecisions(votes);
REQUIRE(result.recommendedAction == 12345);
REQUIRE(result.confidence > 0.0f);
}
SECTION("Multiple votes for same action increase confidence")
{
std::vector<DecisionVote> votes;
votes.emplace_back(
DecisionSource::BEHAVIOR_PRIORITY,
12345,
nullptr,
0.7f,
0.6f,
"Vote 1"
);
votes.emplace_back(
DecisionSource::ACTION_PRIORITY,
12345,
nullptr,
0.8f,
0.7f,
"Vote 2"
);
DecisionResult result = fusion.FuseDecisions(votes);
REQUIRE(result.recommendedAction == 12345);
REQUIRE(result.totalVotes == 2);
}
SECTION("Empty vote list returns no action")
{
std::vector<DecisionVote> votes;
DecisionResult result = fusion.FuseDecisions(votes);
REQUIRE(result.recommendedAction == 0);
REQUIRE(result.confidence == 0.0f);
}
SECTION("High urgency vote wins over low urgency")
{
std::vector<DecisionVote> votes;
// Low urgency vote
votes.emplace_back(
DecisionSource::BEHAVIOR_PRIORITY,
11111,
nullptr,
0.9f, // high confidence
0.3f, // low urgency
"Low urgency"
);
// High urgency vote (should win)
votes.emplace_back(
DecisionSource::ACTION_PRIORITY,
22222,
nullptr,
0.7f, // moderate confidence
0.95f, // very high urgency
"High urgency"
);
DecisionResult result = fusion.FuseDecisions(votes);
// High urgency threshold should prioritize the second vote
// Actual behavior depends on urgency threshold config
REQUIRE(result.recommendedAction != 0);
REQUIRE(result.totalVotes == 2);
}
}
TEST_CASE("DecisionFusion - System Weights", "[Phase5][DecisionFusion]")
{
DecisionFusionSystem fusion;
SECTION("Set custom weights")
{
std::array<float, 5> customWeights = {
1.0f, // BEHAVIOR_PRIORITY
0.8f, // ACTION_PRIORITY
0.6f, // BEHAVIOR_TREE
0.4f, // ADAPTIVE_BEHAVIOR
0.5f // WEIGHTING_SYSTEM
};
fusion.SetSystemWeights(customWeights);
// Weights are set - verified through compilation
REQUIRE(true);
}
SECTION("Reset to default weights")
{
fusion.ResetToDefaultWeights();
// Weights reset - verified through compilation
REQUIRE(true);
}
}
TEST_CASE("DecisionFusion - Debug Logging", "[Phase5][DecisionFusion]")
{
DecisionFusionSystem fusion;
SECTION("Enable debug logging")
{
fusion.EnableDebugLogging(true);
std::vector<DecisionVote> votes;
votes.emplace_back(
DecisionSource::BEHAVIOR_PRIORITY,
12345,
nullptr,
0.8f,
0.7f,
"Test vote"
);
fusion.FuseDecisions(votes);
// Debug logging enabled - verified through compilation
REQUIRE(true);
}
SECTION("Disable debug logging")
{
fusion.EnableDebugLogging(false);
std::vector<DecisionVote> votes;
votes.emplace_back(
DecisionSource::BEHAVIOR_PRIORITY,
12345,
nullptr,
0.8f,
0.7f,
"Test vote"
);
fusion.FuseDecisions(votes);
// Debug logging disabled - verified through compilation
REQUIRE(true);
}
}
TEST_CASE("DecisionFusion - Statistics Tracking", "[Phase5][DecisionFusion]")
{
DecisionFusionSystem fusion;
SECTION("Statistics are updated after fusion")
{
std::vector<DecisionVote> votes;
votes.emplace_back(
DecisionSource::BEHAVIOR_PRIORITY,
12345,
nullptr,
0.8f,
0.7f,
"Vote 1"
);
votes.emplace_back(
DecisionSource::ACTION_PRIORITY,
12345,
nullptr,
0.7f,
0.6f,
"Vote 2"
);
DecisionResult result1 = fusion.FuseDecisions(votes);
DecisionResult result2 = fusion.FuseDecisions(votes);
DecisionStatistics stats = fusion.GetStatistics();
// At least 2 decisions have been made
REQUIRE(stats.totalDecisions >= 2);
}
SECTION("Reset statistics")
{
std::vector<DecisionVote> votes;
votes.emplace_back(
DecisionSource::BEHAVIOR_PRIORITY,
12345,
nullptr,
0.8f,
0.7f,
"Vote"
);
fusion.FuseDecisions(votes);
fusion.ResetStatistics();
DecisionStatistics stats = fusion.GetStatistics();
REQUIRE(stats.totalDecisions == 0);
}
}
TEST_CASE("DecisionFusion - DecisionSource Enumeration", "[Phase5][DecisionFusion]")
{
SECTION("All decision sources are defined")
{
DecisionSource sources[] = {
DecisionSource::BEHAVIOR_PRIORITY,
DecisionSource::ACTION_PRIORITY,
DecisionSource::BEHAVIOR_TREE,
DecisionSource::ADAPTIVE_BEHAVIOR,
DecisionSource::WEIGHTING_SYSTEM
};
REQUIRE(sizeof(sources) / sizeof(sources[0]) == 5);
}
SECTION("DecisionSource::MAX is last value")
{
// MAX should be the sentinel value
REQUIRE(static_cast<uint8>(DecisionSource::MAX) == 5);
}
}
TEST_CASE("DecisionFusion - Urgency Threshold", "[Phase5][DecisionFusion]")
{
DecisionFusionSystem fusion;
SECTION("Set urgency threshold")
{
fusion.SetUrgencyThreshold(0.9f);
// Threshold is set - verified through compilation
REQUIRE(true);
}
SECTION("Get urgency threshold")
{
fusion.SetUrgencyThreshold(0.75f);
float threshold = fusion.GetUrgencyThreshold();
REQUIRE(threshold == Approx(0.75f));
}
}
TEST_CASE("DecisionFusion - DecisionResult Structure", "[Phase5][DecisionFusion]")
{
SECTION("DecisionResult has expected fields")
{
DecisionResult result;
result.recommendedAction = 12345;
result.target = nullptr;
result.confidence = 0.85f;
result.totalVotes = 3;
result.winningSource = DecisionSource::BEHAVIOR_PRIORITY;
result.reasoning = "Test reasoning";
REQUIRE(result.recommendedAction == 12345);
REQUIRE(result.confidence == Approx(0.85f));
REQUIRE(result.totalVotes == 3);
REQUIRE(result.winningSource == DecisionSource::BEHAVIOR_PRIORITY);
REQUIRE(result.reasoning == "Test reasoning");
}
}
TEST_CASE("DecisionFusion - Context-Based Fusion", "[Phase5][DecisionFusion]")
{
SECTION("Combat contexts are defined")
{
CombatContext contexts[] = {
CombatContext::SOLO,
CombatContext::GROUP,
CombatContext::DUNGEON_TRASH,
CombatContext::DUNGEON_BOSS,
CombatContext::RAID_NORMAL,
CombatContext::RAID_HEROIC,
CombatContext::RAID_MYTHIC,
CombatContext::PVP_ARENA,
CombatContext::PVP_BG
};
REQUIRE(sizeof(contexts) / sizeof(contexts[0]) == 9);
}
}
TEST_CASE("DecisionFusion - Unanimous Votes", "[Phase5][DecisionFusion]")
{
DecisionFusionSystem fusion;
SECTION("All votes for same action are unanimous")
{
std::vector<DecisionVote> votes;
votes.emplace_back(DecisionSource::BEHAVIOR_PRIORITY, 12345, nullptr, 0.8f, 0.7f, "Vote 1");
votes.emplace_back(DecisionSource::ACTION_PRIORITY, 12345, nullptr, 0.9f, 0.8f, "Vote 2");
votes.emplace_back(DecisionSource::BEHAVIOR_TREE, 12345, nullptr, 0.7f, 0.6f, "Vote 3");
DecisionResult result = fusion.FuseDecisions(votes);
// All votes for 12345, should be high confidence
REQUIRE(result.recommendedAction == 12345);
REQUIRE(result.totalVotes == 3);
}
SECTION("Mixed votes are not unanimous")
{
std::vector<DecisionVote> votes;
votes.emplace_back(DecisionSource::BEHAVIOR_PRIORITY, 11111, nullptr, 0.8f, 0.7f, "Vote 1");
votes.emplace_back(DecisionSource::ACTION_PRIORITY, 22222, nullptr, 0.9f, 0.8f, "Vote 2");
votes.emplace_back(DecisionSource::BEHAVIOR_TREE, 33333, nullptr, 0.7f, 0.6f, "Vote 3");
DecisionResult result = fusion.FuseDecisions(votes);
// Different actions, winner determined by weighted scores
REQUIRE(result.recommendedAction != 0);
REQUIRE(result.totalVotes == 3);
}
}
TEST_CASE("DecisionFusion - Edge Cases", "[Phase5][DecisionFusion]")
{
DecisionFusionSystem fusion;
SECTION("All votes with zero confidence")
{
std::vector<DecisionVote> votes;
votes.emplace_back(DecisionSource::BEHAVIOR_PRIORITY, 12345, nullptr, 0.0f, 0.5f, "Zero conf 1");
votes.emplace_back(DecisionSource::ACTION_PRIORITY, 22222, nullptr, 0.0f, 0.6f, "Zero conf 2");
DecisionResult result = fusion.FuseDecisions(votes);
// With zero confidence, no clear winner
REQUIRE(result.totalVotes == 2);
}
SECTION("All votes with zero urgency")
{
std::vector<DecisionVote> votes;
votes.emplace_back(DecisionSource::BEHAVIOR_PRIORITY, 12345, nullptr, 0.8f, 0.0f, "Zero urg 1");
votes.emplace_back(DecisionSource::ACTION_PRIORITY, 22222, nullptr, 0.9f, 0.0f, "Zero urg 2");
DecisionResult result = fusion.FuseDecisions(votes);
// With zero urgency, weighted scores are all 0
REQUIRE(result.totalVotes == 2);
}
}
+118
View File
@@ -0,0 +1,118 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
#include "tc_catch2.h"
#include "Config.h"
#include <boost/filesystem.hpp>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <map>
#include <string>
std::string CreateConfigWithMap(std::map<std::string, std::string> const& map)
{
auto mTempFileRel = boost::filesystem::unique_path("deleteme.ini");
auto mTempFileAbs = boost::filesystem::temp_directory_path() / mTempFileRel;
std::ofstream iniStream;
iniStream.open(mTempFileAbs.c_str());
iniStream << "[test]\n";
for (auto const& itr : map)
iniStream << itr.first << " = " << itr.second << "\n";
iniStream.close();
return mTempFileAbs.string();
}
TEST_CASE("Environment variables", "[Config]")
{
std::map<std::string, std::string> config;
config["Int.Nested"] = "4242";
config["lower"] = "simpleString";
config["UPPER"] = "simpleString";
config["SomeLong.NestedNameWithNumber.Like1"] = "1";
auto filePath = CreateConfigWithMap(config);
std::string err;
REQUIRE(sConfigMgr->LoadInitial(filePath, std::vector<std::string>(), err));
REQUIRE(err.empty());
SECTION("Nested int")
{
REQUIRE(sConfigMgr->GetIntDefault("Int.Nested", 10) == 4242);
putenv(strdup("TC_INT_NESTED=8080"));
REQUIRE(!sConfigMgr->OverrideWithEnvVariablesIfAny().empty());
REQUIRE(sConfigMgr->GetIntDefault("Int.Nested", 10) == 8080);
}
SECTION("Simple lower string")
{
REQUIRE(sConfigMgr->GetStringDefault("lower", "") == "simpleString");
putenv(strdup("TC_LOWER=envstring"));
REQUIRE(!sConfigMgr->OverrideWithEnvVariablesIfAny().empty());
REQUIRE(sConfigMgr->GetStringDefault("lower", "") == "envstring");
}
SECTION("Simple upper string")
{
REQUIRE(sConfigMgr->GetStringDefault("UPPER", "") == "simpleString");
putenv(strdup("TC_UPPER=envupperstring"));
REQUIRE(!sConfigMgr->OverrideWithEnvVariablesIfAny().empty());
REQUIRE(sConfigMgr->GetStringDefault("UPPER", "") == "envupperstring");
}
SECTION("Long nested name with number")
{
REQUIRE(sConfigMgr->GetFloatDefault("SomeLong.NestedNameWithNumber.Like1", 0) == 1.0f);
putenv(strdup("TC_SOME_LONG_NESTED_NAME_WITH_NUMBER_LIKE_1=42"));
REQUIRE(!sConfigMgr->OverrideWithEnvVariablesIfAny().empty());
REQUIRE(sConfigMgr->GetFloatDefault("SomeLong.NestedNameWithNumber.Like1", 0) == 42.0f);
}
SECTION("String that not exist in config")
{
putenv(strdup("TC_UNIQUE_STRING=somevalue"));
REQUIRE(sConfigMgr->GetStringDefault("Unique.String", "") == "somevalue");
}
SECTION("Int that not exist in config")
{
putenv(strdup("TC_UNIQUE_INT=100"));
REQUIRE(sConfigMgr->GetIntDefault("Unique.Int", 1) == 100);
}
SECTION("Not existing string")
{
REQUIRE(sConfigMgr->GetStringDefault("NotFound.String", "none") == "none");
}
SECTION("Not existing int")
{
REQUIRE(sConfigMgr->GetIntDefault("NotFound.Int", 1) == 1);
}
std::remove(filePath.c_str());
}
+386
View File
@@ -0,0 +1,386 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
#include "tc_catch2.h"
#include "EventMap.h"
enum EVENTS
{
EVENT_1 = 1,
EVENT_2 = 2,
EVENT_3 = 3
};
enum PHASES
{
PHASE_1 = 1,
PHASE_2 = 2
};
enum GROUPS
{
GROUP_1 = 1,
GROUP_2 = 2
};
TEST_CASE("Schedule an event", "[EventMap]")
{
EventMap eventMap;
REQUIRE(eventMap.Empty());
eventMap.ScheduleEvent(EVENT_1, 1s);
REQUIRE_FALSE(eventMap.Empty());
SECTION("Event has not yet reached its delay")
{
eventMap.Update(100);
uint32 id = eventMap.ExecuteEvent();
REQUIRE(id == 0);
REQUIRE_FALSE(eventMap.Empty());
REQUIRE(eventMap.GetTimeUntilEvent(EVENT_1) == 900ms);
}
SECTION("Event has reached its delay")
{
eventMap.Update(1000);
uint32 id = eventMap.ExecuteEvent();
REQUIRE(id == EVENT_1);
REQUIRE(eventMap.Empty());
REQUIRE(eventMap.GetTimeUntilEvent(EVENT_1) == Milliseconds::max());
}
SECTION("Event is past it's execution time")
{
eventMap.Update(2000);
REQUIRE(eventMap.GetTimeUntilEvent(EVENT_1) == -1s);
}
}
TEST_CASE("Schedule existing event", "[EventMap]")
{
EventMap eventMap;
SECTION("Same time")
{
eventMap.ScheduleEvent(EVENT_1, 1s);
eventMap.ScheduleEvent(EVENT_1, 1s);
eventMap.Update(1000);
uint32 id = eventMap.ExecuteEvent();
REQUIRE(id == EVENT_1);
id = eventMap.ExecuteEvent();
REQUIRE(id == EVENT_1);
REQUIRE(eventMap.Empty());
}
SECTION("Different time")
{
eventMap.ScheduleEvent(EVENT_1, 1s);
eventMap.ScheduleEvent(EVENT_1, 2s);
eventMap.Update(1000);
uint32 id = eventMap.ExecuteEvent();
REQUIRE(id == EVENT_1);
eventMap.Update(1000);
id = eventMap.ExecuteEvent();
REQUIRE(id == EVENT_1);
REQUIRE(eventMap.Empty());
}
}
TEST_CASE("Cancel a scheduled event", "[EventMap]")
{
EventMap eventMap;
eventMap.ScheduleEvent(EVENT_1, 1s);
eventMap.ScheduleEvent(EVENT_2, 1s);
eventMap.CancelEvent(EVENT_1);
REQUIRE_FALSE(eventMap.Empty());
eventMap.Update(1000);
uint32 id = eventMap.ExecuteEvent();
REQUIRE(id == EVENT_2);
REQUIRE(eventMap.Empty());
}
TEST_CASE("Cancel non-existing event", "[EventMap]")
{
EventMap eventMap;
REQUIRE(eventMap.Empty());
eventMap.CancelEvent(EVENT_1);
REQUIRE(eventMap.Empty());
}
TEST_CASE("Reschedule an event", "[EventMap]")
{
EventMap eventMap;
eventMap.ScheduleEvent(EVENT_1, 1s);
eventMap.RescheduleEvent(EVENT_1, 2s);
eventMap.Update(1000);
uint32 id = eventMap.ExecuteEvent();
REQUIRE(id == 0);
eventMap.Update(1000);
id = eventMap.ExecuteEvent();
REQUIRE(id == EVENT_1);
}
TEST_CASE("Reschedule a non-scheduled event", "[EventMap]")
{
EventMap eventMap;
eventMap.RescheduleEvent(EVENT_1, 2s);
eventMap.Update(1000);
uint32 id = eventMap.ExecuteEvent();
REQUIRE(id == 0);
eventMap.Update(1000);
id = eventMap.ExecuteEvent();
REQUIRE(id == EVENT_1);
}
TEST_CASE("Repeat an event (empty map)", "[EventMap]")
{
EventMap eventMap;
eventMap.Repeat(1s);
eventMap.Update(1s);
uint32 id = eventMap.ExecuteEvent();
REQUIRE(id == 0);
}
TEST_CASE("Repeat an event (populated map)", "[EventMap]")
{
EventMap eventMap;
eventMap.ScheduleEvent(EVENT_1, 1s);
SECTION("Scheduled event with delay not reached")
{
eventMap.Update(500ms);
eventMap.Repeat(1s);
uint32 id = eventMap.ExecuteEvent();
REQUIRE(id == 0);
}
SECTION("Scheduled event with delay not reached")
{
eventMap.Update(1s);
eventMap.Repeat(1s);
uint32 id = eventMap.ExecuteEvent();
REQUIRE(id == EVENT_1);
}
}
TEST_CASE("Schedule event with phase", "[EventMap]")
{
EventMap eventMap;
REQUIRE(eventMap.Empty());
eventMap.ScheduleEvent(EVENT_1, 1s, 0, PHASE_1);
eventMap.ScheduleEvent(EVENT_2, 1s, 0, PHASE_2);
SECTION("In default phase. Execute all events.")
{
eventMap.Update(1000);
uint32 id = eventMap.ExecuteEvent();
REQUIRE(id == EVENT_1);
REQUIRE_FALSE(eventMap.Empty());
id = eventMap.ExecuteEvent();
REQUIRE(id == EVENT_2);
REQUIRE(eventMap.Empty());
}
SECTION("Execute only events of specified phase")
{
eventMap.SetPhase(PHASE_1);
eventMap.Update(1000);
uint32 id = eventMap.ExecuteEvent();
REQUIRE(id == EVENT_1);
REQUIRE_FALSE(eventMap.Empty());
id = eventMap.ExecuteEvent();
REQUIRE(id == 0);
REQUIRE(eventMap.Empty());
}
SECTION("Execute events from multiple phases (1)")
{
eventMap.AddPhase(PHASE_1);
eventMap.AddPhase(PHASE_2);
eventMap.Update(1000);
uint32 id = eventMap.ExecuteEvent();
REQUIRE(id == EVENT_1);
REQUIRE_FALSE(eventMap.Empty());
id = eventMap.ExecuteEvent();
REQUIRE(id == EVENT_2);
REQUIRE(eventMap.Empty());
}
SECTION("Execute events from multiple phases (2)")
{
eventMap.AddPhase(PHASE_1);
eventMap.Update(1000);
uint32 id = eventMap.ExecuteEvent();
REQUIRE(id == EVENT_1);
REQUIRE_FALSE(eventMap.Empty());
eventMap.RemovePhase(PHASE_2);
id = eventMap.ExecuteEvent();
REQUIRE(id == 0);
REQUIRE(eventMap.Empty());
}
}
TEST_CASE("Phase helper methods", "[EventMap]")
{
EventMap eventMap;
eventMap.SetPhase(PHASE_1);
REQUIRE(eventMap.GetPhaseMask() == 0x1);
REQUIRE(eventMap.IsInPhase(PHASE_1));
REQUIRE_FALSE(eventMap.IsInPhase(PHASE_2));
eventMap.AddPhase(PHASE_2);
REQUIRE(eventMap.GetPhaseMask() == 0x3);
REQUIRE(eventMap.IsInPhase(PHASE_1));
REQUIRE(eventMap.IsInPhase(PHASE_2));
eventMap.RemovePhase(PHASE_1);
REQUIRE(eventMap.GetPhaseMask() == 0x2);
REQUIRE_FALSE(eventMap.IsInPhase(PHASE_1));
REQUIRE(eventMap.IsInPhase(PHASE_2));
}
TEST_CASE("Cancel event group", "[EventMap]")
{
EventMap eventMap;
eventMap.ScheduleEvent(EVENT_2, 1s, GROUP_1);
SECTION("Only event in group")
{
eventMap.CancelEventGroup(GROUP_1);
REQUIRE(eventMap.Empty());
}
SECTION("Group with groupless event")
{
eventMap.ScheduleEvent(EVENT_1, 1s);
eventMap.CancelEventGroup(GROUP_1);
REQUIRE_FALSE(eventMap.Empty());
}
SECTION("Two groups")
{
eventMap.ScheduleEvent(EVENT_1, 1s);
eventMap.ScheduleEvent(EVENT_3, 1s, GROUP_2);
eventMap.CancelEventGroup(GROUP_1);
REQUIRE_FALSE(eventMap.Empty());
eventMap.CancelEventGroup(GROUP_2);
REQUIRE_FALSE(eventMap.Empty());
}
}
TEST_CASE("Delay all events", "[EventMap]")
{
EventMap eventMap;
eventMap.ScheduleEvent(EVENT_1, 1s);
REQUIRE(eventMap.GetTimeUntilEvent(EVENT_1) == 1s);
SECTION("Without timer update")
{
eventMap.DelayEvents(1s);
// 1s (init) + 1s (delay) = 2s
REQUIRE(eventMap.GetTimeUntilEvent(EVENT_1) == 2s);
}
SECTION("With timer update smaller than delay")
{
eventMap.Update(500);
eventMap.DelayEvents(1s);
// 1s (init) + 1s (delay) - 500ms (tick) = 1500ms
REQUIRE(eventMap.GetTimeUntilEvent(EVENT_1) == 1500ms);
}
SECTION("With timer update larger than delay")
{
eventMap.Update(2000);
eventMap.DelayEvents(1s);
// 1s (init) + 1s (delay) - 2s (tick) = 0s
REQUIRE(eventMap.GetTimeUntilEvent(EVENT_1) == 0s);
}
}
TEST_CASE("Delay grouped events", "[EventMap]")
{
EventMap eventMap;
eventMap.ScheduleEvent(EVENT_1, 1s, GROUP_1);
eventMap.ScheduleEvent(EVENT_2, 2s, GROUP_2);
eventMap.ScheduleEvent(EVENT_3, 6s);
eventMap.Update(2000);
eventMap.DelayEvents(3s, GROUP_1);
REQUIRE(eventMap.GetTimeUntilEvent(EVENT_1) == 2s);
REQUIRE(eventMap.GetTimeUntilEvent(EVENT_2) == 0s);
REQUIRE(eventMap.GetTimeUntilEvent(EVENT_3) == 4s);
}
TEST_CASE("Reset map", "[EventMap]")
{
EventMap eventMap;
eventMap.ScheduleEvent(EVENT_1, 1s);
eventMap.Reset();
REQUIRE(eventMap.Empty());
}
+60
View File
@@ -0,0 +1,60 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tc_catch2.h"
#include "FlatSet.h"
TEST_CASE("Insertion", "[FlatSet]")
{
Trinity::Containers::FlatSet<int> flat;
REQUIRE(flat.insert(5).second == true);
REQUIRE(flat.insert(3).second == true);
REQUIRE(flat.insert(9).second == true);
REQUIRE(flat.insert(7).second == true);
REQUIRE(flat.insert(5).second == false);
REQUIRE(flat.insert(3).second == false);
REQUIRE(flat.insert(9).second == false);
REQUIRE(flat.insert(7).second == false);
REQUIRE(flat.size() == 4);
auto itr = flat.begin();
REQUIRE(*itr == 3);
++itr;
REQUIRE(*itr == 5);
++itr;
REQUIRE(*itr == 7);
++itr;
REQUIRE(*itr == 9);
++itr;
REQUIRE(itr == flat.end());
}
TEST_CASE("Erase", "[FlatSet]")
{
Trinity::Containers::FlatSet<int> flat;
flat.insert(3);
flat.insert(5);
flat.insert(7);
flat.insert(9);
REQUIRE(flat.erase(7) == 1);
REQUIRE(flat.size() == 3);
}
+154
View File
@@ -0,0 +1,154 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tc_catch2.h"
#include "StringConvert.h"
TEST_CASE("String to uint32", "[StringConvert]")
{
REQUIRE(Trinity::StringTo<uint32>("42") == 42u);
REQUIRE(Trinity::StringTo<uint32>("42", 10) == 42u);
REQUIRE(Trinity::StringTo<uint32>(" 42") == std::nullopt);
REQUIRE(Trinity::StringTo<uint32>("tail42") == std::nullopt);
REQUIRE(Trinity::StringTo<uint32>("42tail") == std::nullopt);
REQUIRE(Trinity::StringTo<uint32>("ff", 16) == 0xFFu);
REQUIRE(Trinity::StringTo<uint32>("0xff") == std::nullopt);
REQUIRE(Trinity::StringTo<uint32>("0xff", 0) == 0xFFu);
REQUIRE(Trinity::StringTo<uint32>("101010", 2) == 0b101010u);
REQUIRE(Trinity::StringTo<uint32>("0b101010") == std::nullopt);
REQUIRE(Trinity::StringTo<uint32>("0b101010", 0) == 0b101010u);
REQUIRE(Trinity::StringTo<uint32>("5000000000") == std::nullopt);
REQUIRE(Trinity::StringTo<uint32>("100000000", 16) == std::nullopt);
REQUIRE(Trinity::StringTo<uint32>("0x100000000", 0) == std::nullopt);
REQUIRE(Trinity::StringTo<uint32>("0xffffffff", 0) == 0xFFFFFFFF);
}
TEST_CASE("String to uint64", "[StringConvert]")
{
REQUIRE(Trinity::StringTo<uint64>("42") == 42);
REQUIRE(Trinity::StringTo<uint64>("42", 10) == 42);
REQUIRE(Trinity::StringTo<uint64>(" 42") == std::nullopt);
REQUIRE(Trinity::StringTo<uint64>("tail42") == std::nullopt);
REQUIRE(Trinity::StringTo<uint64>("42tail") == std::nullopt);
REQUIRE(Trinity::StringTo<uint64>("-1", 0) == std::nullopt);
REQUIRE(Trinity::StringTo<uint64>("ff", 16) == 0xff);
REQUIRE(Trinity::StringTo<uint64>("0xff") == std::nullopt);
REQUIRE(Trinity::StringTo<uint64>("0xff", 0) == 0xff);
REQUIRE(Trinity::StringTo<uint64>("101010", 2) == 0b101010);
REQUIRE(Trinity::StringTo<uint64>("0b101010") == std::nullopt);
REQUIRE(Trinity::StringTo<uint64>("0b101010", 0) == 0b101010);
REQUIRE(Trinity::StringTo<uint64>("5000000000") == 5000000000ULL);
REQUIRE(Trinity::StringTo<uint64>("100000000", 16) == 0x100000000ULL);
REQUIRE(Trinity::StringTo<uint64>("20000000000000000000") == std::nullopt);
REQUIRE(Trinity::StringTo<uint64>("10000000000000000", 16) == std::nullopt);
REQUIRE(Trinity::StringTo<uint64>("0x10000000000000000", 0) == std::nullopt);
REQUIRE(Trinity::StringTo<uint64>("0xFFFFFFFFFFFFFFFF", 0) == 0xffffffffffffffffULL);
}
TEST_CASE("String to int32", "[StringConvert]")
{
REQUIRE(Trinity::StringTo<int32>("-42") == -42);
REQUIRE(Trinity::StringTo<int32>("42") == 42);
REQUIRE(Trinity::StringTo<int32>("+42") == std::nullopt);
REQUIRE(Trinity::StringTo<int32>("--42") == std::nullopt);
REQUIRE(Trinity::StringTo<int32>("~42") == std::nullopt);
REQUIRE(Trinity::StringTo<int32>("42-") == std::nullopt);
REQUIRE(Trinity::StringTo<int32>("-ffff", 16) == -0xffff);
REQUIRE(Trinity::StringTo<int32>("ffffffff", 16) == std::nullopt);
REQUIRE(Trinity::StringTo<int32>("7fffffff", 16) == 0x7fffffff);
}
TEST_CASE("String to int64", "[StringConvert]")
{
REQUIRE(Trinity::StringTo<int64>("-42") == -42);
REQUIRE(Trinity::StringTo<int64>("42") == 42);
REQUIRE(Trinity::StringTo<int64>("+42") == std::nullopt);
REQUIRE(Trinity::StringTo<int64>("--42") == std::nullopt);
REQUIRE(Trinity::StringTo<int64>("~42") == std::nullopt);
REQUIRE(Trinity::StringTo<int64>("42-") == std::nullopt);
REQUIRE(Trinity::StringTo<int64>("-ffff", 16) == -0xffff);
REQUIRE(Trinity::StringTo<int64>("ffffffff", 16) == 0xffffffff);
REQUIRE(Trinity::StringTo<int64>("7fffffff", 16) == 0x7fffffff);
REQUIRE(Trinity::StringTo<int64>("ffffffffffffffff", 16) == std::nullopt);
REQUIRE(Trinity::StringTo<int64>("7fffffffffffffff", 16) == 0x7fffffffffffffffLL);
REQUIRE(Trinity::StringTo<int64>("-8500000000000000", 16) == std::nullopt);
}
TEST_CASE("String to smaller integer types", "[StringConvert]")
{
REQUIRE(Trinity::StringTo<uint8>("0xff", 0) == 0xff);
REQUIRE(Trinity::StringTo<uint8>("0x1ff", 0) == std::nullopt);
REQUIRE(Trinity::StringTo<int8>("0xff", 0) == std::nullopt);
REQUIRE(Trinity::StringTo<int8>("0x7f", 0) == 0x7f);
REQUIRE(Trinity::StringTo<int8>("-7f", 16) == -0x7f);
REQUIRE(Trinity::StringTo<uint16>("0x1ff", 0) == 0x1ff);
REQUIRE(Trinity::StringTo<uint16>("0x1ffff", 0) == std::nullopt);
REQUIRE(Trinity::StringTo<uint16>("-1", 0) == std::nullopt);
REQUIRE(Trinity::StringTo<int16>("0x1ff", 0) == 0x1ff);
REQUIRE(Trinity::StringTo<int16>("0xffff", 0) == std::nullopt);
REQUIRE(Trinity::StringTo<int16>("0x7fff", 0) == 0x7fff);
REQUIRE(Trinity::StringTo<int16>("-1", 0) == -1);
}
TEST_CASE("String to boolean", "[StringConvert]")
{
REQUIRE(Trinity::StringTo<bool>("true") == true);
REQUIRE(Trinity::StringTo<bool>("false") == false);
REQUIRE(Trinity::StringTo<bool>("ture") == std::nullopt);
REQUIRE(Trinity::StringTo<bool>("true", 10) == std::nullopt);
REQUIRE(Trinity::StringTo<bool>("1") == true);
REQUIRE(Trinity::StringTo<bool>("1", 10) == true);
REQUIRE(Trinity::StringTo<bool>("0", 10) == false);
}
TEST_CASE("String to double", "[StringConvert]")
{
using namespace Catch::literals;
REQUIRE(Trinity::StringTo<double>("0.5") == 0.5);
REQUIRE(Trinity::StringTo<double>("0.1") == 0.1_a);
REQUIRE(Trinity::StringTo<double>("1.2.3") == std::nullopt);
REQUIRE(Trinity::StringTo<double>("1e+5") == 100000.0);
REQUIRE(Trinity::StringTo<double>("1e+3+5") == std::nullopt);
REQUIRE(Trinity::StringTo<double>("a1.5") == std::nullopt);
REQUIRE(Trinity::StringTo<double>("1.5tail") == std::nullopt);
REQUIRE(Trinity::StringTo<double>("0x0") == 0.0);
REQUIRE(Trinity::StringTo<double>("0x0", 16) == std::nullopt);
REQUIRE(Trinity::StringTo<double>("0", 16) == 0.0);
REQUIRE(Trinity::StringTo<double>("0x1.BC70A3D70A3D7p+6") == 0x1.BC70A3D70A3D7p+6);
REQUIRE(Trinity::StringTo<double>("0x1.BC70A3D70A3D7p+6", 10) == std::nullopt);
REQUIRE(Trinity::StringTo<double>("0x1.BC70A3D70A3D7p+6", 16) == std::nullopt);
REQUIRE(Trinity::StringTo<double>("1.BC70A3D70A3D7p+6", 16) == 0x1.BC70A3D70A3D7p+6);
REQUIRE(Trinity::StringTo<double>("0x1.2.3") == std::nullopt);
REQUIRE(Trinity::StringTo<double>("0x1.AAAp+1-3") == std::nullopt);
REQUIRE(Trinity::StringTo<double>("1.2.3", 16) == std::nullopt);
REQUIRE(Trinity::StringTo<double>("1.AAAp+1-3", 16) == std::nullopt);
}
+59
View File
@@ -0,0 +1,59 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
#include "tc_catch2.h"
#include "Timer.h"
TEST_CASE("TimeTracker: Check if time passed")
{
TimeTracker tracker(1000 /*ms*/);
REQUIRE_FALSE(tracker.Passed());
REQUIRE(tracker.GetExpiry() == 1s);
tracker.Update(500 /*ms*/);
REQUIRE_FALSE(tracker.Passed());
REQUIRE(tracker.GetExpiry() == 500ms);
tracker.Update(500 /*ms*/);
REQUIRE(tracker.Passed());
REQUIRE(tracker.GetExpiry() == 0s);
tracker.Update(500 /*ms*/);
REQUIRE(tracker.Passed());
REQUIRE(tracker.GetExpiry() == -500ms);
}
TEST_CASE("TimeTracker: Reset timer")
{
TimeTracker tracker(1000 /*ms*/);
REQUIRE_FALSE(tracker.Passed());
REQUIRE(tracker.GetExpiry() == 1s);
tracker.Update(1000 /*ms*/);
REQUIRE(tracker.Passed());
REQUIRE(tracker.GetExpiry() == 0s);
tracker.Reset(1000 /*ms*/);
REQUIRE_FALSE(tracker.Passed());
REQUIRE(tracker.GetExpiry() == 1s);
tracker.Update(1000 /*ms*/);
REQUIRE(tracker.Passed());
REQUIRE(tracker.GetExpiry() == 0s);
}
+165
View File
@@ -0,0 +1,165 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tc_catch2.h"
#include "CompilerDefs.h"
#include "UniqueTrackablePtr.h"
struct TestObj
{
TestObj(bool* deleted = nullptr) : Deleted(deleted) { }
virtual ~TestObj()
{
if (Deleted)
*Deleted = true;
}
bool* Deleted = nullptr;
};
struct TestObj2
{
virtual ~TestObj2() = default;
int a = 5;
};
struct TestObj3 : public TestObj2, public TestObj
{
};
struct TestObj4 : public TestObj
{
};
TEST_CASE("Trinity::unique_trackable_ptr frees memory", "[UniqueTrackablePtr]")
{
bool deleted = false;
SECTION("reassigning new object deletes old one")
{
Trinity::unique_trackable_ptr<TestObj> ptr = Trinity::make_unique_trackable<TestObj>(&deleted);
ptr.reset(new TestObj());
REQUIRE(deleted == true);
}
SECTION("going out of scope deletes object")
{
REQUIRE(deleted == false);
{
Trinity::unique_trackable_ptr<TestObj> ptr = Trinity::make_unique_trackable<TestObj>(&deleted);
}
REQUIRE(deleted == true);
}
}
TEST_CASE("Trinity::unique_weak_ptr", "[UniqueTrackablePtr]")
{
Trinity::unique_trackable_ptr<int> ptr = Trinity::make_unique_trackable<int>();
Trinity::unique_weak_ptr<int> weakRef = ptr;
SECTION("when unique_trackable_ptr no longer holds a value then weak cannot retrieve it")
{
ptr.reset();
REQUIRE(weakRef.expired());
REQUIRE(!weakRef.lock());
}
SECTION("when unique_trackable_ptr is reassigned then weak cannot retrieve old value")
{
ptr.reset(new int);
Trinity::unique_weak_ptr<int> weakRef2 = ptr;
REQUIRE(weakRef.expired());
REQUIRE(!weakRef2.expired());
REQUIRE(weakRef.lock() != weakRef2.lock());
}
SECTION("when unique_trackable_ptr holds a value then weak can retrieve it")
{
REQUIRE(!weakRef.expired());
REQUIRE(!!weakRef.lock());
}
}
// disable warning about invalid reinterpret_cast, test intentionally tests this
#ifdef __clang__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wreinterpret-base-class"
#endif
TEST_CASE("Trinity::unique_strong_ref_ptr type casts", "[UniqueTrackablePtr]")
{
Trinity::unique_trackable_ptr<TestObj> ptr = Trinity::make_unique_trackable<TestObj3>();
Trinity::unique_weak_ptr<TestObj> weak = ptr;
Trinity::unique_strong_ref_ptr<TestObj> temp = weak.lock();
REQUIRE(temp != nullptr);
SECTION("static_pointer_cast")
{
Trinity::unique_strong_ref_ptr<TestObj3> testObj2 = Trinity::static_pointer_cast<TestObj3>(temp);
REQUIRE(testObj2.get() == static_cast<TestObj3*>(ptr.get()));
// sanity check that we didn't accidentally setup inheritance of TestObjs incorrectly
REQUIRE(testObj2.get() != reinterpret_cast<TestObj3*>(ptr.get()));
REQUIRE(testObj2 == Trinity::static_pointer_cast<TestObj3>(weak).lock());
}
SECTION("reinterpret_pointer_cast")
{
Trinity::unique_strong_ref_ptr<TestObj3> testObj2 = Trinity::reinterpret_pointer_cast<TestObj3>(temp);
REQUIRE(testObj2.get() == reinterpret_cast<TestObj3*>(ptr.get()));
REQUIRE(testObj2 == Trinity::reinterpret_pointer_cast<TestObj3>(weak).lock());
}
SECTION("succeeding dynamic_pointer_cast")
{
Trinity::unique_strong_ref_ptr<TestObj3> testObj2 = Trinity::dynamic_pointer_cast<TestObj3>(temp);
REQUIRE(testObj2.get() == dynamic_cast<TestObj3*>(ptr.get()));
REQUIRE(testObj2 == Trinity::dynamic_pointer_cast<TestObj3>(weak).lock());
}
SECTION("failing dynamic_pointer_cast")
{
Trinity::unique_strong_ref_ptr<TestObj4> testObj2 = Trinity::dynamic_pointer_cast<TestObj4>(temp);
REQUIRE(testObj2 == nullptr);
REQUIRE(testObj2 == Trinity::dynamic_pointer_cast<TestObj4>(weak).lock());
}
}
#ifdef __clang__
#pragma GCC diagnostic pop
#endif
+136
View File
@@ -0,0 +1,136 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tc_catch2.h"
#include "Chat.h"
#include "ChatCommand.h"
using namespace Trinity::ChatCommands;
using namespace std::string_view_literals;
struct DummyChatHandler : ChatHandler
{
DummyChatHandler() : ChatHandler(nullptr) {}
void SendSysMessage(std::string_view, bool) override {}
char const* GetTrinityString(uint32) const override { return ""; }
};
template <typename F>
static void TestChatCommand(std::string_view c, F f, Optional<bool> expected = true)
{
DummyChatHandler handler;
bool r = Trinity::Impl::ChatCommands::CommandInvoker(*+f)(&handler, c);
if (expected)
REQUIRE(r == *expected);
}
TEST_CASE("Command return pass-through", "[ChatCommand]")
{
TestChatCommand("", [](ChatHandler*) { return true; }, true);
TestChatCommand("", [](ChatHandler*) { return false; }, false);
}
TEST_CASE("Command argument parsing", "[ChatCommand]")
{
SECTION("Single uint32 argument")
{
TestChatCommand("42", [](ChatHandler*, uint32 u)
{
REQUIRE(u == 42);
return true;
});
TestChatCommand("true", [](ChatHandler*, uint32) { return true; }, false);
}
SECTION("Floating point argument")
{
TestChatCommand("0.5", [](ChatHandler*, float f)
{
REQUIRE(f == 0.5);
return true;
});
TestChatCommand("true", [](ChatHandler*, float) { return true; }, false);
}
SECTION("std::vector<uint16>")
{
TestChatCommand("1 2 3 4 5 6 7 8 9 10", [](ChatHandler*, std::vector<uint16> v)
{
REQUIRE(v == std::vector<uint16>{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
return true;
});
}
SECTION("std::array<uint16>")
{
TestChatCommand("1 2 3 4 5 6 7 8 9 10", [](ChatHandler*, std::array<uint16, 10> v)
{
REQUIRE(v == std::array<uint16, 10>{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
return true;
});
}
SECTION("Hyperlink<player>")
{
TestChatCommand("|cffff0000|Hplayer:Test|h[Test]|h|r",
[](ChatHandler*, Hyperlink<player> player)
{
REQUIRE("Test"sv == *player);
return true;
}
);
}
SECTION("Two strings")
{
TestChatCommand("two strings", [](ChatHandler*, std::string_view v1, std::string_view v2)
{
REQUIRE(v1 == "two");
REQUIRE(v2 == "strings");
return true;
});
TestChatCommand("two strings", [](ChatHandler*, std::string_view) { return true; }, false);
TestChatCommand("two strings", [](ChatHandler*, Tail t)
{
REQUIRE(t == "two strings");
return true;
});
}
SECTION("Variant<>")
{
TestChatCommand("0x1ffff", [](ChatHandler*, Variant<uint16, uint32> v)
{
REQUIRE(v.holds_alternative<uint32>());
REQUIRE(v.get<uint32>() == 0x1ffff);
return true;
});
TestChatCommand("0xffff", [](ChatHandler*, Variant<uint16, uint32> v)
{
REQUIRE(v.holds_alternative<uint16>());
REQUIRE(v.get<uint16>() == 0xffff);
return true;
});
TestChatCommand("0x1ffff", [](ChatHandler*, Variant<uint32, uint16> v)
{
REQUIRE(v.holds_alternative<uint32>());
REQUIRE(v.get<uint32>() == 0x1ffff);
return true;
});
}
}
+86
View File
@@ -0,0 +1,86 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tc_catch2.h"
#include "DummyData.h"
#include "Hyperlinks.h"
#include "World.h"
using namespace std::string_view_literals;
using namespace Trinity::Hyperlinks;
TEST_CASE("Basic link structure", "[Hyperlinks]")
{
SECTION("Link without data")
{
HyperlinkInfo info = ParseSingleHyperlink("|cabcdef01|HTag|h[text]|h|r");
REQUIRE(info.ok);
REQUIRE(info.color == 0xabcdef01);
REQUIRE(info.color.data == "abcdef01"sv);
REQUIRE(info.tag == "Tag");
REQUIRE(info.data == "");
REQUIRE(info.text == "text");
REQUIRE(info.tail == "");
}
SECTION("Link with data")
{
HyperlinkInfo info = ParseSingleHyperlink("|c12345678|Htag:data1:data2:data3:data4:data5|h[Text]|h|rtail");
REQUIRE(info.ok);
REQUIRE(info.color == 0x12345678);
REQUIRE(info.color.data == "12345678"sv);
REQUIRE(info.tag == "tag");
REQUIRE(info.data == "data1:data2:data3:data4:data5");
REQUIRE(info.text == "Text");
REQUIRE(info.tail == "tail");
}
}
TEST_CASE("|Hitem validation", "[Hyperlinks]")
{
UnitTestDataLoader::LoadItemTemplates();
sWorld->setIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY, 1);
SECTION("Basic item link")
{
REQUIRE(true == CheckAllLinks("This is my |cffffffff|Hitem:6948::::::::60:::::|h[Hearthstone]|h|r. There are many like it, but this one is mine."));
REQUIRE(true == CheckAllLinks("Some might call it their |cffffffff|Hitem:6948::::::::60:::::|h[Piedra de hogar]|h|r. They all still take you home."));
REQUIRE(false == CheckAllLinks("However, if you call it a |cffffffff|Hitem:6948::::::::60:::::|h|h[Doormat]|h|r, that's a step too far. Get it? Step?"));
REQUIRE(false == CheckAllLinks("Or if you try to pronounce |cffffffff|Hitem::::::::::::::|h|h[Cthulhu fhtagn]|h|r. Also too far."));
REQUIRE(false == CheckAllLinks("I'm out of witty one-liners. |cffffffff|Hitem|h[This]|h|r is just lacking data."));
REQUIRE(false == CheckAllLinks("This is a mis-colored |cffa335ee|Hitem:6948::::::::60:::::|h[Hearthstone]|h|r."));
REQUIRE(false == CheckAllLinks("This is a |cffffffff|Hitem:6948:-1:::::::60:::::|h[Hearthstone]|h|r that is quite negative."));
}
}
TEST_CASE("|Hachievement validation", "[Hyperlinks]")
{
UnitTestDataLoader::LoadAchievementTemplates();
sWorld->setIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY, 1);
REQUIRE(true == CheckAllLinks("|cffffff00|Hachievement:4298:Player-0-000000FD:0:0:0:-1:0:0:0:0|h[Heroic: Trial of the Champion]|h|r"));
REQUIRE(false == CheckAllLinks("|cffffff00|Hachievement|h[Heroic: Trial of the Champion]|h|r"));
REQUIRE(false == CheckAllLinks("|cffffff00|Hachievement:1:Player-0-000000FD:0:0:0:-1:0:0:0:0|h[Heroic: Trial of the Champion]|h|r"));
REQUIRE(false == CheckAllLinks("|cffff0000|Hachievement:4298:Player-0-000000FD:0:0:0:-1:0:0:0:0|h[Heroic: Trial of the Champion]|h|r"));
REQUIRE(false == CheckAllLinks("|cffffff00|Hachievement:4298:00000000000000XY:0:0:0:-1:0:0:0:0|h[Heroic: Trial of the Champion]|h|r"));
REQUIRE(true == CheckAllLinks("|cffffff00|Hachievement:4298:Player-0-000000FD:1:12:20:12:0:0:0:0|h[Heroic: Trial of the Champion]|h|r"));
REQUIRE(false == CheckAllLinks("|cffffff00|Hachievement:4298:Player-0-000000FD:1:12:40:12:0:0:0:0|h[Heroic: Trial of the Champion]|h|r"));
REQUIRE(false == CheckAllLinks("|cffffff00|Hachievement:4298:Player-0-000000FD:1:14:20:12:0:0:0:0|h[Heroic: Trial of the Champion]|h|r"));
REQUIRE(false == CheckAllLinks("|cffffff00|Hachievement:4298:Player-0-000000FD:1:0:0:-1:0:0:0:0|h[Heroic: Trial of the Champion]|h|r"));
REQUIRE(true == CheckAllLinks("|cffffff00|Hachievement:4298:Player-0-000000FD:1:12:20:12:0:0:0:0|h[Heroico: Prueba del Campe\xc3\xb3n]|h|r"));
}
+65
View File
@@ -0,0 +1,65 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tc_catch2.h"
#include "IteratorPair.h"
class ThreatListIterator
{
private:
std::function<int const* ()> _generator;
int const* _current;
public:
explicit ThreatListIterator(std::function<int const* ()>&& generator)
: _generator(std::move(generator)), _current(_generator())
{ }
int const* operator*() const { return _current; }
int const* operator->() const { return _current; }
ThreatListIterator& operator++() { _current = _generator(); return *this; }
bool operator==(ThreatListIterator const& o) const { return _current == o._current; }
bool operator!=(ThreatListIterator const& o) const { return _current != o._current; }
bool operator==(std::nullptr_t) const { return _current == nullptr; }
bool operator!=(std::nullptr_t) const { return _current != nullptr; }
};
std::vector<int> ints{ 1, 2, 3, 4 };
Trinity::IteratorPair<ThreatListIterator, std::nullptr_t> GetUnsortedThreatList()
{
auto itr = ints.begin();
auto end = ints.end();
std::function<int const* ()> generator = [itr, end]() mutable -> int const*
{
if (itr == end)
return nullptr;
return &*(itr++);
};
return { ThreatListIterator{ std::move(generator) }, nullptr };
}
TEST_CASE("Check generator logic", "[ThreatListIterator]")
{
std::vector<int> iterated;
for (int const* i : GetUnsortedThreatList())
iterated.push_back(*i);
REQUIRE(iterated == ints);
}
+20
View File
@@ -0,0 +1,20 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
+44
View File
@@ -0,0 +1,44 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TRINITY_CATCH2_H
#define TRINITY_CATCH2_H
#include "Optional.h"
#include <iostream>
#include <typeinfo>
template <typename T>
std::ostream& operator<<(std::ostream& os, Optional<T> const& value)
{
os << "Opt";
if (value)
os << " { " << *value << " }";
else
os << " (<empty>)";
return os;
}
inline std::ostream& operator<<(std::ostream& os, std::nullopt_t)
{
os << "<empty>";
return os;
}
#include "catch2/catch.hpp"
#endif