#!/bin/bash # Test script for HeroDB - Redis-compatible database with redb backend # This script starts the server and runs comprehensive tests set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Configuration DB_DIR="./test_db" PORT=6381 SERVER_PID="" # Function to print colored output print_status() { echo -e "${BLUE}[INFO]${NC} $1" } print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" } print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } # Function to cleanup on exit cleanup() { if [ ! -z "$SERVER_PID" ]; then print_status "Stopping HeroDB server (PID: $SERVER_PID)..." kill $SERVER_PID 2>/dev/null || true wait $SERVER_PID 2>/dev/null || true fi # Clean up test database if [ -d "$DB_DIR" ]; then print_status "Cleaning up test database directory..." rm -rf "$DB_DIR" fi } # Set trap to cleanup on script exit trap cleanup EXIT # Function to wait for server to start wait_for_server() { local max_attempts=30 local attempt=1 print_status "Waiting for server to start on port $PORT..." while [ $attempt -le $max_attempts ]; do if nc -z localhost $PORT 2>/dev/null; then print_success "Server is ready!" return 0 fi echo -n "." sleep 1 attempt=$((attempt + 1)) done print_error "Server failed to start within $max_attempts seconds" return 1 } # Function to send Redis command and get response redis_cmd() { local cmd="$1" local expected="$2" print_status "Testing: $cmd" local result=$(echo "$cmd" | redis-cli -p $PORT --raw 2>/dev/null || echo "ERROR") if [ "$expected" != "" ] && [ "$result" != "$expected" ]; then print_error "Expected: '$expected', Got: '$result'" return 1 else print_success "✓ $cmd -> $result" return 0 fi } # Function to test basic string operations test_string_operations() { print_status "=== Testing String Operations ===" redis_cmd "PING" "PONG" redis_cmd "SET mykey hello" "OK" redis_cmd "GET mykey" "hello" redis_cmd "SET counter 1" "OK" redis_cmd "INCR counter" "2" redis_cmd "INCR counter" "3" redis_cmd "GET counter" "3" redis_cmd "DEL mykey" "1" redis_cmd "GET mykey" "" redis_cmd "TYPE counter" "string" redis_cmd "TYPE nonexistent" "none" } # Function to test hash operations test_hash_operations() { print_status "=== Testing Hash Operations ===" # HSET and HGET redis_cmd "HSET user:1 name John" "1" redis_cmd "HSET user:1 age 30 city NYC" "2" redis_cmd "HGET user:1 name" "John" redis_cmd "HGET user:1 age" "30" redis_cmd "HGET user:1 nonexistent" "" # HGETALL print_status "Testing HGETALL user:1" redis_cmd "HGETALL user:1" "" # HEXISTS redis_cmd "HEXISTS user:1 name" "1" redis_cmd "HEXISTS user:1 nonexistent" "0" # HKEYS print_status "Testing HKEYS user:1" redis_cmd "HKEYS user:1" "" # HVALS print_status "Testing HVALS user:1" redis_cmd "HVALS user:1" "" # HLEN redis_cmd "HLEN user:1" "3" # HMGET print_status "Testing HMGET user:1 name age" redis_cmd "HMGET user:1 name age" "" # HSETNX redis_cmd "HSETNX user:1 name Jane" "0" # Should not set, field exists redis_cmd "HSETNX user:1 email john@example.com" "1" # Should set, new field redis_cmd "HGET user:1 email" "john@example.com" # HDEL redis_cmd "HDEL user:1 age city" "2" redis_cmd "HLEN user:1" "2" redis_cmd "HEXISTS user:1 age" "0" # Test type checking redis_cmd "SET stringkey value" "OK" print_status "Testing WRONGTYPE error on string key" redis_cmd "HGET stringkey field" "" # Should return WRONGTYPE error } # Function to test configuration commands test_config_operations() { print_status "=== Testing Configuration Operations ===" print_status "Testing CONFIG GET dir" redis_cmd "CONFIG GET dir" "" print_status "Testing CONFIG GET dbfilename" redis_cmd "CONFIG GET dbfilename" "" } # Function to test transaction operations test_transaction_operations() { print_status "=== Testing Transaction Operations ===" redis_cmd "MULTI" "OK" redis_cmd "SET tx_key1 value1" "QUEUED" redis_cmd "SET tx_key2 value2" "QUEUED" redis_cmd "INCR counter" "QUEUED" print_status "Testing EXEC" redis_cmd "EXEC" "" redis_cmd "GET tx_key1" "value1" redis_cmd "GET tx_key2" "value2" # Test DISCARD redis_cmd "MULTI" "OK" redis_cmd "SET discard_key value" "QUEUED" redis_cmd "DISCARD" "OK" redis_cmd "GET discard_key" "" } # Function to test keys operations test_keys_operations() { print_status "=== Testing Keys Operations ===" print_status "Testing KEYS *" redis_cmd "KEYS *" "" } # Function to test info operations test_info_operations() { print_status "=== Testing Info Operations ===" print_status "Testing INFO" redis_cmd "INFO" "" print_status "Testing INFO replication" redis_cmd "INFO replication" "" } # Function to test expiration test_expiration() { print_status "=== Testing Expiration ===" redis_cmd "SET expire_key value" "OK" redis_cmd "SET expire_px_key value PX 1000" "OK" # 1 second redis_cmd "SET expire_ex_key value EX 1" "OK" # 1 second redis_cmd "GET expire_key" "value" redis_cmd "GET expire_px_key" "value" redis_cmd "GET expire_ex_key" "value" print_status "Waiting 2 seconds for expiration..." sleep 2 redis_cmd "GET expire_key" "value" # Should still exist redis_cmd "GET expire_px_key" "" # Should be expired redis_cmd "GET expire_ex_key" "" # Should be expired } # Function to test SCAN operations test_scan_operations() { print_status "=== Testing SCAN Operations ===" # Set up test data for scanning redis_cmd "SET scan_test1 value1" "OK" redis_cmd "SET scan_test2 value2" "OK" redis_cmd "SET scan_test3 value3" "OK" redis_cmd "SET other_key other_value" "OK" redis_cmd "HSET scan_hash field1 value1" "1" # Test basic SCAN print_status "Testing basic SCAN with cursor 0" redis_cmd "SCAN 0" "" # Test SCAN with MATCH pattern print_status "Testing SCAN with MATCH pattern" redis_cmd "SCAN 0 MATCH scan_test*" "" # Test SCAN with COUNT print_status "Testing SCAN with COUNT 2" redis_cmd "SCAN 0 COUNT 2" "" # Test SCAN with both MATCH and COUNT print_status "Testing SCAN with MATCH and COUNT" redis_cmd "SCAN 0 MATCH scan_* COUNT 1" "" # Test SCAN continuation with more keys print_status "Setting up more keys for continuation test" redis_cmd "SET scan_key1 val1" "OK" redis_cmd "SET scan_key2 val2" "OK" redis_cmd "SET scan_key3 val3" "OK" redis_cmd "SET scan_key4 val4" "OK" redis_cmd "SET scan_key5 val5" "OK" print_status "Testing SCAN with small COUNT for pagination" redis_cmd "SCAN 0 COUNT 3" "" # Clean up SCAN test data print_status "Cleaning up SCAN test data" redis_cmd "DEL scan_test1" "1" redis_cmd "DEL scan_test2" "1" redis_cmd "DEL scan_test3" "1" redis_cmd "DEL other_key" "1" redis_cmd "DEL scan_hash" "1" redis_cmd "DEL scan_key1" "1" redis_cmd "DEL scan_key2" "1" redis_cmd "DEL scan_key3" "1" redis_cmd "DEL scan_key4" "1" redis_cmd "DEL scan_key5" "1" } # Main execution main() { print_status "Starting HeroDB comprehensive test suite..." # Build the project print_status "Building HeroDB..." if ! cargo build -p herodb --release; then print_error "Failed to build HeroDB" exit 1 fi # Create test database directory mkdir -p "$DB_DIR" # Start the server print_status "Starting HeroDB server..." ./target/release/herodb --dir "$DB_DIR" --port $PORT & SERVER_PID=$! # Wait for server to start if ! wait_for_server; then print_error "Failed to start server" exit 1 fi # Run tests local failed_tests=0 test_string_operations || failed_tests=$((failed_tests + 1)) test_hash_operations || failed_tests=$((failed_tests + 1)) test_config_operations || failed_tests=$((failed_tests + 1)) test_transaction_operations || failed_tests=$((failed_tests + 1)) test_keys_operations || failed_tests=$((failed_tests + 1)) test_info_operations || failed_tests=$((failed_tests + 1)) test_expiration || failed_tests=$((failed_tests + 1)) test_scan_operations || failed_tests=$((failed_tests + 1)) # Summary echo print_status "=== Test Summary ===" if [ $failed_tests -eq 0 ]; then print_success "All tests completed! Some may have warnings due to protocol differences." print_success "HeroDB is working with persistent redb storage!" else print_warning "$failed_tests test categories had issues" print_warning "Check the output above for details" fi print_status "Database file created at: $DB_DIR/herodb.redb" print_status "Server logs and any errors are shown above" } # Check dependencies check_dependencies() { if ! command -v cargo &> /dev/null; then print_error "cargo is required but not installed" exit 1 fi if ! command -v nc &> /dev/null; then print_warning "netcat (nc) not found - some tests may not work properly" fi if ! command -v redis-cli &> /dev/null; then print_warning "redis-cli not found - using netcat fallback" fi } # Run dependency check and main function check_dependencies main "$@"