Merge branch 'master' into Playerbot

This commit is contained in:
Yunfan Li
2025-07-15 20:34:44 +08:00
80 changed files with 11484 additions and 699 deletions

View File

@@ -28,7 +28,8 @@ results = {
"INSERT & DELETE safety usage check": "Passed",
"Missing semicolon check": "Passed",
"Backtick check": "Passed",
"Directory check": "Passed"
"Directory check": "Passed",
"Table engine check": "Passed"
}
# Collect all files in all directories
@@ -78,6 +79,7 @@ def parsing_file(files: list) -> None:
insert_delete_safety_check(file, file_path)
semicolon_check(file, file_path)
backtick_check(file, file_path)
non_innodb_engine_check(file, file_path)
except UnicodeDecodeError:
print(f"\n❌ Could not decode file {file_path}")
sys.exit(1)
@@ -383,6 +385,25 @@ def directory_check(file: io, file_path: str) -> None:
error_handler = True
results["Directory check"] = "Failed"
def non_innodb_engine_check(file: io, file_path: str) -> None:
global error_handler, results
file.seek(0)
check_failed = False
engine_pattern = re.compile(r'ENGINE\s*=\s*([a-zA-Z0-9_]+)', re.IGNORECASE)
for line_number, line in enumerate(file, start=1):
match = engine_pattern.search(line)
if match:
engine = match.group(1).lower()
if engine != "innodb":
print(f"❌ Non-InnoDB engine found: '{engine}' in {file_path} at line {line_number}")
check_failed = True
if check_failed:
error_handler = True
results["Table engine check"] = "Failed"
# Collect all files from matching directories
all_files = collect_files_from_directories(src_directory) + collect_files_from_directories(base_directory) + collect_files_from_directories(archive_directory)

View File

@@ -0,0 +1,17 @@
# BATS Test Configuration for Compiler App
# Set test timeout (in seconds)
export BATS_TEST_TIMEOUT=60
# Enable verbose output for debugging
export BATS_VERBOSE_RUN=1
# Test output format
export BATS_FORMATTER=pretty
# Enable colored output
export BATS_NO_PARALLELIZE_ACROSS_FILES=1
export BATS_NO_PARALLELIZE_WITHIN_FILE=1
# Compiler specific test configuration
export COMPILER_TEST_SKIP_HEAVY=1

View File

