#!/bin/bash ################################################################################ # Server Toolkit - Functional Testing Module ################################################################################ # Purpose: Verify scripts actually work, not just pass static analysis # Usage: Source this file from toolkit-qa-check.sh or run standalone ################################################################################ # Track test results FUNC_TESTS_PASSED=0 FUNC_TESTS_FAILED=0 FUNC_TESTS_SKIPPED=0 ################################################################################ # TEST 1: Bash Syntax Validation ################################################################################ # Verify all scripts have valid bash syntax test_bash_syntax() { echo "═══════════════════════════════════════════════════════════════" echo "FUNCTIONAL TEST 1: Bash Syntax Validation" echo "═══════════════════════════════════════════════════════════════" echo "" local failed=0 local passed=0 while IFS= read -r script; do # Run bash -n (syntax check without execution) if bash -n "$script" 2>/dev/null; then ((passed++)) else ((failed++)) echo "FAIL|$script|SYNTAX|Script has syntax errors:" bash -n "$script" 2>&1 | head -5 | sed 's/^/ /' echo "" fi done < <(find "$TOOLKIT_PATH" -name "*.sh" -type f 2>/dev/null) echo "Results: $passed passed, $failed failed" echo "" FUNC_TESTS_PASSED=$((FUNC_TESTS_PASSED + passed)) FUNC_TESTS_FAILED=$((FUNC_TESTS_FAILED + failed)) } ################################################################################ # TEST 2: Function Call Validation ################################################################################ # Verify all called functions are actually defined test_function_calls() { echo "═══════════════════════════════════════════════════════════════" echo "FUNCTIONAL TEST 2: Function Call Validation" echo "═══════════════════════════════════════════════════════════════" echo "" local issues=0 # Check each script while IFS= read -r script; do # Skip test files themselves [[ "$script" =~ qa.*test ]] && continue # Extract function definitions in this script and sourced files local defined_functions=$(mktemp) # Get functions defined in this script (must have opening brace or be multi-line) # Pattern: function_name() { or just function_name() at start of line (not in comments) grep -E "^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*\s*\(\)\s*\{" "$script" 2>/dev/null | \ sed -E 's/^[[:space:]]*([a-zA-Z_][a-zA-Z0-9_]*).*/\1/' >> "$defined_functions" # Also catch: function function_name { grep -E "^[[:space:]]*function\s+[a-zA-Z_][a-zA-Z0-9_]*" "$script" 2>/dev/null | \ sed -E 's/^[[:space:]]*function\s+([a-zA-Z_][a-zA-Z0-9_]*).*/\1/' >> "$defined_functions" # Get sourced files and their functions local sourced_files=$(grep -oE 'source.*\.sh|\..*\.sh' "$script" 2>/dev/null | \ grep -oE '[a-zA-Z0-9_-]+\.sh') for sourced in $sourced_files; do # Find the actual file local source_path=$(find "$TOOLKIT_PATH" -name "$sourced" -type f 2>/dev/null | head -1) if [ -f "$source_path" ]; then # Use same strict pattern for sourced files grep -E "^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*\s*\(\)\s*\{" "$source_path" 2>/dev/null | \ sed -E 's/^[[:space:]]*([a-zA-Z_][a-zA-Z0-9_]*).*/\1/' >> "$defined_functions" grep -E "^[[:space:]]*function\s+[a-zA-Z_][a-zA-Z0-9_]*" "$source_path" 2>/dev/null | \ sed -E 's/^[[:space:]]*function\s+([a-zA-Z_][a-zA-Z0-9_]*).*/\1/' >> "$defined_functions" fi done # Find actual function calls (word followed by parenthesis or as command) local called_functions=$(mktemp) # Look for patterns like: function_name() but exclude comments and definitions # Exclude lines starting with # and lines with { (definitions) grep -v "^[[:space:]]*#" "$script" 2>/dev/null | \ grep -oE '([a-zA-Z_][a-zA-Z0-9_]*)\s*\(' | \ grep -v '{' | \ sed 's/\s*($//' >> "$called_functions" # Also find standalone function calls (but this creates many false positives) # Skip for now to reduce noise sort -u "$called_functions" | \ grep -v "^if$\|^then$\|^else$\|^fi$\|^do$\|^done$\|^case$\|^esac$\|^for$\|^while$" \ > "$called_functions.tmp" mv "$called_functions.tmp" "$called_functions" # Check each called function while IFS= read -r func; do [ -z "$func" ] && continue # Skip common bash builtins and external commands if ! command -v "$func" >/dev/null 2>&1 && \ ! grep -qx "$func" "$defined_functions" 2>/dev/null; then # Report undefined custom functions if [[ "$func" =~ ^(get_|check_|print_|show_|detect_|list_|find_|validate_|extract_|parse_|format_|analyze_|build_|test_) ]]; then echo "WARN|$script|UNDEFINED|Function '$func()' called but not defined" ((issues++)) fi fi done < "$called_functions" rm -f "$defined_functions" "$called_functions" done < <(find "$TOOLKIT_PATH/modules" "$TOOLKIT_PATH/tools" -name "*.sh" -type f 2>/dev/null) if [ $issues -eq 0 ]; then echo "✓ All function calls appear valid" ((FUNC_TESTS_PASSED++)) else echo "✗ Found $issues potential undefined function calls" ((FUNC_TESTS_FAILED++)) fi echo "" } ################################################################################ # TEST 3: Dependency Validation ################################################################################ # Verify all sourced files actually exist test_dependencies() { echo "═══════════════════════════════════════════════════════════════" echo "FUNCTIONAL TEST 3: Dependency Validation" echo "═══════════════════════════════════════════════════════════════" echo "" local missing=0 local found=0 local skipped_vars=0 while IFS= read -r script; do # Find all source commands while IFS= read -r source_line; do # Extract the file being sourced (handle various patterns) local sourced_file=$(echo "$source_line" | sed 's/.*source\s\+//; s/.*\.\s\+//' | awk '{print $1}' | tr -d '"' | head -1) # Skip if it's primarily a variable (we can't validate dynamic paths easily) if [[ "$sourced_file" =~ ^\$ ]] || [[ "$sourced_file" =~ ^\$\{ ]]; then ((skipped_vars++)) continue fi # Try to resolve common variable patterns sourced_file=$(echo "$sourced_file" | sed \ -e "s|\$SCRIPT_DIR|$(dirname "$script")|" \ -e "s|\$TOOLKIT_PATH|$TOOLKIT_PATH|" \ -e "s|\$_LIB_DIR|$TOOLKIT_PATH/lib|" \ -e "s|\$LIB_DIR|$TOOLKIT_PATH/lib|" \ -e "s|\$TOOLKIT_ROOT|$TOOLKIT_PATH|" \ -e 's/[{}]//g') # Skip if still contains variables [[ "$sourced_file" =~ \$ ]] && { ((skipped_vars++)); continue; } # Check if file exists if [ -f "$sourced_file" ]; then ((found++)) elif [ -f "$TOOLKIT_PATH/$sourced_file" ]; then ((found++)) elif [ -f "$TOOLKIT_PATH/lib/$sourced_file" ]; then ((found++)) else echo "FAIL|$script|MISSING-DEP|Cannot find: $sourced_file" ((missing++)) fi done < <(grep -n "^\s*source\s\|^\s*\.\s" "$script" 2>/dev/null | grep -v "^#") done < <(find "$TOOLKIT_PATH" -name "*.sh" -type f 2>/dev/null) echo "Results: $found dependencies found, $missing missing, $skipped_vars dynamic paths" echo "" if [ $missing -eq 0 ]; then ((FUNC_TESTS_PASSED++)) else ((FUNC_TESTS_FAILED++)) fi } ################################################################################ # TEST 4: Library Function Unit Tests ################################################################################ # Test key library functions with sample data test_library_functions() { echo "═══════════════════════════════════════════════════════════════" echo "FUNCTIONAL TEST 4: Library Function Unit Tests" echo "═══════════════════════════════════════════════════════════════" echo "" # Source common libraries source "$TOOLKIT_PATH/lib/common-functions.sh" 2>/dev/null || { echo "SKIP|Cannot load common-functions.sh" ((FUNC_TESTS_SKIPPED++)) return } local tests_run=0 local tests_passed=0 # Test 1: validate_email function (if it exists) if type validate_email >/dev/null 2>&1; then ((tests_run++)) source "$TOOLKIT_PATH/lib/email-functions.sh" 2>/dev/null if validate_email "test@example.com" && \ ! validate_email "invalid-email" && \ ! validate_email ""; then echo "✓ validate_email() works correctly" ((tests_passed++)) else echo "✗ validate_email() failed tests" fi fi # Test 2: is_valid_ip function if type is_valid_ip >/dev/null 2>&1; then ((tests_run++)) source "$TOOLKIT_PATH/lib/email-functions.sh" 2>/dev/null if is_valid_ip "192.168.1.1" && \ ! is_valid_ip "999.999.999.999" && \ ! is_valid_ip "not-an-ip"; then echo "✓ is_valid_ip() works correctly" ((tests_passed++)) else echo "✗ is_valid_ip() failed tests" fi fi # Test 3: format_size function if type format_size >/dev/null 2>&1; then ((tests_run++)) source "$TOOLKIT_PATH/lib/email-functions.sh" 2>/dev/null local result=$(format_size 500) if [[ "$result" == "500B" ]]; then echo "✓ format_size() works correctly" ((tests_passed++)) else echo "✗ format_size() returned unexpected: $result" fi fi echo "" echo "Results: $tests_passed/$tests_run unit tests passed" echo "" if [ $tests_run -eq 0 ]; then ((FUNC_TESTS_SKIPPED++)) elif [ $tests_passed -eq $tests_run ]; then ((FUNC_TESTS_PASSED++)) else ((FUNC_TESTS_FAILED++)) fi } ################################################################################ # TEST 5: Script Execution Smoke Tests ################################################################################ # Verify scripts can at least show help/usage without crashing test_script_execution() { echo "═══════════════════════════════════════════════════════════════" echo "FUNCTIONAL TEST 5: Script Execution Smoke Tests" echo "═══════════════════════════════════════════════════════════════" echo "" local tested=0 local passed=0 local failed=0 # Test each executable script while IFS= read -r script; do ((tested++)) # Try to run with --help (many scripts support this) if timeout 5 bash "$script" --help >/dev/null 2>&1 || \ timeout 5 bash "$script" -h >/dev/null 2>&1; then ((passed++)) else # Try to at least parse it without execution if bash -n "$script" 2>/dev/null; then echo "INFO|$script|NO-HELP|Script has no --help but syntax is valid" ((passed++)) else echo "FAIL|$script|CRASH|Script fails to parse or execute" ((failed++)) fi fi done < <(find "$TOOLKIT_PATH/modules" "$TOOLKIT_PATH/tools" -name "*.sh" -type f 2>/dev/null | head -20) echo "" echo "Results: $passed/$tested scripts validated" echo "" if [ $failed -eq 0 ]; then ((FUNC_TESTS_PASSED++)) else ((FUNC_TESTS_FAILED++)) fi } ################################################################################ # Main Test Runner ################################################################################ run_functional_tests() { echo "" echo "═══════════════════════════════════════════════════════════════" echo "FUNCTIONAL TESTING SUITE" echo "═══════════════════════════════════════════════════════════════" echo "Verifying scripts actually work, not just pass static analysis" echo "" # Run all tests test_bash_syntax test_function_calls test_dependencies test_library_functions test_script_execution # Summary echo "═══════════════════════════════════════════════════════════════" echo "FUNCTIONAL TEST SUMMARY" echo "═══════════════════════════════════════════════════════════════" echo "" echo "Tests Passed: $FUNC_TESTS_PASSED" echo "Tests Failed: $FUNC_TESTS_FAILED" echo "Tests Skipped: $FUNC_TESTS_SKIPPED" echo "" local total=$((FUNC_TESTS_PASSED + FUNC_TESTS_FAILED)) if [ $total -gt 0 ]; then local pass_rate=$((FUNC_TESTS_PASSED * 100 / total)) echo "Pass Rate: ${pass_rate}%" fi echo "" if [ $FUNC_TESTS_FAILED -gt 0 ]; then echo "⚠ Some functional tests failed - review output above" return 1 else echo "✓ All functional tests passed" return 0 fi } # If run standalone if [ "${BASH_SOURCE[0]}" = "${0}" ]; then TOOLKIT_PATH="${1:-/root/server-toolkit}" run_functional_tests fi