Skip to content

Feature/errors

Feature/errors #30

Workflow file for this run

name: CI
on:
pull_request:
branches: [ master, develop ]
types: [ opened, synchronize, reopened ]
env:
GO_VERSION: '1.24.2'
jobs:
# Branch validation - ensures PR follows branching model
validate-branch:
name: Validate Branch Model
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Validate branching model
run: |
SOURCE_BRANCH="${{ github.head_ref }}"
TARGET_BRANCH="${{ github.base_ref }}"
echo "Source branch: $SOURCE_BRANCH"
echo "Target branch: $TARGET_BRANCH"
# Define validation function
validate_branch() {
case "$SOURCE_BRANCH" in
enhancement/*)
if [ "$TARGET_BRANCH" != "develop" ]; then
echo "❌ Enhancement branches can only target 'develop', not '$TARGET_BRANCH'"
exit 1
fi
;;
feature/*)
if [ "$TARGET_BRANCH" != "develop" ]; then
echo "❌ Feature branches can only target 'develop', not '$TARGET_BRANCH'"
exit 1
fi
;;
fix/*)
if [ "$TARGET_BRANCH" != "develop" ]; then
echo "❌ Fix branches can only target 'develop', not '$TARGET_BRANCH'"
exit 1
fi
;;
hotfix/*)
if [ "$TARGET_BRANCH" != "master" ]; then
echo "❌ Hotfix branches can only target 'master', not '$TARGET_BRANCH'"
exit 1
fi
;;
release/*)
if [ "$TARGET_BRANCH" != "master" ]; then
echo "❌ Release branches can only target 'master', not '$TARGET_BRANCH'"
exit 1
fi
# Additional check: release branches should come from develop
MERGE_BASE=$(git merge-base origin/develop HEAD)
DEVELOP_HEAD=$(git rev-parse origin/develop)
if [ "$MERGE_BASE" != "$DEVELOP_HEAD" ]; then
echo "❌ Release branches must be created from the latest 'develop' branch"
exit 1
fi
;;
*)
echo "❌ Invalid branch name '$SOURCE_BRANCH'. Must follow pattern:"
echo " - enhancement/name (→ develop)"
echo " - feature/name (→ develop)"
echo " - fix/name (→ develop)"
echo " - hotfix/name (→ master)"
echo " - release/yyyy-mm-dd (develop → master)"
exit 1
;;
esac
echo "✅ Quiver loves your PR: $SOURCE_BRANCH → $TARGET_BRANCH"
}
validate_branch
# Build validation - ensures code compiles
build:
name: Build Validation
runs-on: ubuntu-latest
needs: validate-branch
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}
- name: Cache Go modules
uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Download dependencies
run: make deps
- name: Format check
run: |
make fmt
if [ -n "$(git diff --name-only)" ]; then
echo "❌ Code is not properly formatted. Please run 'make fmt'"
git diff
exit 1
fi
echo "✅ Code formatting is correct"
- name: Vet check
run: make vet
- name: Build application
run: make build
- name: Verify binary
run: |
if [ ! -f "bin/quiver" ]; then
echo "❌ Binary not found after build"
exit 1
fi
echo "✅ Binary built successfully"
ls -la bin/
# Docker-based testing with coverage
test-coverage:
name: Test Coverage (Docker)
runs-on: ubuntu-latest
needs: validate-branch
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Run tests with coverage in Docker
run: |
docker run --rm \
-v ${{ github.workspace }}:/app \
-w /app \
golang:${{ env.GO_VERSION }}-alpine \
sh -c "
apk add --no-cache git make bc gcc musl-dev &&
go mod download &&
CGO_ENABLED=1 go test -race -coverprofile=coverage.out -covermode=atomic ./... &&
go tool cover -func=coverage.out > coverage.txt &&
go tool cover -html=coverage.out -o coverage.html
"
- name: Check overall coverage
run: |
if [ ! -f "coverage.txt" ]; then
echo "❌ Coverage file not found"
exit 1
fi
OVERALL_COVERAGE=$(grep "total:" coverage.txt | awk '{print $3}' | sed 's/%//')
echo "Overall coverage: ${OVERALL_COVERAGE}%"
if [ $(echo "$OVERALL_COVERAGE < 80" | bc -l) -eq 1 ]; then
echo "❌ Overall coverage ${OVERALL_COVERAGE}% is below required 80%"
cat coverage.txt
exit 1
fi
echo "✅ Overall coverage ${OVERALL_COVERAGE}% meets requirement (≥80%)"
- name: Check PR coverage
run: |
# Get list of changed Go files
CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep '\.go$' | grep -v '_test\.go$' || true)
if [ -z "$CHANGED_FILES" ]; then
echo "ℹ️ No Go files changed in this PR"
exit 0
fi
echo "Changed Go files:"
echo "$CHANGED_FILES"
# Calculate coverage for changed files
TOTAL_LINES=0
COVERED_LINES=0
for file in $CHANGED_FILES; do
if [ -f "$file" ]; then
# Get coverage for this specific file
FILE_COVERAGE=$(go tool cover -func=coverage.out | grep "$file" || true)
if [ -n "$FILE_COVERAGE" ]; then
echo "Coverage for $file:"
echo "$FILE_COVERAGE"
# Extract coverage percentage for each function in the file
while IFS= read -r line; do
if [[ $line == *"$file"* ]]; then
FUNC_COVERAGE=$(echo "$line" | awk '{print $3}' | sed 's/%//')
if [ -n "$FUNC_COVERAGE" ] && [ "$FUNC_COVERAGE" != "coverage:" ]; then
TOTAL_LINES=$((TOTAL_LINES + 100))
COVERED_LINES=$((COVERED_LINES + $(echo "$FUNC_COVERAGE * 100 / 100" | bc -l | cut -d. -f1)))
fi
fi
done <<< "$FILE_COVERAGE"
else
echo "⚠️ No coverage data found for $file"
fi
fi
done
if [ $TOTAL_LINES -gt 0 ]; then
PR_COVERAGE=$((COVERED_LINES * 100 / TOTAL_LINES))
echo "PR coverage: ${PR_COVERAGE}%"
if [ $PR_COVERAGE -lt 80 ]; then
echo "❌ PR coverage ${PR_COVERAGE}% is below required 80%"
exit 1
fi
echo "✅ PR coverage ${PR_COVERAGE}% meets requirement (≥80%)"
else
echo "ℹ️ No coverage data to calculate for PR changes"
fi
- name: Upload coverage reports
uses: actions/upload-artifact@v4
with:
name: coverage-reports
path: |
coverage.out
coverage.html
coverage.txt
retention-days: 7
# Security and linting checks
code-quality:
name: Code Quality & Security
runs-on: ubuntu-latest
needs: validate-branch
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}
- name: Cache Go modules
uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Download dependencies
run: make deps
- name: Run linting checks
run: make lint
- name: Run security scan
run: make security
# Final validation - all checks must pass
pr-validation-complete:
name: PR Validation Complete
runs-on: ubuntu-latest
needs: [validate-branch, build, test-coverage, code-quality]
if: always()
steps:
- name: Check all jobs status
run: |
echo "Validation results:"
echo "- Branch validation: ${{ needs.validate-branch.result }}"
echo "- Build validation: ${{ needs.build.result }}"
echo "- Test coverage: ${{ needs.test-coverage.result }}"
echo "- Code quality: ${{ needs.code-quality.result }}"
if [ "${{ needs.validate-branch.result }}" != "success" ] || \
[ "${{ needs.build.result }}" != "success" ] || \
[ "${{ needs.test-coverage.result }}" != "success" ] || \
[ "${{ needs.code-quality.result }}" != "success" ]; then
echo "❌ Some checks failed. Please review and fix the issues."
exit 1
fi
echo "✅ All PR validation checks passed successfully!"
echo ""
echo "📋 Summary:"
echo "- ✅ Branch follows the defined branching model"
echo "- ✅ Code compiles successfully"
echo "- ✅ Overall test coverage ≥ 80%"
echo "- ✅ PR coverage ≥ 80%"
echo "- ✅ Code quality and security checks passed"
echo ""
echo "🎉 This PR is ready for review!"