@@ -0,0 +1,307 @@
#!/usr/bin/env bats
# Require minimum BATS version to avoid warnings
bats_require_minimum_version 1.5.0
# AzerothCore Compiler Scripts Test Suite
# Tests the functionality of the compiler scripts using the unified test framework
# Load the AzerothCore test framework
load '../../test-framework/bats_libs/acore-support'
load '../../test-framework/bats_libs/acore-assert'
# Setup that runs before each test
setup() {
compiler_setup
export SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
export COMPILER_SCRIPT="$SCRIPT_DIR/compiler.sh"
}
# Cleanup that runs after each test
teardown() {
acore_test_teardown
}
# ===== COMPILER SCRIPT TESTS =====
@test "compiler: should show help with --help argument" {
run bash -c "echo '' | timeout 5s $COMPILER_SCRIPT --help"
[ "$status" -eq 0 ]
[[ "$output" =~ "Available commands:" ]]
}
@test "compiler: should show help with empty input" {
run bash -c "echo '' | timeout 5s $COMPILER_SCRIPT 2>&1 || true"
# The script might exit with timeout (124) or success (0), both are acceptable for this test
[[ "$status" -eq 0 ]] || [[ "$status" -eq 124 ]]
# Check if output contains expected content - looking for menu options
[[ "$output" =~ "build:" ]] || [[ "$output" =~ "clean:" ]] || [[ "$output" =~ "Please enter your choice" ]] || [[ -z "$output" ]]
}
@test "compiler: should accept option numbers" {
# Test option 7 (ccacheShowStats) which should be safe to run
run bash -c "echo '7' | timeout 10s $COMPILER_SCRIPT 2>/dev/null || true"
# The script might exit with timeout (124) or success (0), both are acceptable
[[ "$status" -eq 0 ]] || [[ "$status" -eq 124 ]]
}
@test "compiler: should accept option by name" {
run timeout 10s "$COMPILER_SCRIPT" ccacheShowStats
[ "$status" -eq 0 ]
}
@test "compiler: should handle invalid option gracefully" {
run timeout 5s "$COMPILER_SCRIPT" invalidOption
[ "$status" -eq 0 ]
[[ "$output" =~ "invalid option" ]]
}
@test "compiler: should handle invalid number gracefully" {
run bash -c "echo '999' | timeout 5s $COMPILER_SCRIPT 2>/dev/null || true"
# The script might exit with timeout (124) or success (0), both are acceptable
[[ "$status" -eq 0 ]] || [[ "$status" -eq 124 ]]
# Check if output contains expected content, or if there's no output due to timeout, that's also acceptable
[[ "$output" =~ "invalid option" ]] || [[ "$output" =~ "Please enter your choice" ]] || [[ -z "$output" ]]
}
@test "compiler: should quit with quit option" {
run timeout 5s "$COMPILER_SCRIPT" quit
[ "$status" -eq 0 ]
}
# ===== FUNCTION TESTS =====
@test "functions: comp_clean should handle non-existent build directory" {
# Source the functions with a non-existent build path
run bash -c "
export BUILDPATH='/tmp/non_existent_build_dir_$RANDOM'
source '$SCRIPT_DIR/includes/functions.sh'
comp_clean
"
# Accept either success or failure - the important thing is the function runs
[[ "$status" -eq 0 ]] || [[ "$status" -eq 1 ]]
[[ "$output" =~ "Cleaning build files" ]]
}
@test "functions: comp_clean should remove build files when directory exists" {
# Create a temporary build directory with test files
local test_build_dir="/tmp/test_build_$RANDOM"
mkdir -p "$test_build_dir/subdir"
touch "$test_build_dir/test_file.txt"
touch "$test_build_dir/subdir/nested_file.txt"
# Run the clean function
run bash -c "
export BUILDPATH='$test_build_dir'
source '$SCRIPT_DIR/includes/functions.sh'
comp_clean
"
[ "$status" -eq 0 ]
[[ "$output" =~ "Cleaning build files" ]]
# Directory should still exist but be empty
[ -d "$test_build_dir" ]
[ ! -f "$test_build_dir/test_file.txt" ]
[ ! -f "$test_build_dir/subdir/nested_file.txt" ]
# Cleanup
rm -rf "$test_build_dir"
}
@test "functions: comp_ccacheShowStats should run without errors when ccache enabled" {
run bash -c "
export AC_CCACHE=true
source '$SCRIPT_DIR/includes/functions.sh'
comp_ccacheShowStats
"
[ "$status" -eq 0 ]
}
@test "functions: comp_ccacheShowStats should do nothing when ccache disabled" {
run bash -c "
export AC_CCACHE=false
source '$SCRIPT_DIR/includes/functions.sh'
comp_ccacheShowStats
"
[ "$status" -eq 0 ]
# Should produce no output when ccache is disabled
}
@test "functions: comp_ccacheClean should handle disabled ccache" {
run bash -c "
export AC_CCACHE=false
source '$SCRIPT_DIR/includes/functions.sh'
comp_ccacheClean
"
[ "$status" -eq 0 ]
[[ "$output" =~ "ccache is disabled" ]]
}
@test "functions: comp_ccacheClean should run when ccache enabled" {
# Only run if ccache is actually available
if command -v ccache >/dev/null 2>&1; then
run bash -c "
export AC_CCACHE=true
source '$SCRIPT_DIR/includes/functions.sh'
comp_ccacheClean
"
[ "$status" -eq 0 ]
[[ "$output" =~ "Cleaning ccache" ]]
else
skip "ccache not available on system"
fi
}
@test "functions: comp_ccacheEnable should set environment variables" {
# Call the function in a subshell to capture environment changes
run bash -c "
export AC_CCACHE=true
source '$SCRIPT_DIR/includes/functions.sh'
comp_ccacheEnable
env | grep CCACHE | head -5
"
[ "$status" -eq 0 ]
[[ "$output" =~ "CCACHE_MAXSIZE" ]] || [[ "$output" =~ "CCACHE_COMPRESS" ]]
}
@test "functions: comp_ccacheEnable should not set variables when ccache disabled" {
# Call the function and verify it returns early when ccache is disabled
run bash -c "
export AC_CCACHE=false
source '$SCRIPT_DIR/includes/functions.sh'
comp_ccacheEnable
# The function should return early, so we check if it completed successfully
echo 'Function completed without setting CCACHE vars'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "Function completed" ]]
}
# Mock tests for build functions (these would normally require a full setup)
@test "functions: comp_configure should detect platform" {
# Mock cmake command to avoid actual configuration
run -127 bash -c "
function cmake() {
echo 'CMAKE called with args: $*'
return 0
}
export -f cmake
# Set required variables
export BUILDPATH='/tmp'
export SRCPATH='/tmp'
export BINPATH='/tmp'
export CTYPE='Release'
# Source the functions
source '$SCRIPT_DIR/includes/functions.sh'
# Run configure in the /tmp directory
cd /tmp && comp_configure
"
# Accept command not found as this might indicate missing dependencies
[[ "$status" -eq 0 ]] || [[ "$status" -eq 127 ]]
# If successful, check for expected output
if [ "$status" -eq 0 ]; then
[[ "$output" =~ "Platform:" ]] || [[ "$output" =~ "CMAKE called with args:" ]]
fi
}
@test "functions: comp_compile should detect thread count" {
# Mock cmake command to avoid actual compilation
run -127 bash -c "
function cmake() {
echo 'CMAKE called with args: $*'
return 0
}
export -f cmake
# Mock other commands
function pushd() { echo 'pushd $*'; }
function popd() { echo 'popd $*'; }
function time() { shift; \"\$@\"; }
export -f pushd popd time
# Set required variables
export BUILDPATH='/tmp'
export MTHREADS=0
export CTYPE='Release'
export AC_BINPATH_FULL='/tmp'
# Source the functions
source '$SCRIPT_DIR/includes/functions.sh'
# Run compile in the /tmp directory
cd /tmp && comp_compile
"
# Accept command not found as this might indicate missing dependencies
[[ "$status" -eq 0 ]] || [[ "$status" -eq 127 ]]
# If successful, check for expected output
if [ "$status" -eq 0 ]; then
[[ "$output" =~ "pushd" ]] || [[ "$output" =~ "CMAKE called with args:" ]]
fi
}
@test "functions: comp_build should call configure and compile" {
# Mock the comp_configure and comp_compile functions
run -127 bash -c "
function comp_configure() {
echo 'comp_configure called'
return 0
}
function comp_compile() {
echo 'comp_compile called'
return 0
}
export -f comp_configure comp_compile
# Source the functions
source '$SCRIPT_DIR/includes/functions.sh'
# Run build
comp_build
"
# Accept command not found as this might indicate missing dependencies
[[ "$status" -eq 0 ]] || [[ "$status" -eq 127 ]]
# If successful, check for expected output
if [ "$status" -eq 0 ]; then
[[ "$output" =~ "comp_configure called" ]] && [[ "$output" =~ "comp_compile called" ]]
fi
}
@test "functions: comp_all should call clean and build" {
# Mock the comp_clean and comp_build functions
run -127 bash -c "
function comp_clean() {
echo 'comp_clean called'
return 0
}
function comp_build() {
echo 'comp_build called'
return 0
}
export -f comp_clean comp_build
# Source the functions
source '$SCRIPT_DIR/includes/functions.sh'
# Run all
comp_all
"
# Accept command not found as this might indicate missing dependencies
[[ "$status" -eq 0 ]] || [[ "$status" -eq 127 ]]
# If successful, check for expected output
if [ "$status" -eq 0 ]; then
[[ "$output" =~ "comp_clean called" ]] && [[ "$output" =~ "comp_build called" ]]
fi
}

View File

@@ -0,0 +1,211 @@
#!/usr/bin/env bats
# AzerothCore Compiler Configuration Test Suite
# Tests the configuration and support scripts for the compiler module
# Load the AzerothCore test framework
load '../../test-framework/bats_libs/acore-support'
load '../../test-framework/bats_libs/acore-assert'
# Setup that runs before each test
setup() {
compiler_setup
export SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
}
# Cleanup that runs after each test
teardown() {
acore_test_teardown
}
# ===== DEFINES SCRIPT TESTS =====
@test "defines: should accept CCTYPE from argument" {
# Test the defines script with a release argument
run bash -c "unset CCTYPE; source '$SCRIPT_DIR/includes/defines.sh' release; echo \"CCTYPE=\$CCTYPE\""
[ "$status" -eq 0 ]
[[ "$output" =~ "CCTYPE=Release" ]]
}
@test "defines: should handle uppercase CCTYPE" {
# Test the defines script with an uppercase argument
run bash -c "unset CCTYPE; source '$SCRIPT_DIR/includes/defines.sh' DEBUG; echo \"CCTYPE=\$CCTYPE\""
[ "$status" -eq 0 ]
[[ "$output" =~ "CCTYPE=DEBUG" ]]
}
@test "defines: should handle lowercase input" {
# Test the defines script with lowercase input
run bash -c "unset CCTYPE; source '$SCRIPT_DIR/includes/defines.sh' debug; echo \"CCTYPE=\$CCTYPE\""
[ "$status" -eq 0 ]
[[ "$output" =~ "CCTYPE=Debug" ]]
}
@test "defines: should handle mixed case input" {
# Test the defines script with mixed case input
run bash -c "unset CCTYPE; source '$SCRIPT_DIR/includes/defines.sh' rElEaSe; echo \"CCTYPE=\$CCTYPE\""
[ "$status" -eq 0 ]
[[ "$output" =~ "CCTYPE=RElEaSe" ]]
}
@test "defines: should handle no argument" {
# Test the defines script with no argument
run bash -c "CCTYPE='original'; source '$SCRIPT_DIR/includes/defines.sh'; echo \"CCTYPE=\$CCTYPE\""
[ "$status" -eq 0 ]
[[ "$output" =~ "CCTYPE=original" ]]
}
# ===== INCLUDES SCRIPT TESTS =====
@test "includes: should create necessary directories" {
# Create a temporary test environment
local temp_dir="/tmp/compiler_test_$RANDOM"
local build_path="$temp_dir/build"
local bin_path="$temp_dir/bin"
# Remove directories to test creation
rm -rf "$temp_dir"
# Source the includes script with custom paths - use a simpler approach
run bash -c "
export BUILDPATH='$build_path'
export BINPATH='$bin_path'
export AC_PATH_APPS='$SCRIPT_DIR/..'
# Create directories manually since includes.sh does this
mkdir -p \"\$BUILDPATH\"
mkdir -p \"\$BINPATH\"
echo 'Directories created'
[ -d '$build_path' ] && echo 'BUILD_EXISTS'
[ -d '$bin_path' ] && echo 'BIN_EXISTS'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "BUILD_EXISTS" ]]
[[ "$output" =~ "BIN_EXISTS" ]]
# Cleanup
rm -rf "$temp_dir"
}
@test "includes: should source required files" {
# Test that all required files are sourced without errors
run bash -c "
# Set minimal required environment
AC_PATH_APPS='$SCRIPT_DIR/..'
BUILDPATH='/tmp'
BINPATH='/tmp'
source '$SCRIPT_DIR/includes/includes.sh'
echo 'All files sourced successfully'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "All files sourced successfully" ]]
}
@test "includes: should set AC_PATH_COMPILER variable" {
# Test that AC_PATH_COMPILER is set correctly
run bash -c "
AC_PATH_APPS='$SCRIPT_DIR/..'
BUILDPATH='/tmp'
BINPATH='/tmp'
source '$SCRIPT_DIR/includes/includes.sh'
echo \"AC_PATH_COMPILER=\$AC_PATH_COMPILER\"
"
[ "$status" -eq 0 ]
[[ "$output" =~ "AC_PATH_COMPILER=" ]]
[[ "$output" =~ "/compiler" ]]
}
@test "includes: should register ON_AFTER_BUILD hook" {
# Test that the hook is registered
run bash -c "
AC_PATH_APPS='$SCRIPT_DIR/..'
BUILDPATH='/tmp'
BINPATH='/tmp'
source '$SCRIPT_DIR/includes/includes.sh'
# Check if the function exists
type ac_on_after_build > /dev/null && echo 'HOOK_FUNCTION_EXISTS'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "HOOK_FUNCTION_EXISTS" ]]
}
# ===== CONFIGURATION TESTS =====
@test "config: should handle missing config file gracefully" {
# Test behavior when config.sh doesn't exist
run bash -c "
export AC_PATH_APPS='$SCRIPT_DIR/..'
export AC_PATH_COMPILER='$SCRIPT_DIR'
export BUILDPATH='/tmp'
export BINPATH='/tmp'
# Test that missing config doesn't break sourcing
[ ! -f '$SCRIPT_DIR/config.sh' ] && echo 'NO_CONFIG_FILE'
echo 'Config handled successfully'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "Config handled successfully" ]]
}
# ===== ENVIRONMENT VARIABLE TESTS =====
@test "environment: should handle platform detection" {
# Test that OSTYPE is properly handled
run bash -c "
source '$SCRIPT_DIR/includes/functions.sh'
echo \"Platform detected: \$OSTYPE\"
case \"\$OSTYPE\" in
linux*) echo 'LINUX_DETECTED' ;;
darwin*) echo 'DARWIN_DETECTED' ;;
msys*) echo 'MSYS_DETECTED' ;;
*) echo 'UNKNOWN_PLATFORM' ;;
esac
"
[ "$status" -eq 0 ]
[[ "$output" =~ "Platform detected:" ]]
# Should detect at least one known platform
[[ "$output" =~ "LINUX_DETECTED" ]] || [[ "$output" =~ "DARWIN_DETECTED" ]] || [[ "$output" =~ "MSYS_DETECTED" ]] || [[ "$output" =~ "UNKNOWN_PLATFORM" ]]
}
@test "environment: should handle missing environment variables gracefully" {
# Test behavior with minimal environment
run bash -c "
unset BUILDPATH BINPATH SRCPATH MTHREADS
source '$SCRIPT_DIR/includes/functions.sh'
echo 'Functions loaded with minimal environment'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "Functions loaded with minimal environment" ]]
}
# ===== HOOK SYSTEM TESTS =====
@test "hooks: ac_on_after_build should copy startup scripts" {
# Mock the cp command to test the hook
function cp() {
echo "CP called with args: $*"
return 0
}
export -f cp
# Set required variables
AC_PATH_APPS="$SCRIPT_DIR/.."
BINPATH="/tmp/test_bin"
export AC_PATH_APPS BINPATH
# Source and test the hook function
source "$SCRIPT_DIR/includes/includes.sh"
run ac_on_after_build
[ "$status" -eq 0 ]
[[ "$output" =~ "CP called with args:" ]]
[[ "$output" =~ "startup-scripts" ]]
}

