364 lines
9.5 KiB
Bash
Executable File
364 lines
9.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# RuVector-Postgres Test Runner
|
|
# Builds Docker image, runs tests, and cleans up
|
|
|
|
set -e # Exit on error
|
|
set -u # Exit on undefined variable
|
|
set -o pipefail # Exit on pipe failure
|
|
|
|
# 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
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
|
|
CONTAINER_NAME="ruvector-postgres-test"
|
|
IMAGE_NAME="ruvector-postgres:test"
|
|
POSTGRES_PORT="${POSTGRES_PORT:-5433}"
|
|
POSTGRES_USER="${POSTGRES_USER:-ruvector}"
|
|
POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-ruvector}"
|
|
POSTGRES_DB="${POSTGRES_DB:-ruvector_test}"
|
|
|
|
# Detect OS
|
|
OS_TYPE="$(uname -s)"
|
|
case "${OS_TYPE}" in
|
|
Linux*) PLATFORM="linux";;
|
|
Darwin*) PLATFORM="macos";;
|
|
*) PLATFORM="unknown";;
|
|
esac
|
|
|
|
# Functions
|
|
log_info() {
|
|
echo -e "${BLUE}[INFO]${NC} $1"
|
|
}
|
|
|
|
log_success() {
|
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
}
|
|
|
|
log_warn() {
|
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1"
|
|
}
|
|
|
|
cleanup() {
|
|
log_info "Cleaning up containers and volumes..."
|
|
docker stop "${CONTAINER_NAME}" 2>/dev/null || true
|
|
docker rm "${CONTAINER_NAME}" 2>/dev/null || true
|
|
if [ "${KEEP_VOLUMES:-false}" != "true" ]; then
|
|
docker volume rm "${CONTAINER_NAME}_data" 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
wait_for_postgres() {
|
|
log_info "Waiting for PostgreSQL to be healthy..."
|
|
local max_attempts=30
|
|
local attempt=1
|
|
|
|
while [ ${attempt} -le ${max_attempts} ]; do
|
|
if docker exec "${CONTAINER_NAME}" pg_isready -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" &>/dev/null; then
|
|
log_success "PostgreSQL is ready!"
|
|
return 0
|
|
fi
|
|
|
|
echo -n "."
|
|
sleep 1
|
|
attempt=$((attempt + 1))
|
|
done
|
|
|
|
log_error "PostgreSQL failed to become ready after ${max_attempts} seconds"
|
|
docker logs "${CONTAINER_NAME}"
|
|
return 1
|
|
}
|
|
|
|
build_image() {
|
|
log_info "Building Docker image: ${IMAGE_NAME}"
|
|
log_info "Platform: ${PLATFORM}"
|
|
|
|
cd "${PROJECT_ROOT}"
|
|
|
|
# Build with BuildKit for better caching
|
|
DOCKER_BUILDKIT=1 docker build \
|
|
-f crates/ruvector-postgres/docker/Dockerfile \
|
|
-t "${IMAGE_NAME}" \
|
|
--build-arg BUILDKIT_INLINE_CACHE=1 \
|
|
--progress=plain \
|
|
.
|
|
|
|
log_success "Docker image built successfully"
|
|
}
|
|
|
|
start_container() {
|
|
log_info "Starting PostgreSQL container: ${CONTAINER_NAME}"
|
|
|
|
# Create volume for data persistence
|
|
docker volume create "${CONTAINER_NAME}_data" || true
|
|
|
|
# Start container
|
|
docker run -d \
|
|
--name "${CONTAINER_NAME}" \
|
|
-p "${POSTGRES_PORT}:5432" \
|
|
-e POSTGRES_USER="${POSTGRES_USER}" \
|
|
-e POSTGRES_PASSWORD="${POSTGRES_PASSWORD}" \
|
|
-e POSTGRES_DB="${POSTGRES_DB}" \
|
|
-v "${CONTAINER_NAME}_data:/var/lib/postgresql/data" \
|
|
--health-cmd="pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}" \
|
|
--health-interval=5s \
|
|
--health-timeout=5s \
|
|
--health-retries=5 \
|
|
"${IMAGE_NAME}"
|
|
|
|
log_success "Container started"
|
|
}
|
|
|
|
run_tests() {
|
|
log_info "Running test suite..."
|
|
|
|
# Export connection string for tests
|
|
export DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DB}"
|
|
|
|
log_info "Connection string: ${DATABASE_URL}"
|
|
|
|
# Run pgrx tests
|
|
cd "${PROJECT_ROOT}/crates/ruvector-postgres"
|
|
|
|
log_info "Running pgrx tests..."
|
|
if cargo pgrx test pg16; then
|
|
log_success "All tests passed!"
|
|
return 0
|
|
else
|
|
log_error "Tests failed!"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
run_integration_tests() {
|
|
log_info "Running integration tests via SQL..."
|
|
|
|
# Wait a bit more for full initialization
|
|
sleep 2
|
|
|
|
# Test extension loading
|
|
log_info "Testing extension installation..."
|
|
docker exec -it "${CONTAINER_NAME}" psql -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -c "CREATE EXTENSION IF NOT EXISTS ruvector_postgres;" || {
|
|
log_error "Failed to create extension"
|
|
return 1
|
|
}
|
|
|
|
# Test basic vector operations
|
|
log_info "Testing basic vector operations..."
|
|
docker exec -it "${CONTAINER_NAME}" psql -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" << 'EOF'
|
|
-- Test vector creation
|
|
SELECT '[1,2,3]'::vector;
|
|
|
|
-- Test distance functions
|
|
SELECT vector_l2_distance('[1,2,3]'::vector, '[4,5,6]'::vector);
|
|
SELECT vector_cosine_distance('[1,2,3]'::vector, '[4,5,6]'::vector);
|
|
SELECT vector_inner_product('[1,2,3]'::vector, '[4,5,6]'::vector);
|
|
|
|
-- Test table creation with vector column
|
|
CREATE TABLE IF NOT EXISTS test_vectors (
|
|
id SERIAL PRIMARY KEY,
|
|
embedding vector(3)
|
|
);
|
|
|
|
-- Insert test data
|
|
INSERT INTO test_vectors (embedding) VALUES
|
|
('[1,2,3]'::vector),
|
|
('[4,5,6]'::vector),
|
|
('[7,8,9]'::vector);
|
|
|
|
-- Test similarity search
|
|
SELECT * FROM test_vectors ORDER BY embedding <-> '[1,2,3]'::vector LIMIT 3;
|
|
|
|
-- Cleanup
|
|
DROP TABLE test_vectors;
|
|
EOF
|
|
|
|
if [ $? -eq 0 ]; then
|
|
log_success "Integration tests passed!"
|
|
return 0
|
|
else
|
|
log_error "Integration tests failed!"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
collect_results() {
|
|
log_info "Collecting test results..."
|
|
|
|
# Create results directory
|
|
local results_dir="${PROJECT_ROOT}/test-results"
|
|
mkdir -p "${results_dir}"
|
|
|
|
# Export container logs
|
|
docker logs "${CONTAINER_NAME}" > "${results_dir}/postgres.log" 2>&1
|
|
|
|
# Export test database dump (if needed)
|
|
if [ "${EXPORT_DB:-false}" == "true" ]; then
|
|
log_info "Exporting database dump..."
|
|
docker exec "${CONTAINER_NAME}" pg_dump -U "${POSTGRES_USER}" "${POSTGRES_DB}" > "${results_dir}/test_db_dump.sql"
|
|
fi
|
|
|
|
log_success "Results collected in ${results_dir}"
|
|
}
|
|
|
|
show_usage() {
|
|
cat << EOF
|
|
RuVector-Postgres Test Runner
|
|
|
|
Usage: $0 [OPTIONS]
|
|
|
|
Options:
|
|
-b, --build-only Build Docker image only, don't run tests
|
|
-t, --test-only Run tests only (skip build)
|
|
-i, --integration Run integration tests only
|
|
-k, --keep-running Keep container running after tests
|
|
-c, --clean Clean up before starting
|
|
-v, --keep-volumes Keep volumes after cleanup
|
|
-p, --port PORT PostgreSQL port (default: 5433)
|
|
-h, --help Show this help message
|
|
|
|
Environment Variables:
|
|
POSTGRES_PORT PostgreSQL port (default: 5433)
|
|
POSTGRES_USER PostgreSQL user (default: ruvector)
|
|
POSTGRES_PASSWORD PostgreSQL password (default: ruvector)
|
|
POSTGRES_DB PostgreSQL database (default: ruvector_test)
|
|
KEEP_VOLUMES Keep volumes after cleanup (default: false)
|
|
EXPORT_DB Export database dump (default: false)
|
|
|
|
Examples:
|
|
# Run full test suite
|
|
$0
|
|
|
|
# Build and keep container running for debugging
|
|
$0 --keep-running
|
|
|
|
# Run integration tests only
|
|
$0 --integration --test-only
|
|
|
|
# Clean rebuild
|
|
$0 --clean --build-only
|
|
EOF
|
|
}
|
|
|
|
main() {
|
|
local build_only=false
|
|
local test_only=false
|
|
local integration_only=false
|
|
local keep_running=false
|
|
local clean_first=false
|
|
|
|
# Parse arguments
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
-b|--build-only)
|
|
build_only=true
|
|
shift
|
|
;;
|
|
-t|--test-only)
|
|
test_only=true
|
|
shift
|
|
;;
|
|
-i|--integration)
|
|
integration_only=true
|
|
shift
|
|
;;
|
|
-k|--keep-running)
|
|
keep_running=true
|
|
shift
|
|
;;
|
|
-c|--clean)
|
|
clean_first=true
|
|
shift
|
|
;;
|
|
-v|--keep-volumes)
|
|
KEEP_VOLUMES=true
|
|
shift
|
|
;;
|
|
-p|--port)
|
|
POSTGRES_PORT="$2"
|
|
shift 2
|
|
;;
|
|
-h|--help)
|
|
show_usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
log_error "Unknown option: $1"
|
|
show_usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Setup trap for cleanup
|
|
if [ "${keep_running}" != "true" ]; then
|
|
trap cleanup EXIT
|
|
fi
|
|
|
|
log_info "RuVector-Postgres Test Runner"
|
|
log_info "Platform: ${PLATFORM}"
|
|
log_info "PostgreSQL Port: ${POSTGRES_PORT}"
|
|
|
|
# Clean if requested
|
|
if [ "${clean_first}" == "true" ]; then
|
|
cleanup
|
|
fi
|
|
|
|
# Build phase
|
|
if [ "${test_only}" != "true" ]; then
|
|
build_image
|
|
fi
|
|
|
|
if [ "${build_only}" == "true" ]; then
|
|
log_success "Build complete!"
|
|
exit 0
|
|
fi
|
|
|
|
# Test phase
|
|
start_container
|
|
wait_for_postgres
|
|
|
|
local test_result=0
|
|
|
|
if [ "${integration_only}" == "true" ]; then
|
|
run_integration_tests || test_result=$?
|
|
else
|
|
# Run both pgrx and integration tests
|
|
run_integration_tests || test_result=$?
|
|
|
|
if [ ${test_result} -eq 0 ]; then
|
|
# Only run pgrx tests if integration tests passed
|
|
run_tests || test_result=$?
|
|
fi
|
|
fi
|
|
|
|
collect_results
|
|
|
|
if [ "${keep_running}" == "true" ]; then
|
|
log_info "Container is still running: ${CONTAINER_NAME}"
|
|
log_info "Connection: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DB}"
|
|
log_info "To stop: docker stop ${CONTAINER_NAME}"
|
|
trap - EXIT # Disable cleanup trap
|
|
fi
|
|
|
|
if [ ${test_result} -eq 0 ]; then
|
|
log_success "All tests completed successfully!"
|
|
exit 0
|
|
else
|
|
log_error "Tests failed with exit code ${test_result}"
|
|
exit ${test_result}
|
|
fi
|
|
}
|
|
|
|
# Run main function
|
|
main "$@"
|