From 40d6be1eac06b3d053cc55384ede1b40a65e5458 Mon Sep 17 00:00:00 2001 From: Bear Date: Mon, 24 Nov 2025 07:52:24 -0600 Subject: [PATCH] Add CI/CD badge and testing documentation to README --- .github/workflows/README.md | 190 ++++++++ .github/workflows/python-test.yml | 699 ++++++++++++++++++++++++++++++ .gitignore | 12 + README.md | 27 ++ 4 files changed, 928 insertions(+) create mode 100644 .github/workflows/README.md create mode 100644 .github/workflows/python-test.yml diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 00000000..59c0c65e --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,190 @@ +# CI/CD Workflows + +This directory contains GitHub Actions workflows for automated testing. + +## Workflows + +### python-test.yml + +Automated testing workflow for Python modules that: + +1. **Detects versions to test** based on: + - Pre-release files in PR (e.g., `bearsampp-python-3.13.5-2025.8.21.7z`) + - Version numbers in PR titles (fallback) + - Manual workflow dispatch with specific version + +2. **Tests each Python version** through multiple phases: + - **Phase 1.1**: Download and extract Python module + - **Phase 1.2**: Verify Python installation and executables + - **Phase 2**: Test basic functionality (version, pip, imports) + +3. **Reports results**: + - Generates test summary with badges + - Comments on pull requests + - Uploads test artifacts + +## Triggering Tests + +### Automatic Triggers + +Tests run automatically when: + +- **Push to main**: Tests latest version only +- **Pull Request with pre-release files**: Tests versions detected from `.7z` filenames +- **Pull Request with version in title**: Tests versions mentioned in PR title (fallback) +- **Manual workflow dispatch**: Tests specified version or all versions + +### Manual Trigger + +You can manually trigger tests: + +1. Go to the Actions tab +2. Select "Python Module Tests" +3. Click "Run workflow" +4. Optionally specify a version to test + +### PR Title Format + +To test specific versions in a PR, include version numbers in the title: + +- โœ… `Add Python 3.13.5 support` +- โœ… `Update docs for 3.12.9` +- โœ… `Fix issue with Python 3.13.3 and 3.12.6.0` +- โŒ `Update documentation` (no version, won't trigger tests unless releases.properties changed) + +## Test Phases + +### Phase 1: Installation Validation + +**Phase 1.1 - Download and Extract** +- Reads version from `releases.properties` +- Downloads the `.7z` archive +- Extracts to test directory +- Verifies Python directory structure + +**Phase 1.2 - Verify Executables** +- Checks for `python.exe` +- Checks for `pip.exe` (in root or Scripts directory) +- Validates executable paths + +### Phase 2: Basic Functionality + +- Tests `python --version` +- Tests `pip --version` (via `python -m pip`) +- Tests Python import system +- Validates all critical components work + +## Test Results + +### Artifacts + +Test results are uploaded as artifacts: + +- `test-results-python-{version}`: Individual version test results + +Artifacts are retained for 30 days. + +### PR Comments + +For pull requests, the workflow posts a comment with: + +- Overall test status +- Version-specific badges (PASS/FAIL) +- Detailed results for failed tests +- Links to artifacts + +### Status Badges + +Each version gets a badge: + +- ๐ŸŸข ![PASS](https://img.shields.io/badge/Python_3.13.5-PASS-success?style=flat-square&logo=python&logoColor=white) +- ๐Ÿ”ด ![FAIL](https://img.shields.io/badge/Python_3.13.5-FAIL-critical?style=flat-square&logo=python&logoColor=white) +- โšช ![NO RESULTS](https://img.shields.io/badge/Python_3.13.5-NO_RESULTS-inactive?style=flat-square&logo=python&logoColor=white) + +## Configuration + +### releases.properties Format + +```properties +# Version = Download URL +3.13.5 = https://github.com/Bearsampp/module-python/releases/download/2025.8.21/bearsampp-python-3.13.5-2025.8.21.7z +3.12.9 = https://github.com/Bearsampp/module-python/releases/download/2025.4.19/bearsampp-python-3.12.9-2025.4.19.7z +``` + +### Required Archive Structure + +The `.7z` archive must contain: + +``` +bearsampp-python-X.X.X-YYYY.MM.DD.7z +โ””โ”€โ”€ python-X.X.X/ + โ”œโ”€โ”€ python.exe # Required + โ”œโ”€โ”€ python3.dll # Required + โ”œโ”€โ”€ python3XX.dll # Required + โ”œโ”€โ”€ Scripts/ + โ”‚ โ””โ”€โ”€ pip.exe # Optional but recommended + โ””โ”€โ”€ Lib/ # Required +``` + +## Troubleshooting + +### Tests Not Running + +**Problem**: Tests skipped on PR + +**Solutions**: +- Add version number to PR title +- Modify `releases.properties` +- Manually trigger workflow + +### Download Failures + +**Problem**: Download fails with 404 + +**Solutions**: +- Verify URL in `releases.properties` is correct +- Check that release exists on GitHub +- Ensure URL is publicly accessible + +### Extraction Failures + +**Problem**: Archive extraction fails + +**Solutions**: +- Verify `.7z` archive is valid +- Check archive structure matches expected format +- Ensure archive is not corrupted + +### Executable Not Found + +**Problem**: `python.exe` not found after extraction + +**Solutions**: +- Verify archive contains `python-X.X.X/` directory +- Check that `python.exe` is in the root of that directory +- Ensure extraction completed successfully + +## Permissions + +The workflow requires these permissions: + +- `contents: read` - Read repository contents +- `pull-requests: write` - Comment on PRs +- `issues: write` - Update issue comments + +## Best Practices + +1. **Test locally first**: Verify changes work before pushing +2. **Use descriptive PR titles**: Include version numbers when relevant +3. **Check workflow logs**: Review detailed logs for failures +4. **Update documentation**: Keep docs in sync with code changes +5. **Monitor artifacts**: Download and review test results + +## Support + +For issues with the CI/CD workflow: + +1. Check workflow logs in the Actions tab +2. Review this README for troubleshooting tips +3. Open an issue with workflow run details +4. Include error messages and relevant logs diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml new file mode 100644 index 00000000..a8951d72 --- /dev/null +++ b/.github/workflows/python-test.yml @@ -0,0 +1,699 @@ +name: Python Module Tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + workflow_dispatch: + inputs: + version: + description: 'Python version to test (e.g., 3.13.5, 3.12.9)' + required: false + type: string + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + detect-versions: + name: Detect Python Versions to Test + runs-on: ubuntu-latest + outputs: + versions: ${{ steps.detect.outputs.versions }} + has-changes: ${{ steps.detect.outputs.has-changes }} + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Detect Versions to Test + id: detect + run: | + echo "=== Detecting Python Versions to Test ===" + + # Initialize versions array + VERSIONS="[]" + HAS_CHANGES="false" + + # Check if manual dispatch with specific version + if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "${{ github.event.inputs.version }}" ]; then + echo "Manual workflow dispatch with version: ${{ github.event.inputs.version }}" + VERSIONS='["${{ github.event.inputs.version }}"]' + HAS_CHANGES="true" + echo "versions=$VERSIONS" >> $GITHUB_OUTPUT + echo "has-changes=$HAS_CHANGES" >> $GITHUB_OUTPUT + exit 0 + fi + + # For pull requests, detect versions from pre-release files + if [ "${{ github.event_name }}" = "pull_request" ]; then + echo "Pull request detected, checking for pre-release files..." + + # Get changed files in the PR + CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) + echo "Changed files:" + echo "$CHANGED_FILES" + + # Look for pre-release files (e.g., bearsampp-python-3.13.5-2025.8.21.7z) + PRERELEASE_FILES=$(echo "$CHANGED_FILES" | grep -E 'bearsampp-python-[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?(-[a-z0-9]+)?-[0-9]+\.[0-9]+\.[0-9]+\.7z' || true) + + if [ -n "$PRERELEASE_FILES" ]; then + echo "โœ… Found pre-release files:" + echo "$PRERELEASE_FILES" + + # Extract version numbers from pre-release filenames + DETECTED_VERSIONS="" + while IFS= read -r file; do + # Extract version from filename (e.g., bearsampp-python-3.13.5-2025.8.21.7z -> 3.13.5) + VERSION=$(echo "$file" | grep -oE 'python-[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?(-[a-z0-9]+)?' | sed 's/python-//') + if [ -n "$VERSION" ]; then + echo " Detected version: $VERSION from $file" + DETECTED_VERSIONS="$DETECTED_VERSIONS $VERSION" + fi + done <<< "$PRERELEASE_FILES" + + # Verify these versions exist in releases.properties + VALID_VERSIONS="" + for version in $DETECTED_VERSIONS; do + if grep -q "^${version}" releases.properties; then + echo "โœ… Version $version exists in releases.properties" + VALID_VERSIONS="$VALID_VERSIONS $version" + HAS_CHANGES="true" + else + echo "โš ๏ธ Version $version not found in releases.properties" + fi + done + + if [ -n "$VALID_VERSIONS" ]; then + VERSIONS=$(echo "$VALID_VERSIONS" | tr ' ' '\n' | grep -v '^$' | sort -u | jq -R -s -c 'split("\n") | map(select(length > 0))') + echo "Valid versions to test from pre-release files: $VERSIONS" + fi + else + echo "โ„น๏ธ No pre-release files found in PR" + fi + + # Fallback: Check PR title for version numbers if no pre-release files found + if [ "$HAS_CHANGES" = "false" ]; then + echo "Checking PR title for version numbers as fallback..." + PR_TITLE="${{ github.event.pull_request.title }}" + echo "PR Title: $PR_TITLE" + + # Extract version numbers from PR title (e.g., 3.13.5, 3.12.9) + TITLE_VERSIONS=$(echo "$PR_TITLE" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?(-[a-z0-9]+)?' || true) + + if [ -n "$TITLE_VERSIONS" ]; then + echo "Found versions in PR title: $TITLE_VERSIONS" + + # Verify these versions exist in releases.properties + VALID_VERSIONS="" + for version in $TITLE_VERSIONS; do + if grep -q "^${version}" releases.properties; then + echo "โœ… Version $version exists in releases.properties" + VALID_VERSIONS="$VALID_VERSIONS $version" + HAS_CHANGES="true" + else + echo "โš ๏ธ Version $version not found in releases.properties" + fi + done + + if [ -n "$VALID_VERSIONS" ]; then + VERSIONS=$(echo "$VALID_VERSIONS" | tr ' ' '\n' | grep -v '^$' | sort -u | jq -R -s -c 'split("\n") | map(select(length > 0))') + echo "Valid versions to test from PR title: $VERSIONS" + fi + else + echo "โ„น๏ธ No version numbers found in PR title" + fi + fi + else + # For push events, test only the latest version (first line in releases.properties) + echo "Push event detected, testing latest version only" + HAS_CHANGES="true" + LATEST_VERSION=$(grep -E "^[0-9]" releases.properties | cut -d'=' -f1 | tr -d ' ' | head -n 1) + if [ -n "$LATEST_VERSION" ]; then + VERSIONS=$(echo "$LATEST_VERSION" | jq -R -s -c 'split("\n") | map(select(length > 0))') + echo "Latest version to test: $VERSIONS" + else + echo "โš ๏ธ No versions found in releases.properties" + VERSIONS="[]" + HAS_CHANGES="false" + fi + fi + + echo "Final versions to test: $VERSIONS" + echo "Has changes: $HAS_CHANGES" + + echo "versions=$VERSIONS" >> $GITHUB_OUTPUT + echo "has-changes=$HAS_CHANGES" >> $GITHUB_OUTPUT + + test-python: + name: Test Python ${{ matrix.version }} + needs: detect-versions + if: needs.detect-versions.outputs.has-changes == 'true' + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + version: ${{ fromJson(needs.detect-versions.outputs.versions) }} + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Create Test Results Directory + run: | + New-Item -ItemType Directory -Force -Path "test-results" + Write-Host "โœ… Created test-results directory" + + - name: Phase 1.1 - Download and Extract Python + id: download-python + continue-on-error: true + run: | + $ErrorActionPreference = "Stop" + $version = "${{ matrix.version }}" + + Write-Host "=== Phase 1.1: Download and Extract Python $version ===" + + # Create test directory + New-Item -ItemType Directory -Force -Path "test-python" | Out-Null + + # Read releases.properties + $releasesFile = "releases.properties" + if (-not (Test-Path $releasesFile)) { + Write-Host "โŒ ERROR: releases.properties not found" + echo "success=false" >> $env:GITHUB_OUTPUT + echo "error=releases.properties not found" >> $env:GITHUB_OUTPUT + exit 1 + } + + # Parse releases.properties to find download URL + $downloadUrl = $null + Get-Content $releasesFile | ForEach-Object { + $line = $_.Trim() + if ($line -match "^$version\s*=\s*(.+)$") { + $downloadUrl = $matches[1].Trim() + } + } + + if (-not $downloadUrl) { + Write-Host "โŒ ERROR: Version $version not found in releases.properties" + Write-Host "Available versions in releases.properties:" + Get-Content $releasesFile | Select-String "^[0-9]" | ForEach-Object { Write-Host " - $($_.Line.Split('=')[0].Trim())" } + echo "success=false" >> $env:GITHUB_OUTPUT + echo "error=Version $version not found in releases.properties" >> $env:GITHUB_OUTPUT + exit 1 + } + + Write-Host "Download URL: $downloadUrl" + + try { + $fileName = [System.IO.Path]::GetFileName($downloadUrl) + $downloadPath = Join-Path "test-python" $fileName + + Write-Host "Downloading Python $version..." + Write-Host "Target file: $downloadPath" + + try { + Invoke-WebRequest -Uri $downloadUrl -OutFile $downloadPath -UseBasicParsing -TimeoutSec 300 + } catch { + Write-Host "โŒ ERROR: Download failed!" + Write-Host "Error details: $($_.Exception.Message)" + Write-Host "Status Code: $($_.Exception.Response.StatusCode.value__)" + Write-Host "URL attempted: $downloadUrl" + echo "success=false" >> $env:GITHUB_OUTPUT + echo "error=Download failed: $($_.Exception.Message)" >> $env:GITHUB_OUTPUT + exit 1 + } + + if (Test-Path $downloadPath) { + $fileSize = (Get-Item $downloadPath).Length / 1MB + Write-Host "โœ… Downloaded: $fileName ($([math]::Round($fileSize, 2)) MB)" + + # Verify file is not empty or too small + if ($fileSize -lt 0.1) { + Write-Host "โŒ ERROR: Downloaded file is too small ($([math]::Round($fileSize, 2)) MB), likely corrupted" + echo "success=false" >> $env:GITHUB_OUTPUT + echo "error=Downloaded file is too small or corrupted" >> $env:GITHUB_OUTPUT + exit 1 + } + + # Extract the archive + Write-Host "Extracting archive..." + $extractOutput = & 7z x $downloadPath -o"test-python" -y 2>&1 + + if ($LASTEXITCODE -eq 0) { + Write-Host "โœ… Extraction successful" + + # List extracted contents + Write-Host "Extracted contents:" + Get-ChildItem -Path "test-python" -Directory | ForEach-Object { Write-Host " - $($_.Name)" } + + # Find the python directory + $pythonDir = Get-ChildItem -Path "test-python" -Directory | Where-Object { $_.Name -match "^python" } | Select-Object -First 1 + + if ($pythonDir) { + $pythonPath = $pythonDir.FullName + Write-Host "โœ… Python directory found: $pythonPath" + + # Verify python.exe exists + $pythonExe = Join-Path $pythonPath "python.exe" + if (Test-Path $pythonExe) { + Write-Host "โœ… python.exe exists" + echo "python-path=$pythonPath" >> $env:GITHUB_OUTPUT + echo "success=true" >> $env:GITHUB_OUTPUT + } else { + Write-Host "โŒ ERROR: python.exe not found in $pythonPath" + Write-Host "Directory structure:" + Get-ChildItem -Path $pythonPath | ForEach-Object { Write-Host " - $($_.Name)" } + echo "success=false" >> $env:GITHUB_OUTPUT + echo "error=python.exe not found in extracted archive" >> $env:GITHUB_OUTPUT + exit 1 + } + } else { + Write-Host "โŒ ERROR: Python directory not found after extraction" + Write-Host "Expected directory pattern: python*" + Write-Host "Found directories:" + Get-ChildItem -Path "test-python" -Directory | ForEach-Object { Write-Host " - $($_.Name)" } + echo "success=false" >> $env:GITHUB_OUTPUT + echo "error=Python directory not found after extraction" >> $env:GITHUB_OUTPUT + exit 1 + } + } else { + Write-Host "โŒ ERROR: Extraction failed with exit code: $LASTEXITCODE" + Write-Host "7z output:" + Write-Host $extractOutput + echo "success=false" >> $env:GITHUB_OUTPUT + echo "error=Extraction failed with exit code $LASTEXITCODE" >> $env:GITHUB_OUTPUT + exit 1 + } + } else { + Write-Host "โŒ ERROR: Download file not found at expected path: $downloadPath" + echo "success=false" >> $env:GITHUB_OUTPUT + echo "error=Download file not found after download attempt" >> $env:GITHUB_OUTPUT + exit 1 + } + } catch { + Write-Host "โŒ ERROR: Unexpected error occurred" + Write-Host "Error message: $($_.Exception.Message)" + Write-Host "Stack trace: $($_.ScriptStackTrace)" + echo "success=false" >> $env:GITHUB_OUTPUT + echo "error=$($_.Exception.Message)" >> $env:GITHUB_OUTPUT + exit 1 + } + + - name: Phase 1.2 - Verify Python Installation + id: verify-python + if: steps.download-python.outputs.success == 'true' + continue-on-error: true + run: | + $ErrorActionPreference = "Continue" + $pythonPath = "${{ steps.download-python.outputs.python-path }}" + + Write-Host "=== Phase 1.2: Verify Python Installation ===" + + # Check for required executables + $requiredExes = @("python.exe", "pip.exe") + + $allFound = $true + $verifyResults = @{} + + foreach ($exe in $requiredExes) { + $exePath = Join-Path $pythonPath $exe + if (Test-Path $exePath) { + Write-Host "โœ… Found: $exe" + $verifyResults[$exe] = @{ found = $true; path = $exePath } + } else { + # pip might be in Scripts directory + $scriptsPath = Join-Path $pythonPath "Scripts" + $exePath = Join-Path $scriptsPath $exe + if (Test-Path $exePath) { + Write-Host "โœ… Found: $exe (in Scripts)" + $verifyResults[$exe] = @{ found = $true; path = $exePath } + } else { + Write-Host "โŒ Missing: $exe" + $verifyResults[$exe] = @{ found = $false } + if ($exe -ne "pip.exe") { + $allFound = $false + } + } + } + } + + # Test python version + if ($allFound) { + try { + $pythonExe = Join-Path $pythonPath "python.exe" + $versionOutput = & $pythonExe --version 2>&1 | Out-String + Write-Host "Version: $versionOutput" + $verifyResults["version"] = $versionOutput.Trim() + } catch { + Write-Host "โš ๏ธ Could not get version: $_" + } + } + + $verifyResults | ConvertTo-Json -Depth 10 | Out-File "test-results/verify.json" + + if ($allFound) { + echo "success=true" >> $env:GITHUB_OUTPUT + echo "python-exe=$pythonExe" >> $env:GITHUB_OUTPUT + } else { + echo "success=false" >> $env:GITHUB_OUTPUT + exit 1 + } + + - name: Phase 2 - Test Basic Functionality + id: test-basic + if: steps.verify-python.outputs.success == 'true' + continue-on-error: true + run: | + $ErrorActionPreference = "Stop" + $pythonExe = "${{ steps.verify-python.outputs.python-exe }}" + + Write-Host "=== Phase 2: Test Basic Functionality ===" + + try { + $allFunctional = $true + $testResults = @() + + Write-Host "`nTesting python.exe --version..." + try { + $pythonVersion = & $pythonExe --version 2>&1 + Write-Host $pythonVersion + if ($LASTEXITCODE -eq 0) { + Write-Host "โœ… python.exe is functional" + $testResults += "python: PASS" + } else { + Write-Host "โŒ python.exe failed with exit code: $LASTEXITCODE" + $allFunctional = $false + $testResults += "python: FAIL" + } + } catch { + Write-Host "โŒ python.exe error: $_" + $allFunctional = $false + $testResults += "python: ERROR" + } + + Write-Host "`nTesting pip..." + try { + $pipVersion = & $pythonExe -m pip --version 2>&1 + Write-Host $pipVersion + if ($LASTEXITCODE -eq 0) { + Write-Host "โœ… pip is functional" + $testResults += "pip: PASS" + } else { + Write-Host "โš ๏ธ pip failed with exit code: $LASTEXITCODE" + $testResults += "pip: WARN" + } + } catch { + Write-Host "โš ๏ธ pip error: $_" + $testResults += "pip: WARN" + } + + Write-Host "`nTesting Python import system..." + try { + $importTest = & $pythonExe -c "import sys; print(f'Python {sys.version}')" 2>&1 + Write-Host $importTest + if ($LASTEXITCODE -eq 0) { + Write-Host "โœ… Python import system is functional" + $testResults += "import: PASS" + } else { + Write-Host "โŒ Python import failed with exit code: $LASTEXITCODE" + $allFunctional = $false + $testResults += "import: FAIL" + } + } catch { + Write-Host "โŒ Python import error: $_" + $allFunctional = $false + $testResults += "import: ERROR" + } + + Write-Host "`nTest Results:" + $testResults | ForEach-Object { Write-Host " $_" } + + if ($allFunctional) { + Write-Host "`nโœ… All critical tests passed" + echo "success=true" >> $env:GITHUB_OUTPUT + } else { + Write-Host "`nโŒ Some critical tests failed" + echo "success=false" >> $env:GITHUB_OUTPUT + echo "error=One or more critical tests failed" >> $env:GITHUB_OUTPUT + exit 1 + } + } catch { + Write-Host "โŒ Error testing Python: $_" + echo "success=false" >> $env:GITHUB_OUTPUT + echo "error=$($_.Exception.Message)" >> $env:GITHUB_OUTPUT + exit 1 + } + + - name: Generate Test Summary + if: always() + run: | + $version = "${{ matrix.version }}" + + Write-Host "`n=== Test Summary for Python $version ===" + + $phase1_1 = "${{ steps.download-python.outputs.success }}" -eq "true" + $phase1_2 = "${{ steps.verify-python.outputs.success }}" -eq "true" + $phase2 = "${{ steps.test-basic.outputs.success }}" -eq "true" + + # Get error messages if any + $error1_1 = "${{ steps.download-python.outputs.error }}" + $error2 = "${{ steps.test-basic.outputs.error }}" + + $summary = "### Python $version`n`n" + + $summary += "**Phase 1: Installation Validation**`n" + $summary += "- Download & Extract: $(if ($phase1_1) { 'โœ… PASS' } else { 'โŒ FAIL' })`n" + if (-not $phase1_1 -and $error1_1) { + $summary += " - Error: $error1_1`n" + } + $summary += "- Verify Executables: $(if ($phase1_2) { 'โœ… PASS' } else { 'โŒ FAIL' })`n`n" + + if ($phase1_2) { + $summary += "**Phase 2: Basic Functionality**`n" + $summary += "- Test Executables: $(if ($phase2) { 'โœ… PASS' } else { 'โŒ FAIL' })`n" + if (-not $phase2 -and $error2) { + $summary += " - Error: $error2`n" + } + $summary += "`n" + } + + # Overall status + $allPassed = $phase1_1 -and $phase1_2 -and $phase2 + + if ($allPassed) { + $summary += "**Overall Status:** โœ… ALL TESTS PASSED`n" + } else { + $summary += "**Overall Status:** โŒ SOME TESTS FAILED`n" + $summary += "`n" + $summary += "
`n" + $summary += "๐Ÿ’ก Click here for troubleshooting tips`n`n" + $summary += "- Check the workflow logs for detailed error messages`n" + $summary += "- Download the test artifacts for complete logs`n" + $summary += "- Verify the .7z archive structure matches expected format`n" + $summary += "- Ensure all required DLL dependencies are included`n" + $summary += "
`n" + } + + Write-Host $summary + $summary | Out-File "test-results/summary.md" + + # Set job outcome based on test results + if (-not $allPassed) { + Write-Host "##[error]Tests failed for Python $version" + exit 1 + } + + - name: Upload Test Results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-python-${{ matrix.version }} + path: test-results/ + retention-days: 30 + + report-results: + name: Report Test Results + needs: [detect-versions, test-python] + if: always() && needs.detect-versions.outputs.has-changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Download all test results + uses: actions/download-artifact@v4 + with: + path: all-results + continue-on-error: true + + - name: Generate PR Comment + run: | + echo "## ๐Ÿ Python Module Tests - Results" > comment.md + echo "" >> comment.md + echo "**Test Date:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> comment.md + + # Determine overall test status + TEST_STATUS="${{ needs.test-python.result }}" + VERSIONS='${{ needs.detect-versions.outputs.versions }}' + + if [ "$TEST_STATUS" = "skipped" ] || [ "$VERSIONS" = "[]" ]; then + echo "**Status:** โญ๏ธ Tests skipped - no versions to test" >> comment.md + echo "" >> comment.md + echo "โ„น๏ธ **Why were tests skipped?**" >> comment.md + echo "" >> comment.md + echo "Tests are only run when:" >> comment.md + echo "- PR includes pre-release files (e.g., \`bearsampp-python-3.13.5-2025.8.21.7z\`), OR" >> comment.md + echo "- PR title contains version numbers (e.g., \"3.13.5\", \"3.12.9\") as fallback" >> comment.md + echo "" >> comment.md + echo "**To trigger tests:**" >> comment.md + echo "1. Add pre-release .7z files to your PR, OR" >> comment.md + echo "2. Add version numbers to your PR title (e.g., \"Update Python 3.13.5\"), OR" >> comment.md + echo "3. Manually trigger the workflow from the Actions tab" >> comment.md + elif [ "$TEST_STATUS" = "success" ]; then + echo "**Status:** โœ… All tests passed" >> comment.md + elif [ "$TEST_STATUS" = "failure" ]; then + echo "**Status:** โŒ Some tests failed" >> comment.md + else + echo "**Status:** โš ๏ธ Tests completed with issues" >> comment.md + fi + + echo "" >> comment.md + + # Generate badges for each version + if [ "$TEST_STATUS" != "skipped" ] && [ "$VERSIONS" != "[]" ]; then + echo "### ๐Ÿ“Š Test Results by Version" >> comment.md + echo "" >> comment.md + + # Parse versions and check results + VERSION_LIST=$(echo '${{ needs.detect-versions.outputs.versions }}' | jq -r '.[]') + + for version in $VERSION_LIST; do + # Check if summary file exists for this version + SUMMARY_FILE="all-results/test-results-python-${version}/summary.md" + + if [ -f "$SUMMARY_FILE" ]; then + # Check if tests passed by looking for "ALL TESTS PASSED" in summary + if grep -q "ALL TESTS PASSED" "$SUMMARY_FILE"; then + # Success badge (green) + echo "![Python ${version}](https://img.shields.io/badge/Python_${version}-PASS-success?style=flat-square&logo=python&logoColor=white)" >> comment.md + else + # Failure badge (red) + echo "![Python ${version}](https://img.shields.io/badge/Python_${version}-FAIL-critical?style=flat-square&logo=python&logoColor=white)" >> comment.md + fi + else + # No results badge (gray) + echo "![Python ${version}](https://img.shields.io/badge/Python_${version}-NO_RESULTS-inactive?style=flat-square&logo=python&logoColor=white)" >> comment.md + fi + done + + echo "" >> comment.md + fi + + echo "" >> comment.md + + # Check if artifacts exist + if [ -d "all-results" ]; then + # Count expected vs actual results + EXPECTED_COUNT=$(echo '${{ needs.detect-versions.outputs.versions }}' | jq '. | length') + ACTUAL_COUNT=$(find all-results -name "summary.md" 2>/dev/null | wc -l) + + echo "**Results:** $ACTUAL_COUNT of $EXPECTED_COUNT versions tested" >> comment.md + echo "" >> comment.md + + # Check if there are any failures + HAS_FAILURES=false + for version_dir in all-results/test-results-python-*; do + if [ -d "$version_dir" ]; then + for summary_file in "$version_dir"/summary.md; do + if [ -f "$summary_file" ]; then + if ! grep -q "ALL TESTS PASSED" "$summary_file"; then + HAS_FAILURES=true + break 2 + fi + fi + done + fi + done + + # Only show detailed results if there are failures + if [ "$HAS_FAILURES" = true ]; then + echo "### ๐Ÿ“‹ Detailed Test Results" >> comment.md + echo "" >> comment.md + + for version_dir in all-results/test-results-python-*; do + if [ -d "$version_dir" ]; then + for summary_file in "$version_dir"/summary.md; do + if [ -f "$summary_file" ]; then + cat "$summary_file" >> comment.md + echo "" >> comment.md + fi + done + fi + done + else + echo "_All tests passed successfully! โœจ_" >> comment.md + echo "" >> comment.md + fi + elif [ "$TEST_STATUS" != "skipped" ] && [ "$VERSIONS" != "[]" ]; then + echo "โš ๏ธ No test results available" >> comment.md + echo "" >> comment.md + fi + + if [ "$TEST_STATUS" != "skipped" ] && [ "$VERSIONS" != "[]" ]; then + echo "---" >> comment.md + echo "" >> comment.md + echo "### ๐Ÿ“‹ Test Phases" >> comment.md + echo "" >> comment.md + echo "Each version is tested through the following phases:" >> comment.md + echo "- **Phase 1:** Installation Validation (Download, Extract, Verify Executables)" >> comment.md + echo "- **Phase 2:** Basic Functionality (Test Python Version, pip, Import System)" >> comment.md + echo "" >> comment.md + echo "_Check artifacts for detailed logs._" >> comment.md + fi + + cat comment.md + + - name: Comment on PR + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const comment = fs.readFileSync('comment.md', 'utf8'); + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + + const botComment = comments.find(comment => + comment.user.type === 'Bot' && + comment.body.includes('๐Ÿ Python Module Tests') + ); + + if (botComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: comment + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: comment + }); + } + + - name: Display Results Summary (Manual Run) + if: github.event_name == 'workflow_dispatch' + run: | + echo "## ๐Ÿ Python Module Tests - Manual Run Results" + echo "" + cat comment.md diff --git a/.gitignore b/.gitignore index 207bc8a7..2a3274e6 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,15 @@ # Qodo /.qodo + +# Gradle +.gradle/ +build/ +.gradletasknamecache +gradle-app.setting +!gradle-wrapper.jar +!gradle-wrapper.properties + +# OS +.DS_Store +Thumbs.db diff --git a/README.md b/README.md index 34658d25..3e986636 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![GitHub release](https://img.shields.io/github/release/bearsampp/module-python.svg?style=flat-square)](https://github.com/bearsampp/module-python/releases/latest) ![Total downloads](https://img.shields.io/github/downloads/bearsampp/module-python/total.svg?style=flat-square) +[![CI/CD Tests](https://img.shields.io/github/actions/workflow/status/bearsampp/module-python/python-test.yml?branch=main&label=tests&style=flat-square)](https://github.com/bearsampp/module-python/actions/workflows/python-test.yml) @@ -92,6 +93,32 @@ gradle showWheelInfo "-PbundleVersion=3.13.5" - โœ… Parallel execution support - โœ… Interactive and non-interactive build modes - โœ… Comprehensive verification and validation tasks +- โœ… Automated CI/CD testing for all Python versions + +## CI/CD System + +This module includes automated CI/CD testing that validates Python modules. + +### Automated Testing + +The CI/CD pipeline automatically: + +- ๐Ÿ” Detects Python versions from pre-release files in PRs +- ๐Ÿ“ฅ Downloads and extracts Python modules +- โœ… Verifies executables (python.exe, pip.exe) +- ๐Ÿงช Tests basic functionality (version, pip, imports) +- ๐Ÿ“Š Generates detailed test reports +- ๐Ÿ’ฌ Comments on pull requests with results + +### Triggering Tests + +Tests run automatically when: +- Pull requests include pre-release `.7z` files (e.g., `bearsampp-python-3.13.5-2025.8.21.7z`) +- PR titles contain version numbers as fallback (e.g., "Add Python 3.13.5") +- Pushes to main branch (tests latest version only) +- Manual workflow dispatch + +See [CI/CD Workflow Documentation](.github/workflows/README.md) for details. ## Project Structure