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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
126 changes: 126 additions & 0 deletions .github/workflows/flakiness-detection.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Flakiness Detection Workflow
# Runs changed tests multiple times to detect flaky/unstable tests
# Last Updated: 2025-11-05

name: Flakiness Detection

on:
pull_request:
paths:
- '**/*_test.go' # Run when test files change
- 'pkg/**/*.go' # Run when production code changes (tests might become flaky)

jobs:
detect-flaky-tests:
runs-on: ubuntu-latest
timeout-minutes: 30

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 2 # Need previous commit for diff

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
cache: true

- name: Get changed test files
id: changed-tests
run: |
# Find all changed test files (both new and modified)
git diff --name-only HEAD~1 HEAD | grep '_test.go$' > changed_tests.txt || true

if [ -s changed_tests.txt ]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "::notice::Found $(wc -l < changed_tests.txt) changed test files"
cat changed_tests.txt
else
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "::notice::No test files changed"
fi

- name: Run changed tests 10 times to detect flakiness
if: steps.changed-tests.outputs.has_changes == 'true'
id: flakiness-check
continue-on-error: true
run: |
# Track failures
FLAKY_TESTS=""
EXIT_CODE=0

while IFS= read -r test_file; do
package_path=$(dirname "$test_file")
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Testing $package_path for flakiness (10 runs with race detector)..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

# Run test 10 times with race detector
if ! go test -count=10 -race -v "./$package_path"; then
echo "::error file=$test_file::Flaky test detected - failed when run multiple times"
FLAKY_TESTS="$FLAKY_TESTS\n- $test_file"
EXIT_CODE=1
else
echo "::notice file=$test_file::Test is stable (passed all 10 runs)"
fi

echo ""
done < changed_tests.txt

if [ $EXIT_CODE -ne 0 ]; then
echo "flaky_tests<<EOF" >> $GITHUB_OUTPUT
echo -e "$FLAKY_TESTS" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
fi

exit $EXIT_CODE

- name: Comment on PR if flaky tests found
if: failure() && steps.flakiness-check.outcome == 'failure'
uses: actions/github-script@v7
with:
script: |
const flakyTests = process.env.FLAKY_TESTS || 'Unknown tests';

const message = `## ⚠️ Flaky Test Detected!

One or more tests failed when run multiple times with the race detector. This indicates non-deterministic behavior that must be fixed before merging.

### Flaky Tests
${flakyTests}

### Common Causes
- **Race conditions**: Use \`-race\` flag to detect data races
- **Timing dependencies**: Replace \`time.Sleep()\` with polling + timeout
- **Map iteration order**: Sort maps before comparing
- **Shared global state**: Ensure proper test isolation
- **Non-deterministic random values**: Use fixed seeds for testing

### How to Fix
1. Run locally with \`go test -count=10 -race ./path/to/package\`
2. Review [Flakiness Prevention Guide](https://github.com/CodeMonkeyCybersecurity/eos/blob/main/INTEGRATION_TESTING.md#flakiness-prevention)
3. Consider quarantining with \`//go:build flaky\` tag if immediate fix isn't possible

### Resources
- [Go Testing Best Practices](https://go.dev/wiki/TestComments)
- [Detecting Flakiness](https://circleci.com/blog/reducing-flaky-test-failures/)
- [Eos Integration Testing Guide](/INTEGRATION_TESTING.md)

**This PR cannot be merged until flakiness is resolved.**`;

await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: message
});
env:
FLAKY_TESTS: ${{ steps.flakiness-check.outputs.flaky_tests }}

- name: Fail workflow if flaky tests detected
if: failure() && steps.flakiness-check.outcome == 'failure'
run: |
echo "::error::Flaky tests detected. See PR comment for details."
exit 1
92 changes: 92 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Eos Pre-Commit Hook Configuration
# Last Updated: 2025-11-05
#
# Installation:
# pip install pre-commit
# pre-commit install
#
# Run manually:
# pre-commit run --all-files
#
# Update hooks:
# pre-commit autoupdate

