name: 'Pytest' description: 'Run pytest on pre-built container images' inputs: pytest_marks: description: 'Pytest marks' required: true default: 'e2e and vllm and gpu_1 and not slow' image_tag: description: 'Image Tag to run tests on' required: true cpu_limit: description: 'Maximum number of cores available to docker' required: false default: '10' framework: description: 'Framework name for test metrics' required: false default: 'unknown' test_type: description: 'Test type (unit, e2e, integration)' required: false default: 'e2e' platform_arch: description: 'Platform architecture (amd64, arm64)' required: false default: 'amd64' dry_run: description: 'Run pytest in dry-run mode (collect tests only, do not execute)' required: false default: 'false' runs: using: "composite" steps: - name: Setup Test Environment shell: bash run: | # Setup test directories mkdir -p test-results # Set platform architecture from input PLATFORM_ARCH="${{ inputs.platform_arch }}" if [[ -z "${PLATFORM_ARCH}" ]]; then PLATFORM_ARCH="amd64" fi echo "PLATFORM_ARCH=${PLATFORM_ARCH}" >> $GITHUB_ENV echo "๐Ÿ—๏ธ Platform architecture: ${PLATFORM_ARCH}" - name: Run tests shell: bash env: NUM_CPUS: ${{ inputs.cpu_limit }} CONTAINER_ID: test_${{ github.run_id }}_${{ github.run_attempt }}_${{ github.job }} PYTEST_XML_FILE: pytest_test_report.xml HF_HOME: /runner/_work/_temp run: | # Run pytest with detailed output and JUnit XML set +e # Don't exit on test failures # Determine docker runtime flags and pytest command based on dry_run mode if [[ "${{ inputs.dry_run }}" == "true" ]]; then echo "๐Ÿ” Running pytest in dry-run mode (collect-only, no GPU required)" GPU_FLAGS="" PYTEST_CMD="pytest -v --collect-only -m \"${{ inputs.pytest_marks }}\"" else echo "๐Ÿš€ Running pytest in normal mode" PYTEST_CMD="pytest -v --tb=short --basetemp=/tmp -o cache_dir=/tmp/.pytest_cache --junitxml=/workspace/test-results/${{ env.PYTEST_XML_FILE }} --durations=10 -m \"${{ inputs.pytest_marks }}\"" # Detect GPU availability and conditionally add GPU flags GPU_FLAGS="" if command -v nvidia-smi &> /dev/null && nvidia-smi &> /dev/null; then echo "โœ“ GPU detected, enabling GPU runtime" GPU_FLAGS="--runtime=nvidia --gpus all" else echo "โš ๏ธ No GPU detected, running in CPU-only mode" fi fi # Run without --rm so we can copy results even if container crashes (example SIGSEGV exit 139) docker run ${GPU_FLAGS} -w /workspace \ --cpus=${NUM_CPUS} \ --network host \ --name ${{ env.CONTAINER_ID }}_pytest \ ${{ inputs.image_tag }} \ bash -c "mkdir -p /workspace/test-results && ${PYTEST_CMD}" TEST_EXIT_CODE=$? echo "TEST_EXIT_CODE=${TEST_EXIT_CODE}" >> $GITHUB_ENV echo "๐Ÿงช Tests completed with exit code: ${TEST_EXIT_CODE}" # Copy test results from container to host docker cp ${{ env.CONTAINER_ID }}_pytest:/workspace/test-results . || echo "Failed to copy test results" # Clean up container docker rm -f ${{ env.CONTAINER_ID }}_pytest || echo "Failed to clean up container" # Always continue to results processing exit 0 - name: Process Test Results shell: bash run: | # Sanitize test_type for filenames (always set this for artifact upload) # Remove commas and spaces from test_type for use in filenames STR_TEST_TYPE=$(echo "${{ inputs.test_type }}" | tr ', ' '_') echo "STR_TEST_TYPE=${STR_TEST_TYPE}" >> $GITHUB_ENV # Skip XML processing if in dry-run mode if [[ "${{ inputs.dry_run }}" == "true" ]]; then echo "โœ… Dry-run mode: Test collection completed" echo "โญ๏ธ No JUnit XML generated (dry-run mode)" exit 0 fi # Check for JUnit XML file and determine test status JUNIT_FILE="test-results/pytest_test_report.xml" if [[ -f "$JUNIT_FILE" ]]; then echo "โœ… JUnit XML generated successfully" # Extract basic test counts for status determination TOTAL_TESTS=$(grep -o 'tests="[0-9]*"' "$JUNIT_FILE" | grep -o '[0-9]*' | head -1 || echo "0") FAILED_TESTS=$(grep -o 'failures="[0-9]*"' "$JUNIT_FILE" | grep -o '[0-9]*' | head -1 || echo "0") ERROR_TESTS=$(grep -o 'errors="[0-9]*"' "$JUNIT_FILE" | grep -o '[0-9]*' | head -1 || echo "0") echo "๐Ÿ“Š ${TOTAL_TESTS} tests completed (${FAILED_TESTS} failed, ${ERROR_TESTS} errors)" # Create uniquely named metadata file with step context information # Use framework-testtype-arch to make it unique per test run METADATA_FILE="test-results/test_metadata_${{ inputs.framework }}_${STR_TEST_TYPE}_${{ inputs.platform_arch }}.json" JUNIT_NAME="pytest_test_report_${{ inputs.framework }}_${STR_TEST_TYPE}_${{ inputs.platform_arch }}.xml" # Rename XML file to unique name mv "$JUNIT_FILE" "test-results/$JUNIT_NAME" echo '{' > "$METADATA_FILE" echo ' "job_name": "${{ github.job }}",' >> "$METADATA_FILE" echo ' "framework": "${{ inputs.framework }}",' >> "$METADATA_FILE" echo ' "test_type": "${{ inputs.test_type }}",' >> "$METADATA_FILE" echo ' "platform_arch": "${{ inputs.platform_arch }}",' >> "$METADATA_FILE" echo ' "junit_xml_file": "'"$JUNIT_NAME"'",' >> "$METADATA_FILE" echo ' "step_name": "Run ${{ inputs.test_type }} tests"' >> "$METADATA_FILE" echo '}' >> "$METADATA_FILE" echo "๐Ÿ“ Created test metadata file: $METADATA_FILE" echo "๐Ÿ“ Renamed XML file to: $JUNIT_NAME" else echo "โš ๏ธ JUnit XML file not found - test results may not be available for upload" TOTAL_TESTS=0 FAILED_TESTS=1 # Treat missing XML as failure ERROR_TESTS=0 fi # Exit with original test result to maintain workflow behavior exit ${TEST_EXIT_CODE} - name: Upload Test Results uses: actions/upload-artifact@v4 if: always() && inputs.dry_run != 'true' # Skip upload in dry-run mode with: name: test-results-${{ inputs.framework }}-${{ env.STR_TEST_TYPE }}-${{ env.PLATFORM_ARCH }} path: | test-results/pytest_test_report_${{ inputs.framework }}_${{ env.STR_TEST_TYPE }}_${{ inputs.platform_arch }}.xml test-results/test_metadata_${{ inputs.framework }}_${{ env.STR_TEST_TYPE }}_${{ inputs.platform_arch }}.json retention-days: 7