Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 87 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,36 @@
# CI Pipeline for Thump (HeartCoach)
# Builds iOS and watchOS targets and runs unit tests.
# Triggered on push to main and on pull requests.
# Builds iOS and watchOS targets, runs ALL unit tests, and validates
# that every test function in source is actually executed.
#
# Runs on: push to main, ALL pull requests (any branch).
# Branch protection requires this workflow to pass before merge.

name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
# Minimum number of test functions that must execute.
# Update this when adding new tests. Current count: 833
MIN_TEST_COUNT: 1050
IOS_SIMULATOR: "platform=iOS Simulator,name=iPhone 16 Pro"
WATCH_SIMULATOR: "platform=watchOS Simulator,name=Apple Watch Series 10 (46mm)"

jobs:
build-and-test:
name: Build & Test
runs-on: macos-15
steps:
- uses: actions/checkout@v4

# ── Cache SPM packages ──────────────────────────────────
# -- Cache SPM packages --
- name: Cache SPM packages
uses: actions/cache@v4
with:
Expand All @@ -32,7 +41,7 @@ jobs:
restore-keys: |
spm-${{ runner.os }}-

# ── Install tools ───────────────────────────────────────
# -- Install tools --
- name: Install XcodeGen
run: brew install xcodegen

Expand All @@ -47,15 +56,34 @@ jobs:
cd apps/HeartCoach
xcodegen generate

# ── Build iOS ───────────────────────────────────────────
# -- Validate all test files are in project --
- name: Validate test file inclusion
run: |
cd apps/HeartCoach
MISSING=0
for f in $(find Tests -name "*Tests.swift" -exec basename {} \;); do
if ! grep -q "$f" Thump.xcodeproj/project.pbxproj 2>/dev/null; then
echo "::error::Test file $f exists on disk but is NOT in ThumpCoreTests target"
MISSING=$((MISSING + 1))
fi
done
if [ "$MISSING" -gt 0 ]; then
echo "::error::$MISSING test file(s) missing from Xcode project. Add them to project.yml."
exit 1
fi
echo "All test files are in the project"

# -- Build iOS --
- name: Build iOS
env:
SIMULATOR: ${{ env.IOS_SIMULATOR }}
run: |
set -o pipefail
cd apps/HeartCoach
xcodebuild build \
-project Thump.xcodeproj \
-scheme Thump \
-destination 'platform=iOS Simulator,name=iPhone 16 Pro' \
-destination "platform=iOS Simulator,name=iPhone 16 Pro" \
-configuration Debug \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_REQUIRED=NO \
Expand All @@ -64,15 +92,15 @@ jobs:
if: failure()
run: grep -A2 "error:" /tmp/xcodebuild-ios.log || echo "No error lines found"

# ── Build watchOS ───────────────────────────────────────
# -- Build watchOS --
- name: Build watchOS
run: |
set -o pipefail
cd apps/HeartCoach
xcodebuild build \
-project Thump.xcodeproj \
-scheme ThumpWatch \
-destination 'platform=watchOS Simulator,name=Apple Watch Series 10 (46mm)' \
-destination "platform=watchOS Simulator,name=Apple Watch Series 10 (46mm)" \
-configuration Debug \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_REQUIRED=NO \
Expand All @@ -81,30 +109,76 @@ jobs:
if: failure()
run: grep -A2 "error:" /tmp/xcodebuild-watchos.log 2>/dev/null || echo "No watchOS error log"

# ── Run unit tests ──────────────────────────────────────
# -- Run ALL unit tests --
- name: Run Tests
run: |
set -o pipefail
cd apps/HeartCoach
xcodebuild test \
-project Thump.xcodeproj \
-scheme Thump \
-destination 'platform=iOS Simulator,name=iPhone 16 Pro' \
-destination "platform=iOS Simulator,name=iPhone 16 Pro" \
-enableCodeCoverage YES \
-resultBundlePath TestResults.xcresult \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_REQUIRED=NO \
2>&1 | tee /tmp/xcodebuild-test.log | xcpretty
- name: Show Test Errors (if failed)
if: failure()
run: grep -A2 "error:" /tmp/xcodebuild-test.log 2>/dev/null || echo "No test error log"
run: |
echo "### Test Failures" >> "$GITHUB_STEP_SUMMARY"
grep -E "error:|FAIL|failed" /tmp/xcodebuild-test.log | head -30 >> "$GITHUB_STEP_SUMMARY"
grep -A2 "error:" /tmp/xcodebuild-test.log 2>/dev/null || echo "No test error log"

# -- Validate test count to catch orphaned tests --
- name: Validate test count
if: success()
env:
MIN_TESTS: ${{ env.MIN_TEST_COUNT }}
run: |
cd apps/HeartCoach

# Count executed tests from xcodebuild output
EXECUTED=$(grep "Test Case.*started" /tmp/xcodebuild-test.log | wc -l | tr -d ' ')

# Count defined test functions in source
DEFINED=$(grep -rn "func test" Tests --include="*.swift" | wc -l | tr -d ' ')

echo "### Test Pipeline Report" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "| Metric | Count |" >> "$GITHUB_STEP_SUMMARY"
echo "|--------|-------|" >> "$GITHUB_STEP_SUMMARY"
echo "| Defined in source | **${DEFINED}** |" >> "$GITHUB_STEP_SUMMARY"
echo "| Executed by Xcode | **${EXECUTED}** |" >> "$GITHUB_STEP_SUMMARY"
echo "| Minimum required | **${MIN_TESTS}** |" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"

# Fail if executed count drops below minimum
if [ "$EXECUTED" -lt "$MIN_TESTS" ]; then
echo "::error::Only ${EXECUTED} tests executed, minimum is ${MIN_TESTS}. Tests may have been excluded or orphaned."
echo "FAILED: ${EXECUTED} tests executed < ${MIN_TESTS} minimum" >> "$GITHUB_STEP_SUMMARY"
exit 1
fi

# Warn if defined > executed (some tests not running)
DIFF=$((DEFINED - EXECUTED))
if [ "$DIFF" -gt 10 ]; then
echo "::warning::${DIFF} test functions defined but not executed. Check for excluded files in project.yml."
echo "WARNING: ${DIFF} tests defined but not executed" >> "$GITHUB_STEP_SUMMARY"
fi

echo "All ${EXECUTED} tests executed (minimum: ${MIN_TESTS})"
echo "PASSED: ${EXECUTED} tests executed" >> "$GITHUB_STEP_SUMMARY"

# ── Coverage report ─────────────────────────────────────
# -- Coverage report --
- name: Extract Code Coverage
if: success()
run: |
cd apps/HeartCoach
echo "### Code Coverage" >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
xcrun xccov view --report TestResults.xcresult | head -30 >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"

- name: Upload Test Results
if: always()
Expand Down
1 change: 1 addition & 0 deletions apps/HeartCoach/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ let package = Package(
"DashboardBuddyIntegrationTests.swift",
"DashboardReadinessIntegrationTests.swift",
"StressViewActionTests.swift",
"SimulatorFallbackAndActionBugTests.swift",
// iOS-only (uses LegalDocument from iOS/Views)
"LegalGateTests.swift",
// Empty MockProfiles dir (files moved to EngineTimeSeries)
Expand Down
Loading
Loading