repos:
# Go-specific hooks from TekWizely (most flexible for Go monorepos)
- repo: https://github.com/TekWizely/pre-commit-golang
rev: v1.0.0-rc.1
hooks:
# Format Go code
- id: go-fmt
name: Format Go code (gofmt)
description: Ensures all Go code is properly formatted

# Organize imports
- id: go-imports
name: Organize imports (goimports)
description: Ensures imports are organized correctly

# Run go vet
- id: go-vet
name: Static analysis (go vet)
description: Runs go vet for static analysis
args: [] # Can add CGO_ENABLED=1 if needed

# Run golangci-lint
- id: golangci-lint
name: Lint (golangci-lint)
description: Runs golangci-lint with project config
args: [--timeout=5m]

# Ensure go.mod and go.sum are tidy
- id: go-mod-tidy
name: Verify go.mod is tidy
description: Ensures go.mod and go.sum are up to date
args: [-v]

# Local hooks for custom checks
- repo: local
hooks:
# Run fast tests (skip integration and E2E)
- id: go-test-fast
name: Run unit tests
entry: go test -race -short -v ./...
language: system
pass_filenames: false
description: Runs fast unit tests with race detector

# Check test coverage
- id: go-coverage-check
name: Check test coverage
entry: bash -c 'go test -coverprofile=coverage.out -covermode=atomic ./... && go run github.com/vladopajic/go-test-coverage/v2@latest --config=.testcoverage.yml'
language: system
pass_filenames: false
description: Ensures test coverage meets thresholds

# Build verification
- id: go-build
name: Verify build
entry: go build -o /tmp/eos-build-precommit ./cmd/
language: system
pass_filenames: false
description: Ensures code compiles successfully

# Verify E2E tests have build tags
- id: verify-e2e-build-tags
name: Verify E2E build tags
entry: bash -c 'for f in test/e2e/*_test.go; do head -1 "$f" | grep -q "//go:build e2e" || { echo "ERROR: $f missing //go:build e2e tag"; exit 1; }; done'
language: system
pass_filenames: false
description: Ensures all E2E tests have proper build tags

# Check for deprecated benchmark pattern
- id: check-benchmark-pattern
name: Check for deprecated benchmarks
entry: bash -c '! git grep -n "for.*b\.N.*{" -- "*_test.go" || { echo "ERROR: Found deprecated benchmark pattern. Use B.Loop() instead of for b.N"; exit 1; }'
language: system
pass_filenames: false
description: Detects deprecated benchmark patterns

# Global settings
fail_fast: false # Run all hooks even if one fails
minimum_pre_commit_version: '2.20.0'
62 changes: 62 additions & 0 deletions .testcoverage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Test Coverage Configuration for Eos
# Last Updated: 2025-11-05
#
# Tool: vladopajic/go-test-coverage
# Docs: https://github.com/vladopajic/go-test-coverage
#
# Usage:
# go test -coverprofile=coverage.out -covermode=atomic ./...
# go-test-coverage --config=.testcoverage.yml

# Coverage thresholds
threshold:
# Overall minimum coverage across all packages
total: 80

# Per-file minimum coverage
file: 70

# Files to exclude from coverage requirements
exclude:
# Generated code (protobuf, codegen, etc.)
- ".*\\.pb\\.go$"
- ".*\\.gen\\.go$"
- ".*_generated\\.go$"

# Mock files
- "mock_.*\\.go$"
- ".*_mock\\.go$"

# Platform compatibility stubs (intentionally minimal)
- ".*_stub\\.go$"

# Test utilities themselves
- "pkg/testutil/.*"
- "test/e2e/framework\\.go$"

# Main functions (hard to test without full binary execution)
- "cmd/.*/main\\.go$"

# Vendor directory (external dependencies)
- "vendor/.*"

# Documentation/examples that don't need coverage
- ".*_example\\.go$"

# Badge configuration (optional - generates coverage badge)
badge:
# File name for coverage badge SVG
file-name: coverage.svg

# Badge styling
badge-color: green # Color when coverage is good

# Output format
output:
format: text # Options: text, github-actions

# Exclusion rules by package
package:
# Example: Exclude entire packages if needed
# exclude:
# - "github.com/CodeMonkeyCybersecurity/eos/pkg/deprecated"
Loading
Loading