name: Continuous Deployment Tests on: push: branches: - main workflow_dispatch: inputs: deployment_dir: description: "Root deployment directory (overrides CK_DEPLOYMENT_DIR)" required: false default: "" env: CK_DEPLOYMENT_DIR: ${{ github.event.inputs.deployment_dir || '~/deployment' }} CK_TESTS_DIR: ${{ github.workspace }}/solution/tests WORKERS_COUNT: 4 jobs: run_system_tests: runs-on: ubuntu-latest timeout-minutes: 25 steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Find oldest untested deployment id: find_deployment shell: bash run: | set -euo pipefail DEPLOYMENT_DIR="${CK_DEPLOYMENT_DIR}" DEPLOYMENT_DIR="${DEPLOYMENT_DIR/#\~/$HOME}" oldest_time="" oldest_dir="" oldest_wheel="" for user_dir in "$DEPLOYMENT_DIR"/*/; do [ -d "$user_dir" ] || continue for deployment_dir in "$user_dir"*/; do [ -d "$deployment_dir" ] || continue # Skip if already started [ -f "${deployment_dir}started.flag" ] && continue # Find wheel files wheel_count=$(find "$deployment_dir" -maxdepth 1 -name '*.whl' | wc -l) [ "$wheel_count" -eq 0 ] && continue if [ "$wheel_count" -gt 1 ]; then echo "ERROR: Multiple wheel files found in $deployment_dir" >&2 exit 1 fi wheel_file=$(find "$deployment_dir" -maxdepth 1 -name '*.whl') mtime=$(stat -c '%Y' "$deployment_dir") if [ -z "$oldest_time" ] || [ "$mtime" -lt "$oldest_time" ]; then oldest_time="$mtime" oldest_dir="$deployment_dir" oldest_wheel="$wheel_file" fi done done if [ -z "$oldest_dir" ]; then echo "No untested deployments found." echo "found=false" >> "$GITHUB_OUTPUT" exit 0 fi echo "Found untested deployment: $oldest_dir" echo "found=true" >> "$GITHUB_OUTPUT" echo "deployment_dir=$oldest_dir" >> "$GITHUB_OUTPUT" echo "wheel_file=$oldest_wheel" >> "$GITHUB_OUTPUT" touch "${oldest_dir}started.flag" - name: Create virtual environment if: steps.find_deployment.outputs.found == 'true' shell: bash run: | set -euo pipefail python -m venv "${{ steps.find_deployment.outputs.deployment_dir }}venv" - name: Install wheel if: steps.find_deployment.outputs.found == 'true' shell: bash run: | set -euo pipefail VENV_PY="${{ steps.find_deployment.outputs.deployment_dir }}venv/bin/python" "$VENV_PY" -m pip install --quiet "${{ steps.find_deployment.outputs.wheel_file }}" - name: Install pytest and plugins if: steps.find_deployment.outputs.found == 'true' shell: bash run: | set -euo pipefail VENV_PY="${{ steps.find_deployment.outputs.deployment_dir }}venv/bin/python" "$VENV_PY" -m pip install --quiet pytest pytest-xdist jsonschema - name: Resolve test files from deployment config id: resolve_tests if: steps.find_deployment.outputs.found == 'true' shell: bash run: | set -euo pipefail CONFIG_FILE="${{ steps.find_deployment.outputs.deployment_dir }}deployment_config.json" TESTS_DIR="${CK_TESTS_DIR}" if [ ! -f "$CONFIG_FILE" ]; then echo "ERROR: deployment_config.json not found at $CONFIG_FILE" >&2 exit 1 fi test_files=() while IFS= read -r test_name; do test_file="${TESTS_DIR}/test_${test_name}_system.py" if [ ! -f "$test_file" ]; then echo "ERROR: Test file not found: $test_file" >&2 exit 1 fi test_files+=("$test_file") done < <(jq -r '.systemTests[]' "$CONFIG_FILE") # Space-separated list for output echo "test_files=${test_files[*]}" >> "$GITHUB_OUTPUT" - name: Run system tests id: run_tests if: steps.find_deployment.outputs.found == 'true' shell: bash run: | set -euo pipefail DEPLOYMENT_DIR="${{ steps.find_deployment.outputs.deployment_dir }}" VENV_PY="${DEPLOYMENT_DIR}venv/bin/python" CONFIG_FILE="${DEPLOYMENT_DIR}deployment_config.json" JUNIT_XML="${DEPLOYMENT_DIR}test_results.xml" # Read test files back from prior step output read -ra test_files <<< "${{ steps.resolve_tests.outputs.test_files }}" echo "Running system tests: ${test_files[*]}" start_ns=$(date +%s%N) set +e "$VENV_PY" -m pytest \ "${test_files[@]}" \ -n "${WORKERS_COUNT}" \ -W error::pytest.PytestUnhandledThreadExceptionWarning \ --venv-path="${DEPLOYMENT_DIR}venv" \ --deployment-config="${CONFIG_FILE}" \ --junit-xml="${JUNIT_XML}" pytest_exit=$? set -e end_ns=$(date +%s%N) duration_ms=$(( (end_ns - start_ns) / 1000000 )) echo "duration_ms=${duration_ms}" >> "$GITHUB_OUTPUT" echo "pytest_exit=${pytest_exit}" >> "$GITHUB_OUTPUT" - name: Write final report if: always() && steps.find_deployment.outputs.found == 'true' shell: bash run: | set -euo pipefail DEPLOYMENT_DIR="${{ steps.find_deployment.outputs.deployment_dir }}" FINAL_REPORT="${DEPLOYMENT_DIR}final_report.json" duration_ms="${{ steps.run_tests.outputs.duration_ms }}" timed_out="false" # Job cancelled (timeout) maps to testing_timed_out = true if [ "${{ job.status }}" = "cancelled" ]; then timed_out="true" duration_ms="${duration_ms:-0}" fi cat > "$FINAL_REPORT" <