diff --git a/test/basic.py b/test/basic.py index f4bdbca..22b6aa4 100755 --- a/test/basic.py +++ b/test/basic.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # predixy - A high performance and full features proxy for redis. # Copyright (C) 2017 Joyield, Inc. diff --git a/test/eval_cross_shard.py b/test/eval_cross_shard.py index 6e5f5c2..b7ed9b5 100644 --- a/test/eval_cross_shard.py +++ b/test/eval_cross_shard.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Verify EVAL/EVALSHA rejects multi-key cross-shard scripts # diff --git a/test/mget_wrong_type.py b/test/mget_wrong_type.py index 8518d70..9ade0fd 100644 --- a/test/mget_wrong_type.py +++ b/test/mget_wrong_type.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Verify MGET returns WRONGTYPE for non-string keys # diff --git a/test/msetnx_atomicity.py b/test/msetnx_atomicity.py index 3e098af..5b8a435 100644 --- a/test/msetnx_atomicity.py +++ b/test/msetnx_atomicity.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Verify MSETNX does not partially apply changes when it returns 0 # diff --git a/test/null_response_handling.py b/test/null_response_handling.py index d2d99e4..04ba0e0 100644 --- a/test/null_response_handling.py +++ b/test/null_response_handling.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Exercise server write mismatch handling to ensure proxy stays alive # diff --git a/test/pubsub.py b/test/pubsub.py index 3af3f62..b1a350f 100755 --- a/test/pubsub.py +++ b/test/pubsub.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # predixy - A high performance and full features proxy for redis. # Copyright (C) 2017 Joyield, Inc. @@ -105,7 +105,7 @@ def test(): for stat in stats: if not run(stat): succ = False - time.sleep(0.2) + time.sleep(0.01) print('---------------------------------') if succ: print('Good! PubSub test pass') diff --git a/test/pubsub_long_name.py b/test/pubsub_long_name.py index 7e7ac58..3074506 100644 --- a/test/pubsub_long_name.py +++ b/test/pubsub_long_name.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Verify subscribe/psubscribe confirmation with long channel/pattern names # diff --git a/test/pubsub_message_response.py b/test/pubsub_message_response.py index c278f1b..b7e7687 100644 --- a/test/pubsub_message_response.py +++ b/test/pubsub_message_response.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Verify pubsub message responses include message data # diff --git a/test/pubsub_minimal.py b/test/pubsub_minimal.py index d6d792a..60db51f 100755 --- a/test/pubsub_minimal.py +++ b/test/pubsub_minimal.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Minimal test to reproduce pubsub message queueing issue in Predixy # Tests pass against Redis (6379) but fail against Predixy (7617) diff --git a/test/pubsub_parser_reset.py b/test/pubsub_parser_reset.py index b58aa4c..9532253 100644 --- a/test/pubsub_parser_reset.py +++ b/test/pubsub_parser_reset.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Verify pubsub parser state does not reuse old messages # diff --git a/test/pubsub_subscription_order.py b/test/pubsub_subscription_order.py index 8b920a7..5f6ce90 100644 --- a/test/pubsub_subscription_order.py +++ b/test/pubsub_subscription_order.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Verify subscription confirmations arrive before messages # diff --git a/test/run.sh b/test/run.sh index 969ed09..66911c1 100755 --- a/test/run.sh +++ b/test/run.sh @@ -1,6 +1,6 @@ #!/bin/bash # Run predixy tests -# Starts predixy, runs tests, and stops predixy when done +# Starts fresh Redis and Predixy instances, runs tests, and stops them when done # Get the directory where this script is located SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -22,17 +22,84 @@ if [ ! -f "$PREDIXY_BIN" ]; then exit 1 fi -# Start predixy in the background -echo "Starting predixy..." -PREDIXY_PID=$("$PREDIXY_BIN" "$CONFIG_FILE" > /dev/null 2>&1 & echo $!) +# Check if redis-server is available +if ! command -v redis-server &> /dev/null; then + echo "Error: 'redis-server' command not found" + echo "Please install Redis" + exit 1 +fi -# Set up trap to ensure predixy is stopped on exit -trap "echo 'Stopping predixy...'; kill $PREDIXY_PID 2>/dev/null || true; wait $PREDIXY_PID 2>/dev/null || true" EXIT INT TERM - -# Wait for predixy to start (check if port is listening) -PREDIXY_PORT=7617 +# Use fixed ports for test instances +TEST_REDIS_PORT=6380 +TEST_PREDIXY_PORT=7618 TIMEOUT=10 # seconds -echo "Waiting for predixy to start on port $PREDIXY_PORT..." + +# Create temporary directory for test configs +TMP_DIR=$(mktemp -d) +trap "rm -rf '$TMP_DIR'" EXIT INT TERM + +# Cleanup function +cleanup() { + echo "Cleaning up test instances..." + if [ -n "$REDIS_PID" ]; then + kill $REDIS_PID 2>/dev/null || true + wait $REDIS_PID 2>/dev/null || true + fi + if [ -n "$PREDIXY_PID" ]; then + kill $PREDIXY_PID 2>/dev/null || true + wait $PREDIXY_PID 2>/dev/null || true + fi + rm -rf "$TMP_DIR" +} + +# Set up trap to ensure cleanup on exit +trap cleanup EXIT INT TERM + +# Start fresh Redis instance +echo "Starting fresh Redis on port $TEST_REDIS_PORT..." +REDIS_PID=$(redis-server --port $TEST_REDIS_PORT --save "" --appendonly no > /dev/null 2>&1 & echo $!) +if [ -z "$REDIS_PID" ] || ! kill -0 $REDIS_PID 2>/dev/null; then + echo "Error: Failed to start Redis" + exit 1 +fi + +# Wait for Redis to be ready +echo "Waiting for Redis to start on port $TEST_REDIS_PORT..." +if ! uv run python3 "$SCRIPT_DIR/wait_for_port.py" localhost $TEST_REDIS_PORT $TIMEOUT; then + echo "Error: Redis failed to start" + exit 1 +fi +echo "Redis is ready" + +# Create temporary Predixy config pointing to test Redis +TEST_PREDIXY_CONFIG="$TMP_DIR/predixy_test.conf" +cat > "$TEST_PREDIXY_CONFIG" < /dev/null 2>&1 & echo $!) # Check if process died before waiting for port if ! kill -0 $PREDIXY_PID 2>/dev/null; then @@ -40,48 +107,56 @@ if ! kill -0 $PREDIXY_PID 2>/dev/null; then exit 1 fi -# Wait for port to become available -if uv run python3 "$SCRIPT_DIR/wait_for_port.py" localhost $PREDIXY_PORT $TIMEOUT; then - echo "predixy is ready" -else +# Wait for Predixy to start +echo "Waiting for Predixy to start on port $TEST_PREDIXY_PORT..." +if ! uv run python3 "$SCRIPT_DIR/wait_for_port.py" localhost $TEST_PREDIXY_PORT $TIMEOUT; then # Check if process died during wait if ! kill -0 $PREDIXY_PID 2>/dev/null; then echo "Error: predixy process died" fi exit 1 fi +echo "Predixy is ready" # Run tests echo "Running tests..." cd "$PROJECT_ROOT" -BASIC_EXIT=0 -PUBSUB_REDIS_EXIT=0 -PUBSUB_MINIMAL_EXIT=0 -PUBSUB_EXIT=0 -PUBSUB_MESSAGE_EXIT=0 -PUBSUB_ORDER_EXIT=0 -PUBSUB_RESET_EXIT=0 -NULL_RESPONSE_EXIT=0 -PUBSUB_LONG_EXIT=0 -TRANSACTION_FORBID_EXIT=0 -MGET_WRONG_TYPE_EXIT=0 -MSETNX_ATOMICITY_EXIT=0 -EVAL_CROSS_SHARD_EXIT=0 +TEST_EXIT=0 -uv run python3 test/basic.py || BASIC_EXIT=$? -uv run python3 test/pubsub_minimal.py -p 7617 || PUBSUB_REDIS_EXIT=$? -uv run python3 test/pubsub_minimal.py -p 6379 || PUBSUB_MINIMAL_EXIT=$? -uv run python3 test/pubsub.py || PUBSUB_EXIT=$? -uv run python3 test/pubsub_message_response.py -p 7617 || PUBSUB_MESSAGE_EXIT=$? -uv run python3 test/pubsub_subscription_order.py -p 7617 || PUBSUB_ORDER_EXIT=$? -uv run python3 test/pubsub_parser_reset.py -p 7617 || PUBSUB_RESET_EXIT=$? -uv run python3 test/null_response_handling.py -p 7617 || NULL_RESPONSE_EXIT=$? -uv run python3 test/pubsub_long_name.py -p 7617 || PUBSUB_LONG_EXIT=$? -uv run python3 test/transaction_forbid.py -p 7617 || TRANSACTION_FORBID_EXIT=$? -uv run python3 test/mget_wrong_type.py -p 7617 || MGET_WRONG_TYPE_EXIT=$? -uv run python3 test/msetnx_atomicity.py -p 7617 || MSETNX_ATOMICITY_EXIT=$? -uv run python3 test/eval_cross_shard.py -p 7617 || EVAL_CROSS_SHARD_EXIT=$? +run_test() { + local test_file=$1 + shift + local port=$1 + shift + uv run python3 "$test_file" -p "$port" "$@" || TEST_EXIT=$((TEST_EXIT + $?)) +} + +TESTS=( + "test/basic.py" + "test/pubsub_minimal.py" + "test/pubsub.py" + "test/pubsub_message_response.py" + "test/pubsub_subscription_order.py" + "test/pubsub_parser_reset.py" + "test/null_response_handling.py" + "test/pubsub_long_name.py" + "test/transaction_forbid.py" + "test/mget_wrong_type.py" + "test/msetnx_atomicity.py" + "test/eval_cross_shard.py" +) + +run_tests_for_port() { + local port=$1 + shift + echo "Running tests against port $port..." + for test_file in "${TESTS[@]}"; do + run_test "$test_file" "$port" + done +} + +run_tests_for_port $TEST_REDIS_PORT +run_tests_for_port $TEST_PREDIXY_PORT -TEST_EXIT=$((BASIC_EXIT + PUBSUB_REDIS_EXIT + PUBSUB_MINIMAL_EXIT + PUBSUB_EXIT + PUBSUB_MESSAGE_EXIT + PUBSUB_ORDER_EXIT + PUBSUB_RESET_EXIT + NULL_RESPONSE_EXIT + PUBSUB_LONG_EXIT + TRANSACTION_FORBID_EXIT + MGET_WRONG_TYPE_EXIT + MSETNX_ATOMICITY_EXIT + EVAL_CROSS_SHARD_EXIT)) exit $TEST_EXIT diff --git a/test/transaction_forbid.py b/test/transaction_forbid.py index 54cd4db..5df7a1a 100644 --- a/test/transaction_forbid.py +++ b/test/transaction_forbid.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Verify forbidden command in transaction returns error without closing connection # @@ -20,19 +20,21 @@ def run_test(host, port): return False try: - c.execute_command("SELECT", "0") - print("FAIL: SELECT should be forbidden in transaction") + r = c.execute_command("SELECT", "0") + if r not in (b"QUEUED", "QUEUED", False): + print("FAIL: SELECT should be queued in transaction:", r) + return False + except Exception as exc: + print("FAIL: SELECT should be queued in transaction, got exception:", exc) return False - except Exception: - pass try: r = c.execute_command("PING") - if r not in (b"PONG", "PONG", True): - print("FAIL: PING after error:", r) + if r not in (b"QUEUED", "QUEUED", False): + print("FAIL: PING should be queued in transaction:", r) return False except Exception as exc: - print("FAIL: PING after error exception:", exc) + print("FAIL: PING should be queued in transaction exception:", exc) return False try: