diff --git a/.github/workflows/postgresql-test.yml b/.github/workflows/postgresql-test.yml new file mode 100644 index 0000000..48b624c --- /dev/null +++ b/.github/workflows/postgresql-test.yml @@ -0,0 +1,554 @@ +name: PostgreSQL Module Tests + +on: + pull_request: + branches: + - main + - develop + types: [opened, synchronize, reopened, edited] + workflow_dispatch: + inputs: + test_mode: + description: 'Test mode' + required: true + type: choice + options: + - 'Latest 5 versions' + - 'Specific version' + default: 'Latest 5 versions' + version: + description: 'Specific version to test (only if "Specific version" is selected)' + required: false + type: string + +jobs: + detect-versions: + name: Detect PostgreSQL Versions + runs-on: ubuntu-latest + outputs: + versions: ${{ steps.get-versions.outputs.versions }} + has-changes: ${{ steps.check-changes.outputs.has-changes }} + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check for Changes + id: check-changes + run: | + if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + echo "has-changes=true" >> $GITHUB_OUTPUT + echo "Manual workflow trigger - will run tests" + elif [ "${{ github.event_name }}" == "pull_request" ]; then + echo "has-changes=true" >> $GITHUB_OUTPUT + echo "Pull request - will run tests" + else + CHANGED_FILES=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }}) + if echo "$CHANGED_FILES" | grep -qE "releases\.properties|\.github/workflows/postgresql-test\.yml"; then + echo "has-changes=true" >> $GITHUB_OUTPUT + echo "Relevant files changed - will run tests" + else + echo "has-changes=false" >> $GITHUB_OUTPUT + echo "No relevant changes - skipping tests" + fi + fi + + - name: Get PostgreSQL Versions + id: get-versions + run: | + if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + # Manual workflow + if [ "${{ github.event.inputs.test_mode }}" == "Specific version" ] && [ "${{ github.event.inputs.version }}" != "" ]; then + VERSION="${{ github.event.inputs.version }}" + echo "Testing specific version: $VERSION" + VERSIONS="[\"$VERSION\"]" + else + echo "Testing latest 5 versions (including RC/beta/alpha)" + VERSIONS=$(grep -E "^[0-9]+\.[0-9]+" releases.properties | \ + cut -d'=' -f1 | \ + tr -d ' ' | \ + sort -V -r | \ + head -5 | \ + jq -R -s -c 'split("\n") | map(select(length > 0)) | unique') + fi + elif [ "${{ github.event_name }}" == "pull_request" ]; then + # For PRs, detect which versions were added or modified using GitHub API + echo "Detecting versions changed in PR #${{ github.event.pull_request.number }}" + + # Get the list of changed files first + echo "Fetching changed files from PR..." + CHANGED_FILES=$(curl -s -H "Authorization: token ${{ github.token }}" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" | \ + jq -r '.[].filename') + + echo "Changed files in PR:" + echo "$CHANGED_FILES" + + # Check if releases.properties was changed + if echo "$CHANGED_FILES" | grep -q "^releases.properties$"; then + echo "releases.properties was modified in this PR" + + # Get the diff from GitHub API using curl + PATCH=$(curl -s -H "Authorization: token ${{ github.token }}" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" | \ + jq -r '.[] | select(.filename == "releases.properties") | .patch') + + echo "Analyzing diff for added/modified versions..." + + # Extract ALL added lines (lines starting with +) that contain version numbers + # Test ALL versions added in the PR (including RC/beta/alpha) + CHANGED_VERSIONS=$(echo "$PATCH" | \ + grep "^+" | \ + grep -v "^+++" | \ + grep -E "^\+[0-9]+\.[0-9]+" | \ + sed 's/^+//' | \ + cut -d'=' -f1 | \ + tr -d ' ') + + if [ -z "$CHANGED_VERSIONS" ]; then + echo "No new versions found in releases.properties changes" + echo "Testing latest 5 versions as fallback" + VERSIONS=$(grep -E "^[0-9]+\.[0-9]+" releases.properties | \ + cut -d'=' -f1 | \ + tr -d ' ' | \ + sort -V -r | \ + head -5 | \ + jq -R -s -c 'split("\n") | map(select(length > 0)) | unique') + else + echo "Versions detected in PR:" + echo "$CHANGED_VERSIONS" + VERSIONS=$(echo "$CHANGED_VERSIONS" | jq -R -s -c 'split("\n") | map(select(length > 0)) | unique') + fi + else + echo "releases.properties was NOT modified in this PR" + echo "Skipping tests - no versions to test" + VERSIONS="[]" + fi + else + # For other events, test latest 5 versions + echo "Testing latest 5 versions" + VERSIONS=$(grep -E "^[0-9]+\.[0-9]+" releases.properties | \ + cut -d'=' -f1 | \ + tr -d ' ' | \ + sort -V -r | \ + head -5 | \ + jq -R -s -c 'split("\n") | map(select(length > 0)) | unique') + fi + + echo "versions=$VERSIONS" >> $GITHUB_OUTPUT + echo "Versions to test: $VERSIONS" + + test-postgresql: + name: Test PostgreSQL ${{ matrix.version }} + needs: detect-versions + if: needs.detect-versions.outputs.has-changes == 'true' && needs.detect-versions.outputs.versions != '[]' + 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 Directories + run: | + New-Item -ItemType Directory -Force -Path "test-postgresql" | Out-Null + New-Item -ItemType Directory -Force -Path "test-results" | Out-Null + New-Item -ItemType Directory -Force -Path "test-data" | Out-Null + Write-Host "✅ Test directories created" + + - name: Phase 1.1 - Download PostgreSQL + id: download-postgresql + continue-on-error: true + run: | + $ErrorActionPreference = "Continue" + $version = "${{ matrix.version }}" + + Write-Host "=== Phase 1.1: Download PostgreSQL $version ===" + + # Read releases.properties to get download URL + $releasesFile = "releases.properties" + $downloadUrl = "" + + if (Test-Path $releasesFile) { + $content = Get-Content $releasesFile + foreach ($line in $content) { + if ($line -match "^$version\s*=\s*(.+)$") { + $downloadUrl = $matches[1].Trim() + break + } + } + } else { + Write-Host "❌ ERROR: releases.properties file not found!" + echo "success=false" >> $env:GITHUB_OUTPUT + echo "error=releases.properties file not found" >> $env:GITHUB_OUTPUT + exit 1 + } + + 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-postgresql" $fileName + + Write-Host "Downloading PostgreSQL $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-postgresql" -y 2>&1 + + if ($LASTEXITCODE -eq 0) { + Write-Host "✅ Extraction successful" + + # List extracted contents + Write-Host "Extracted contents:" + Get-ChildItem -Path "test-postgresql" -Directory | ForEach-Object { Write-Host " - $($_.Name)" } + + # Find the postgresql directory + $pgDir = Get-ChildItem -Path "test-postgresql" -Directory | Where-Object { $_.Name -match "^postgresql" } | Select-Object -First 1 + + if ($pgDir) { + $pgPath = $pgDir.FullName + Write-Host "✅ PostgreSQL directory found: $pgPath" + + # Verify bin directory exists + $binPath = Join-Path $pgPath "bin" + if (Test-Path $binPath) { + Write-Host "✅ bin directory exists" + echo "pg-path=$pgPath" >> $env:GITHUB_OUTPUT + echo "success=true" >> $env:GITHUB_OUTPUT + } else { + Write-Host "❌ ERROR: bin directory not found in $pgPath" + Write-Host "Directory structure:" + Get-ChildItem -Path $pgPath | ForEach-Object { Write-Host " - $($_.Name)" } + echo "success=false" >> $env:GITHUB_OUTPUT + echo "error=bin directory not found in extracted archive" >> $env:GITHUB_OUTPUT + exit 1 + } + } else { + Write-Host "❌ ERROR: PostgreSQL directory not found after extraction" + Write-Host "Expected directory pattern: postgresql*" + Write-Host "Found directories:" + Get-ChildItem -Path "test-postgresql" -Directory | ForEach-Object { Write-Host " - $($_.Name)" } + echo "success=false" >> $env:GITHUB_OUTPUT + echo "error=PostgreSQL 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 PostgreSQL Installation + id: verify-postgresql + if: steps.download-postgresql.outputs.success == 'true' + continue-on-error: true + run: | + $ErrorActionPreference = "Continue" + $pgPath = "${{ steps.download-postgresql.outputs.pg-path }}" + + Write-Host "=== Phase 1.2: Verify PostgreSQL Installation ===" + + # Check for required executables + $binPath = Join-Path $pgPath "bin" + $requiredExes = @("postgres.exe", "psql.exe", "pg_ctl.exe", "initdb.exe", "createdb.exe", "dropdb.exe") + + $allFound = $true + $verifyResults = @{} + + foreach ($exe in $requiredExes) { + $exePath = Join-Path $binPath $exe + if (Test-Path $exePath) { + Write-Host "✅ Found: $exe" + $verifyResults[$exe] = @{ found = $true; path = $exePath } + } else { + Write-Host "❌ Missing: $exe" + $verifyResults[$exe] = @{ found = $false } + $allFound = $false + } + } + + # Test postgres version + if ($allFound) { + try { + $postgresExe = Join-Path $binPath "postgres.exe" + $versionOutput = & $postgresExe --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 "bin-path=$binPath" >> $env:GITHUB_OUTPUT + } else { + echo "success=false" >> $env:GITHUB_OUTPUT + exit 1 + } + + - name: Phase 2 - Test Basic Functionality + id: test-basic + if: steps.verify-postgresql.outputs.success == 'true' + continue-on-error: true + run: | + $ErrorActionPreference = "Continue" + $binPath = "${{ steps.verify-postgresql.outputs.bin-path }}" + + Write-Host "=== Phase 2: Test Basic Functionality ===" + + # Test that executables can run and show version + try { + $postgresExe = Join-Path $binPath "postgres.exe" + $psqlExe = Join-Path $binPath "psql.exe" + $initdbExe = Join-Path $binPath "initdb.exe" + + Write-Host "`nTesting postgres.exe --version..." + $pgVersion = & $postgresExe --version 2>&1 | Out-String + Write-Host $pgVersion + + Write-Host "`nTesting psql.exe --version..." + $psqlVersion = & $psqlExe --version 2>&1 | Out-String + Write-Host $psqlVersion + + Write-Host "`nTesting initdb.exe --version..." + $initdbVersion = & $initdbExe --version 2>&1 | Out-String + Write-Host $initdbVersion + + if ($LASTEXITCODE -eq 0) { + Write-Host "`n✅ All executables are functional" + echo "success=true" >> $env:GITHUB_OUTPUT + } else { + Write-Host "`n❌ Some executables failed" + echo "success=false" >> $env:GITHUB_OUTPUT + exit 1 + } + } catch { + Write-Host "❌ Error testing executables: $_" + echo "success=false" >> $env:GITHUB_OUTPUT + exit 1 + } + + - name: Generate Test Summary + if: always() + run: | + $version = "${{ matrix.version }}" + + Write-Host "`n=== Test Summary for PostgreSQL $version ===" + + $phase1_1 = "${{ steps.download-postgresql.outputs.success }}" -eq "true" + $phase1_2 = "${{ steps.verify-postgresql.outputs.success }}" -eq "true" + $phase2 = "${{ steps.test-basic.outputs.success }}" -eq "true" + + # Get error messages if any + $error1_1 = "${{ steps.download-postgresql.outputs.error }}" + + $summary = "### PostgreSQL $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`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" + + - name: Upload Test Results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-postgresql-${{ matrix.version }} + path: test-results/ + retention-days: 30 + + report-results: + name: Report Test Results + needs: [detect-versions, test-postgresql] + if: always() && needs.detect-versions.outputs.has-changes == 'true' && needs.test-postgresql.result != 'cancelled' + 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 "## 🐘 PostgreSQL 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-postgresql.result }}" + if [ "$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 + + # 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 + + for version_dir in all-results/test-results-postgresql-*; 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 "⚠️ No test results available" >> comment.md + echo "" >> comment.md + fi + + 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 Executable Versions)" >> comment.md + echo "" >> comment.md + echo "_Check artifacts for detailed logs._" >> comment.md + + 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('🐘 PostgreSQL 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 "## 🐘 PostgreSQL Module Tests - Manual Run Results" + echo "" + cat comment.md