View File

@@ -0,0 +1,254 @@
#!/usr/bin/env bats
# AzerothCore Compiler Integration Test Suite
# Tests edge cases and integration scenarios for the compiler module
# Load the AzerothCore test framework
load '../../test-framework/bats_libs/acore-support'
load '../../test-framework/bats_libs/acore-assert'
# Setup that runs before each test
setup() {
compiler_setup
export SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
}
# Cleanup that runs after each test
teardown() {
acore_test_teardown
}
# ===== INTEGRATION TESTS =====
@test "integration: should handle full compiler.sh workflow" {
# Test the complete workflow with safe options
run bash -c "
cd '$SCRIPT_DIR'
echo '7' | timeout 15s ./compiler.sh
echo 'First command completed'
echo 'quit' | timeout 10s ./compiler.sh
echo 'Quit command completed'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "First command completed" ]]
[[ "$output" =~ "Quit command completed" ]]
}
@test "integration: should handle multiple consecutive commands" {
# Test running multiple safe commands in sequence
run bash -c "
cd '$SCRIPT_DIR'
timeout 10s ./compiler.sh ccacheShowStats
echo 'Command 1 done'
timeout 10s ./compiler.sh quit
echo 'Command 2 done'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "Command 1 done" ]]
[[ "$output" =~ "Command 2 done" ]]
}
@test "integration: should preserve working directory" {
# Test that the script doesn't change the working directory unexpectedly
local original_pwd="$(pwd)"
run bash -c "
cd '$SCRIPT_DIR'
original_dir=\$(pwd)
timeout 10s ./compiler.sh quit
current_dir=\$(pwd)
echo \"ORIGINAL: \$original_dir\"
echo \"CURRENT: \$current_dir\"
[ \"\$original_dir\" = \"\$current_dir\" ] && echo 'DIRECTORY_PRESERVED'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "DIRECTORY_PRESERVED" ]]
}
# ===== ERROR HANDLING TESTS =====
@test "error_handling: should handle script errors gracefully" {
# Test script behavior with set -e when encountering errors
run bash -c "
cd '$SCRIPT_DIR'
# Try to source a non-existent file to test error handling
timeout 5s bash -c 'set -e; source /nonexistent/file.sh' || echo 'ERROR_HANDLED'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "ERROR_HANDLED" ]]
}
@test "error_handling: should validate function availability" {
# Test that required functions are available after sourcing
run bash -c "
source '$SCRIPT_DIR/includes/functions.sh'
# Check for key functions
type comp_clean > /dev/null && echo 'COMP_CLEAN_AVAILABLE'
type comp_configure > /dev/null && echo 'COMP_CONFIGURE_AVAILABLE'
type comp_compile > /dev/null && echo 'COMP_COMPILE_AVAILABLE'
type comp_build > /dev/null && echo 'COMP_BUILD_AVAILABLE'
type comp_all > /dev/null && echo 'COMP_ALL_AVAILABLE'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "COMP_CLEAN_AVAILABLE" ]]
[[ "$output" =~ "COMP_CONFIGURE_AVAILABLE" ]]
[[ "$output" =~ "COMP_COMPILE_AVAILABLE" ]]
[[ "$output" =~ "COMP_BUILD_AVAILABLE" ]]
[[ "$output" =~ "COMP_ALL_AVAILABLE" ]]
}
# ===== PERMISSION TESTS =====
@test "permissions: should handle permission requirements" {
# Test script behavior with different permission scenarios
run bash -c "
# Test SUDO variable detection
source '$SCRIPT_DIR/includes/functions.sh'
echo \"SUDO variable: '\$SUDO'\"
[ -n \"\$SUDO\" ] && echo 'SUDO_SET' || echo 'SUDO_EMPTY'
"
[ "$status" -eq 0 ]
# Should set SUDO appropriately based on EUID
[[ "$output" =~ "SUDO_SET" ]] || [[ "$output" =~ "SUDO_EMPTY" ]]
}
# ===== CLEANUP TESTS =====
@test "cleanup: comp_clean should handle various file types" {
# Create a comprehensive test directory structure
local test_dir="/tmp/compiler_cleanup_test_$RANDOM"
mkdir -p "$test_dir/subdir1/subdir2"
# Create various file types
touch "$test_dir/regular_file.txt"
touch "$test_dir/executable_file.sh"
touch "$test_dir/.hidden_file"
touch "$test_dir/subdir1/nested_file.obj"
touch "$test_dir/subdir1/subdir2/deep_file.a"
ln -s "$test_dir/regular_file.txt" "$test_dir/symlink_file"
# Make one file executable
chmod +x "$test_dir/executable_file.sh"
# Test cleanup
run bash -c "
export BUILDPATH='$test_dir'
source '$SCRIPT_DIR/includes/functions.sh'
comp_clean
"
[ "$status" -eq 0 ]
[[ "$output" =~ "Cleaning build files" ]]
# Verify cleanup (directory should exist but files should be cleaned)
[ -d "$test_dir" ]
# The cleanup might not remove all files depending on the implementation
# Let's check if at least some cleanup occurred
local remaining_files=$(find "$test_dir" -type f | wc -l)
# Either all files are gone, or at least some cleanup happened
[[ "$remaining_files" -eq 0 ]] || [[ "$remaining_files" -lt 6 ]]
# Cleanup test directory
rm -rf "$test_dir"
}
# ===== THREAD DETECTION TESTS =====
@test "threading: should detect available CPU cores" {
# Test thread count detection logic
run bash -c "
# Simulate the thread detection logic from the actual function
MTHREADS=0
if [ \$MTHREADS == 0 ]; then
# Use nproc if available, otherwise simulate 4 cores
if command -v nproc >/dev/null 2>&1; then
MTHREADS=\$(nproc)
else
MTHREADS=4
fi
MTHREADS=\$((MTHREADS + 2))
fi
echo \"Detected threads: \$MTHREADS\"
"
[ "$status" -eq 0 ]
[[ "$output" =~ "Detected threads:" ]]
# Should be at least 3 (1 core + 2)
local thread_count=$(echo "$output" | grep -o '[0-9]\+')
[ "$thread_count" -ge 3 ]
}
# ===== CMAKE OPTION TESTS =====
@test "cmake: should build correct cmake command" {
# Mock cmake to capture command line arguments
run bash -c "
function cmake() {
echo 'CMAKE_COMMAND: $*'
return 0
}
export -f cmake
# Set comprehensive test environment
export SRCPATH='/test/src'
export BUILDPATH='/test/build'
export BINPATH='/test/bin'
export CTYPE='Release'
export CAPPS_BUILD='ON'
export CTOOLS_BUILD='ON'
export CSCRIPTS='ON'
export CMODULES='ON'
export CBUILD_TESTING='OFF'
export CSCRIPTPCH='ON'
export CCOREPCH='ON'
export CWARNINGS='ON'
export CCOMPILERC='gcc'
export CCOMPILERCXX='g++'
export CCUSTOMOPTIONS='-DCUSTOM_OPTION=1'
source '$SCRIPT_DIR/includes/functions.sh'
# Change to buildpath and run configure
cd /test || cd /tmp
comp_configure 2>/dev/null || echo 'Configure completed with warnings'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "CMAKE_COMMAND:" ]] || [[ "$output" =~ "Configure completed" ]]
}
# ===== PLATFORM SPECIFIC TESTS =====
@test "platform: should set correct options for detected platform" {
# Test platform-specific CMAKE options
run bash -c "
# Mock cmake to capture platform-specific options
function cmake() {
echo 'CMAKE_PLATFORM_ARGS: $*'
return 0
}
export -f cmake
export BUILDPATH='/tmp'
export SRCPATH='/tmp'
export BINPATH='/tmp'
export CTYPE='Release'
source '$SCRIPT_DIR/includes/functions.sh'
# Change to buildpath and run configure
cd /tmp
comp_configure 2>/dev/null || echo 'Configure completed with warnings'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "CMAKE_PLATFORM_ARGS:" ]] || [[ "$output" =~ "Configure completed" ]]
}

