full source
This commit is contained in:
@@ -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")
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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*
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user