test.sh•15.1 kB
#!/bin/bash
# Run tests with coverage using Hatch
# --- Define Project Root ---
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
# --- Change to Project Root ---
cd "$PROJECT_ROOT"
echo "ℹ️ Changed working directory to project root: $PROJECT_ROOT"
# --- Define Test Artifacts Directories ---
TEST_LOGS_DIR="${PROJECT_ROOT}/logs/tests"
JUNIT_DIR="${TEST_LOGS_DIR}/junit"
COVERAGE_DIR="${TEST_LOGS_DIR}/coverage"
WORKFLOW_DIR="${TEST_LOGS_DIR}/workflows"
# Create directories if they don't exist
mkdir -p "${JUNIT_DIR}" "${COVERAGE_DIR}" "${COVERAGE_DIR}/html" "${WORKFLOW_DIR}"
# Install hatch if not installed
if ! command -v hatch &> /dev/null; then
echo "Hatch not found. Installing hatch..."
pip install hatch
fi
# Parse command line arguments
COVERAGE=false
VERBOSE_LEVEL=0 # 0: default, 1: -v, 2: -vv, 3: -vvv
HTML=false
CLEAN_ENV=false
TEST_PATHS=()
PYTHON_VERSION=""
LOG_RESULTS=false # Added flag for logging results to validation system
BEFORE_XML="" # Path to before XML for comparison
AUTO_CAPTURE_WORKFLOW=false # Flag for automatic test workflow capture
while [[ $# -gt 0 ]]; do
case $1 in
--coverage|-c)
COVERAGE=true
shift
;;
-vvv)
VERBOSE_LEVEL=3
shift
;;
-vv)
# Only set if not already -vvv
[[ $VERBOSE_LEVEL -lt 3 ]] && VERBOSE_LEVEL=2
shift
;;
--verbose|-v)
# Only set if not already -vv or -vvv
[[ $VERBOSE_LEVEL -lt 2 ]] && VERBOSE_LEVEL=1
shift
;;
--html)
HTML=true
COVERAGE=true # HTML implies coverage
shift
;;
--clean)
CLEAN_ENV=true
shift
;;
--log-results)
LOG_RESULTS=true
shift
;;
--auto-capture-workflow)
AUTO_CAPTURE_WORKFLOW=true
LOG_RESULTS=true # Auto-capture implies logging results
shift
;;
--before-xml)
if [[ -z "$2" || "$2" == -* ]]; then
echo "Error: --before-xml requires a path to JUnit XML file"
exit 1
fi
BEFORE_XML="$2"
shift 2
;;
--python|--py)
if [[ -z "$2" || "$2" == -* ]]; then
echo "Error: --python requires a version argument (e.g., 3.10, 3.11, 3.12)"
exit 1
fi
PYTHON_VERSION="$2"
shift 2
;;
-*)
echo "Unknown option: $1"
echo "Usage: $0 [--coverage|-c] [-v|-vv|-vvv] [--html] [--clean] [--python VERSION] [--log-results] [--before-xml FILE] [--auto-capture-workflow] [test_path1 test_path2 ...]"
echo " --python, --py VERSION Run tests only on specified Python version (e.g., 3.10, 3.11, 3.12)"
echo " --log-results Log test results to the validation system"
echo " --before-xml FILE Path to JUnit XML from before changes for comparison"
echo " --auto-capture-workflow Automatically capture and process test workflow (failures & fixes)"
exit 1
;;
*)
# Collect all non-option arguments as test paths
TEST_PATHS+=("$1")
shift
;;
esac
done
# Remove environment if clean flag is set
if [ "$CLEAN_ENV" = true ]; then
echo "Removing existing test environment..."
hatch env remove test
echo "Test environment removed."
fi
# Always generate JUnit XML output
XML_OUTPUT_PATH="${JUNIT_DIR}/test-results.xml"
PYTEST_XML_ARG="--junitxml=${XML_OUTPUT_PATH}"
# Build the base pytest command arguments
# Using the settings we found work reliably (timeout, no xdist)
PYTEST_BASE_ARGS="--timeout=10 -p no:xdist ${PYTEST_XML_ARG}"
# Add verbosity flag if requested
VERBOSITY_FLAG=""
if [ "$VERBOSE_LEVEL" -eq 1 ]; then
VERBOSITY_FLAG="-v"
elif [ "$VERBOSE_LEVEL" -eq 2 ]; then
VERBOSITY_FLAG="-vv"
elif [ "$VERBOSE_LEVEL" -eq 3 ]; then
VERBOSITY_FLAG="-vvv"
fi
PYTEST_ARGS="$PYTEST_BASE_ARGS $VERBOSITY_FLAG"
# Add test paths if specified, otherwise use the default tests/ directory
if [ ${#TEST_PATHS[@]} -gt 0 ]; then
echo "Running specified test path(s): ${TEST_PATHS[*]}"
PYTEST_ARGS="$PYTEST_ARGS ${TEST_PATHS[*]}"
else
echo "Running all tests in tests/ directory"
PYTEST_ARGS="$PYTEST_ARGS tests/"
fi
# Determine the execution command based on coverage flag and Python version
if [ -n "$PYTHON_VERSION" ]; then
# Format the Python version into the hatch environment name (e.g., hatch-test.py3.10)
# First, ensure the version doesn't already start with "py"
if [[ "$PYTHON_VERSION" == py* ]]; then
PY_ENV="hatch-test.py$PYTHON_VERSION"
else
PY_ENV="hatch-test.py$PYTHON_VERSION"
fi
echo "Limiting tests to Python version $PYTHON_VERSION (environment: $PY_ENV)"
if [ "$COVERAGE" = true ]; then
echo "Running tests with Coverage enabled (Verbosity: $VERBOSE_LEVEL)..."
RUN_CMD="hatch -e $PY_ENV run coverage run -m pytest $PYTEST_ARGS"
else
echo "Running tests without Coverage (Verbosity: $VERBOSE_LEVEL)..."
RUN_CMD="hatch -e $PY_ENV run python -m pytest $PYTEST_ARGS"
fi
else
# Run on python 3.10 by default
if [ "$COVERAGE" = true ]; then
echo "Running tests with Coverage enabled (Verbosity: $VERBOSE_LEVEL)..."
RUN_CMD="hatch run hatch-test.py3.10:coverage run -m pytest $PYTEST_ARGS"
else
echo "Running tests without Coverage (Verbosity: $VERBOSE_LEVEL)..."
RUN_CMD="hatch run hatch-test.py3.10:python -m pytest $PYTEST_ARGS"
fi
fi
# Get current git commit hash
CURRENT_COMMIT=$(git rev-parse HEAD || echo "unknown")
# Execute the tests
echo "Executing: $RUN_CMD"
$RUN_CMD # Execute the constructed command
EXIT_CODE=$?
# Check if JUnit XML was generated
if [ -f "${XML_OUTPUT_PATH}" ]; then
echo "📊 Test results XML generated: ${XML_OUTPUT_PATH}"
# Determine if tests failed
TESTS_FAILED=false
if [ $EXIT_CODE -ne 0 ]; then
TESTS_FAILED=true
fi
# Handle automatic workflow capture
if [ "$AUTO_CAPTURE_WORKFLOW" = true ]; then
echo "🔄 Automatic test workflow capture enabled"
# Save the run time for this test execution
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
if [ "$TESTS_FAILED" = true ]; then
# Save failing test results for future comparison
FAILURE_XML="${JUNIT_DIR}/failed_tests_${TIMESTAMP}.xml"
cp "${XML_OUTPUT_PATH}" "${FAILURE_XML}"
echo "❌ Tests failed - saved results to ${FAILURE_XML} for future comparison"
# Store the failure in validation system
echo "📋 Registering test failure in workflow system..."
hatch run chroma-mcp-client log-test-results "${XML_OUTPUT_PATH}" --commit-after "${CURRENT_COMMIT}"
# Save commit hash for the failure
echo "${CURRENT_COMMIT}" > "${FAILURE_XML}.commit"
# Create a workflow state file
WORKFLOW_FILE="${WORKFLOW_DIR}/test_workflow_${TIMESTAMP}.json"
echo "{\"status\": \"failed\", \"timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\", \"xml_path\": \"${FAILURE_XML}\", \"commit\": \"${CURRENT_COMMIT}\"}" > "${WORKFLOW_FILE}"
echo "🔍 Created workflow state file: ${WORKFLOW_FILE}"
else
# Tests passed - look for previous failures to compare with
echo "✅ Tests passed - checking for previous failures to detect transitions"
# Find the most recent workflow state file (check both old and new locations for backward compatibility)
LATEST_WORKFLOW_OLD=$(ls -t test_workflow_*.json 2>/dev/null | head -n 1)
LATEST_WORKFLOW_NEW=$(ls -t "${WORKFLOW_DIR}"/test_workflow_*.json 2>/dev/null | head -n 1)
# Determine which workflow file to use
LATEST_WORKFLOW=""
if [ -n "$LATEST_WORKFLOW_NEW" ]; then
LATEST_WORKFLOW="${LATEST_WORKFLOW_NEW}"
elif [ -n "$LATEST_WORKFLOW_OLD" ]; then
# If found in old location, copy to new structure
LATEST_WORKFLOW="${WORKFLOW_DIR}/$(basename ${LATEST_WORKFLOW_OLD})"
cp "${LATEST_WORKFLOW_OLD}" "${LATEST_WORKFLOW}"
echo "📋 Migrated workflow file to new location: ${LATEST_WORKFLOW}"
fi
if [ -n "$LATEST_WORKFLOW" ]; then
echo "🔍 Found previous workflow state: ${LATEST_WORKFLOW}"
# Extract the failure XML path from the workflow state
FAILURE_XML=$(grep -o '"xml_path": "[^"]*"' "${LATEST_WORKFLOW}" | cut -d'"' -f4)
FAILURE_COMMIT=$(grep -o '"commit": "[^"]*"' "${LATEST_WORKFLOW}" | cut -d'"' -f4)
# Check if the path is a legacy path without the logs/tests/ prefix
if [[ "${FAILURE_XML}" != "${JUNIT_DIR}"* && -f "${FAILURE_XML}" ]]; then
# It's a legacy path, copy to new location
NEW_FAILURE_XML="${JUNIT_DIR}/$(basename ${FAILURE_XML})"
cp "${FAILURE_XML}" "${NEW_FAILURE_XML}"
echo "📋 Migrated test failure XML to new location: ${NEW_FAILURE_XML}"
# Also copy the commit file if it exists
if [ -f "${FAILURE_XML}.commit" ]; then
cp "${FAILURE_XML}.commit" "${NEW_FAILURE_XML}.commit"
fi
# Update the path for processing
FAILURE_XML="${NEW_FAILURE_XML}"
fi
if [ -f "${FAILURE_XML}" ]; then
echo "🧪 Comparing with previous failure: ${FAILURE_XML}"
# Run test transition detection with the found failure XML
echo "📊 Analyzing test transitions..."
hatch run chroma-mcp-client log-test-results "${XML_OUTPUT_PATH}" --before-xml "${FAILURE_XML}" --commit-before "${FAILURE_COMMIT}" --commit-after "${CURRENT_COMMIT}"
# Create a completed workflow file in the new location
COMPLETED_WORKFLOW="${WORKFLOW_DIR}/test_workflow_complete_${TIMESTAMP}.json"
echo "{\"status\": \"transitioned\", \"timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\", \"before_xml\": \"${FAILURE_XML}\", \"after_xml\": \"${XML_OUTPUT_PATH}\", \"before_commit\": \"${FAILURE_COMMIT}\", \"after_commit\": \"${CURRENT_COMMIT}\"}" > "${COMPLETED_WORKFLOW}"
echo "✅ Created completed workflow record: ${COMPLETED_WORKFLOW}"
# Attempt auto-cleanup of processed artifacts
echo "🗑️ Cleaning up processed artifacts..."
hatch run chroma-mcp-client cleanup-test-artifacts "${COMPLETED_WORKFLOW}"
else
echo "⚠️ Previous failure XML not found: ${FAILURE_XML}"
fi
else
echo "ℹ️ No previous test failures found to compare with"
fi
fi
# Standard log results mode
elif [ "$LOG_RESULTS" = true ]; then
echo "🔍 Logging test results to validation system..."
# Base log command
LOG_CMD="hatch run chroma-mcp-client log-test-results ${XML_OUTPUT_PATH}"
# Add before XML if specified
if [ -n "$BEFORE_XML" ]; then
# Check if the before XML is in the old location and needs migration
if [[ "${BEFORE_XML}" != "${JUNIT_DIR}"* && -f "${BEFORE_XML}" ]]; then
NEW_BEFORE_XML="${JUNIT_DIR}/$(basename ${BEFORE_XML})"
cp "${BEFORE_XML}" "${NEW_BEFORE_XML}"
echo "📋 Migrated before XML to new location: ${NEW_BEFORE_XML}"
BEFORE_XML="${NEW_BEFORE_XML}"
fi
LOG_CMD="${LOG_CMD} --before-xml ${BEFORE_XML}"
# Get git hash for the before file if possible
BEFORE_COMMIT="unknown"
COMMIT_FILE="${BEFORE_XML}.commit"
if [ -f "${COMMIT_FILE}" ]; then
BEFORE_COMMIT=$(cat "${COMMIT_FILE}")
fi
LOG_CMD="${LOG_CMD} --commit-before ${BEFORE_COMMIT} --commit-after ${CURRENT_COMMIT}"
fi
# Execute log command
echo "Executing: ${LOG_CMD}"
$LOG_CMD
# Save current commit hash for future comparisons
echo "${CURRENT_COMMIT}" > "${XML_OUTPUT_PATH}.commit"
echo "📝 Saved commit hash for future comparisons"
fi
else
echo "⚠️ No JUnit XML output found at ${XML_OUTPUT_PATH}"
fi
# Check if pytest run failed (exit code 1 or higher)
# Exit code 0 is success, others indicate issues.
if [ $EXIT_CODE -ne 0 ]; then
echo "❌ Pytest run exited with non-zero code: $EXIT_CODE"
# Optionally, be more specific: if [ $EXIT_CODE -eq 1 ]; then echo "Test failures occurred"; fi
exit $EXIT_CODE
fi
# Handle coverage reporting if coverage was enabled
if [ "$COVERAGE" = true ]; then
echo "✅ Coverage run finished."
echo "<<< Coverage run finished >>>"
# Comment out ALL subsequent coverage commands
echo "Combining parallel coverage data (if any)..."
hatch run hatch-test.py3.10:coverage combine || echo "No data to combine" # Allow to fail if no .coverage files
echo "Generating XML coverage report..."
hatch run hatch-test.py3.10:coverage xml -o "${COVERAGE_DIR}/coverage.xml"
COVERAGE_OUTPUT_FILE="${COVERAGE_DIR}/coverage_output.txt" # File to store text output
echo "Generating XML terminal coverage report..."
hatch run hatch-test.py3.10:coverage report > "${COVERAGE_OUTPUT_FILE}"
cat "${COVERAGE_OUTPUT_FILE}"
if [ "$LOG_RESULTS" = true ]; then
echo "🔍 Logging code quality metrics from coverage..."
# Use hatch run for consistency and to ensure correct environment
echo "Executing: hatch run chroma-mcp-client log-quality-check --tool coverage --after-output $COVERAGE_OUTPUT_FILE --metric-type coverage"
hatch run chroma-mcp-client log-quality-check --tool coverage --after-output "$COVERAGE_OUTPUT_FILE" --metric-type coverage
fi
if [ "$HTML" = true ]; then
echo "Generating HTML coverage report..."
# Directory is now configured in pyproject.toml to be "${COVERAGE_DIR}/html"
hatch run hatch-test.py3.10:coverage html
fi
echo "<<< Coverage combination finished >>>"
fi
echo "✅ Tests complete."
exit 0