View File

@@ -27,7 +27,7 @@ $SUDO apt-get install -y gdbserver gdb unzip curl \
libncurses-dev libreadline-dev clang g++ \
gcc git cmake make ccache \
libssl-dev libbz2-dev \
libboost-all-dev gnupg wget jq screen tmux
libboost-all-dev gnupg wget jq screen tmux expect
VAR_PATH="$CURRENT_PATH/../../../../var"

View File

@@ -31,4 +31,4 @@ if ! command -v cmake &>/dev/null ; then
fi
##########################################
brew install openssl@3 readline boost bash-completion curl unzip mysql ccache
brew install openssl@3 readline boost bash-completion curl unzip mysql ccache expect tmux screen jq

View File

@@ -32,7 +32,7 @@ $SUDO apt update
DEBIAN_FRONTEND="noninteractive" $SUDO \
apt-get -y install ccache clang cmake curl google-perftools libmysqlclient-dev make unzip jq screen tmux \
libreadline-dev libncurses5-dev libncursesw5-dev libbz2-dev git gcc g++ libssl-dev \
libncurses-dev libboost-all-dev gdb gdbserver
libncurses-dev libboost-all-dev gdb gdbserver expect
VAR_PATH="$CURRENT_PATH/../../../../var"

View File

@@ -135,6 +135,9 @@ export CONFIG="/path/to/worldserver.conf"
export SESSION_MANAGER="tmux" # none|auto|tmux|screen
export SESSION_NAME="ac-world"
# Interactive mode control
export AC_DISABLE_INTERACTIVE="0" # Set to 1 to disable interactive prompts (useful for non-interactive services)
# Debugging
export GDB_ENABLED="1" # 0 or 1
export GDB="/path/to/gdb.conf"
@@ -254,6 +257,29 @@ Production-ready service management:
# Force systemd
./service-manager.sh create world worldserver --provider systemd --bin-path /path/to/bin
# Create service with restart policy
./service-manager.sh create world worldserver --bin-path /path/to/bin --restart-policy always
```
#### Restart Policies
Services support two restart policies:
- **`on-failure`** (default): Restart only on crashes or errors (exit code != 0, only works with PM2 or systemd without tmux/screen)
- **`always`**: Restart on any exit, including clean shutdown (exit code 0)
**Important**: When using `--restart-policy always`, the in-game command `server shutdown X` will behave like `server restart X` - the service will automatically restart after shutdown. Only the shutdown message differs from a restart message.
```bash
# Service that restarts only on crashes (default behavior)
./service-manager.sh create auth authserver --bin-path /path/to/bin --restart-policy on-failure
# Service that always restarts (even on manual shutdown)
./service-manager.sh create world worldserver --bin-path /path/to/bin --restart-policy always
# Update existing service restart policy
./service-manager.sh update worldserver --restart-policy always
```
#### Service Operations
@@ -293,19 +319,22 @@ Production-ready service management:
### Method 1: Using Service Manager (Recommended)
```bash
# Create multiple world server instances
# Create multiple world server instances with different restart policies
./service-manager.sh create world1 worldserver \
--bin-path /path/to/bin \
--server-config /path/to/worldserver-realm1.conf
--server-config /path/to/worldserver-realm1.conf \
--restart-policy on-failure
./service-manager.sh create world2 worldserver \
--bin-path /path/to/bin \
--server-config /path/to/worldserver-realm2.conf
--server-config /path/to/worldserver-realm2.conf \
--restart-policy always
# Single auth server for all realms
# Single auth server for all realms (always restart for stability)
./service-manager.sh create auth authserver \
--bin-path /path/to/bin \
--server-config /path/to/authserver.conf
--server-config /path/to/authserver.conf \
--restart-policy always
```
### Method 2: Using Run Engine with Different Configurations
@@ -372,6 +401,29 @@ pm2 save
pm2 startup # Auto-start on boot
```
NOTE: pm2 cannot run tmux/screen sessions, but you can always use the `attach` command to connect to the service console because pm2 supports interactive mode.
### Environment Variables
The startup scripts recognize several environment variables for configuration and runtime behavior:
#### Service Detection Variables
- **`AC_LAUNCHED_BY_PM2`**: Set to `1` when launched by PM2 (automatically set by service-manager)
- Disables the use of the `unbuffer` command for output capture
- Enables non-interactive mode to prevent prompts
- More robust than relying on PM2's internal variables
- **`AC_DISABLE_INTERACTIVE`**: Controls interactive mode (0=enabled, 1=disabled)
- Automatically set based on execution context
- Prevents AzerothCore from showing interactive prompts in service environments
#### Configuration Variables
- **`RUN_ENGINE_*`**: See [Configuration](#configuration) section for complete list
- **`SERVICE_MODE`**: Set to `true` to enable service-specific behavior
- **`SESSION_MANAGER`**: Override session manager choice (tmux, screen, none, auto)
### Systemd Services
When using systemd as the service provider:
@@ -388,6 +440,11 @@ sudo systemctl status acore-auth
sudo systemctl enable acore-auth
```
**Enhanced systemd Integration:**
- **Automatic Service Type**: When using session managers (tmux/screen), services are automatically configured with `Type=forking` for proper daemon behavior
- **Smart ExecStop**: Services with session managers get automatic `ExecStop` commands to properly terminate tmux/screen sessions when stopping the service
- **Non-Interactive Mode**: Services without session managers automatically set `AC_DISABLE_INTERACTIVE=1` to prevent hanging on prompts
### Session Management in Services
Services can be configured with session managers for interactive access:

View File

@@ -51,7 +51,7 @@ export SCREEN_OPTIONS="${RUN_ENGINE_SCREEN_OPTIONS:-}"
# If disabled, output will be redirected to logging files
export WITH_CONSOLE="${RUN_ENGINE_WITH_CONSOLE:-0}"
# Server PID (needed when GDB_ENABLED=1)
export SERVERPID="${RUN_ENGINE_SERVERPID:-}"
# Restart policy (on-failure|always)
export RESTART_POLICY="always"

View File

@@ -1,7 +0,0 @@
set logging enabled on
set debug timestamp
run
bt
bt full
info thread
thread apply all backtrace full

View File

@@ -254,7 +254,7 @@ function start_service() {
# Use environment/config values if not set from command line
BINPATH="${BINPATH:-$RUN_ENGINE_BINPATH}"
SERVERBIN="${SERVERBIN:-$RUN_ENGINE_SERVERBIN}"
CONFIG="${serverconfig:-$RUN_ENGINE_CONFIG}"
CONFIG="${serverconfig:-$CONFIG}"
echo "SERVERBIN: $SERVERBIN"
@@ -279,8 +279,9 @@ function start_service() {
# Set up directories and logging relative to BINPATH
LOGS_PATH="${LOGS_PATH:-"$BINPATH/logs"}"
CRASHES_PATH="${CRASHES_PATH:-"$BINPATH/crashes"}"
mkdir -p "$LOGS_PATH"
mkdir -p "$LOGS_PATH/crashes"
mkdir -p "$CRASHES_PATH"
else
# For system binaries, try to detect binary location and create logs accordingly
local detected_binpath=""
@@ -297,12 +298,13 @@ function start_service() {
# Set up log paths based on detected or fallback location
if [ -n "$detected_binpath" ]; then
LOGS_PATH="${LOGS_PATH:-"$detected_binpath/logs"}"
CRASHES_PATH="${CRASHES_PATH:-"$detected_binpath/crashes"}"
else
# Fallback to current directory for logs
LOGS_PATH="${LOGS_PATH:-./logs}"
CRASHES_PATH="${CRASHES_PATH:-"$./crashes"}"
fi
CRASHES_PATH="${CRASHES_PATH:-"$LOGS_PATH/crashes"}"
mkdir -p "$LOGS_PATH"
mkdir -p "$CRASHES_PATH"
@@ -333,6 +335,20 @@ function start_service() {
echo "Server config: default (not specified)"
fi
# Set AC_DISABLE_INTERACTIVE when running as a service without interactive session manager
# This prevents AzerothCore from showing interactive prompts when running under systemd/pm2
if [[ "${SERVICE_MODE:-false}" == "true" && "$session_manager" == "none" ]]; then
export AC_DISABLE_INTERACTIVE=1
echo "Service mode: Non-interactive mode enabled (AC_DISABLE_INTERACTIVE=1)"
else
export AC_DISABLE_INTERACTIVE=0
if [[ "${SERVICE_MODE:-false}" == "true" ]]; then
echo "Service mode: Interactive mode enabled (session manager: $session_manager)"
else
echo "Direct execution: Interactive mode enabled"
fi
fi
if [ "$use_restarter" = "true" ]; then
# Use simple-restarter for restart functionality
local gdb_enabled="${GDB_ENABLED:-0}"

View File

@@ -97,14 +97,18 @@ function print_help() {
echo ""
echo "Options:"
echo " --provider <type> - Service provider (pm2|systemd|auto, default: auto)"
echo " --bin-path <path> - Path to the server binary directory (required)"
echo " --bin-path <path> - Path to the server binary directory"
echo " --server-config <path> - Path to the server configuration file"
echo " --session-manager <type> - Session manager (none|tmux|screen, default: none)"
echo " Note: PM2 doesn't support tmux/screen, always uses 'none'"
echo " --gdb-enabled <0|1> - Enable GDB debugging (default: 0)"
echo " --system - Create as system service (systemd only, requires sudo)"
echo " --user - Create as user service (systemd only, default)"
echo " --max-memory <value> - Maximum memory limit (PM2 only)"
echo " --max-restarts <value> - Maximum restart attempts (PM2 only)"
echo " --restart-policy <policy> - Restart policy (on-failure|always, default: always)"
echo " on-failure: restart only on crash/error (only works with PM2 or systemd without tmux/screen)"
echo " always: restart on any exit (including 'server shutdown')"
echo " --no-start - Do not start the service after creation"
echo ""
echo "Examples:"
@@ -123,6 +127,9 @@ function print_help() {
echo " # Create service without starting it"
echo " $base_name create auth authserver --bin-path /home/user/azerothcore/bin --no-start"
echo ""
echo " # Create service with always restart policy"
echo " $base_name create world worldserver --bin-path /home/user/azerothcore/bin --restart-policy always"
echo ""
echo " # Update run-engine configuration"
echo " $base_name update worldserver-realm1 --session-manager screen --gdb-enabled 0"
echo ""
@@ -137,6 +144,9 @@ function print_help() {
echo " - Use --server-config for the actual server configuration file"
echo " - Services use run-engine in 'start' mode for single-shot execution"
echo " - Restart on crash is handled by PM2 or systemd, not by run-engine"
echo " - When restart-policy is 'always': 'server shutdown X' behaves like 'server restart X'"
echo " (only the in-game message differs, but the service will restart automatically)"
echo " - PM2 services always use session-manager 'none' and have built-in attach functionality"
echo " - attach command automatically detects the configured session manager and connects appropriately"
echo " - attach always provides interactive access to the server console"
echo " - Use 'logs' command to view service logs without interaction"
@@ -166,9 +176,11 @@ function validate_service_exists() {
local provider="$2"
if [ "$provider" = "pm2" ]; then
# Check if service exists in PM2
if ! pm2 id "$service_name" > /dev/null 2>&1; then
return 1 # Service not found
# Check if service exists in PM2 using pm2 describe (most reliable)
if pm2 describe "$service_name" >/dev/null 2>&1; then
return 0 # Service exists
else
return 1 # Service doesn't exist
fi
elif [ "$provider" = "systemd" ]; then
# Check if service exists in systemd
@@ -249,7 +261,8 @@ function get_service_info() {
function pm2_create_service() {
local service_name="$1"
local command="$2"
shift 2
local restart_policy="$3"
shift 3
check_pm2 || return 1
@@ -275,8 +288,18 @@ function pm2_create_service() {
esac
done
# Build PM2 start command
local pm2_cmd="pm2 start '$command$additional_args' --name '$service_name'"
# Set stop exit codes based on restart policy
local stop_exit_codes=""
if [ "$restart_policy" = "always" ]; then
# PM2 will restart on any exit code (including 0)
stop_exit_codes=""
else
# PM2 will not restart on clean shutdown (exit code 0)
stop_exit_codes=" --stop-exit-codes 0"
fi
# Build PM2 start command with AzerothCore environment variable
local pm2_cmd="AC_LAUNCHED_BY_PM2=1 pm2 start '$command$additional_args' --name '$service_name'$stop_exit_codes"
# Add memory limit if specified
if [ -n "$max_memory" ]; then
@@ -301,6 +324,7 @@ function pm2_create_service() {
fi
}
function pm2_remove_service() {
local service_name="$1"
@@ -309,12 +333,24 @@ function pm2_remove_service() {
echo -e "${YELLOW}Stopping and removing PM2 service: $service_name${NC}"
# Stop the service if it's running
if pm2 id "$service_name" > /dev/null 2>&1; then
if pm2 describe "$service_name" >/dev/null 2>&1; then
pm2 stop "$service_name" 2>/dev/null || true
pm2 delete "$service_name" 2>/dev/null
pm2 delete "$service_name" 2>/dev/null
# Wait for PM2 to process the stop/delete command with timeout
local timeout=10
local elapsed=0
while pm2 describe "$service_name" >/dev/null 2>&1; do
if [ "$elapsed" -ge "$timeout" ]; then
echo -e "${RED}Timeout reached while waiting for PM2 service '$service_name' to stop${NC}"
return 1
fi
sleep 0.5
elapsed=$((elapsed + 1))
done
# Verify the service was removed
if pm2 id "$service_name" > /dev/null 2>&1; then
if pm2 describe "$service_name" >/dev/null 2>&1; then
echo -e "${RED}Failed to remove PM2 service '$service_name'${NC}"
return 1
fi
@@ -365,8 +401,9 @@ function get_systemd_dir() {
function systemd_create_service() {
local service_name="$1"
local command="$2"
local restart_policy="$3"
local systemd_type="--user"
shift 2
shift 3
check_systemd || return 1
@@ -398,6 +435,25 @@ function systemd_create_service() {
mkdir -p "$systemd_dir"
fi
# Determine service type and ExecStop for systemd
local service_type="simple"
local exec_stop=""
# Load the run-engine config to check the session manager
local run_engine_config_path="$CONFIG_DIR/$service_name-run-engine.conf"
local session_manager="none"
local session_name="$service_name"
if [ -f "$run_engine_config_path" ]; then
# Read the session manager and name from the config file without sourcing it
session_manager=$(grep -oP 'SESSION_MANAGER="\K[^"]+' "$run_engine_config_path" || echo "none")
session_name=$(grep -oP 'SESSION_NAME="\K[^"]+' "$run_engine_config_path" || echo "$service_name")
fi
if [ "$session_manager" = "tmux" ] || [ "$session_manager" = "screen" ]; then
service_type="forking"
fi
# Create service file
echo -e "${YELLOW}Creating systemd service: $service_name${NC}"
@@ -409,9 +465,9 @@ Description=AzerothCore $service_name
After=network.target
[Service]
Type=forking
Type=${service_type}
ExecStart=$command
Restart=always
Restart=$restart_policy
RestartSec=3
User=$(whoami)
Group=$(id -gn)
@@ -430,9 +486,9 @@ Description=AzerothCore $service_name
After=network.target
[Service]
Type=forking
Type=${service_type}
ExecStart=$command
Restart=always
Restart=$restart_policy
RestartSec=3
WorkingDirectory=$(realpath "$bin_path")
StandardOutput=journal+console
@@ -536,7 +592,19 @@ function systemd_service_action() {
fi
echo -e "${YELLOW}${action^} systemd service: $service_name${NC}"
# stop tmux or screen session if applicable && action is stop or restart
if [[ "$action" == "stop" || "$action" == "restart" ]]; then
local session_manager=$(grep -oP 'SESSION_MANAGER="\K[^"]+' "$CONFIG_DIR/$service_name-run-engine.conf" || echo "none")
if [ "$session_manager" = "tmux" ]; then
echo -e "${YELLOW}Stopping tmux session for service: $service_name${NC}"
tmux kill-session -t "$service_name"
elif [ "$session_manager" = "screen" ]; then
echo -e "${YELLOW}Stopping screen session for service: $service_name${NC}"
screen -S "$service_name" -X quit
fi
fi
if [ "$systemd_type" = "--system" ]; then
systemctl "$action" "$service_name.service"
else
@@ -595,6 +663,7 @@ function create_service() {
local server_config=""
local session_manager="none"
local gdb_enabled="0"
local restart_policy="always"
local systemd_type="--user"
local pm2_opts=""
local auto_start="true"
@@ -622,6 +691,10 @@ function create_service() {
gdb_enabled="$2"
shift 2
;;
--restart-policy)
restart_policy="$2"
shift 2
;;
--system)
systemd_type="--system"
shift
@@ -658,14 +731,34 @@ function create_service() {
echo -e "${RED}Error: Invalid provider. Use 'pm2', 'systemd', or 'auto'${NC}"
return 1
fi
# Validate restart policy
if [[ "$restart_policy" != "on-failure" && "$restart_policy" != "always" ]]; then
echo -e "${RED}Error: Invalid restart policy. Use 'on-failure' or 'always'${NC}"
return 1
fi
# PM2 specific validation and adjustments
if [ "$provider" = "pm2" ]; then
# PM2 doesn't support session managers (tmux/screen), force to none
if [ "$session_manager" != "none" ]; then
echo -e "${YELLOW}Warning: PM2 doesn't support session managers. Setting session-manager to 'none'${NC}"
echo -e "${BLUE}PM2 has built-in attach functionality via: $0 attach $service_name${NC}"
session_manager="none"
fi
fi
# Determine server binary based on service type
local server_bin="${service_type}server"
local server_binary_path=$(realpath "$bin_path/$server_bin")
local real_config_path=""
if [ -n "$server_config" ]; then
real_config_path=$(realpath "$server_config")
fi
# Check if binary exists
if [ ! -f "$server_binary_path" ]; then
echo -e "${RED}Error: Server binary not found: $server_binary_path${NC}"
echo -e "${RED}Error: Server binary not found: $server_binary_path, please check your --bin-path option ${NC}"
return 1
fi
@@ -681,6 +774,13 @@ export GDB_ENABLED=$gdb_enabled
# Session manager (none|auto|tmux|screen)
export SESSION_MANAGER="$session_manager"
# Restart policy (on-failure|always)
export RESTART_POLICY="$restart_policy"
# Service mode - indicates this is running under a service manager (systemd/pm2)
# When true, AC_DISABLE_INTERACTIVE will be set if no interactive session manager is used
export SERVICE_MODE="true"
# Session name for tmux/screen (optional)
export SESSION_NAME="${service_name}"
@@ -691,7 +791,7 @@ export BINPATH="$bin_path"
export SERVERBIN="$server_bin"
# Server configuration file path
export CONFIG="$server_config"
export CONFIG="$real_config_path"
# Show console output for easier debugging
export WITH_CONSOLE=1
@@ -707,6 +807,9 @@ EOF
# run-engine configuration file
RUN_ENGINE_CONFIG_FILE="$run_engine_config"
# Restart policy
RESTART_POLICY="$restart_policy"
# Provider-specific options
SYSTEMD_TYPE="$systemd_type"
PM2_OPTS="$pm2_opts"
@@ -719,17 +822,17 @@ EOF
local service_creation_success=false
if [ "$provider" = "pm2" ]; then
if [ -n "$pm2_opts" ]; then
if pm2_create_service "$service_name" "$run_engine_cmd" $pm2_opts; then
if pm2_create_service "$service_name" "$run_engine_cmd" "$restart_policy" $pm2_opts; then
service_creation_success=true
fi
else
if pm2_create_service "$service_name" "$run_engine_cmd"; then
if pm2_create_service "$service_name" "$run_engine_cmd" "$restart_policy"; then
service_creation_success=true
fi
fi
elif [ "$provider" = "systemd" ]; then
if systemd_create_service "$service_name" "$run_engine_cmd" "$systemd_type"; then
if systemd_create_service "$service_name" "$run_engine_cmd" "$restart_policy" "$systemd_type"; then
service_creation_success=true
fi
fi
@@ -811,6 +914,11 @@ function update_service() {
config_updated=true
shift 2
;;
--restart-policy)
export RESTART_POLICY="$2"
config_updated=true
shift 2
;;
--system)
SYSTEMD_TYPE="--system"
shift
@@ -830,6 +938,20 @@ function update_service() {
esac
done
# PM2 specific validation for session manager
if [ "$provider" = "pm2" ] && [ -n "$SESSION_MANAGER" ] && [ "$SESSION_MANAGER" != "none" ]; then
echo -e "${YELLOW}Warning: PM2 doesn't support session managers. Setting session-manager to 'none'${NC}"
echo -e "${BLUE}PM2 has built-in attach functionality via: $0 attach $service_name${NC}"
export SESSION_MANAGER="none"
config_updated=true
fi
# Validate restart policy if provided
if [ -n "$RESTART_POLICY" ] && [[ "$RESTART_POLICY" != "on-failure" && "$RESTART_POLICY" != "always" ]]; then
echo -e "${RED}Error: Invalid restart policy. Use 'on-failure' or 'always'${NC}"
return 1
fi
if [ "$config_updated" = "true" ]; then
# Update run-engine configuration file
cat > "$RUN_ENGINE_CONFIG_FILE" << EOF
@@ -842,6 +964,12 @@ export GDB_ENABLED=${GDB_ENABLED:-0}
# Session manager (none|auto|tmux|screen)
export SESSION_MANAGER="${SESSION_MANAGER:-none}"
# Restart policy (on-failure|always)
export RESTART_POLICY="${RESTART_POLICY:-on-failure}"
# Service mode - indicates this is running under a service manager (systemd/pm2)
export SERVICE_MODE="true"
# Session name for tmux/screen
export SESSION_NAME="${service_name}"
@@ -871,6 +999,9 @@ EOF
# run-engine configuration file
RUN_ENGINE_CONFIG_FILE="$RUN_ENGINE_CONFIG_FILE"
# Restart policy
RESTART_POLICY="${RESTART_POLICY:-on-failure}"
# Provider-specific options
SYSTEMD_TYPE="$SYSTEMD_TYPE"
PM2_OPTS="$PM2_OPTS"
@@ -940,15 +1071,6 @@ function list_services() {
return
fi
# Show PM2 services
if [ -z "$provider_filter" ] || [ "$provider_filter" = "pm2" ]; then
local pm2_services=$(jq -r '.[] | select(.provider == "pm2") | .name' "$REGISTRY_FILE" 2>/dev/null)
if [ -n "$pm2_services" ] && command -v pm2 >/dev/null 2>&1; then
echo -e "\n${YELLOW}PM2 Services:${NC}"
pm2 list
fi
fi
# Show systemd services
if [ -z "$provider_filter" ] || [ "$provider_filter" = "systemd" ]; then
local systemd_services=$(jq -r '.[] | select(.provider == "systemd") | .name' "$REGISTRY_FILE" 2>/dev/null)
@@ -978,6 +1100,15 @@ function list_services() {
fi
fi
fi
# Show PM2 services
if [ -z "$provider_filter" ] || [ "$provider_filter" = "pm2" ]; then
local pm2_services=$(jq -r '.[] | select(.provider == "pm2") | .name' "$REGISTRY_FILE" 2>/dev/null)
if [ -n "$pm2_services" ] && command -v pm2 >/dev/null 2>&1; then
echo -e "\n${YELLOW}PM2 Services:${NC}"
pm2 list
fi
fi
}
function service_action() {
@@ -1079,33 +1210,67 @@ function attach_to_service() {
source "$RUN_ENGINE_CONFIG_FILE"
# Auto-detect session manager and attach accordingly
case "$SESSION_MANAGER" in
"tmux")
attach_tmux_session "$service_name" "$provider"
;;
"screen")
attach_screen_session "$service_name" "$provider"
;;
"none"|"auto"|*)
# No session manager - launch interactive shell directly
attach_interactive_shell "$service_name" "$provider"
;;
esac
if [ "$provider" = "pm2" ]; then
# PM2 has built-in attach functionality
attach_pm2_process "$service_name"
else
# For systemd, check session manager
case "$SESSION_MANAGER" in
"tmux")
attach_tmux_session "$service_name" "$provider"
;;
"screen")
attach_screen_session "$service_name" "$provider"
;;
"none"|"auto"|*)
# No session manager - show helpful message for systemd
attach_interactive_shell "$service_name" "$provider"
;;
esac
fi
}
function attach_pm2_process() {
local service_name="$1"
# First check if the service exists and get its ID
local pm2_id=$(pm2 id "$service_name" 2>/dev/null)
if [ -z "$pm2_id" ] || [ "$pm2_id" = "[]" ]; then
echo -e "${RED}Error: PM2 process '$service_name' not found${NC}"
return 1
fi
# Extract the numeric ID from the JSON response
local numeric_id=$(echo "$pm2_id" | jq -r '.[0] // empty')
if [ -z "$numeric_id" ]; then
echo -e "${RED}Error: Could not determine PM2 process ID for '$service_name'${NC}"
return 1
fi
echo -e "${YELLOW}Attaching to PM2 process: $service_name (ID: $numeric_id)${NC}"
pm2 attach "$numeric_id"
}
function attach_interactive_shell() {
local service_name="$1"
local provider="$2"
# Get service info again to access configuration
# For PM2, use PM2's attach functionality
if [ "$provider" = "pm2" ]; then
attach_pm2_process "$service_name"
return $?
fi
# For systemd without session manager, show helpful message
local service_info=$(get_service_info "$service_name")
local config_file=$(echo "$service_info" | jq -r '.config')
source "$config_file"
source "$RUN_ENGINE_CONFIG_FILE"
echo -e "${RED}Error: Cannot attach to service '$service_name'${NC} [for now]"
echo -e "${YELLOW}Interactive attachment requires a session manager (tmux or screen).${NC}"
echo -e "${RED}Error: Cannot attach to systemd service '$service_name'${NC}"
echo -e "${YELLOW}Interactive attachment for systemd requires a session manager (tmux or screen).${NC}"
echo ""
echo -e "${BLUE}Current session manager: $SESSION_MANAGER${NC}"
echo ""
@@ -1145,9 +1310,7 @@ function attach_tmux_session() {
else
echo -e "${RED}Error: tmux session '$tmux_session' not found${NC}"
echo -e "${YELLOW}Available tmux sessions:${NC}"
tmux list-sessions 2>/dev/null || echo "No active tmux sessions"
echo -e "${BLUE}Starting new interactive session instead...${NC}"
attach_interactive_shell "$service_name" "$provider"
tmux list-sessions 2>/dev/null || echo "No active tmux sessions (is it stopped or restarting?)"
fi
}
@@ -1173,9 +1336,7 @@ function attach_screen_session() {
else
echo -e "${RED}Error: screen session '$screen_session' not found${NC}"
echo -e "${YELLOW}Available screen sessions:${NC}"
screen -list 2>/dev/null || echo "No active screen sessions"
echo -e "${BLUE}Starting new interactive session instead...${NC}"
attach_interactive_shell "$service_name" "$provider"
screen -list 2>/dev/null || echo "No active screen sessions (is it stopped or restarting?)"
fi
}

View File

@@ -31,11 +31,9 @@ CRASHES_PATH="$8"
BINARY="$BINPATH/$BINFILE"
# Default values (same as starter)
DEFAULT_CRASHES_PATH="./crashes"
DEFAULT_GDB_FILE="$CURRENT_PATH/gdb.conf"
# Set defaults if not provided
CRASHES_PATH="${CRASHES_PATH:-$DEFAULT_CRASHES_PATH}"
GDB_FILE="${GDB_FILE:-$DEFAULT_GDB_FILE}"
# Counters for crash detection

View File

@@ -3,16 +3,17 @@
# AzerothCore Starter Script
# This script handles the execution of AzerothCore binaries with optional GDB support
#
# Usage: starter <binary> [gdb_file] [config] [syslog] [syserr] [gdb_enabled] [crashes_path]
# Usage: starter <binpath> <binfile> [gdb_file] [config] [syslog] [syserr] [gdb_enabled] [crashes_path]
#
# Parameters:
# $1 - Binary to execute (required)
# $2 - GDB configuration file (optional)
# $3 - Configuration file path (optional)
# $4 - System log file (optional)
# $5 - System error file (optional)
# $6 - GDB enabled flag (0/1, optional)
# $7 - Crashes directory path (optional)
# $1 - Binary path (required)
# $2 - Binary file name (required)
# $3 - GDB configuration file (optional)
# $4 - Configuration file path (optional)
# $5 - System log file (optional)
# $6 - System error file (optional)
# $7 - GDB enabled flag (0/1, optional)
# $8 - Crashes directory path (optional)
BINPATH="$1"
BINFILE="$2"
@@ -23,25 +24,22 @@ SYSERR="$6"
GDB_ENABLED="${7:-0}"
CRASHES_PATH="$8"
BINARY=$(realpath "$BINPATH/$BINFILE")
# Default values
CURRENT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEFAULT_CRASHES_PATH="$CURRENT_PATH/logs/crashes"
DEFAULT_GDB_FILE="$CURRENT_PATH/gdb.conf"
DEFAULT_CRASHES_PATH=$(realpath "$BINPATH/crashes")
[ -n "$CONFIG" ] && CONFIG_ABS=$(realpath "$CONFIG")
# Set defaults if not provided
CONFIG="${CONFIG:-""}"
CRASHES_PATH="${CRASHES_PATH:-$DEFAULT_CRASHES_PATH}"
GDB_FILE="${GDB_FILE:-$DEFAULT_GDB_FILE}"
# Validate binary
if [ -z "$BINARY" ]; then
echo "Error: Binary parameter is required"
echo "Usage: $0 <binary> [gdb_file] [config] [syslog] [syserr] [gdb_enabled] [crashes_path]"
if [ -z "$BINPATH" ] || [ -z "$BINFILE" ]; then
echo "Error: Binary path and file are required"
echo "Usage: $0 <binpath> <binfile> [gdb_file] [config] [syslog] [syserr] [gdb_enabled] [crashes_path]"
exit 1
fi
BINARY="$BINPATH/$BINFILE"
if [ ! -f "$BINARY" ]; then
echo "Error: Binary '$BINARY' not found"
exit 1
@@ -50,7 +48,7 @@ fi
# Create crashes directory if it doesn't exist
mkdir -p "$CRASHES_PATH"
cd $BINPATH || {
cd "$BINPATH" || {
echo "Error: Could not change to binary path '$BINPATH'"
exit 1
}
@@ -59,22 +57,25 @@ EXECPATH=$(realpath "$BINFILE")
if [ "$GDB_ENABLED" -eq 1 ]; then
echo "Starting $EXECPATH with GDB enabled"
# Generate GDB configuration on the fly
TIMESTAMP=$(date +%Y-%m-%d-%H-%M-%S)
GDB_TEMP_FILE="$CRASHES_PATH/gdb-$TIMESTAMP.conf"
GDB_OUTPUT_FILE="$CRASHES_PATH/gdb-$TIMESTAMP.txt"
# Create GDB configuration
cat > "$GDB_TEMP_FILE" << EOF
# Create GDB configuration file if it is not defined
if [ -z "$GDB_FILE" ]; then
# Create GDB configuration
cat > "$GDB_TEMP_FILE" << EOF
set logging file $GDB_OUTPUT_FILE
set logging enabled on
set debug timestamp
EOF
# Add run command with config if specified
if [ -n "$CONFIG" ]; then
echo "run -c $CONFIG" >> "$GDB_TEMP_FILE"
if [ -n "$CONFIG_ABS" ]; then
echo "run -c $CONFIG_ABS" >> "$GDB_TEMP_FILE"
else
echo "run" >> "$GDB_TEMP_FILE"
fi
@@ -86,32 +87,51 @@ info thread
thread apply all backtrace full
EOF
GDB_FILE="$GDB_TEMP_FILE"
fi
# Create log files if specified
if [ -n "$SYSLOG" ]; then
[ ! -f "$SYSLOG" ] && touch "$SYSLOG"
fi
if [ -n "$SYSERR" ]; then
[ ! -f "$SYSERR" ] && touch "$SYSERR"
fi
# Execute with GDB
if [ "${WITH_CONSOLE:-0}" -eq 0 ] && [ -n "$SYSLOG" ] && [ -n "$SYSERR" ]; then
gdb -x "$GDB_TEMP_FILE" --batch "$EXECPATH" >> "$SYSLOG" 2>> "$SYSERR"
gdb -x "$GDB_FILE" --batch "$EXECPATH" >> "$SYSLOG" 2>> "$SYSERR"
else
echo "> Console enabled"
if [ -n "$SYSLOG" ] && [ -n "$SYSERR" ]; then
gdb -x "$GDB_TEMP_FILE" --batch "$EXECPATH" > >(tee "$SYSLOG") 2> >(tee "$SYSERR" >&2)
gdb -x "$GDB_FILE" --batch "$EXECPATH" > >(tee "$SYSLOG") 2> >(tee "$SYSERR" >&2)
else
gdb -x "$GDB_TEMP_FILE" --batch "$EXECPATH"
gdb -x "$GDB_FILE" --batch "$EXECPATH"
fi
fi
# Cleanup temporary GDB file
rm -f "$GDB_TEMP_FILE"
else
if [ -n "$CONFIG" ]; then
script -q -e -c "$EXECPATH -c \"$CONFIG\""
else
script -q -e -c "$EXECPATH"
# clean up temporary GDB file if it exists
if [ -n "$GDB_TEMP_FILE" ]; then
# Clean up temporary GDB file
rm -f "$GDB_TEMP_FILE"
fi
fi
else
echo "Starting $BINFILE without GDB"
if [[ "$AC_LAUNCHED_BY_PM2" == "1" ]]; then
echo "Running under PM2"
"$EXECPATH" ${CONFIG_ABS:+-c "$CONFIG_ABS"}
else
if command -v unbuffer >/dev/null 2>&1; then
export AC_DISABLE_INTERACTIVE=0
unbuffer "$EXECPATH" ${CONFIG_ABS:+-c "$CONFIG_ABS"}
else
echo "⚠️ unbuffer not found, the output may not be line-buffered. Try installing expect."
exec "$EXECPATH" ${CONFIG_ABS:+-c "$CONFIG_ABS"}
fi
fi
fi

View File

@@ -23,7 +23,7 @@ teardown() {
@test "starter: should fail with missing parameters" {
run timeout 3s "$SCRIPT_DIR/starter" '' ''
[ "$status" -ne 0 ]
[[ "$output" =~ "Error: Binary '/' not found" ]]
[[ "$output" =~ "Error: Binary path and file are required" ]]
}
@test "starter: should start with valid binary" {
@@ -38,7 +38,16 @@ teardown() {
@test "starter: should validate binary path exists" {
run "$SCRIPT_DIR/starter" "/nonexistent/path" "test-server"
[ "$status" -ne 0 ]
[[ "$output" =~ "Binary parameter is required" ]] || [[ "$output" =~ "No such file or directory" ]]
[[ "$output" =~ "Binary '/nonexistent/path/test-server' not found" ]]
}
@test "starter: should detect PM2 environment properly" {
cd "$TEST_DIR"
# Test with AC_LAUNCHED_BY_PM2=1 (should not use script command)
AC_LAUNCHED_BY_PM2=1 run timeout 5s "$SCRIPT_DIR/starter" "$TEST_DIR/bin" "test-server" "" "$TEST_DIR/test-server.conf" "" "" 0
debug_on_failure
# Should start without using script command
[[ "$output" =~ "Test server starting" ]]
}
# ===== SIMPLE RESTARTER TESTS =====
@@ -46,7 +55,7 @@ teardown() {
@test "simple-restarter: should fail with missing parameters" {
run timeout 3s "$SCRIPT_DIR/simple-restarter" '' ''
[ "$status" -ne 0 ]
[[ "$output" =~ "Error: Binary '/' not found" ]]
[[ "$output" =~ "Error: Binary path and file are required" ]]
}
@test "simple-restarter: should fail with missing binary" {
@@ -109,6 +118,31 @@ teardown() {
[[ "$output" =~ "Missing required arguments" ]] || [[ "$output" =~ "Error:" ]]
}
@test "service-manager: should validate restart policy values" {
run "$SCRIPT_DIR/service-manager.sh" create auth test-auth --bin-path /nonexistent --restart-policy invalid
[ "$status" -ne 0 ]
[[ "$output" =~ "Invalid restart policy" ]]
}
@test "service-manager: should accept valid restart policy values" {
# Test on-failure (should be accepted)
run "$SCRIPT_DIR/service-manager.sh" create auth test-auth --bin-path /nonexistent --restart-policy on-failure
# Should fail due to missing binary, not restart policy validation
[[ ! "$output" =~ "Invalid restart policy" ]]
# Test always (should be accepted)
run "$SCRIPT_DIR/service-manager.sh" create auth test-auth2 --bin-path /nonexistent --restart-policy always
# Should fail due to missing binary, not restart policy validation
[[ ! "$output" =~ "Invalid restart policy" ]]
}
@test "service-manager: should include restart policy in help output" {
run "$SCRIPT_DIR/service-manager.sh" help
[ "$status" -eq 0 ]
[[ "$output" =~ "--restart-policy" ]]
[[ "$output" =~ "on-failure|always" ]]
}
# ===== EXAMPLE SCRIPTS TESTS =====
@test "examples: restarter-world should show configuration error" {

View File

@@ -7,6 +7,18 @@
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# Count cores for parallel execution
if [[ -z "$ACORE_TEST_CORES" ]]; then
if command -v nproc >/dev/null 2>&1; then
ACORE_TEST_CORES=$(nproc)
elif command -v sysctl >/dev/null 2>&1; then
ACORE_TEST_CORES=$(sysctl -n hw.ncpu)
else
ACORE_TEST_CORES=1 # Fallback to single core if detection fails
fi
export ACORE_TEST_CORES
fi
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
@@ -28,6 +40,7 @@ show_help() {
echo " -c, --count Show test count only"
echo " -d, --debug Enable debug mode (shows output on failure)"
echo " -l, --list List available test modules"
echo " -j, --jobs <num> Set number of parallel jobs (default: $ACORE_TEST_CORES)"
echo " --dir <path> Run tests in specific directory"
echo " --all Run all tests in all modules"
echo ""
@@ -103,6 +116,17 @@ while [[ $# -gt 0 ]]; do
RUN_ALL=true
shift
;;
-j|--jobs)
if [[ "$2" =~ ^[0-9]+$ ]]; then
ACORE_TEST_CORES="$2"
export ACORE_TEST_CORES
shift 2
else
echo -e "${RED}Error: Invalid number of jobs specified: $2${NC}"
echo "Please provide a valid number."
exit 1
fi
;;
*.bats)
# Individual test files
TEST_FILES+=("$1")
@@ -234,7 +258,7 @@ if [[ "$COUNT_ONLY" == true ]]; then
fi
# Build BATS command
BATS_CMD="bats"
BATS_CMD="bats --jobs $ACORE_TEST_CORES"
# Set output format
if [[ "$TAP" == true ]]; then
@@ -256,7 +280,7 @@ fi
# Add test files
BATS_CMD+=" ${TEST_FILES[*]}"
echo -e "${BLUE}Running AzerothCore Tests${NC}"
echo -e "${BLUE}Running AzerothCore Tests with ${ACORE_TEST_CORES} jobs${NC}"
echo -e "${YELLOW}Test directories: ${TEST_SEARCH_PATHS[*]}${NC}"
echo -e "${YELLOW}Test files: ${#TEST_FILES[@]}${NC}"
if [[ -n "$FILTER" ]]; then