test new optimization script #17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Swift CI | |
| on: | |
| push: | |
| branches: [ main, develop ] | |
| pull_request: | |
| branches: [ main, develop ] | |
| concurrency: | |
| group: swift-ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| test: | |
| name: Test and Coverage | |
| runs-on: macos-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Swift | |
| uses: swift-actions/setup-swift@v3 | |
| with: | |
| swift-version: "latest" | |
| # SwiftPM caches (faster + smaller than caching the whole .build) | |
| - name: Cache SwiftPM | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/Library/Caches/org.swift.swiftpm | |
| ~/.swiftpm | |
| key: ${{ runner.os }}-swiftpm-${{ hashFiles('**/Package.resolved') }} | |
| restore-keys: | | |
| ${{ runner.os }}-swiftpm- | |
| - name: Run tests with coverage | |
| id: tests | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| echo "🧪 Running tests…" | |
| swift test --enable-code-coverage --parallel 2>&1 | tee test_output.txt | |
| # XCTest usually prints: "Executed N tests" (most reliable) | |
| TEST_COUNT=$(grep -Eo "Executed[[:space:]]+[0-9]+[[:space:]]+tests" test_output.txt | grep -Eo "[0-9]+" | tail -1 || true) | |
| TEST_COUNT=${TEST_COUNT:-0} | |
| echo "test_count=$TEST_COUNT" >> "$GITHUB_OUTPUT" | |
| echo "📊 Test count: $TEST_COUNT" | |
| - name: Locate coverage profdata | |
| id: coverage | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # SwiftPM typically writes a default.profdata, but keep a fallback. | |
| PROFDATA=$(find .build -name "default.profdata" -print -quit 2>/dev/null || true) | |
| if [ -z "${PROFDATA}" ]; then | |
| PROFDATA=$(find .build -name "*.profdata" -print -quit 2>/dev/null || true) | |
| fi | |
| if [ -z "${PROFDATA}" ]; then | |
| echo "❌ No coverage profdata found in .build" | |
| find .build -type f -name "*.profdata" -maxdepth 6 2>/dev/null || true | |
| exit 1 | |
| fi | |
| echo "profdata=$PROFDATA" >> "$GITHUB_OUTPUT" | |
| echo "📊 profdata: $PROFDATA" | |
| - name: Locate test binary | |
| id: binary | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # Prefer xctest bundles produced by SwiftPM (works across package names) | |
| TEST_BINARY=$(find .build -path "*.xctest/Contents/MacOS/*" -type f -perm +111 -print -quit 2>/dev/null || true) | |
| # Fallback: any executable ending with Tests inside .build | |
| if [ -z "${TEST_BINARY}" ]; then | |
| TEST_BINARY=$(find .build -type f -perm +111 -name "*Tests" 2>/dev/null | grep -v "\.dSYM" | head -1 || true) | |
| fi | |
| if [ -z "${TEST_BINARY}" ]; then | |
| echo "❌ Test binary not found" | |
| echo "Top executables in .build:" | |
| find .build -type f -perm +111 2>/dev/null | head -20 || true | |
| exit 1 | |
| fi | |
| echo "binary=$TEST_BINARY" >> "$GITHUB_OUTPUT" | |
| echo "🧪 Test binary: $TEST_BINARY" | |
| - name: Generate coverage report | |
| id: cov | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| PROFDATA="${{ steps.coverage.outputs.profdata }}" | |
| TEST_BINARY="${{ steps.binary.outputs.binary }}" | |
| # Detect architecture (llvm-cov requires correct arch on macOS) | |
| ARCH=$(uname -m) | |
| if [ "$ARCH" = "arm64" ]; then | |
| ARCH_FLAG="-arch arm64" | |
| else | |
| ARCH_FLAG="-arch x86_64" | |
| fi | |
| IGNORE_REGEX='.*Tests.*|.*Version\\.swift|.*EKNetworkVersion\\.swift|.*ProgressDelegate\\.swift|.*ProgressSessionManager\\.swift' | |
| echo "📈 Exporting coverage JSON…" | |
| xcrun llvm-cov export \ | |
| $ARCH_FLAG \ | |
| -instr-profile "$PROFDATA" \ | |
| -ignore-filename-regex="$IGNORE_REGEX" \ | |
| -format="json" \ | |
| "$TEST_BINARY" \ | |
| > coverage.json | |
| echo "📄 Generating human-readable report…" | |
| xcrun llvm-cov show \ | |
| $ARCH_FLAG \ | |
| -instr-profile "$PROFDATA" \ | |
| -ignore-filename-regex="$IGNORE_REGEX" \ | |
| -format=text \ | |
| "$TEST_BINARY" \ | |
| > coverage_report.txt | |
| echo "✅ Coverage artifacts generated (coverage.json, coverage_report.txt)" | |
| - name: Calculate coverage | |
| id: calc | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [ ! -s coverage.json ]; then | |
| echo "❌ coverage.json not found or empty" | |
| exit 1 | |
| fi | |
| # Parse llvm-cov JSON via python (available on macOS runners) | |
| python3 - <<'PY' | |
| import json | |
| with open('coverage.json', 'r', encoding='utf-8') as f: | |
| data = json.load(f) | |
| # llvm-cov export JSON structure: data["data"][...]["totals"]["lines"] | |
| totals = data.get('data', [{}])[0].get('totals', {}) | |
| lines = totals.get('lines', {}) | |
| covered = int(lines.get('covered', 0)) | |
| count = int(lines.get('count', 0)) | |
| if count == 0: | |
| raise SystemExit('No line coverage data found (count == 0)') | |
| coverage = (covered * 100.0) / count | |
| print(f"COVERED={covered}") | |
| print(f"TOTAL={count}") | |
| print(f"COVERAGE={coverage:.2f}") | |
| PY | |
| COVERED=$(python3 -c "import json; d=json.load(open('coverage.json')); l=d['data'][0]['totals']['lines']; print(int(l.get('covered',0)))") | |
| TOTAL=$(python3 -c "import json; d=json.load(open('coverage.json')); l=d['data'][0]['totals']['lines']; print(int(l.get('count',0)))") | |
| COVERAGE=$(python3 -c "import json; d=json.load(open('coverage.json')); l=d['data'][0]['totals']['lines']; c=int(l.get('covered',0)); t=int(l.get('count',0)); print(f'{(c*100.0/t):.2f}' if t else '0.00')") | |
| COVERAGE_INT=${COVERAGE%.*} | |
| echo "covered=$COVERED" >> "$GITHUB_OUTPUT" | |
| echo "total=$TOTAL" >> "$GITHUB_OUTPUT" | |
| echo "coverage=$COVERAGE" >> "$GITHUB_OUTPUT" | |
| echo "coverage_int=$COVERAGE_INT" >> "$GITHUB_OUTPUT" | |
| echo "" | |
| echo "✅ Code Coverage Report" | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| echo "Total lines: $TOTAL" | |
| echo "Covered lines: $COVERED" | |
| echo "Coverage: ${COVERAGE}%" | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| - name: Check coverage threshold | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| COVERAGE_INT="${{ steps.calc.outputs.coverage_int }}" | |
| COVERAGE="${{ steps.calc.outputs.coverage }}" | |
| if [ "$COVERAGE_INT" -lt 98 ]; then | |
| echo "❌ Code coverage is ${COVERAGE}%, but required minimum is 98%" | |
| exit 1 | |
| fi | |
| echo "✅ Code coverage is ${COVERAGE}% (meets 98% requirement)" | |
| - name: Upload coverage artifacts | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: coverage | |
| path: | | |
| coverage_report.txt | |
| coverage.json | |
| retention-days: 30 | |
| - name: Create test summary | |
| if: always() | |
| shell: bash | |
| run: | | |
| { | |
| echo "## Test Results" | |
| echo "" | |
| echo "✅ **Tests:** ${{ steps.tests.outputs.test_count }}" | |
| echo "📊 **Coverage:** ${{ steps.calc.outputs.coverage }}% (${{ steps.calc.outputs.covered }}/${{ steps.calc.outputs.total }} lines)" | |
| echo "" | |
| echo "### Coverage Details" | |
| echo "- Total lines: ${{ steps.calc.outputs.total }}" | |
| echo "- Covered lines: ${{ steps.calc.outputs.covered }}" | |
| echo "- Coverage: ${{ steps.calc.outputs.coverage }}%" | |
| } >> "$GITHUB_STEP_SUMMARY" |